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