From 8c042925eb07f8200013fe33cca66ea39da94ad9 Mon Sep 17 00:00:00 2001 From: Sebastian Frick Date: Fri, 20 Feb 2026 08:46:08 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20manuelle=20Testf=C3=A4lle=20f=C3=BCr=20?= =?UTF-8?q?Inventory=20und=20Production=20BC=20erg=C3=A4nzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testfälle aus allen GitHub-Issues mit Status 'Done' abgeleitet: - TC-SL (1-15): Lagerorte - TC-STK (1-5): Bestandspositionen - TC-BATCH (1-10): Chargen - TC-REC (1-22): Rezepte - TC-CYCLE (1-3): Zyklus-Erkennung - TC-BAT (1-4): Chargenplanung - TC-INV-CROSS/TC-PROD-CROSS: Übergreifende Tests --- backend/docs/MASTERDATA_MANUAL_TESTS.md | 350 +++++++++++++++++++++++- 1 file changed, 349 insertions(+), 1 deletion(-) diff --git a/backend/docs/MASTERDATA_MANUAL_TESTS.md b/backend/docs/MASTERDATA_MANUAL_TESTS.md index 5d39bd7..20b6a96 100644 --- a/backend/docs/MASTERDATA_MANUAL_TESTS.md +++ b/backend/docs/MASTERDATA_MANUAL_TESTS.md @@ -305,6 +305,346 @@ POST /api/customers/{b2c-id}/frame-contract --- +# Manuelle Testfälle – Inventory BC + +## Kontext +Der Inventory Bounded Context umfasst zwei Aggregate: +- **StorageLocation** – Lagerorte mit Typ und optionalem Temperaturbereich +- **Stock** – Bestandspositionen mit chargengenauer Führung (Batches) + +Alle Schreiboperationen erfordern `INVENTORY_WRITE`-Permission. API-Basis: `/api/inventory/` + +--- + +## TC-SL: Lagerorte (GitHub #1, #2, #3) + +### TC-SL-01: Lagerort erstellen – Pflichtfelder (Happy Path) +1. `POST /api/inventory/storage-locations` +2. Body: `name: "Kühlraum 1"`, `storageType: "COLD_ROOM"` → ohne Temperaturbereich +- [ ] **Erwartung:** 201 Created; Lagerort hat ID, Status aktiv + +### TC-SL-02: Lagerort erstellen – mit Temperaturbereich +1. `POST /api/inventory/storage-locations` +2. Body: `name: "Tiefkühllager"`, `storageType: "FREEZER"`, `minTemperature: -25`, `maxTemperature: -18` +- [ ] **Erwartung:** 201 Created; Temperaturbereich korrekt gespeichert + +### TC-SL-03: Lagerort erstellen – ungültiger Temperaturbereich (min >= max) +1. Body: `name: "Fehler-Lager"`, `storageType: "COLD_ROOM"`, `minTemperature: 10`, `maxTemperature: 5` +- [ ] **Erwartung:** 400 Bad Request; Validierungsfehler + +### TC-SL-04: Lagerort erstellen – Temperatur außerhalb Grenzen +1. Body: `minTemperature: -60`, `maxTemperature: 90` (Grenzen: -50 bis +80) +- [ ] **Erwartung:** 400 Bad Request + +### TC-SL-05: Doppelter Name wird abgelehnt +1. Lagerort `Kühlraum 1` erneut anlegen (Name existiert bereits) +- [ ] **Erwartung:** 409 Conflict; spezifische Fehlermeldung + +### TC-SL-06: Leerer Name wird abgelehnt +1. Body: `name: ""`, `storageType: "COLD_ROOM"` +- [ ] **Erwartung:** 400 Bad Request + +### TC-SL-07: Lagerort bearbeiten – Name und Temperatur ändern +1. `PUT /api/inventory/storage-locations/{id}` +2. Name auf `Kühlraum A` ändern, Temperaturbereich anpassen +- [ ] **Erwartung:** 200 OK; Änderungen gespeichert +- [ ] **Erwartung:** StorageType ist unverändert (immutable) + +### TC-SL-08: Lagerort bearbeiten – Unique-Check bei Namensänderung +1. Lagerort umbenennen auf einen bereits existierenden Namen +- [ ] **Erwartung:** 409 Conflict + +### TC-SL-09: Lagerort deaktivieren (ohne Bestand) +1. `PATCH /api/inventory/storage-locations/{id}/deactivate` (Lagerort ohne Stock) +- [ ] **Erwartung:** 200 OK; Status inaktiv + +### TC-SL-10: Lagerort deaktivieren (mit Bestand) schlägt fehl +1. Lagerort hat zugeordneten Stock → Deaktivieren versuchen +- [ ] **Erwartung:** 400/409; Fehlermeldung – Bestand existiert + +### TC-SL-11: Lagerort aktivieren +1. Deaktivierten Lagerort reaktivieren +- [ ] **Erwartung:** 200 OK; Status aktiv + +### TC-SL-12: Doppelte Deaktivierung/Aktivierung +1. Bereits inaktiven Lagerort erneut deaktivieren +- [ ] **Erwartung:** Spezifischer Fehler (bereits inaktiv) +2. Bereits aktiven Lagerort erneut aktivieren +- [ ] **Erwartung:** Spezifischer Fehler (bereits aktiv) + +### TC-SL-13: Lagerorte auflisten +1. `GET /api/inventory/storage-locations` +- [ ] **Erwartung:** Liste aller Lagerorte mit id, name, storageType, temperatureRange, active + +### TC-SL-14: Lagerorte filtern nach Typ +1. `GET /api/inventory/storage-locations?storageType=COLD_ROOM` +- [ ] **Erwartung:** Nur Kühlraum-Lagerorte + +### TC-SL-15: Lagerorte filtern nach Status +1. `GET /api/inventory/storage-locations?active=true` +- [ ] **Erwartung:** Nur aktive Lagerorte + +--- + +## TC-STK: Bestandspositionen (GitHub #4) + +### TC-STK-01: Bestandsposition erstellen (Happy Path) +1. `POST /api/inventory/stocks` +2. Body: `articleId: "{existierendeId}"`, `storageLocationId: "{existierendeId}"` +- [ ] **Erwartung:** 201 Created; Stock angelegt + +### TC-STK-02: Bestandsposition erstellen – mit MinimumLevel und MinimumShelfLife +1. Body: `articleId`, `storageLocationId`, `minimumLevel: 10`, `minimumShelfLife: 5` +- [ ] **Erwartung:** 201 Created; Werte korrekt gespeichert + +### TC-STK-03: Doppelte Bestandsposition (gleicher Artikel + Lagerort) +1. Gleiche Kombination `articleId + storageLocationId` erneut anlegen +- [ ] **Erwartung:** 409 Conflict + +### TC-STK-04: Bestandsposition – MinimumLevel negativ +1. Body: `minimumLevel: -1` +- [ ] **Erwartung:** 400 Bad Request + +### TC-STK-05: Bestandsposition – MinimumShelfLife 0 oder negativ +1. Body: `minimumShelfLife: 0` +- [ ] **Erwartung:** 400 Bad Request + +--- + +## TC-BATCH: Chargen einbuchen/entnehmen/sperren (GitHub #5, #6, #7) + +### TC-BATCH-01: Charge einbuchen (Happy Path) +1. `POST /api/inventory/stocks/{stockId}/batches` +2. Body: `batchId: "CH-001"`, `batchType: "PURCHASED"`, `quantity: 50`, `expiryDate: "2026-06-01"` +- [ ] **Erwartung:** 201 Created; Status AVAILABLE; receivedAt automatisch gesetzt + +### TC-BATCH-02: Charge einbuchen – Menge 0 oder negativ +1. Body: `quantity: 0` +- [ ] **Erwartung:** 400 Bad Request +2. Body: `quantity: -5` +- [ ] **Erwartung:** 400 Bad Request + +### TC-BATCH-03: Doppelte BatchReference im selben Stock +1. Erneut `batchId: "CH-001"`, `batchType: "PURCHASED"` einbuchen +- [ ] **Erwartung:** 400/409; Duplikat abgelehnt + +### TC-BATCH-04: Charge teilweise entnehmen +1. `POST /api/inventory/stocks/{stockId}/batches/{batchId}/remove` +2. Body: `quantity: 20` (von 50 verfügbar) +- [ ] **Erwartung:** 200 OK; Restmenge = 30 + +### TC-BATCH-05: Charge vollständig entnehmen +1. Restmenge (30) komplett entnehmen +- [ ] **Erwartung:** 200 OK; Charge wird entfernt + +### TC-BATCH-06: Entnahme übersteigt Bestand +1. Mehr entnehmen als verfügbar (z.B. 100 von 50) +- [ ] **Erwartung:** 400; NegativeStockNotAllowed + +### TC-BATCH-07: Charge sperren +1. `POST /api/inventory/stocks/{stockId}/batches/{batchId}/block` +2. Body: `reason: "Qualitätsprüfung ausstehend"` +- [ ] **Erwartung:** 200 OK; Status wechselt auf BLOCKED; Grund dokumentiert + +### TC-BATCH-08: Gesperrte Charge kann nicht entnommen werden +1. Entnahme aus BLOCKED-Charge versuchen +- [ ] **Erwartung:** 400; Entnahme verweigert + +### TC-BATCH-09: Charge entsperren +1. `POST /api/inventory/stocks/{stockId}/batches/{batchId}/unblock` +- [ ] **Erwartung:** 200 OK; Status zurück auf AVAILABLE (oder EXPIRING_SOON) + +### TC-BATCH-10: Doppeltes Sperren/Entsperren +1. Bereits gesperrte Charge erneut sperren +- [ ] **Erwartung:** Spezifischer Fehler +2. Bereits freigegebene Charge erneut entsperren +- [ ] **Erwartung:** Spezifischer Fehler + +--- + +# Manuelle Testfälle – Production BC + +## Kontext +Der Production Bounded Context umfasst: +- **Recipe** – Rezeptverwaltung mit Zutaten, Produktionsschritten, Status-Lifecycle +- **Batch** – Chargenplanung und -produktion +- **Quantity** – Value Object mit Catch-Weight-Unterstützung + +API-Basis: `/api/recipes` und `/api/batches` + +--- + +## TC-REC: Rezepte (GitHub #26, #27, #28, #29, #30, #31) + +### TC-REC-01: Rezept erstellen (Happy Path) +1. `POST /api/recipes` +2. Body: `name: "Bratwurst Classic"`, `version: 1`, `recipeType: "FINISHED_PRODUCT"`, `yieldPercentage: 85`, `shelfLifeDays: 14`, `outputQuantity: {amount: 10, uom: "KILOGRAM"}` +- [ ] **Erwartung:** 201 Created; Status = DRAFT + +### TC-REC-02: Rezept erstellen – YieldPercentage Grenzen +1. Body: `yieldPercentage: 0` +- [ ] **Erwartung:** 400; InvalidYieldPercentage +2. Body: `yieldPercentage: 201` +- [ ] **Erwartung:** 400; InvalidYieldPercentage + +### TC-REC-03: Rezept erstellen – ShelfLifeDays 0 bei FINISHED_PRODUCT +1. Body: `recipeType: "FINISHED_PRODUCT"`, `shelfLifeDays: 0` +- [ ] **Erwartung:** 400; InvalidShelfLife + +### TC-REC-04: Doppelter Name + Version wird abgelehnt +1. Erneut `name: "Bratwurst Classic"`, `version: 1` anlegen +- [ ] **Erwartung:** 409 Conflict + +### TC-REC-05: Zutat zu DRAFT-Rezept hinzufügen (Happy Path) +1. `POST /api/recipes/{id}/ingredients` +2. Body: `position: 1`, `articleId: "{id}"`, `quantity: {amount: 5, uom: "KILOGRAM"}`, `substitutable: false` +- [ ] **Erwartung:** 200 OK; Zutat im Rezept + +### TC-REC-06: Zweite Zutat hinzufügen +1. Body: `position: 2`, andere ArticleId, Menge +- [ ] **Erwartung:** 200 OK; 2 Zutaten im Rezept + +### TC-REC-07: Zutat mit doppelter Position +1. Body: `position: 1` (bereits vergeben) +- [ ] **Erwartung:** 400; DuplicatePosition + +### TC-REC-08: Zutat zu ACTIVE-Rezept hinzufügen +1. Rezept aktivieren, dann Zutat hinzufügen versuchen +- [ ] **Erwartung:** 400; NotInDraftStatus + +### TC-REC-09: Zutat mit SubRecipeId (Zwischen-Rezept) +1. INTERMEDIATE-Rezept als SubRecipe anlegen +2. Zutat mit `subRecipeId` zum Hauptrezept hinzufügen +- [ ] **Erwartung:** 200 OK; Zutat referenziert Sub-Rezept + +### TC-REC-10: Zutat entfernen +1. `DELETE /api/recipes/{id}/ingredients/{ingredientId}` +- [ ] **Erwartung:** 200 OK; Zutat entfernt, Position-Lücke erlaubt + +### TC-REC-11: Produktionsschritt hinzufügen (Happy Path) +1. `POST /api/recipes/{id}/steps` +2. Body: `stepNumber: 1`, `description: "Fleisch wolfen"`, `durationMinutes: 15`, `temperatureCelsius: 4` +- [ ] **Erwartung:** 200 OK; Schritt im Rezept + +### TC-REC-12: Produktionsschritt – leere Beschreibung +1. Body: `stepNumber: 2`, `description: ""` +- [ ] **Erwartung:** 400; Validierungsfehler + +### TC-REC-13: Produktionsschritt zu ACTIVE-Rezept +1. Schritt zu aktiviertem Rezept hinzufügen versuchen +- [ ] **Erwartung:** 400; NotInDraftStatus + +### TC-REC-14: Produktionsschritt entfernen +1. `DELETE /api/recipes/{id}/steps/{stepNumber}` +- [ ] **Erwartung:** 200 OK; Schritt entfernt, keine automatische Umnummerierung + +### TC-REC-15: Rezept aktivieren (Happy Path) +1. Rezept mit mindestens einer Zutat → `POST /api/recipes/{id}/activate` +- [ ] **Erwartung:** 200 OK; Status = ACTIVE + +### TC-REC-16: Rezept aktivieren – ohne Zutaten +1. Leeres DRAFT-Rezept aktivieren +- [ ] **Erwartung:** 400; NoIngredients + +### TC-REC-17: Rezept aktivieren – bereits ACTIVE +1. Aktives Rezept erneut aktivieren +- [ ] **Erwartung:** 400; InvalidStatusTransition + +### TC-REC-18: Rezept archivieren (Happy Path) +1. `POST /api/recipes/{id}/archive` (Rezept ist ACTIVE) +- [ ] **Erwartung:** 200 OK; Status = ARCHIVED + +### TC-REC-19: Rezept archivieren – aus DRAFT +1. DRAFT-Rezept archivieren versuchen +- [ ] **Erwartung:** 400; InvalidStatusTransition + +### TC-REC-20: Rezept per ID abfragen +1. `GET /api/recipes/{id}` +- [ ] **Erwartung:** 200 OK; vollständiges Rezept mit Zutaten und Schritten + +### TC-REC-21: Rezept nicht gefunden +1. `GET /api/recipes/{nicht-existierende-id}` +- [ ] **Erwartung:** 404 Not Found + +### TC-REC-22: Rezepte nach Status filtern +1. `GET /api/recipes?status=ACTIVE` +- [ ] **Erwartung:** Nur aktive Rezepte; Liste enthält Zutaten-Count aber nicht volle Zutaten + +--- + +## TC-CYCLE: Zyklus-Erkennung (GitHub #32) + +### TC-CYCLE-01: Lineare Verschachtelung erlaubt (A → B → C) +1. Rezept C (ACTIVE, mit Zutat) +2. Rezept B (DRAFT) → Zutat mit `subRecipeId: C` +3. Rezept A (DRAFT) → Zutat mit `subRecipeId: B` +- [ ] **Erwartung:** Alle Zuweisungen erfolgreich; keine Fehler + +### TC-CYCLE-02: Direkte Zirkularität (A → B → A) +1. Rezept A enthält Zutat mit `subRecipeId: B` +2. Rezept B: Zutat mit `subRecipeId: A` hinzufügen +- [ ] **Erwartung:** 400; CyclicDependencyDetected mit Pfad-Angabe + +### TC-CYCLE-03: Indirekte Zirkularität (A → B → C → A) +1. A → B → C existiert +2. Zutat mit `subRecipeId: A` zu Rezept C hinzufügen +- [ ] **Erwartung:** 400; CyclicDependencyDetected mit Pfad-Angabe + +--- + +## TC-BAT: Chargenplanung (GitHub #33) + +### TC-BAT-01: Charge planen (Happy Path) +1. `POST /api/batches` +2. Body: `recipeId: "{id}"`, `plannedQuantity: {amount: 50, uom: "KILOGRAM"}`, `productionDate: "2026-03-01"`, `bestBeforeDate: "2026-03-15"` +- [ ] **Erwartung:** 201 Created; Status = PLANNED; BatchNumber automatisch generiert (Format `P-YYYY-MM-DD-XXX`) + +### TC-BAT-02: Charge planen – PlannedQuantity 0 +1. Body: `plannedQuantity: {amount: 0, uom: "KILOGRAM"}` +- [ ] **Erwartung:** 400; Menge muss positiv sein + +### TC-BAT-03: Charge planen – BestBeforeDate vor ProductionDate +1. Body: `productionDate: "2026-03-15"`, `bestBeforeDate: "2026-03-01"` +- [ ] **Erwartung:** 400; BestBeforeDate muss nach ProductionDate liegen + +### TC-BAT-04: BatchNumber-Format prüfen +1. Mehrere Chargen am selben Tag planen +- [ ] **Erwartung:** BatchNumbers haben Format `P-YYYY-MM-DD-XXX` mit aufsteigender Sequenz + +--- + +## TC-INV-CROSS: Übergreifende Tests – Inventory + +### TC-INV-CROSS-01: Sequenz – Kompletter Lagerort-Workflow +1. Lagerort erstellen → Name ändern → Temperatur anpassen → deaktivieren (ohne Stock) → reaktivieren +- [ ] **Erwartung:** Alle Schritte funktionieren ohne Datenverlust + +### TC-INV-CROSS-02: Sequenz – Kompletter Chargen-Workflow +1. Lagerort erstellen → Stock anlegen → Charge einbuchen → Teilentnahme → Charge sperren → Charge entsperren → Rest entnehmen +- [ ] **Erwartung:** Konsistenz über alle Schritte; Mengen korrekt + +### TC-INV-CROSS-03: Lagerort mit Bestand kann nicht deaktiviert werden +1. Lagerort mit Stock + Charge → Deaktivierung versuchen +- [ ] **Erwartung:** Fehler; Bestand muss erst entfernt werden + +--- + +## TC-PROD-CROSS: Übergreifende Tests – Production + +### TC-PROD-CROSS-01: Sequenz – Rezept-Lifecycle komplett +1. Rezept erstellen (DRAFT) → 2 Zutaten hinzufügen → 2 Schritte hinzufügen → 1 Zutat entfernen → aktivieren → archivieren +- [ ] **Erwartung:** Konsistenz über alle Schritte + +### TC-PROD-CROSS-02: Sequenz – Rezept + Chargenplanung +1. Rezept erstellen → Zutaten → aktivieren → Charge planen mit diesem Rezept +- [ ] **Erwartung:** Charge referenziert Rezept korrekt + +### TC-PROD-CROSS-03: Verschachteltes Rezept vollständig +1. INTERMEDIATE-Rezept erstellen + aktivieren → FINISHED_PRODUCT-Rezept mit SubRecipe-Zutat → aktivieren → Charge planen +- [ ] **Erwartung:** Alle Abhängigkeiten korrekt aufgelöst + +--- + ## Verifikation / Testdurchführung ```bash @@ -315,7 +655,7 @@ POST /api/customers/{b2c-id}/frame-contract cd frontend/apps/cli && pnpm dev # Backend-Logs beobachten (Fehler sichtbar machen) -# Direkte API-Tests (für TC-B2B-02, TC-AUTH) +# Direkte API-Tests (für TC-B2B-02, TC-AUTH, Inventory, Production) curl -X POST http://localhost:8080/api/... -H "Authorization: Bearer " ``` @@ -327,3 +667,11 @@ curl -X POST http://localhost:8080/api/... -H "Authorization: Bearer " - [ ] TC-B2B (1-2) durchgeführt - [ ] TC-AUTH (1-3) durchgeführt - [ ] TC-CROSS (1-5) durchgeführt +- [ ] Alle TC-SL (1-15) durchgeführt +- [ ] Alle TC-STK (1-5) durchgeführt +- [ ] Alle TC-BATCH (1-10) durchgeführt +- [ ] Alle TC-REC (1-22) durchgeführt +- [ ] TC-CYCLE (1-3) durchgeführt +- [ ] TC-BAT (1-4) durchgeführt +- [ ] TC-INV-CROSS (1-3) durchgeführt +- [ ] TC-PROD-CROSS (1-3) durchgeführt