"""Verwaltung der selbst definierten Felder je Kategorie.""" import json from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from ..database import get_db from ..deps import get_current_user, require_admin from ..models import Category, FieldDefinition, FieldType, ProductFieldValue, User from ..schemas import FieldDefinitionCreate, FieldDefinitionOut, FieldDefinitionUpdate from ..services.fields import options_list, slugify router = APIRouter(prefix="/field-definitions", tags=["field-definitions"]) def to_out(fd: FieldDefinition, inherited: bool = False) -> FieldDefinitionOut: return FieldDefinitionOut( id=fd.id, category_id=fd.category_id, label=fd.label, key=fd.key, field_type=FieldType(fd.field_type), unit=fd.unit, options=options_list(fd), required=fd.required, position=fd.position, is_builtin=fd.is_builtin, inherited=inherited, ) def _unique_key(db: Session, category_id: int, label: str, exclude_id: int | None = None) -> str: """Eindeutigen Schlüssel je Kategorie erzeugen (kapazitaet, kapazitaet_2, …).""" basis = slugify(label) kandidat = basis n = 1 while True: query = db.query(FieldDefinition).filter( FieldDefinition.category_id == category_id, FieldDefinition.key == kandidat ) if exclude_id is not None: query = query.filter(FieldDefinition.id != exclude_id) if query.first() is None: return kandidat n += 1 kandidat = f"{basis}_{n}" def _options_json(field_type: FieldType, options: list[str] | None) -> str | None: if field_type != FieldType.select: return None return json.dumps([o.strip() for o in (options or []) if o.strip()]) @router.get("", response_model=list[FieldDefinitionOut]) def list_field_definitions( category_id: int | None = None, db: Session = Depends(get_db), _: User = Depends(get_current_user), ) -> list[FieldDefinitionOut]: """Felder auflisten – ohne category_id alle, sonst nur die dieser Kategorie.""" query = db.query(FieldDefinition) if category_id is not None: query = query.filter(FieldDefinition.category_id == category_id) rows = query.order_by( FieldDefinition.category_id, FieldDefinition.position, FieldDefinition.id ).all() return [to_out(fd) for fd in rows] @router.post("", response_model=FieldDefinitionOut, status_code=status.HTTP_201_CREATED) def create_field_definition( payload: FieldDefinitionCreate, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> FieldDefinitionOut: if db.get(Category, payload.category_id) is None: raise HTTPException(status.HTTP_404_NOT_FOUND, "Kategorie nicht gefunden") fd = FieldDefinition( category_id=payload.category_id, label=payload.label.strip(), key=_unique_key(db, payload.category_id, payload.label), field_type=payload.field_type.value, unit=(payload.unit or None), options=_options_json(payload.field_type, payload.options), required=bool(payload.required), position=payload.position, ) db.add(fd) db.commit() db.refresh(fd) return to_out(fd) @router.patch("/{field_id}", response_model=FieldDefinitionOut) def update_field_definition( field_id: int, payload: FieldDefinitionUpdate, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> FieldDefinitionOut: fd = db.get(FieldDefinition, field_id) if fd is None: raise HTTPException(status.HTTP_404_NOT_FOUND, "Feld nicht gefunden") data = payload.model_dump(exclude_unset=True) if "label" in data and data["label"]: fd.label = data["label"].strip() fd.key = _unique_key(db, fd.category_id, fd.label, exclude_id=fd.id) if "field_type" in data and data["field_type"] is not None: fd.field_type = data["field_type"].value if "unit" in data: fd.unit = data["unit"] or None if "required" in data and data["required"] is not None: fd.required = bool(data["required"]) if "position" in data and data["position"] is not None: fd.position = data["position"] # Optionen immer passend zum (ggf. neuen) Typ ablegen. if "options" in data or "field_type" in data: neuer_typ = FieldType(fd.field_type) optionen = data["options"] if "options" in data else options_list(fd) fd.options = _options_json(neuer_typ, optionen) db.commit() db.refresh(fd) return to_out(fd) @router.delete("/{field_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_field_definition( field_id: int, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> None: """Löscht das Feld samt aller dazu erfassten Werte (ON DELETE CASCADE).""" fd = db.get(FieldDefinition, field_id) if fd is None: raise HTTPException(status.HTTP_404_NOT_FOUND, "Feld nicht gefunden") db.query(ProductFieldValue).filter( ProductFieldValue.field_definition_id == field_id ).delete() db.delete(fd) db.commit()