Zum Hauptinhalt springen

Polymorpher Parent-Pattern

Zweck

In SpeamCore sind viele Datensaetze nicht fest an einen Datentyp gebunden, sondern koennen flexibel zu mehreren passen. Das Schema heisst polymorpher Parent-Pattern und besteht aus zwei Spalten:

  • parentId — UUID des Eltern-Datensatzes.
  • parentType — Name des Datentyps des Eltern-Datensatzes, z. B. 'Customer', 'Location', 'SalesDocument'.

Damit kann z. B. ein Kontakt sowohl zu einem Kunden als auch zu einem Standort gehören, ohne zwei separate Tabellen zu brauchen.

Wo der Pattern verwendet wird

TabelleZweckErlaubte parentType
ContactParentKontakteCustomer, Location, Supplier, PurchaseDocument, SalesDocument
BankAssignmentBankverbindungenCustomer, Location
ProductSpecialConditionKunden-SonderkonditionenCustomer, Location
DocumentFolderDokumentenordnerCustomer, Location, Workorder, Project, Product, ...
DocumentParentDokumenten-Anhängejede Entitaet, die Anhänge fuehren kann
TextBlockParentTextbloecke (Belegtexte)SalesDocument, PurchaseDocument
AttributeParentstrukturierte Attribute (Adresse, Telefonnummern)viele
SalesDocument.parentId/parentTypeBeleg-EmpfaengerCustomer, Location
PurchaseDocument.parentId/parentTypeBeleg-EmpfaengerCustomer, Location, Warehouse
Letter.parentId/parentTypeBrief-Empfaenger (Welle 148)Customer, Location, Supplier, Manufacturer, Employee
BusinessCard.parentId/parentTypeVisitenkarten-Owner (Welle 137)Employee, Contact
CalendarPage.employeeIdBuchungsseite (Welle 138)nur Employee (kein echter polymorpher Parent — semantisch verwandt)
AiObservation.entityRefs[]Memory-Verknuepfungen (Welle 140)Liste polymorpher Refs zu beliebigen Entitaeten

Lese-Zugriff

Sie greifen auf polymorphe Verweise typischerweise so zu:

GET /api/contact-parents?parentId=:id&parentType=Customer
GET /api/document-parents?parentId=:id&parentType=Workorder
GET /api/text-block-parents?parentId=:id&parentType=SalesDocument

Das Backend filtert serverseitig auf den Polymorph-Typ, das Frontend zeigt nur die passenden Datensaetze an.

Adress-Denormalisierung

Einige Belege halten zusaetzlich eine Kopie der Adresse des Parents — z. B. SalesDocument.parentName/Street/Zip/City/CountryId oder Workorder.locationParent* und customerParent*.

Sinn:

  • Rechtssicherheit — Belege frieren die Adresse zum Belegdatum ein. Ein nachtraeglicher Adresswechsel am Kunden wirkt nicht auf alte Rechnungen.
  • Performance — keine Joins auf Customer/Location bei jedem Listen-Abruf.

Die Synchronisation passiert in den Lifecycle-Hooks beforeCreate / beforeUpdate der jeweiligen Belege (updateParentAddress, updateLocationAndCustomerAddress). Sie wird nicht automatisch nachgezogen, wenn sich die Quelladresse ändert — Änderungen am Kunden wirken erst auf neu erstellte Belege.

Auswirkungen auf Berechtigungen

Page-Guards muessen oft mehrere CASL-Subjects prüfen:

requiredAbility: [
['view', 'FE_PurchaseDocument'],
['view', 'PurchaseDocument'],
['view', 'TextBlockParent'], // wegen Tab "Textbloecke"
['view', 'DocumentParent'], // wegen Tab "Dokumente"
]

Wenn ein Anwender den Beleg sieht, aber den Tab Textbloecke leer findet, fehlt typischerweise TextBlockParent:view.

Drift-Risiken

  1. Adress-Drift: Adresse am Parent geändert → alte Belege zeigen die alte Adresse. Beabsichtigt für abgeschlossene Belege, aber stoerend für Entwurfs-Belege. Workaround: bei status = created die Adresse manuell aktualisieren.
  2. Cleanup beim Parent-Löschen: Wenn ein Kunde gelöscht wird, gehen polymorphe Einträge (ContactParent, BankAssignment etc.) nicht automatisch mit — parentId zeigt ins Leere. Prüfen: gibt es einen Bulk-Cleanup-Job?
  3. Index-Strategie: parentId-Indizes existieren, parentType ist häufig nicht indiziert. Bei sehr grossen Tabellen kann WHERE parentType = 'X' AND parentId = Y ineffizient werden.

Code-Lookup: Es gibt keinen automatischen Cleanup-Job für verwaiste polymorphe Verweise. In der Praxis fallen sie kaum ins Gewicht, weil die meisten Modelle paranoid: true (Soft-Delete) sind und parentId damit weiter aufloesbar bleibt. Bei tatsaechlichem Hard-Delete koennte ein periodischer Job über Cron-Jobs konfiguriert werden.

Praxis-Beispiel: Eine Adresse ändern

Sie ändern den Firmennamen eines Kunden von „Muster GmbH" auf „Muster Brandschutz GmbH". Auswirkungen:

DatensatzAktualisierung
Standorte (Location.customerId = K)nicht direkt — Standortname ist eigene Spalte
Aufträge bestehender StandortenichtWorkorder.customerParentName bleibt alter Wert
Verkaufsbelege im Status creatednicht automatisch — manuell speichern, Hook setzt parentName neu
Verkaufsbelege im Status sent / acceptednie — Beleg ist rechtlich an alten Stand gebunden
Auto-DocumentFolderjaafterUpdate-Hook synct den Foldernamen
ContactParent / BankAssignment / ProductSpecialConditionbleiben unveraendert (sie referenzieren nur per ID)

Verknuepfungen zu anderen Modulen

Praktisch alle Routen-Funktionsseiten (Kunden, Standorte, Belege, Aufträge, Produkte) referenzieren den Pattern. Pflicht-Lese-Reihenfolge für KIera AI:

  1. Berechtigungen verstehen (CASL)
  2. Diese Seite.
  3. Anschliessend die jeweilige Routen-Seite.

Versionshinweise

  • 2026-04-29: Initiale Veroeffentlichung.