diff --git a/alembic/versions/20240111_1031_fffca9fb2990_add_price_price_without_discount_field.py b/alembic/versions/20240111_1031_fffca9fb2990_add_price_price_without_discount_field.py new file mode 100644 index 00000000..c7a471d5 --- /dev/null +++ b/alembic/versions/20240111_1031_fffca9fb2990_add_price_price_without_discount_field.py @@ -0,0 +1,35 @@ +"""add price_price_without_discount field + +Revision ID: fffca9fb2990 +Revises: 6492fd03aab5 +Create Date: 2024-01-11 10:31:00.992969 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "fffca9fb2990" +down_revision: Union[str, None] = "6492fd03aab5" +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_without_discount", sa.Numeric(precision=10, scale=2), nullable=True + ), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("prices", "price_without_discount") + # ### end Alembic commands ### diff --git a/app/models.py b/app/models.py index bd41ff60..20858a90 100644 --- a/app/models.py +++ b/app/models.py @@ -108,6 +108,7 @@ class Price(Base): product: Mapped[Product] = relationship(back_populates="prices") price = Column(Numeric(precision=10, scale=2)) + price_without_discount = Column(Numeric(precision=10, scale=2), nullable=True) currency = Column(ChoiceType(CurrencyEnum)) price_per = Column(ChoiceType(PricePerEnum)) diff --git a/app/schemas.py b/app/schemas.py index 3be6f86f..c74ba778 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -152,11 +152,15 @@ class PriceCreate(BaseModel): ) price: float = Field( gt=0, - description="price of the product, without its currency, taxes included. " - "If the price is about a barcode-less product, it must be the price per " - "kilogram or per liter.", + description="price of the product, without its currency, taxes included.", examples=["1.99"], ) + price_without_discount: float | None = Field( + default=None, + description="price of the product without discount, without its currency, taxes included. " + "If the product is not discounted, this field must be null. ", + examples=["2.99"], + ) price_per: PricePerEnum | None = Field( default=PricePerEnum.KILOGRAM, description="""if the price is about a barcode-less product @@ -267,6 +271,16 @@ def set_price_per_to_null_if_barcode(self): self.price_per = None return self + @model_validator(mode="after") + def check_price_without_discout(self): + """Check that `price_without_discount` is greater than `price`.""" + if self.price_without_discount is not None: + if self.price_without_discount <= self.price: + raise ValueError( + "`price_without_discount` must be greater than `price`" + ) + return self + class PriceBase(PriceCreate): product_id: int | None diff --git a/tests/test_api.py b/tests/test_api.py index 6da3f1c4..4e035232 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -75,6 +75,7 @@ def override_get_db(): product_name="PATE NOCCIOLATA BIO 700G", # category="en:tomatoes", price=3.5, + price_without_discount=4.5, currency="EUR", location_osm_id=123, location_osm_type="NODE", diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py index 4f2fd3a7..36b28d60 100644 --- a/tests/unit/test_schema.py +++ b/tests/unit/test_schema.py @@ -85,3 +85,18 @@ def test_simple_price_with_product_code_and_labels_tags_raise(self): currency="EUR", date="2021-01-01", ) + + def test_price_without_discount_raise(self): + with pytest.raises( + pydantic.ValidationError, + match="`price_without_discount` must be greater than `price`", + ): + PriceCreateWithValidation( + product_code="5414661000456", + location_osm_id=123, + location_osm_type=LocationOSMEnum.NODE, + price=1.99, + price_without_discount=1.50, + currency="EUR", + date="2021-01-01", + )