diff --git a/insalan/payment/admin.py b/insalan/payment/admin.py index 22c89cd5..1ea83abc 100644 --- a/insalan/payment/admin.py +++ b/insalan/payment/admin.py @@ -8,7 +8,7 @@ from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ -from .models import Product, Transaction, Payment, TransactionStatus +from .models import Product, Transaction, Payment, TransactionStatus, Discount class ProductAdmin(admin.ModelAdmin): @@ -123,3 +123,15 @@ def has_delete_permission(self, _request, _obj=None): admin.site.register(Transaction, TransactionAdmin) + +class DiscountAdmin(admin.ModelAdmin): + """ + Admin handler for Discounts + """ + + list_display = ("id", "discount", "user", "product", "used") + search_fields = ["id", "discount", "user", "product", "reason"] + + + +admin.site.register(Discount, DiscountAdmin) diff --git a/insalan/payment/models.py b/insalan/payment/models.py index c1f3c5c1..f64aa8de 100644 --- a/insalan/payment/models.py +++ b/insalan/payment/models.py @@ -85,6 +85,12 @@ def can_be_bought_now(self) -> bool: """Returns whether or not the product can be bought now""" return self.available_from <= timezone.now() <= self.available_until + def __str__(self): + """ + Return the name of the product + """ + return str(self.name) + class Payment(models.Model): """ @@ -161,6 +167,11 @@ class Meta: decimal_places=2, verbose_name=_("Montant"), ) + discounts = models.ManyToManyField( + "Discount", + blank=True, + verbose_name=_("Réductions") + ) @staticmethod def new(**data): @@ -326,6 +337,10 @@ def validate_transaction(self): self.payment_status = TransactionStatus.SUCCEEDED self.last_modification_date = timezone.make_aware(datetime.now()) + # For each discount, mark it as used + for discount in self.discounts.all(): + discount.use() + self.save() logger.info("Transaction %s succeeded", self.id) self.run_success_hooks() @@ -378,3 +393,47 @@ class Meta: null=True, ) count = models.IntegerField(default=1, editable=True, verbose_name=_("Quantité")) + + +class Discount(models.Model): + """ + A discount is a temporary reduction of the price of a product + + A discount is tied to a user, a product and can be used only once + """ + + class Meta: + """Meta information""" + + verbose_name = _("Réduction") + verbose_name_plural = _("Réductions") + + id: int + user = models.ForeignKey( + User, null=True, on_delete=models.SET_NULL, verbose_name=_("Utilisateur") + ) + product = models.ForeignKey( + Product, null=True, on_delete=models.SET_NULL, verbose_name=_("Produit") + ) + discount = models.DecimalField( + null=False, max_digits=5, decimal_places=2, verbose_name=_("Réduction") + ) + reason = models.CharField(max_length=200, verbose_name=_("Motif")) + creation_date = models.DateTimeField( + verbose_name=_("Date de création"), + editable=False, + default=timezone.now + ) + used = models.BooleanField(default=False, verbose_name=_("Utilisé")) + used_date = models.DateTimeField( + verbose_name=_("Date d'utilisation"), + editable=False, + null=True, + blank=True + ) + + def use(self): + """Use the discount""" + self.used = True + self.used_date = timezone.make_aware(datetime.now()) + self.save() diff --git a/insalan/payment/views.py b/insalan/payment/views.py index 7414f837..02c853ae 100644 --- a/insalan/payment/views.py +++ b/insalan/payment/views.py @@ -19,7 +19,7 @@ import insalan.settings as app_settings import insalan.payment.serializers as serializers -from .models import Transaction, TransactionStatus, Product, Payment +from .models import Transaction, TransactionStatus, Product, Payment, Discount from .tokens import Token logger = logging.getLogger(__name__) @@ -424,10 +424,21 @@ def create(self, request): {"err": _("Préconditions de paiement non remplies")}, status=status.HTTP_400_BAD_REQUEST, ) + + amount = transaction_obj.amount + + # If the user has a discount for some products, apply them + for product in transaction_obj.products.all(): + discounts = Discount.objects.filter(user=payer, product=product) + if discounts.exists(): + discount = discounts.first() + amount = transaction_obj.amount - discount.discount + # Add the discount to the transaction object + transaction_obj.discounts.add(discount) # helloasso intent helloasso_amount = int( - transaction_obj.amount * 100 + amount * 100 ) # helloasso reads prices in cents intent_body = { "totalAmount": helloasso_amount, diff --git a/insalan/tournament/models/tournament.py b/insalan/tournament/models/tournament.py index 4b8e9d94..5dd6df9b 100644 --- a/insalan/tournament/models/tournament.py +++ b/insalan/tournament/models/tournament.py @@ -195,7 +195,7 @@ def save(self, *args, **kwargs): if self.player_online_product is None: prod = Product.objects.create( price=self.player_price_online, - name=_(f"Place {self.name} Joueur en ligne"), + name=_(f"Place {self.name} Joueur en ligne - {self.event.name}"), desc=_(f"Inscription au tournoi {self.name} joueur"), category=ProductCategory.REGISTRATION_PLAYER, associated_tournament=self, @@ -212,7 +212,7 @@ def save(self, *args, **kwargs): if self.manager_online_product is None: prod = Product.objects.create( price=self.manager_price_online, - name=_(f"Place {self.name} manager en ligne"), + name=_(f"Place {self.name} manager en ligne - {self.event.name}"), desc=_(f"Inscription au tournoi {self.name} manager"), category=ProductCategory.REGISTRATION_MANAGER, associated_tournament=self, @@ -229,7 +229,7 @@ def save(self, *args, **kwargs): if self.substitute_online_product is None: prod = Product.objects.create( price=self.substitute_price_online, - name=_(f"Place {self.name} remplaçant en ligne"), + name=_(f"Place {self.name} remplaçant en ligne - {self.event.name}"), desc=_(f"Inscription au tournoi {self.name} remplaçant"), category=ProductCategory.REGISTRATION_SUBSTITUTE, associated_tournament=self,