From 5d2dfe37c1c1b23a5639eebb51d914d48ee3a1da Mon Sep 17 00:00:00 2001 From: Niklas Meinzer Date: Sat, 13 Dec 2025 12:25:00 +0100 Subject: [PATCH] View order --- src/allmende_payment_system/api/shop.py | 27 ++++++++-- .../templates/index.html.jinja | 35 ++++++------ .../{cart.html.jinja => order.html.jinja} | 16 ++++-- src/allmende_payment_system/tools.py | 3 +- test/test_shop.py | 54 +++++++++++++++---- 5 files changed, 101 insertions(+), 34 deletions(-) rename src/allmende_payment_system/templates/{cart.html.jinja => order.html.jinja} (80%) diff --git a/src/allmende_payment_system/api/shop.py b/src/allmende_payment_system/api/shop.py index c5a64dc..db05c3c 100644 --- a/src/allmende_payment_system/api/shop.py +++ b/src/allmende_payment_system/api/shop.py @@ -1,12 +1,12 @@ from decimal import Decimal -from fastapi import APIRouter, Request +from fastapi import APIRouter, HTTPException, Request from sqlalchemy import select from starlette import status from starlette.responses import RedirectResponse from allmende_payment_system.api.dependencies import SessionDep, UserDep -from allmende_payment_system.models import Area, OrderItem, Product +from allmende_payment_system.models import Area, Order, OrderItem, Product from allmende_payment_system.tools import get_jinja_renderer shop_router = APIRouter() @@ -28,8 +28,8 @@ async def get_shop(request: Request, session: SessionDep): async def get_cart(request: Request, session: SessionDep, user: UserDep): return templates.TemplateResponse( - "cart.html.jinja", - context={"request": request, "user": user}, + "order.html.jinja", + context={"request": request, "order": user.shopping_cart, "is_cart": True}, ) @@ -76,3 +76,22 @@ async def add_to_cart(request: Request, session: SessionDep, user: UserDep): return RedirectResponse( url=f"/shop/area/{form_data['area_id']}", status_code=status.HTTP_302_FOUND ) + + +@shop_router.get("/shop/order/{order_id}") +async def add_to_cart( + request: Request, session: SessionDep, user: UserDep, order_id: int +): + + query = select(Order).where(Order.id == order_id) + order = session.scalars(query).one() + + if user.id != order.user_id: + raise HTTPException( + status_code=403, detail=f"User not authorized to view this order." + ) + + return templates.TemplateResponse( + "order.html.jinja", + context={"request": request, "order": order, "is_cart": False}, + ) diff --git a/src/allmende_payment_system/templates/index.html.jinja b/src/allmende_payment_system/templates/index.html.jinja index f73dbc2..88b2cd6 100644 --- a/src/allmende_payment_system/templates/index.html.jinja +++ b/src/allmende_payment_system/templates/index.html.jinja @@ -44,21 +44,26 @@ {% if transactions and transactions|length > 0 %} {% for transaction in transactions[:10] %}
-
-
{{ transaction.type|transaction_type_de }}
- - {{ transaction.timestamp }} - -
-
- - {{ '%+.2f' | format(transaction.total_amount | default(0)) }} € - - {% if transaction.quantity %} -
{{ transaction.quantity }} €
- {% endif %} -
-
+
+
{{ transaction.type|transaction_type_de }}
+ + {{ transaction.timestamp | timestamp_de }} + + {% if transaction.type == "order" %} + + {% endif %} +
+
+ + {{ '%+.2f' | format(transaction.total_amount | default(0)) }} € + + {% if transaction.quantity %} +
{{ transaction.quantity }} €
+ {% endif %} +
+ {% endfor %} {% else %}
diff --git a/src/allmende_payment_system/templates/cart.html.jinja b/src/allmende_payment_system/templates/order.html.jinja similarity index 80% rename from src/allmende_payment_system/templates/cart.html.jinja rename to src/allmende_payment_system/templates/order.html.jinja index 4d78641..be2fe5f 100644 --- a/src/allmende_payment_system/templates/cart.html.jinja +++ b/src/allmende_payment_system/templates/order.html.jinja @@ -1,11 +1,11 @@ {% extends "base.html.jinja" %} {% block content %}
-

Warenkorb

-

Überprüfe deine Artikel und fahre zur Kasse fort.

+

{% if is_cart %}Warenkorb{% else %}Einkauf #{{ order.id }}{% endif %}

+ {% if not is_cart %}

Einkauf abgeschickt: {{ order.transaction.timestamp | timestamp_de }}

{% endif %}
- {% set items = user.shopping_cart.items %} + {% set items = order.items %} {% if items|length == 0 %}
Dein Warenkorb ist leer. Weiter einkaufen.
@@ -34,15 +34,19 @@
{{ item.description or '' }}
+ {% if is_cart %}
+ {% else %} + {{ item.quantity | format_number }} + {% endif %} {{ item.product.price | format_number }} € {{ item.total_amount | format_number }} € - Entfernen + {% if is_cart %}Entfernen{% endif %} {% endfor %} @@ -50,19 +54,21 @@ Gesamtsumme - {{ user.shopping_cart.total_amount | format_number }} € + {{ order.total_amount | format_number }} €
+ {% if is_cart %}
Warenkorb leeren Jetzt Buchen
+ {% endif %} {% endif %} {% endblock %} diff --git a/src/allmende_payment_system/tools.py b/src/allmende_payment_system/tools.py index dc80968..c38065a 100644 --- a/src/allmende_payment_system/tools.py +++ b/src/allmende_payment_system/tools.py @@ -15,7 +15,7 @@ UNITS_OF_MEASURE = {"piece": "Stück"} def format_number(value: float): try: - return f"{value:n}" + return f"{value:.2f}".replace(".", ",") except TypeError: return value @@ -25,4 +25,5 @@ def get_jinja_renderer() -> Jinja2Templates: renderer.env.filters["transaction_type_de"] = lambda x: TRANSACTION_TYPE_DE[x] renderer.env.filters["units_of_measure_de"] = lambda x: UNITS_OF_MEASURE.get(x, x) renderer.env.filters["format_number"] = format_number + renderer.env.filters["timestamp_de"] = lambda x: x.strftime("%d.%m.%Y %H:%M") return renderer diff --git a/test/test_shop.py b/test/test_shop.py index 64697aa..8fae41b 100644 --- a/test/test_shop.py +++ b/test/test_shop.py @@ -1,5 +1,7 @@ from decimal import Decimal +from unittest import mock +import pytest from fake_data import fake from starlette.testclient import TestClient @@ -29,14 +31,33 @@ def create_user_with_account(test_db, username: str, balance: float | None = Non return user -def test_add_item_to_cart(client: TestClient, test_db): +def add_finalized_order_to_user(test_db, user, product) -> Order: + order = Order(user=user) + total_amount = product.price + order.items.append( + OrderItem(product=product, quantity=1, total_amount=total_amount) + ) + order.transaction = Transaction(total_amount=total_amount, type="order") + order.transaction.account = user.accounts[0] + test_db.add(order) + test_db.flush() + return order + + +@pytest.fixture +def product(test_db): area = Area(**fake.area()) test_db.add(area) product = Product(**fake.product()) product.area = area test_db.add(product) test_db.flush() - form_data = {"product_id": product.id, "quantity": 2, "area_id": area.id} + return product + + +def test_add_item_to_cart(client: TestClient, test_db, product): + + form_data = {"product_id": product.id, "quantity": 2, "area_id": product.area.id} response = client.post( "/shop/cart/add", @@ -47,13 +68,7 @@ def test_add_item_to_cart(client: TestClient, test_db): assert response.status_code == 302 -def test_finalize_order(client: TestClient, test_db): - area = Area(**fake.area()) - test_db.add(area) - product = Product(**fake.product()) - product.area = area - test_db.add(product) - test_db.flush() +def test_finalize_order(client: TestClient, test_db, product): user = create_user_with_account(test_db, "test", balance=100.0) @@ -69,3 +84,24 @@ def test_finalize_order(client: TestClient, test_db): assert len(user.orders) == 2 # shopping cart + finalized order assert user.accounts[0].balance == Decimal(100.0) - (product.price * 2) + + +def test_view_order(client: TestClient, test_db, product): + user = create_user_with_account(test_db, "test") + + order = add_finalized_order_to_user(test_db, user, product) + + response = client.get(f"/shop/order/{order.id}") + assert response.status_code == 200 + assert f"Einkauf #{order.id}" in response.text + assert product.name in response.text + + +def test_view_order_wrong_user(client: TestClient, test_db, product): + user = create_user_with_account(test_db, "test") + + order = add_finalized_order_to_user(test_db, user, product) + + with mock.patch.dict("os.environ", {"APS_username": "other_user"}): + response = client.get(f"/shop/order/{order.id}") + assert response.status_code == 403