diff --git a/src/allmende_payment_system/api/shop.py b/src/allmende_payment_system/api/shop.py index db05c3c..de5b6a0 100644 --- a/src/allmende_payment_system/api/shop.py +++ b/src/allmende_payment_system/api/shop.py @@ -78,8 +78,43 @@ async def add_to_cart(request: Request, session: SessionDep, user: UserDep): ) +@shop_router.get("/shop/cart/remove/{item_id}") +async def remove_from_cart( + request: Request, session: SessionDep, user: UserDep, item_id: int +): + + cart = user.shopping_cart + for item in cart.items: + if item.id == item_id: + item.order = None + session.delete(item) + break + else: + raise HTTPException(status_code=404, detail="Item not found in cart.") + + return RedirectResponse(url=f"/shop/cart", status_code=status.HTTP_302_FOUND) + + +@shop_router.post("/shop/cart/update/{item_id}") +async def update_cart_item( + request: Request, session: SessionDep, user: UserDep, item_id: int +): + + form_data = await request.form() + + cart = user.shopping_cart + for item in cart.items: + if item.id == item_id: + item.update_quantity(Decimal(form_data["quantity"])) + break + else: + raise HTTPException(status_code=404, detail="Item not found in cart.") + + return RedirectResponse(url=f"/shop/cart", status_code=status.HTTP_302_FOUND) + + @shop_router.get("/shop/order/{order_id}") -async def add_to_cart( +async def get_order_details( request: Request, session: SessionDep, user: UserDep, order_id: int ): diff --git a/src/allmende_payment_system/models.py b/src/allmende_payment_system/models.py index 96d6ed0..a856baa 100644 --- a/src/allmende_payment_system/models.py +++ b/src/allmende_payment_system/models.py @@ -94,6 +94,8 @@ class Product(Base): price: Mapped[decimal.Decimal] = mapped_column(Numeric(10, 2)) unit_of_measure: Mapped[UnitsOfMeasure] = mapped_column(nullable=False) + allow_fractional: Mapped[bool] = mapped_column(nullable=False, default=True) + # TODO: limit this to actually used vat rates? vat_rate: Mapped[decimal.Decimal] = mapped_column(Numeric(10, 2)) @@ -162,6 +164,13 @@ class OrderItem(Base): Numeric(10, 2), nullable=False ) + def update_quantity(self, new_quantity: decimal.Decimal): + if new_quantity <= 0: + raise ValueError("Quantity must be positive.") + + self.quantity = new_quantity + self.total_amount = self.product.price * new_quantity + TransactionTypes = typing.Literal[ "order", diff --git a/src/allmende_payment_system/templates/area.html.jinja b/src/allmende_payment_system/templates/area.html.jinja index 94b2938..6f2d47a 100644 --- a/src/allmende_payment_system/templates/area.html.jinja +++ b/src/allmende_payment_system/templates/area.html.jinja @@ -67,14 +67,15 @@
- +
diff --git a/src/allmende_payment_system/templates/index.html.jinja b/src/allmende_payment_system/templates/index.html.jinja index 88b2cd6..f079ef0 100644 --- a/src/allmende_payment_system/templates/index.html.jinja +++ b/src/allmende_payment_system/templates/index.html.jinja @@ -35,7 +35,7 @@
-

Latest Transactions

+

Letzte Buchungen

View All
diff --git a/src/allmende_payment_system/templates/order.html.jinja b/src/allmende_payment_system/templates/order.html.jinja index be2fe5f..1f2422c 100644 --- a/src/allmende_payment_system/templates/order.html.jinja +++ b/src/allmende_payment_system/templates/order.html.jinja @@ -25,8 +25,7 @@ {% set total = namespace(value=0) %} {% for item in items %} {% set price = item.price if item.price is defined else (item.unit_price if item.unit_price is defined else 0) %} - {% set qty = item.quantity if item.quantity is defined else (item.qty if item.qty is defined else 1) %} - {% set subtotal = price * qty %} + {% set subtotal = price * item.quantity %} {% set total.value = total.value + subtotal %} @@ -35,18 +34,26 @@ {% if is_cart %} -
- + + + {% if item.product.unit_of_measure != 'piece' %}{{ item.product.unit_of_measure }}{% endif %}
{% else %} - {{ item.quantity | format_number }} + {{ item.quantity | format_number }}{% if item.product.unit_of_measure != 'piece' %} {{ item.product.unit_of_measure }}{% endif %} {% endif %} {{ item.product.price | format_number }} € {{ item.total_amount | format_number }} € - {% if is_cart %}Entfernen{% endif %} + {% if is_cart %}Entfernen{% endif %} {% endfor %} @@ -64,7 +71,6 @@ {% if is_cart %}
- Warenkorb leeren Jetzt Buchen
diff --git a/test/test_shop.py b/test/test_shop.py index 8fae41b..d6eeaff 100644 --- a/test/test_shop.py +++ b/test/test_shop.py @@ -3,6 +3,7 @@ from unittest import mock import pytest from fake_data import fake +from sqlalchemy import select from starlette.testclient import TestClient from allmende_payment_system.database import ensure_user @@ -67,6 +68,80 @@ def test_add_item_to_cart(client: TestClient, test_db, product): ) assert response.status_code == 302 + cart = test_db.scalar(select(Order)) + assert len(cart.items) == 1 + + +def test_edit_item_in_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", + data=form_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + follow_redirects=False, + ) + assert response.status_code == 302 + + form_data = {"quantity": 3} + + response = client.post( + f"/shop/cart/update/{test_db.scalar(select(OrderItem)).id}", + data=form_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + follow_redirects=False, + ) + assert response.status_code == 302 + + cart = test_db.scalar(select(Order)) + assert len(cart.items) == 1 + assert cart.items[0].quantity == 3 + assert cart.items[0].total_amount == product.price * 3 + + +def test_remove_item_from_cart(client: TestClient, test_db, product): + + user = create_user_with_account(test_db, "test") + + user.shopping_cart.items.append( + OrderItem(product=product, quantity=2, total_amount=product.price * 2) + ) + test_db.flush() + + cart = test_db.scalar(select(Order)) + assert len(cart.items) == 1 + + response = client.get( + f"/shop/cart/remove/{user.shopping_cart.items[0].id}", follow_redirects=False + ) + assert response.status_code == 302 + + cart = test_db.scalar(select(Order)) + assert len(cart.items) == 0 + + assert len(test_db.scalars(select(OrderItem)).all()) == 0 + + +def test_remove_item_from_cart_wrong_user(client: TestClient, test_db, product): + user = create_user_with_account(test_db, "test") + + user.shopping_cart.items.append( + OrderItem(product=product, quantity=2, total_amount=product.price * 2) + ) + test_db.flush() + + cart = test_db.scalar(select(Order)) + assert len(cart.items) == 1 + + id_ = user.shopping_cart.items[0].id + + with mock.patch.dict("os.environ", {"APS_username": "other_user"}): + response = client.get(f"/shop/cart/remove/{id_}", follow_redirects=False) + assert response.status_code == 404 + + cart = test_db.scalar(select(Order)) + assert len(cart.items) == 1 + def test_finalize_order(client: TestClient, test_db, product):