feat(identity): IDENT-003 konfigurierbare Nummernschemata je Objekttyp
CI / backend-tests (push) Successful in 2m24s
CI / frontend-build (push) Successful in 27s

Neue Stammdaten-Tabelle nummernschema (Migration 0032): Präfix + Stellenzahl
einmal je Objekttyp festlegen statt bei jeder Objekt-Anlage manuell
einzutippen (Fehlerquelle - Tippfehler, uneinheitliche Schreibweise,
falsches Präfix für den Typ). GET /objekte/naechster-code akzeptiert jetzt
objekttyp_id als Alternative zu praefix; ohne konfiguriertes Schema bleibt
der bisherige manuelle Präfix-Weg nutzbar (Rückwärtskompatibilität).

Admin-UI: Nummernschema-Pflege direkt in der Objekttyp-Verwaltung
(ObjekttypSection). ObjektAnlegenFormular versucht zuerst das Schema des
gewählten Objekttyps, fällt bei 404 auf manuelles Präfix zurück.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-08 20:23:14 +02:00
co-authored by Claude Sonnet 5
parent 15f783fb5e
commit 3b5cf8feab
12 changed files with 380 additions and 53 deletions
+25 -4
View File
@@ -12,7 +12,7 @@ from app.models.auth import KnotenTyp, RolleTyp, Systemknoten
from app.models.fahrzeugdetails import Fahrzeugdetails
from app.models.objekt import Objekt
from app.models.objektposition import Objektposition
from app.models.stammdaten import Material, Objekttyp
from app.models.stammdaten import Material, Nummernschema, Objekttyp
from app.models.vorlage import Beladungsvorlage
from app.services import historie as historie_service
from app.services.label import generiere_code128_label_pdf, generiere_qr_label_pdf
@@ -105,11 +105,32 @@ async def hole_objekt_per_code(
@router.get("/objekte/naechster-code")
async def naechster_objekt_code(
praefix: str, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
praefix: str | None = None,
objekttyp_id: int | None = None,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
) -> dict[str, str]:
"""Code-Vorschlag beim Anlegen/Duplizieren (Konzept-Ergänzung Karte 10/Prompt 09).
Vorschlag ist überschreibbar, Eindeutigkeit erzwingt weiterhin objekt.code UNIQUE."""
return {"code": await naechster_freier_code(db, Objekt.code, praefix)}
Vorschlag ist überschreibbar, Eindeutigkeit erzwingt weiterhin objekt.code UNIQUE.
IDENT-003: `objekttyp_id` statt `praefix` nutzt das konfigurierte
Nummernschema (Präfix + Mindest-Stellenzahl) - kein manuelles Eintippen/
Merken des richtigen Präfix je Objekttyp mehr nötig. `praefix` bleibt für
Objekttypen ohne konfiguriertes Schema nutzbar (Rückwärtskompatibilität)."""
stellenzahl = 5
if objekttyp_id is not None:
result = await db.execute(select(Nummernschema).where(Nummernschema.objekttyp_id == objekttyp_id))
schema = result.scalar_one_or_none()
if schema is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Kein Nummernschema für diesen Objekttyp konfiguriert - Präfix manuell angeben",
)
praefix = schema.praefix
stellenzahl = schema.stellenzahl
if not praefix:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Präfix oder Objekttyp angeben")
return {"code": await naechster_freier_code(db, Objekt.code, praefix, stellenzahl)}
@router.get("/objektpositionen/naechster-code")
+51 -1
View File
@@ -6,7 +6,7 @@ from app.api.deps import get_current_user, require_roles, require_roles_or_permi
from app.db.session import get_db
from app.models.auth import RolleTyp
from app.models.objekt import Objekt
from app.models.stammdaten import Bereich, Fach, Hersteller, Kategorie, Material, Modell, Objekttyp, Standort
from app.models.stammdaten import Bereich, Fach, Hersteller, Kategorie, Material, Modell, Nummernschema, Objekttyp, Standort
from app.models.vorlage import Beladungsvorlage, Vorlagenposition
from app.services.material_merge import finde_konflikte, fuehre_zusammen, hole_verwendung
from app.schemas.stammdaten import (
@@ -25,6 +25,9 @@ from app.schemas.stammdaten import (
KategorieUpdate,
ModellCreate,
ModellRead,
NummernschemaCreate,
NummernschemaRead,
NummernschemaUpdate,
MaterialCreate,
MaterialRead,
MaterialUpdate,
@@ -141,6 +144,53 @@ async def erstelle_modell(
return modell
# -- Nummernschema (IDENT-003) ---------------------------------------------------
@router.get("/nummernschemata", response_model=list[NummernschemaRead])
async def liste_nummernschemata(
db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
) -> list[Nummernschema]:
result = await db.execute(select(Nummernschema))
return list(result.scalars().all())
@router.post("/nummernschemata", response_model=NummernschemaRead, status_code=status.HTTP_201_CREATED)
async def erstelle_nummernschema(
payload: NummernschemaCreate,
db: AsyncSession = Depends(get_db),
_=Depends(require_roles(RolleTyp.administration)),
) -> Nummernschema:
if await db.get(Objekttyp, payload.objekttyp_id) is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekttyp nicht gefunden")
bestehendes = await db.execute(
select(Nummernschema).where(Nummernschema.objekttyp_id == payload.objekttyp_id)
)
if bestehendes.scalar_one_or_none() is not None:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Für diesen Objekttyp existiert bereits ein Nummernschema"
)
schema = Nummernschema(**payload.model_dump())
db.add(schema)
await db.flush()
return schema
@router.patch("/nummernschemata/{schema_id}", response_model=NummernschemaRead)
async def aendere_nummernschema(
schema_id: int,
payload: NummernschemaUpdate,
db: AsyncSession = Depends(get_db),
_=Depends(require_roles(RolleTyp.administration)),
) -> Nummernschema:
schema = await db.get(Nummernschema, schema_id)
if schema is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Nummernschema nicht gefunden")
for feld, wert in payload.model_dump(exclude_unset=True).items():
setattr(schema, feld, wert)
await db.flush()
return schema
# -- Kategorie ------------------------------------------------------------------
@router.get("/kategorien", response_model=list[KategorieRead])