1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 11:59:35 +01:00
effigenix/backend/docs/mvp/sequence-diagrams.md
2026-02-24 22:19:37 +01:00

884 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |