96 lines
6.1 KiB
Markdown
96 lines
6.1 KiB
Markdown
<!-- status: canonical -->
|
||
<!-- lastReviewed: 2026-04-18 -->
|
||
<!-- verifiedAgainst: gateway + frontend_nyla (codebase audit 2026-04-18) -->
|
||
|
||
# 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 `name` als 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:** `name` ist URL-tauglich (lowercase, keine Leerzeichen, keine Sonderzeichen). Zukuenftige Subdomain- oder Pfad-Routing-Features koennen `name` direkt 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`:
|
||
|
||
1. **Transliteration**: `ae | oe | ue | ss` fuer `ä | ö | ü | ß` (auch Grossvarianten).
|
||
2. **Lowercasen** und alles, was nicht `[a-z0-9]` ist, zu `-` ersetzen.
|
||
3. **Bindestriche kollabieren** und an Raendern trimmen.
|
||
4. **Min-Laenge sichern** (Auffuellen mit `x`).
|
||
5. **Max-Laenge erzwingen** (an letztem Bindestrich ueberhalb der Laengenschranke abschneiden).
|
||
6. **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:
|
||
|
||
1. Wenn `label` leer/None → `label := name`.
|
||
2. Wenn `name` nicht regex-konform → Slug aus `label` generieren mit Kollisions-Suffix.
|
||
3. Stable order ueber `id`-String, deterministisch.
|
||
4. 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-Util `mandateDisplayUtils.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`.
|