Skip to content

Verantwortlichkeits-Scopes

Verantwortlichkeiten werden fachlich pro Scope getrennt verwaltet. Ein Scope steht für einen Einsatzbereich wie Raum allgemein, Elektroinstallationen, Schlüssel oder bauliche Veränderungen.

Der Server ist die Quelle der Wahrheit für die verfügbaren Scopes. Die App erwartet keine feste Picklist im Client mehr, sondern liest die Scope-Liste aus dem Setup-Payload.

Setup-Vertrag

Der Server muss im /setup-Payload diese Felder liefern:

  • responsibilityScopes: Liste der verfügbaren Scopes
  • responsibilitySettings: Pflichtfeld- und Vorauswahl-Einstellung je Scope
  • responsibilityOptions: Verantwortlichkeits-Einträge mit Scope-Zuordnung

Ein Scope-Eintrag besteht aus:

ts
{
  key: 'equipment_damage',
  label: 'Ausstattungsschäden',
  description: 'Verantwortlichkeiten für Schäden an Ausstattung und Inventar.',
  sort: 50,
  icon: 'i-heroicons-cube'
}

Wichtig:

  • key ist der technische, stabile Scope-Key.
  • label, description, sort und icon kommen vom Server und steuern die Settings-UI.
  • Der Client akzeptiert neue Scope-Keys als Strings. Für das reine Anzeigen in den Verantwortlichkeits-Einstellungen ist keine Client-Picklist zu erweitern.

Die zugehörigen Scope-Einstellungen haben diese Form:

ts
{
  scope: 'equipment_damage',
  required: false,
  defaultResponsibilityId: null
}

Verantwortlichkeits-Einträge werden separat pro Scope geführt. Wenn in der UI beim Anlegen mehrere Bereiche gewählt werden, erstellt der Client mehrere Datensätze mit gleichem Label, jeweils mit genau einem Scope:

ts
{
  id: 123,
  label: 'Mieter',
  scopeKeys: ['equipment_damage'],
  legacyField: 'tenant_equipment_damage',
  sortOrder: 10,
  isActive: true
}

Gleiche Labels dürfen in mehreren Scopes existieren. Mieter in key und Mieter in building_modification sind unterschiedliche Datensätze. Beim Anlegen in mehreren Scopes erstellt der Client mehrere eigenständige Einträge mit gleichem Label.

Neuen Scope serverseitig bereitstellen

Ein neuer Scope ist für die App bekannt, sobald der Server ihn in responsibilityScopes ausliefert und die API denselben Key in diesen Stellen akzeptiert:

  • responsibilityOptions[].scopeKeys
  • responsibilitySettings[].scope
  • PUT /responsibility-settings/{scope}
  • Create/Update der Verantwortlichkeiten über scopeKeys

Für lokale Entwicklung ist die Scope-Liste bewusst serverseitig gepflegt. Der Mock-Server lädt diese Scopes aus seinen Seed-Daten in eine eigene Tabelle; sie werden nicht über einen öffentlichen App-Endpunkt angelegt. Wenn dort ein neuer Scope ergänzt wird, erscheint er nach dem nächsten Setup-Reload automatisch als neuer Tab unter Einstellungen > Verantwortlichkeiten.

In einer Section verwenden

Eine bestehende oder neue Section verwendet einen Verantwortlichkeits-Scope erst dann, wenn ihre UI und ihr Datenmodell bewusst an diesen Scope gebunden werden.

Typische Voraussetzungen:

  • Entity-Schema enthält responsibilityId: number | null
  • Store und Backend-Payload persistieren responsibilityId
  • Formular nutzt den gewünschten Scope-Key für Optionen, Pflichtfeld und Vorauswahl
  • PDF- oder Tabellenanzeige löst responsibilityId wieder über den Setup-Store auf

Für eine tabellenbasierte Section sieht das Muster so aus:

ts
const responsibilityScope: ResponsibilityScopeKey = 'equipment_damage'

selectColumn<MyEntry, 'responsibilityId'>('responsibilityId', t('mySection.responsibility'), {
  update: myStore.updateFieldByKey,
  items: (entry) =>
    setupStore.getSelectableResponsibilityOptionsForScope(
      responsibilityScope,
      entry.responsibilityId,
    ),
  valueKey: 'id',
  labelKey: 'displayLabel',
  placeholder: t('mySection.responsibilityPlaceholder'),
  clear: !setupStore.isResponsibilityRequired(responsibilityScope),
})

Beim Anlegen neuer Einträge wird die Scope-Vorauswahl so gesetzt:

ts
myStore.updateField(
  entry.id,
  'responsibilityId',
  setupStore.getDefaultResponsibilityIdForScope(responsibilityScope),
)

Für Modal-Formulare ist das gleiche Prinzip maßgeblich: items kommt aus getSelectableResponsibilityOptionsForScope(...), required aus isResponsibilityRequired(...), und neue Einträge bekommen getDefaultResponsibilityIdForScope(...).

Section existiert noch nicht

Ein Scope kann serverseitig bereits existieren, bevor die dazugehörige Section in der App gebaut ist.

Dann gilt:

  • Die Verantwortlichkeiten und die Vorauswahl können in den Einstellungen schon gepflegt werden.
  • Die App zeigt den Scope in der Verantwortlichkeitsverwaltung, weil die Tabs aus /setup kommen.
  • Es gibt noch keine Protokoll-Erfassung für diesen Scope, bis eine Section oder ein Formular diesen Scope-Key verwendet.
  • Wenn die Section später ergänzt wird, nutzt sie denselben stabilen Scope-Key und erhält dadurch sofort die bereits gepflegten Einträge und Defaults.

PDF und Anzeige

In Tabellen, Zusammenfassungen und PDFs wird nicht der Scope-Key ausgegeben, sondern das Label der gewählten Verantwortlichkeit:

ts
setupStore.getResponsibilityOptionById(entry.responsibilityId)?.label

Wenn gleiche Labels innerhalb eines Scopes existieren, liefern Auswahlfelder zusätzlich displayLabel. Dieses Label dient nur der UI-Unterscheidung im Dropdown, zum Beispiel über legacyField oder ID. Für fachliche Ausgaben bleibt normalerweise label maßgeblich.

Löschen

Das Löschen einer Verantwortlichkeit entfernt den Lookup-Eintrag aus der Setup-Verwaltung. Ob vorhandene Protokollverweise oder Scope-Vorauswahlen dabei bereinigt werden, ist Backend-Vertrag; der Client patcht nach erfolgreicher Löschung nur die lokale Verantwortlichkeitsliste.

Legacy-Mapping

legacyField ist optional und dient nur zur späteren Zuordnung alter Daten. Bei gleichen fachlichen Labels in mehreren Scopes sollte legacyField pro Scope eindeutig gepflegt werden, zum Beispiel:

  • tenant_key
  • tenant_building_modification
  • tenant_equipment_damage