mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 17:29:58 +01:00
884 lines
31 KiB
Markdown
884 lines
31 KiB
Markdown
# 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 |
|