Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add price.price_per field #127

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 ###
12 changes: 11 additions & 1 deletion app/enums.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import Enum
from enum import Enum, unique

from babel.numbers import list_currencies

Expand All @@ -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"
3 changes: 2 additions & 1 deletion app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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))
Expand Down
17 changes: 16 additions & 1 deletion app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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. "
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down
Loading