31 KiB
Effigenix ERP – Fachliche Sequenzdiagramme
Generiert aus den GitHub Issues und DDD-Planungsdokumenten. Alle Diagramme nutzen Mermaid-Syntax.
Inhaltsverzeichnis
- Stammdaten: Lagerorte verwalten
- Bestände: Bestandsposition & Chargen verwalten
- Chargen einbuchen & entnehmen
- Chargen sperren/entsperren & MHD-Überwachung
- Reservierungen (FEFO)
- Bestandsbewegungen (Stock Movements)
- Inventur
- Rezeptverwaltung
- Chargenplanung & Produktion (Batch Lifecycle)
- Produktionsaufträge (Production Orders)
- Produktion über Auftrag starten (End-to-End)
- Integration: Production → Inventory
- Integration: Procurement → Inventory
- Integration: Quality → Inventory
- Traceability: Vorwärts- & Rückwärts-Tracing
1. Stammdaten: Lagerorte verwalten
Issues: #1 (anlegen), #2 (bearbeiten/deaktivieren), #3 (abfragen)
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)
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)
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)
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
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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 |