diff --git a/backend/docs/mvp/sequence-diagrams-e2e.md b/backend/docs/mvp/sequence-diagrams-e2e.md new file mode 100644 index 0000000..08da945 --- /dev/null +++ b/backend/docs/mvp/sequence-diagrams-e2e.md @@ -0,0 +1,615 @@ +# Effigenix ERP – End-to-End Real-Life Workflows + +> Reale Tagesabläufe einer Fleischerei, BC-übergreifend. +> Alle Diagramme nutzen Mermaid-Syntax. + +--- + +## Inhaltsverzeichnis + +1. [Morgenroutine: Wareneingang Frischfleisch](#1-morgenroutine-wareneingang-frischfleisch) +2. [Tagesproduktion: Bratwurst herstellen](#2-tagesproduktion-bratwurst-herstellen) +3. [Wochenplanung & Bedarfsermittlung](#3-wochenplanung--bedarfsermittlung) +4. [Rückruf-Szenario: Salmonellen beim Lieferanten](#4-rückruf-szenario-salmonellen-beim-lieferanten) +5. [Qualitätsproblem in der Produktion](#5-qualitätsproblem-in-der-produktion) +6. [MHD-Management: Ablaufende Ware & Abverkauf](#6-mhd-management-ablaufende-ware--abverkauf) +7. [Inventur: Monatliche Bestandszählung](#7-inventur-monatliche-bestandszählung) +8. [Zentrale Produktion & Filialbelieferung](#8-zentrale-produktion--filialbelieferung) + +--- + +## 1. Morgenroutine: Wareneingang Frischfleisch + +**Szenario:** Montag, 5:30 Uhr. Der LKW von Lieferant „Müller Fleisch" kommt mit 200 kg Schweineschulter und 80 kg Rindfleisch. Der Wareneingangs-Mitarbeiter muss die Lieferung prüfen, dokumentieren und einbuchen. + +**Beteiligte BCs:** Procurement, Quality, Inventory, Master Data + +```mermaid +sequenceDiagram + actor Fahrer as LKW-Fahrer (Müller Fleisch) + actor WE as Wareneingangs-Mitarbeiter + participant MD as Master Data BC + participant Proc as Procurement BC
(PurchaseOrder, GoodsReceipt) + participant QM as Quality BC
(GoodsReceiptInspection) + participant Inv as Inventory BC
(Stock, StockMovement) + + Note over Fahrer,Inv: 🕠 05:30 – LKW-Anlieferung + + Fahrer->>WE: Lieferschein übergeben
(200kg Schweineschulter, 80kg Rind,
Charge "MF-2026-0847", MHD 2026-03-03) + + WE->>Proc: Wareneingang erfassen + Note right of Proc: GoodsReceipt anlegen
Referenz: PurchaseOrder #PO-2026-041
Lieferanten-Charge: MF-2026-0847
MHD: 2026-03-03 + + Note over WE,QM: 🌡️ HACCP-Pflichtprüfung + + WE->>QM: Qualitätsprüfung starten + Note right of QM: GoodsReceiptInspection erstellt + + WE->>QM: 1. Temperaturmessung + Note right of QM: Kerntemperatur Schwein: 2,1°C ✅
Kerntemperatur Rind: 3,4°C ✅
(Grenzwert: ≤ 4°C für Frischfleisch) + + WE->>QM: 2. Sichtkontrolle + Note right of QM: Verpackung: unbeschädigt ✅
Farbe: frisch, keine Verfärbung ✅
Geruch: neutral, arttypisch ✅ + + WE->>QM: 3. MHD-Prüfung + Note right of QM: MHD 2026-03-03 → noch 7 Tage ✅
(Mindest-Restlaufzeit: 3 Tage) + + WE->>QM: 4. Dokumentenprüfung + Note right of QM: Lieferschein: vorhanden ✅
Veterinärbescheinigung: vorhanden ✅
Identitätskennzeichen: DE NW 20145 ✅ + + QM-->>QM: Alle Prüfungen bestanden + QM->>Proc: GoodsReceiptAccepted + Note right of Proc: GoodsReceipt Status → ACCEPTED
PurchaseOrder → FULLY_RECEIVED + + Note over WE,Inv: 📦 Bestand einbuchen + + Proc->>Inv: Event: GoodsReceiptAccepted + Inv->>Inv: Stock.addBatch(Schweineschulter) + Note right of Inv: StockBatch: MF-2026-0847
BatchType: PURCHASED
200 kg, MHD 2026-03-03
Lagerort: KÜHLRAUM_1 + + Inv->>Inv: Stock.addBatch(Rindfleisch) + Note right of Inv: StockBatch: MF-2026-0847-R
100 kg, MHD 2026-03-03 + + Inv->>Inv: StockMovement.record(GOODS_RECEIPT, IN) + Note right of Inv: 2× Bewegungen protokolliert
(immutable Audit-Trail) + + Note over WE,Inv: ✅ 05:45 – Ware eingelagert, System aktuell +``` + +### Sonderfall: Prüfung fehlgeschlagen + +```mermaid +sequenceDiagram + actor WE as Wareneingangs-Mitarbeiter + actor Fahrer as LKW-Fahrer + participant QM as Quality BC + participant Proc as Procurement BC + + Note over WE,Proc: ❌ Temperatur zu hoch + + WE->>QM: Temperaturmessung: 7,2°C + Note right of QM: GRENZWERT ÜBERSCHRITTEN!
Max. erlaubt: 4°C
Gemessen: 7,2°C + + QM->>QM: TemperatureCriticalLimitExceeded + QM->>Proc: GoodsReceiptRejected + Note right of Proc: Grund: Kühlkette unterbrochen
GoodsReceipt Status → REJECTED
PurchaseOrder bleibt CONFIRMED + + WE->>Fahrer: Ware zurückweisen + Note right of WE: Ablehnungsprotokoll erstellt
Fotos dokumentiert
Lieferant wird informiert + + Note over WE,Proc: Kein Bestand eingebucht!
Reklamation beim Lieferant +``` + +--- + +## 2. Tagesproduktion: Bratwurst herstellen + +**Szenario:** Dienstag, 6:00 Uhr. Der Metzgermeister soll laut Wochenplan 150 kg Nürnberger Bratwurst herstellen. Rezept „R-NB-001" (Ausbeute 85%, Haltbarkeit 5 Tage). Zutaten: Schweineschulter, Speck, Gewürzmischung, Naturdärme. + +**Beteiligte BCs:** Production, Inventory, (Labeling, Quality) + +```mermaid +sequenceDiagram + actor PL as Produktionsleiter + actor MM as Metzgermeister + participant PO as Production BC
(ProductionOrder) + participant Batch as Production BC
(Batch) + participant Recipe as Production BC
(Recipe) + participant Stock as Inventory BC
(Stock) + participant SM as Inventory BC
(StockMovement) + participant Label as Labeling BC
(ProductLabel) + + Note over PL,Label: 🕕 06:00 – Produktionsauftrag vorbereiten + + PL->>PO: Produktionsauftrag anlegen + Note right of PO: Rezept: R-NB-001 (Nürnberger)
Menge: 150 kg
Datum: 2026-02-24
Priorität: NORMAL
Status → PLANNED + + PL->>PO: Auftrag freigeben + Note right of PO: Prüfung: Rezept R-NB-001 ist ACTIVE ✅
Status → RELEASED + + PL->>PO: Produktion starten + PO->>Batch: Charge automatisch erstellen + Note right of Batch: BatchNumber: P-2026-02-24-001
Geplante Menge: 150 kg
Status → IN_PRODUCTION + PO-->>PO: Status → IN_PRODUCTION
BatchId verknüpft + + Note over MM,SM: 🥩 06:15 – Material vorbereiten & reservieren + + PO->>Recipe: Materialbedarf berechnen + Note right of Recipe: Ausbeute 85% → Bedarf = 150/0,85 = 176 kg
Schweineschulter: 60% → 106 kg
Speck: 25% → 44 kg
Gewürzmischung: 10% → 18 kg
Naturdärme: 5% → 9 kg + + MM->>Stock: Material reservieren (FEFO) + Note right of Stock: Schweineschulter: 106 kg reservieren + Stock->>Stock: FEFO-Allokation + Note right of Stock: Charge MF-2026-0847 (MHD 03.03): 106 kg ✅
(älteste Charge zuerst) + Stock-->>MM: Reservierung erstellt + + MM->>Stock: Speck reservieren + Note right of Stock: Charge SP-2026-0812 (MHD 05.03): 44 kg ✅ + + MM->>Stock: Gewürze + Därme reservieren + Note right of Stock: GW-2026-0099 (MHD 15.08): 18 kg ✅
ND-2026-0201 (MHD 20.04): 9 kg ✅ + + Note over MM,SM: 🔪 06:30 – Produktion & Entnahme + + MM->>Stock: Reservierungen bestätigen (entnehmen) + Stock->>SM: StockMovement(PRODUCTION_CONSUMPTION, OUT) + Note right of SM: 4× Entnahme-Bewegungen protokolliert + + MM->>Batch: Verbrauch dokumentieren + Note right of Batch: Consumption 1: MF-2026-0847, 106 kg Schweineschulter
Consumption 2: SP-2026-0812, 44 kg Speck
Consumption 3: GW-2026-0099, 18 kg Gewürze
Consumption 4: ND-2026-0201, 9 kg Därme + Note right of Batch: → Chargen-Genealogie aufgebaut!
Rückverfolgbar bis zum Lieferanten + + Note over MM,Label: ⚖️ 09:30 – Produktion abschließen + + MM->>Batch: Charge abschließen + Note right of Batch: Ist-Menge: 148 kg (gewogen)
Verschnitt: 3,5 kg
Ausbeute real: 148/177 = 83,6%
MHD berechnet: 2026-02-24 + 5 = 2026-03-01
Status → COMPLETED + + Batch->>Stock: Event: BatchCompleted + Stock->>Stock: addBatch(P-2026-02-24-001) + Note right of Stock: Neuer StockBatch:
148 kg Nürnberger Bratwurst
BatchType: PRODUCED
MHD: 2026-03-01
Lagerort: KÜHLRAUM_2 + Stock->>SM: StockMovement(PRODUCTION_OUTPUT, IN) + + PL->>PO: Produktionsauftrag abschließen + Note right of PO: Prüfung: Charge COMPLETED ✅
Status → COMPLETED + + Note over MM,Label: 🏷️ Etikett steht bereit + + Note right of Label: ProductLabel für Nürnberger verfügbar:
Zutaten: Schweinefleisch (60%),
Speck (25%), Gewürze...
Allergene: SELLERIE, SENF (auto)
MHD: 01.03.2026
Nährwerte pro 100g berechnet +``` + +--- + +## 3. Wochenplanung & Bedarfsermittlung + +**Szenario:** Freitag Nachmittag. Der Produktionsleiter plant die Produktion für nächste Woche. Er prüft Bestände, legt Aufträge an und das System ermittelt automatisch den Materialbedarf und löst Bestellungen aus. + +**Beteiligte BCs:** Production, Inventory, Procurement, Master Data + +```mermaid +sequenceDiagram + actor PL as Produktionsleiter + actor EK as Einkäufer + participant PO as Production BC
(ProductionOrder) + participant Recipe as Production BC
(Recipe) + participant Stock as Inventory BC
(Stock) + participant Demand as Procurement BC
(DemandPlan) + participant Order as Procurement BC
(PurchaseOrder) + participant MD as Master Data BC
(Supplier) + + Note over PL,MD: 📋 Freitag 14:00 – Wochenplanung + + PL->>Stock: Aktuelle Bestände prüfen + Stock-->>PL: Bestandsübersicht: + Note right of Stock: Schweineschulter: 94 kg (MHD 03.03)
Speck: 36 kg (MHD 05.03)
Rindfleisch: 80 kg (MHD 03.03)
Gewürze: 45 kg (MHD 15.08)
⚠️ Schweineschulter unter Mindestbestand (100 kg)! + + PL->>PO: Aufträge für nächste Woche anlegen + Note right of PO: Mo: 200 kg Bratwurst (R-NB-001)
Di: 100 kg Leberwurst (R-LW-003)
Mi: 150 kg Wiener (R-WI-002)
Do: 80 kg Leberkäse (R-LK-005) + + PL->>PO: Alle Aufträge freigeben + PO->>PO: 4× Status → RELEASED + + Note over PL,MD: 📊 Bedarfsermittlung + + PO->>Demand: Event: ProductionOrderReleased (4×) + Demand->>Recipe: Materialbedarf aus Rezepten berechnen + Note right of Recipe: Mo Bratwurst: 235 kg Rohstoffe
Di Leberwurst: 125 kg Rohstoffe
Mi Wiener: 180 kg Rohstoffe
Do Leberkäse: 100 kg Rohstoffe + + Demand->>Stock: Aktuelle Bestände abfragen + Stock-->>Demand: Verfügbare Mengen + + Demand->>Demand: Bedarfsplan generieren + Note right of Demand: Schweineschulter:
Bedarf: 320 kg
Bestand: 94 kg
Fehlmenge: 226 kg
+ Sicherheitsbestand: 50 kg
→ Bestellvorschlag: 276 kg

Speck:
Bedarf: 95 kg
Bestand: 36 kg
→ Bestellvorschlag: 109 kg

Rindfleisch:
Bedarf: 60 kg
Bestand: 80 kg
→ kein Bedarf ✅

Gewürze: → kein Bedarf ✅ + + Note over EK,MD: 🛒 Bestellungen aufgeben + + EK->>Demand: Bedarfsplan prüfen & genehmigen + Note right of Demand: Status → APPROVED + + EK->>MD: Lieferantenkonditionen prüfen + MD-->>EK: Müller Fleisch: Mindestbestellmenge 50 kg,
Lieferzeit 1 Tag, Preis 5,80€/kg + + EK->>Order: Bestellung anlegen + Note right of Order: PurchaseOrder #PO-2026-048
Lieferant: Müller Fleisch
Pos 1: 280 kg Schweineschulter (≥ MBM)
Pos 2: 110 kg Speck
Lieferdatum: Montag 05:00
Gesamtwert: 2.262,00€
Status → ORDERED + + EK->>Order: Bestellung an Lieferant senden + Note right of Order: Status → CONFIRMED
(Lieferant hat bestätigt) + + Note over PL,MD: ✅ Planung abgeschlossen
Mo-Lieferung und Produktion gesichert +``` + +--- + +## 4. Rückruf-Szenario: Salmonellen beim Lieferanten + +**Szenario:** Mittwoch, 10:00 Uhr. Das Veterinäramt informiert, dass bei Lieferant „Müller Fleisch" Salmonellen in der Charge „MF-2026-0847" (Schweineschulter, geliefert am Montag) nachgewiesen wurden. Sofortmaßnahmen sind nötig. + +**Beteiligte BCs:** Quality, Production (Traceability), Inventory, Filiales + +```mermaid +sequenceDiagram + actor Amt as Veterinäramt + actor QM as Qualitätsmanager + actor FL as Filialleiter (3 Filialen) + participant Trace as Production BC
(TraceabilityService) + participant Quality as Quality BC
(QualityHold) + participant Stock as Inventory BC
(Stock) + participant SM as Inventory BC
(StockMovement) + participant Branch as Filiales BC + + Note over Amt,Branch: ☎️ 10:00 – Veterinäramt meldet Kontamination + + Amt->>QM: Salmonellen in Charge MF-2026-0847
(Schweineschulter, Müller Fleisch) + + Note over QM,Branch: 🔍 10:05 – Vorwärts-Tracing starten + + QM->>Trace: traceForward("MF-2026-0847") + Trace->>Trace: Suche: Welche Chargen verwenden MF-2026-0847? + + Trace-->>QM: Betroffene Chargen ermittelt: + Note right of Trace: Stufe 1 – Direkte Verwendung:
P-2026-02-24-001: 148 kg Nürnberger Bratwurst
P-2026-02-24-003: 95 kg Leberwurst

Stufe 2 – Weiterverarbeitung:
(keine – Endprodukte) + + QM->>SM: Wo ist die Ware jetzt? + SM-->>QM: Bestandsbewegungen: + Note right of SM: P-2026-02-24-001 (Bratwurst 148kg):
→ 50 kg Filiale Hauptstraße (verkauft: 12 kg)
→ 40 kg Filiale Marktplatz (verkauft: 8 kg)
→ 58 kg Zentrale (Lager, unverkauft)

P-2026-02-24-003 (Leberwurst 95kg):
→ 95 kg Zentrale (Lager, unverkauft)

Rohstoff-Rest:
→ 0 kg MF-2026-0847 (vollständig verbraucht) + + Note over QM,Branch: 🚨 10:15 – Sofortige Qualitätssperre + + QM->>Quality: QualityHold initiieren + Note right of Quality: Grund: CONTAMINATION_SUSPECTED
Referenz: Veterinäramt-Meldung #VA-2026-0412 + + Quality->>Stock: Event: QualityHoldCreated (Bratwurst) + Stock->>Stock: blockBatch(P-2026-02-24-001) + Note right of Stock: Bratwurst: AVAILABLE → BLOCKED
Kann nicht mehr verkauft/entnommen werden + + Quality->>Stock: Event: QualityHoldCreated (Leberwurst) + Stock->>Stock: blockBatch(P-2026-02-24-003) + Note right of Stock: Leberwurst: AVAILABLE → BLOCKED + + Note over QM,Branch: 📞 10:20 – Filialen informieren + + QM->>Branch: Betroffene Filialen ermitteln + Branch-->>QM: Filiale Hauptstraße, Filiale Marktplatz + + QM->>FL: SOFORT: Bratwurst Charge P-2026-02-24-001
aus Theke nehmen! Gesperrt wegen
Salmonellen-Verdacht Rohstoff. + + FL->>Stock: Ware aus Verkauf entfernt + Note right of Stock: Physisch separiert & gekennzeichnet + + Note over QM,Branch: 📋 10:30 – Rückruf-Dokumentation + + QM->>Quality: Korrekturmaßnahmen dokumentieren + Note right of Quality: 1. Alle betroffenen Chargen gesperrt ✅
2. Filialen informiert ✅
3. Ware aus Verkauf entfernt ✅
4. Veterinäramt Rückmeldung: 20 kg verkauft
5. Kundeninformation vorbereitet + + QM->>Quality: Bereits verkaufte Ware: + Note right of Quality: 20 kg verkauft (12+8 kg)
→ Kundenrückruf-Aushang in Filialen
→ Social-Media-Warnung
→ Veterinäramt informiert + + Note over QM,Branch: 🧪 Nächste Tage – Laborergebnis abwarten + + alt Laborergebnis: Keine Salmonellen in Endprodukt + Note over QM,Branch: ✅ Entwarnung + actor Approver as Chef (≠ QM) + QM->>Quality: Entwarnung dokumentieren + Approver->>Quality: QualityHold freigeben (4-Augen) + Note right of Quality: initiatedBy ≠ releasedBy ✅ + Quality->>Stock: Event: QualityHoldReleased + Stock->>Stock: unblockBatch(P-2026-02-24-001) + Stock->>Stock: unblockBatch(P-2026-02-24-003) + Note right of Stock: Status: BLOCKED → AVAILABLE
Ware wieder verkaufbar + else Laborergebnis: Salmonellen bestätigt + Note over QM,Branch: ❌ Vernichtung + QM->>Quality: Ablehnung dokumentieren + Quality->>Stock: Event: QualityHoldRejected + Stock->>Stock: removeBatch(P-2026-02-24-001, FULL) + Stock->>Stock: removeBatch(P-2026-02-24-003, FULL) + Stock->>SM: StockMovement(WASTE, OUT) + Note right of SM: 2× Vernichtungs-Buchungen
Grund: Salmonellen-Kontamination
Referenz: VA-2026-0412 + end +``` + +--- + +## 5. Qualitätsproblem in der Produktion + +**Szenario:** Während der Bratwurstproduktion misst der Metzgermeister bei der Kerntemperaturprüfung nach dem Brühen 65°C statt der geforderten mindestens 72°C. Das CCP (Critical Control Point) ist nicht erreicht. + +**Beteiligte BCs:** Quality, Production, Inventory + +```mermaid +sequenceDiagram + actor MM as Metzgermeister + actor QM as Qualitätsmanager + actor Chef as Betriebsleiter + participant Batch as Production BC
(Batch) + participant QC as Quality BC
(ProcessParameter) + participant QH as Quality BC
(QualityHold) + participant Stock as Inventory BC + + Note over MM,Stock: 🌡️ 08:45 – CCP-Messung nach Brühvorgang + + MM->>QC: Kerntemperatur messen + Note right of QC: CCP: CORE_TEMPERATURE
Charge: P-2026-02-24-002 (Wiener)
Gemessen: 65°C
Akzeptabler Bereich: 72–78°C
Status: OUT_OF_SPEC ❌ + + QC->>QC: DeviationRecorded + Note right of QC: Abweichung: -7°C unter Minimum + + Note over MM,Stock: 🚨 Automatische Eskalation + + QC->>QH: QualityHold automatisch erstellt + Note right of QH: Grund: CCP_DEVIATION
Charge: P-2026-02-24-002
Details: Kerntemp 65°C < 72°C Minimum + + MM->>QM: Abweichung melden + + Note over QM,Stock: 🔧 Korrekturmaßnahme + + QM->>QH: Korrekturmaßnahme festlegen + Note right of QH: Maßnahme: Erneutes Brühen
auf 75°C Kerntemperatur
Verantwortlich: Metzgermeister
Frist: sofort + + MM->>Batch: Erneut brühen (Prozess wiederholen) + + MM->>QC: Zweite Kerntemperatur-Messung + Note right of QC: CCP: CORE_TEMPERATURE
Gemessen: 74°C
Akzeptabler Bereich: 72–78°C
Status: IN_SPEC ✅ + + QM->>QH: Korrekturmaßnahme dokumentieren + Note right of QH: Ergebnis: Erfolgreich nachgebrüht
Zweitmessung: 74°C ✅ + + Chef->>QH: QualityHold freigeben + Note right of QH: 4-Augen: QM initiiert, Chef gibt frei ✅ + + Note over MM,Stock: ✅ Produktion kann fortgesetzt werden + + MM->>Batch: Charge abschließen + Note right of Batch: Alle CCP-Werte dokumentiert
Korrekturmaßnahme nachvollziehbar
Status → COMPLETED + + Batch->>Stock: Event: BatchCompleted + Note right of Stock: Ware eingebucht
Audit-Trail lückenlos +``` + +--- + +## 6. MHD-Management: Ablaufende Ware & Abverkauf + +**Szenario:** Der tägliche System-Job (6:00 Uhr) erkennt, dass 25 kg Leberwurst morgen ablaufen und 40 kg Aufschnitt in 2 Tagen. Das Team muss reagieren. + +**Beteiligte BCs:** Inventory, (Sales, Labeling – noch nicht implementiert, daher vereinfacht) + +```mermaid +sequenceDiagram + participant Cron as Täglicher Scheduler
(06:00 Uhr) + participant Stock as Inventory BC
(Stock) + actor VK as Verkaufsleiter + actor Theke as Theken-Mitarbeiter + participant SM as Inventory BC
(StockMovement) + + Note over Cron,SM: ⏰ 06:00 – Tägliche MHD-Prüfung + + Cron->>Stock: markExpiredBatches(today = 2026-02-24) + + Stock->>Stock: Alle Chargen prüfen + Note right of Stock: ❌ EXPIRED (MHD überschritten):
LW-Charge P-2026-02-19-004: 3 kg Weißwurst
(MHD war 2026-02-23 → gestern abgelaufen)

⚠️ EXPIRING_SOON (innerhalb MinimumShelfLife):
LW-Charge P-2026-02-20-002: 25 kg Leberwurst
(MHD 2026-02-25 → morgen, MinShelfLife=2d)

AS-Charge P-2026-02-21-001: 40 kg Aufschnitt
(MHD 2026-02-26 → übermorgen) + + Stock-->>Stock: Events publiziert: + Note right of Stock: BatchExpired(Weißwurst, 3kg)
BatchExpiringSoon(Leberwurst, 25kg, 1 Tag)
BatchExpiringSoon(Aufschnitt, 40kg, 2 Tage) + + Note over VK,SM: 📊 06:30 – Verkaufsleiter prüft Warnungen + + VK->>Stock: MHD-Warnungen abrufen + Stock-->>VK: 3 Positionen mit Handlungsbedarf + + Note over VK,SM: 🗑️ Abgelaufene Ware entsorgen + + VK->>Theke: Weißwurst 3 kg entsorgen + Theke->>Stock: removeBatch(P-2026-02-19-004, 3kg) + Stock->>SM: StockMovement(WASTE, OUT) + Note right of SM: Grund: MHD abgelaufen
3 kg Weißwurst vernichtet + + Note over VK,SM: 💰 Bald ablaufende Ware rabattieren + + VK->>Theke: Leberwurst 25 kg: 30% Rabatt-Etikett + Note right of Theke: Preisreduzierung im Verkauf
"Heute 30% günstiger" + + VK->>Theke: Aufschnitt 40 kg: Aktionsplatzierung + Note right of Theke: Sichtbare Platzierung in Theke
Ggf. morgen rabattieren + + Note over VK,SM: 🔄 Bei Verkauf normale Entnahme (FEFO) + + Note right of SM: FEFO stellt automatisch sicher,
dass älteste Chargen zuerst
entnommen werden – auch
im regulären Verkauf +``` + +--- + +## 7. Inventur: Monatliche Bestandszählung + +**Szenario:** Letzter Freitag im Monat. Inventur im Kühlraum 1 (Rohstoffe). Der Lagermitarbeiter zählt, der Betriebsleiter genehmigt. Abweichungen werden automatisch ausgeglichen. + +**Beteiligte BCs:** Inventory + +```mermaid +sequenceDiagram + actor Lager as Lagermitarbeiter (Zähler) + actor Chef as Betriebsleiter (Prüfer) + participant IC as Inventory BC
(InventoryCount) + participant Stock as Inventory BC
(Stock) + participant SM as Inventory BC
(StockMovement) + + Note over Lager,SM: 📋 Freitag 16:00 – Inventur starten + + Lager->>IC: Inventur anlegen für KÜHLRAUM_1 + IC->>Stock: Aktuelle Soll-Bestände abrufen + Stock-->>IC: Bestände nach Artikel: + Note right of IC: Soll-Bestände auto-befüllt:
Schweineschulter: 94,0 kg
Rindfleisch: 80,0 kg
Speck: 36,0 kg
Hackfleisch: 22,5 kg
Status → COUNTING + + Note over Lager,SM: 📦 16:15 – Physische Zählung + + Lager->>IC: Schweineschulter: 91,5 kg (Ist) + Note right of IC: Soll: 94,0 kg → Differenz: -2,5 kg + + Lager->>IC: Rindfleisch: 80,0 kg (Ist) + Note right of IC: Soll: 80,0 kg → Differenz: 0 kg ✅ + + Lager->>IC: Speck: 37,2 kg (Ist) + Note right of IC: Soll: 36,0 kg → Differenz: +1,2 kg + + Lager->>IC: Hackfleisch: 20,0 kg (Ist) + Note right of IC: Soll: 22,5 kg → Differenz: -2,5 kg + + Note over Lager,SM: ✍️ 16:45 – Zählung einreichen + + Lager->>IC: Zählung einreichen (submitForApproval) + Note right of IC: Status → SUBMITTED
Zähler: Lagermitarbeiter + + Note over Chef,SM: 👀 17:00 – 4-Augen-Prüfung + + Chef->>IC: Abweichungen prüfen + Note right of IC: Abweichungen:
Schweineschulter: -2,5 kg (Schwund?)
Speck: +1,2 kg (Wiegefehler?)
Hackfleisch: -2,5 kg (nicht erfasste Entnahme?) + + Chef->>IC: Inventur genehmigen + Note right of IC: Prüfung: Chef ≠ Lagermitarbeiter ✅ (4-Augen)
Status → COMPLETED + + Note over Chef,SM: ⚖️ Automatische Ausgleichsbuchungen + + IC->>SM: Ausgleich Schweineschulter + Note right of SM: StockMovement(ADJUSTMENT, OUT)
-2,5 kg Schweineschulter
Grund: Inventurdifferenz + + IC->>SM: Ausgleich Speck + Note right of SM: StockMovement(ADJUSTMENT, IN)
+1,2 kg Speck
Grund: Inventurdifferenz + + IC->>SM: Ausgleich Hackfleisch + Note right of SM: StockMovement(ADJUSTMENT, OUT)
-2,5 kg Hackfleisch
Grund: Inventurdifferenz + + IC->>Stock: Bestände korrigiert + Note right of Stock: Schweineschulter: 94,0 → 91,5 kg
Rindfleisch: 80,0 kg (unverändert)
Speck: 36,0 → 37,2 kg
Hackfleisch: 22,5 → 20,0 kg + + Note over Lager,SM: ✅ 17:10 – Inventur abgeschlossen
Alle Bewegungen im Audit-Trail +``` + +--- + +## 8. Zentrale Produktion & Filialbelieferung + +**Szenario:** Die Zentrale (Hauptfiliale mit Produktion) stellt morgens 300 kg Bratwurst her und verteilt sie an 3 Filialen. Der Transport muss chargengenau dokumentiert werden für die Rückverfolgbarkeit. + +**Beteiligte BCs:** Production, Inventory, Filiales + +```mermaid +sequenceDiagram + actor PL as Produktionsleiter (Zentrale) + actor FL_A as Filialleiter Hauptstraße + actor FL_B as Filialleiter Marktplatz + actor Fahrer as Fahrer/Auslieferer + participant Batch as Production BC
(Batch) + participant DP as Filiales BC
(DistributionPlan) + participant IBT as Filiales BC
(InterBranchTransfer) + participant Stock_Z as Inventory BC
(Stock – Zentrale) + participant Stock_A as Inventory BC
(Stock – Hauptstraße) + participant Stock_B as Inventory BC
(Stock – Marktplatz) + participant SM as Inventory BC
(StockMovement) + + Note over PL,SM: 🏭 06:00-09:00 – Zentrale Produktion + + PL->>Batch: Charge P-2026-02-24-001 abschließen + Note right of Batch: 300 kg Bratwurst
MHD: 2026-03-01
Status → COMPLETED + + Batch->>Stock_Z: Event: BatchCompleted + Stock_Z->>Stock_Z: addBatch(P-2026-02-24-001, 300 kg) + Note right of Stock_Z: 300 kg im Zentrallager + + Note over PL,SM: 📦 09:15 – Verteilplan erstellen + + PL->>DP: Verteilplan anlegen + Note right of DP: Charge: P-2026-02-24-001 (300 kg)
Produzierende Filiale: Zentrale

Verteilung:
→ Hauptstraße: 100 kg
→ Marktplatz: 80 kg
→ Verbleibt Zentrale: 120 kg

Summe: 300 kg ✅ + + Note over PL,SM: 🚚 09:30 – Transfers anlegen + + DP->>IBT: Transfer Zentrale → Hauptstraße + Note right of IBT: 100 kg Bratwurst
Charge: P-2026-02-24-001
MHD: 2026-03-01
Status → REQUESTED + + DP->>IBT: Transfer Zentrale → Marktplatz + Note right of IBT: 80 kg Bratwurst
Charge: P-2026-02-24-001
MHD: 2026-03-01
Status → REQUESTED + + FL_A->>IBT: Transfer genehmigen + Note right of IBT: Status → APPROVED + FL_B->>IBT: Transfer genehmigen + + Note over Fahrer,SM: 🚚 10:00 – Auslieferung + + Fahrer->>IBT: Versand bestätigen (beide) + IBT->>Stock_Z: Event: TransferShipped + Stock_Z->>Stock_Z: removeBatch(P-2026-02-24-001, 100 kg) + Stock_Z->>Stock_Z: removeBatch(P-2026-02-24-001, 80 kg) + Stock_Z->>SM: 2× StockMovement(INTER_BRANCH_TRANSFER, OUT) + Note right of Stock_Z: Zentrale: 300 → 120 kg + + Note over Fahrer,SM: 📍 10:30 – Ankunft Hauptstraße + + FL_A->>IBT: Empfang bestätigen (98 kg erhalten) + Note right of IBT: Geplant: 100 kg
Empfangen: 98 kg
(2 kg Transportverlust)
Status → RECEIVED + + IBT->>Stock_A: Event: TransferReceived + Stock_A->>Stock_A: addBatch(P-2026-02-24-001, 98 kg) + Stock_A->>SM: StockMovement(INTER_BRANCH_TRANSFER, IN) + Note right of Stock_A: Hauptstraße: 0 → 98 kg Bratwurst
Gleiche Charge-ID! Rückverfolgbar. + + Note over Fahrer,SM: 📍 11:00 – Ankunft Marktplatz + + FL_B->>IBT: Empfang bestätigen (80 kg erhalten) + Note right of IBT: Geplant: 80 kg, Empfangen: 80 kg ✅ + + IBT->>Stock_B: Event: TransferReceived + Stock_B->>Stock_B: addBatch(P-2026-02-24-001, 80 kg) + Stock_B->>SM: StockMovement(INTER_BRANCH_TRANSFER, IN) + Note right of Stock_B: Marktplatz: 0 → 80 kg Bratwurst + + DP->>DP: Alle Lieferungen abgeschlossen + Note right of DP: Status → COMPLETED + + Note over PL,SM: ✅ Ergebnis: 300 kg verteilt + Note right of SM: Zentrale: 120 kg
Hauptstraße: 98 kg (2 kg Schwund)
Marktplatz: 80 kg

Chargen-ID überall identisch!
→ Bei Rückruf: alle 3 Filialen
sofort identifizierbar +``` + +--- + +## Workflow-Zusammenfassung + +| # | Workflow | Beteiligte BCs | Status | +|---|---------|----------------|--------| +| 1 | Wareneingang Frischfleisch | Procurement, Quality, Inventory | Quality/Procurement: geplant, Inventory: implementiert | +| 2 | Tagesproduktion Bratwurst | Production, Inventory, Labeling | Production: teilw. impl., Inventory: implementiert, Labeling: geplant | +| 3 | Wochenplanung & Bedarfsermittlung | Production, Inventory, Procurement | Procurement: geplant | +| 4 | Rückruf-Szenario | Quality, Production, Inventory, Filiales | Tracing: offen (#43/#44), Quality: geplant | +| 5 | Qualitätsproblem Produktion | Quality, Production | Quality: geplant | +| 6 | MHD-Management | Inventory | Implementiert (#10, #11) | +| 7 | Inventur | Inventory | Offen (#17–#20) | +| 8 | Zentrale Produktion & Filialbelieferung | Production, Inventory, Filiales | Filiales: geplant | + +### Legende + +| Symbol | Bedeutung | +|--------|-----------| +| `actor` | Menschlicher Akteur mit Rolle | +| `participant` | Bounded Context + Aggregate | +| `Note` | Fachliche Details, Validierungen, Berechnungen | +| `alt/else` | Entscheidungspunkt | +| `→ Event:` | Domain Event zwischen BCs | +| ✅ | Prüfung bestanden / Aktion erfolgreich | +| ❌ | Prüfung fehlgeschlagen / Fehler | +| ⚠️ | Warnung / Handlungsbedarf | diff --git a/backend/docs/mvp/sequence-diagrams.md b/backend/docs/mvp/sequence-diagrams.md new file mode 100644 index 0000000..51a4d66 --- /dev/null +++ b/backend/docs/mvp/sequence-diagrams.md @@ -0,0 +1,884 @@ +# Effigenix ERP – Fachliche Sequenzdiagramme + +> Generiert aus den GitHub Issues und DDD-Planungsdokumenten. +> Alle Diagramme nutzen Mermaid-Syntax. + +--- + +## Inhaltsverzeichnis + +1. [Stammdaten: Lagerorte verwalten](#1-stammdaten-lagerorte-verwalten) +2. [Bestände: Bestandsposition & Chargen verwalten](#2-bestände-bestandsposition--chargen-verwalten) +3. [Chargen einbuchen & entnehmen](#3-chargen-einbuchen--entnehmen) +4. [Chargen sperren/entsperren & MHD-Überwachung](#4-chargen-sperrenentsperren--mhd-überwachung) +5. [Reservierungen (FEFO)](#5-reservierungen-fefo) +6. [Bestandsbewegungen (Stock Movements)](#6-bestandsbewegungen-stock-movements) +7. [Inventur](#7-inventur) +8. [Rezeptverwaltung](#8-rezeptverwaltung) +9. [Chargenplanung & Produktion (Batch Lifecycle)](#9-chargenplanung--produktion-batch-lifecycle) +10. [Produktionsaufträge (Production Orders)](#10-produktionsaufträge-production-orders) +11. [Produktion über Auftrag starten (End-to-End)](#11-produktion-über-auftrag-starten-end-to-end) +12. [Integration: Production → Inventory](#12-integration-production--inventory) +13. [Integration: Procurement → Inventory](#13-integration-procurement--inventory) +14. [Integration: Quality → Inventory](#14-integration-quality--inventory) +15. [Traceability: Vorwärts- & Rückwärts-Tracing](#15-traceability-vorwärts---rückwärts-tracing) + +--- + +## 1. Stammdaten: Lagerorte verwalten + +**Issues:** #1 (anlegen), #2 (bearbeiten/deaktivieren), #3 (abfragen) + +```mermaid +sequenceDiagram + actor User as Verwaltung + participant Ctrl as StorageLocationController + participant UC as CreateStorageLocation + participant SL as StorageLocation (Aggregate) + participant Repo as StorageLocationRepository + + Note over User,Repo: Story 1.1 – Lagerort anlegen + + User->>Ctrl: POST /api/storage-locations + Ctrl->>UC: execute(CreateStorageLocationCommand) + UC->>Repo: existsByName(name) + Repo-->>UC: false + UC->>SL: StorageLocation.create(draft) + SL-->>UC: Result.ok(storageLocation) + UC->>Repo: save(storageLocation) + UC-->>Ctrl: Result.ok(storageLocationId) + Ctrl-->>User: 201 Created + + Note over User,Repo: Story 1.2 – Lagerort bearbeiten + + User->>Ctrl: PUT /api/storage-locations/{id} + Ctrl->>UC: execute(UpdateStorageLocationCommand) + UC->>Repo: findById(id) + Repo-->>UC: storageLocation + UC->>SL: update(updateDraft) + SL-->>UC: Result.ok() + UC->>Repo: save(storageLocation) + UC-->>Ctrl: Result.ok() + Ctrl-->>User: 200 OK + + Note over User,Repo: Story 1.2 – Lagerort deaktivieren + + User->>Ctrl: POST /api/storage-locations/{id}/deactivate + Ctrl->>UC: execute(DeactivateStorageLocationCommand) + UC->>Repo: findById(id) + UC->>SL: deactivate() + SL-->>UC: Result.ok() + UC->>Repo: save(storageLocation) + Ctrl-->>User: 200 OK + + Note over User,Repo: Story 1.3 – Lagerorte abfragen + + User->>Ctrl: GET /api/storage-locations?type=COLD_ROOM&active=true + Ctrl->>UC: execute(ListStorageLocationsQuery) + UC->>Repo: findAll(filter) + Repo-->>UC: List + UC-->>Ctrl: List + Ctrl-->>User: 200 OK [...] +``` + +--- + +## 2. Bestände: Bestandsposition & Chargen verwalten + +**Issues:** #4 (anlegen), #8 (abfragen), #9 (Parameter ändern) + +```mermaid +sequenceDiagram + actor User as Produktion/Lager + participant Ctrl as StockController + participant UC as CreateStock / UpdateStockParams + participant Stock as Stock (Aggregate) + participant Repo as StockRepository + + Note over User,Repo: Story 2.1 – Bestandsposition anlegen + + User->>Ctrl: POST /api/stocks + Ctrl->>UC: execute(CreateStockCommand) + UC->>Repo: existsByArticleAndLocation(articleId, locationId) + Repo-->>UC: false + UC->>Stock: Stock.create(articleId, locationId) + Stock-->>UC: Result.ok(stock) + UC->>Repo: save(stock) + Ctrl-->>User: 201 Created {stockId} + + Note over User,Repo: Story 2.6 – Bestandsparameter ändern + + User->>Ctrl: PATCH /api/stocks/{id}/parameters + Ctrl->>UC: execute(UpdateStockParamsCommand) + UC->>Repo: findById(stockId) + Repo-->>UC: stock + UC->>Stock: updateMinimumLevel(newLevel) + Stock-->>UC: Result.ok() + UC->>Stock: updateMinimumShelfLife(newDays) + Stock-->>UC: Result.ok() + UC->>Repo: save(stock) + Ctrl-->>User: 200 OK + + Note over User,Repo: Story 2.5 – Bestandsposition abfragen + + User->>Ctrl: GET /api/stocks/{id} + Ctrl->>UC: execute(GetStockQuery) + UC->>Repo: findById(stockId) + Repo-->>UC: stock (inkl. Batches & Reservierungen) + UC-->>Ctrl: StockResponse + Ctrl-->>User: 200 OK {totalQuantity, batches[], reservations[]} +``` + +--- + +## 3. Chargen einbuchen & entnehmen + +**Issues:** #5 (einbuchen), #6 (entnehmen) + +```mermaid +sequenceDiagram + actor User as Lager/Produktion + participant Ctrl as StockController + participant UC as AddBatch / RemoveBatch + participant Stock as Stock (Aggregate) + participant Repo as StockRepository + + Note over User,Repo: Story 2.2 – Charge einbuchen (addBatch) + + User->>Ctrl: POST /api/stocks/{id}/batches + Note right of User: {batchRef, quantity, expiryDate, batchType} + Ctrl->>UC: execute(AddBatchCommand) + UC->>Repo: findById(stockId) + Repo-->>UC: stock + UC->>Stock: addBatch(batchReference, quantity, expiryDate) + Note right of Stock: Validierung: qty > 0, expiryDate in Zukunft + Stock-->>UC: Result.ok() + Note right of Stock: totalQuantity += quantity + UC->>Repo: save(stock) + Ctrl-->>User: 201 Created + + Note over User,Repo: Story 2.3 – Charge entnehmen (removeBatch) + + User->>Ctrl: POST /api/stocks/{id}/batches/{batchId}/remove + Note right of User: {quantity} + Ctrl->>UC: execute(RemoveBatchCommand) + UC->>Repo: findById(stockId) + Repo-->>UC: stock + UC->>Stock: removeBatch(batchId, quantity) + Note right of Stock: Prüfung: Batch vorhanden, qty ≤ verfügbar + alt Menge reicht + Stock-->>UC: Result.ok() + UC->>Repo: save(stock) + Ctrl-->>User: 200 OK + else Nicht genug Bestand + Stock-->>UC: Result.error(InsufficientStock) + Ctrl-->>User: 409 Conflict + end +``` + +--- + +## 4. Chargen sperren/entsperren & MHD-Überwachung + +**Issues:** #7 (sperren/entsperren), #10 (MHD-Markierung), #11 (Mindestbestand) + +```mermaid +sequenceDiagram + actor User as QM/Lager + participant Ctrl as StockController + participant UC as BlockBatch / UnblockBatch + participant Stock as Stock (Aggregate) + participant Repo as StockRepository + + Note over User,Repo: Story 2.4 – Charge sperren + + User->>Ctrl: POST /api/stocks/{id}/batches/{batchId}/block + Note right of User: {reason} + Ctrl->>UC: execute(BlockBatchCommand) + UC->>Repo: findById(stockId) + UC->>Stock: blockBatch(batchId, reason) + Note right of Stock: Status: AVAILABLE → BLOCKED + Stock-->>UC: Result.ok() + UC->>Repo: save(stock) + Ctrl-->>User: 200 OK + + User->>Ctrl: POST /api/stocks/{id}/batches/{batchId}/unblock + Ctrl->>UC: execute(UnblockBatchCommand) + UC->>Repo: findById(stockId) + UC->>Stock: unblockBatch(batchId) + Note right of Stock: Status: BLOCKED → AVAILABLE + Stock-->>UC: Result.ok() + UC->>Repo: save(stock) + Ctrl-->>User: 200 OK +``` + +```mermaid +sequenceDiagram + participant Scheduler as Täglicher Scheduler + participant UC as MarkExpiredBatches + participant Repo as StockRepository + participant Stock as Stock (Aggregate) + + Note over Scheduler,Stock: Story 3.1 – Abgelaufene & bald ablaufende Chargen + + Scheduler->>UC: execute() + UC->>Repo: findAllWithBatches() + Repo-->>UC: List + loop Für jeden Stock + UC->>Stock: markExpiredBatches(today) + Note right of Stock: expiryDate < today → EXPIRED + Note right of Stock: expiryDate - today < minimumShelfLife → EXPIRING_SOON + Stock-->>UC: List + Note right of UC: Events: BatchExpired, BatchExpiringSoon + end + UC->>Repo: saveAll(stocks) + + Note over Scheduler,Stock: Story 3.2 – Bestände unter Mindestbestand + + Scheduler->>UC: execute() + UC->>Repo: findAllBelowMinimumLevel() + Repo-->>UC: List + loop Für jeden Stock unter Minimum + Note right of UC: Event: StockLevelBelowMinimum + end +``` + +--- + +## 5. Reservierungen (FEFO) + +**Issues:** #12 (reservieren), #13 (freigeben), #14 (bestätigen/entnehmen) + +```mermaid +sequenceDiagram + actor User as Produktion/Verkauf + participant Ctrl as StockController + participant UC as ReserveStock + participant Stock as Stock (Aggregate) + participant FEFO as FEFO-Algorithmus + participant Repo as StockRepository + + Note over User,Repo: Story 4.1 – Bestand reservieren (FEFO) + + User->>Ctrl: POST /api/stocks/{id}/reservations + Note right of User: {quantity, referenceType, referenceId} + Ctrl->>UC: execute(ReserveStockCommand) + UC->>Repo: findById(stockId) + Repo-->>UC: stock + UC->>Stock: reserve(quantity, refType, refId) + Stock->>FEFO: allocate(availableBatches, quantity) + Note right of FEFO: Sortiere nach expiryDate ASC + Note right of FEFO: Allokiere von ältester Charge zuerst + FEFO-->>Stock: List [(batch1, qty1), (batch2, qty2)] + Stock-->>UC: Result.ok(reservationId) + UC->>Repo: save(stock) + Ctrl-->>User: 201 Created {reservationId} + + Note over User,Repo: Story 4.2 – Reservierung freigeben + + User->>Ctrl: DELETE /api/stocks/{id}/reservations/{resId} + Ctrl->>UC: execute(ReleaseReservationCommand) + UC->>Repo: findById(stockId) + UC->>Stock: releaseReservation(reservationId) + Note right of Stock: Allokierte Mengen → wieder AVAILABLE + Stock-->>UC: Result.ok() + UC->>Repo: save(stock) + Ctrl-->>User: 200 OK + + Note over User,Repo: Story 4.3 – Reservierung bestätigen (entnehmen) + + User->>Ctrl: POST /api/stocks/{id}/reservations/{resId}/confirm + Ctrl->>UC: execute(ConfirmReservationCommand) + UC->>Repo: findById(stockId) + UC->>Stock: confirmReservation(reservationId) + Note right of Stock: Reservierte Batches physisch entnommen + Note right of Stock: totalQuantity -= reservedQuantity + Note right of Stock: Reservierung entfernt + Stock-->>UC: Result.ok(removedBatches) + UC->>Repo: save(stock) + Note right of UC: → StockMovement wird erstellt (Story 5.1) + Ctrl-->>User: 200 OK +``` + +--- + +## 6. Bestandsbewegungen (Stock Movements) + +**Issues:** #15 (erfassen), #16 (abfragen) + +```mermaid +sequenceDiagram + actor User as Lager/System + participant Ctrl as StockMovementController + participant UC as RecordStockMovement + participant SM as StockMovement (Aggregate) + participant Repo as StockMovementRepository + participant StockRepo as StockRepository + + Note over User,Repo: Story 5.1 – Bestandsbewegung erfassen + + User->>Ctrl: POST /api/stock-movements + Note right of User: {articleId, batchId, movementType,
direction, quantity, fromLocation,
toLocation, reason, referenceDoc} + Ctrl->>UC: execute(RecordStockMovementCommand) + UC->>SM: StockMovement.create(draft) + Note right of SM: Validierung:
- GOODS_RECEIPT: fromLocation = null
- WASTE: reason pflicht
- TRANSFER: from ≠ to + SM-->>UC: Result.ok(stockMovement) + UC->>Repo: save(stockMovement) + Note right of UC: Immutabel – keine Änderung möglich + Ctrl-->>User: 201 Created {movementId} + + Note over User,Repo: Story 5.2 – Bestandsbewegungen abfragen + + User->>Ctrl: GET /api/stock-movements?articleId=...&from=...&to=... + Ctrl->>UC: execute(ListStockMovementsQuery) + UC->>Repo: findByFilters(articleId, period, type, batchRef) + Repo-->>UC: List + Ctrl-->>User: 200 OK [{movementId, type, direction, qty, date, ...}] +``` + +--- + +## 7. Inventur + +**Issues:** #17 (anlegen), #18 (durchführen), #19 (abschließen), #20 (abbrechen/abfragen) + +```mermaid +sequenceDiagram + actor Counter as Zähler + actor Approver as Prüfer (≠ Zähler) + participant Ctrl as InventoryCountController + participant UC as InventoryCount Use Cases + participant IC as InventoryCount (Aggregate) + participant StockRepo as StockRepository + participant SMRepo as StockMovementRepository + participant Repo as InventoryCountRepository + + Note over Counter,Repo: Story 6.1 – Inventur anlegen + + Counter->>Ctrl: POST /api/inventory-counts + Note right of Counter: {storageLocationId, countDate} + Ctrl->>UC: execute(CreateInventoryCountCommand) + UC->>StockRepo: findByLocation(locationId) + StockRepo-->>UC: List (aktuelle Bestände) + UC->>IC: InventoryCount.create(location, date, expectedItems) + Note right of IC: CountItems[] auto-befüllt:
Artikel + Soll-Menge aus Bestand + IC-->>UC: Result.ok(inventoryCount) + UC->>Repo: save(inventoryCount) + Ctrl-->>Counter: 201 Created {countId, items[]} + + Note over Counter,Repo: Story 6.2 – Ist-Mengen erfassen + + Counter->>Ctrl: PATCH /api/inventory-counts/{id}/items/{itemId} + Note right of Counter: {actualQuantity} + Ctrl->>UC: execute(RecordCountItemCommand) + UC->>Repo: findById(countId) + UC->>IC: recordActualQuantity(itemId, actualQty) + Note right of IC: variance = actualQty - expectedQty + IC-->>UC: Result.ok() + UC->>Repo: save(inventoryCount) + Ctrl-->>Counter: 200 OK + + Note over Counter,Repo: Story 6.3 – Inventur abschließen (4-Augen) + + Counter->>Ctrl: POST /api/inventory-counts/{id}/submit + Ctrl->>UC: submitForApproval(countId, countedBy) + UC->>IC: submit(countedBy) + IC-->>UC: Result.ok() + + Approver->>Ctrl: POST /api/inventory-counts/{id}/approve + Note right of Approver: approvedBy ≠ countedBy (4-Augen-Prinzip) + Ctrl->>UC: execute(ApproveInventoryCountCommand) + UC->>Repo: findById(countId) + UC->>IC: approve(approvedBy) + Note right of IC: Prüfung: approvedBy ≠ countedBy + IC-->>UC: Result.ok(variances[]) + + loop Für jede Abweichung + UC->>SMRepo: save(StockMovement.ADJUSTMENT) + Note right of SMRepo: Automatische Ausgleichsbuchung
IN bei Mehr, OUT bei Weniger + end + UC->>Repo: save(inventoryCount) + Ctrl-->>Approver: 200 OK {variances[], adjustments[]} + + Note over Counter,Repo: Story 6.4 – Inventur abbrechen + + Counter->>Ctrl: POST /api/inventory-counts/{id}/cancel + Ctrl->>UC: execute(CancelInventoryCountCommand) + UC->>IC: cancel() + Note right of IC: Nur möglich wenn noch nicht abgeschlossen + IC-->>UC: Result.ok() + UC->>Repo: save(inventoryCount) + Ctrl-->>Counter: 200 OK +``` + +--- + +## 8. Rezeptverwaltung + +**Issues:** #26 (anlegen), #27 (Zutaten), #28 (Schritte), #29 (aktivieren), #30 (archivieren), #31 (abfragen), #32 (Zykluserkennung) + +```mermaid +sequenceDiagram + actor User as Metzgermeister + participant Ctrl as RecipeController + participant UC as Recipe Use Cases + participant Recipe as Recipe (Aggregate) + participant CycleCheck as CycleDependencyChecker + participant Repo as RecipeRepository + + Note over User,Repo: US-P02 – Rezept anlegen (DRAFT) + + User->>Ctrl: POST /api/recipes + Note right of User: {articleId, name, recipeType,
yieldPercentage, shelfLifeDays} + Ctrl->>UC: execute(CreateRecipeCommand) + UC->>Recipe: Recipe.create(draft) + Note right of Recipe: Status = DRAFT + Recipe-->>UC: Result.ok(recipe) + UC->>Repo: save(recipe) + Ctrl-->>User: 201 Created {recipeId} + + Note over User,Repo: US-P03 – Zutaten verwalten + + User->>Ctrl: POST /api/recipes/{id}/ingredients + Note right of User: {articleId, quantity, unit, subRecipeId?} + Ctrl->>UC: execute(AddIngredientCommand) + UC->>Repo: findById(recipeId) + UC->>Recipe: addIngredient(ingredientDraft) + Note right of Recipe: Nur im DRAFT-Status erlaubt + alt SubRecipeId angegeben + UC->>CycleCheck: checkForCycles(recipeId, subRecipeId) + Note right of CycleCheck: DFS-Traversierung aller
verschachtelten Rezepte + CycleCheck-->>UC: noCycle / cycleDetected + end + Recipe-->>UC: Result.ok() + UC->>Repo: save(recipe) + Ctrl-->>User: 201 Created + + Note over User,Repo: US-P04 – Produktionsschritte verwalten + + User->>Ctrl: POST /api/recipes/{id}/steps + Note right of User: {stepNumber, description,
durationMinutes, temperatureCelsius} + Ctrl->>UC: execute(AddProductionStepCommand) + UC->>Recipe: addProductionStep(stepDraft) + Recipe-->>UC: Result.ok() + UC->>Repo: save(recipe) + Ctrl-->>User: 201 Created + + Note over User,Repo: US-P05 – Rezept aktivieren + + User->>Ctrl: POST /api/recipes/{id}/activate + Ctrl->>UC: execute(ActivateRecipeCommand) + UC->>Repo: findById(recipeId) + UC->>Recipe: activate() + Note right of Recipe: Prüfung: mind. 1 Zutat vorhanden
Status: DRAFT → ACTIVE + Recipe-->>UC: Result.ok() + UC->>Repo: save(recipe) + Ctrl-->>User: 200 OK + + Note over User,Repo: US-P06 – Rezept archivieren + + User->>Ctrl: POST /api/recipes/{id}/archive + Ctrl->>UC: execute(ArchiveRecipeCommand) + UC->>Recipe: archive() + Note right of Recipe: Status: ACTIVE → ARCHIVED + Recipe-->>UC: Result.ok() + UC->>Repo: save(recipe) + Ctrl-->>User: 200 OK +``` + +--- + +## 9. Chargenplanung & Produktion (Batch Lifecycle) + +**Issues:** #33 (planen), #34 (abfragen), #35 (starten + Verbrauch), #36 (abschließen), #37 (stornieren) + +```mermaid +sequenceDiagram + actor User as Metzgermeister + participant Ctrl as BatchController + participant UC as Batch Use Cases + participant Batch as Batch (Aggregate) + participant RecipeRepo as RecipeRepository + participant Repo as BatchRepository + + Note over User,Repo: US-P09 – Charge planen + + User->>Ctrl: POST /api/batches + Note right of User: {recipeId, plannedQuantity, productionDate} + Ctrl->>UC: execute(PlanBatchCommand) + UC->>RecipeRepo: findById(recipeId) + RecipeRepo-->>UC: recipe (muss ACTIVE sein) + UC->>Batch: Batch.plan(draft) + Note right of Batch: BatchNumber generiert: P-2026-02-24-001
BestBeforeDate = productionDate + shelfLifeDays
Status = PLANNED + Batch-->>UC: Result.ok(batch) + UC->>Repo: save(batch) + Ctrl-->>User: 201 Created {batchId, batchNumber} + + Note over User,Repo: US-P10 – Produktion starten + + User->>Ctrl: POST /api/batches/{id}/start + Ctrl->>UC: execute(StartBatchCommand) + UC->>Repo: findById(batchId) + UC->>Batch: startProduction() + Note right of Batch: Status: PLANNED → IN_PRODUCTION + Batch-->>UC: Result.ok() + UC->>Repo: save(batch) + Ctrl-->>User: 200 OK + + Note over User,Repo: US-P10 – Rohstoffverbrauch dokumentieren + + User->>Ctrl: POST /api/batches/{id}/consumptions + Note right of User: {inputBatchId, quantity, unit} + Ctrl->>UC: execute(RecordConsumptionCommand) + UC->>Repo: findById(batchId) + UC->>Batch: recordConsumption(consumptionDraft) + Note right of Batch: Nur im Status IN_PRODUCTION
inputBatchId muss eindeutig sein
→ Chargen-Genealogie wird aufgebaut + Batch-->>UC: Result.ok() + UC->>Repo: save(batch) + Ctrl-->>User: 201 Created + + Note over User,Repo: US-P11 – Charge abschließen + + User->>Ctrl: POST /api/batches/{id}/complete + Note right of User: {actualQuantity, waste, remarks} + Ctrl->>UC: execute(CompleteBatchCommand) + UC->>Repo: findById(batchId) + UC->>Batch: complete(actualQty, waste, remarks) + Note right of Batch: Prüfung: mind. 1 Consumption
actualQuantity > 0
Status: IN_PRODUCTION → COMPLETED + Batch-->>UC: Result.ok() + Note right of Batch: Event: BatchCompleted + UC->>Repo: save(batch) + Ctrl-->>User: 200 OK + + Note over User,Repo: US-P12 – Charge stornieren + + User->>Ctrl: POST /api/batches/{id}/cancel + Note right of User: {reason} (Pflichtfeld) + Ctrl->>UC: execute(CancelBatchCommand) + UC->>Batch: cancel(reason) + Note right of Batch: Status: PLANNED/IN_PRODUCTION → CANCELLED + Batch-->>UC: Result.ok() + UC->>Repo: save(batch) + Ctrl-->>User: 200 OK +``` + +--- + +## 10. Produktionsaufträge (Production Orders) + +**Issues:** #38 (anlegen), #39 (freigeben), #40 (Produktion starten), #41 (abschließen/stornieren), #42 (umterminieren/abfragen) + +```mermaid +sequenceDiagram + actor User as Produktionsleiter + participant Ctrl as ProductionOrderController + participant UC as ProductionOrder Use Cases + participant PO as ProductionOrder (Aggregate) + participant RecipeRepo as RecipeRepository + participant BatchUC as PlanBatch Use Case + participant Repo as ProductionOrderRepository + + Note over User,Repo: US-P13 – Produktionsauftrag anlegen + + User->>Ctrl: POST /api/production-orders + Note right of User: {recipeId, plannedQuantity,
plannedDate, priority} + Ctrl->>UC: execute(CreateProductionOrderCommand) + UC->>PO: ProductionOrder.create(draft) + Note right of PO: Status = PLANNED
Priority = NORMAL/HIGH/URGENT + PO-->>UC: Result.ok(productionOrder) + UC->>Repo: save(productionOrder) + Ctrl-->>User: 201 Created {orderId} + + Note over User,Repo: US-P14 – Produktionsauftrag freigeben + + User->>Ctrl: POST /api/production-orders/{id}/release + Ctrl->>UC: execute(ReleaseProductionOrderCommand) + UC->>Repo: findById(orderId) + UC->>RecipeRepo: findById(recipeId) + RecipeRepo-->>UC: recipe + UC->>PO: release(recipe) + Note right of PO: Prüfung: Rezept muss ACTIVE sein
Status: PLANNED → RELEASED + PO-->>UC: Result.ok() + UC->>Repo: save(productionOrder) + Ctrl-->>User: 200 OK + + Note over User,Repo: US-P15 – Produktion über Auftrag starten + + User->>Ctrl: POST /api/production-orders/{id}/start-production + Ctrl->>UC: execute(StartProductionFromOrderCommand) + UC->>Repo: findById(orderId) + + UC->>BatchUC: planBatch(recipeId, plannedQty, date) + Note right of BatchUC: Neue Charge wird automatisch erstellt + BatchUC-->>UC: batchId + + UC->>PO: startProduction(batchId) + Note right of PO: Status: RELEASED → IN_PRODUCTION
BatchId wird verknüpft + PO-->>UC: Result.ok() + UC->>Repo: save(productionOrder) + Ctrl-->>User: 200 OK {batchId} + + Note over User,Repo: US-P16 – Auftrag abschließen + + User->>Ctrl: POST /api/production-orders/{id}/complete + Ctrl->>UC: execute(CompleteProductionOrderCommand) + UC->>Repo: findById(orderId) + UC->>PO: complete() + Note right of PO: Prüfung: verknüpfte Charge
muss COMPLETED sein
Status: IN_PRODUCTION → COMPLETED + PO-->>UC: Result.ok() + UC->>Repo: save(productionOrder) + Ctrl-->>User: 200 OK + + Note over User,Repo: US-P17 – Auftrag umterminieren + + User->>Ctrl: PATCH /api/production-orders/{id}/reschedule + Note right of User: {newPlannedDate} + Ctrl->>UC: execute(RescheduleProductionOrderCommand) + UC->>PO: reschedule(newDate) + Note right of PO: Nur in PLANNED/RELEASED erlaubt + PO-->>UC: Result.ok() + UC->>Repo: save(productionOrder) + Ctrl-->>User: 200 OK +``` + +--- + +## 11. Produktion über Auftrag starten (End-to-End) + +**Issues:** #38–#42 (Production Orders), #33–#37 (Batches), #12–#14 (Reservierungen) + +```mermaid +sequenceDiagram + actor PL as Produktionsleiter + actor MM as Metzgermeister + participant PO as ProductionOrder + participant Batch as Batch + participant Stock as Stock (Inventory) + participant SM as StockMovement + + Note over PL,SM: End-to-End: Vom Auftrag zur fertigen Charge + + PL->>PO: 1. Auftrag anlegen (recipeId, qty, date) + Note right of PO: Status: PLANNED + + PL->>PO: 2. Auftrag freigeben + Note right of PO: Prüft: Rezept ACTIVE
Status: PLANNED → RELEASED + + PL->>PO: 3. Produktion starten + PO->>Batch: Charge wird automatisch erstellt + Note right of Batch: BatchNumber: P-2026-02-24-001
Status: PLANNED → IN_PRODUCTION + Note right of PO: Status: RELEASED → IN_PRODUCTION + + MM->>Stock: 4. Material reservieren (FEFO) + Note right of Stock: Älteste Chargen zuerst allokiert + + MM->>Stock: 5. Reservierung bestätigen (entnehmen) + Stock->>SM: StockMovement PRODUCTION_CONSUMPTION (OUT) + Note right of Stock: totalQuantity sinkt + + MM->>Batch: 6. Verbrauch dokumentieren + Note right of Batch: Consumption(inputBatchId, qty)
→ Chargen-Genealogie + + MM->>Batch: 7. Charge abschließen + Note right of Batch: actualQty, waste, remarks
Status: IN_PRODUCTION → COMPLETED + + Batch->>Stock: 8. Fertiges Produkt einbuchen + Stock->>SM: StockMovement PRODUCTION_OUTPUT (IN) + Note right of Stock: Neuer StockBatch mit MHD + + PL->>PO: 9. Auftrag abschließen + Note right of PO: Prüft: Charge COMPLETED
Status: IN_PRODUCTION → COMPLETED +``` + +--- + +## 12. Integration: Production → Inventory + +**Issues:** #22 (Produktionsergebnis einbuchen), #23 (Produktionsverbrauch verbuchen) + +```mermaid +sequenceDiagram + participant Batch as Batch (Production BC) + participant EventBus as Domain Event Bus + participant Handler as Integration Event Handler + participant Stock as Stock (Inventory BC) + participant SM as StockMovement + participant StockRepo as StockRepository + + Note over Batch,StockRepo: Story 7.2 – BatchCompleted → Bestand einbuchen + + Batch->>EventBus: BatchCompleted {batchId, articleId,
actualQuantity, bestBeforeDate} + EventBus->>Handler: handle(BatchCompleted) + Handler->>StockRepo: findByArticleAndLocation(articleId, productionLocation) + StockRepo-->>Handler: stock + Handler->>Stock: addBatch(batchRef=PRODUCED, qty, expiryDate) + Stock-->>Handler: Result.ok() + Handler->>SM: StockMovement.create(PRODUCTION_OUTPUT, IN) + Handler->>StockRepo: save(stock) + Note right of Handler: Fertiges Produkt ist jetzt im Bestand + + Note over Batch,StockRepo: Story 7.3 – ConsumptionRecorded → Bestand entnehmen + + Batch->>EventBus: ConsumptionRecorded {batchId,
inputBatchId, quantity} + EventBus->>Handler: handle(ConsumptionRecorded) + Handler->>StockRepo: findByBatchReference(inputBatchId) + StockRepo-->>Handler: stock + Handler->>Stock: removeBatch(inputBatchId, quantity) + Stock-->>Handler: Result.ok() + Handler->>SM: StockMovement.create(PRODUCTION_CONSUMPTION, OUT) + Handler->>StockRepo: save(stock) + Note right of Handler: Rohstoff-Bestand reduziert +``` + +--- + +## 13. Integration: Procurement → Inventory + +**Issue:** #21 (Wareneingang verarbeiten) + +```mermaid +sequenceDiagram + actor Lager as Wareneingang + participant GR as GoodsReceipt (Procurement BC) + participant QI as GoodsReceiptInspection (Quality BC) + participant EventBus as Domain Event Bus + participant Handler as Integration Event Handler + participant Stock as Stock (Inventory BC) + participant SM as StockMovement + + Note over Lager,SM: Story 7.1 – Wareneingang → Bestand einbuchen + + Lager->>GR: Wareneingang erfassen + Note right of GR: supplierBatchNumber, qty, expiryDate + + GR->>QI: Qualitätsprüfung anstoßen + Note right of QI: Temperatur ≤ 4°C (Fleisch)
Verpackung OK
MHD ausreichend
Dokumente vollständig + + alt Prüfung bestanden + QI-->>GR: ACCEPTED + GR->>EventBus: GoodsReceiptAccepted {articleId,
supplierBatchNumber, qty, expiryDate} + EventBus->>Handler: handle(GoodsReceiptAccepted) + Handler->>Stock: addBatch(batchRef=PURCHASED,
supplierBatchNumber, qty, expiryDate) + Handler->>SM: StockMovement.create(GOODS_RECEIPT, IN) + Note right of Stock: Ware im Bestand verfügbar + else Prüfung fehlgeschlagen + QI-->>GR: REJECTED + GR->>EventBus: GoodsReceiptRejected {reason} + Note right of GR: Ware wird zurückgewiesen
Kein Bestand eingebucht + end +``` + +--- + +## 14. Integration: Quality → Inventory + +**Issue:** #24 (Qualitätssperre/Freigabe) + +```mermaid +sequenceDiagram + actor QM as Qualitätsmanager + actor Approver as Freigeber (≠ QM) + participant QH as QualityHold (Quality BC) + participant EventBus as Domain Event Bus + participant Handler as Integration Event Handler + participant Stock as Stock (Inventory BC) + + Note over QM,Stock: Story 7.4 – Qualitätssperre → Charge sperren + + QM->>QH: Qualitätssperre initiieren + Note right of QH: Grund: TEMPERATURE_DEVIATION /
SAMPLE_FAILED / CONTAMINATION + QH->>EventBus: QualityHoldInitiated {batchId, reason} + EventBus->>Handler: handle(QualityHoldInitiated) + Handler->>Stock: blockBatch(batchId, reason) + Note right of Stock: StockBatch Status: AVAILABLE → BLOCKED
Kann nicht entnommen werden + + Note over QM,Stock: Korrekturmaßnahmen dokumentieren + + QM->>QH: addCorrectiveAction(description) + + Note over QM,Stock: Freigabe (4-Augen-Prinzip) + + Approver->>QH: release(approvedBy) + Note right of QH: Prüfung: initiatedBy ≠ approvedBy + QH->>EventBus: QualityHoldReleased {batchId} + EventBus->>Handler: handle(QualityHoldReleased) + Handler->>Stock: unblockBatch(batchId) + Note right of Stock: StockBatch Status: BLOCKED → AVAILABLE + + Note over QM,Stock: Alternative: Charge abgelehnt + + QM->>QH: reject(reason) + QH->>EventBus: QualityHoldRejected {batchId} + EventBus->>Handler: handle(QualityHoldRejected) + Handler->>Stock: removeBatch(batchId, fullQuantity) + Note right of Stock: Charge komplett aus Bestand entfernt +``` + +--- + +## 15. Traceability: Vorwärts- & Rückwärts-Tracing + +**Issues:** #43 (Vorwärts-Tracing / Rückruf), #44 (Rückwärts-Tracing / Herkunft) + +```mermaid +sequenceDiagram + actor QM as Qualitätsmanager + participant Ctrl as TraceabilityController + participant Svc as BatchTraceabilityService + participant BatchRepo as BatchRepository + participant SMRepo as StockMovementRepository + + Note over QM,SMRepo: US-P18 – Vorwärts-Tracing (Rückruf-Szenario) + Note over QM,SMRepo: "Welche Endprodukte sind von
Rohstoff-Charge X betroffen?" + + QM->>Ctrl: GET /api/traceability/forward/{inputBatchId} + Ctrl->>Svc: traceForward(inputBatchId) + + Svc->>BatchRepo: findByConsumptionInputBatchId(inputBatchId) + BatchRepo-->>Svc: List (Stufe 1: direkte Verwendung) + + loop Für jede gefundene Charge + Svc->>BatchRepo: findByConsumptionInputBatchId(batchId) + Note right of Svc: Rekursiv: auch Zwischenprodukte
die in weiteren Chargen verwendet wurden + end + + Svc->>SMRepo: findByBatchIds(allAffectedBatchIds) + Note right of SMRepo: Welche Bewegungen?
An welche Filialen geliefert? + + Svc-->>Ctrl: TraceabilityResult {
affectedBatches[],
affectedBranches[],
stockMovements[]
} + Ctrl-->>QM: 200 OK – Rückruf-Daten + + Note over QM,SMRepo: US-P19 – Rückwärts-Tracing (Herkunftsnachweis) + Note over QM,SMRepo: "Woher stammen die Zutaten
von Endprodukt-Charge Y?" + + QM->>Ctrl: GET /api/traceability/backward/{outputBatchId} + Ctrl->>Svc: traceBackward(outputBatchId) + + Svc->>BatchRepo: findById(outputBatchId) + BatchRepo-->>Svc: batch (mit Consumptions) + + loop Für jede Consumption + Note right of Svc: inputBatchId → Herkunft ermitteln + Svc->>BatchRepo: findById(inputBatchId) + BatchRepo-->>Svc: inputBatch + Note right of Svc: Falls inputBatch eigene Consumptions hat:
Rekursiv weiter zurückverfolgen + end + + Svc-->>Ctrl: TraceabilityResult {
genealogyTree: {
outputBatch → [inputBatch1, inputBatch2]
inputBatch1 → [rawMaterialBatch1]
},
supplierBatches[] (Blätter des Baums)
} + Ctrl-->>QM: 200 OK – Herkunftsnachweis +``` + +--- + +## Legende + +| Symbol | Bedeutung | +|--------|-----------| +| `actor` | Menschlicher Akteur (Rolle) | +| `participant` | Systemkomponente | +| `Note` | Erklärung / Invarianten | +| `alt/else` | Bedingte Verzweigung | +| `loop` | Schleife / Wiederholung | +| `→` | Synchroner Aufruf | +| `-->>` | Rückantwort | +| Event: `XXX` | Domain Event |