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 |