Kundenportal:
- Neue Seite /struktur - OE-Baum (rekursives Jinja-Makro), Anlegen/
Umbenennen/Löschen (Standard-OE 'Neue Geräte' geschützt), Merkmale-
Editor pro OE mit Tristate aktiv/inaktiv/nicht festgelegt.
- geraete_liste.html: neue Spalte 'OE', Aktionen-Dropdown um 'In OE
verschieben' erweitert.
- auftragskatalog.html: zeigt jetzt die Herkunft einer Einstellung an
(OE/Gruppe/manuell), sofern sie nicht vom Workspace-Default kommt.
Admin (/admin/kunden/{id}):
- Neue Sektion 'Gruppen' - Anlegen/Löschen, verlinkt auf neue
Detailseite (admin/gruppe_detail.html) mit Merkmale-Editor (wie OE)
und Geräte-Mitgliedschaft-Checkliste. Bewusst nur intern verwaltbar
in v1 (siehe Konfigurationsgruppen-Diskussion).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
157 lines
5.7 KiB
Python
157 lines
5.7 KiB
Python
from fastapi import APIRouter, Depends, Form, HTTPException, Request
|
|
from fastapi.responses import RedirectResponse
|
|
|
|
from anode_client import anode_request
|
|
from auth import get_current_user
|
|
from templating import templates
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def fetch_organisationseinheiten(organization_id: str) -> list[dict]:
|
|
result = anode_request("GET", f"/api/v1/organizations/{organization_id}/organisationseinheiten")
|
|
return result.get("organisationseinheiten", []) if result.get("success") else []
|
|
|
|
|
|
def find_oe_in_organization(oe_id: str, organization_id: str) -> dict | None:
|
|
"""
|
|
Besitz-Validierung analog zu find_device_in_organization() in
|
|
routers/geraete.py (siehe ADR-0011): eine oe_id aus der URL wird nur
|
|
akzeptiert, wenn sie tatsächlich in der OE-Liste der eigenen
|
|
Organisation auftaucht.
|
|
"""
|
|
|
|
for oe in fetch_organisationseinheiten(organization_id):
|
|
if oe["id"] == oe_id:
|
|
return oe
|
|
|
|
return None
|
|
|
|
|
|
@router.get("/struktur")
|
|
def struktur_ansicht(request: Request, user=Depends(get_current_user)):
|
|
oes = fetch_organisationseinheiten(user.organization_id)
|
|
|
|
return templates.TemplateResponse(
|
|
request, "struktur.html", {"user": user, "organisationseinheiten": oes}
|
|
)
|
|
|
|
|
|
@router.post("/struktur")
|
|
def oe_anlegen(
|
|
name: str = Form(...),
|
|
parent_id: str = Form(default=""),
|
|
user=Depends(get_current_user),
|
|
):
|
|
anode_request(
|
|
"POST",
|
|
f"/api/v1/organizations/{user.organization_id}/organisationseinheiten",
|
|
json={"name": name, "parent_id": parent_id or None},
|
|
)
|
|
|
|
return RedirectResponse("/struktur", status_code=303)
|
|
|
|
|
|
@router.get("/struktur/{oe_id}/umbenennen")
|
|
def oe_umbenennen_form(request: Request, oe_id: str, user=Depends(get_current_user)):
|
|
oe = find_oe_in_organization(oe_id, user.organization_id)
|
|
|
|
if oe is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
return templates.TemplateResponse(request, "struktur_umbenennen.html", {"user": user, "oe": oe})
|
|
|
|
|
|
@router.post("/struktur/{oe_id}/umbenennen")
|
|
def oe_umbenennen(oe_id: str, name: str = Form(...), user=Depends(get_current_user)):
|
|
oe = find_oe_in_organization(oe_id, user.organization_id)
|
|
|
|
if oe is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
anode_request(
|
|
"PATCH",
|
|
f"/api/v1/organisationseinheiten/{oe_id}",
|
|
json={"name": name, "parent_id": oe["parent_id"]},
|
|
)
|
|
|
|
return RedirectResponse("/struktur", status_code=303)
|
|
|
|
|
|
@router.get("/struktur/{oe_id}/loeschen")
|
|
def oe_loeschen_form(request: Request, oe_id: str, user=Depends(get_current_user)):
|
|
oe = find_oe_in_organization(oe_id, user.organization_id)
|
|
|
|
if oe is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
return templates.TemplateResponse(request, "struktur_loeschen.html", {"user": user, "oe": oe, "fehler": None})
|
|
|
|
|
|
@router.post("/struktur/{oe_id}/loeschen")
|
|
def oe_loeschen(request: Request, oe_id: str, user=Depends(get_current_user)):
|
|
oe = find_oe_in_organization(oe_id, user.organization_id)
|
|
|
|
if oe is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
result = anode_request("DELETE", f"/api/v1/organisationseinheiten/{oe_id}")
|
|
|
|
if not result.get("success"):
|
|
return templates.TemplateResponse(
|
|
request, "struktur_loeschen.html", {"user": user, "oe": oe, "fehler": result.get("message")}
|
|
)
|
|
|
|
return RedirectResponse("/struktur", status_code=303)
|
|
|
|
|
|
@router.get("/struktur/{oe_id}/merkmale")
|
|
def oe_merkmale_ansicht(request: Request, oe_id: str, user=Depends(get_current_user)):
|
|
oe = find_oe_in_organization(oe_id, user.organization_id)
|
|
|
|
if oe is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
result = anode_request("GET", f"/api/v1/organisationseinheiten/{oe_id}/merkmale")
|
|
merkmale = result.get("merkmale", []) if result.get("success") else []
|
|
|
|
kategorien: dict[str, list[dict]] = {}
|
|
for eintrag in merkmale:
|
|
kategorie_name = eintrag["kategorie"]["name"] if eintrag["kategorie"] else "Ohne Kategorie"
|
|
kategorien.setdefault(kategorie_name, []).append(eintrag)
|
|
|
|
return templates.TemplateResponse(
|
|
request, "struktur_merkmale.html", {"user": user, "oe": oe, "kategorien": kategorien}
|
|
)
|
|
|
|
|
|
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/select")
|
|
def oe_merkmal_select(oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
|
if find_oe_in_organization(oe_id, user.organization_id) is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/select")
|
|
|
|
return RedirectResponse(f"/struktur/{oe_id}/merkmale", status_code=303)
|
|
|
|
|
|
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/deselect")
|
|
def oe_merkmal_deselect(oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
|
if find_oe_in_organization(oe_id, user.organization_id) is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/deselect")
|
|
|
|
return RedirectResponse(f"/struktur/{oe_id}/merkmale", status_code=303)
|
|
|
|
|
|
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/unset")
|
|
def oe_merkmal_unset(oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
|
if find_oe_in_organization(oe_id, user.organization_id) is None:
|
|
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
|
|
|
anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/unset")
|
|
|
|
return RedirectResponse(f"/struktur/{oe_id}/merkmale", status_code=303)
|