diff --git a/core/management/commands/populate_more.py b/core/management/commands/populate_more.py index 38803dc8..76115c70 100644 --- a/core/management/commands/populate_more.py +++ b/core/management/commands/populate_more.py @@ -350,7 +350,6 @@ class Command(BaseCommand): date=make_aware( self.faker.date_time_between(customer.since, localdate()) ), - is_validated=True, ) ) sales.extend(this_customer_sales) diff --git a/core/tests/test_user.py b/core/tests/test_user.py index 6d99d2a2..b90e626e 100644 --- a/core/tests/test_user.py +++ b/core/tests/test_user.py @@ -187,11 +187,7 @@ class TestFilterInactive(TestCase): time_inactive = time_active - timedelta(days=3) counter, seller = baker.make(Counter), baker.make(User) sale_recipe = Recipe( - Selling, - counter=counter, - club=counter.club, - seller=seller, - is_validated=True, + Selling, counter=counter, club=counter.club, seller=seller, unit_price=0 ) cls.users = [ diff --git a/counter/apps.py b/counter/apps.py index 9348cd1c..54e7ad4c 100644 --- a/counter/apps.py +++ b/counter/apps.py @@ -24,12 +24,6 @@ from django.apps import AppConfig from django.utils.translation import gettext_lazy as _ -PAYMENT_METHOD = [ - ("CHECK", _("Check")), - ("CASH", _("Cash")), - ("CARD", _("Credit card")), -] - class CounterConfig(AppConfig): name = "counter" diff --git a/counter/forms.py b/counter/forms.py index 87d2c954..ed47232a 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -136,7 +136,10 @@ class GetUserForm(forms.Form): class RefillForm(forms.ModelForm): - allowed_refilling_methods = ["CASH", "CARD"] + allowed_refilling_methods = [ + Refilling.PaymentMethod.CASH, + Refilling.PaymentMethod.CARD, + ] error_css_class = "error" required_css_class = "required" @@ -146,7 +149,7 @@ class RefillForm(forms.ModelForm): class Meta: model = Refilling - fields = ["amount", "payment_method", "bank"] + fields = ["amount", "payment_method"] widgets = {"payment_method": forms.RadioSelect} def __init__(self, *args, **kwargs): @@ -160,9 +163,6 @@ class RefillForm(forms.ModelForm): if self.fields["payment_method"].initial not in self.allowed_refilling_methods: self.fields["payment_method"].initial = self.allowed_refilling_methods[0] - if "CHECK" not in self.allowed_refilling_methods: - del self.fields["bank"] - class CounterEditForm(forms.ModelForm): class Meta: diff --git a/counter/management/commands/dump_accounts.py b/counter/management/commands/dump_accounts.py index b25cb93c..de8d113a 100644 --- a/counter/management/commands/dump_accounts.py +++ b/counter/management/commands/dump_accounts.py @@ -119,7 +119,6 @@ class Command(BaseCommand): quantity=1, unit_price=account.amount, date=now(), - is_validated=True, ) for account in accounts ] diff --git a/counter/migrations/0035_remove_selling_is_validated_and_more.py b/counter/migrations/0035_remove_selling_is_validated_and_more.py new file mode 100644 index 00000000..a8a2e115 --- /dev/null +++ b/counter/migrations/0035_remove_selling_is_validated_and_more.py @@ -0,0 +1,84 @@ +# Generated by Django 5.2.8 on 2025-11-19 17:59 + +from django.db import migrations, models +from django.db.migrations.state import StateApps +from django.db.models import Case, When + + +def migrate_selling_payment_method(apps: StateApps, schema_editor): + # 0 <=> SITH_ACCOUNT is the default value, so no need to migrate it + Selling = apps.get_model("counter", "Selling") + Selling.objects.filter(payment_method_str="CARD").update(payment_method=1) + + +def migrate_selling_payment_method_reverse(apps: StateApps, schema_editor): + Selling = apps.get_model("counter", "Selling") + Selling.objects.filter(payment_method=1).update(payment_method_str="CARD") + + +def migrate_refilling_payment_method(apps: StateApps, schema_editor): + Refilling = apps.get_model("counter", "Refilling") + Refilling.objects.update( + payment_method=Case( + When(payment_method_str="CARD", then=0), + When(payment_method_str="CASH", then=1), + When(payment_method_str="CHECK", then=2), + ) + ) + + +def migrate_refilling_payment_method_reverse(apps: StateApps, schema_editor): + Refilling = apps.get_model("counter", "Refilling") + Refilling.objects.update( + payment_method_str=Case( + When(payment_method=0, then="CARD"), + When(payment_method=1, then="CASH"), + When(payment_method=2, then="CHECK"), + ) + ) + + +class Migration(migrations.Migration): + dependencies = [("counter", "0034_alter_selling_date_selling_date_month_idx")] + + operations = [ + migrations.RemoveField(model_name="selling", name="is_validated"), + migrations.RemoveField(model_name="refilling", name="is_validated"), + migrations.RemoveField(model_name="refilling", name="bank"), + migrations.RenameField( + model_name="selling", + old_name="payment_method", + new_name="payment_method_str", + ), + migrations.AddField( + model_name="selling", + name="payment_method", + field=models.PositiveSmallIntegerField( + choices=[(0, "Sith account"), (1, "Credit card")], + default=0, + verbose_name="payment method", + ), + ), + migrations.RunPython( + migrate_selling_payment_method, migrate_selling_payment_method_reverse + ), + migrations.RemoveField(model_name="selling", name="payment_method_str"), + migrations.RenameField( + model_name="refilling", + old_name="payment_method", + new_name="payment_method_str", + ), + migrations.AddField( + model_name="refilling", + name="payment_method", + field=models.PositiveSmallIntegerField( + choices=[(0, "Credit card"), (1, "Cash"), (2, "Check")], + default=0, + verbose_name="payment method", + ), + ), + migrations.RunPython( + migrate_refilling_payment_method, migrate_refilling_payment_method_reverse + ), + migrations.RemoveField(model_name="refilling", name="payment_method_str"), + ] diff --git a/counter/models.py b/counter/models.py index f98f3e44..996f22e6 100644 --- a/counter/models.py +++ b/counter/models.py @@ -44,7 +44,6 @@ from club.models import Club from core.fields import ResizedImageField from core.models import Group, Notification, User from core.utils import get_start_of_semester -from counter.apps import PAYMENT_METHOD from counter.fields import CurrencyField from subscription.models import Subscription @@ -80,7 +79,8 @@ class CustomerQuerySet(models.QuerySet): ) money_out = Subquery( Selling.objects.filter( - customer=OuterRef("pk"), payment_method="SITH_ACCOUNT" + customer=OuterRef("pk"), + payment_method=Selling.PaymentMethod.SITH_ACCOUNT, ) .values("customer_id") .annotate(res=Sum(F("unit_price") * F("quantity"), default=0)) @@ -731,6 +731,11 @@ class RefillingQuerySet(models.QuerySet): class Refilling(models.Model): """Handle the refilling.""" + class PaymentMethod(models.IntegerChoices): + CARD = 0, _("Credit card") + CASH = 1, _("Cash") + CHECK = 2, _("Check") + counter = models.ForeignKey( Counter, related_name="refillings", blank=False, on_delete=models.CASCADE ) @@ -745,16 +750,9 @@ class Refilling(models.Model): Customer, related_name="refillings", blank=False, on_delete=models.CASCADE ) date = models.DateTimeField(_("date")) - payment_method = models.CharField( - _("payment method"), - max_length=255, - choices=PAYMENT_METHOD, - default="CARD", + payment_method = models.PositiveSmallIntegerField( + _("payment method"), choices=PaymentMethod, default=PaymentMethod.CARD ) - bank = models.CharField( - _("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER" - ) - is_validated = models.BooleanField(_("is validated"), default=False) objects = RefillingQuerySet.as_manager() @@ -771,10 +769,9 @@ class Refilling(models.Model): if not self.date: self.date = timezone.now() self.full_clean() - if not self.is_validated: + if self._state.adding: self.customer.amount += self.amount self.customer.save() - self.is_validated = True if self.customer.user.preferences.notify_on_refill: Notification( user=self.customer.user, @@ -814,6 +811,10 @@ class SellingQuerySet(models.QuerySet): class Selling(models.Model): """Handle the sellings.""" + class PaymentMethod(models.IntegerChoices): + SITH_ACCOUNT = 0, _("Sith account") + CARD = 1, _("Credit card") + # We make sure that sellings have a way begger label than any product name is allowed to label = models.CharField(_("label"), max_length=128) product = models.ForeignKey( @@ -850,13 +851,9 @@ class Selling(models.Model): on_delete=models.SET_NULL, ) date = models.DateTimeField(_("date"), db_index=True) - payment_method = models.CharField( - _("payment method"), - max_length=255, - choices=[("SITH_ACCOUNT", _("Sith account")), ("CARD", _("Credit card"))], - default="SITH_ACCOUNT", + payment_method = models.PositiveSmallIntegerField( + _("payment method"), choices=PaymentMethod, default=PaymentMethod.SITH_ACCOUNT ) - is_validated = models.BooleanField(_("is validated"), default=False) objects = SellingQuerySet.as_manager() @@ -875,10 +872,12 @@ class Selling(models.Model): if not self.date: self.date = timezone.now() self.full_clean() - if not self.is_validated: + if ( + self._state.adding + and self.payment_method == self.PaymentMethod.SITH_ACCOUNT + ): self.customer.amount -= self.quantity * self.unit_price self.customer.save(allow_negative=allow_negative) - self.is_validated = True user = self.customer.user if user.was_subscribed: if ( @@ -948,7 +947,9 @@ class Selling(models.Model): def is_owned_by(self, user: User) -> bool: if user.is_anonymous: return False - return self.payment_method != "CARD" and user.is_owner(self.counter) + return self.payment_method != self.PaymentMethod.CARD and user.is_owner( + self.counter + ) def can_be_viewed_by(self, user: User) -> bool: if ( @@ -958,7 +959,7 @@ class Selling(models.Model): return user == self.customer.user def delete(self, *args, **kwargs): - if self.payment_method == "SITH_ACCOUNT": + if self.payment_method == Selling.PaymentMethod.SITH_ACCOUNT: self.customer.amount += self.quantity * self.unit_price self.customer.save() super().delete(*args, **kwargs) diff --git a/counter/tests/test_account_dump.py b/counter/tests/test_account_dump.py index db074efb..0d41576e 100644 --- a/counter/tests/test_account_dump.py +++ b/counter/tests/test_account_dump.py @@ -116,7 +116,6 @@ class TestAccountDumpCommand(TestAccountDump): operation: Selling = customer.buyings.order_by("date").last() assert operation.unit_price == initial_amount assert operation.counter_id == settings.SITH_COUNTER_ACCOUNT_DUMP_ID - assert operation.is_validated is True dump = customer.dumps.last() assert dump.dump_operation == operation diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index af0e07ad..4f3e2df6 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -53,7 +53,7 @@ def set_age(user: User, age: int): def force_refill_user(user: User, amount: Decimal | int): - baker.make(Refilling, amount=amount, customer=user.customer, is_validated=False) + baker.make(Refilling, amount=amount, customer=user.customer) class TestFullClickBase(TestCase): @@ -115,18 +115,10 @@ class TestRefilling(TestFullClickBase): ) -> HttpResponse: used_client = client if client is not None else self.client return used_client.post( - reverse( - "counter:refilling_create", - kwargs={"customer_id": user.pk}, - ), - { - "amount": str(amount), - "payment_method": "CASH", - "bank": "OTHER", - }, + reverse("counter:refilling_create", kwargs={"customer_id": user.pk}), + {"amount": str(amount), "payment_method": Refilling.PaymentMethod.CASH}, HTTP_REFERER=reverse( - "counter:click", - kwargs={"counter_id": counter.id, "user_id": user.pk}, + "counter:click", kwargs={"counter_id": counter.id, "user_id": user.pk} ), ) @@ -149,11 +141,7 @@ class TestRefilling(TestFullClickBase): "counter:refilling_create", kwargs={"customer_id": self.customer.pk}, ), - { - "amount": "10", - "payment_method": "CASH", - "bank": "OTHER", - }, + {"amount": "10", "payment_method": "CASH"}, ) self.client.force_login(self.club_admin) diff --git a/counter/tests/test_customer.py b/counter/tests/test_customer.py index 3676a701..311e48ed 100644 --- a/counter/tests/test_customer.py +++ b/counter/tests/test_customer.py @@ -298,7 +298,6 @@ def test_update_balance(): _quantity=len(customers), unit_price=10, quantity=1, - payment_method="SITH_ACCOUNT", _save_related=True, ), *sale_recipe.prepare( @@ -306,14 +305,12 @@ def test_update_balance(): _quantity=3, unit_price=5, quantity=2, - payment_method="SITH_ACCOUNT", _save_related=True, ), sale_recipe.prepare( customer=customers[4], quantity=1, unit_price=50, - payment_method="SITH_ACCOUNT", _save_related=True, ), *sale_recipe.prepare( @@ -324,7 +321,7 @@ def test_update_balance(): _quantity=len(customers), unit_price=50, quantity=1, - payment_method="CARD", + payment_method=Selling.PaymentMethod.CARD, _save_related=True, ), ] diff --git a/counter/views/invoice.py b/counter/views/invoice.py index dce34f0d..8cb846f0 100644 --- a/counter/views/invoice.py +++ b/counter/views/invoice.py @@ -68,15 +68,13 @@ class InvoiceCallView( end_date = start_date + relativedelta(months=1) kwargs["sum_cb"] = Refilling.objects.filter( - payment_method="CARD", - is_validated=True, + payment_method=Refilling.PaymentMethod.CARD, date__gte=start_date, date__lte=end_date, ).aggregate(res=Sum("amount", default=0))["res"] kwargs["sum_cb"] += ( Selling.objects.filter( - payment_method="CARD", - is_validated=True, + payment_method=Selling.PaymentMethod.CARD, date__gte=start_date, date__lte=end_date, ) diff --git a/eboutic/models.py b/eboutic/models.py index f0016073..5a37f70f 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -110,7 +110,9 @@ class Basket(models.Model): )["total"] ) - def generate_sales(self, counter, seller: User, payment_method: str): + def generate_sales( + self, counter, seller: User, payment_method: Selling.PaymentMethod + ): """Generate a list of sold items corresponding to the items of this basket WITHOUT saving them NOR deleting the basket. @@ -251,8 +253,7 @@ class Invoice(models.Model): customer=customer, operator=self.user, amount=i.product_unit_price * i.quantity, - payment_method="CARD", - bank="OTHER", + payment_method=Refilling.PaymentMethod.CARD, date=self.date, ) new.save() @@ -267,8 +268,7 @@ class Invoice(models.Model): customer=customer, unit_price=i.product_unit_price, quantity=i.quantity, - payment_method="CARD", - is_validated=True, + payment_method=Selling.PaymentMethod.CARD, date=self.date, ) new.save() diff --git a/eboutic/tests/test_basket.py b/eboutic/tests/test_basket.py index ff7f2077..539502cc 100644 --- a/eboutic/tests/test_basket.py +++ b/eboutic/tests/test_basket.py @@ -108,12 +108,22 @@ def test_eboutic_basket_expiry( client.force_login(customer.user) - for date in sellings: + if sellings: sale_recipe.make( - customer=customer, counter=eboutic, date=date, is_validated=True + customer=customer, + counter=eboutic, + date=iter(sellings), + _quantity=len(sellings), + _bulk_create=True, + ) + if refillings: + refill_recipe.make( + customer=customer, + counter=eboutic, + date=iter(refillings), + _quantity=len(refillings), + _bulk_create=True, ) - for date in refillings: - refill_recipe.make(customer=customer, counter=eboutic, date=date) assert ( f'x-data="basket({int(expected.timestamp() * 1000) if expected else "null"})"' diff --git a/eboutic/tests/test_payment.py b/eboutic/tests/test_payment.py index dd619169..813e82c8 100644 --- a/eboutic/tests/test_payment.py +++ b/eboutic/tests/test_payment.py @@ -114,13 +114,13 @@ class TestPaymentSith(TestPaymentBase): "quantity" ) assert len(sellings) == 2 - assert sellings[0].payment_method == "SITH_ACCOUNT" + assert sellings[0].payment_method == Selling.PaymentMethod.SITH_ACCOUNT assert sellings[0].quantity == 1 assert sellings[0].unit_price == self.snack.selling_price assert sellings[0].counter.type == "EBOUTIC" assert sellings[0].product == self.snack - assert sellings[1].payment_method == "SITH_ACCOUNT" + assert sellings[1].payment_method == Selling.PaymentMethod.SITH_ACCOUNT assert sellings[1].quantity == 2 assert sellings[1].unit_price == self.beer.selling_price assert sellings[1].counter.type == "EBOUTIC" @@ -198,13 +198,13 @@ class TestPaymentCard(TestPaymentBase): "quantity" ) assert len(sellings) == 2 - assert sellings[0].payment_method == "CARD" + assert sellings[0].payment_method == Selling.PaymentMethod.CARD assert sellings[0].quantity == 1 assert sellings[0].unit_price == self.snack.selling_price assert sellings[0].counter.type == "EBOUTIC" assert sellings[0].product == self.snack - assert sellings[1].payment_method == "CARD" + assert sellings[1].payment_method == Selling.PaymentMethod.CARD assert sellings[1].quantity == 2 assert sellings[1].unit_price == self.beer.selling_price assert sellings[1].counter.type == "EBOUTIC" diff --git a/eboutic/views.py b/eboutic/views.py index fa25df66..f48c86c1 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -275,7 +275,9 @@ class EbouticPayWithSith(CanViewMixin, SingleObjectMixin, View): return redirect("eboutic:payment_result", "failure") eboutic = get_eboutic() - sales = basket.generate_sales(eboutic, basket.user, "SITH_ACCOUNT") + sales = basket.generate_sales( + eboutic, basket.user, Selling.PaymentMethod.SITH_ACCOUNT + ) try: with transaction.atomic(): # Selling.save has some important business logic in it. diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 4cfa038e..6be36d8f 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-12 21:44+0100\n" +"POT-Creation-Date: 2025-11-19 21:00+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -2928,18 +2928,6 @@ msgstr "Photos" msgid "Account" msgstr "Compte" -#: counter/apps.py sith/settings.py -msgid "Check" -msgstr "Chèque" - -#: counter/apps.py sith/settings.py -msgid "Cash" -msgstr "Espèces" - -#: counter/apps.py counter/models.py sith/settings.py -msgid "Credit card" -msgstr "Carte bancaire" - #: counter/apps.py counter/models.py msgid "counter" msgstr "comptoir" @@ -3152,22 +3140,30 @@ msgstr "vendeurs" msgid "token" msgstr "jeton" +#: counter/models.py sith/settings.py +msgid "Credit card" +msgstr "Carte bancaire" + +#: counter/models.py sith/settings.py +msgid "Cash" +msgstr "Espèces" + +#: counter/models.py sith/settings.py +msgid "Check" +msgstr "Chèque" + #: counter/models.py subscription/models.py msgid "payment method" msgstr "méthode de paiement" -#: counter/models.py -msgid "bank" -msgstr "banque" - -#: counter/models.py -msgid "is validated" -msgstr "est validé" - #: counter/models.py msgid "refilling" msgstr "rechargement" +#: counter/models.py +msgid "Sith account" +msgstr "Compte utilisateur" + #: counter/models.py eboutic/models.py msgid "unit price" msgstr "prix unitaire" @@ -3176,10 +3172,6 @@ msgstr "prix unitaire" msgid "quantity" msgstr "quantité" -#: counter/models.py -msgid "Sith account" -msgstr "Compte utilisateur" - #: counter/models.py msgid "selling" msgstr "vente" @@ -3332,6 +3324,10 @@ msgid "" "“%(value)s” value has the correct format (YYYY-MM) but it is an invalid date." msgstr "La valeur « %(value)s » a le bon format, mais est une date invalide." +#: counter/models.py +msgid "is validated" +msgstr "est validé" + #: counter/models.py msgid "invoice date" msgstr "date de la facture" diff --git a/rootplace/tests/test_merge_users.py b/rootplace/tests/test_merge_users.py index 294e2bae..63c406cd 100644 --- a/rootplace/tests/test_merge_users.py +++ b/rootplace/tests/test_merge_users.py @@ -114,7 +114,6 @@ class TestMergeUser(TestCase): seller=self.root, unit_price=2, quantity=2, - payment_method="SITH_ACCOUNT", ).save() Selling( label="barbar", @@ -125,7 +124,6 @@ class TestMergeUser(TestCase): seller=self.root, unit_price=2, quantity=4, - payment_method="SITH_ACCOUNT", ).save() today = localtime(now()).date() # both subscriptions began last month and shall end in 5 months @@ -197,7 +195,6 @@ class TestMergeUser(TestCase): seller=self.root, unit_price=2, quantity=4, - payment_method="SITH_ACCOUNT", ).save() data = {"user1": self.to_keep.id, "user2": self.to_delete.id} res = self.client.post(reverse("rootplace:merge"), data) @@ -225,7 +222,6 @@ class TestMergeUser(TestCase): seller=self.root, unit_price=2, quantity=4, - payment_method="SITH_ACCOUNT", ).save() data = {"user1": self.to_keep.id, "user2": self.to_delete.id} res = self.client.post(reverse("rootplace:merge"), data) diff --git a/sith/settings.py b/sith/settings.py index 30f02c46..6a266eba 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -440,19 +440,6 @@ SITH_SUBSCRIPTION_LOCATIONS = [ SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] -SITH_COUNTER_BANK = [ - ("OTHER", "Autre"), - ("SOCIETE-GENERALE", "Société générale"), - ("BANQUE-POPULAIRE", "Banque populaire"), - ("BNP", "BNP"), - ("CAISSE-EPARGNE", "Caisse d'épargne"), - ("CIC", "CIC"), - ("CREDIT-AGRICOLE", "Crédit Agricole"), - ("CREDIT-MUTUEL", "Credit Mutuel"), - ("CREDIT-LYONNAIS", "Credit Lyonnais"), - ("LA-POSTE", "La Poste"), -] - SITH_PEDAGOGY_UV_TYPE = [ ("FREE", _("Free")), ("CS", _("CS")), diff --git a/subscription/views.py b/subscription/views.py index 035f9035..fb4cde07 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -24,7 +24,6 @@ from django.views.generic import CreateView, DetailView, TemplateView from django.views.generic.edit import FormView from core.views.group import PermissionGroupsUpdateView -from counter.apps import PAYMENT_METHOD from subscription.forms import ( SelectionDateForm, SubscriptionExistingUserForm, @@ -129,6 +128,6 @@ class SubscriptionsStatsView(FormView): subscription_end__gte=self.end_date, subscription_start__lte=self.start_date ) kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS - kwargs["payment_types"] = PAYMENT_METHOD + kwargs["payment_types"] = settings.SITH_SUBSCRIPTION_PAYMENT_METHOD kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS return kwargs