diff --git a/app/modules/amap/cruds_amap.py b/app/modules/amap/cruds_amap.py index f0a13b1732..1a697f57d6 100644 --- a/app/modules/amap/cruds_amap.py +++ b/app/modules/amap/cruds_amap.py @@ -2,7 +2,7 @@ import logging from collections.abc import Sequence -from datetime import date +from datetime import datetime from sqlalchemy import delete, select, update from sqlalchemy.ext.asyncio import AsyncSession @@ -118,16 +118,6 @@ async def get_delivery_by_id( return result.scalars().first() -async def is_there_a_delivery_on(db: AsyncSession, delivery_date: date) -> bool: - result = await db.execute( - select(models_amap.Delivery).where( - models_amap.Delivery.delivery_date == delivery_date, - models_amap.Delivery.status != DeliveryStatusType.archived, - ), - ) - return result.scalars().all() != [] - - async def create_delivery( delivery: schemas_amap.DeliveryComplete, db: AsyncSession, @@ -248,9 +238,11 @@ async def get_products_of_order( async def add_order_to_delivery( db: AsyncSession, order: schemas_amap.OrderComplete, + delivery: models_amap.Delivery, ): db.add( models_amap.Order( + delivery=delivery, **order.model_dump(exclude={"products_ids", "products_quantity"}), ), ) @@ -358,7 +350,7 @@ async def add_cash(db: AsyncSession, user_id: str, amount: float): await db.execute( update(models_amap.Cash) .where(models_amap.Cash.user_id == user_id) - .values(user_id=balance.user_id, balance=balance.balance + amount), + .values(balance=balance.balance + amount), ) await db.flush() @@ -372,7 +364,21 @@ async def remove_cash(db: AsyncSession, user_id: str, amount: float): await db.execute( update(models_amap.Cash) .where(models_amap.Cash.user_id == user_id) - .values(user_id=balance.user_id, balance=balance.balance - amount), + .values(balance=balance.balance - amount), + ) + await db.flush() + + +async def update_last_ordering_date(db: AsyncSession, user_id: str, date: datetime): + result = await db.execute( + select(models_amap.Cash).where(models_amap.Cash.user_id == user_id), + ) + balance = result.scalars().first() + if balance is not None: + await db.execute( + update(models_amap.Cash) + .where(models_amap.Cash.user_id == user_id) + .values(last_order_date=date), ) await db.flush() diff --git a/app/modules/amap/endpoints_amap.py b/app/modules/amap/endpoints_amap.py index 62542cdd67..ddce51acb8 100644 --- a/app/modules/amap/endpoints_amap.py +++ b/app/modules/amap/endpoints_amap.py @@ -196,14 +196,6 @@ async def create_delivery( status=DeliveryStatusType.creation, **delivery.model_dump(), ) - if await cruds_amap.is_there_a_delivery_on( - db=db, - delivery_date=db_delivery.delivery_date, - ): - raise HTTPException( - status_code=400, - detail="There is already a delivery planned that day.", - ) return await cruds_amap.create_delivery(delivery=db_delivery, db=db) @@ -359,7 +351,14 @@ async def get_orders_from_delivery( schemas_amap.ProductQuantity(**product.__dict__) for product in order_content ] - res.append(schemas_amap.OrderReturn(productsdetail=products, **order.__dict__)) + res.append( + schemas_amap.OrderReturn( + productsdetail=products, + delivery_date=delivery.delivery_date, + delivery_name=delivery.name, + **order.__dict__, + ), + ) return res @@ -384,7 +383,12 @@ async def get_order_by_id( raise HTTPException(status_code=404, detail="Delivery not found") products = await cruds_amap.get_products_of_order(db=db, order_id=order_id) - return schemas_amap.OrderReturn(productsdetail=products, **order.__dict__) + return schemas_amap.OrderReturn( + productsdetail=products, + delivery_name=order.delivery.name, + delivery_date=order.delivery.delivery_date, + **order.__dict__, + ) @module.router.post( @@ -443,7 +447,6 @@ async def add_order_to_delievery( order_id=order_id, amount=amount, ordering_date=ordering_date, - delivery_date=delivery.delivery_date, **order.model_dump(), ) balance: models_amap.Cash | None = await cruds_amap.get_cash_by_id( @@ -453,12 +456,10 @@ async def add_order_to_delievery( # If the balance does not exist, we create a new one with a balance of 0 if not balance: - new_cash_db = schemas_amap.CashDB( + balance = models_amap.Cash( balance=0, user_id=order.user_id, - ) - balance = models_amap.Cash( - **new_cash_db.model_dump(), + last_order_date=ordering_date, ) await cruds_amap.create_cash_of_user( cash=balance, @@ -481,6 +482,7 @@ async def add_order_to_delievery( await cruds_amap.add_order_to_delivery( order=db_order, db=db, + delivery=delivery, ) await cruds_amap.remove_cash( db=db, @@ -488,14 +490,28 @@ async def add_order_to_delievery( amount=amount, ) + await cruds_amap.update_last_ordering_date( + db=db, + user_id=order.user_id, + date=ordering_date, + ) + orderret = await cruds_amap.get_order_by_id(order_id=db_order.order_id, db=db) productsret = await cruds_amap.get_products_of_order(db=db, order_id=order_id) hyperion_amap_logger.info( f"Add_order_to_delivery: An order has been created for user {order.user_id} for an amount of {amount}€. ({request_id})", ) - return schemas_amap.OrderReturn(productsdetail=productsret, **orderret.__dict__) + if orderret is None: + raise HTTPException(status_code=404, detail="added order not found") + + return schemas_amap.OrderReturn( + productsdetail=productsret, + delivery_name=orderret.delivery.name, + delivery_date=orderret.delivery.delivery_date, + **orderret.__dict__, + ) finally: locker_set(redis_client=redis_client, key=redis_key, lock=False) @@ -607,6 +623,12 @@ async def edit_order_from_delivery( user_id=previous_order.user_id, amount=previous_amount, ) + date = datetime.now(UTC) + await cruds_amap.update_last_ordering_date( + db=db, + user_id=previous_order.user_id, + date=date, + ) hyperion_amap_logger.info( f"Edit_order: Order {order_id} has been edited for user {db_order.user_id}. Amount was {previous_amount}€, is now {amount}€. ({request_id})", ) @@ -835,6 +857,7 @@ async def get_cash_by_id( balance=0, user_id=user_id, user=schemas_users.CoreUserSimple(**user_db.__dict__), + last_order_date=datetime.now(UTC), ) return cash @@ -870,7 +893,11 @@ async def create_cash_of_user( detail="This user already has a cash.", ) - cash_db = models_amap.Cash(user_id=user_id, balance=cash.balance) + cash_db = models_amap.Cash( + user_id=user_id, + balance=cash.balance, + last_order_date=datetime.now(UTC), + ) await cruds_amap.create_cash_of_user( cash=cash_db, @@ -967,7 +994,16 @@ async def get_orders_of_user( db=db, order_id=order.order_id, ) - res.append(schemas_amap.OrderReturn(productsdetail=products, **order.__dict__)) + if order is None: + raise HTTPException(status_code=404, detail="at least one order not found") + res.append( + schemas_amap.OrderReturn( + productsdetail=products, + delivery_date=order.delivery.delivery_date, + delivery_name=order.delivery.name, + **order.__dict__, + ), + ) return res diff --git a/app/modules/amap/factory_amap.py b/app/modules/amap/factory_amap.py index bde9f78eef..626795ec33 100644 --- a/app/modules/amap/factory_amap.py +++ b/app/modules/amap/factory_amap.py @@ -45,6 +45,7 @@ async def create_delivery(cls, db: AsyncSession): db=db, delivery=schemas_amap.DeliveryComplete( id=str(uuid.uuid4()), + name="Première livraison", status=DeliveryStatusType.orderable, delivery_date=(datetime.now(UTC) + timedelta(days=8)).date(), products_ids=[product.id for product in products], @@ -55,6 +56,7 @@ async def create_delivery(cls, db: AsyncSession): db=db, delivery=schemas_amap.DeliveryComplete( id=str(uuid.uuid4()), + name="Deuxième livraison", status=DeliveryStatusType.orderable, delivery_date=(datetime.now(UTC) + timedelta(days=1)).date(), products_ids=[product.id for product in products], @@ -68,6 +70,7 @@ async def create_cash_of_user(cls, db: AsyncSession): cash=models_amap.Cash( user_id=CoreUsersFactory.demo_users_id[0], balance=100, + last_order_date=datetime.now(UTC), ), ) diff --git a/app/modules/amap/models_amap.py b/app/modules/amap/models_amap.py index 855e75d2e0..43c31e2a4f 100644 --- a/app/modules/amap/models_amap.py +++ b/app/modules/amap/models_amap.py @@ -52,12 +52,16 @@ class Delivery(Base): __tablename__ = "amap_delivery" id: Mapped[str] = mapped_column(primary_key=True, index=True) + name: Mapped[str] = mapped_column(index=True, unique=False) delivery_date: Mapped[date] = mapped_column( unique=False, index=True, ) status: Mapped[DeliveryStatusType] = mapped_column(String) - orders: Mapped[list["Order"]] = relationship("Order", init=False) + orders: Mapped[list["Order"]] = relationship( + init=False, + back_populates="delivery", + ) products: Mapped[list[Product]] = relationship( "Product", secondary="amap_delivery_content", @@ -79,7 +83,10 @@ class Order(Base): amount: Mapped[float] collection_slot: Mapped[AmapSlotType] ordering_date: Mapped[datetime] - delivery_date: Mapped[date] + delivery: Mapped["Delivery"] = relationship( + lazy="joined", + back_populates="orders", + ) user: Mapped[CoreUser] = relationship( "CoreUser", init=False, @@ -100,6 +107,7 @@ class Cash(Base): primary_key=True, ) balance: Mapped[float] + last_order_date: Mapped[datetime] user: Mapped[CoreUser] = relationship("CoreUser", init=False) diff --git a/app/modules/amap/schemas_amap.py b/app/modules/amap/schemas_amap.py index 26224ffd51..4ee811b1e1 100644 --- a/app/modules/amap/schemas_amap.py +++ b/app/modules/amap/schemas_amap.py @@ -47,6 +47,7 @@ class ProductQuantity(BaseModel): class DeliveryBase(BaseModel): """Base schema for AMAP deliveries""" + name: str delivery_date: date products_ids: list[str] = [] @@ -58,6 +59,7 @@ class DeliveryComplete(DeliveryBase): class DeliveryUpdate(BaseModel): + name: str | None = None delivery_date: date | None = None @@ -77,13 +79,13 @@ class OrderComplete(OrderBase): order_id: str amount: float ordering_date: datetime - delivery_date: date model_config = ConfigDict(from_attributes=True) class OrderReturn(BaseModel): user: CoreUserSimple delivery_id: str + delivery_name: str productsdetail: Sequence[ProductQuantity] collection_slot: AmapSlotType order_id: str @@ -101,6 +103,7 @@ class OrderEdit(BaseModel): class DeliveryReturn(BaseModel): + name: str delivery_date: date products: list[ProductComplete] = [] id: str @@ -121,10 +124,7 @@ class CashBase(BaseModel): class CashComplete(CashBase): user: CoreUserSimple - - -class CashDB(CashBase): - user_id: str + last_order_date: datetime class CashEdit(BaseModel): diff --git a/migrations/versions/44-amap_delivery_names.py b/migrations/versions/44-amap_delivery_names.py new file mode 100644 index 0000000000..a6cfd6f448 --- /dev/null +++ b/migrations/versions/44-amap_delivery_names.py @@ -0,0 +1,78 @@ +"""empty message + +Create Date: 2025-10-21 19:53:38.521697 +""" + +from collections.abc import Sequence +from datetime import UTC, datetime +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pytest_alembic import MigrationContext + +import sqlalchemy as sa +from alembic import op + +from app.types.sqlalchemy import TZDateTime + +# revision identifiers, used by Alembic. +revision: str = "9fc3dc926600" +down_revision: str | None = "d1079d6b8e6b" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.add_column("amap_delivery", sa.Column("name", sa.String(), nullable=True)) + op.execute("UPDATE amap_delivery SET name = ''") + op.alter_column("amap_delivery", "name", nullable=False) + op.create_index( + op.f("ix_amap_delivery_name"), + "amap_delivery", + ["name"], + unique=False, + ) + + op.add_column( + "amap_cash", + sa.Column("last_order_date", TZDateTime(), nullable=True), + ) + default_time = datetime(2025, 1, 1, tzinfo=UTC) + op.execute( + sa.text("UPDATE amap_cash SET last_order_date = :last_order_date").bindparams( + sa.bindparam("last_order_date", value=default_time), + ), + ) + op.alter_column("amap_cash", "last_order_date", nullable=False) + + op.drop_column("amap_order", "delivery_date") + + +def downgrade() -> None: + op.drop_column("amap_cash", "last_order_date") + op.drop_column("amap_delivery", "name") + op.add_column( + "amap_order", + sa.Column("delivery_date", TZDateTime(), nullable=True), + ) + default_time = datetime(2025, 1, 1, tzinfo=UTC) + op.execute( + sa.text("UPDATE amap_order SET delivery_date = :delivery_date").bindparams( + sa.bindparam("delivery_date", value=default_time), + ), + ) + op.alter_column("amap_order", "delivery_date", nullable=False) + + +def pre_test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass + + +def test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass diff --git a/tests/test_amap.py b/tests/test_amap.py index 4619e94b19..53ec479dab 100644 --- a/tests/test_amap.py +++ b/tests/test_amap.py @@ -58,17 +58,18 @@ async def init_objects() -> None: ) await add_object_to_db(deletable_product) - # We can not create two deliveries with the same date delivery = models_amap.Delivery( id=str(uuid.uuid4()), delivery_date=datetime(2022, 8, 15, tzinfo=UTC), status=DeliveryStatusType.creation, + name="Livraison 1", ) await add_object_to_db(delivery) deletable_delivery = models_amap.Delivery( id=str(uuid.uuid4()), delivery_date=datetime(2022, 8, 16, tzinfo=UTC), status=DeliveryStatusType.creation, + name="Livraison supprimable", ) await add_object_to_db(deletable_delivery) @@ -76,17 +77,18 @@ async def init_objects() -> None: id=str(uuid.uuid4()), delivery_date=datetime(2022, 8, 17, tzinfo=UTC), status=DeliveryStatusType.locked, + name="Livraison verrouillée", ) await add_object_to_db(locked_delivery) order = models_amap.Order( order_id=str(uuid.uuid4()), user_id=student_user.id, + delivery=delivery, delivery_id=delivery.id, amount=0.0, collection_slot=AmapSlotType.midi, ordering_date=datetime(2022, 8, 10, 12, 16, 26, tzinfo=UTC), - delivery_date=delivery.delivery_date, ) await add_object_to_db(order) @@ -97,11 +99,15 @@ async def init_objects() -> None: amount=0.0, collection_slot=AmapSlotType.midi, ordering_date=datetime(2022, 8, 18, 12, 16, 26, tzinfo=UTC), - delivery_date=locked_delivery.delivery_date, + delivery=locked_delivery, ) await add_object_to_db(deletable_order_by_admin) - cash = models_amap.Cash(user_id=student_user.id, balance=666) + cash = models_amap.Cash( + user_id=student_user.id, + balance=666, + last_order_date=datetime.now(UTC), + ) await add_object_to_db(cash) @@ -159,7 +165,7 @@ def test_delete_product(client: TestClient) -> None: def test_get_deliveries(client: TestClient) -> None: - # The user don't need to be part of group amap to get a product + # The user don't need to be part of group amap to get a delivery student_token = create_api_access_token(student_user) response = client.get( @@ -175,6 +181,7 @@ def test_create_delivery(client: TestClient) -> None: response = client.post( "/amap/deliveries", json={ + "name": "Livraison", "delivery_date": "2022-08-18", "products_ids": [product.id], "locked": False, @@ -199,7 +206,11 @@ def test_edit_delivery(client: TestClient) -> None: response = client.patch( f"/amap/deliveries/{delivery.id}", - json={"delivery_date": "2022-08-18", "locked": False}, + json={ + "name": "Livraison editee", + "delivery_date": "2022-08-18", + "locked": False, + }, headers={"Authorization": f"Bearer {token}"}, ) assert response.status_code == 204