Skip to content

Commit

Permalink
feat(Prices): new discount_type field with a list of choices (#711)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphodn authored Feb 20, 2025
1 parent c177457 commit 521dc19
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 6 deletions.
1 change: 1 addition & 0 deletions open_prices/api/prices/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Meta:
"location_id",
"price",
"price_is_discounted",
"discount_type",
"currency",
"date",
"proof_id",
Expand Down
18 changes: 18 additions & 0 deletions open_prices/prices/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@
TYPE_CHOICES = [(key, key) for key in TYPE_LIST]


DISCOUNT_TYPE_QUANTITY = "QUANTITY" # example: buy 1 get 1 free
DISCOUNT_TYPE_SALE = "SALE" # example: 50% off
DISCOUNT_TYPE_SEASONAL = "SEASONAL" # example: Christmas sale
DISCOUNT_TYPE_LOYALTY_PROGRAM = "LOYALTY_PROGRAM" # example: 10% off for members
DISCOUNT_TYPE_EXPIRES_SOON = "EXPIRES_SOON" # example: 30% off expiring soon
DISCOUNT_TYPE_PICK_IT_YOURSELF = "PICK_IT_YOURSELF" # example: 5% off for pick-up
DISCOUNT_TYPE_OTHER = "OTHER"
DISCOUNT_TYPE_LIST = [
DISCOUNT_TYPE_QUANTITY,
DISCOUNT_TYPE_SALE,
DISCOUNT_TYPE_SEASONAL,
DISCOUNT_TYPE_LOYALTY_PROGRAM,
DISCOUNT_TYPE_EXPIRES_SOON,
DISCOUNT_TYPE_PICK_IT_YOURSELF,
DISCOUNT_TYPE_OTHER,
]
DISCOUNT_TYPE_CHOICES = [(key, key) for key in DISCOUNT_TYPE_LIST]

"""
PRICE_PER
For raw products (TYPE_CATEGORY) (fruits, vegetables, etc.),
Expand Down
35 changes: 35 additions & 0 deletions open_prices/prices/migrations/0006_price_discount_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 5.1.4 on 2025-02-19 18:06

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("prices", "0005_alter_price_receipt_quantity"),
]

operations = [
migrations.AddField(
model_name="price",
name="discount_type",
field=models.CharField(
blank=True,
choices=[
("QUANTITY", "QUANTITY"),
("SALE", "SALE"),
("SEASONAL", "SEASONAL"),
("LOYALTY_PROGRAM", "LOYALTY_PROGRAM"),
("EXPIRES_SOON", "EXPIRES_SOON"),
("PICK_IT_YOURSELF", "PICK_IT_YOURSELF"),
("OTHER", "OTHER"),
],
max_length=20,
null=True,
),
),
migrations.AlterField(
model_name="price",
name="price_is_discounted",
field=models.BooleanField(default=False),
),
]
25 changes: 19 additions & 6 deletions open_prices/prices/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Price(models.Model):
"price",
"price_is_discounted",
"price_without_discount",
"discount_type",
"price_per",
"currency",
"date",
Expand Down Expand Up @@ -116,16 +117,21 @@ class Price(models.Model):
blank=True,
null=True,
)
price_is_discounted = models.BooleanField(
default=False, blank=True, null=True
) # TODO: remove default=False
price_is_discounted = models.BooleanField(default=False)
price_without_discount = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(decimal.Decimal(0))],
blank=True,
null=True,
)
discount_type = models.CharField(
max_length=20,
choices=price_constants.DISCOUNT_TYPE_CHOICES,
blank=True,
null=True,
)

price_per = models.CharField(
max_length=10,
choices=price_constants.PRICE_PER_CHOICES,
Expand Down Expand Up @@ -323,7 +329,7 @@ def clean(self, *args, **kwargs):
# - price_is_discounted must be set if price_without_discount is set
# - price_without_discount must be greater or equal to price
# - price_per should be set if category_tag is set
# - date should have the right format & not be in the future
# - discount_type can only be set if price_is_discounted is True
if self.price in [None, "true", "false", "none", "null"]:
validation_errors = utils.add_validation_error(
validation_errors,
Expand All @@ -348,6 +354,13 @@ def clean(self, *args, **kwargs):
"price_without_discount",
"Should be greater than `price`",
)
if self.discount_type:
if not self.price_is_discounted:
validation_errors = utils.add_validation_error(
validation_errors,
"discount_type",
"Should not be set if `price_is_discounted` is False",
)
if self.product_code:
if self.price_per:
validation_errors = utils.add_validation_error(
Expand All @@ -362,6 +375,8 @@ def clean(self, *args, **kwargs):
"price_per",
"Should be set if `category_tag` is filled",
)
# date rules
# - date should have the right format & not be in the future
if self.date:
if type(self.date) is str:
validation_errors = utils.add_validation_error(
Expand Down Expand Up @@ -421,7 +436,6 @@ def clean(self, *args, **kwargs):
"location",
f"Location {LOCATION_FIELD} ({location_field_value}) does not match the price {LOCATION_FIELD} ({price_field_value})",
)

else:
if self.location_osm_id:
if not self.location_osm_type:
Expand Down Expand Up @@ -459,7 +473,6 @@ def clean(self, *args, **kwargs):
"proof",
"Proof not found",
)

if proof:
if (
proof.owner != self.owner
Expand Down
22 changes: 22 additions & 0 deletions open_prices/prices/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,28 @@ def test_price_discount_validation(self):
price_is_discounted=True,
price_without_discount=5,
)
# discount_type
price_not_discounted = PriceFactory(price=3)
self.assertEqual(price_not_discounted.price_is_discounted, False)
self.assertEqual(price_not_discounted.discount_type, None)
price_discounted_1 = PriceFactory(
price=3,
price_is_discounted=True,
discount_type=price_constants.DISCOUNT_TYPE_QUANTITY,
)
self.assertEqual(
price_discounted_1.discount_type, price_constants.DISCOUNT_TYPE_QUANTITY
)
price_discounted_2 = PriceFactory(
price=3, price_is_discounted=True, discount_type=None
)
self.assertEqual(price_discounted_2.discount_type, None)
self.assertRaises(
ValidationError,
PriceFactory,
price=10,
discount_type=price_constants.DISCOUNT_TYPE_QUANTITY,
)

def test_price_currency_validation(self):
for CURRENCY_OK in ["EUR", "USD"]:
Expand Down

0 comments on commit 521dc19

Please sign in to comment.