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