# Manuelle Testfälle – Masterdata BC ## Kontext Der Masterdata Bounded Context ist vollständig implementiert und umfasst vier Aggregate: - **ProductCategory** – einfaches Kategorie-Aggregate - **Supplier** – Lieferanten mit Zertifikaten, Bewertungen, Adressen - **Article** – Artikel mit Verkaufseinheiten (SalesUnits), Lieferantenzuordnung - **Customer** – Kunden (B2B/B2C) mit Lieferadressen, Präferenzen, Rahmenvertrag (nur B2B) Die TUI ist das Testwerkzeug für das Backend. Alle Schreiboperationen erfordern `MASTERDATA_WRITE`-Permission. --- ## Voraussetzungen - Backend läuft (`./mvnw spring-boot:run` oder Docker) - TUI läuft (`pnpm dev` im CLI-Verzeichnis) - Eingeloggt als User **mit** `MASTERDATA_WRITE`-Permission (z.B. `admin`) - Eingeloggt als User **ohne** `MASTERDATA_WRITE`-Permission (z.B. `viewer`) – für Authz-Tests --- ## TC-CAT: Produktkategorien ### TC-CAT-01: Kategorie erstellen (Happy Path) 1. Masterdata → Produktkategorien → `[n]` Neu 2. Name: `Obst & Gemüse`, Beschreibung: `Frische Produkte` → Enter - [x] **Erwartung:** Kategorie erscheint in der Liste; Name und Beschreibung korrekt ### TC-CAT-02: Kategorie erstellen – ohne Beschreibung 1. Name: `Milchprodukte`, Beschreibung: leer → Enter - [x] **Erwartung:** Kategorie wird angelegt; Beschreibung fehlt ohne Fehler ### TC-CAT-03: Kategorie bearbeiten 1. Kategorie `Obst & Gemüse` auswählen → `[e]` 2. Name ändern auf `Obst und Gemüse`, Beschreibung auf `Saisonale Frische` - [x] **Erwartung:** Änderungen werden gespeichert und korrekt angezeigt ### TC-CAT-04: Doppelter Name wird abgelehnt 1. Neue Kategorie mit Name `Milchprodukte` (existiert bereits) anlegen - [x] **Erwartung:** Fehlermeldung – Name bereits vergeben; kein Datensatz angelegt ### TC-CAT-05: Kategorie löschen 1. Kategorie `Milchprodukte` in der Liste auswählen → `[d]` 2. Bestätigungsdialog mit `Ja` bestätigen - [c] **Erwartung:** Kategorie verschwindet aus der Liste ### TC-CAT-06: Leerer Name wird abgelehnt 1. Neue Kategorie, Name leer → Enter - [x] **Erwartung:** Fehler / Eingabe nicht möglich; kein API-Aufruf --- ## TC-SUP: Lieferanten ### TC-SUP-01: Lieferant erstellen – Pflichtfelder 1. Lieferanten → `[n]` Neu 2. Name: `Frisch AG`, Telefon: `+49 30 12345` → restliche Felder leer → Enter - [x] **Erwartung:** Lieferant erscheint in der Liste, Status `AKTIV` ### TC-SUP-02: Lieferant erstellen – alle Felder 1. Name: `Bio GmbH`, Telefon: `+49 89 999`, E-Mail: `bio@example.com` 2. Ansprechpartner: `Max Muster` 3. Adresse: `Gartenstraße 5`, `12`, `80333`, `München`, `Deutschland` 4. Zahlungsziel: `30` Tage - [/] **Erwartung:** Alle Daten in der Detailansicht korrekt angezeigt ### TC-SUP-03: Lieferant erstellen – ohne Pflichtfelder 1. Name leer → versuchen zu speichern - [x] **Erwartung:** Fehler; kein Lieferant angelegt 3. Name gefüllt, Telefon leer → versuchen zu speichern - [x] **Erwartung:** Fehler; kein Lieferant angelegt ### TC-SUP-04: Doppelter Name wird abgelehnt 1. Neuen Lieferanten `Frisch AG` anlegen (Name existiert bereits) - [x] **Erwartung:** Fehlermeldung – Name bereits vergeben ### TC-SUP-05: Lieferant deaktivieren und aktivieren 1. `Frisch AG` → Detailansicht → `[Deaktivieren]` → Bestätigen - [x] **Erwartung:** Status wechselt auf `INAKTIV` (roter Punkt in Liste) 3. Erneut öffnen → `[Aktivieren]` → Bestätigen - [x] **Erwartung:** Status wechselt auf `AKTIV` ### TC-SUP-06: Lieferant filtern 1. Lieferantenliste: `[A]` – nur Aktive - [x] **Erwartung:** Nur AKTIV-Lieferanten sichtbar 3. `[I]` – nur Inaktive - [x] **Erwartung:** Nur INAKTIV-Lieferanten sichtbar 5. `[a]` – alle - [x] **Erwartung:** Alle Lieferanten sichtbar ### TC-SUP-07: Lieferant bewerten 1. `Frisch AG` → `[Bewerten]` 2. Qualität: 4, Lieferung: 3, Preis: 5 → Enter - [x] **Erwartung:** Bewertung in Detailansicht sichtbar; Durchschnitt = 4.0 4. In der Liste: Stern-Anzeige `★ 4.0` ### TC-SUP-08: Bewertung – Grenzen (1 und 5) 1. Alle Scores auf 1 setzen → Speichern - [x] **Erwartung:** Gespeichert; Durchschnitt = 1.0 3. Alle Scores auf 5 → Speichern - [x] **Erwartung:** Gespeichert; Durchschnitt = 5.0 ### TC-SUP-09: Zertifikat hinzufügen (gültig) 1. `Frisch AG` → `[Zertifikat hinzufügen]` 2. Typ: `ISO9001`, Aussteller: `TÜV`, ab: `2024-01-01`, bis: `2027-01-01` - [x] **Erwartung:** Zertifikat erscheint in Detailansicht; Anzahl in Liste = 1 ### TC-SUP-10: Zertifikat hinzufügen (abgelaufen) 1. Zertifikat, bis: `2023-12-31` (Datum in der Vergangenheit) - [x] **Erwartung:** Zertifikat wird angelegt (keine Ablauf-Prüfung beim Hinzufügen); in Detail sichtbar *(Edge Case: System soll abgelaufene Zertifikate anzeigen, nicht blocken)* ### TC-SUP-11: Doppeltes Zertifikat wird abgelehnt 1. Erneut `ISO9001`, `TÜV`, `2024-01-01` hinzufügen - [x] **Erwartung:** Fehlermeldung – Duplikat abgelehnt ### TC-SUP-12: Zertifikat entfernen 1. `Frisch AG` → `[Zertifikat entfernen]` → Zertifikat auswählen → Enter - [x] **Erwartung:** Zertifikat aus Detailansicht verschwunden --- ## TC-ART: Artikel ### TC-ART-01: Artikel erstellen – PIECE_FIXED 1. Artikel → `[n]` Neu 2. Name: `Äpfel Gala`, Nummer: `OG-001` 3. Kategorie: `Obst & Gemüse` (mit ← →) 4. Einheit: `PIECE_FIXED`, Preis: `1.99` - [x] **Erwartung:** Artikel in Liste; Preismodell automatisch `FIXED` ### TC-ART-02: Artikel erstellen – KG (gewichtsbasiert) 1. Name: `Bananen`, Nummer: `OG-002` 2. Einheit: `KG`, Preis: `2.49` - [x] **Erwartung:** Preismodell automatisch `WEIGHT_BASED` ### TC-ART-03: Artikel erstellen – HUNDRED_GRAM und PIECE_VARIABLE 1. Einheit `HUNDRED_GRAM` → Preismodell `WEIGHT_BASED` 2. Einheit `PIECE_VARIABLE` → Preismodell `WEIGHT_BASED` - [x] **Erwartung:** Konsistenz in beiden Fällen korrekt ### TC-ART-04: Doppelte Artikelnummer wird abgelehnt 1. Neuen Artikel mit Nummer `OG-001` anlegen - [x] **Erwartung:** Fehlermeldung – Artikelnummer bereits vergeben ### TC-ART-05: Artikel deaktivieren und aktivieren 1. `Äpfel Gala` → `[Deaktivieren]` → Bestätigen - [x] **Erwartung:** Status INAKTIV - [x] **Erwartung:** Status AKTIV ### TC-ART-06: Artikel filtern 1. `[A]` nur Aktive, `[I]` nur Inaktive, `[a]` alle - [x] **Erwartung:** Filter wirkt korrekt ### TC-ART-07: Verkaufseinheit hinzufügen 1. `Äpfel Gala` → `[Verkaufseinheit hinzufügen]` 2. Einheit: `KG`, Preis: `3.50` - [x] **Erwartung:** Zweite VE in Detailansicht; Anzahl VE in Liste = 2 ### TC-ART-08: Doppelte Einheit wird abgelehnt 1. Erneut `PIECE_FIXED` für `Äpfel Gala` hinzufügen - [x] **Erwartung:** Fehlermeldung – Einheit bereits vorhanden ### TC-ART-09: Letzte Verkaufseinheit kann nicht entfernt werden 1. Artikel mit genau einer VE → `[Verkaufseinheit entfernen]` - [x] **Erwartung:** Aktion nicht verfügbar / Fehler – mindestens eine VE erforderlich ### TC-ART-10: Verkaufseinheit entfernen (wenn 2+ vorhanden) 1. `Äpfel Gala` hat 2 VE → `[Verkaufseinheit entfernen]` → KG-Einheit wählen - [x] **Erwartung:** VE entfernt; nur noch PIECE_FIXED vorhanden ### TC-ART-11: Lieferant dem Artikel zuweisen *(falls TUI-Unterstützung vorhanden)* 1. `Äpfel Gala` → Lieferant `Frisch AG` zuweisen - [ ] **Erwartung:** Lieferant in Detailansicht sichtbar --- ## TC-CUS: Kunden ### TC-CUS-01: B2C-Kunde erstellen 1. Kunden → `[n]` Neu 2. Typ: `B2C`, Name: `Max Mustermann`, Telefon: `+49 176 12345` 3. Rechnungsadresse: `Musterstr. 1`, `2`, `10115`, `Berlin`, `Deutschland` - [ ] **Erwartung:** Kunde in Liste, Typ-Badge `B2C`, Status `AKTIV` ### TC-CUS-02: B2B-Kunde erstellen 1. Typ: `B2B`, Name: `Gastro GmbH`, Telefon: `+49 30 9876` 2. Rechnungsadresse vollständig ausfüllen - [ ] **Erwartung:** Kunde in Liste, Typ-Badge `B2B` ### TC-CUS-03: Pflichtfelder Validierung 1. Name leer → Fehler 2. Telefon leer → Fehler 3. Rechnungsadresse unvollständig → Fehler - [ ] **Erwartung:** Jeweils spezifische Fehlermeldung ### TC-CUS-04: Doppelter Kundenname wird abgelehnt 1. Erneut `Gastro GmbH` anlegen - [ ] **Erwartung:** Fehlermeldung – Name bereits vergeben ### TC-CUS-05: Kunde deaktivieren und aktivieren 1. `Max Mustermann` → `[Deaktivieren]` → Bestätigen → `[Aktivieren]` - [ ] **Erwartung:** Status wechselt korrekt ### TC-CUS-06: Kunden filtern (Status + Typ) 1. `[A]` → nur Aktive; `[I]` → nur Inaktive; `[a]` → alle 2. `[B]` → nur B2B; `[C]` → nur B2C; `[b]` → alle 3. Kombination: Aktive B2B-Kunden - [ ] **Erwartung:** Alle Filter wirken korrekt und kombinierbar ### TC-CUS-07: Lieferadresse hinzufügen 1. `Gastro GmbH` → `[Lieferadresse hinzufügen]` 2. Label: `Hauptküche`, Straße: `Kochstr.`, Nr: `12`, PLZ: `10963`, Stadt: `Berlin`, Land: `Deutschland` 3. Ansprechpartner: `Koch Müller`, Lieferhinweis: `Bitte kühlen` - [ ] **Erwartung:** Lieferadresse in Detailansicht; Anzahl in Liste = 1 ### TC-CUS-08: Lieferadresse entfernen 1. `[Lieferadresse entfernen]` → `Hauptküche` auswählen - [ ] **Erwartung:** Lieferadresse entfernt ### TC-CUS-09: Mehrere Lieferadressen 1. Zwei Lieferadressen `Filiale Nord` und `Filiale Süd` hinzufügen - [ ] **Erwartung:** Beide in Detailansicht; Anzahl in Liste = 2 ### TC-CUS-10: Präferenzen setzen 1. `Max Mustermann` → `[Präferenzen setzen]` 2. `BIO` und `REGIONAL` aktivieren → Enter - [ ] **Erwartung:** Präferenzen in Detailansicht sichtbar 4. Erneut öffnen → nur `HALAL` aktivieren → Enter - [ ] **Erwartung:** Nur `HALAL` gesetzt (Set wird ersetzt, nicht ergänzt) ### TC-CUS-11: Alle Präferenzen abwählen 1. `[Präferenzen setzen]` → alle deaktivieren → Enter - [ ] **Erwartung:** Keine Präferenzen mehr sichtbar --- ## TC-B2B: Rahmenverträge (B2B-spezifisch) *Vorbedingung: `Gastro GmbH` (B2B) existiert; Artikel `Äpfel Gala` existiert* ### TC-B2B-01: Rahmenvertrag für B2B-Kunden erstellen *(falls TUI-Unterstützung vorhanden)* 1. `Gastro GmbH` → Rahmenvertrag-Bereich 2. Gültig ab: `2025-01-01`, bis: `2025-12-31`, Rhythmus: `WEEKLY` 3. Position: Artikel `Äpfel Gala`, vereinbarter Preis: `1.50`, Menge: `100` - [ ] **Erwartung:** Rahmenvertrag in Detailansicht sichtbar ### TC-B2B-02: Rahmenvertrag für B2C-Kunden nicht möglich *(Backend-Test via API)* ``` POST /api/customers/{b2c-id}/frame-contract ``` - [ ] **Erwartung:** HTTP 400/422, Fehler `FrameContractNotAllowed` --- ## TC-AUTH: Autorisierung ### TC-AUTH-01: Lesezugriff ohne MASTERDATA_WRITE 1. Als `viewer` (ohne MASTERDATA_WRITE) einloggen 2. Alle Listen aufrufen (Kategorien, Lieferanten, Artikel, Kunden) - [ ] **Erwartung:** Daten sichtbar, kein Fehler ### TC-AUTH-02: Schreibzugriff ohne MASTERDATA_WRITE wird abgelehnt 1. Als `viewer` versuchen: neue Kategorie erstellen - [ ] **Erwartung:** HTTP 403 / Fehlermeldung in TUI; kein Datensatz angelegt 3. Dasselbe für Lieferant, Artikel, Kunde anlegen - [ ] **Erwartung:** Jeweils Ablehnung ### TC-AUTH-03: Schreibzugriff mit MASTERDATA_WRITE funktioniert 1. Als `admin` (mit MASTERDATA_WRITE) → alle CRUD-Operationen möglich - [ ] **Erwartung:** Alle Operationen erfolgreich --- ## TC-CROSS: Übergreifende / Integrations-Tests ### TC-CROSS-01: Artikel-Lieferant-Verknüpfung konsistent 1. Lieferant `Frisch AG` einem Artikel zuweisen 2. `Frisch AG` deaktivieren 3. Artikel aufrufen → Lieferant weiterhin referenziert (keine Zwangstrennung) - [ ] **Erwartung:** Artikel zeigt `Frisch AG` trotz INAKTIV-Status ### TC-CROSS-02: Kategorie in Artikelauswahl verfügbar 1. Neue Kategorie `Getränke` anlegen 2. Artikel erstellen → Kategorie-Auswahl enthält `Getränke` - [ ] **Erwartung:** Neue Kategorien sofort im Artikel-Formular verfügbar ### TC-CROSS-03: Sequenz – Kompletter Lieferant-Workflow 1. Lieferant erstellen → bewerten → Zertifikat hinzufügen → deaktivieren → wieder aktivieren → Zertifikat entfernen - [ ] **Erwartung:** Alle Schritte funktionieren in Folge ohne Datenverlust ### TC-CROSS-04: Sequenz – Kompletter Artikel-Workflow 1. Kategorie erstellen → Artikel erstellen (mit Kategorie) → 2. VE hinzufügen → 1. VE entfernen → Artikel deaktivieren → aktivieren - [ ] **Erwartung:** Konsistenz über alle Schritte ### TC-CROSS-05: Sequenz – B2B-Kunde vollständig 1. B2B-Kunde erstellen → 2 Lieferadressen → Präferenzen setzen → 1 Adresse entfernen → Präferenzen ändern → deaktivieren → aktivieren - [ ] **Erwartung:** Konsistenz über alle Schritte --- # 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 # Backend starten ./mvnw spring-boot:run # TUI starten cd frontend/apps/cli && pnpm dev # Backend-Logs beobachten (Fehler sichtbar machen) # Direkte API-Tests (für TC-B2B-02, TC-AUTH, Inventory, Production) curl -X POST http://localhost:8080/api/... -H "Authorization: Bearer " ``` **Checkliste nach Test-Durchlauf:** - [ ] Alle TC-CAT (1-6) durchgeführt - [ ] Alle TC-SUP (1-12) durchgeführt - [ ] Alle TC-ART (1-11) durchgeführt - [ ] Alle TC-CUS (1-11) durchgeführt - [ ] 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