Snapshot-Pattern (Stamm vs Verwendung)
Zweck
Ein durchgaengiges Designprinzip in SpeamCore: Stamm-Datensaetze werden zum Zeitpunkt der Verwendung kopiert, nicht referenziert. So bleiben bestehende Belege, Aufträge und Verträge stabil, auch wenn Stamm-Daten später geändert werden.
Dieses Pattern ist kritisch für:
- Buchhaltungs-/GoBD-Konformitaet (alte Belege bleiben unveraendert).
- Vertragsstabilitaet (ein Mitarbeiter-Vertrag aus 2020 hat 2026 noch dieselben Konditionen, auch wenn der Tarif geändert wurde).
- Inventur-Differenz-Rechnung (Bestand zum Inventur-Stichtag bleibt eingefroren).
Warum nicht einfach referenzieren?
Die Alternative waere productId als reiner Foreign-Key — dann wuerde der Beleg den aktuellen Produkt-Preis zeigen, auch wenn er vor zwei Jahren geschrieben wurde. Das waere:
- Buchhalterisch falsch (Beleg-Summe ändert sich rueckwirkend).
- Audit-untauglich (kein Beleg dafür was zur Belegerstellung galt).
- Verwirrend für Anwender (alte Rechnung zeigt neue Preise).
Daher: bei der Verwendung wird Stamm-Daten kopiert (Snapshot), bei Anzeige wird die Kopie angezeigt — nicht der Stamm.
Wo das Pattern in SpeamCore angewendet wird
Verkaufsbeleg-Position (SalesDocumentItem)
Beim Hinzufuegen eines Produkts zur Beleg-Position werden aus dem Produkt-Stamm kopiert:
priceNet(Verkaufspreis)taxRate(Steuersatz)unitId(Einheit)titleunddescription(bei „Produkt"-Typ; bei „Produkt benutzerdefiniert" sind es ueberschreibbare Custom-Werte)
Änderungen am Produkt-Stamm wirken nicht rueckwirkend auf bestehende Belege. Manuelles Re-Sync über den Toolbar-Button „Produktdaten aktualisieren" (siehe Belegpositionen-Toolbar) aktualisiert die Snapshots gezielt — sinnvoll bei Beleg-Entwuerfen, NICHT bei gesperrten Belegen.
Auftrag-Material (WorkorderMaterial)
Beim Hinzufuegen eines Materials werden Verkaufspreis, Einheit und Stammdaten als Snapshot übernommen. Änderungen am Produkt-Stamm wirken nicht rueckwirkend — siehe workorders-materials.
Auftrag-Adressen (Workorder.locationParent* / customerParent*)
Beim Anlegen eines Auftrags werden Adressfelder vom Standort und Kunde als Snapshot kopiert (locationParentName, locationParentStreet, ..., customerParentName, ...). Änderungen am Standort/Kunde wirken nicht auf bestehende Aufträge. Siehe workorders.
Vertrags-bezogene Auftrags-Defaults
Beim Auftrags-Anlegen werden branchId und employeeOfficeId aus dem aktiven Vertrag des Mitarbeiters kopiert. Bei spaeterem Vertragswechsel bleibt der Auftrag stabil — der neue Vertrag wirkt nur auf neue Aufträge. Siehe employees-contracts.
Verkaufsbeleg-Textbausteine (TextBlockParent)
Beim Verknuepfen eines Textbausteins zum Beleg wird der Stamm-Text als Snapshot kopiert. Beleg-spezifische Override-Texte bleiben editierbar, ohne den Stamm zu touchieren. Siehe sales-documents-text-blocks.
Sub-Unternehmer-Stundensaetze (ContractorEmployeeCostRate)
Beim Erfassen einer Arbeitszeit für einen Subunternehmer wird der zur Erfassungs-Zeit gültige rate aus ContractorEmployeeCostRate als Snapshot übernommen — Stundensatz-Änderungen wirken nicht rueckwirkend. Siehe contractors.
Inventur-Position (InventoryWarehouseProduct)
Beim Anlegen einer Inventur werden alle aktuellen Bestaende des Lagers als Snapshot kopiert (currentAmount zum Inventur-Beginn). Spaetere normale Lager-Bewegungen ändern die Inventur nicht. Siehe inventories.
Steuersatz-Gültigkeiten (TaxRateValidity)
Eine Belegposition speichert den Steuersatz als Snapshot zur Belegzeit. Änderungen am Steuersatz (z. B. 19% → 16% in 2020) wirken nur auf neue Belege, alte bleiben mit dem damaligen Satz stabil.
Diagramm: Stamm vs Snapshot beim Verkaufsbeleg
Wann KEIN Snapshot — sondern Live-Referenz?
Manche Bezuege sind absichtlich Live, weil eine rueckwirkende Änderung gewünscht ist:
| Bezug | Live oder Snapshot? | Warum |
|---|---|---|
Customer.id ↔ SalesDocument.parentId | Live | Die Kunden-Identitaet ist stabil — der Beleg bezieht sich immer auf denselben Kunden. |
Customer.address ↔ SalesDocument.customerParent* | Snapshot | Adressen ändern sich; auf alten Belegen muss die alte Adresse stehen bleiben. |
Employee.id ↔ Workorder.employeeId | Live | Der Mitarbeiter ist die Person — bleibt stabil. |
Employee.activeContract ↔ Workorder.branchId | Snapshot | Der zur Auftragszeit gültige Vertrag liefert die Niederlassung; spaeterer Vertragswechsel ändert das nicht. |
Product.id ↔ SalesDocumentItem.productId | Live | Wenn das Produkt gelöscht wird, soll der Beleg-Verweis ins Leere zeigen. |
Product.priceNet ↔ SalesDocumentItem.priceNet | Snapshot | Preis ändert sich; alter Beleg behaelt alten Preis. |
Verknuepfungen
- Verkaufsbelege —
documentType-Snapshot, Empfaenger-Snapshot. - Verkaufsbeleg-Positionen — Produkt-Snapshot.
- Aufträge — Adress-Snapshot, Vertrags-Snapshot.
- Auftrag-Material — Produkt-Snapshot.
- Inventuren — Bestand-Snapshot.
- Mitarbeiter-Vertraege — Vertrags-Snapshot.
Versionshinweise
- 2026-04-30: Initiale Veroeffentlichung — durchgaengiges Stamm-vs-Snapshot-Pattern dokumentiert mit allen Anwendungsfaellen.