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>
45 KiB
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) + Screensscreens/2026-12-06-Rezeptur-phase.jpeg,screens/2026-12-06-neue-produktion.jpegReferenzen: V4.0 (AP-00 … AP-19),briefing-anpassungen-27-04-2026.md,feedback.md,konzept-final.md,docs/Todos.mdMethodik: 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:
- Rezeptur-Phasen auf der Hersteller-Rezeptur (Screen 1).
- 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 mitvendor/bin/pint --dirtyund 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:labelVARCHAR,activebool,posint. Seeder mit den bisherigen 6 Werten (idempotent perfirstOrCreate), damit Bestandsdaten unverändert bleiben.
Code
- Model
DisposalReason(scopeActive, casts), Factory,InventoryStammdatenSeeder-Erweiterung. - CRUD
DisposalReasonController(create/store/edit/update/destroy → Redirectgeneral) +Store/UpdateDisposalReasonRequest— analog zuTaxRateController/DeliveryTimeController. - Dritte Karte „Ausschuss-Gründe" auf der Seite Einstellungen → Allgemein (
general/index.blade.php) +GeneralSettingControllerumdisposalReasonserweitern. StockDisposalController:reasons()liest jetzt aktivedisposal_reasons(Reihenfolgepos);create/Formular nutzt sie.StoreStockDisposalRequestvalidiert weiterhin nur „nicht leer" (kein harterin:-Zwang, da frei pflegbar).- Routen Resource
disposal-reasons(ohne index/show) in dersuperadmin-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/…+ JStoggleOutOfStockinedit.blade.php): Tagefeld → Datepicker „Wieder lieferbar ab" (datepicker-base,dd.mm.yyyy), vorbefüllt ausout_of_stock_until->format('d.m.Y'). Checkbox „unbestimmt" bleibt. ProductRepository::update(): stattaddDays($tage)jetztCarbon::parse($datum)aus dem Datepicker; „unbestimmt" hat weiter Vorrang (Datum=null), Deaktivierung leert beides. HelferoutOfStockRemainingDays()/outOfStockNotice()bleiben unverändert (zählen schon tagesgenau herunter).- Interne Bestellliste kaufbar machen: in
OrderController::datatable(Produkt-Spalte) undadmin/modal/show_productdie 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;
ProductRepositoryparstd.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:
- Sortierung nach Dringlichkeit (Klick auf „Status" sortiert dringlichste oben; darf default so sein). Kacheln „Produkte" und „Bestand ok" sollen ebenfalls klickbar sein.
- Produkt-Flag: „im Produktbestand anzeigen ja/nein" (z. B. Abrechnung Druckkosten / Logo-Etiketten gehören da nicht rein).
- 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_stockbool default 1 (Migration). Bestehende Produkte bleiben sichtbar; gezielt abwählbar.
Code
- Flag:
Productfillable + cast; Checkbox „Im Produktbestand anzeigen" in der Warenwirtschaft-Card des Produktformulars;ProductRepository::update()normalisiert (isset ? 1 : 0).ProductStockController@indexfiltert zusätzlichwhere('show_in_product_stock', true). AuchcriticalProductCount()(ProductStockService) respektiert das Flag. - Dringlichkeits-Sortierung:
ProductStockService::productStatus()liefert bereitscritical/warning/ok. Default-Reihenfolge serverseitig nach Status-Rang (critical → warning → ok), innerhalb gleich nach Reichweite/Name; DataTables-orderauf die Status-Spalte (mitdata-order-Rang) setzen, sodass ein Klick auf „Status" die dringlichsten oben hält. - Kacheln klickbar: in
product-stock/index.blade.phpallen vierwawi-stat-Kacheln (all/ok/warning/critical)is-clickable+data-filtergeben; JS-Filter (analog Rohstoffbestand) auf alle vier Werte erweitern. „Produkte" (=all) hebt Filter auf, „Bestand ok" filtert aufok. - Verbrauch/Monat: neue Methode
ProductStockService::monthlyConsumptionByProduct()= Ø derout-Bewegungen mitsource 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-rankan 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):
- Pro INCI mehrere Lieferanten mit eigener URL (URL frei ⇒ Bestellung per Mail), „+ weiteren Lieferanten anlegen".
- INCI-Liste „nur aktive" filtern (aktiv = in mind. einer Hersteller-Rezeptur eines aktiven Produkts).
- Feld „Alternativer Name" (Handelsname; findbar für Mitarbeiter).
- 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_nameVARCHAR 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), jelabel,active,pos. (Alternativ generischestorage_segmentsmittype-Enumroom|shelf|slot; bei der Umsetzung die einfachere von beiden wählen — Default: drei Tabellen für klare Dropdowns.) - Lieferanten-URL: Pivot
ingredient_supplier.urlist vorhanden → keine Migration nötig; nur das Formular muss die Spalte bespielen.
Code
Ingredient: fillablealt_name+ Lagerort-FKs; Relationenroom()/shelf()/slot(); HelperstorageLabel()="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 ZeileLieferant-Select +URL-Textfeld (type="text", max 2048) + Entfernen; „+ weiterer Lieferant"-Button (JS klont eine Zeile). Speichern viasuppliers()->sync([$id => ['url' => …, 'preferred' => …]])(Pivot-Daten).StoreIngredientRequestvalidiert paarweise (suppliers.*.idexists,suppliers.*.urlnullable string max 2048). Ableitung Bestellweg: je INCI-Lieferant gilturlleer ⇒ Mailbestellung, sonst Shop-Link — diese Ableitung ersetzt im Rohstoffbestand-Detail (AP-10) den bisherigensupplier.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);
GeneralSettingControllerum 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 überproduct_ingredientsmitrecipe_type='manufacturer'zu aktiven Produkten). In der INCI-Übersicht (IngredientController@indexbzw. die Verwaltungsliste) eine Checkbox „nur aktive" (server- oder DataTables-clientseitig). Achtung: „aktiv" hier ≙ in Herstellerrezeptur eines aktiven Produkts — nicht das Feldingredients.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_stockbool default 0 (Migration). (Name bewusst „Kein Rohstoffbestand".)
Code
Ingredient: fillable + castexclude_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, Defaultall): beirecipezusätzlichscopeUsedInActiveRecipes()(aus AP-21) anwenden. UI: zwei Pills/Tabs „Alle" / „Nur aus Herstellerrezepturen" imwawi-toolbar. criticalIngredientCount()(für den Badge) respektiert ebenfallsexclude_from_stock.
- grundsätzlich
- 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_idFK,recipe_type(vorerst nurmanufacturer, Feld für spätere Ausweitung),nameVARCHAR (z. B. „Phase A"),noteTEXT nullable,posint. Unique (product_id,recipe_type,name). product_ingredients.recipe_phase_idnullable 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());ProductIngredientumrecipe_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_prefixVARCHAR nullable (z. B. „TP1").
Code
Product: fillablebatch_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')(BeispielTP1+150626). Wird in AP-28 (Produktionsergebnis) als Vorbelegung des editierbaren Chargen-Nr.-Felds genutzt.Format-Klärung (vermerkt): Beispiel „TP1150626" =
TP1+150626(Produktionsdatumddmmyy, 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):
- Kopf/Planung: Produkt, Größe, Produktionsdatum, Planung Stückzahl (rein informativ).
- INCI-Liste (Hersteller-Rezeptur): Tabelle mit Name, Alternativer Name (AP-21), Qualität, Anteil %, Gramm, Regal (Lagerort, AP-21). Summenzeile 100 % / Gesamt-Gramm.
- Verpackung (Vorschau): Artikel + Stück gesamt (aus BOM).
- Notizen + „Speichern"/„Drucken".
- 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). - 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_idFK cascade,product_idFK,quantityUINT,batch_numberVARCHAR nullable,posint. (Ein Production-Lauf ⇒ n Ergebniszeilen.) productions.quantitybleibt als geplante Stückzahl erhalten (umdeuten zu „Planung"; Spaltenname unverändert, um Bestandsdaten/Tests stabil zu halten) — optional Kommentar/Accessorplanned_quantity.- Migration der Buchungslogik (wichtig): Produktbestand wird künftig aus
production_outputsgebucht, nicht mehr ausproductions.quantity.
Code
- Model
ProductionOutput(+Production::outputs()). ProductStockService::recordProductionStock(Production)umstellen: idempotenter Differenz-Abgleich jetzt pro Output-Produkt (Summe derproduction_outputs.quantityjeproduct_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ätzlichoutputs[](Produkt + Stückzahl + Chargen-Nr.) entgegen, persistiert sie, ruft danachrecordProductionStock. Rohstoffverbrauch (production_ingredients) bleibt unverändert chargenbasiert.StoreProductionRequest:outputsPflicht (≥ 1 Zeile), je Zeileproduct_idexists +quantity≥ 1;batch_numbernullable 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.
- INCI-Tabelle um Spalten „Alternativer Name" + „Regal" (
- 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_outputseinmalig 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)
- Lagerort-Modell (AP-21): drei getrennte Stammdaten (Raum/Regal/Fach) vs. eine generische Tabelle. Empfehlung: drei Tabellen (klare Dropdowns). → bei Umsetzungsstart final entscheiden.
- Chargen-Nr.-Datumsformat (AP-24): Beispiel „TP1150626" als
TP1+ddmmyyinterpretiert (15.06.26). Bestätigen. - „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.
- 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_idführen. - Wareneingang-Charge (AP-27): „addiert sich dazu" — Bestand zusammenführen, aber jeder Einkaufsvorgang bleibt als eigener
stock_entryerhalten (nur Anzeige/Restbestand summiert jebatch_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 vonbatch_number+ingredient_idper kleinem JSON-Endpoint prüfen, ob eine Charge existiert; UI-Hinweis „Charge bereits im Haus — Menge wird dieser Charge zugerechnet" + Vorbelegungbest_before/location_idaus dem bestehenden Eintrag (überschreibbar).InventoryService: Restbestand-/Detailansicht (AP-10) so anpassen, dass Chargen jebatch_numberüber mehrerestock_entriessummiert dargestellt werden (Eingang = Summereceived_quantityaller Einträge dieser Charge; Verbrauch wie gehabt). FallsremainingByLocationForIngredient()/Charge-Liste bereits prostock_entrygruppiert: auf Gruppierung nachbatch_numberumstellen.- Produktion (Charge-Auswahl): Charge-Dropdown nach
batch_numbergruppieren (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:
- 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).
- AP-20 (Rohstoffbestand-Filter + „Kein Rohstoffbestand"-Flag) — direkt nach AP-21, nutzt dessen Scope + Formularfeld.
- AP-23 (Rezeptur-Phasen) → AP-24 (Chargen-Präfix; Datumsformat
ddmmyybestä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). - 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).
- 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 --dirtyund betroffene Tests (php artisan test --filter=...). - UI-Konventionen (aus V4.0 weiter gültig): Design-System
partials/wawi-ui.blade.php(AP-19); Datumsfelder alsdatepicker-base(dd.mm.yyyy, kein nativestype="date"); Status überwawi-pill(ok/warning/danger). - Neue Anforderungsquelle dieser Runde:
docs/nächsten Anforderungen an die WaWi.md(12.06.2026) + die beiden 12.06.-Screens.