6.1 KiB
Mandate-Identifier: name (Kurzzeichen) und label (Voller Name)
Ueberblick
Ein Mandate (Mandant / Tenant) hat zwei semantisch getrennte Bezeichner und einen technischen Primaerschluessel:
| Feld | Rolle | Aenderbarkeit | Format |
|---|---|---|---|
id |
Technischer PK / FK-Anker (UUID) | Niemals (auch nicht durch SysAdmin) | UUID v4 |
name |
Kurzzeichen — global eindeutiger Slug, technischer Identifier in URLs/Logs/Audit | Nur PlatformAdmin oder SysAdmin | ^[a-z0-9]+(-[a-z0-9]+)*$, Laenge 2–32 |
label |
Voller Name — Anzeige-Name fuer das UI | MandateAdmin und hoeher | non-empty String, beliebige Zeichen |
id bleibt der einzige FK-Anker. Saemtliche Tabellen (Role.mandateId, UserMandate.mandateId, MandateSubscription.mandateId, BillingSettings.mandateId, …) referenzieren id, nicht name. Rebranding (Label-Wechsel) bricht damit niemals Audit-Trails oder Foreign Keys.
Warum zwei Felder?
- Audit-Stabilitaet: Audit-Reports und Logs zeigen
nameals kompakten, stabilen Code. Wenn ein Kunde sein Anzeige-Label aendert ("Mueller AG" → "Mueller Holding"), bleibt der Code (mueller-ag) gleich. Sonst wuerden historische Reports unleserlich werden. - URL-/API-Friendliness:
nameist URL-tauglich (lowercase, keine Leerzeichen, keine Sonderzeichen). Zukuenftige Subdomain- oder Pfad-Routing-Features koennennamedirekt verwenden. - Klare Rollen-Trennung: MandateAdmin darf sein Label umbenennen, ohne den globalen Identifier zu beeinflussen; nur Plattform-Governance darf den Identifier touchieren.
Format-Regeln
name muss erfuellen:
- nur
a-z,0-9,- - nicht mit
-beginnen oder enden - keine doppelten Bindestriche
- Laenge 2–32 Zeichen
label muss:
- nicht-leer (nach Trim)
- darf alle Zeichen enthalten (Umlaute, Akzente, Punkte, Leerzeichen)
Slug-Generierung (Auto-Allokation)
Wenn name bei einem POST nicht angegeben oder leer ist, generiert der Server ihn aus label:
- Transliteration:
ae | oe | ue | ssfuerä | ö | ü | ß(auch Grossvarianten). - Lowercasen und alles, was nicht
[a-z0-9]ist, zu-ersetzen. - Bindestriche kollabieren und an Raendern trimmen.
- Min-Laenge sichern (Auffuellen mit
x). - Max-Laenge erzwingen (an letztem Bindestrich ueberhalb der Laengenschranke abschneiden).
- Kollisions-Suffix
-2,-3, … wenn der Basis-Slug bereits existiert.
Beispiele:
| Label | Erzeugter name |
|---|---|
Müller AG |
mueller-ag |
Home Patrick.Möller |
home-patrick-moeller |
Müller AG (zweites Mal) |
mueller-ag-2 |
(leer) |
mn (Fallback) |
Code: gateway/modules/shared/mandateNameUtils.py (Backend) und frontend_nyla/src/utils/slugUtils.ts + mandateNameUtils.ts (Frontend, mirror).
RBAC und Editierbarkeit
| Operation | Rolle | Verhalten |
|---|---|---|
POST /api/mandates/ |
PlatformAdmin | label mandatory; name optional → Server generiert |
PUT /api/mandates/{id} name |
PlatformAdmin oder SysAdmin | Format + Uniqueness validiert (excl. self) |
PUT /api/mandates/{id} name |
MandateAdmin | Ignoriert (Whitelist _MANDATE_ADMIN_EDITABLE_FIELDS = {"label"}) |
PUT /api/mandates/{id} label |
MandateAdmin oder hoeher | Pflicht: nicht-leer (nach Trim) |
PUT /api/mandates/{id} isSystem |
SysAdmin | Nur SysAdmin |
DELETE /api/mandates/{id}?force=true |
PlatformAdmin (X-Confirm-Name=name) |
Hard-Delete mit Cascade |
Im UI ist das Kurzzeichen-Feld in den Mandate-Formularen vom Typ slug (Live-Maskierung, Auto-Vorschlag aus label im Create-Modus, im Edit fuer non-PlatformAdmin disabled).
Bootstrap-Migration
interfaceBootstrap._migrateMandateNameLabelSlugRules() laeuft idempotent beim Boot und bringt Bestandsdaten auf das neue Schema:
- Wenn
labelleer/None →label := name. - Wenn
namenicht regex-konform → Slug auslabelgenerieren mit Kollisions-Suffix. - Stable order ueber
id-String, deterministisch. - Zweiter Lauf ist No-op.
Tests: gateway/tests/unit/bootstrap/test_mandateNameMigration.py.
UI-Anzeige (Konvention)
- Listen / Auswahlen / Header →
mandateDisplayLabel(m)=label || name || id(Frontend-UtilmandateDisplayUtils.ts). - Detail-Pages mit Identifier-Bezug →
mandateDisplayLineLabelThenSlug(m)=Voller Name (kurzzeichen)wenn beide vorhanden und unterschiedlich. - Hard-Delete-Confirm → fragt explizit nach
name(Kurzzeichen) ab.
Verwandte Dateien
- Backend:
gateway/modules/datamodels/datamodelUam.py(Pydantic-Modell),gateway/modules/interfaces/interfaceDbApp.py(createMandate,updateMandate,_provisionMandateForUser,_generateUniqueMandateName),gateway/modules/routes/routeDataMandates.py,gateway/modules/shared/mandateNameUtils.py. - Frontend:
frontend_nyla/src/types/mandate.ts,frontend_nyla/src/api/mandateApi.ts,frontend_nyla/src/utils/slugUtils.ts,frontend_nyla/src/utils/mandateNameUtils.ts,frontend_nyla/src/utils/mandateDisplayUtils.ts,frontend_nyla/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx(slug-Type Renderer). - Tests:
gateway/tests/unit/shared/test_mandateNameUtils.py,gateway/tests/unit/bootstrap/test_mandateNameMigration.py,gateway/tests/integration/mandates/. - Plan-Doku:
wiki/c-work/1-plan/2026-04-mandate-name-label-logic.md.