diff --git a/open_prices/api/prices/tests.py b/open_prices/api/prices/tests.py index 74e93674..aab5aa2f 100644 --- a/open_prices/api/prices/tests.py +++ b/open_prices/api/prices/tests.py @@ -104,6 +104,7 @@ def setUpTestData(cls): ) cls.user_price = PriceFactory( **PRICE_8001505005707, + receipt_quantity=2, proof_id=cls.user_proof.id, owner=cls.user_session.user.user_id, ) @@ -377,6 +378,7 @@ def test_price_create(self): self.assertEqual(response.data["price"], 15.00) self.assertEqual(response.data["currency"], "EUR") self.assertEqual(response.data["date"], "2024-01-01") + self.assertEqual(response.data["receipt_quantity"], 1) # default self.assertTrue("source" not in response.data) self.assertEqual(response.data["owner"], self.user_session.user.user_id) # with proof, product & location diff --git a/open_prices/prices/migrations/0003_price_receipt_quantity.py b/open_prices/prices/migrations/0003_price_receipt_quantity.py new file mode 100644 index 00000000..aff713d0 --- /dev/null +++ b/open_prices/prices/migrations/0003_price_receipt_quantity.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1 on 2024-10-18 20:51 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("prices", "0002_alter_price_currency"), + ] + + operations = [ + migrations.AddField( + model_name="price", + name="receipt_quantity", + field=models.PositiveIntegerField( + blank=True, + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="Receipt's price quantity (user input)", + ), + ), + ] diff --git a/open_prices/prices/models.py b/open_prices/prices/models.py index 3af72456..94ed1cc6 100644 --- a/open_prices/prices/models.py +++ b/open_prices/prices/models.py @@ -56,6 +56,7 @@ class Price(models.Model): "price_per", "currency", "date", + "receipt_quantity", ] CREATE_FIELDS = UPDATE_FIELDS + [ "product_code", @@ -133,6 +134,13 @@ class Price(models.Model): related_name="prices", ) + receipt_quantity = models.PositiveIntegerField( + verbose_name="Receipt's price quantity (user input)", + validators=[MinValueValidator(1)], + blank=True, + null=True, + ) + owner = models.CharField(blank=True, null=True) source = models.CharField(blank=True, null=True) @@ -328,6 +336,7 @@ def clean(self, *args, **kwargs): # proof rules # - proof must exist and belong to the price owner # - some proof fields should be the same as the price fields + # - receipt_quantity can only be set for receipts (default to 1) if self.proof_id: from open_prices.proofs.models import Proof @@ -361,6 +370,16 @@ def clean(self, *args, **kwargs): "proof", f"Proof {PROOF_FIELD} ({proof_field_value}) does not match the price {PROOF_FIELD} ({price_field_value})", ) + if proof.type in proof_constants.TYPE_SHOPPING_SESSION_LIST: + if not self.receipt_quantity: + self.receipt_quantity = 1 + else: + if self.receipt_quantity is not None: + validation_errors = utils.add_validation_error( + validation_errors, + "receipt_quantity", + f"Can only be set if proof type in {proof_constants.TYPE_SHOPPING_SESSION_LIST}", + ) # return if bool(validation_errors): raise ValidationError(validation_errors) diff --git a/open_prices/prices/tests.py b/open_prices/prices/tests.py index 7fd83e92..0a52f271 100644 --- a/open_prices/prices/tests.py +++ b/open_prices/prices/tests.py @@ -278,7 +278,7 @@ def test_price_location_validation(self): def test_price_proof_validation(self): self.user_session = SessionFactory() - self.user_proof = ProofFactory( + self.user_proof_receipt = ProofFactory( type=proof_constants.TYPE_RECEIPT, location_osm_id=652825274, location_osm_type=location_constants.OSM_TYPE_NODE, @@ -288,69 +288,94 @@ def test_price_proof_validation(self): ) self.proof_2 = ProofFactory() # proof not set - PriceFactory(proof=None, owner=self.user_proof.owner) + PriceFactory(proof=None, owner=self.user_proof_receipt.owner) # same price & proof fields PriceFactory( - proof=self.user_proof, - location_osm_id=self.user_proof.location_osm_id, - location_osm_type=self.user_proof.location_osm_type, - date=self.user_proof.date, - currency=self.user_proof.currency, - owner=self.user_proof.owner, + proof=self.user_proof_receipt, + location_osm_id=self.user_proof_receipt.location_osm_id, + location_osm_type=self.user_proof_receipt.location_osm_type, + date=self.user_proof_receipt.date, + currency=self.user_proof_receipt.currency, + owner=self.user_proof_receipt.owner, ) # different price & proof owner self.assertRaises( ValidationError, PriceFactory, proof=self.proof_2, # different - location_osm_id=self.user_proof.location_osm_id, - location_osm_type=self.user_proof.location_osm_type, - date=self.user_proof.date, - currency=self.user_proof.currency, - owner=self.user_proof.owner, + location_osm_id=self.user_proof_receipt.location_osm_id, + location_osm_type=self.user_proof_receipt.location_osm_type, + date=self.user_proof_receipt.date, + currency=self.user_proof_receipt.currency, + owner=self.user_proof_receipt.owner, ) # proof location_osm_id & location_osm_type self.assertRaises( ValidationError, PriceFactory, - proof=self.user_proof, + proof=self.user_proof_receipt, location_osm_id=5, # different location_osm_id - location_osm_type=self.user_proof.location_osm_type, - date=self.user_proof.date, - currency=self.user_proof.currency, - owner=self.user_proof.owner, + location_osm_type=self.user_proof_receipt.location_osm_type, + date=self.user_proof_receipt.date, + currency=self.user_proof_receipt.currency, + owner=self.user_proof_receipt.owner, ) self.assertRaises( ValidationError, PriceFactory, - proof=self.user_proof, - location_osm_id=self.user_proof.location_osm_id, + proof=self.user_proof_receipt, + location_osm_id=self.user_proof_receipt.location_osm_id, location_osm_type="WAY", # different location_osm_type - date=self.user_proof.date, - currency=self.user_proof.currency, - owner=self.user_proof.owner, + date=self.user_proof_receipt.date, + currency=self.user_proof_receipt.currency, + owner=self.user_proof_receipt.owner, ) # proof date & currency self.assertRaises( ValidationError, PriceFactory, - proof=self.user_proof, - location_osm_id=self.user_proof.location_osm_id, - location_osm_type=self.user_proof.location_osm_type, + proof=self.user_proof_receipt, + location_osm_id=self.user_proof_receipt.location_osm_id, + location_osm_type=self.user_proof_receipt.location_osm_type, date="2024-07-01", # different date - currency=self.user_proof.currency, - owner=self.user_proof.owner, + currency=self.user_proof_receipt.currency, + owner=self.user_proof_receipt.owner, ) self.assertRaises( ValidationError, PriceFactory, - proof=self.user_proof, - location_osm_id=self.user_proof.location_osm_id, - location_osm_type=self.user_proof.location_osm_type, - date=self.user_proof.date, + proof=self.user_proof_receipt, + location_osm_id=self.user_proof_receipt.location_osm_id, + location_osm_type=self.user_proof_receipt.location_osm_type, + date=self.user_proof_receipt.date, currency="USD", # different currency - owner=self.user_proof.owner, - ) + owner=self.user_proof_receipt.owner, + ) + # receipt_quantity + for RECEIPT_QUANTITY_NOT_OK in [-5, 0]: + with self.subTest(RECEIPT_QUANTITY_NOT_OK=RECEIPT_QUANTITY_NOT_OK): + self.assertRaises( + ValidationError, + PriceFactory, + proof=self.user_proof_receipt, + location_osm_id=self.user_proof_receipt.location_osm_id, + location_osm_type=self.user_proof_receipt.location_osm_type, + date=self.user_proof_receipt.date, + currency=self.user_proof_receipt.currency, + owner=self.user_proof_receipt.owner, + receipt_quantity=RECEIPT_QUANTITY_NOT_OK, + ) + for RECEIPT_QUANTITY_OK in [None, 1, 2]: + with self.subTest(RECEIPT_QUANTITY_OK=RECEIPT_QUANTITY_OK): + PriceFactory( + proof=self.user_proof_receipt, + location_osm_id=self.user_proof_receipt.location_osm_id, + location_osm_type=self.user_proof_receipt.location_osm_type, + date=self.user_proof_receipt.date, + currency=self.user_proof_receipt.currency, + owner=self.user_proof_receipt.owner, + receipt_quantity=RECEIPT_QUANTITY_OK, + ) def test_price_count_increment(self): user_session = SessionFactory() diff --git a/open_prices/proofs/constants.py b/open_prices/proofs/constants.py index a6610aeb..89f17579 100644 --- a/open_prices/proofs/constants.py +++ b/open_prices/proofs/constants.py @@ -8,4 +8,5 @@ # 1 proof = 1 shop + 1 date TYPE_SINGLE_SHOP_LIST = [TYPE_PRICE_TAG, TYPE_RECEIPT, TYPE_SHOP_IMPORT] +TYPE_SHOPPING_SESSION_LIST = [TYPE_RECEIPT, TYPE_GDPR_REQUEST] TYPE_MULTIPLE_SHOP_LIST = [TYPE_GDPR_REQUEST] diff --git a/open_prices/proofs/models.py b/open_prices/proofs/models.py index 6465b5d7..c03bf2fe 100644 --- a/open_prices/proofs/models.py +++ b/open_prices/proofs/models.py @@ -28,6 +28,9 @@ def has_type_shop_import(self): def has_type_single_shop(self): return self.filter(type__in=proof_constants.TYPE_SINGLE_SHOP_LIST) + def has_type_shopping_session(self): + return self.filter(type=proof_constants.TYPE_SHOPPING_SESSION_LIST) + def has_prices(self): return self.filter(price_count__gt=0)