diff --git a/src/allmende_payment_system/api/dependencies.py b/src/allmende_payment_system/api/dependencies.py index 1295243..95dc882 100644 --- a/src/allmende_payment_system/api/dependencies.py +++ b/src/allmende_payment_system/api/dependencies.py @@ -41,4 +41,4 @@ async def get_user_object(request: Request, session: SessionDep) -> User: return user -UserDep = Annotated[dict, Depends(get_user_object)] +UserDep = Annotated[User, Depends(get_user_object)] diff --git a/src/allmende_payment_system/api/shop.py b/src/allmende_payment_system/api/shop.py index a6b9eee..c5a64dc 100644 --- a/src/allmende_payment_system/api/shop.py +++ b/src/allmende_payment_system/api/shop.py @@ -34,18 +34,18 @@ async def get_cart(request: Request, session: SessionDep, user: UserDep): @shop_router.get("/shop/finalize_order") -async def get_cart(request: Request, session: SessionDep, user: UserDep): +async def finalize_order(request: Request, session: SessionDep, user: UserDep): cart = user.shopping_cart # TODO: Implement - cart.finalize_order() + cart.finalize(user.accounts[0]) return RedirectResponse(url=f"/", status_code=status.HTTP_302_FOUND) @shop_router.get("/shop/area/{area_id}") -async def get_shop(request: Request, session: SessionDep, area_id: int): +async def get_shop_area(request: Request, session: SessionDep, area_id: int): query = select(Area).where(Area.id == area_id) area = session.scalars(query).one() return templates.TemplateResponse( diff --git a/src/allmende_payment_system/database.py b/src/allmende_payment_system/database.py index 1b6c6b8..5bff15b 100644 --- a/src/allmende_payment_system/database.py +++ b/src/allmende_payment_system/database.py @@ -15,6 +15,18 @@ def create_tables(): def ensure_user(user_info: dict, session: Session) -> User: + """ + Retrieve an existing user or create a new one if it doesn't exist. + This function queries the database for a user with the given username. + If found, it returns the existing user. If not found, it creates a new user + with the provided information, adds it to the session, and returns it. + + :param user_info: Dictionary containing user information with keys: + - "username" (str): The unique username to search for or create + - "display_name" (str, optional): The display name for the new user + :param session: SQLAlchemy session for database operations + :return: The existing or newly created user object + """ statement = select(User).where(User.username == user_info["username"]) if user := session.scalars(statement).one_or_none(): diff --git a/src/allmende_payment_system/models.py b/src/allmende_payment_system/models.py index b82beb9..96d6ed0 100644 --- a/src/allmende_payment_system/models.py +++ b/src/allmende_payment_system/models.py @@ -58,7 +58,7 @@ class User(Base): @property def shopping_cart(self): for order in self.orders: - if order.account_id is None: + if order.transaction is None: cart = order break else: @@ -109,10 +109,7 @@ class Order(Base): user_id: Mapped[int] = mapped_column(ForeignKey(TABLE_PREFIX + "user.id")) user: Mapped[User] = relationship("User", back_populates="orders") - account_id: Mapped[int] = mapped_column( - ForeignKey(TABLE_PREFIX + "account.id"), nullable=True - ) - account: Mapped[Account | None] = relationship("Account") + transaction: Mapped["Transaction | None"] = relationship("Transaction") items: Mapped[list["OrderItem"]] = relationship( "OrderItem", cascade="all, delete-orphan", back_populates="order" @@ -120,12 +117,37 @@ class Order(Base): @property def is_in_shopping_cart(self): - return self.account is None + return self.transaction is None @property def total_amount(self): return sum(item.total_amount for item in self.items) + def finalize(self, account: Account): + """ + Moves the order from the shopping cart to a given account + and adds a transaction to the account. + + :param account: The account to which the order should be finalized + :raises ValueError: If the order is already finalized or empty""" + if not self.is_in_shopping_cart: + raise ValueError("Order is already finalized.") + + if not self.items: + raise ValueError("Cannot finalize an empty order.") + + assert account in self.user.accounts, "Account does not belong to user." + + # create a transaction for the order + transaction = Transaction( + type="order", + total_amount=-self.total_amount, + order=self, + account=account, + ) + session = object_session(self) + session.add(transaction) + class OrderItem(Base): __tablename__ = TABLE_PREFIX + "order_item" @@ -142,7 +164,7 @@ class OrderItem(Base): TransactionTypes = typing.Literal[ - "product", + "order", "deposit", "withdrawal", "expense", @@ -161,10 +183,10 @@ class Transaction(Base): Numeric(10, 2), nullable=False ) - product_id: Mapped[int] = mapped_column( - ForeignKey(TABLE_PREFIX + "product.id"), nullable=True + order_id: Mapped[int] = mapped_column( + ForeignKey(TABLE_PREFIX + "order.id"), nullable=True ) - product: Mapped["Product"] = relationship("Product") + order: Mapped["Order"] = relationship("Order", back_populates="transaction") account_id: Mapped[int] = mapped_column(ForeignKey(TABLE_PREFIX + "account.id")) account: Mapped["Account"] = relationship("Account", back_populates="transactions") diff --git a/src/allmende_payment_system/templates/index.html.jinja b/src/allmende_payment_system/templates/index.html.jinja index 817ea01..f73dbc2 100644 --- a/src/allmende_payment_system/templates/index.html.jinja +++ b/src/allmende_payment_system/templates/index.html.jinja @@ -45,7 +45,7 @@ {% for transaction in transactions[:10] %}