# 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
<!-- TODO: Löschen nicht implementiert -->

### 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
<!-- TODO: Wenn  'Deutschland' eingegeben wird bekommt man einen Fehler dass nur Länderkennzeichen z.B. DE erlaubt sind -->

### 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
<!-- TODO: Duplikat wird abgelehnt, ABER Fehlermeldung schlecht 'Unexpected error occurred. ...' -->

### TC-SUP-12: Zertifikat entfernen
1. `Frisch AG` → `[Zertifikat entfernen]` → Zertifikat auswählen → Enter
- [x] **Erwartung:** Zertifikat aus Detailansicht verschwunden
<!-- TODO: Zertifikatsauswahl zeigt kein Datum, daher schwer zu entscheiden wenn mehrere gleiche Zertifikate für untersch. Jahre -->

---

## 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
<!-- TODO: Aktuell noch nicht testbar -->

---

## 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 <token>"
```

**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
