mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 06:29:35 +01:00
docs: Sequenzdiagramme
This commit is contained in:
parent
11fb62383b
commit
85f96d685e
2 changed files with 1499 additions and 0 deletions
615
backend/docs/mvp/sequence-diagrams-e2e.md
Normal file
615
backend/docs/mvp/sequence-diagrams-e2e.md
Normal file
|
|
@ -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<br/>(PurchaseOrder, GoodsReceipt)
|
||||
participant QM as Quality BC<br/>(GoodsReceiptInspection)
|
||||
participant Inv as Inventory BC<br/>(Stock, StockMovement)
|
||||
|
||||
Note over Fahrer,Inv: 🕠 05:30 – LKW-Anlieferung
|
||||
|
||||
Fahrer->>WE: Lieferschein übergeben<br/>(200kg Schweineschulter, 80kg Rind,<br/>Charge "MF-2026-0847", MHD 2026-03-03)
|
||||
|
||||
WE->>Proc: Wareneingang erfassen
|
||||
Note right of Proc: GoodsReceipt anlegen<br/>Referenz: PurchaseOrder #PO-2026-041<br/>Lieferanten-Charge: MF-2026-0847<br/>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 ✅<br/>Kerntemperatur Rind: 3,4°C ✅<br/>(Grenzwert: ≤ 4°C für Frischfleisch)
|
||||
|
||||
WE->>QM: 2. Sichtkontrolle
|
||||
Note right of QM: Verpackung: unbeschädigt ✅<br/>Farbe: frisch, keine Verfärbung ✅<br/>Geruch: neutral, arttypisch ✅
|
||||
|
||||
WE->>QM: 3. MHD-Prüfung
|
||||
Note right of QM: MHD 2026-03-03 → noch 7 Tage ✅<br/>(Mindest-Restlaufzeit: 3 Tage)
|
||||
|
||||
WE->>QM: 4. Dokumentenprüfung
|
||||
Note right of QM: Lieferschein: vorhanden ✅<br/>Veterinärbescheinigung: vorhanden ✅<br/>Identitätskennzeichen: DE NW 20145 ✅
|
||||
|
||||
QM-->>QM: Alle Prüfungen bestanden
|
||||
QM->>Proc: GoodsReceiptAccepted
|
||||
Note right of Proc: GoodsReceipt Status → ACCEPTED<br/>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<br/>BatchType: PURCHASED<br/>200 kg, MHD 2026-03-03<br/>Lagerort: KÜHLRAUM_1
|
||||
|
||||
Inv->>Inv: Stock.addBatch(Rindfleisch)
|
||||
Note right of Inv: StockBatch: MF-2026-0847-R<br/>100 kg, MHD 2026-03-03
|
||||
|
||||
Inv->>Inv: StockMovement.record(GOODS_RECEIPT, IN)
|
||||
Note right of Inv: 2× Bewegungen protokolliert<br/>(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!<br/>Max. erlaubt: 4°C<br/>Gemessen: 7,2°C
|
||||
|
||||
QM->>QM: TemperatureCriticalLimitExceeded
|
||||
QM->>Proc: GoodsReceiptRejected
|
||||
Note right of Proc: Grund: Kühlkette unterbrochen<br/>GoodsReceipt Status → REJECTED<br/>PurchaseOrder bleibt CONFIRMED
|
||||
|
||||
WE->>Fahrer: Ware zurückweisen
|
||||
Note right of WE: Ablehnungsprotokoll erstellt<br/>Fotos dokumentiert<br/>Lieferant wird informiert
|
||||
|
||||
Note over WE,Proc: Kein Bestand eingebucht!<br/>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<br/>(ProductionOrder)
|
||||
participant Batch as Production BC<br/>(Batch)
|
||||
participant Recipe as Production BC<br/>(Recipe)
|
||||
participant Stock as Inventory BC<br/>(Stock)
|
||||
participant SM as Inventory BC<br/>(StockMovement)
|
||||
participant Label as Labeling BC<br/>(ProductLabel)
|
||||
|
||||
Note over PL,Label: 🕕 06:00 – Produktionsauftrag vorbereiten
|
||||
|
||||
PL->>PO: Produktionsauftrag anlegen
|
||||
Note right of PO: Rezept: R-NB-001 (Nürnberger)<br/>Menge: 150 kg<br/>Datum: 2026-02-24<br/>Priorität: NORMAL<br/>Status → PLANNED
|
||||
|
||||
PL->>PO: Auftrag freigeben
|
||||
Note right of PO: Prüfung: Rezept R-NB-001 ist ACTIVE ✅<br/>Status → RELEASED
|
||||
|
||||
PL->>PO: Produktion starten
|
||||
PO->>Batch: Charge automatisch erstellen
|
||||
Note right of Batch: BatchNumber: P-2026-02-24-001<br/>Geplante Menge: 150 kg<br/>Status → IN_PRODUCTION
|
||||
PO-->>PO: Status → IN_PRODUCTION<br/>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<br/>Schweineschulter: 60% → 106 kg<br/>Speck: 25% → 44 kg<br/>Gewürzmischung: 10% → 18 kg<br/>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 ✅<br/>(ä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 ✅<br/>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<br/>Consumption 2: SP-2026-0812, 44 kg Speck<br/>Consumption 3: GW-2026-0099, 18 kg Gewürze<br/>Consumption 4: ND-2026-0201, 9 kg Därme
|
||||
Note right of Batch: → Chargen-Genealogie aufgebaut!<br/>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)<br/>Verschnitt: 3,5 kg<br/>Ausbeute real: 148/177 = 83,6%<br/>MHD berechnet: 2026-02-24 + 5 = 2026-03-01<br/>Status → COMPLETED
|
||||
|
||||
Batch->>Stock: Event: BatchCompleted
|
||||
Stock->>Stock: addBatch(P-2026-02-24-001)
|
||||
Note right of Stock: Neuer StockBatch:<br/>148 kg Nürnberger Bratwurst<br/>BatchType: PRODUCED<br/>MHD: 2026-03-01<br/>Lagerort: KÜHLRAUM_2
|
||||
Stock->>SM: StockMovement(PRODUCTION_OUTPUT, IN)
|
||||
|
||||
PL->>PO: Produktionsauftrag abschließen
|
||||
Note right of PO: Prüfung: Charge COMPLETED ✅<br/>Status → COMPLETED
|
||||
|
||||
Note over MM,Label: 🏷️ Etikett steht bereit
|
||||
|
||||
Note right of Label: ProductLabel für Nürnberger verfügbar:<br/>Zutaten: Schweinefleisch (60%),<br/>Speck (25%), Gewürze...<br/>Allergene: SELLERIE, SENF (auto)<br/>MHD: 01.03.2026<br/>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<br/>(ProductionOrder)
|
||||
participant Recipe as Production BC<br/>(Recipe)
|
||||
participant Stock as Inventory BC<br/>(Stock)
|
||||
participant Demand as Procurement BC<br/>(DemandPlan)
|
||||
participant Order as Procurement BC<br/>(PurchaseOrder)
|
||||
participant MD as Master Data BC<br/>(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)<br/>Speck: 36 kg (MHD 05.03)<br/>Rindfleisch: 80 kg (MHD 03.03)<br/>Gewürze: 45 kg (MHD 15.08)<br/>⚠️ 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)<br/>Di: 100 kg Leberwurst (R-LW-003)<br/>Mi: 150 kg Wiener (R-WI-002)<br/>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<br/>Di Leberwurst: 125 kg Rohstoffe<br/>Mi Wiener: 180 kg Rohstoffe<br/>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:<br/> Bedarf: 320 kg<br/> Bestand: 94 kg<br/> Fehlmenge: 226 kg<br/> + Sicherheitsbestand: 50 kg<br/> → Bestellvorschlag: 276 kg<br/><br/>Speck:<br/> Bedarf: 95 kg<br/> Bestand: 36 kg<br/> → Bestellvorschlag: 109 kg<br/><br/>Rindfleisch:<br/> Bedarf: 60 kg<br/> Bestand: 80 kg<br/> → kein Bedarf ✅<br/><br/>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,<br/>Lieferzeit 1 Tag, Preis 5,80€/kg
|
||||
|
||||
EK->>Order: Bestellung anlegen
|
||||
Note right of Order: PurchaseOrder #PO-2026-048<br/>Lieferant: Müller Fleisch<br/>Pos 1: 280 kg Schweineschulter (≥ MBM)<br/>Pos 2: 110 kg Speck<br/>Lieferdatum: Montag 05:00<br/>Gesamtwert: 2.262,00€<br/>Status → ORDERED
|
||||
|
||||
EK->>Order: Bestellung an Lieferant senden
|
||||
Note right of Order: Status → CONFIRMED<br/>(Lieferant hat bestätigt)
|
||||
|
||||
Note over PL,MD: ✅ Planung abgeschlossen<br/>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<br/>(TraceabilityService)
|
||||
participant Quality as Quality BC<br/>(QualityHold)
|
||||
participant Stock as Inventory BC<br/>(Stock)
|
||||
participant SM as Inventory BC<br/>(StockMovement)
|
||||
participant Branch as Filiales BC
|
||||
|
||||
Note over Amt,Branch: ☎️ 10:00 – Veterinäramt meldet Kontamination
|
||||
|
||||
Amt->>QM: Salmonellen in Charge MF-2026-0847<br/>(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:<br/> P-2026-02-24-001: 148 kg Nürnberger Bratwurst<br/> P-2026-02-24-003: 95 kg Leberwurst<br/><br/>Stufe 2 – Weiterverarbeitung:<br/> (keine – Endprodukte)
|
||||
|
||||
QM->>SM: Wo ist die Ware jetzt?
|
||||
SM-->>QM: Bestandsbewegungen:
|
||||
Note right of SM: P-2026-02-24-001 (Bratwurst 148kg):<br/> → 50 kg Filiale Hauptstraße (verkauft: 12 kg)<br/> → 40 kg Filiale Marktplatz (verkauft: 8 kg)<br/> → 58 kg Zentrale (Lager, unverkauft)<br/><br/>P-2026-02-24-003 (Leberwurst 95kg):<br/> → 95 kg Zentrale (Lager, unverkauft)<br/><br/>Rohstoff-Rest:<br/> → 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<br/>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<br/>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<br/>aus Theke nehmen! Gesperrt wegen<br/>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 ✅<br/>2. Filialen informiert ✅<br/>3. Ware aus Verkauf entfernt ✅<br/>4. Veterinäramt Rückmeldung: 20 kg verkauft<br/>5. Kundeninformation vorbereitet
|
||||
|
||||
QM->>Quality: Bereits verkaufte Ware:
|
||||
Note right of Quality: 20 kg verkauft (12+8 kg)<br/>→ Kundenrückruf-Aushang in Filialen<br/>→ Social-Media-Warnung<br/>→ 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<br/>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<br/>Grund: Salmonellen-Kontamination<br/>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<br/>(Batch)
|
||||
participant QC as Quality BC<br/>(ProcessParameter)
|
||||
participant QH as Quality BC<br/>(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<br/>Charge: P-2026-02-24-002 (Wiener)<br/>Gemessen: 65°C<br/>Akzeptabler Bereich: 72–78°C<br/>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<br/>Charge: P-2026-02-24-002<br/>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<br/>auf 75°C Kerntemperatur<br/>Verantwortlich: Metzgermeister<br/>Frist: sofort
|
||||
|
||||
MM->>Batch: Erneut brühen (Prozess wiederholen)
|
||||
|
||||
MM->>QC: Zweite Kerntemperatur-Messung
|
||||
Note right of QC: CCP: CORE_TEMPERATURE<br/>Gemessen: 74°C<br/>Akzeptabler Bereich: 72–78°C<br/>Status: IN_SPEC ✅
|
||||
|
||||
QM->>QH: Korrekturmaßnahme dokumentieren
|
||||
Note right of QH: Ergebnis: Erfolgreich nachgebrüht<br/>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<br/>Korrekturmaßnahme nachvollziehbar<br/>Status → COMPLETED
|
||||
|
||||
Batch->>Stock: Event: BatchCompleted
|
||||
Note right of Stock: Ware eingebucht<br/>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<br/>(06:00 Uhr)
|
||||
participant Stock as Inventory BC<br/>(Stock)
|
||||
actor VK as Verkaufsleiter
|
||||
actor Theke as Theken-Mitarbeiter
|
||||
participant SM as Inventory BC<br/>(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):<br/> LW-Charge P-2026-02-19-004: 3 kg Weißwurst<br/> (MHD war 2026-02-23 → gestern abgelaufen)<br/><br/>⚠️ EXPIRING_SOON (innerhalb MinimumShelfLife):<br/> LW-Charge P-2026-02-20-002: 25 kg Leberwurst<br/> (MHD 2026-02-25 → morgen, MinShelfLife=2d)<br/><br/> AS-Charge P-2026-02-21-001: 40 kg Aufschnitt<br/> (MHD 2026-02-26 → übermorgen)
|
||||
|
||||
Stock-->>Stock: Events publiziert:
|
||||
Note right of Stock: BatchExpired(Weißwurst, 3kg)<br/>BatchExpiringSoon(Leberwurst, 25kg, 1 Tag)<br/>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<br/>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<br/>"Heute 30% günstiger"
|
||||
|
||||
VK->>Theke: Aufschnitt 40 kg: Aktionsplatzierung
|
||||
Note right of Theke: Sichtbare Platzierung in Theke<br/>Ggf. morgen rabattieren
|
||||
|
||||
Note over VK,SM: 🔄 Bei Verkauf normale Entnahme (FEFO)
|
||||
|
||||
Note right of SM: FEFO stellt automatisch sicher,<br/>dass älteste Chargen zuerst<br/>entnommen werden – auch<br/>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<br/>(InventoryCount)
|
||||
participant Stock as Inventory BC<br/>(Stock)
|
||||
participant SM as Inventory BC<br/>(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:<br/>Schweineschulter: 94,0 kg<br/>Rindfleisch: 80,0 kg<br/>Speck: 36,0 kg<br/>Hackfleisch: 22,5 kg<br/>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<br/>Zähler: Lagermitarbeiter
|
||||
|
||||
Note over Chef,SM: 👀 17:00 – 4-Augen-Prüfung
|
||||
|
||||
Chef->>IC: Abweichungen prüfen
|
||||
Note right of IC: Abweichungen:<br/>Schweineschulter: -2,5 kg (Schwund?)<br/>Speck: +1,2 kg (Wiegefehler?)<br/>Hackfleisch: -2,5 kg (nicht erfasste Entnahme?)
|
||||
|
||||
Chef->>IC: Inventur genehmigen
|
||||
Note right of IC: Prüfung: Chef ≠ Lagermitarbeiter ✅ (4-Augen)<br/>Status → COMPLETED
|
||||
|
||||
Note over Chef,SM: ⚖️ Automatische Ausgleichsbuchungen
|
||||
|
||||
IC->>SM: Ausgleich Schweineschulter
|
||||
Note right of SM: StockMovement(ADJUSTMENT, OUT)<br/>-2,5 kg Schweineschulter<br/>Grund: Inventurdifferenz
|
||||
|
||||
IC->>SM: Ausgleich Speck
|
||||
Note right of SM: StockMovement(ADJUSTMENT, IN)<br/>+1,2 kg Speck<br/>Grund: Inventurdifferenz
|
||||
|
||||
IC->>SM: Ausgleich Hackfleisch
|
||||
Note right of SM: StockMovement(ADJUSTMENT, OUT)<br/>-2,5 kg Hackfleisch<br/>Grund: Inventurdifferenz
|
||||
|
||||
IC->>Stock: Bestände korrigiert
|
||||
Note right of Stock: Schweineschulter: 94,0 → 91,5 kg<br/>Rindfleisch: 80,0 kg (unverändert)<br/>Speck: 36,0 → 37,2 kg<br/>Hackfleisch: 22,5 → 20,0 kg
|
||||
|
||||
Note over Lager,SM: ✅ 17:10 – Inventur abgeschlossen<br/>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<br/>(Batch)
|
||||
participant DP as Filiales BC<br/>(DistributionPlan)
|
||||
participant IBT as Filiales BC<br/>(InterBranchTransfer)
|
||||
participant Stock_Z as Inventory BC<br/>(Stock – Zentrale)
|
||||
participant Stock_A as Inventory BC<br/>(Stock – Hauptstraße)
|
||||
participant Stock_B as Inventory BC<br/>(Stock – Marktplatz)
|
||||
participant SM as Inventory BC<br/>(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<br/>MHD: 2026-03-01<br/>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)<br/>Produzierende Filiale: Zentrale<br/><br/>Verteilung:<br/> → Hauptstraße: 100 kg<br/> → Marktplatz: 80 kg<br/> → Verbleibt Zentrale: 120 kg<br/><br/>Summe: 300 kg ✅
|
||||
|
||||
Note over PL,SM: 🚚 09:30 – Transfers anlegen
|
||||
|
||||
DP->>IBT: Transfer Zentrale → Hauptstraße
|
||||
Note right of IBT: 100 kg Bratwurst<br/>Charge: P-2026-02-24-001<br/>MHD: 2026-03-01<br/>Status → REQUESTED
|
||||
|
||||
DP->>IBT: Transfer Zentrale → Marktplatz
|
||||
Note right of IBT: 80 kg Bratwurst<br/>Charge: P-2026-02-24-001<br/>MHD: 2026-03-01<br/>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<br/>Empfangen: 98 kg<br/>(2 kg Transportverlust)<br/>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<br/>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<br/>Hauptstraße: 98 kg (2 kg Schwund)<br/>Marktplatz: 80 kg<br/><br/>Chargen-ID überall identisch!<br/>→ Bei Rückruf: alle 3 Filialen<br/> 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 |
|
||||
884
backend/docs/mvp/sequence-diagrams.md
Normal file
884
backend/docs/mvp/sequence-diagrams.md
Normal file
|
|
@ -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<StorageLocation>
|
||||
UC-->>Ctrl: List<StorageLocationResponse>
|
||||
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<Stock>
|
||||
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<DomainEvent>
|
||||
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<Stock>
|
||||
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<Allocation> [(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,<br/>direction, quantity, fromLocation,<br/>toLocation, reason, referenceDoc}
|
||||
Ctrl->>UC: execute(RecordStockMovementCommand)
|
||||
UC->>SM: StockMovement.create(draft)
|
||||
Note right of SM: Validierung:<br/>- GOODS_RECEIPT: fromLocation = null<br/>- WASTE: reason pflicht<br/>- 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<StockMovement>
|
||||
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<Stock> (aktuelle Bestände)
|
||||
UC->>IC: InventoryCount.create(location, date, expectedItems)
|
||||
Note right of IC: CountItems[] auto-befüllt:<br/>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<br/>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,<br/>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<br/>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,<br/>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<br/>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<br/>BestBeforeDate = productionDate + shelfLifeDays<br/>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<br/>inputBatchId muss eindeutig sein<br/>→ 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<br/>actualQuantity > 0<br/>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,<br/>plannedDate, priority}
|
||||
Ctrl->>UC: execute(CreateProductionOrderCommand)
|
||||
UC->>PO: ProductionOrder.create(draft)
|
||||
Note right of PO: Status = PLANNED<br/>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<br/>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<br/>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<br/>muss COMPLETED sein<br/>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<br/>Status: PLANNED → RELEASED
|
||||
|
||||
PL->>PO: 3. Produktion starten
|
||||
PO->>Batch: Charge wird automatisch erstellt
|
||||
Note right of Batch: BatchNumber: P-2026-02-24-001<br/>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)<br/>→ Chargen-Genealogie
|
||||
|
||||
MM->>Batch: 7. Charge abschließen
|
||||
Note right of Batch: actualQty, waste, remarks<br/>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<br/>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,<br/>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,<br/>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)<br/>Verpackung OK<br/>MHD ausreichend<br/>Dokumente vollständig
|
||||
|
||||
alt Prüfung bestanden
|
||||
QI-->>GR: ACCEPTED
|
||||
GR->>EventBus: GoodsReceiptAccepted {articleId,<br/>supplierBatchNumber, qty, expiryDate}
|
||||
EventBus->>Handler: handle(GoodsReceiptAccepted)
|
||||
Handler->>Stock: addBatch(batchRef=PURCHASED,<br/>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<br/>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 /<br/>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<br/>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<br/>Rohstoff-Charge X betroffen?"
|
||||
|
||||
QM->>Ctrl: GET /api/traceability/forward/{inputBatchId}
|
||||
Ctrl->>Svc: traceForward(inputBatchId)
|
||||
|
||||
Svc->>BatchRepo: findByConsumptionInputBatchId(inputBatchId)
|
||||
BatchRepo-->>Svc: List<Batch> (Stufe 1: direkte Verwendung)
|
||||
|
||||
loop Für jede gefundene Charge
|
||||
Svc->>BatchRepo: findByConsumptionInputBatchId(batchId)
|
||||
Note right of Svc: Rekursiv: auch Zwischenprodukte<br/>die in weiteren Chargen verwendet wurden
|
||||
end
|
||||
|
||||
Svc->>SMRepo: findByBatchIds(allAffectedBatchIds)
|
||||
Note right of SMRepo: Welche Bewegungen?<br/>An welche Filialen geliefert?
|
||||
|
||||
Svc-->>Ctrl: TraceabilityResult {<br/> affectedBatches[],<br/> affectedBranches[],<br/> stockMovements[]<br/>}
|
||||
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<br/>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:<br/>Rekursiv weiter zurückverfolgen
|
||||
end
|
||||
|
||||
Svc-->>Ctrl: TraceabilityResult {<br/> genealogyTree: {<br/> outputBatch → [inputBatch1, inputBatch2]<br/> inputBatch1 → [rawMaterialBatch1]<br/> },<br/> supplierBatches[] (Blätter des Baums)<br/>}
|
||||
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 |
|
||||
Loading…
Add table
Add a link
Reference in a new issue