|
10 | 10 | from django.db import models
|
11 | 11 | from django.utils.translation import gettext_lazy as _
|
12 | 12 | from django.utils import timezone
|
| 13 | +from django.core.validators import MinValueValidator |
13 | 14 | from rest_framework.serializers import ValidationError
|
14 | 15 |
|
15 | 16 | import insalan.settings as app_settings
|
@@ -85,6 +86,12 @@ def can_be_bought_now(self) -> bool:
|
85 | 86 | """Returns whether or not the product can be bought now"""
|
86 | 87 | return self.available_from <= timezone.now() <= self.available_until
|
87 | 88 |
|
| 89 | + def __str__(self): |
| 90 | + """ |
| 91 | + Return the name of the product |
| 92 | + """ |
| 93 | + return str(self.name) |
| 94 | + |
88 | 95 |
|
89 | 96 | class Payment(models.Model):
|
90 | 97 | """
|
@@ -161,6 +168,11 @@ class Meta:
|
161 | 168 | decimal_places=2,
|
162 | 169 | verbose_name=_("Montant"),
|
163 | 170 | )
|
| 171 | + discounts = models.ManyToManyField( |
| 172 | + "Discount", |
| 173 | + blank=True, |
| 174 | + verbose_name=_("Réductions") |
| 175 | + ) |
164 | 176 |
|
165 | 177 | @staticmethod
|
166 | 178 | def new(**data):
|
@@ -326,6 +338,10 @@ def validate_transaction(self):
|
326 | 338 |
|
327 | 339 | self.payment_status = TransactionStatus.SUCCEEDED
|
328 | 340 | self.last_modification_date = timezone.make_aware(datetime.now())
|
| 341 | + # For each discount, mark it as used |
| 342 | + for discount in self.discounts.all(): |
| 343 | + discount.use() |
| 344 | + |
329 | 345 | self.save()
|
330 | 346 | logger.info("Transaction %s succeeded", self.id)
|
331 | 347 | self.run_success_hooks()
|
@@ -378,3 +394,57 @@ class Meta:
|
378 | 394 | null=True,
|
379 | 395 | )
|
380 | 396 | count = models.IntegerField(default=1, editable=True, verbose_name=_("Quantité"))
|
| 397 | + |
| 398 | +# Discount |
| 399 | + |
| 400 | +class DiscountAlreadyUsedError(Exception): |
| 401 | + """Error raised when trying to use an already used discount""" |
| 402 | + |
| 403 | +class Discount(models.Model): |
| 404 | + """ |
| 405 | + A discount is a temporary reduction of the price of a product |
| 406 | + |
| 407 | + A discount is tied to a user, a product and can be used only once |
| 408 | + """ |
| 409 | + |
| 410 | + class Meta: |
| 411 | + """Meta information""" |
| 412 | + |
| 413 | + verbose_name = _("Réduction") |
| 414 | + verbose_name_plural = _("Réductions") |
| 415 | + |
| 416 | + id: int |
| 417 | + user = models.ForeignKey( |
| 418 | + User, null=True, on_delete=models.SET_NULL, verbose_name=_("Utilisateur") |
| 419 | + ) |
| 420 | + product = models.ForeignKey( |
| 421 | + Product, null=True, on_delete=models.SET_NULL, verbose_name=_("Produit") |
| 422 | + ) |
| 423 | + discount = models.DecimalField( |
| 424 | + null=False, |
| 425 | + max_digits=5, |
| 426 | + decimal_places=2, |
| 427 | + verbose_name=_("Réduction"), |
| 428 | + validators=[MinValueValidator(Decimal('0.01'))] |
| 429 | + ) |
| 430 | + reason = models.CharField(max_length=200, verbose_name=_("Motif")) |
| 431 | + creation_date = models.DateTimeField( |
| 432 | + verbose_name=_("Date de création"), |
| 433 | + editable=False, |
| 434 | + default=timezone.now |
| 435 | + ) |
| 436 | + used = models.BooleanField(default=False, verbose_name=_("Utilisé")) |
| 437 | + used_date = models.DateTimeField( |
| 438 | + verbose_name=_("Date d'utilisation"), |
| 439 | + editable=False, |
| 440 | + null=True, |
| 441 | + blank=True |
| 442 | + ) |
| 443 | + |
| 444 | + def use(self): |
| 445 | + """Use the discount""" |
| 446 | + if self.used: |
| 447 | + raise DiscountAlreadyUsedError("Discount already used") |
| 448 | + self.used = True |
| 449 | + self.used_date = timezone.make_aware(datetime.now()) |
| 450 | + self.save() |
0 commit comments