feat(order): Add finalize order functionality
This commit is contained in:
@@ -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)]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
{% for transaction in transactions[:10] %}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-start py-3">
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-semibold">{{ transaction.product.name or transaction.type|transaction_type_de }}</div>
|
||||
<div class="fw-semibold">{{ transaction.type|transaction_type_de }}</div>
|
||||
<small class="text-muted">
|
||||
{{ transaction.timestamp }}
|
||||
</small>
|
||||
|
||||
@@ -7,7 +7,7 @@ TRANSACTION_TYPE_DE = {
|
||||
"deposit": "Einzahlung",
|
||||
"withdrawal": "Auszahlung",
|
||||
"expense": "Auslage",
|
||||
"product": "Einkauf",
|
||||
"order": "Einkauf",
|
||||
}
|
||||
|
||||
UNITS_OF_MEASURE = {"piece": "Stück"}
|
||||
|
||||
Reference in New Issue
Block a user