35 KiB
Aktualisierter Entwicklungsplan: Warenwirtschaft, Produktion & Produktbestand
Version: 6.0 - Stand 06.07.2026 Ersetzt:
entwicklungsplan-aktualisiert-12-06-2026.md(V5.0) als operative Arbeitsgrundlage Anforderungsquellen:docs/nächsten Anforderungen an die WaWi.md(12.06.2026),docs/Todos.md, Screensscreens/2026-12-06-Rezeptur-phase.jpeg+screens/2026-12-06-neue-produktion.jpegReferenzen: V5.0 (vollständige AP-Details zu AP-20 … AP-28), V4.0 (AP-00 … AP-19),briefing-anpassungen-27-04-2026.md,konzept-final.mdMethodik: unverändert — kleine, sequenziell abarbeitbare Arbeitspakete (AP), jedes einzeln deploybar; jedes AP endet mitvendor/bin/pint --dirty+ Pest-Tests.
0. Was dieses Dokument neu macht
V6.0 basiert auf einer vollständigen Code-Verifikation vom 06.07.2026 (alle AP-Status per Grep/Migrations-/Test-Abgleich geprüft, komplette Testsuite ausgeführt) plus einem Code-Qualitäts-Review des gesamten WaWi-Moduls. Es gibt zwei Neuerungen gegenüber V5.0:
- Verifizierter Stand: Seit dem 12.06.2026 wurde nicht weiterentwickelt (letzter Commit
e53201f, Arbeitsverzeichnis sauber). Der V5.0-Status stimmt exakt: AP-22, AP-25, AP-26 sind fertig, alles andere offen. - Neuer Optimierungs-Block AP-29 (OPT-1 … OPT-5): Ergebnisse des Code-Reviews als eigene, kleine Arbeitspakete. Zwei davon (OPT-1, OPT-2) werden vor AP-21 eingeschoben, weil AP-21 drei weitere Settings-CRUDs erzeugt und von der Entduplizierung direkt profitiert.
1. Verifizierter Ist-Stand (06.07.2026)
1.1 Testsuite
php artisan test: 153 Tests grün, 533 Assertions, Dauer ~2,3 s. Einziger Fail: ExampleTest (erwartet 200 auf /, bekommt den Domain-Redirect 302) — kein WaWi-Problem, Test sollte auf assertRedirect() angepasst oder entfernt werden (in OPT-1 miterledigt).
Stand nach Blockabschluss 06.07.2026 abends: 201 Tests grün, 731 Assertions — Suite dauerhaft 100 % grün.
1.2 AP-Status (per Code verifiziert; Status nach Blockabschluss 06.07.2026 aktualisiert)
| AP | Titel | Status | Evidenz |
|---|---|---|---|
| AP-00…AP-12, AP-18, AP-19 | V4.0-Pakete (Bugfixes, Stammdaten, Einkauf, Produktion, Rohstoff-/Produktbestand, Ausschuss, Doku, UI) | ✅ fertig | Migrationen bis 2026_06_03_*, 20+ Feature-Tests grün |
| AP-22 | Produktbestand-Erweiterungen (Flag, Dringlichkeits-Sortierung, 4 Kacheln, Verbrauch/Monat) | ✅ fertig | 2026_06_12_175615_add_show_in_product_stock_to_products_table, ProductStockTest (13 grün) |
| AP-25 | Lieferbestand: Datum statt Tage, überall kaufbar | ✅ fertig | out_of_stock_until-Datepicker in product/form.blade.php, ProductOutOfStockTest (8 grün) |
| AP-26 | Ausschuss-Gründe konfigurierbar | ✅ fertig | 2026_06_12_170531_create_disposal_reasons_table, DisposalReasonController, DisposalReasonSettingsTest (8 grün) |
| AP-21 | INCI-Erweiterungen | ✅ fertig 06.07.2026 (d05df49) |
alt_name + 3 Storage-Tabellen (Migrationen 2026_07_06_1207**), Lieferanten-Repeater mit URL, scopeUsedInActiveRecipes(), StorageSettingsTest/IngredientOrderFieldsTest grün |
| AP-20 | Rohstoffbestand-Filter + „Kein Rohstoffbestand" | ✅ fertig 06.07.2026 (ea36067) |
exclude_from_stock (Migration 2026_07_06_133714), ?scope=all|recipe-Umschalter, Kritisch-Badge respektiert Flag |
| AP-23 | Rezeptur-Phasen | ✅ fertig 06.07.2026 (97d420c) |
recipe_phases + product_ingredients.recipe_phase_id (Migration 2026_07_06_134518), Phasen-Blöcke mit Drag&Drop im Produktformular, RecipePhaseTest (6 grün) |
| AP-24 | Chargen-Nr.-Präfix | ✅ fertig 06.07.2026 (209ea46) |
products.batch_prefix (Migration 2026_07_06_140324), Product::suggestedBatchNumber() (TP1 + ttmmjj), BatchPrefixTest (6 grün) |
| AP-27 | Wareneingang: Charge zusammenführen | ✅ fertig 06.07.2026 (7a5b5e0) |
Batch-Check-Endpoint + Vorbelegung, Chargen-Gruppierung Detail/Produktion, FEFO-expandBatchLines(), StockEntryChargeMergeTest (7 grün) |
| AP-28 | Neue Produktions-Maske + Produktionsergebnis | ✅ fertig 06.07.2026 (99a6c92) |
production_outputs + Backfill (Migration 2026_07_06_153907), Buchung je Output-Produkt, Phasen-Maske + INCI-Liste + Ergebnis-Repeater, ProductionOutputTest (8 grün) |
| AP-13 | Shop-Bestandsabzug bei Versand (inkl. Sets) | ⏸ nachgelagert (Kundenentscheid 06.07.2026: weitere Anforderungen werden erst geprüft) | Konzept liegt vor (V4.0); monthlyConsumptionByProduct() und production_outputs-Buchung sind vorbereitet |
| AP-14 | Audit-Trail | ⏸ nachgelagert | kein Package, keine Spur |
| AP-15 | Blockbasierte Rechte | ⏸ nachgelagert | nur isAdmin()-Stufen |
| AP-16 | 2FA (Google Authenticator) | ⏸ nachgelagert | kein TOTP-Package in composer.json |
| AP-17 | WaWi-Einstellungsseite | ⏸ nachgelagert | wawi-ui.blade.php ist nur das CSS-Partial |
1.3 docs/Todos.md (03.06.2026) — Abgleich
Alle Punkte dieser älteren Runde sind in V4.0 eingeflossen und umgesetzt (URL-Felder inkl. Parameter-Validierung, Brutto/Netto-Rechner + UST-Dropdown im Einkauf, Einkauf duplizierbar, Lieferzeiten Lieferant/INCI, INCI-Lieferanten-Mehrfachauswahl, Produktions-Maske-Feinschliff, Produktbestand + Historie, Set-/Child-Produkte). Offen daraus nur noch: 2FA (= AP-16) und blockweise Rechtevergabe (= AP-15). Die Datei kann als „historisch/eingearbeitet" markiert werden.
1.4 Code-Qualitäts-Review (06.07.2026) — Kernaussagen
Positiv: Bestands- und Verbrauchsrechnungen laufen über aggregierte SQL-Queries (kein Zeilen-Looping), keine Queries in Blade-Views, Produktionsbuchung korrekt in DB::transaction() mit idempotentem Differenz-Abgleich. Die Architektur (Controller → Repository/Service) ist tragfähig.
Gefundene Schwachstellen (nach Impact):
| # | Befund | Ort | Impact |
|---|---|---|---|
| 1 | N+1 in Ausschuss-Übersicht: eager load user, aber getFullName() greift auf user.account → bis zu 500 Lazy-Loads |
StockDisposalController.php:27 + stock-disposals/index.blade.php:72 |
hoch |
| 2 | 7× Copy-Paste-Settings-CRUD (TaxRate, DeliveryTime, DisposalReason, MaterialQuality, Location, SupplierCategory, PackagingMaterial — je ~50 nahezu wortgleiche Zeilen) | app/Http/Controllers/Admin/Inventory/* |
hoch (Wartbarkeit; AP-21 würde 3 weitere Kopien erzeugen) |
| 3 | Index-Listen ohne Limit/Pagination: Wareneingänge und Produktionen laden die komplette Tabelle | StockEntryRepository::listForIndex():128, ProductionRepository:13 |
mittel-hoch (Skalierung) |
| 4 | Fehlende Composite-Indizes für den Aggregations-Hotpath (entry_type, status, ingredient_id/packaging_item_id) auf stock_entries |
InventoryService.php:33-191 vs. Migration create_stock_entries_table |
mittel |
| 5 | Query-Logik im Controller + In-Memory-Preisermittlung: lastNetPricePerSupplier() lädt alle Eingänge eines Rohstoffs |
RawMaterialStockController::show():63-159 |
mittel |
| 6 | Kleinigkeiten: Horizont-Filter im Rohstoffbestand wirkt nur clientseitig (Service ist bereits parametrisierbar); ProductionRepository lädt producedByUser ungenutzt |
RawMaterialStockController:58, ProductionRepository:13 |
niedrig |
2. Neuer Block: AP-29 — Code-Optimierung (OPT-1 … OPT-5)
Kleine, risikoarme Pakete. OPT-1 und OPT-2 kommen vor AP-21 (Begründung: OPT-2 liefert die Basis für die drei neuen Lagerort-CRUDs aus AP-21). OPT-3/4/5 laufen als Lückenfüller zwischen den Fach-APs.
OPT-1 — Quick Wins: N+1-Fix, Indizes, Test-Hygiene (0,5 Tage)
StockDisposalController@index: eager loaduser.accountstattuser(Muster:ProductStockController::history).- Migration: Composite-Indizes
stock_entries(entry_type, status, ingredient_id)+stock_entries(entry_type, status, packaging_item_id); redundanten Indexproduction_ingredients(production_id, ingredient_id)prüfen/entfernen. ExampleTestreparieren (assertRedirectstatt 200) — Suite dauerhaft 100 % grün.- Unnötigen Eager-Load
producedByUserinProductionRepository::listForIndex()entfernen. - Tests: Regression
StockDisposalTest,RawMaterialStockTest,ProductionPhase5Test; Query-Count-Assertion für die Ausschussliste.
OPT-2 — Settings-CRUD entduplizieren (1 Tag)
- Abstrakte Basisklasse
AbstractSettingCrudController(oder Trait): Template-Methoden für Model-Klasse, View-Prefix, Redirect-Route, FormRequests; Kind-Controller schrumpfen auf ~10 Zeilen Konfiguration. - Umstellen:
TaxRateController,DeliveryTimeController,DisposalReasonController,MaterialQualityController,LocationController,SupplierCategoryController,PackagingMaterialController. - Gemeinsames Blade-Partial für das Settings-Formular prüfen (Karten auf „Einstellungen → Allgemein" sind bereits ähnlich).
- Akzeptanz: Verhalten unverändert (alle bestehenden Settings-Tests grün), neue CRUDs in < 30 Minuten anlegbar.
- Tests: bestehende
TaxRateSettingsTest,DeliveryTimeSettingsTest,DisposalReasonSettingsTest+ je ein Smoke-Test der umgestellten Controller.
OPT-3 — Pagination der Index-Listen (1–1,5 Tage)
StockEntryRepository::listForIndex()undProductionRepository::listForIndex(): serverseitige Pagination bzw. hartes Limit + „ältere anzeigen"; DataTables-Konfiguration entsprechend anpassen. Ausschuss- und Produktbestands-Historie (limit(500)) vereinheitlichen.- Akzeptanz: Seiten bleiben bei 10.000+ Einträgen unter 1 s; UI-Verhalten (Suche/Sortierung) bleibt erhalten.
OPT-4 — RawMaterialStockController::show entschlacken (0,5–1 Tag)
entries-Ladeblock,openOrders-Query undlastNetPricePerSupplier()nachInventoryService/StockEntryRepositoryverschieben.- Letzten Netto-Preis je Lieferant per gruppierter SQL-Query statt In-Memory-Schleife.
- Sinnvoll unmittelbar vor AP-27/AP-28, weil die Chargen-Gruppierung dieselben Stellen anfasst.
OPT-5 — Horizont-Filter serverseitig (entfällt, Kundenentscheid 06.07.2026)
- Kunde: clientseitige Filterung reicht vorerst. Falls später doch gewünscht:
dailyConsumptionByIngredient($ids, $windowDays)ist bereits parametrisierbar → nur?horizon=-Parameter imRawMaterialStockController@indexdurchreichen.
3. Priorisierte Roadmap V6.0
| # | AP | Titel | Abhängigkeit | Aufwand |
|---|---|---|---|---|
| 1 | ✅ OPT-1 | Quick Wins (N+1, Indizes, ExampleTest) — erledigt 06.07.2026 | – | 0,5 Tage |
| 2 | ✅ OPT-2 | Settings-CRUD-Basisklasse — erledigt 06.07.2026 | – | 1 Tag |
| 3 | ✅ AP-21 | INCI-Erweiterungen (Lieferant+URL-Repeater, „nur aktive", Alternativname, Lagerort + 3 Stammdaten-CRUDs) — erledigt 06.07.2026 | OPT-2 | 2–3,5 Tage |
| 4 | ✅ AP-20 | Rohstoffbestand-Filter + „Kein Rohstoffbestand"-Flag — erledigt 06.07.2026 | AP-21 | 1–2 Tage |
| 5 | ✅ AP-23 | Rezeptur-Phasen (Hersteller-Rezeptur) — erledigt 06.07.2026 | – | 3–5 Tage |
| 6 | ✅ AP-24 | Chargen-Nr.-Präfix — erledigt 06.07.2026 | – | 1 Tag |
| 7 | ✅ OPT-3 + OPT-4 | Pagination + show()-Refactoring — erledigt 06.07.2026 | – | 1,5–2,5 Tage |
| 8 | ✅ AP-27 | Wareneingang: Chargen-Merge — erledigt 06.07.2026 | OPT-4 | 1–2 Tage |
| 9 | ✅ AP-28 | Neue Produktions-Maske + Produktionsergebnis — erledigt 06.07.2026 | AP-21, AP-23, AP-24, AP-27 | 5–8 Tage |
| 10 | ⏸ AP-13 | Shop-Bestandsabzug bei Versand (inkl. Sets; Konzept aus V4.0) — nachgelagert | AP-28 (saubere Buchungslogik) ✅ | 3–5 Tage |
| 11 | ⏸ AP-14 | Audit-Trail — nachgelagert | – | 2–3 Tage |
| 12 | ⏸ AP-16 | 2FA für Admins (Google Authenticator) — nachgelagert | – | 2–3 Tage |
| 13 | ⏸ AP-15 | Blockbasierte Rechte (sehen/bearbeiten je Block) — nachgelagert | AP-16 sinnvoll davor | 4–6 Tage |
| 14 | ⏸ AP-17 | WaWi-Einstellungsseite (Schwellwerte, Fenster, Defaults) — nachgelagert | – | 1–2 Tage |
Blockabschluss 06.07.2026: Positionen 1–9 (OPT-1/2/3/4 + AP-21/20/23/24/27/28) sind vollständig umgesetzt und einzeln committet. Die Positionen 10–14 sind auf Kundenwunsch nachgelagert — vor der Umsetzung prüft der Kunde weitere Anforderungen, die dort mit einfließen sollen.
Begründung der Reihenfolge:
- OPT-1/OPT-2 zuerst: minimales Risiko, sofortiger Nutzen, und AP-21 baut direkt auf OPT-2 auf (drei neue Lagerort-CRUDs Raum/Regal/Fach).
- Danach unverändert die V5.0-Logik: kleine fachliche Ergänzungen (AP-21/20) → Datenmodell-Grundlagen (AP-23/24) → große Produktions-Maske (AP-28), der OPT-4 und AP-27 vorgelagert sind (gemeinsame Chargen-Gruppierung).
- AP-13 direkt nach AP-28: dann existiert die endgültige Buchungslogik über
production_outputs, undmonthlyConsumptionByProduct()(wartet aufsource='sale') wird automatisch korrekt. - Sicherheits-/Governance-Pakete (AP-14/15/16/17) zum Schluss als eigener Block — fachlich unabhängig, gut geschlossen lieferbar.
Gesamtaufwand offen: grob 28–42 Personentage.
4. Arbeitspakete im Detail
Die fachlichen Detailspezifikationen zu AP-20, AP-21, AP-23, AP-24, AP-27, AP-28 stehen unverändert und weiterhin gültig in entwicklungsplan-aktualisiert-12-06-2026.md (§4 + §6) — dort sind DB-Schema, Dateipfade, Akzeptanzkriterien und Tests je AP vollständig beschrieben. Die OPT-Pakete sind in §2 dieses Dokuments spezifiziert. Für AP-13 gilt das Konzept aus V4.0 (entwicklungsplan-aktualisiert-02-06-2026.md), mit einer Änderung: Buchung gegen production_outputs-basierte Bestände (nach AP-28).
Geklärte Punkte mit dem Kunden (beantwortet 06.07.2026)
- Lagerort-Modell (AP-21): ✅ drei getrennte Stammdaten-Tabellen (Raum/Regal/Fach) — wie empfohlen.
- Chargen-Nr.-Datumsformat (AP-24): ✅
TP1+ddmmyybestätigt (Beispiel TP1150626 = 15.06.26). Bei AP-24-Umsetzung in der Hinweise-Doku (AP-18) hinterlegen. - „Verbrauch/Monat" (AP-22): ✅ rollierendes 6-Monats-Fenster bleibt wie umgesetzt.
- Produktionsergebnis (AP-28): ✅ Output-Produkte dürfen andere Produkte sein als das geplante (z. B. 40×50 ml + 50×5 ml).
- Horizont-Filter Rohstoffbestand (OPT-5): ✅ vorerst nur clientseitige Filterung → OPT-5 entfällt.
5. Pflege dieses Dokuments
- Jedes abgeschlossene AP im Umsetzungsprotokoll (§6) mit Datum, Dateien und Test-Status protokollieren.
- UI-Konventionen unverändert: Design-System
resources/views/admin/inventory/partials/wawi-ui.blade.php(AP-19), Datumsfelderdatepicker-base(dd.mm.yyyy), Status überwawi-pill. - Vor jedem Commit:
vendor/bin/pint --dirty+ betroffene Tests. docs/Todos.mdist vollständig eingearbeitet (Rest: AP-15/AP-16) und kann archiviert werden.
6. Umsetzungsprotokoll V6.0 (laufend)
| Datum | AP | Kurzbeschreibung | Tests |
|---|---|---|---|
| 06.07.2026 | OPT-1 (Quick Wins) | N+1-Fix Ausschussliste: StockDisposalController@index lädt jetzt user.account eager (vorher user → bis zu 500 Lazy-Loads durch getFullName()). Composite-Indizes: Migration 2026_07_06_113731_add_aggregation_indexes_to_stock_entries_table — stock_entries(entry_type, status, ingredient_id) + (entry_type, status, packaging_item_id) für die Aggregations-Queries im InventoryService; auf DB ausgeführt. Befund-Korrektur: Der Index production_ingredients(production_id, ingredient_id) ist nicht redundant — er bedient den FK auf production_id mit (kein separater FK-Index vorhanden, per SHOW INDEX verifiziert) → bleibt bestehen. Ungenutzter Eager-Load producedByUser aus ProductionRepository::listForIndex() entfernt (wird nur in show gebraucht, dort bereits via producedByUser.account geladen). ExampleTest auf assertRedirect() umgestellt (Domain-Redirect auf /) — Suite dauerhaft 100 % grün. |
Neuer Query-Count-Test ausschuss-liste laedt benutzerkonten ohne n+1 in StockDisposalTest (verifiziert: schlägt ohne Fix fehl). Volle Suite: 155 grün, 538 Assertions |
| 06.07.2026 | OPT-2 (Settings-CRUD-Basisklasse) | Neue abstrakte Basisklasse app/Http/Controllers/Admin/Inventory/SettingCrudController.php: generisches index/create/store/edit/update/destroy; Kind-Controller definieren nur noch modelClass(), viewPrefix(), redirectRoute(), storeRequestClass() (+ optional newModelAttributes(), indexOrder(), updateRequestClass()). Validierung per Container-Auflösung des FormRequests (Verhalten wie Method-Injection), Model-Auflösung aus dem Routen-Parameter via findOrFail (404-Verhalten unverändert). Umgestellt: TaxRate, DeliveryTime, DisposalReason, MaterialQuality, Location, SupplierCategory, PackagingMaterial (je ~50 → ~25 Zeilen reine Konfiguration). Die 7 Update*Request-Klassen waren mit den Store-Requests identisch und wurden entfernt (Update nutzt den Store-Request; bei künftiger Abweichung updateRequestClass() überschreiben). Form-Views bleiben bewusst je Entität (unterschiedliche Felder). Bilanz: −441/+73 Zeilen. Neue Settings-CRUDs (z. B. Raum/Regal/Fach in AP-21) = 1 Model + 1 Store-Request + ~25-Zeilen-Controller. |
Volle Suite 155 grün, 538 Assertions (inkl. TaxRateSettingsTest, DeliveryTimeSettingsTest, DisposalReasonSettingsTest, InventoryPhase2Test, InventoryPhase3Test) |
| 06.07.2026 | AP-21 (INCI-Erweiterungen) | DB: Migrationen 2026_07_06_120752_create_storage_segment_tables (storage_rooms/storage_shelves/storage_slots, je label/active/pos) + 2026_07_06_120752_add_storage_and_alt_name_to_ingredients_table (ingredients.alt_name + 3 FKs storage_room/shelf/slot_id, nullOnDelete); auf DB ausgeführt. Models: abstrakte Basis StorageSegment + StorageRoom/StorageShelf/StorageSlot (+ Factories); Ingredient um alt_name, Lagerort-FKs, Relationen, storageLabel() („Raum 1 | Regal 3 | A") und scopeUsedInActiveRecipes() (Hersteller-Rezeptur ∧ aktives Produkt) erweitert. Settings: 3 CRUDs als SettingCrudController-Kinder (OPT-2, je ~25 Zeilen) mit einem gemeinsamen StoreStorageSegmentRequest; 3 Karten auf Einstellungen → Allgemein über neues generisches Partial setting-list-card; gemeinsames Form-Partial setting-segment-form; Routen-Resources in der superadmin-Gruppe. INCI-Formular: Lieferanten-Repeater (Zeile = Lieferant-Select + URL-Feld, „+ weiteren Lieferanten anlegen", Entfernen-Button; jQuery-Template) statt Multi-Select — speichert ingredient_supplier.url per sync() (URL leer ⇒ null ⇒ Mailbestellung; preferred/supplier_sku bleiben erhalten); Feld „Alternativer Name"; 3 Lagerort-Dropdowns (nur aktive Bausteine). INCI-Liste: Checkbox „nur aktive (in Rezeptur aktiver Produkte)" (?only_active=1, serverseitig via Scope) + Spalte „Alternativer Name". Rohstoffbestand-Detail: Bestellweg jetzt pro INCI-Lieferant — Pivot-URL gesetzt ⇒ „Zum Shop", leer ⇒ „Per Mail" (ersetzt order_method-Fallback); Kopf zeigt Alternativname + Lagerort. Hinweise-Doku (AP-18) fortgeschrieben inkl. Kundenentscheid Chargen-Format ttmmjj (für AP-24). |
StorageSettingsTest (7), IngredientActiveRecipeFilterTest (3), IngredientOrderFieldsTest (9, umgestellt auf Repeater-Format + URL/Lagerort), RawMaterialStockTest +2 (Bestellweg, Alternativname/Lagerort). Volle Suite: 170 grün, 613 Assertions |
| 06.07.2026 | AP-20 (Rohstoffbestand-Filter + „Kein Rohstoffbestand") | Migration 2026_07_06_133714_add_exclude_from_stock_to_ingredients_table (ingredients.exclude_from_stock bool default 0); Ingredient fillable+cast; Checkbox „Kein Rohstoffbestand" (Hinweistext Allergen-Angaben) im INCI-Formular. RawMaterialStockController@index: grundsätzlich exclude_from_stock=false, neuer Umschalt-Modus ?scope=all|recipe (Default all; recipe nutzt scopeUsedInActiveRecipes() aus AP-21); Button-Gruppe „Alle / Nur aus Herstellerrezepturen" in der Toolbar. InventoryService::criticalIngredientCount() (Sidenav-Badge) respektiert das Flag. Hinweise-Doku fortgeschrieben. Nebenbei behoben: die beiden AP-21-Migrationen hatten denselben Timestamp, wodurch add_storage… alphabetisch vor create_storage… lief und auf MySQL am FK scheiterte (Teilreste alt_name/storage_room_id) — Migration auf …120753 umbenannt, Teilreste bereinigt, alle drei Migrationen sauber ausgeführt. |
RawMaterialStockTest +2 (Flag blendet aus Übersicht + Kritisch-Zähler aus; Modus recipe filtert, Default zeigt alle, Pills rendern). Volle Suite: 172 grün, 624 Assertions |
| 06.07.2026 | AP-23 (Rezeptur-Phasen) | DB: Migration 2026_07_06_134518_create_recipe_phases_table — Tabelle recipe_phases (product_id FK cascade als unsignedInteger — products.id ist historisch int, kein foreignId!; recipe_type default manufacturer, name, note nullable, pos) + product_ingredients.recipe_phase_id (nullable FK, nullOnDelete); auf DB ausgeführt. Abweichung zur V5.0-Spec: kein Unique auf (product_id,recipe_type,name) — frei editierbare Namen sollen beim Speichern nicht crashen; Duplikate sind fachlich unkritisch. Models: RecipePhase (+ Product::recipePhases(), ProductIngredient::phase(), Pivot um recipe_phase_id erweitert). Formular (admin/product/form.blade.php + JS in edit.blade.php): Hersteller-Rezeptur-Card in Phasen-Blöcke umgebaut — je Block editierbarer Name (Platzhalter Phase A/B … aktualisiert sich), Rohstoff-Tabelle (Anteil %/Faktor/Effektiv wie bisher), Phasen-Notiz, „+ Rohstoffe hinzufügen" je Phase (gemeinsames Modal mit Ziel-Phase), „+ Neue Phase" (JS-Template), Phase entfernen (mit Warnung bei Zeilen); Drag&Drop: Phasen untereinander + Zeilen innerhalb und zwischen Phasen (Sortable group); 100-%-Summe global über alle Phasen (Anzeige grün/rot wie bisher); mfg_row_phase[]-Sync bei Drag/Add/Remove/Submit. Repository: updateManufacturerIngredients() persistiert Phasen (persistRecipePhases(): leerer Name ⇒ Auto „Phase A/B…", einzelner unbenannter Block ohne Notiz ⇒ phasenlos wie bisher, recipe_phase_id=NULL); copy() kopiert Phasen inkl. Zeilen-Zuordnung (ID-Mapping). Produktion (requiredGramsByIngredient) rechnet unverändert über alle Zeilen. Blade-Falle behoben: inline @php(…) (AP-25-Verfügbarkeits-Card) + neuer @php…@endphp-Block im selben Template ließen den Compiler beide falsch paaren (Raw-Block von Z. 106 bis 771) — inline-Variante auf Block-Form umgestellt. Hinweise-Doku fortgeschrieben. |
Neuer tests/Feature/RecipePhaseTest.php (6 grün, 25 Assertions): Phasen+Zeilen+Notiz+Reihenfolge, Auto-Name, implizit phasenlos, Re-Save ersetzt vollständig, Kopie inkl. Zuordnung, Produktion phasenunabhängig. Volle Suite: 178 grün, 649 Assertions |
| 06.07.2026 | AP-24 (Chargen-Nr.-Präfix) | Migration 2026_07_06_140324_add_batch_prefix_to_products_table (products.batch_prefix VARCHAR(20) nullable); auf DB ausgeführt. Product fillable + Helper suggestedBatchNumber(?Carbon $date) = Präfix + Datum ttmmjj (Beispiel TP1 + 15.06.26 ⇒ TP1150626; ohne Präfix ⇒ null; Default-Datum heute). Eingabefeld „Chargen-Nr.-Präfix" in der Warenwirtschaft-Card des Produktformulars (Hilfetext mit Beispiel); ProductRepository::update() trimmt (leer ⇒ null). Vorbelegung des editierbaren Chargen-Nr.-Felds folgt in AP-28 (Produktionsergebnis). Chargen-Format war bereits in der Hinweise-Doku dokumentiert (AP-21-Lieferung). |
Neuer tests/Feature/BatchPrefixTest.php (6 grün): Speichern/Trim, leer ⇒ null, ttmmjj-Zusammensetzung, Default heute, ohne Präfix null, Formular-Render. Volle Suite: 184 grün, 657 Assertions |
| 06.07.2026 | OPT-3 + OPT-4 | OPT-3 (Index-Limits): StockEntryRepository::listForIndex(?int $receivedLimit = 500) — offene Bestellungen immer vollständig, Eingegangene auf 500 gedeckelt (neueste zuerst, stabile Sekundärsortierung nach id); ungenutzte Eager-Loads orderedByUser/receivedByUser entfernt (nur show braucht sie). ProductionRepository::listForIndex(?int $limit = 500) analog. Beide Controller unterstützen ?all=1; die Views zeigen bei erreichtem Limit einen Hinweis mit „Alle anzeigen"-Link. OPT-4 (show() entschlackt): Chargen-Restmengen (chargesWithRemainingForIngredient(), FEFO) und offene Bestellungen (openOrdersForIngredient()) in den InventoryService verschoben (Konstruktor-Injection ProductionService, kein Zyklus); lastNetPricePerSupplier() in StockEntryRepository als eine Window-Function-Query (ROW_NUMBER() OVER (PARTITION BY supplier_id …), läuft auf MySQL 8 + SQLite) statt In-Memory-Schleife über alle Einkäufe. RawMaterialStockController::show() ist nur noch Orchestrierung. |
RawMaterialStockTest +2 (letzter Netto-Preis je Lieferant aus neuestem Einkauf; Liste deckelt Eingegangene, Offene vollständig, null = alle). Volle Suite: 186 grün, 663 Assertions |
| 06.07.2026 | AP-27 (Wareneingang: gleiche Charge zusammenführen) | Grundprinzip wie in §4 V5.0 bestätigt: jeder Einkauf bleibt ein eigener stock_entry (Preis/Datum/Audit) — zusammengeführt werden nur Anzeige und Restbestand je batch_number. Gruppierungsschlüssel: StockEntry::batchGroupKey() (b:<batch_number>, Einträge ohne Chargennummer bleiben einzeln als e:<id>). Existenz-Check: neuer JSON-Endpoint GET api/stock-entries/batch-check (StockEntryController::batchCheck, Route admin.inventory.api.stock-entries.batch-check; Parameter ingredient_id+batch_number+optional exclude) liefert exists/purchases/remaining/best_before/location/quality_id; die Buchen-Maske (Stufe 2, stock-entries/show) prüft beim Tippen (debounced) und zeigt „Charge bereits im Haus — Menge wird dieser Charge zugerechnet" inkl. Rest/Lagerort/MHD; MHD + Qualität werden vorbelegt (nur wenn leer, überschreibbar). Rohstoffbestand-Detail: InventoryService::chargeGroupsForIngredient() (baut auf chargesWithRemainingForIngredient() aus OPT-4, FEFO bleibt) — Chargen-Tabelle zeigt je Charge eine Zeile mit Summen-Rest, frühestem MHD und Badge „x Einkäufe"; Einzel-Einkäufe als eingerückte Unterzeilen („Einkauf vom …"). Produktion: Recipe-Payload je Rohstoff um batches erweitert (ProductionService::batchOptionsForEntries(), eine Option je Charge mit Summen-Rest; stock_entries bleibt für Audit/Kompatibilität); Charge-Dropdown im Formular listet Chargen statt Einzel-Einkäufe (ingredient_lines[i][batch_key]); StoreProductionRequest akzeptiert batch_key oder stock_entry_id (required_without); ProductionService::expandBatchLines() löst Chargen-Zeilen FEFO auf die Einzel-Einkäufe auf (Überhang auf letzten Eintrag — kein neuer Hardstop, konsistent zum Bestandsverhalten), Controller ruft sie vor store/updateProduction auf (bei Update mit exclude_production); Service-API unverändert (store()/updateProduction() weiter je stock_entry_id — bestehende Tests/Aufrufer unberührt). Edit/Copy fassen bestehende Zeilen je Charge zusammen (Summe je batch_key). Hinweise-Doku fortgeschrieben. |
Neuer tests/Feature/StockEntryChargeMergeTest.php (7 grün, 35 Assertions): Gruppierung + separate Einkäufe, Endpoint (Treffer/Fehlschlag), Detailansicht summiert + Unterzeilen, Payload-batches, FEFO-Split, Passthrough + unbekannte Charge, HTTP-Produktion bucht auf 2 Einkäufe. Volle Suite: 193 grün, 698 Assertions |
| 06.07.2026 | AP-28 (Neue Produktions-Maske + Produktionsergebnis) | DB: Migration 2026_07_06_153907_create_production_outputs_table — Tabelle production_outputs (production_id FK cascade als foreignId — productions.id ist bigint; product_id als unsignedInteger+foreign — products.id historisch int!; quantity UINT, batch_number VARCHAR(100) nullable, pos; Index (production_id, pos)) inkl. idempotentem Backfill (Bestandsproduktionen ⇒ je eine Output-Zeile aus geplantem Produkt + Stückzahl, NOT EXISTS-Guard); auf DB ausgeführt (3 Produktionen backgefüllt). Buchung: ProductStockService::recordProductionStock() bucht jetzt je Output-Produkt (Soll = Summe production_outputs.quantity je product_id, idempotenter Differenz-Abgleich, append-only; entfernte Output-Produkte werden per Gegenbuchung auf null zurückgeführt) — die geplante Stückzahl bucht nichts mehr. Service: ProductionService::store()/updateProduction() um outputs[] erweitert (4. Parameter, Default [] ⇒ Fallback auf geplantes Produkt + Stückzahl mit suggestedBatchNumber(produced_at) — hält Alt-Aufrufer/Tests stabil und entspricht der Backfill-Semantik); persistOutputs() validiert (Produkt existiert, kein Set, Menge ≥ 1), Update ersetzt Outputs vollständig. HTTP: StoreProductionRequest verlangt outputs (≥ 1 Zeile; product_id exists, quantity ≥ 1, batch_number nullable). Payload: buildRecipePayload() liefert zusätzlich product.batch_prefix, phases[] (AP-23) und je Rohstoff alt_name/quality/storage_label/phase_id (Relationen eager, kein N+1). Maske (_form_fields + _scripts): Planungs-Stückzahl umbenannt („Geplante Stückzahl (Planung)" + Hinweis „bucht keinen Bestand"), INCI-Liste (Name, Alternativname, Qualität, Anteil %, Gramm gesamt, Regal, Summenzeile), Chargen-Zuordnung nach Phasen gruppiert (Phasen-Block mit Name + Notiz; Rohstoffe ohne Phase in eigenem Block), Status-Ampel je Rohstoff (grün = Summe ≈ Soll ± 0,02 g), Produktionsergebnis-Repeater (Produkt-Select, Stückzahl, Chargen-Nr. mit Auto-Vorbelegung Präfix+ttmmjj aus Produktionsdatum — folgt Produkt-/Datumswechsel bis zur manuellen Eingabe; Default-Zeile spiegelt die Planung, bis sie angefasst wird; old()/Edit/Copy-Vorbelegung). Blade-Falle erneut: ein Kommentar mit literalem @endphp im @php-Block beendete den Block vorzeitig — Kommentartext angepasst. Show: neue Karte „Produktionsergebnis"; Planung als solche gekennzeichnet. Index: Spalte „Ergebnis" (Outputs summiert, Fallback Planung). Hinweise-Doku fortgeschrieben. |
Neuer tests/Feature/ProductionOutputTest.php (8 grün, 32 Assertions): Mehrfach-Outputs buchen je Produkt / Planung bucht nichts, Service-Fallback inkl. Chargen-Nr., Differenz-Korrektur + Entfernen ⇒ Rückbuchung, Rohstoffverbrauch bleibt chargenbasiert, HTTP-Pflicht ≥ 1 Output, Set/Menge-Validierung, Payload-Felder, Masken-Render. Zwei bestehende HTTP-Tests um outputs ergänzt. Volle Suite: 201 grün, 731 Assertions |
| 15.07.2026 | Bugfix Layout | Doppeltes @yield('scripts') in layout-2.blade.php entfernt (seit 9ce711d rendert das Eltern-Layout application.blade.php:145 die Section zusätzlich) — alle Seiten-Skripte liefen doppelt, sichtbar u. a. als „Weitere Charge" legt 2 Zeilen an (Kundenmeldung 15.07.). Commit d8d4a9b. |
Produktions-/Chargen-Tests grün |
| 15.07.2026 | AP-31 (Löschen: Soft-Delete + Abhängigkeitsprüfung) | Kundenanforderung 15.07.: nahezu alle WaWi-Einträge löschbar. Migration 2026_07_15_100000 (deleted_at auf 14 Tabellen) + SoftDeletes-Trait in den Models. Neuer App\Services\DeletionGuard: zentrale Abhängigkeitsprüfung — referenzierte Einträge nicht löschbar, Fehlermeldung nennt die Verwendungen (Stammdaten: TaxRate/MaterialQuality/Location/SupplierCategory/PackagingMaterial/StorageSegmente; Lieferanten/Verpackungsartikel/INCI; Wareneingänge). Wareneingänge: auch gebuchte löschbar ohne Verbrauch/Ausschuss. Ausschuss: löschbar, Menge wieder verfügbar. Produktionen: löschbar (admin ≥ 7), Produktbestand per Gegenbuchung zurück (reverseProductionStock), Verbrauchs-Queries schließen gelöschte Produktionen aus. Bestätigungswarnung vor jeder Löschung (deutsche Confirm-Texte). INCI-Löschroute GET→DELETE. Nebenbei: Debug-Logs PackagingItemController entfernt, lastNetPricePerSupplier-Global-Scope-Fix. Behebt Audit-Befunde M-4, M-6 (teilw.), N-16 (teilw.). Commit 35b9076. |
Neuer InventoryDeletionTest (10 grün), StorageSettingsTest angepasst. Volle Suite: 214 grün, 783 Assertions |
| 15.07.2026 | OPT-6 (Bestandswahrheit) | Audit H-1/M-5: Chargen-Restmengen ziehen Ausschuss ab (consumedByStockEntry inkl. chargen-bezogener Disposals) — Detailseite, Produktions-FEFO und Batch-Check konsistent zur Index-Aggregation; Kopfwert der Detailseite aus remainingByIngredient(); Aggregationen über Key-Vereinigung (negative Bestände sichtbar); Standort-Zurechnung über Chargen-Location. Commit 8445038. |
ChargeDisposalConsistencyTest (5 grün) |
| 15.07.2026 | OPT-7 (Buchungs-Härtung) | Audit H-2/H-3/M-1/M-9: quantity_used > 0 erzwungen (Request + Service); expandBatchLines() dekrementiert Restmengen (kein Doppel-Verbrauch derselben Charge); Ausschuss validiert Chargen-Zugehörigkeit + Restbestand; lockForUpdate bei Produktions-Update/-Löschung. Commit c69721d. |
ProductionHardeningTest (4 grün) |
| 15.07.2026 | OPT-8 (Rezeptur-Robustheit) | Audit H-4/H-5/M-8/N-11/12/14: Rezeptur-Neuaufbau transaktional + validiert (unbekannte INCI-IDs, Phasenname ≤ 100); Phasen ohne Zeilen bleiben erhalten; Duplikat-Rohstoffe summieren; Set-Umstellung löscht Phasen; Auto-Phasenname AA-Schema; Legacy-Listen-Sync erhält gram/factor. Commit fa44af0. |
RecipeRobustnessTest (6 grün) |
| 15.07.2026 | OPT-9 (AuthZ/CSRF) | Audit M-2/M-3/M-6/M-7/M-10: Produktionen erfordern admin ≥ 7 (Request + Controller-Gates + UI); tote Formulare für Stufen 1–6 ausgeblendet; stock-entries/copy POST, Produkt-Löschung DELETE-Formulare; imageAttribute mit Spalten-Whitelist; URL-Felder url:http,https. Commit 9ed47c0. |
+2 Tests, 3 Tests an neue Regeln angepasst |
| 15.07.2026 | Audit-Niedrigbefunde | N-2/3/4/5/7/8/9/15 (Preis nur aus Eingängen, exaktes Verbrauchsfenster, einheitliche Kritisch-Zählung, Limit-Hinweis Ausschussliste, konsistentes Zahlenparsing, Kopie ohne Alt-Chargen-Nr., withTrashed-Korrekturen, Markdown-html_input=strip). Bewusst offen: N-1/6/10/13 (Design-/Kundenentscheid). Commit d34f0a7. |
Volle Suite: 231 grün, 823 Assertions |
| offen | AP-13 | Shop-Bestandsabzug | – |
| offen | AP-14/15/16/17 | Audit-Trail, Blockrechte, 2FA, WaWi-Einstellungen | – |
✅ BLOCK ABGESCHLOSSEN (06.07.2026). Alle fachlichen Pakete der Anforderungsrunde vom 12.06.2026 sind umgesetzt (OPT-1 … OPT-4, AP-20/21/23/24/27/28); die Quelldateien
docs/nächsten Anforderungen an die WaWi.mdunddocs/Todos.mdsind vollständig eingearbeitet. Suite: 201 Tests grün, 731 Assertions.Nachgelagert (auf Kundenwunsch, neue Anforderungen werden vorher geprüft): AP-13 (Shop-Bestandsabzug bei Versand, inkl. Sets — Konzept in V4.0, Buchung gegen
production_outputs-Bestände, Abgangsource='sale', Sets auflösen, Stornos/Retouren als Gegenbuchung;monthlyConsumptionByProduct()berücksichtigtsource='sale'bereits automatisch), danach AP-14 (Audit-Trail) → AP-16 (2FA) → AP-15 (Blockrechte) → AP-17 (WaWi-Einstellungen). Wiedereinstieg: neue Anforderungen des Kunden mit diesem Plan abgleichen und als V6.1/V7.0 fortschreiben.