wiki/b-reference/platform/mandate.md
2026-06-02 09:42:12 +02:00

6.2 KiB
Raw Blame History

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 232
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 232 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: platform-core/modules/shared/mandateNameUtils.py (Backend) und ui-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: platform-core/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: platform-core/modules/datamodels/datamodelUam.py (Pydantic-Modell), platform-core/modules/interfaces/interfaceDbApp.py (createMandate, updateMandate, _provisionMandateForUser, _generateUniqueMandateName), platform-core/modules/routes/routeDataMandates.py, platform-core/modules/shared/mandateNameUtils.py.
  • Frontend: ui-nyla/src/types/mandate.ts, ui-nyla/src/api/mandateApi.ts, ui-nyla/src/utils/slugUtils.ts, ui-nyla/src/utils/mandateNameUtils.ts, ui-nyla/src/utils/mandateDisplayUtils.ts, ui-nyla/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx (slug-Type Renderer).
  • Tests: platform-core/tests/unit/shared/test_mandateNameUtils.py, platform-core/tests/unit/bootstrap/test_mandateNameMigration.py, platform-core/tests/integration/mandates/.
  • Plan-Doku: wiki/c-work/1-plan/2026-04-mandate-name-label-logic.md.