From 4495d813b00b3452e97b664080b8c7f27d77197e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bournhonesque?= Date: Thu, 11 Jan 2024 10:22:44 +0100 Subject: [PATCH] feat: add price.price_per field --- ...8ad2f078f3f_add_product_price_per_field.py | 40 +++++++++++++++++++ app/enums.py | 12 +++++- app/models.py | 3 +- app/schemas.py | 17 +++++++- tests/test_api.py | 2 + 5 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 alembic/versions/20240111_0914_a8ad2f078f3f_add_product_price_per_field.py diff --git a/alembic/versions/20240111_0914_a8ad2f078f3f_add_product_price_per_field.py b/alembic/versions/20240111_0914_a8ad2f078f3f_add_product_price_per_field.py new file mode 100644 index 00000000..688ba29b --- /dev/null +++ b/alembic/versions/20240111_0914_a8ad2f078f3f_add_product_price_per_field.py @@ -0,0 +1,40 @@ +"""add product.price_per field + +Revision ID: a8ad2f078f3f +Revises: 007443669adc +Create Date: 2024-01-11 09:14:25.778905 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "a8ad2f078f3f" +down_revision: Union[str, None] = "007443669adc" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "prices", + sa.Column( + "price_per", + sa.String(length=255), + nullable=True, + ), + ) + op.execute( + "UPDATE prices SET price_per = 'KILOGRAM' WHERE price_per IS NULL AND category_tag IS NOT NULL;" + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("prices", "price_per") + # ### end Alembic commands ### diff --git a/app/enums.py b/app/enums.py index afde645e..232a1d1f 100644 --- a/app/enums.py +++ b/app/enums.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import Enum, unique from babel.numbers import list_currencies @@ -17,3 +17,13 @@ class ProofTypeEnum(Enum): PRICE_TAG = "PRICE_TAG" RECEIPT = "RECEIPT" GDPR_REQUEST = "GDPR_REQUEST" + + +@unique +class PricePerEnum(Enum): + """For raw products (fruits, vegetables, etc.), the price is either + per unit or per kilogram. This enum is used to store this information. + """ + + UNIT = "UNIT" + KILOGRAM = "KILOGRAM" diff --git a/app/models.py b/app/models.py index b5cabe0b..817962b2 100644 --- a/app/models.py +++ b/app/models.py @@ -18,7 +18,7 @@ from sqlalchemy_utils.types.choice import ChoiceType from app.db import Base -from app.enums import CurrencyEnum, LocationOSMEnum, ProofTypeEnum +from app.enums import CurrencyEnum, LocationOSMEnum, PricePerEnum, ProofTypeEnum force_auto_coercion() JSONVariant = JSON().with_variant(JSONB(), "postgresql") @@ -108,6 +108,7 @@ class Price(Base): price = Column(Numeric(precision=10, scale=2)) currency = Column(ChoiceType(CurrencyEnum)) + price_per = Column(ChoiceType(PricePerEnum)) location_osm_id = Column(BigInteger, index=True) location_osm_type = Column(ChoiceType(LocationOSMEnum)) diff --git a/app/schemas.py b/app/schemas.py index bc4a629f..8afa338f 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -13,7 +13,7 @@ model_validator, ) -from app.enums import CurrencyEnum, LocationOSMEnum, ProofTypeEnum +from app.enums import CurrencyEnum, LocationOSMEnum, PricePerEnum, ProofTypeEnum from app.models import Price, Product @@ -154,6 +154,14 @@ class PriceCreate(BaseModel): "kilogram or per liter.", examples=["1.99"], ) + price_per: PricePerEnum | None = Field( + default=PricePerEnum.KILOGRAM, + description="""if the price is about a barcode-less product + (if `category_tag` is provided), this field must be set to `KILOGRAM` + or `UNIT` (KILOGRAM by default). + This field is set to null and ignored if `product_code` is provided. + """, + ) currency: CurrencyEnum = Field( description="currency of the price, as a string. " "The currency must be a valid currency code. " @@ -249,6 +257,13 @@ def product_code_and_category_tag_are_exclusive(self): raise ValueError("either `product_code` or `category_tag` must be set") return self + @model_validator(mode="after") + def set_price_per_to_null_if_barcode(self): + """Validator that sets `price_per` to null if `product_code` is set.""" + if self.product_code is not None: + self.price_per = None + return self + class PriceBase(PriceCreate): product_id: int | None diff --git a/tests/test_api.py b/tests/test_api.py index 0f44520f..6da3f1c4 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -142,6 +142,7 @@ def test_create_price_with_category_tag(db_session, user, clean_prices): "labels_tags": ["en:Organic"], "origins_tags": ["en:France"], "date": "2023-12-01", + "price_per": "UNIT", } ) response = client.post( @@ -155,6 +156,7 @@ def test_create_price_with_category_tag(db_session, user, clean_prices): assert json_response.get("labels_tags") == ["en:organic"] assert json_response.get("origins_tags") == ["en:france"] assert json_response.get("date") == "2023-12-01" + assert json_response.get("price_per") == "UNIT" assert "id" not in response.json() db_prices = crud.get_prices(db_session) assert len(db_prices) == 1