diff --git a/finance/app/routers/admin.py b/finance/app/routers/admin.py index 17dc2c5..904ba3c 100644 --- a/finance/app/routers/admin.py +++ b/finance/app/routers/admin.py @@ -1,8 +1,10 @@ from fastapi import APIRouter, Depends, Form, Request +from sqlalchemy import select from sqlalchemy.orm import Session from app.auth import require_auth from app.db import get_session +from app.models.tables import Category from app.routers.gui import gui_session, templates from app.services.admin import apply_rules_retroactively, change_password @@ -10,8 +12,12 @@ router = APIRouter() @router.get("/admin", dependencies=[Depends(gui_session)]) -def admin_page(request: Request): - return templates.TemplateResponse(request, "admin.html", {"error": None, "success": None}) +def admin_page(request: Request, session: Session = Depends(get_session)): + return templates.TemplateResponse(request, "admin.html", { + "error": None, + "success": None, + "categories": session.execute(select(Category)).scalars().all(), + }) @router.post("/admin/passwort", dependencies=[Depends(gui_session)]) @@ -20,18 +26,24 @@ def admin_change_password( alt: str = Form(...), neu: str = Form(...), neu2: str = Form(...), + session: Session = Depends(get_session), ): + categories = session.execute(select(Category)).scalars().all() # UX-Regel: das Formular selbst bleibt immer sichtbar/bedienbar; Fehler # werden inline auf derselben Seite gemeldet statt still zu verwerfen. + # Der Kategorien-Abschnitt bleibt aus demselben Grund in JEDEM Render-Pfad + # (Fehler wie Erfolg) im Kontext, statt nur bei der GET-Route. if len(neu) < 8: return templates.TemplateResponse(request, "admin.html", { "error": "Das neue Passwort muss mindestens 8 Zeichen lang sein.", "success": None, + "categories": categories, }, status_code=400) if neu != neu2: return templates.TemplateResponse(request, "admin.html", { "error": "Die Wiederholung stimmt nicht mit dem neuen Passwort überein.", "success": None, + "categories": categories, }, status_code=400) try: change_password(alt, neu) @@ -39,11 +51,13 @@ def admin_change_password( return templates.TemplateResponse(request, "admin.html", { "error": str(exc), "success": None, + "categories": categories, }, status_code=400) return templates.TemplateResponse(request, "admin.html", { "error": None, "success": "Passwort erfolgreich geändert (gilt für GUI und Grafana). " "Bestehende Sitzungen bleiben angemeldet.", + "categories": categories, }) diff --git a/finance/app/routers/categories.py b/finance/app/routers/categories.py index f3d96a6..4bd79d0 100644 --- a/finance/app/routers/categories.py +++ b/finance/app/routers/categories.py @@ -48,6 +48,22 @@ def create_category(data: CategoryIn, session: Session = Depends(get_session)): return CategoryOut.model_validate(cat) +@router.patch("/categories/{category_id}", response_model=CategoryOut) +def patch_category(category_id: int, data: CategoryIn, + session: Session = Depends(get_session)): + cat = session.get(Category, category_id) + if cat is None: + raise HTTPException(404, "Kategorie nicht gefunden") + clash = session.execute( + select(Category).where(Category.name == data.name)).scalar() + if clash is not None and clash.id != category_id: + raise HTTPException(409, "Kategorie existiert bereits") + cat.name = data.name + session.commit() + session.refresh(cat) + return CategoryOut.model_validate(cat) + + @router.get("/category-rules", response_model=list[CategoryRuleOut]) def list_category_rules(session: Session = Depends(get_session)): rules = session.execute( diff --git a/finance/app/templates/admin.html b/finance/app/templates/admin.html index c1f9027..48e5fa6 100644 --- a/finance/app/templates/admin.html +++ b/finance/app/templates/admin.html @@ -32,6 +32,43 @@

+
+

Kategorien

+

Kategorien für Buchungen, Fixposten und Szenario-Modifikatoren. + Löschen ist bewusst nicht vorgesehen (Kategorien hängen an Buchungen und Regeln).

+ + + + {% for c in categories %} + + + + + + + + {% else %} + + {% endfor %} + +
Name
{{ c.name }}
Noch keine Kategorien.
+
+ Neue Kategorie anlegen +
+ + +
+
+
+