diff --git a/arbeitskacheln/00_index.md b/arbeitskacheln/00_index.md index 98e3d7e..508afa6 100644 --- a/arbeitskacheln/00_index.md +++ b/arbeitskacheln/00_index.md @@ -135,7 +135,7 @@ vergeben statt geraten. | FOUND-012 | Foundation | Dokumentationsgerüst | P1 | XS | LOW | FOUND-001 | ✅ (`README.md` im Projektwurzelverzeichnis) | | IDENT-001 | Identity | Objekt-ID-Schema | P0 | S | LOW | FOUND-002 | ✅ (Objekt-Code-Vergabe, `/objekte/naechster-code`) | | IDENT-002 | Identity | Inventarnummer manuell | P0 | S | LOW | IDENT-001 | ✅ (Objektposition-Code, `objektposition.py`) | -| IDENT-003 | Identity | Inventarnummer-Nummernschemata | P1 | M | MEDIUM | IDENT-002 | ⬜ (kein konfigurierbares Schema gefunden, nur fester Code-Zähler) | +| IDENT-003 | Identity | Inventarnummer-Nummernschemata | P1 | M | MEDIUM | IDENT-002 | ✅ (`Nummernschema` je Objekttyp, Migration 0032, `/objekte/naechster-code?objekttyp_id=` nutzt es, Admin-UI in ObjekttypSection) | | IDENT-004 | Identity | Seriennummer + Eindeutigkeitsprüfung | P0 | S | LOW | IDENT-001 | ✅ (`geraet_instanz.py` Seriennummer-Feld/Unique) | | IDENT-005 | Identity | Hersteller/Modell-Stammdaten | P1 | S | LOW | IDENT-004 | ✅ (`Hersteller`/`Modell`-Tabellen, Migration 0030, `geraet_instanz.modell_id` optional; kein eigenes Admin-UI, nur API) | | IDENT-006 | Identity | QR-Code-Erzeugung | P0 | S | LOW | IDENT-001 | ✅ (`label.pdf`-Endpunkte für Objekt/Objektposition) | diff --git a/backend/alembic/versions/0032_nummernschema.py b/backend/alembic/versions/0032_nummernschema.py new file mode 100644 index 0000000..803425a --- /dev/null +++ b/backend/alembic/versions/0032_nummernschema.py @@ -0,0 +1,31 @@ +"""IDENT-003: Nummernschema je Objekttyp (Präfix + Stellenzahl konfigurierbar). + +Revision ID: 0032_nummernschema +Revises: 0031_einsatzfunktion +Create Date: 2026-09-08 +""" +from typing import Sequence, Union + +from alembic import op + +revision: str = "0032_nummernschema" +down_revision: Union[str, None] = "0031_einsatzfunktion" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute( + """ + CREATE TABLE nummernschema ( + id SERIAL PRIMARY KEY, + objekttyp_id INTEGER NOT NULL UNIQUE REFERENCES objekttyp(id), + praefix TEXT NOT NULL, + stellenzahl INTEGER NOT NULL DEFAULT 5 + ) + """ + ) + + +def downgrade() -> None: + op.execute("DROP TABLE nummernschema") diff --git a/backend/app/api/v1/endpoints/objekte.py b/backend/app/api/v1/endpoints/objekte.py index 5354ad2..fe77f6b 100644 --- a/backend/app/api/v1/endpoints/objekte.py +++ b/backend/app/api/v1/endpoints/objekte.py @@ -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") diff --git a/backend/app/api/v1/endpoints/stammdaten.py b/backend/app/api/v1/endpoints/stammdaten.py index d11cba1..9db3f6c 100644 --- a/backend/app/api/v1/endpoints/stammdaten.py +++ b/backend/app/api/v1/endpoints/stammdaten.py @@ -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]) diff --git a/backend/app/models/stammdaten.py b/backend/app/models/stammdaten.py index f0cde2b..337f849 100644 --- a/backend/app/models/stammdaten.py +++ b/backend/app/models/stammdaten.py @@ -1,6 +1,6 @@ import enum -from sqlalchemy import Boolean, ForeignKey, String, Text, UniqueConstraint +from sqlalchemy import Boolean, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.dialects.postgresql import ENUM as PgEnum from sqlalchemy.orm import Mapped, mapped_column @@ -54,6 +54,24 @@ class Objekttyp(Base): ist_zugfahrzeug: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) +class Nummernschema(Base): + """IDENT-003: statt bei jeder Objekt-Anlage das Präfix manuell einzutippen + (Fehlerquelle - Tippfehler, uneinheitliche Groß-/Kleinschreibung, falsches + Präfix für den Objekttyp) legt der Admin je Objekttyp einmal Präfix + + Stellenzahl fest. `naechster_freier_code` schlägt darauf aufbauend den + nächsten Code vor (PRAEFIX-NNN...), wie bisher, nur nicht mehr frei + eingebbar. Ein Objekttyp OHNE Schema kann weiterhin mit explizitem Präfix + angelegt werden (Rückwärtskompatibilität, kein Zwang zur Migration + bestehender Objekte).""" + + __tablename__ = "nummernschema" + + id: Mapped[int] = mapped_column(primary_key=True) + objekttyp_id: Mapped[int] = mapped_column(ForeignKey("objekttyp.id"), unique=True, nullable=False) + praefix: Mapped[str] = mapped_column(String, nullable=False) + stellenzahl: Mapped[int] = mapped_column(Integer, nullable=False, default=5) + + class Fach(Base): """Nutzer-Vorgabe: feste Fächer-Liste je Objekttyp, damit beim Anlegen einer Vorlagenposition nicht jedes Mal Freitext getippt werden muss. `sortierung` diff --git a/backend/app/schemas/stammdaten.py b/backend/app/schemas/stammdaten.py index 57b8f0a..efe08b6 100644 --- a/backend/app/schemas/stammdaten.py +++ b/backend/app/schemas/stammdaten.py @@ -54,6 +54,22 @@ class ModellRead(ModellCreate): id: int +class NummernschemaCreate(BaseModel): + objekttyp_id: int + praefix: str + stellenzahl: int = 5 + + +class NummernschemaRead(NummernschemaCreate): + model_config = ConfigDict(from_attributes=True) + id: int + + +class NummernschemaUpdate(BaseModel): + praefix: str | None = None + stellenzahl: int | None = None + + class StandortCreate(BaseModel): name: str adresse: str | None = None diff --git a/backend/app/services/objekte.py b/backend/app/services/objekte.py index 9736970..008a72d 100644 --- a/backend/app/services/objekte.py +++ b/backend/app/services/objekte.py @@ -13,14 +13,16 @@ from app.models.vorlage import Beladungsvorlage, Vorlagenposition _CODE_MUSTER = re.compile(r"^-(\d+)$") -async def naechster_freier_code(db: AsyncSession, spalte, praefix: str) -> str: +async def naechster_freier_code(db: AsyncSession, spalte, praefix: str, mindestbreite: int = 5) -> str: """Konzept-Ergänzung Karte 10/Prompt 09: Code-Vorschlag PRAEFIX-NNN mit fortlaufender Nummer je Präfix. Zahlenbreite folgt der bisher größten - vergebenen Nummer (mind. 5 Stellen bei Erstvergabe), damit die Sortierung im - Regal stabil bleibt, solange nicht mehr als 10^Breite Objekte existieren.""" + vergebenen Nummer (mindestens `mindestbreite` bei Erstvergabe - IDENT-003: + kommt aus dem konfigurierten Nummernschema, falls vorhanden, sonst 5 wie + bisher), damit die Sortierung im Regal stabil bleibt, solange nicht mehr + als 10^Breite Objekte existieren.""" result = await db.execute(select(spalte).where(spalte.like(f"{praefix}-%"))) hoechste = 0 - breite = 5 + breite = mindestbreite for (code,) in result.all(): treffer = _CODE_MUSTER.match(code[len(praefix) :]) if treffer is None: diff --git a/backend/tests/test_objekte.py b/backend/tests/test_objekte.py index f3be65c..57cde26 100644 --- a/backend/tests/test_objekte.py +++ b/backend/tests/test_objekte.py @@ -262,6 +262,71 @@ async def test_naechster_objekt_code_zaehlt_hoch( assert response.json()["code"] == "RK-00018" +@pytest.mark.asyncio +async def test_nummernschema_liefert_code_ohne_manuelles_praefix( + client, objekttyp_factory, admin_user +): + """IDENT-003: Präfix + Stellenzahl kommen aus dem konfigurierten Schema, + nicht mehr manuell eingegeben.""" + objekttyp = await objekttyp_factory("Typ-Nummernschema") + token = await login(client, "admin1") + + schema = await client.post( + "/api/v1/nummernschemata", + json={"objekttyp_id": objekttyp.id, "praefix": "SEG", "stellenzahl": 3}, + headers=auth_header(token), + ) + assert schema.status_code == 201 + + duplikat = await client.post( + "/api/v1/nummernschemata", + json={"objekttyp_id": objekttyp.id, "praefix": "ANDERES"}, + headers=auth_header(token), + ) + assert duplikat.status_code == 409 + + vorschlag = await client.get( + f"/api/v1/objekte/naechster-code?objekttyp_id={objekttyp.id}", headers=auth_header(token) + ) + assert vorschlag.status_code == 200 + assert vorschlag.json()["code"] == "SEG-001" + + aenderung = await client.patch( + f"/api/v1/nummernschemata/{schema.json()['id']}", + json={"stellenzahl": 4}, + headers=auth_header(token), + ) + assert aenderung.json()["stellenzahl"] == 4 + + +@pytest.mark.asyncio +async def test_nummernschema_ohne_konfiguration_verlangt_praefix( + client, objekttyp_factory, admin_user +): + objekttyp = await objekttyp_factory("Typ-Ohne-Schema") + token = await login(client, "admin1") + + ohne_schema = await client.get( + f"/api/v1/objekte/naechster-code?objekttyp_id={objekttyp.id}", headers=auth_header(token) + ) + assert ohne_schema.status_code == 404 + + ohne_beides = await client.get("/api/v1/objekte/naechster-code", headers=auth_header(token)) + assert ohne_beides.status_code == 422 + + +@pytest.mark.asyncio +async def test_mitarbeiter_darf_nummernschema_nicht_anlegen(client, objekttyp_factory, mitarbeiter_user): + objekttyp = await objekttyp_factory("Typ-Nummernschema-Verboten") + token = await login(client, "mitarbeiter1") + response = await client.post( + "/api/v1/nummernschemata", + json={"objekttyp_id": objekttyp.id, "praefix": "X"}, + headers=auth_header(token), + ) + assert response.status_code == 403 + + @pytest.mark.asyncio async def test_naechster_objektposition_code( client, db_session, objekt_mit_position, materialverantwortlicher_user diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 66d20b4..ff554dd 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -302,6 +302,13 @@ export interface Objekttyp { ist_zugfahrzeug: boolean; } +export interface Nummernschema { + id: number; + objekttyp_id: number; + praefix: string; + stellenzahl: number; +} + export interface Fach { id: number; objekttyp_id: number; diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx index e83f936..58e6501 100644 --- a/frontend/src/pages/AdminPage.tsx +++ b/frontend/src/pages/AdminPage.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from "react-router-dom"; import { apiRequest } from "../api/client"; import { AusgabeSection } from "./admin/AusgabeSection"; import { useAuth } from "../auth/AuthContext"; -import type { Benutzer, Bereich, Beladungsvorlage, Einheit, Einsatzfunktion, Fach, Kategorie, Material, Objekt, Objekttyp, Qualifikationstyp, Standort } from "../api/types"; +import type { Benutzer, Bereich, Beladungsvorlage, Einheit, Einsatzfunktion, Fach, Kategorie, Material, Nummernschema, Objekt, Objekttyp, Qualifikationstyp, Standort } from "../api/types"; import { BenutzerSection } from "./admin/BenutzerSection"; import { BereichSection } from "./admin/BereichSection"; import { EinheitSection } from "./admin/EinheitSection"; @@ -109,6 +109,7 @@ export function AdminPage() { const [faecher, setFaecher] = useState([]); const [einheiten, setEinheiten] = useState([]); const [einsatzfunktionen, setEinsatzfunktionen] = useState([]); + const [nummernschemata, setNummernschemata] = useState([]); const [qualifikationstypen, setQualifikationstypen] = useState([]); const [fehler, setFehler] = useState(null); const [laedt, setLaedt] = useState(true); @@ -128,7 +129,7 @@ export function AdminPage() { // Seite als "nicht geladen" zeigen, obwohl deren Tabs (z.B. Kontroll- // verantwortung) trotzdem funktionieren sollen. allSettled + leerer // Fallback statt hartem Fehler. - const [be, ka, ot, s, v, o, m, b, f, ei, qt, ef] = await Promise.allSettled([ + const [be, ka, ot, s, v, o, m, b, f, ei, qt, ef, ns] = await Promise.allSettled([ apiRequest("/bereiche"), apiRequest("/kategorien"), apiRequest("/objekttypen"), @@ -141,6 +142,7 @@ export function AdminPage() { apiRequest("/einheiten"), apiRequest("/qualifikationstypen"), apiRequest("/einsatzfunktionen"), + apiRequest("/nummernschemata"), ]); const wert = (r: PromiseSettledResult): T[] => (r.status === "fulfilled" ? r.value : []); setBereiche(wert(be)); @@ -155,6 +157,7 @@ export function AdminPage() { setEinheiten(wert(ei)); setQualifikationstypen(wert(qt)); setEinsatzfunktionen(wert(ef)); + setNummernschemata(wert(ns)); } useEffect(() => { @@ -226,6 +229,7 @@ export function AdminPage() { bereiche={bereiche} kategorien={kategorien} objekttypen={objekttypen} + nummernschemata={nummernschemata} onGeaendert={ladeAlles} onFehler={setFehler} /> diff --git a/frontend/src/pages/admin/ObjektAnlegenFormular.tsx b/frontend/src/pages/admin/ObjektAnlegenFormular.tsx index 9b45605..b3a1764 100644 --- a/frontend/src/pages/admin/ObjektAnlegenFormular.tsx +++ b/frontend/src/pages/admin/ObjektAnlegenFormular.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { apiRequest } from "../../api/client"; +import { apiRequest, ApiError } from "../../api/client"; import type { Beladungsvorlage, Objekt, Objekttyp, Standort } from "../../api/types"; interface Props { @@ -30,8 +30,27 @@ export function ObjektAnlegenFormular({ objekte, objekttypen, vorlagen, standort }); async function codeVorschlagen() { + const objekttypId = vorlagen.find((v) => v.id === vorlageId)?.objekttyp_id; + // IDENT-003: erst versuchen, das für den Objekttyp konfigurierte + // Nummernschema zu nutzen (kein manuelles Präfix nötig) - nur wenn keins + // konfiguriert ist (404), auf das Präfix-Feld zurückfallen. + if (objekttypId !== undefined) { + try { + const antwort = await apiRequest<{ code: string }>( + `/objekte/naechster-code?objekttyp_id=${objekttypId}` + ); + setCode(antwort.code); + return; + } catch (fehler) { + if (!(fehler instanceof ApiError && fehler.status === 404)) { + onFehler("Code-Vorschlag fehlgeschlagen."); + return; + } + // kein Nummernschema für diesen Objekttyp -> weiter mit manuellem Präfix + } + } if (!praefix) { - onFehler("Präfix eingeben (z. B. RK, FZ)."); + onFehler("Kein Nummernschema für diesen Objekttyp konfiguriert - Präfix eingeben (z. B. RK, FZ)."); return; } try { diff --git a/frontend/src/pages/admin/ObjekttypSection.tsx b/frontend/src/pages/admin/ObjekttypSection.tsx index 44c7dca..ff7dc6d 100644 --- a/frontend/src/pages/admin/ObjekttypSection.tsx +++ b/frontend/src/pages/admin/ObjekttypSection.tsx @@ -1,17 +1,25 @@ import { useState } from "react"; import { apiRequest } from "../../api/client"; -import type { Bereich, Kategorie, Objekttyp } from "../../api/types"; +import type { Bereich, Kategorie, Nummernschema, Objekttyp } from "../../api/types"; interface Props { bereiche: Bereich[]; kategorien: Kategorie[]; objekttypen: Objekttyp[]; + nummernschemata: Nummernschema[]; onGeaendert: () => Promise; onFehler: (text: string) => void; } -export function ObjekttypSection({ bereiche, kategorien, objekttypen, onGeaendert, onFehler }: Props) { +export function ObjekttypSection({ + bereiche, + kategorien, + objekttypen, + nummernschemata, + onGeaendert, + onFehler, +}: Props) { const [name, setName] = useState(""); const [bereichId, setBereichId] = useState(""); const [kategorieId, setKategorieId] = useState(""); @@ -20,6 +28,13 @@ export function ObjekttypSection({ bereiche, kategorien, objekttypen, onGeaender const [bearbeitungsName, setBearbeitungsName] = useState(""); const [istZugfahrzeug, setIstZugfahrzeug] = useState(false); const [bearbeitungsZugfahrzeug, setBearbeitungsZugfahrzeug] = useState(false); + const [schemaBearbeitenId, setSchemaBearbeitenId] = useState(null); + const [schemaPraefix, setSchemaPraefix] = useState(""); + const [schemaStellenzahl, setSchemaStellenzahl] = useState(5); + + function schemaFuer(objekttypId: number): Nummernschema | undefined { + return nummernschemata.find((n) => n.objekttyp_id === objekttypId); + } function bearbeiten(o: Objekttyp) { setBearbeiteteId(o.id); @@ -61,6 +76,38 @@ export function ObjekttypSection({ bereiche, kategorien, objekttypen, onGeaender } } + function schemaBearbeiten(objekttypId: number) { + const bestehendes = schemaFuer(objekttypId); + setSchemaBearbeitenId(objekttypId); + setSchemaPraefix(bestehendes?.praefix ?? ""); + setSchemaStellenzahl(bestehendes?.stellenzahl ?? 5); + } + + async function schemaSpeichern(objekttypId: number) { + if (!schemaPraefix.trim()) { + onFehler("Präfix eingeben."); + return; + } + const bestehendes = schemaFuer(objekttypId); + try { + if (bestehendes) { + await apiRequest(`/nummernschemata/${bestehendes.id}`, { + method: "PATCH", + body: { praefix: schemaPraefix.toUpperCase(), stellenzahl: schemaStellenzahl }, + }); + } else { + await apiRequest("/nummernschemata", { + method: "POST", + body: { objekttyp_id: objekttypId, praefix: schemaPraefix.toUpperCase(), stellenzahl: schemaStellenzahl }, + }); + } + setSchemaBearbeitenId(null); + await onGeaendert(); + } catch { + onFehler("Nummernschema konnte nicht gespeichert werden."); + } + } + return (

Objekttyp anlegen

@@ -105,43 +152,90 @@ export function ObjekttypSection({ bereiche, kategorien, objekttypen, onGeaender )} {objekttypen.length > 0 && (
    - {objekttypen.map((o) => ( -
  • - {bearbeiteteId === o.id ? ( -
    - setBearbeitungsName(e.target.value)} - /> - - - + {objekttypen.map((o) => { + const schema = schemaFuer(o.id); + return ( +
  • +
    + {bearbeiteteId === o.id ? ( +
    + setBearbeitungsName(e.target.value)} + /> + + + +
    + ) : ( + <> + + {o.name} + {o.ist_zugfahrzeug && (Zugfahrzeug)} + + + + )}
    - ) : ( - <> - - {o.name} - {o.ist_zugfahrzeug && (Zugfahrzeug)} - - - - )} -
  • - ))} +
    + {schemaBearbeitenId === o.id ? ( + <> + + setSchemaPraefix(e.target.value)} + placeholder="Präfix" + /> + setSchemaStellenzahl(Number(e.target.value) || 5)} + /> + + + + ) : ( + <> + + Nummernschema:{" "} + {schema ? `${schema.praefix}-${"0".repeat(schema.stellenzahl)}` : "keins konfiguriert"} + + + + )} +
    + + ); + })}
)}