Warenwirtschaft: Anforderungsrunde 12.06. — Plan V5.0 + AP-26/AP-25/AP-22
Neue Anforderungen (docs/) interpretiert und als Entwicklungsplan V5.0 (AP-20 bis AP-28) aufgenommen; erste drei Pakete umgesetzt: AP-26 Ausschuss-Gründe konfigurierbar: - Stammdaten-Tabelle disposal_reasons + CRUD unter Einstellungen → Allgemein - StockDisposalController liest aktive DB-Gründe statt hartkodierter Liste - Seeder übernimmt die bisherigen 6 Gründe idempotent AP-25 Lieferbestand — Datum statt Tage: - "Nicht vorrätig" wird über Datepicker "Wieder lieferbar ab" gepflegt; Resttage-Hinweis zählt täglich automatisch herunter - Interne Bestellliste wieder kaufbar: Hinweis erscheint zusätzlich zu den Mengen-Buttons (VP entscheidet selbst) AP-22 Produktbestand-Erweiterungen: - Default-Sortierung nach Dringlichkeit, Status-Kopf toggelt - Alle vier Status-Kacheln als Filter klickbar - Neue Spalte "Verbrauch/Monat" (Ø Abgänge der letzten 6 Monate) - Produkt-Flag "Im Produktbestand anzeigen" (products.show_in_product_stock) Tests: 77 grün (DisposalReasonSettings 8, ProductOutOfStock 8, ProductStock 13 + Regression). Hinweise-Doku + Plan-Protokoll fortgeschrieben; nächster Schritt laut Plan: AP-21 (INCI-Erweiterungen). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a8f6fef38e
commit
e53201f229
32 changed files with 1377 additions and 94 deletions
|
|
@ -1,5 +1,7 @@
|
|||
# Aktualisierter Entwicklungsplan: Warenwirtschaft, Produktion & Produktbestand
|
||||
|
||||
> ⚠️ **ABGELÖST (12.06.2026):** Operative Arbeitsgrundlage ist jetzt `entwicklungsplan-aktualisiert-12-06-2026.md` (V5.0). Dieses Dokument (V4.0) bleibt als Referenz/Historie für AP-00 … AP-19 bestehen.
|
||||
>
|
||||
> **Version:** 4.0 - Stand 02.06.2026
|
||||
> **Ersetzt:** `entwicklungsplan-aktualisiert-27-04-2026.md` (V3.0) als operative Arbeitsgrundlage
|
||||
> **Referenzen:** `entwicklungsplan.md` (V2.0), `briefing-anpassungen-27-04-2026.md`, `feedback.md`, `konzept-final.md`, `docs/Todos.md`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,367 @@
|
|||
# Aktualisierter Entwicklungsplan: Warenwirtschaft, Produktion & Produktbestand
|
||||
|
||||
> **Version:** 5.0 - Stand 12.06.2026
|
||||
> **Ersetzt:** `entwicklungsplan-aktualisiert-02-06-2026.md` (V4.0) als operative Arbeitsgrundlage
|
||||
> **Neue Anforderungsquelle:** `docs/nächsten Anforderungen an die WaWi.md` (Kunde, 12.06.2026) + Screens `screens/2026-12-06-Rezeptur-phase.jpeg`, `screens/2026-12-06-neue-produktion.jpeg`
|
||||
> **Referenzen:** V4.0 (AP-00 … AP-19), `briefing-anpassungen-27-04-2026.md`, `feedback.md`, `konzept-final.md`, `docs/Todos.md`
|
||||
> **Methodik:** Backlog aus kleinen, sequenziell abarbeitbaren Arbeitspaketen (AP). Jedes AP hat Ziel, konkrete Schritte mit Dateipfaden, DB-Änderungen, Akzeptanzkriterien und Tests. Reihenfolge so gewählt, dass jedes AP einzeln deploybar ist.
|
||||
|
||||
---
|
||||
|
||||
## 0. Was dieses Dokument neu macht
|
||||
|
||||
V4.0 hat **AP-00 bis AP-19 abgeschlossen** (Bugfixes, Einstellungen, Lieferanten/INCI/Einkauf erweitert, Produktion auf Hersteller-Rezeptur, Sets, „Nicht vorrätig", Rohstoffbestand, Produktbestand + Historie, Ausgang/Ausschuss, Hinweise-Doku, UI-Vereinheitlichung). **AP-13 (Shop-Bestandsabzug) liegt als Konzept vor, ist aber noch nicht umgesetzt.**
|
||||
|
||||
V5.0 nimmt die **neue Anforderungsrunde vom 12.06.2026** auf. Diese ist überwiegend **Feinschliff und Ergänzung an bereits gebauten Bausteinen** (Rohstoffbestand, INCI, Produktbestand, Ausschuss, Wareneingang) plus **zwei größere fachliche Erweiterungen**:
|
||||
|
||||
1. **Rezeptur-Phasen** auf der Hersteller-Rezeptur (Screen 1).
|
||||
2. **Neue Produktions-Maske mit Produktionsergebnis** — der Produktbestand wird **nicht** mehr aus der geplanten Stückzahl gebucht, sondern aus dem real eingetragenen Ergebnis (mehrere Outputs möglich). Der Rohstoffverbrauch bleibt wie gehabt chargenbasiert (Screen 2).
|
||||
|
||||
Der **reale Code-Stand wurde erneut verifiziert** (12.06.2026); die daraus folgenden Abgrenzungen stehen je AP unter „Ist-Stand".
|
||||
|
||||
Geprüfte Dateien u. a.: `RawMaterialStockController`, `ProductStockController` + `ProductStockService`, `IngredientController` + `app/Models/Ingredient.php` + `resources/views/admin/ingredient/*`, `StockDisposalController`, `StockEntryController` + `app/Models/StockEntry.php`, `ProductionController` + `app/Services/ProductionService.php` + `app/Models/Production*.php`, `app/Models/Product.php`, `GeneralSettingController`, `app/Models/Location.php`, Migrationen unter `database/migrations/`.
|
||||
|
||||
---
|
||||
|
||||
## 0a. Umsetzungsprotokoll V5.0 (laufend)
|
||||
|
||||
> Jede abgeschlossene Teil-Lieferung wird hier mit Datum, betroffenen Dateien und Test-Status protokolliert.
|
||||
|
||||
| Datum | AP | Kurzbeschreibung | Tests |
|
||||
|---|---|---|---|
|
||||
| 12.06.2026 | **AP-22 (Produktbestand-Erweiterungen)** | Migration `2026_06_12_175615_add_show_in_product_stock_to_products_table` (`products.show_in_product_stock` bool default 1). `Product` fillable+cast; Checkbox „Im Produktbestand anzeigen" in der Warenwirtschaft-Card (`ProductRepository` normalisiert; Hinweistext „Abrechnung Druckkosten / Logo-Etiketten"). `ProductStockController@index` + `ProductStockService::criticalProductCount()` filtern auf das Flag (ausgeblendete Produkte zählen nicht im Badge). **Dringlichkeits-Sortierung:** Übersicht serverseitig default nach Status-Rang (kritisch → niedrig → ok), innerhalb nach Bestand aufsteigend, dann Name; Klick auf den „Status"-Spaltenkopf toggelt die Reihenfolge (JS über `data-rank`). **Kacheln:** alle vier Kacheln (`Produkte`/`Bestand OK`/`Niedrig`/`Kritisch`) klickbar — „Produkte" hebt den Filter auf, die anderen filtern exakt ihren Status (Checkbox „nur kritische" bleibt erhalten). **Neue Spalte „Verbrauch/Monat":** `ProductStockService::monthlyConsumptionByProduct()` = Ø der `out`-Bewegungen der letzten 6 Monate (rollierendes Fenster), **ohne** `source=production` (Produktions-Gegenbuchungen sind Korrekturen, kein Verbrauch); mit AP-13 fließen Verkäufe (`source=sale`) automatisch ein. Migration auf DB ausgeführt. | `ProductStockTest` erweitert (13 grün, 37 Assertions): Flag blendet aus Übersicht + Kritisch-Zähler aus, Verbrauch/Monat-Mittelung (Fenstergrenze, production-Ausschluss, Eingänge zählen nie), Default-Sortierung kritisch vor ok, Render Verbrauch-Spalte + 4 Kachel-Filter. Regression Produkt-Suite (32 grün) |
|
||||
| 12.06.2026 | **AP-25 (Lieferbestand: Datum statt Tage)** | „Nicht vorrätig" wird jetzt über ein festes **Datum** („Wieder lieferbar ab", Datepicker `datepicker-base`, `dd.mm.yyyy`) statt über eine Tagesangabe gepflegt; Datenmodell unverändert (`out_of_stock_until` DATE passte bereits). `ProductRepository::update()` parst `out_of_stock_date` via `Carbon::createFromFormat('d.m.Y')` (ungültige Eingabe ⇒ null statt Crash); „unbestimmt" behält Vorrang, Deaktivierung leert beides. Resttage-Helper (`outOfStockRemainingDays()`/`outOfStockNotice()`) unverändert — zählen täglich automatisch herunter. **Interne Bestellliste wieder kaufbar:** `User/OrderController::datatable` zeigt den roten Hinweis jetzt **zusätzlich** zu den Mengen-Buttons (vorher ersetzte er sie) — der VP entscheidet selbst, ob er später beliefert wird; Produkt-Detail-Modal zeigte den Hinweis bereits nur zusätzlich. Formular-Card „Verfügbarkeit" + JS-Toggle (`js-out-of-stock-date`) angepasst. | `tests/Feature/ProductOutOfStockTest.php` umgebaut (8 grün, 29 Assertions): Datum→`out_of_stock_until`, Unbestimmt-Vorrang, Deaktivierung leert, ungültiges Datum ⇒ null, Vergangenheit gilt nicht, Resttage zählen mit `travel()` herunter, HTTP-Store mit Datum, **Bestellliste zeigt Hinweis + Mengen-Buttons**. Regression `ProductSetTest`/`ProductPhase51Test`/`ProductStockTest` (24 grün) |
|
||||
| 12.06.2026 | **AP-26 (Ausschuss-Gründe konfigurierbar)** | Neue Stammdaten-Tabelle `disposal_reasons` (`label`, `active`, `pos`) via Migration `2026_06_12_170531_create_disposal_reasons_table`; Model `DisposalReason` (cast `active`, `scopeActive`), `DisposalReasonFactory`, Seeder-Erweiterung `InventoryStammdatenSeeder` (bisherige 6 Gründe idempotent per `firstOrCreate` übernommen). CRUD: `DisposalReasonController` (create/store/edit/update/destroy → Redirect `general`), `Store/UpdateDisposalReasonRequest` (`label` Pflicht max 100, passend zur `stock_disposals.reason`-Spalte). Dritte Karte „Ausschuss-Gründe" auf **Einstellungen → Allgemein** (`general/index.blade.php` + `GeneralSettingController`), View `disposal-reasons/form.blade.php`. Route Resource `disposal-reasons` (ohne index/show) in der `superadmin`-Gruppe. **`StockDisposalController::reasons()` liest jetzt aktive `disposal_reasons`** (Reihenfolge `pos`, dann `label`) statt der hartkodierten Liste; `StoreStockDisposalRequest` bleibt bewusst ohne `in:`-Zwang (bestehende Disposals behalten ihren String-Grund). Migration + Seed auf DB ausgeführt. | `tests/Feature/DisposalReasonSettingsTest.php` (8 grün, 24 Assertions): Render Allgemein-Karte, CRUD, Pflicht-Label, `active`-Scope, **Ausschuss-Formular zeigt nur aktive Gründe in Sortierung**, Zugriffsschutz Nicht-SuperAdmin. Regression `StockDisposalTest`/`TaxRateSettingsTest`/`DeliveryTimeSettingsTest` (24 grün) |
|
||||
| _offen_ | AP-20 | Rohstoffbestand-Filter „alle / nur aus Herstellerrezepturen" + INCI-Flag „Kein Rohstoffbestand" | – |
|
||||
| _offen_ | AP-21 | INCI: Lieferant **+ URL** je Zeile (Repeater, „+ weiterer Lieferant"), „nur aktive"-Filter, „Alternativer Name", Lagerort (Raum/Regal/Buchstabe) inkl. zentraler Einstellungen | – |
|
||||
| _offen_ | AP-23 | Rezeptur-**Phasen** auf der Hersteller-Rezeptur (Phase A/B …, je Phase Rohstoffe + Notiz, Drag&Drop, Gesamt 100 %) | – |
|
||||
| _offen_ | AP-24 | Chargen-Nr.-**Präfix** auf Produktebene + Auto-Generierung (editierbar) in der Produktion | – |
|
||||
| _offen_ | AP-27 | Wareneingang: gleiche **Charge addieren** (Bestand zusammenführen), Einkaufsvorgang bleibt separat erfasst | – |
|
||||
| _offen_ | AP-28 | **Neue Produktions-Maske + Produktionsergebnis** (mehrere Outputs, entkoppelt von der Planung; Produktbestand aus Ergebnis, Rohstoffbestand aus Chargen, Phasen-Anzeige, Regal-Spalte) | – |
|
||||
|
||||
**Status Roadmap V5.0 (12.06.2026):** **AP-26, AP-25 und AP-22 sind erledigt** (Details siehe Protokoll oben). Offen aus V5.0: AP-21, AP-20, AP-23, AP-24, AP-27, AP-28.
|
||||
|
||||
**Übernommen aus V4.0 / weiterhin offen:** **AP-13** (Shop-Bestandsabzug bei Versand inkl. Sets, Konzept liegt vor), **AP-14** (Audit-Trail), **AP-15** (Blockbasierte Rechte), **AP-16** (2FA), **AP-17** (WaWi-Einstellungen).
|
||||
|
||||
> **➡️ HIER GEHT ES WEITER: AP-21 (INCI-Erweiterungen).** Das ist das nächste Paket in der Reihenfolge und zugleich die Grundlage für AP-20 (liefert `scopeUsedInActiveRecipes()` + „Kein Rohstoffbestand"-Flag-Formularfeld) und für die neue Produktions-Maske AP-28 (liefert Alternativname + Lagerort/„Regal"-Spalte). Vor dem Start die Lagerort-Modell-Frage final entscheiden (§5 Punkt 1 — Empfehlung: drei getrennte Stammdaten-Tabellen Raum/Regal/Fach). Danach: AP-20 → AP-23 → AP-24 → AP-27+AP-28 gemeinsam.
|
||||
|
||||
---
|
||||
|
||||
## 1. Verifizierter Ist-Stand (12.06.2026) — relevant für die neue Runde
|
||||
|
||||
| Bereich | Ist im Code | Neue Anforderung verlangt |
|
||||
|---|---|---|
|
||||
| **Rohstoffbestand** (`RawMaterialStockController@index`) | Listet **alle aktiven** Rohstoffe (`active=true`), unabhängig von Rezeptur-Zugehörigkeit. Filter: Suche, „nur kritische", Status-Kacheln. | Umschalt-Filter **„alle" / „nur aus Herstellerrezepturen"**; Rohstoffe ohne Bestandsführung (Allergene etc.) per Flag ausblenden. |
|
||||
| **INCI / `ingredients`** (`app/Models/Ingredient.php`, `IngredientController`, `ingredient/form.blade.php`) | Felder: `name`, `inci`, `effect`, `active`, `pos`, `default_factor`, `min_stock_alert`, `material_quality_id`, `tax_rate_id`, `delivery_time(_days)`. Lieferanten über Pivot `ingredient_supplier` (Spalten `preferred`, `supplier_sku`, **`url`** vorhanden), Form macht aber **nur Multi-Select** der Lieferanten — **keine** Per-Lieferant-URL-Eingabe. **Kein** `alt_name`, **kein** „Kein Rohstoffbestand"-Flag, **kein** Lagerort. | `alt_name` (Alternativer Name), Per-Lieferant-URL-Repeater mit „+", „nur aktive"-Filter, Lagerort (Raum/Regal/Buchstabe), „Kein Rohstoffbestand"-Flag. |
|
||||
| **Produktbestand** (`ProductStockController@index`, `ProductStockService`) | Sortiert nach `pos`,`name`. Kacheln: nur **warning/critical** klickbar (`all`/`ok` nicht). Filtert `active=true`, `is_set=false`, `main_product_id IS NULL`. **Kein** Sichtbarkeits-Flag, **keine** „Verbrauch/Monat"-Spalte. | Default-Sortierung nach **Dringlichkeit**, **alle** Kacheln klickbar, Produkt-Flag „im Produktbestand anzeigen", Spalte „Verbrauch/Monat" (Ø 6 Mon.). |
|
||||
| **Rezeptur / `product_ingredients`** | Spalten: `product_id`, `ingredient_id`, `pos`, `gram`, `factor`, `recipe_type` (`product`/`manufacturer`). **Kein** Phasen-Begriff. | Phasen auf der **Hersteller-Rezeptur** (Phase A/B …, je Phase Rohstoffe + Notiz, Gesamt 100 %). |
|
||||
| **Produkt / `products`** | Hat `out_of_stock_until`, `out_of_stock_indefinite`, `min/critical_product_stock`, Set-Felder, `no_recipe_required`. **Kein** Chargen-Präfix, **kein** Produktbestand-Sichtbarkeits-Flag. | `batch_prefix`, `show_in_product_stock`, Lieferbestand auf Datum umstellen. |
|
||||
| **Ausschuss** (`StockDisposalController::reasons()`) | Gründe **hartkodiert** im Controller (6 Werte). | Gründe als **konfigurierbare Stammdaten** (Einstellungen). |
|
||||
| **Wareneingang / `stock_entries`** | `batch_number` (string) vorhanden, **keine** Erkennung/Zusammenführung gleicher Chargen. Restbestand wird ohnehin chargenweise über `production_ingredients` gerechnet. | Beim Erfassen prüfen, ob `batch_number` für denselben Rohstoff schon existiert → Mengen zusammenführen, Einkaufsvorgang trotzdem separat erfassen. |
|
||||
| **Produktion / `productions`** | Ein `quantity` (Output), `produced_at`, `location_id`, `notes`. `production_ingredients` (`stock_entry_id`, `quantity_used`). **Produktbestand wird heute aus `quantity` gebucht** (`ProductStockService::recordProductionStock`). **Kein** Ergebnis-/Mehrfach-Output-Begriff, **keine** Phasen-Anzeige, **keine** Regal-Spalte. | Neue Maske: Planung (Produkt/Größe/Datum/geplante Stückzahl) nur informativ; **Produktionsergebnis** mit **mehreren** Outputs (Produkt, produzierte Stückzahl, Chargen-Nr.) bucht den Produktbestand; Rohstoffverbrauch chargenbasiert wie bisher; Phasen + Regal-Spalte. |
|
||||
| **Locations** | `locations`: `name`, `active`. Keine Raum/Regal-Struktur. | Lagerort-Bausteine (Raum/Regal/Buchstabe) zentral pflegbar, am INCI referenziert. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Interpretation der neuen Anforderungen (Mapping auf APs)
|
||||
|
||||
| # | Anforderung (12.06.) | AP |
|
||||
|---|---|---|
|
||||
| Rohstoffbestand 1a/1b | Filter „alle Rohstoffe" / „nur aus Herstellerrezepturen" | **AP-20** |
|
||||
| Rohstoffbestand (INCI-Flag) | „Kein Rohstoffbestand"-Kontrollkästchen (Allergene etc. ausblenden) | **AP-20** |
|
||||
| INCI 1 | Lieferant **+ URL** je Zeile, URL leer ⇒ Mailbestellung; „+ weiteren Lieferanten" | **AP-21** |
|
||||
| INCI 2 | INCI-Liste „nur aktive" (≥ 1 aktives Produkt) | **AP-21** |
|
||||
| INCI 3 | Feld „Alternativer Name" (z. B. LECITHIN → Phospholipon 80 H) | **AP-21** |
|
||||
| INCI 4 | Lagerort RAUM \| REGAL-NR. \| BUCHSTABE, zentral unter Einstellungen pflegbar | **AP-21** |
|
||||
| Produktbestand 1 | Sortierung nach Dringlichkeit (default), Kacheln „Produkte"/„Bestand ok" klickbar | **AP-22** |
|
||||
| Produktbestand 2 | Produkt-Flag „im Produktbestand anzeigen" | **AP-22** |
|
||||
| Produktbestand 3 | Spalte „Verbrauch pro Monat" (Ø letzte 6 Monate) | **AP-22** |
|
||||
| Produktebene 1 | Rezeptur-**Phasen** unter der Hersteller-Rezeptur (Screen 1) | **AP-23** |
|
||||
| Produktebene 2 | Chargen-Nr.-**Präfix** (Kategorie-Kürzel + Produkt-Nr. + Produktionsdatum) | **AP-24** |
|
||||
| Lieferbestand | **Datum** statt Tage, Produkt bleibt kaufbar, Resttage für VP zählen herunter | **AP-25** |
|
||||
| Ausschuss | Gründe unter Einstellungen selbst anlegen | **AP-26** |
|
||||
| Wareneingang | Gleiche Charge addieren, Einkaufsvorgang trotzdem erfassen | **AP-27** |
|
||||
| Produktion | Neue Maske + **Produktionsergebnis** (mehrere Outputs), Phasen, Regal (Screen 2) | **AP-28** |
|
||||
|
||||
---
|
||||
|
||||
## 3. Priorisierte Roadmap V5.0
|
||||
|
||||
> **Leitidee der Reihenfolge:** Erst die kleinen, isolierten Ergänzungen an bestehenden Seiten (schnell sichtbarer Nutzen, geringes Risiko), dann die Datenmodell-Grundlagen (Phasen, Chargen-Präfix), zuletzt die große Produktions-Maske, die auf Phasen + Präfix + Lagerort aufsetzt.
|
||||
|
||||
| Reihenfolge | AP | Titel | Abhängigkeit | Aufwand |
|
||||
|---|---|---|---|---|
|
||||
| 1 | ✅ AP-26 | Ausschuss-Gründe konfigurierbar — **erledigt 12.06.2026** | – | 0,5–1 Tag |
|
||||
| 2 | ✅ AP-25 | Lieferbestand: Datum statt Tage (Revision AP-03) — **erledigt 12.06.2026** | – | 0,5–1 Tag |
|
||||
| 3 | ✅ AP-22 | Produktbestand-Erweiterungen (Sortierung, Kacheln, Flag, Verbrauch/Monat) — **erledigt 12.06.2026** | – | 1,5–2,5 Tage |
|
||||
| 4 | ➡️ AP-21 | **NÄCHSTER SCHRITT:** INCI-Erweiterungen (Lieferant+URL, nur aktive, Alternativname, Lagerort + Einstellungen) | AP-05-Muster (Stammdaten) | 2,5–4 Tage |
|
||||
| 5 | AP-20 | Rohstoffbestand-Filter + „Kein Rohstoffbestand"-Flag | AP-21 (Flag/Lagerort am INCI) | 1–2 Tage |
|
||||
| 6 | AP-23 | Rezeptur-Phasen (Hersteller-Rezeptur) | – | 3–5 Tage |
|
||||
| 7 | AP-24 | Chargen-Nr.-Präfix (Produktebene) | – | 1 Tag |
|
||||
| 8 | AP-28 | Neue Produktions-Maske + Produktionsergebnis | AP-21 (Lagerort), AP-23 (Phasen), AP-24 (Präfix) | 5–8 Tage |
|
||||
|
||||
> **Querschnitt:** Alle neuen/angepassten Seiten verwenden das Design-System aus AP-19 (`resources/views/admin/inventory/partials/wawi-ui.blade.php`) und die Datumsfeld-Konvention (`datepicker-base`, `dd.mm.yyyy`). Jede DB-Änderung wird per Migration nachgezogen, jedes AP mit `vendor/bin/pint --dirty` und Pest-Tests abgeschlossen.
|
||||
|
||||
---
|
||||
|
||||
## 4. Arbeitspakete im Detail
|
||||
|
||||
### AP-26 — Ausschuss-Gründe konfigurierbar
|
||||
**Anforderung:** „Ich möchte die Gründe für den Ausschluss unter Einstellungen selber anlegen können."
|
||||
|
||||
**Ist-Stand:** Gründe sind in `StockDisposalController::reasons()` (≈ Z. 114–124) hartkodiert.
|
||||
|
||||
**DB**
|
||||
- Neue Tabelle `disposal_reasons`: `label` VARCHAR, `active` bool, `pos` int. Seeder mit den bisherigen 6 Werten (idempotent per `firstOrCreate`), damit Bestandsdaten unverändert bleiben.
|
||||
|
||||
**Code**
|
||||
- Model `DisposalReason` (`scopeActive`, casts), Factory, `InventoryStammdatenSeeder`-Erweiterung.
|
||||
- CRUD `DisposalReasonController` (create/store/edit/update/destroy → Redirect `general`) + `Store/UpdateDisposalReasonRequest` — analog zu `TaxRateController`/`DeliveryTimeController`.
|
||||
- Dritte Karte „Ausschuss-Gründe" auf der Seite **Einstellungen → Allgemein** (`general/index.blade.php`) + `GeneralSettingController` um `disposalReasons` erweitern.
|
||||
- `StockDisposalController`: `reasons()` liest jetzt aktive `disposal_reasons` (Reihenfolge `pos`); `create`/Formular nutzt sie. `StoreStockDisposalRequest` validiert weiterhin nur „nicht leer" (kein harter `in:`-Zwang, da frei pflegbar).
|
||||
- Routen Resource `disposal-reasons` (ohne index/show) in der `superadmin`-Gruppe; Sidenav unverändert (liegt unter „Allgemein").
|
||||
|
||||
**Akzeptanz:** SuperAdmin legt/ändert/deaktiviert Ausschuss-Gründe; das Ausschuss-Formular zeigt nur aktive Gründe in gepflegter Reihenfolge; bestehende Disposals behalten ihren (als String gespeicherten) Grund.
|
||||
|
||||
**Tests:** `DisposalReasonSettingsTest` (Render, CRUD, Validierung, `active`-Scope, Zugriffsschutz) + Regression `StockDisposalTest`.
|
||||
|
||||
> **Status:** Erledigt (12.06.2026). Umgesetzt wie geplant — Tabelle `disposal_reasons`, CRUD analog TaxRate/DeliveryTime, dritte Karte unter Einstellungen → Allgemein, `StockDisposalController::reasons()` liest aktive DB-Gründe. Details siehe Umsetzungsprotokoll (§0a). Tests: `tests/Feature/DisposalReasonSettingsTest.php` (8 grün).
|
||||
|
||||
---
|
||||
|
||||
### AP-25 — Lieferbestand: Datum statt Tage (Revision AP-03)
|
||||
**Anforderung:** „Bei ‚erst in 12 Tagen wieder lieferbar' machen wir das Produkt doch wieder kaufbar — der VP entscheidet selbst. Ich präferiere, dass ich auf Produktebene ein **Datum** einstelle und für den VP dann die **Anzahl der Tage** erscheint, die sich täglich aktualisiert."
|
||||
|
||||
**Ist-Stand:** AP-03 hat `out_of_stock_until` (DATE) + `out_of_stock_indefinite` (bool). Eingabe erfolgt heute über ein **Tagefeld** (`now()->addDays($tage)`); Kauf bleibt im Shop bereits möglich. **Interne Bestellliste** (`OrderController::datatable` + `admin/modal/show_product`) ersetzt die Mengen-Buttons aktuell durch einen Hinweis → laut neuer Anforderung soll **kaufbar bleiben**.
|
||||
|
||||
**Kern der Änderung:** Eingabe-Logik von „Tage" auf **„Datum"** umstellen (Datenmodell `out_of_stock_until` bleibt — passt bereits), Resttage werden für den VP weiterhin tagesgenau berechnet und gezählt. Kauf **überall** möglich (auch interne Bestellliste).
|
||||
|
||||
**Code**
|
||||
- **Produktformular** (Card „Verfügbarkeit", `resources/views/admin/product/…` + JS `toggleOutOfStock` in `edit.blade.php`): Tagefeld → **Datepicker** „Wieder lieferbar ab" (`datepicker-base`, `dd.mm.yyyy`), vorbefüllt aus `out_of_stock_until->format('d.m.Y')`. Checkbox „unbestimmt" bleibt.
|
||||
- **`ProductRepository::update()`**: statt `addDays($tage)` jetzt `Carbon::parse($datum)` aus dem Datepicker; „unbestimmt" hat weiter Vorrang (Datum=null), Deaktivierung leert beides. Helfer `outOfStockRemainingDays()`/`outOfStockNotice()` bleiben unverändert (zählen schon tagesgenau herunter).
|
||||
- **Interne Bestellliste kaufbar machen:** in `OrderController::datatable` (Produkt-Spalte) und `admin/modal/show_product` die Mengen-Buttons **nicht mehr** unterdrücken; stattdessen Hinweis „In ca. X Tag(en) wieder da!" **zusätzlich** anzeigen (Kauf bleibt). Shop zeigt den Hinweis bereits zusätzlich.
|
||||
|
||||
**Akzeptanz:** Auf Produktebene wird ein Datum gesetzt; VP sieht im Shop und in der internen Bestellliste „In ca. X Tagen wieder da!", die Tage zählen täglich herunter, das Produkt bleibt überall kaufbar; nach Ablauf verschwindet der Hinweis automatisch.
|
||||
|
||||
**Tests:** `ProductOutOfStockTest` anpassen (Datum→Resttage statt Tage→Datum; kaufbar in interner Liste; Vergangenheit gilt nicht; „unbestimmt"-Vorrang). Hinweise-Doku (AP-18) aktualisieren (Kauf-Sperre weiterhin nur optionale Zukunftsoption).
|
||||
|
||||
> **Status:** Erledigt (12.06.2026). Datepicker „Wieder lieferbar ab" statt Tagefeld; `ProductRepository` parst `d.m.Y` (ungültig ⇒ null); interne Bestellliste zeigt den Hinweis jetzt **zusätzlich** zu den Mengen-Buttons (Kauf überall möglich). Hinweise-Doku aktualisiert. Details siehe Umsetzungsprotokoll (§0a). Tests: `tests/Feature/ProductOutOfStockTest.php` (8 grün).
|
||||
|
||||
---
|
||||
|
||||
### AP-22 — Produktbestand-Erweiterungen
|
||||
**Anforderungen:**
|
||||
1. Sortierung nach **Dringlichkeit** (Klick auf „Status" sortiert dringlichste oben; darf default so sein). Kacheln „Produkte" und „Bestand ok" sollen ebenfalls **klickbar** sein.
|
||||
2. Produkt-Flag: „im Produktbestand anzeigen ja/nein" (z. B. Abrechnung Druckkosten / Logo-Etiketten gehören da nicht rein).
|
||||
3. Spalte **„Verbrauch pro Monat"** (Ø der letzten 6 Monate).
|
||||
|
||||
**Ist-Stand:** Sortierung `pos`,`name`; nur warning/critical-Kacheln klickbar; Filter `active`, `is_set=false`, `main_product_id IS NULL`; keine Verbrauchsspalte; kein Sichtbarkeits-Flag.
|
||||
|
||||
**DB (`products`)**
|
||||
- `show_in_product_stock` bool default 1 (Migration). Bestehende Produkte bleiben sichtbar; gezielt abwählbar.
|
||||
|
||||
**Code**
|
||||
- **Flag:** `Product` fillable + cast; Checkbox „Im Produktbestand anzeigen" in der Warenwirtschaft-Card des Produktformulars; `ProductRepository::update()` normalisiert (`isset ? 1 : 0`). `ProductStockController@index` filtert zusätzlich `where('show_in_product_stock', true)`. Auch `criticalProductCount()` (`ProductStockService`) respektiert das Flag.
|
||||
- **Dringlichkeits-Sortierung:** `ProductStockService::productStatus()` liefert bereits `critical`/`warning`/`ok`. Default-Reihenfolge serverseitig nach Status-Rang (critical → warning → ok), innerhalb gleich nach Reichweite/Name; DataTables-`order` auf die Status-Spalte (mit `data-order`-Rang) setzen, sodass ein Klick auf „Status" die dringlichsten oben hält.
|
||||
- **Kacheln klickbar:** in `product-stock/index.blade.php` allen vier `wawi-stat`-Kacheln (`all`/`ok`/`warning`/`critical`) `is-clickable` + `data-filter` geben; JS-Filter (analog Rohstoffbestand) auf alle vier Werte erweitern. „Produkte" (=all) hebt Filter auf, „Bestand ok" filtert auf `ok`.
|
||||
- **Verbrauch/Monat:** neue Methode `ProductStockService::monthlyConsumptionByProduct()` = Ø der `out`-Bewegungen mit `source IN ('sale','production_out',…)` bzw. der relevanten Abgänge der **letzten 6 Kalendermonate** (Summe der Abgänge / 6). Spalte „Verbrauch/Monat" in der Übersicht (rechtsbündig, Einheit Stück).
|
||||
> **Klärung (vermerkt, nicht blockierend):** „Verbrauch" = abgehende Bewegungen. Solange AP-13 (Verkaufsabzug) nicht live ist, basiert der Wert auf manuellen `out`-Bewegungen; mit AP-13 fließen Verkäufe automatisch ein. Default-Fenster 6 Monate.
|
||||
|
||||
**Akzeptanz:** Übersicht ist standardmäßig nach Dringlichkeit sortiert; alle vier Kacheln filtern; ausgeblendete Produkte (Flag aus) erscheinen nicht und zählen nicht in den Kritisch-Badge; „Verbrauch/Monat" zeigt den 6-Monats-Durchschnitt.
|
||||
|
||||
**Tests:** `ProductStockTest` erweitern (Flag blendet aus + aus Kritisch-Zähler, Verbrauch/Monat-Mittelung über 6 Monate, Status-Sortierrang, Render der vier Kachel-Filter).
|
||||
|
||||
> **Status:** Erledigt (12.06.2026). Abweichung zur Planung: Sortier-Toggle nicht über DataTables-`order`, sondern leichtgewichtig per eigenem JS (`data-rank` an der Zeile, Klick auf den „Status"-Kopf sortiert um) — die Seite nutzt kein DataTables. „Verbrauch/Monat" als **rollierendes** 6-Monats-Fenster umgesetzt (statt Kalendermonate; Klärungspunkt §5.3 bleibt zur Bestätigung offen), Produktions-Gegenbuchungen (`source=production`) ausgenommen. Details siehe Umsetzungsprotokoll (§0a). Tests: `ProductStockTest` (13 grün).
|
||||
|
||||
---
|
||||
|
||||
### AP-21 — INCI-Erweiterungen (Lieferant+URL, nur aktive, Alternativname, Lagerort)
|
||||
**Anforderungen (INCI-Ebene 1–4):**
|
||||
1. Pro INCI mehrere **Lieferanten mit eigener URL** (URL frei ⇒ Bestellung per Mail), „+ weiteren Lieferanten anlegen".
|
||||
2. INCI-Liste „**nur aktive**" filtern (aktiv = in mind. einer Hersteller-Rezeptur eines **aktiven** Produkts).
|
||||
3. Feld **„Alternativer Name"** (Handelsname; findbar für Mitarbeiter).
|
||||
4. Feld **Lagerort** im Schema RAUM \| REGAL-NR. \| BUCHSTABE; die einzelnen Bausteine zentral unter **Einstellungen** pflegbar.
|
||||
|
||||
**Ist-Stand:** `ingredient_supplier.url` existiert im Schema, wird im Formular aber nicht ausgespielt (nur Multi-Select). Kein `alt_name`, kein Lagerort, keine „nur aktive"-Liste.
|
||||
|
||||
**DB**
|
||||
- `ingredients.alt_name` VARCHAR nullable (Migration).
|
||||
- Lagerort am INCI: `ingredients.location_room_id`, `location_shelf_id`, `location_slot_id` (nullable FKs) **oder** kompakt drei Strings — **Entscheidung: drei FK auf neue Stammdaten** (Punkt 4 verlangt zentrale Pflege).
|
||||
- Zentrale Stammdaten unter Einstellungen — drei kleine Tabellen `storage_rooms` (Raum), `storage_shelves` (Regal-Nr.), `storage_slots` (Buchstabe), je `label`, `active`, `pos`. (Alternativ generische `storage_segments` mit `type`-Enum `room|shelf|slot`; bei der Umsetzung die einfachere von beiden wählen — Default: **drei Tabellen** für klare Dropdowns.)
|
||||
- **Lieferanten-URL:** Pivot `ingredient_supplier.url` ist vorhanden → **keine** Migration nötig; nur das Formular muss die Spalte bespielen.
|
||||
|
||||
**Code**
|
||||
- `Ingredient`: fillable `alt_name` + Lagerort-FKs; Relationen `room()/shelf()/slot()`; Helper `storageLabel()` = `"Raum 1 | Regal 3 | O"` (für die Anzeige in Rohstoffbestand-Detail **und** in der Produktions-Maske, Spalte „Regal").
|
||||
- **Lieferanten-Repeater** in `ingredient/form.blade.php`: pro Zeile `Lieferant`-Select + `URL`-Textfeld (`type="text"`, max 2048) + Entfernen; „+ weiterer Lieferant"-Button (JS klont eine Zeile). Speichern via `suppliers()->sync([$id => ['url' => …, 'preferred' => …]])` (Pivot-Daten). `StoreIngredientRequest` validiert paarweise (`suppliers.*.id` exists, `suppliers.*.url` nullable string max 2048). **Ableitung Bestellweg:** je INCI-Lieferant gilt `url` leer ⇒ **Mailbestellung**, sonst Shop-Link — diese Ableitung ersetzt im Rohstoffbestand-Detail (AP-10) den bisherigen `supplier.order_method`-Fallback (INCI-spezifisch sticht).
|
||||
- **„Alternativer Name":** Eingabefeld neben „Name"/„INCI"; Anzeige als eigene Spalte in der INCI-Verwaltungsliste und in der Produktions-INCI-Liste (Screen 2 zeigt Spalte „Alternativer Name").
|
||||
- **Lagerort:** drei abhängige Dropdowns (Raum/Regal/Buchstabe) aus den Stammdaten; Anzeige als „Regal"-Spalte (Screen 2) und im Rohstoffbestand-Detail.
|
||||
- **Einstellungen → Allgemein:** je eine Karte „Räume", „Regale", „Fächer/Buchstaben" mit CRUD (Muster AP-05); `GeneralSettingController` um die drei Listen erweitern; Routen-Resources (`superadmin`).
|
||||
- **„nur aktive"-Filter der INCI-Liste:** Scope `Ingredient::scopeUsedInActiveRecipes()` = `whereHas('products', fn($q) => $q->where('recipe_type','manufacturer')->whereHas('product', active))` (Pivot-Pfad über `product_ingredients` mit `recipe_type='manufacturer'` zu aktiven Produkten). In der INCI-Übersicht (`IngredientController@index` bzw. die Verwaltungsliste) eine Checkbox „nur aktive" (server- oder DataTables-clientseitig). **Achtung:** „aktiv" hier ≙ in Herstellerrezeptur eines aktiven Produkts — **nicht** das Feld `ingredients.active`. Begriffe in der UI sauber trennen („nur aktive (in Rezeptur)").
|
||||
|
||||
**Akzeptanz:** Pro INCI können mehrere Lieferanten mit je eigener URL gepflegt werden (leer = Mail); INCI hat einen Alternativnamen und einen zentral gepflegten Lagerort; die INCI-Liste lässt sich auf „nur in aktiven Herstellerrezepturen verwendete" filtern; Lagerort und Alternativname erscinen in Rohstoffbestand-Detail und Produktions-Maske.
|
||||
|
||||
**Tests:** `IngredientOrderFieldsTest` erweitern (Per-Lieferant-URL speichern/sync, `alt_name`, Lagerort-FKs); neuer `IngredientActiveRecipeFilterTest` (Scope liefert nur INCI aus aktiven Herstellerrezepturen); `StorageSettingsTest` (CRUD Räume/Regale/Fächer, Zugriffsschutz).
|
||||
|
||||
---
|
||||
|
||||
### AP-20 — Rohstoffbestand-Filter + „Kein Rohstoffbestand"-Flag
|
||||
**Anforderungen (Rohstoffbestand 1):**
|
||||
- Filter (a) **alle** Rohstoffe (auch nicht in aktiven Produkten — z. B. für Neuentwicklung eingekauft) und (b) **nur Rohstoffe aus Herstellerrezepturen**.
|
||||
- INCI-Kontrollkästchen „**Kein Rohstoffbestand**" — Rohstoffe/Einträge (Allergene etc.), die gar nicht im Rohstoffbestand auftauchen sollen.
|
||||
|
||||
**Ist-Stand:** `RawMaterialStockController@index` listet **alle aktiven** Rohstoffe ohne Rezeptur-Bezug; kein Ausschluss-Flag.
|
||||
|
||||
**DB (`ingredients`)**
|
||||
- `exclude_from_stock` bool default 0 (Migration). (Name bewusst „Kein Rohstoffbestand".)
|
||||
|
||||
**Code**
|
||||
- `Ingredient`: fillable + cast `exclude_from_stock`; Checkbox „Kein Rohstoffbestand (z. B. Allergen-Angabe, nicht bevorratet)" im INCI-Formular.
|
||||
- `RawMaterialStockController@index`:
|
||||
- grundsätzlich `where('exclude_from_stock', false)`.
|
||||
- Umschalt-Filter **Modus** (`?scope=all|recipe`, Default `all`): bei `recipe` zusätzlich `scopeUsedInActiveRecipes()` (aus AP-21) anwenden. UI: zwei Pills/Tabs „Alle" / „Nur aus Herstellerrezepturen" im `wawi-toolbar`.
|
||||
- `criticalIngredientCount()` (für den Badge) respektiert ebenfalls `exclude_from_stock`.
|
||||
- Optional: gleiche Ausschlusslogik in der Ausschuss-/Disposal-Auswahl, damit Allergen-INCIs dort nicht auftauchen (nice-to-have, nicht zwingend).
|
||||
|
||||
**Akzeptanz:** Rohstoffbestand lässt sich zwischen „alle" und „nur aus Herstellerrezepturen" umschalten; mit „Kein Rohstoffbestand" markierte INCI erscheinen nie im Rohstoffbestand (auch nicht im Kritisch-Badge).
|
||||
|
||||
**Tests:** `RawMaterialStockTest` erweitern (Modus „recipe" zeigt nur Rezeptur-INCI; `exclude_from_stock` blendet aus + aus Kritisch-Zähler; Default „alle" zeigt auch nicht-verwendete).
|
||||
|
||||
**Abhängigkeit:** AP-21 (liefert `scopeUsedInActiveRecipes()` + INCI-Formularfeld).
|
||||
|
||||
---
|
||||
|
||||
### AP-23 — Rezeptur-Phasen (Hersteller-Rezeptur)
|
||||
**Anforderung (Produktebene 1):** „Auf Produktebene müssen unter der Hersteller-Rezeptur noch die Phasen angelegt werden können." (Screen `2026-12-06-Rezeptur-phase.jpeg`.)
|
||||
|
||||
**Screen-Analyse:** Mehrere Phasen (Phase A, Phase B …), je Phase eine Liste aus Rohstoff-Auswahl + Anteil %, ein **Notiz-Textfeld pro Phase**, „+" zum Hinzufügen einer Rohstoffzeile, Drag-Handle (≡) zum Sortieren, „**+ Neue Phase**". **Gesamt 100,000 %** über alle Phasen.
|
||||
|
||||
**Ist-Stand:** `product_ingredients` hat `recipe_type` (`product`/`manufacturer`), `gram`, `factor`, `pos` — **keinen** Phasen-Begriff.
|
||||
|
||||
**DB**
|
||||
- Phasen als eigene Tabelle `recipe_phases`: `product_id` FK, `recipe_type` (vorerst nur `manufacturer`, Feld für spätere Ausweitung), `name` VARCHAR (z. B. „Phase A"), `note` TEXT nullable, `pos` int. Unique (`product_id`,`recipe_type`,`name`).
|
||||
- `product_ingredients.recipe_phase_id` nullable FK (nur Hersteller-Zeilen tragen eine Phase; Produkt-Rezeptur bleibt phasenlos). Bestehende Hersteller-Zeilen ⇒ `recipe_phase_id = NULL` (= „ohne Phase"), Migration bricht nichts.
|
||||
|
||||
**Code**
|
||||
- Models `RecipePhase` (+ `Product::recipePhases()`, `phaseIngredients()`); `ProductIngredient` um `recipe_phase_id` + `phase()`-Relation.
|
||||
- **Produktformular** (Card „Hersteller-Rezeptur", `resources/views/admin/product/…`): Umbau analog Screen 1 — Phasen-Blöcke (jede mit Rohstoffzeilen, Anteil %, Notizfeld), Drag&Drop (Rohstoffzeilen innerhalb der Phase + Phasen untereinander), „+ Rohstoff" je Phase, „+ Neue Phase". **Gesamtsumme 100 % über alle Phasen** (vorhandene 100%-Validierung auf die Summe **aller** Phasen umstellen).
|
||||
- **Repository:** Speichern persistiert Phasen (`recipe_phases`) und ordnet Hersteller-Rezepturzeilen ihrer Phase zu (`recipe_phase_id`); `Product::copy()` kopiert Phasen + Zuordnung mit.
|
||||
- **Rückwärtskompatibilität:** Produkte ohne Phasen funktionieren weiter (eine implizite „Phase ohne Namen"). Die Produkt-Rezeptur (`recipe_type='product'`) bleibt unverändert ohne Phasen.
|
||||
- **Konsumenten:** `ProductionService::requiredGramsByIngredient()`/`buildRecipePayload()` rechnen weiterhin über alle Hersteller-Zeilen (Summe ist phasenunabhängig) — Phasen sind primär Darstellung/Reihenfolge; die Produktions-Maske (AP-28) gruppiert die Anzeige nach Phase.
|
||||
|
||||
**Akzeptanz:** Unter der Hersteller-Rezeptur lassen sich beliebig viele Phasen mit Rohstoffen, Anteilen und einer Phasen-Notiz anlegen, sortieren und löschen; die Gesamtsumme über alle Phasen muss 100 % ergeben; Kopieren übernimmt die Phasenstruktur.
|
||||
|
||||
**Tests:** `RecipePhaseTest` (Phasen + Zeilen speichern/sortieren, 100%-Summe über Phasen, Hersteller-Zeile trägt `recipe_phase_id`, Kopie inkl. Phasen, Produktion rechnet unverändert über alle Zeilen). Regression `ProductionManufacturerRecipeTest`.
|
||||
|
||||
---
|
||||
|
||||
### AP-24 — Chargen-Nr.-Präfix (Produktebene)
|
||||
**Anforderung (Produktebene 2):** Präfix aus Kategorie-Kürzel (TP – Tattoopflege) + Produkt-Nr. (1) + Produktionsdatum → Beispiel `TP1150626`. Das Präfix „TP1" wird auf Produktebene angelegt; in der Produktions-Maske erzeugt sich die Chargen-Nr. von selbst im Textfeld, bleibt aber **editierbar** (alte Etiketten).
|
||||
|
||||
**Ist-Stand:** Kein `batch_prefix` am Produkt; Produktion hat keine Chargen-Nr.-Logik. Screen 2 zeigt im „Produktionsergebnis" das Feld „Chargen-Nr." vorbefüllt mit `KP1100626`.
|
||||
|
||||
**DB (`products`)**
|
||||
- `batch_prefix` VARCHAR nullable (z. B. „TP1").
|
||||
|
||||
**Code**
|
||||
- `Product`: fillable `batch_prefix`; Eingabefeld „Chargen-Nr.-Präfix" in der Warenwirtschaft-Card des Produktformulars (Hilfetext: „Kürzel + Produkt-Nr., z. B. TP1 → Charge TP1150626").
|
||||
- Helper `Product::suggestedBatchNumber(?Carbon $date)` = `batch_prefix . $date->format('dmy')` (Beispiel `TP1` + `150626`). Wird in AP-28 (Produktionsergebnis) als Vorbelegung des editierbaren Chargen-Nr.-Felds genutzt.
|
||||
> **Format-Klärung (vermerkt):** Beispiel „TP1150626" = `TP1` + `150626` (Produktionsdatum `ddmmyy`, hier 15.06.26). Bestätigung Datumsformat (`ddmmyy`) beim Kunden, falls abweichend (z. B. `dmmyy`).
|
||||
|
||||
**Akzeptanz:** Pro Produkt ist ein Chargen-Präfix hinterlegbar; in der Produktion wird die Chargen-Nr. daraus + Produktionsdatum vorgeschlagen und bleibt frei editierbar.
|
||||
|
||||
**Tests:** `BatchPrefixTest` (Präfix speichern; `suggestedBatchNumber()` setzt Präfix + Datum korrekt zusammen).
|
||||
|
||||
---
|
||||
|
||||
### AP-28 — Neue Produktions-Maske + Produktionsergebnis
|
||||
**Anforderung (Produktion):** Neue Maske (Screen `2026-12-06-neue-produktion.jpeg`). Phasen fließen ein. **Wichtig:** Der **Produktbestand** errechnet sich **nicht** aus der oben gewählten Auswahl Produkt + Stückzahl (nur Planung). Ganz unten („Produktionsergebnis") trägt man ein, was real entstanden ist — **mehrere Outputs** möglich (Beispiel: Plan 50×50 ml → Ergebnis 40×50 ml + 50×5 ml). Der **Rohstoffbestand** berechnet sich wie gehabt aus den Werten hinter jeder Charge.
|
||||
|
||||
**Screen-Analyse (Aufbau der Maske):**
|
||||
1. **Kopf/Planung:** Produkt, Größe, Produktionsdatum, **Planung Stückzahl** (rein informativ).
|
||||
2. **INCI-Liste (Hersteller-Rezeptur):** Tabelle mit Name, **Alternativer Name** (AP-21), Qualität, Anteil %, Gramm, **Regal** (Lagerort, AP-21). Summenzeile 100 % / Gesamt-Gramm.
|
||||
3. **Verpackung (Vorschau):** Artikel + Stück gesamt (aus BOM).
|
||||
4. **Notizen** + „Speichern"/„Drucken".
|
||||
5. **Phasen-Erfassung (Phase A/B …, AP-23):** je Rohstoffzeile Soll-Gramm + Status-Ampel (grün/orange) + **Charge wählen** + Ist-Gramm-Eingabe; je Phase ein Notiz-Textfeld. Gesamt-Gramm. **→ treibt den Rohstoffbestand** (chargenbasiert, wie bisher `production_ingredients`).
|
||||
6. **Produktionsergebnis:** Zeilen mit Produkt, **Produzierte Stückzahl**, **Chargen-Nr.** (vorbefüllt aus AP-24, editierbar), „+" für weitere Ergebniszeile. **→ treibt den Produktbestand.**
|
||||
|
||||
**Ist-Stand:** `productions.quantity` ist der einzige Output und bucht heute den Produktbestand (`ProductStockService::recordProductionStock`). Keine Mehrfach-Outputs, keine Phasen-Anzeige, keine Regal-Spalte, kein Chargen-Nr.-Feld.
|
||||
|
||||
**DB**
|
||||
- Neue Tabelle `production_outputs`: `production_id` FK cascade, `product_id` FK, `quantity` UINT, `batch_number` VARCHAR nullable, `pos` int. (Ein Production-Lauf ⇒ n Ergebniszeilen.)
|
||||
- `productions.quantity` bleibt als **geplante** Stückzahl erhalten (umdeuten zu „Planung"; Spaltenname unverändert, um Bestandsdaten/Tests stabil zu halten) — optional Kommentar/Accessor `planned_quantity`.
|
||||
- **Migration der Buchungslogik (wichtig):** Produktbestand wird künftig aus `production_outputs` gebucht, **nicht** mehr aus `productions.quantity`.
|
||||
|
||||
**Code**
|
||||
- Model `ProductionOutput` (+ `Production::outputs()`).
|
||||
- **`ProductStockService::recordProductionStock(Production)` umstellen:** idempotenter Differenz-Abgleich jetzt **pro Output-Produkt** (Summe der `production_outputs.quantity` je `product_id`), append-only — statt der bisherigen Einzel-`quantity`. Beim Bearbeiten einer Produktion wird die Differenz je betroffenem Produkt nachgebucht (vorhandenes idempotentes Muster beibehalten).
|
||||
- **`ProductionService::store()/updateProduction()`:** nimmt zusätzlich `outputs[]` (Produkt + Stückzahl + Chargen-Nr.) entgegen, persistiert sie, ruft danach `recordProductionStock`. Rohstoffverbrauch (`production_ingredients`) bleibt **unverändert** chargenbasiert. `StoreProductionRequest`: `outputs` Pflicht (≥ 1 Zeile), je Zeile `product_id` exists + `quantity` ≥ 1; `batch_number` nullable string.
|
||||
- **Phasen-Anzeige:** Hersteller-Rezeptur nach Phase gruppieren (`RecipePhase`, AP-23); Soll-Gramm/Ampel/Charge wie heute, nur nach Phase gegliedert + Phasen-Notiz anzeigen.
|
||||
- **Views:** `productions/_form_fields.blade.php` + `_scripts.blade.php` (von create/edit/copy genutzt) erweitern:
|
||||
- INCI-Tabelle um Spalten „Alternativer Name" + „Regal" (`Ingredient::storageLabel()`).
|
||||
- Phasen-Gruppierung der Charge-Erfassung.
|
||||
- **Produktionsergebnis-Block** unten: Repeater (Produkt-Select default = geplantes Produkt, Stückzahl, Chargen-Nr. vorbefüllt aus `Product::suggestedBatchNumber(produced_at)`, editierbar; „+" für weitere Zeile). Standard: eine Ergebniszeile mit geplantem Produkt + geplanter Stückzahl als Vorschlag.
|
||||
- **Klarer Hinweis-Text** in der Maske: „Planung oben ist unverbindlich. Der Produktbestand entsteht aus dem Produktionsergebnis unten." (Anforderung explizit.)
|
||||
- **Produktentwicklung** (Platzhalter aus AP-09) bleibt unberührt.
|
||||
|
||||
**Akzeptanz:**
|
||||
- Die geplante Stückzahl oben bucht **nichts**; nur das Produktionsergebnis unten bucht den Produktbestand (auch mehrere Output-Produkte, z. B. 40×50 ml + 50×5 ml).
|
||||
- Rohstoffbestand wird weiterhin aus den je Charge eingetragenen Ist-Mengen reduziert.
|
||||
- Chargen-Nr. je Ergebniszeile ist vorbefüllt (Präfix + Datum) und editierbar.
|
||||
- INCI-Liste zeigt Alternativname + Regal; die Charge-Erfassung ist nach Phasen gegliedert; Bearbeiten einer Produktion korrigiert den Produktbestand sauber per Differenz.
|
||||
|
||||
**Tests:** `ProductionOutputTest` (mehrere Outputs buchen Produktbestand je Produkt; Plan-Stückzahl bucht nicht; Bearbeiten korrigiert per Differenz; Rohstoffverbrauch unverändert chargenbasiert; Chargen-Nr.-Vorbelegung; Validierung ≥ 1 Output). Regression `ProductStockTest` + `ProductionManufacturerRecipeTest` (Produktionsbuchung jetzt über Outputs).
|
||||
|
||||
> **Migrationshinweis Bestandsdaten:** Für bereits erfasste Produktionen ohne `production_outputs` einmalig je Produktion eine Output-Zeile (`product_id`=Produktionsprodukt, `quantity`=`productions.quantity`) backfillen, damit der gebuchte Produktbestand konsistent bleibt. Backfill als Teil der Migration (idempotent).
|
||||
|
||||
---
|
||||
|
||||
## 5. Offene Klärungspunkte (nicht blockierend, parallel mit Kunde abstimmen)
|
||||
|
||||
1. **Lagerort-Modell (AP-21):** drei getrennte Stammdaten (Raum/Regal/Fach) vs. eine generische Tabelle. Empfehlung: drei Tabellen (klare Dropdowns). → bei Umsetzungsstart final entscheiden.
|
||||
2. **Chargen-Nr.-Datumsformat (AP-24):** Beispiel „TP1150626" als `TP1`+`ddmmyy` interpretiert (15.06.26). Bestätigen.
|
||||
3. **„Verbrauch/Monat" (AP-22):** Definition „abgehende Bewegungen, Ø 6 Monate". Vor AP-13 nur manuelle/Produktions-Abgänge; mit AP-13 fließen Verkäufe ein. Bestätigen, ob 6 **Kalendermonate** gewünscht.
|
||||
4. **Produktionsergebnis-Produkte (AP-28):** Dürfen Output-Produkte **andere** Produkte als das geplante sein (z. B. anderes Gebinde 5 ml als eigenes Produkt)? Screen + Beispiel legen **ja** nahe (40×50 ml + 50×5 ml ⇒ zwei Produkte/Varianten). → bestätigen; ggf. Varianten als eigene Produkte/`main_product_id` führen.
|
||||
5. **Wareneingang-Charge (AP-27):** „addiert sich dazu" — Bestand zusammenführen, aber **jeder** Einkaufsvorgang bleibt als eigener `stock_entry` erhalten (nur Anzeige/Restbestand summiert je `batch_number`). Bestätigt durch die Formulierung „den Einkaufsvorgang erfassen wir trotzdem" → so umgesetzt.
|
||||
|
||||
---
|
||||
|
||||
## 6. AP-27 — Wareneingang: gleiche Charge zusammenführen
|
||||
**Anforderung:** Gleiche Charge (z. B. Sonnenblumenöl `XY-123`) kommt erneut → soll sich beim weiteren Einkauf **dazuaddieren**. In der Maske erst prüfen, ob die Charge schon im Haus ist. Die Charge wird **nicht** einfach erweitert — der **Einkaufsvorgang** wird trotzdem (separat) erfasst.
|
||||
|
||||
**Ist-Stand:** `stock_entries.batch_number` vorhanden; keine Erkennung. Restbestand wird ohnehin pro `batch_number`/`stock_entry` über `production_ingredients` gerechnet.
|
||||
|
||||
**Interpretation:** Jeder Einkauf bleibt ein **eigener** `stock_entry` (Audit/Preis/Datum je Vorgang). „Dazuaddieren" = **Restbestand und Anzeige werden je `batch_number` (+ Rohstoff) summiert**. In der Erfassungsmaske ein **Hinweis/Autofill**, wenn die Charge für denselben Rohstoff bereits existiert (MHD/Lagerort vorbelegen, Bestätigung „zu bestehender Charge hinzufügen").
|
||||
|
||||
**Code**
|
||||
- `StockEntryController` (`create`/`store`): beim Eingeben von `batch_number` + `ingredient_id` per kleinem JSON-Endpoint prüfen, ob eine Charge existiert; UI-Hinweis „Charge bereits im Haus — Menge wird dieser Charge zugerechnet" + Vorbelegung `best_before`/`location_id` aus dem bestehenden Eintrag (überschreibbar).
|
||||
- **`InventoryService`:** Restbestand-/Detailansicht (AP-10) so anpassen, dass Chargen **je `batch_number`** über mehrere `stock_entries` summiert dargestellt werden (Eingang = Summe `received_quantity` aller Einträge dieser Charge; Verbrauch wie gehabt). Falls `remainingByLocationForIngredient()`/Charge-Liste bereits pro `stock_entry` gruppiert: auf Gruppierung nach `batch_number` umstellen.
|
||||
- **Produktion (Charge-Auswahl):** Charge-Dropdown nach `batch_number` gruppieren (nicht je Einzel-`stock_entry`), Restbestand = Summe der Charge; Verbrauch bucht FEFO auf die Einträge der Charge.
|
||||
|
||||
**Akzeptanz:** Erfasst man eine bereits vorhandene Charge erneut, weist die Maske darauf hin und führt die Mengen in Bestand/Anzeige zusammen; jeder Einkauf bleibt als eigener Vorgang (Preis/Datum) erhalten; Produktion sieht die Charge mit summiertem Restbestand.
|
||||
|
||||
**Tests:** `StockEntryChargeMergeTest` (zwei Einkäufe gleicher Charge ⇒ summierter Restbestand, zwei separate `stock_entry`-Zeilen; Existenz-Check-Endpoint; Produktion sieht Charge einmal mit Summen-Rest). Regression `RawMaterialStockTest`, `StockEntryPriceTest`.
|
||||
|
||||
> **Einordnung:** AP-27 ist in der Roadmap (Abschnitt 3) bewusst **nicht** terminiert vorgereiht, da es Anzeige-Logik des Rohstoffbestands (AP-10) und der Produktion (AP-28) berührt. Empfehlung: **zusammen mit AP-28** umsetzen (gemeinsame Chargen-Gruppierung), spätestens davor. In der Reihenfolge daher zwischen AP-24 und AP-28 einzuplanen.
|
||||
|
||||
---
|
||||
|
||||
## 7. Empfohlene Sofort-Reihenfolge (nächste Schritte)
|
||||
|
||||
✅ **Erledigt (12.06.2026):** AP-26 (Ausschuss-Gründe konfigurierbar), AP-25 (Lieferbestand: Datum statt Tage, überall kaufbar), AP-22 (Produktbestand: Dringlichkeits-Sortierung / 4 klickbare Kacheln / Sichtbarkeits-Flag / Verbrauch-Monat). Hinweise-Doku (AP-18) jeweils fortgeschrieben.
|
||||
|
||||
**➡️ Hier geht es weiter:**
|
||||
1. **AP-21** (INCI: Lieferant+URL-Repeater, „nur aktive", Alternativname, Lagerort + Einstellungen) — Grundlage für AP-20 und für die Produktions-Maske. **Vorab final entscheiden:** Lagerort-Modell (§5 Punkt 1, Empfehlung: drei getrennte Stammdaten-Tabellen Raum/Regal/Fach).
|
||||
2. **AP-20** (Rohstoffbestand-Filter + „Kein Rohstoffbestand"-Flag) — direkt nach AP-21, nutzt dessen Scope + Formularfeld.
|
||||
3. **AP-23** (Rezeptur-Phasen) → **AP-24** (Chargen-Präfix; Datumsformat `ddmmyy` bestätigen, §5 Punkt 2) → **AP-27 + AP-28** (Wareneingang-Charge-Merge gemeinsam mit der neuen Produktions-Maske inkl. Produktionsergebnis; Output-Produkte-Frage §5 Punkt 4 bestätigen).
|
||||
4. Danach offene V4.0-Pakete: **AP-13** (Shop-Bestandsabzug, Konzept liegt vor), **AP-14** (Audit-Trail), **AP-15** (Blockrechte), **AP-16** (2FA), **AP-17** (WaWi-Einstellungen).
|
||||
5. **AP-18 (Hinweise-Doku)** mit jedem AP fortschreiben.
|
||||
|
||||
---
|
||||
|
||||
## 8. Pflege dieses Dokuments
|
||||
|
||||
- Jedes abgeschlossene AP im Umsetzungsprotokoll (0a) mit Datum + Kurzbeschreibung + Test-Status protokollieren.
|
||||
- Bei DB-Änderungen Migration-Dateinamen referenzieren; Casts in `casts()` pflegen (L11-Konvention).
|
||||
- Vor jedem Commit: `vendor/bin/pint --dirty` und betroffene Tests (`php artisan test --filter=...`).
|
||||
- **UI-Konventionen** (aus V4.0 weiter gültig): Design-System `partials/wawi-ui.blade.php` (AP-19); Datumsfelder als `datepicker-base` (`dd.mm.yyyy`, kein natives `type="date"`); Status über `wawi-pill` (ok/warning/danger).
|
||||
- Neue Anforderungsquelle dieser Runde: `docs/nächsten Anforderungen an die WaWi.md` (12.06.2026) + die beiden 12.06.-Screens.
|
||||
BIN
dev/product management /screens/2026-12-06-Rezeptur-phase.jpeg
Normal file
BIN
dev/product management /screens/2026-12-06-Rezeptur-phase.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
dev/product management /screens/2026-12-06-neue-produktion.jpeg
Normal file
BIN
dev/product management /screens/2026-12-06-neue-produktion.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
Loading…
Add table
Add a link
Reference in a new issue