diff --git a/src/allmende_payment_system/api/admin.py b/src/allmende_payment_system/api/admin.py index eefb380..b25244f 100644 --- a/src/allmende_payment_system/api/admin.py +++ b/src/allmende_payment_system/api/admin.py @@ -1,12 +1,14 @@ from decimal import Decimal +from typing import Annotated -from fastapi import APIRouter, HTTPException, Request +from fastapi import APIRouter, File, Form, HTTPException, Request from sqlalchemy import select from starlette import status from starlette.responses import RedirectResponse +from allmende_payment_system.api import types from allmende_payment_system.api.dependencies import SessionDep, UserDep -from allmende_payment_system.models import Permission, User, UserGroup +from allmende_payment_system.models import Area, Permission, Product, User, UserGroup from allmende_payment_system.tools import get_jinja_renderer admin_router = APIRouter(prefix="/admin") @@ -156,3 +158,112 @@ async def delete_group( ).scalar_one() session.delete(group) return RedirectResponse(url="/admin/groups", status_code=status.HTTP_303_SEE_OTHER) + + +# PRODUCTS + + +@admin_router.get("/products") +async def get_products( + request: Request, + session: SessionDep, + user: UserDep, +): + products = session.scalars( + select(Product).order_by(Product.area_id, Product.name) + ).all() + + templates = get_jinja_renderer() + + return templates.TemplateResponse( + "products.html.jinja", + context={"request": request, "products": products}, + ) + + +@admin_router.get("/products/edit/{product_id}") +async def edit_product_get( + request: Request, session: SessionDep, user: UserDep, product_id: int +): + product = session.execute(select(Product).where(Product.id == product_id)).scalar() + + areas = session.scalars(select(Area)).all() + + templates = get_jinja_renderer() + + return templates.TemplateResponse( + "product_edit.html.jinja", + context={ + "request": request, + "product": product, + "edit_mode": True, + "areas": areas, + }, + ) + + +@admin_router.post("/products/edit/{product_id}") +async def edit_product_post( + request: Request, + session: SessionDep, + user: UserDep, + product_id: int, + product_data: Annotated[types.Product, Form()], +): + if not user.has_permission("product", "edit"): + raise HTTPException(status_code=403, detail="Insufficient permissions") + + product = session.execute( + select(Product).where(Product.id == product_id) + ).scalar_one() + + for field_name, data in product_data.model_dump().items(): + setattr(product, field_name, data) + + session.flush() + + return RedirectResponse( + url="/admin/products", status_code=status.HTTP_303_SEE_OTHER + ) + + +@admin_router.get("/products/new") +async def new_product_get( + request: Request, + session: SessionDep, + user: UserDep, +): + if not user.has_permission("product", "edit"): + raise HTTPException(status_code=403, detail="Insufficient permissions") + + areas = session.scalars(select(Area)).all() + + templates = get_jinja_renderer() + + return templates.TemplateResponse( + "product_edit.html.jinja", + context={"request": request, "edit_mode": False, "areas": areas}, + ) + + +@admin_router.post("/products/new") +async def new_product_post( + request: Request, + session: SessionDep, + user: UserDep, + product_data: Annotated[types.Product, Form()], + # product_image: Annotated[bytes, File()] +): + if not user.has_permission("product", "edit"): + raise HTTPException(status_code=403, detail="Insufficient permissions") + # print(len(product_image)) + product = Product() + + for field_name, data in product_data.model_dump().items(): + setattr(product, field_name, data) + + session.add(product) + + return RedirectResponse( + url="/admin/products", status_code=status.HTTP_303_SEE_OTHER + ) diff --git a/src/allmende_payment_system/api/types.py b/src/allmende_payment_system/api/types.py new file mode 100644 index 0000000..17070ed --- /dev/null +++ b/src/allmende_payment_system/api/types.py @@ -0,0 +1,20 @@ +import typing +from decimal import Decimal + +from pydantic import BaseModel + +UnitsOfMeasure = typing.Literal[ + "g", + "kg", + "l", + "piece", +] + + +class Product(BaseModel): + name: str + price: Decimal + area_id: int + vat_rate: Decimal + allow_fractional: bool = False + unit_of_measure: UnitsOfMeasure diff --git a/src/allmende_payment_system/models.py b/src/allmende_payment_system/models.py index cf56d9c..6760d59 100644 --- a/src/allmende_payment_system/models.py +++ b/src/allmende_payment_system/models.py @@ -12,6 +12,8 @@ from sqlalchemy.orm import ( relationship, ) +from allmende_payment_system.api.types import UnitsOfMeasure + TABLE_PREFIX = "aps_" @@ -131,14 +133,6 @@ class Area(Base): products: Mapped[list["Product"]] = relationship("Product") -UnitsOfMeasure = typing.Literal[ - "g", - "kg", - "l", - "piece", -] - - class Product(Base): __tablename__ = TABLE_PREFIX + "product" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) diff --git a/src/allmende_payment_system/templates/base.html.jinja b/src/allmende_payment_system/templates/base.html.jinja index f7a6039..f529aa6 100644 --- a/src/allmende_payment_system/templates/base.html.jinja +++ b/src/allmende_payment_system/templates/base.html.jinja @@ -29,6 +29,13 @@ Einkaufen + {% if request.state.user.has_permission("product", "edit") %} +
| ID | +Name | +Bereich | +Preis | +Einheit | +Steuersatz | ++ |
|---|---|---|---|---|---|---|
| {{ product.id }} | +{{ product.name }} | +{{ product.area.name }} | +{{ product.price | format_number}} € | +{{ product.unit_of_measure | units_of_measure_de}} | +{{ product.vat_rate | int }} % | ++ Bearbeiten + | +