Bestell-Übertragung (wp/order) akzeptiert wp_label_url + wp_tracking_number: Label-PDF wird als lokale Kopie im privaten Storage gespeichert (Shop-URL ist öffentlich erratbar; Kopie überlebt Löschung im Shop), Sendungsnummer landet in shopping_orders.tracking. Neuer Endpoint wp/label zum Nachreichen (Connector-Hook pr_save_dhl_label_tracking), wp/status liefert die Sendungsnummer an den Shop zurück. Abwicklungs-Board + Bestell-Details zeigen Label-Download (copyreader-Dokumentroute type=label, Fallback Shop-Link) und Sendungsnummer. Download-Fehler blockieren die Übertragung nie. Doku: Connector-Update-Anleitung (dev/product management), Hinweise, Testleitfaden §11, Plan-Protokoll. pint.json schließt dev/ (Fremdcode) aus. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
58 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) + API-Bestellungen-Protokoll | ✅ fertig 16.07.2026 (211aeed, 71d5591) |
ProductStockService::syncSaleStock(), api_order_logs, Menüpunkt „API-Bestellungen"; SaleStockDeductionTest (10), ApiOrderLogTest (8) |
| AP-32 | Versand im System: DHL-Etiketten per API | ⏸ nachgelagert (Kundenentscheid 16.07.2026: erst Testphase) — Brückenlösung aktiv seit 17.07.2026 (Label aus dem Shop, §4/§6) | Umsetzungsansatz in §4; Andockpunkt = Versand-Statuswechsel (wie AP-13) |
| AP-33a | Bestell-Abwicklung: Nav „Bestellungen" + Fulfillment-Board | ✅ fertig 16.07.2026 (a367d85) |
OrderFulfillmentController, OrderStatusService, shipped_status_locked; OrderFulfillmentTest (10) |
| AP-33b | Sammel-Dokumente (Lieferscheine/Rechnungen als ein PDF) | ✅ fertig 16.07.2026 (1252c30) |
Checkbox-Auswahl im Board, batchDocuments() via PDFMerger; OrderFulfillmentTest (14) |
| AP-33c | Etiketten-Stapel | ⏸ nachgelagert (mit AP-32) | Ansatz in §4 |
| 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) + API-Bestellungen-Protokoll — erledigt 16.07.2026 | AP-28 (saubere Buchungslogik) ✅ | 3–5 Tage |
| 11 | ✅ AP-33a | Bestell-Abwicklung: Nav „Bestellungen" + Fulfillment-Board — erledigt 16.07.2026 | AP-13 ✅ | 2–3 Tage |
| 12 | ⏸ AP-32 | Versand im System: DHL-Etiketten per API — nachgelagert nach Testphase (Kundenentscheid 16.07.2026) | Kundenantworten (DHL-Konto, Versandprodukte, Druck) | 3–5 Tage |
| 13 | ✅ AP-33b | Sammel-Dokumente (Lieferscheine/Rechnungen als ein PDF) — erledigt 16.07.2026 | AP-33a ✅ | 1,5–2 Tage |
| 13b | ⏸ AP-33c | Etiketten-Stapel + Automatik-Option — nachgelagert (mit AP-32) | AP-32 | 1 Tag |
| 14 | ⏸ AP-14 | Audit-Trail — nachgelagert | – | 2–3 Tage |
| 15 | ⏸ AP-16 | 2FA für Admins (Google Authenticator) — nachgelagert | – | 2–3 Tage |
| 16 | ⏸ AP-15 | Blockbasierte Rechte (sehen/bearbeiten je Block) — nachgelagert | AP-16 sinnvoll davor | 4–6 Tage |
| 17 | ⏸ 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).
AP-13 — Umsetzungsansatz (ergänzt 15.07.2026 nach Sichtung des WooCommerce-Plugins)
Das in den Partner-Shops installierte Plugin „PARTNERCENTER API Connector" v1.3.3 ist als Referenz eingecheckt (dev/partnercenter-api/, Commit 592b03a). Die Sichtung ergibt: Für den Shop-Bestandsabzug ist kein Plugin-Update und keine neue API-Route nötig — alle benötigten Daten liegen zum Versandzeitpunkt bereits im Partnercenter.
So arbeitet das Plugin heute (verifiziert im Code):
| Auslöser | Richtung | Endpoint | Inhalt |
|---|---|---|---|
Automatisch bei neuer Bestellung (woocommerce_new_order-Hook, auch Admin-Anlage) |
WP → PC | wp/store |
Kunde + Rechnungs-/Lieferadresse (ohne Warenkorb) |
Automatisch bei Adressänderung (Hash-Vergleich in woocommerce_update_order) |
WP → PC | wp/update |
geänderte Adressen |
| Manuell per Button „Send to PARTNERCENTER for fulfillment" (Metabox in der WooCommerce-Bestellansicht) | WP → PC | wp/order |
Warenkorb (SKU + Menge + Preis) inkl. Rechnungs-Link; SKU-Matching hier über products.wp_number |
Manuell per Button „Retrieve" (nur solange Status open) |
WP → PC | wp/cancel |
Bestellung zurückziehen (serverseitig gegen versendete Bestellungen gesperrt) |
| Stündlicher WP-Cron | WP → PC | wp/status |
fragt Versandstatus ab und schreibt ihn in die WooCommerce-Bestellung |
Der Warenkorb kommt also per Knopfdruck (Fulfillment-Übergabe), nicht automatisch — die Kundendaten dagegen automatisch. Das Versandereignis selbst findet im Partnercenter statt (SalesController::store, Aktion store_shipped, setzt shipped_at und Provisionsfreigaben); das Plugin holt den Status nur ab.
Daraus folgt das AP-13-Design (reiner Serverseiten-Hook):
- Abzug beim erstmaligen Setzen von
shipped_at(Statuswechsel auf „versendet"/„abgeschlossen" inSalesController::store): jeShoppingOrderItemAbgang mitsource='sale'ins bestehende Ledgerproduct_stock_movements; Sets werden über die Rezeptur in Einzelprodukte aufgelöst.monthlyConsumptionByProduct()wertetsource='sale'bereits aus. - Gegenbuchung bei Storno (Status 10) einer bereits versendeten Bestellung — einziger Weg dafür ist der Admin, der API-Storno ist gegen Versendetes gesperrt.
- Gilt automatisch für alle Bestellquellen (WooCommerce-Fulfillment, Whitelabel-Shop, manuelle Anlage), da alle als
ShoppingOrderdurch dieselbe Versand-Logik laufen. - Bewusste Lücke (fachlich korrekt): Self-Shipping-Bestellungen der Partner übertragen keinen Warenkorb und erzeugen keinen Abzug — der Partner versendet aus eigenem Bestand, das Partnercenter-Lager wird nicht berührt. ✅ Kundenentscheid 16.07.2026: bestätigt — nur was per Knopfdruck (Fulfillment-Übergabe) übertragen wird, zählt fürs Partnercenter-Lager.
- Mitzulösen: (a) unbekannte SKUs sichtbar machen — heute geht „article not found" nur in der API-Antwort unter, die Position fehlt dann still in Bestellung und Abzug; (b)
wp_number-Konsistenzprüfung (aktive Produkte ohne/mit doppelter SKU).
✅ Kundenentscheid 15.07.2026: Ansatz bestätigt — so umsetzen.
✅ Umgesetzt 16.07.2026 (Protokoll §6): Punkte 1–4 wie beschrieben; Punkt 5a als eigener Menüpunkt „API-Bestellungen" (Kundenanforderung 16.07.: Protokoll aller API-Bestellungen, Fehlermarkierung, Modal mit Bestellinhalt), Punkt 5b als SKU-Konsistenzprüfung auf derselben Seite. Zusätzlich abgesichert: Gegenbuchung auch beim Auto-Storno der Zahlungserinnerung und beim Zurücksetzen des Versandstatus (Doppelabzug-Risiko behoben).
AP-32 — Versand im System: DHL-Etiketten per API (neu aufgenommen 15.07.2026)
Kundenanforderung 15.07.2026: Der Versand soll vollständig im Partnercenter abgewickelt werden — nicht nur der Statuswechsel per Button, sondern am Ende auch die Erstellung der Versandetiketten über die DHL-API.
Zielbild: Aus der Bestellansicht heraus wird das DHL-Label per API erzeugt (PDF zum Druck), die Sendungsnummer an der Bestellung gespeichert und der Status auf „versendet" gesetzt — dieser Statuswechsel ist derselbe Andockpunkt wie der AP-13-Bestandsabzug, beides greift dann in einem Schritt. Perspektivisch kann die Sendungsnummer über den bestehenden wp/status-Poll auch an WooCommerce zurückgegeben werden (Tracking-Link für den Endkunden; kleines Plugin-Update, optional).
Technische Eckpunkte:
- DHL „Parcel DE Shipping" API (REST, v2) — Nachfolger der SOAP-Geschäftskundenversand-API; benötigt ein DHL-Geschäftskundenkonto (Geschäftskundenportal), API-Key über das DHL-Entwicklerportal, Sandbox vorhanden. Label anlegen + stornieren, PDF in gängigen Druckformaten.
- Datenlage im System ist gut: Lieferadresse (
ShoppingUser/ShoppingOrder), Gewicht je Bestellung (shopping_orders.weight, wird beim Checkout aus den Produktgewichten berechnet) und Länderkennung sind vorhanden. - Neue Felder an
shopping_orders: Sendungsnummer, Label-Referenz/Storno-Referenz, Versandprodukt; Label-PDF als Download/Druck aus der Bestellansicht. - Länder: DE/FR/NL sind EU-Standard; CH ist Drittland → Zolldaten (Inhaltsangaben, Warenwert, ggf. CN23) müssen mit übergeben werden — eigener Fall in der Umsetzung.
Vor Umsetzung mit dem Kunden zu klären (nächste Anforderungsrunde):
- DHL-Geschäftskundenkonto vorhanden? (EKP-Nummer + Abrechnungsnummern je Versandprodukt)
- Welche Versandprodukte? (DHL Paket, Warenpost/Kleinpaket; national/international)
- Standard-Paketmaße/-gewichte oder je Bestellung manuell?
- Druck-Setup (Format A4 vs. Etikettendrucker 910-300-700)
- Retourenlabel mit erzeugen? (separates DHL-Produkt „Retoure")
Aufwand: 3–5 Tage (ohne Retourenlabel). Reihenfolge: direkt nach oder gemeinsam mit AP-13 — gleicher Andockpunkt am Versand-Statuswechsel, gemeinsamer Test-Aufbau.
➡️ Brückenlösung umgesetzt 17.07.2026 (Protokoll §6): Das im WooCommerce-Shop erstellte DHL-Label wird bei der API-Übertragung übernommen (lokale PDF-Kopie + Sendungsnummer an der Bestellung, „Label"-Download im Abwicklungs-Board und in den Bestell-Details, Nachreich-Endpoint
wp/label, Sendungsnummer imwp/status-Poll). Damit ist der Versand komplett über das Partnercenter abwickelbar, bis die DHL-Klärungspunkte beantwortet sind. Connector-Update-Anleitung:ap-32-brueckenloesung-connector-update.md.
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.
AP-33 — Bestell-Abwicklung: Nav-Punkt „Bestellungen" mit Fulfillment-Board (neu aufgenommen 16.07.2026)
Kundenanforderung 16.07.2026: Eigener Navigationspunkt „Bestellungen" in der Warenwirtschaft mit zwei Unterpunkten: „Abwicklung" (neu — kompakte Tagesübersicht für die Bestellabwicklung) und „API-Bestellungen" (bestehend, wandert als Unterpunkt hinein). Das Board dient dem Mitarbeiter zur täglichen Abarbeitung: sehen, was zu konfektionieren ist und wo Etiketten fehlen, Zugriff auf Rechnung/Lieferschein, Etikett erstellen, per Klick auf „versendet" setzen — kompakter als die bestehende Verkäufe-Seite. Perspektivisch: Etiketten für bestätigte Bestellungen gesammelt als PDF erzeugen/drucken, dazu Lieferscheine und Rechnungen.
Vorhandene Bausteine (verifiziert 16.07.2026):
- Rechnung und Lieferschein entstehen heute schon gemeinsam in
InvoiceRepository::create()(DomPDF-Templatesresources/views/pdf/invoice.blade.php+delivery.blade.php, Nummernkreis imInvoice-Service). Download-Routen existieren imFileController(Disksinvoice,delivery,invoice_delivery= kombiniert). - Statuskette deckt den Prozess ab: 0 offen (= zu konfektionieren) → 1 in Bearbeitung (= gepackt/versandfertig) → 2 versendet / 3 abgeschlossen; 4 Abholung, 5 Wartestellung, 10 storniert. Der Statuswechsel ist seit AP-13 gehärtet (Transaktion + Lock) und bucht den Bestandsabzug — das Board erbt das kostenlos.
setasign/fpdiist installiert → Zusammenführen mehrerer PDFs (Sammel-Druck) ist ohne neues Package machbar.- AP-32 (DHL) hat denselben Andockpunkt; „Etikett erstellen → Sendungsnr. speichern → versendet setzen" wird eine Aktion.
Umsetzung in drei Teilpaketen:
AP-33a — Abwicklungs-Board (2–3 Tage, sofort nutzbar ohne DHL):
- Sidenav: „Bestellungen" mit Unterpunkten „Abwicklung" + „API-Bestellungen" (bestehende Route zieht um, Redirect/aktiver Menüpfad beachten).
- Neue Seite
admin/inventory/orders(WaWi-Design AP-19): Standardfilter Status offen + in Bearbeitung, Umschalter Zeitraum (heute/7 Tage/alle offenen), Quelle (API/Whitelabel/manuell), Zahlstatus. - 4 Status-Kacheln als Filter: Heute eingegangen / Zu konfektionieren / Versandfertig / Heute versendet (Muster Produktbestand AP-22).
- Kompakte Zeile je Bestellung: Nr. + Quelle-Badge, Datum, Kunde + Land, Positionen aufklappbar als Packliste (Menge × Produkt, Sets mit Komponenten), Gewicht, Zahlstatus-Pill, Dokumente (Rechnung/Lieferschein: vorhanden ⇒ Download-Icons inkl. Kombi-PDF, fehlt ⇒ Button „erstellen" über bestehendes
InvoiceRepository::create()), Aktionen: „Konfektioniert" (→ Status 1), „Etikett" (Platzhalter bis AP-32), „Versendet" (→ Status 2, mit Confirm; löst AP-13-Abzug aus). - Rechte wie Verkäufe: admin ≥ 7 für Statuswechsel/Dokumente; Sichtbarkeit des Nav-Punkts entsprechend.
- Tests: Board-Render + Filter, Statuswechsel über die bestehende gehärtete Route, Dokument-Erzeugung fehlender Rechnungen.
AP-33b — Sammel-Dokumente (1,5–2 Tage, nach AP-33a):
- Checkbox-Auswahl im Board + Aktion „Dokumente drucken": ein zusammengeführtes PDF (FPDI) aus Lieferscheinen + optional Rechnungen der gewählten Bestellungen; fehlende Dokumente werden dabei erzeugt. Download als eine Datei zum Stapeldruck.
- Gleiches Muster vorbereitet für Etiketten-Sammel-PDF (kommt mit AP-32).
AP-33c — Etiketten-Integration & Automatik (mit/nach AP-32):
- Je Zeile „Etikett erstellen" (DHL-API aus AP-32): Label-PDF + Sendungsnr. an der Bestellung, danach Ein-Klick „Versendet" (oder kombiniert „Etikett + versendet").
- Sammel-Aktion „Etiketten für Auswahl erstellen" ⇒ ein PDF mit allen Labels.
- Empfehlung zur Vollautomatik („bei bestätigter Bestellung automatisch Etikett"): zunächst halbautomatisch lassen (Sammel-Aktion mit einem Klick für alle versandfertigen Bestellungen). Begründung: jedes fehlerhafte Label muss kostenpflichtig storniert werden, Gewicht/Versandprodukt und Adressqualität müssen je Bestellung stimmen, und „bestätigt" ist noch nicht definiert (bezahlt?
txaction?). Auto-Modus später als Option auf der WaWi-Einstellungsseite (AP-17), sobald der DHL-Prozess stabil läuft.
Kundenentscheide 16.07.2026 (alle vier Klärungspunkte beantwortet):
- „Versandfertig" = bezahlte Bestellung. Zahlart Rechnung gilt sofort als bezahlt (Versand vor Zahlungseingang); Vorkasse erst bei Geldeingang; API-Bestellungen gelten immer als bezahlt (Zahlung liegt im externen Shop).
- Rechnungen halbautomatisch (per Klick/Sammel-Aktion, keine Vollautomatik). Rechnungen werden nur für direkt im Partnercenter gekaufte Bestellungen erstellt. API-Bestellungen bekommen keine Rechnung im System — die Rechnung existiert im externen Shop; der übertragene Link (
wp_invoice_path) wird im Board verlinkt. - Auto-Wechsel „offen → in Bearbeitung" beim Öffnen der Detailseite bleibt vorerst — aber: wurde ein Status über die Warenwirtschaft (Abwicklungs-Board) gesetzt, überschreibt die Detailseite ihn nicht mehr. Umsetzung in AP-33a über ein Sperr-Flag an der Bestellung (Detailseiten prüfen das Flag vor dem Auto-Wechsel). ⚠️ Dokumentationspflichtig (Kundenwunsch: „nicht vergessen") — auch in der Hinweise-Doku hinterlegen.
- Rechte: auch Versand-Mitarbeiter dürfen abwickeln → Board-Routen in der
copyreader-Gruppe. Die Board-Statusaktionen sind bewusst auf Vorwärts-Schritte beschränkt (konfektioniert → versendet); Storno/Rücksetzung bleibt der Verkäufe-Seite (admin ≥ 7) vorbehalten. - Etiketten-Vollautomatik: halbautomatische Lösung (Sammel-Aktion) bestätigt — „machen wir erst mal so".
Aufwand gesamt: 4–6 Tage (ohne AP-32). Reihenfolge: AP-33a sofort umsetzbar; AP-32 sobald DHL-Klärungen da sind; AP-33b/c dazwischen bzw. danach.
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 |
| 15.07.2026 | N-1/N-6/N-10 (Kundenentscheide) | N-1: Chargen-Gruppierung je Lieferant (batchGroupKey = b:<supplier>:<lot>, Batch-Check prüft Lieferant) — Kundenentscheid: Lot-Nummern kommen von extern und können kollidieren (9b6c9de). N-6: WaWi-Eingaben lesen einen einzelnen Punkt mit 1–2 Nachkommastellen als Dezimaltrenner („5.1" = 5,1; „1.500" = 1500) — neuer Helper reFormatWawiNumber() + JS-Parser vereinheitlicht (016e8bb). N-10: Bewegungs-Modal zeigt aktuellen Bestand + Bestätigung bei Minus-Buchung (6af6760). |
+2 Tests. Volle Suite: 233 grün, 832 Assertions |
| 16.07.2026 | Vorbereitung AP-13: SalesController-Härtung | SalesController::store() (Versand-Statuswechsel) validiert Eingaben (Status aus $shippedTypes, Action-Whitelist, txaction/payment_id) und läuft in DB::transaction mit lockForUpdate gegen Doppelklick/parallele Tabs. Verhalten unverändert. Commit 6b8fcad. |
Neuer SalesShippedStatusTest (6 grün). Suite: 239 grün, 847 Assertions |
| 16.07.2026 | AP-13 (Shop-Bestandsabzug bei Versand) | ProductStockService::syncSaleStock(): idempotenter Differenz-Abgleich der Verkaufs-Abgänge (source='sale', Referenz = Bestellung) mit dem Versandstatus — Abzug je Bestellposition bei sent/close, Sets rekursiv über product_set_items in Einzelprodukte aufgelöst (Zyklus-Schutz, withTrashed wie N-9); Zurücksetzen/Storno bucht per Gegenbuchung zurück. Behebt das Doppelabzug-Risiko Versand→Zurücksetzen→Versand (Ledger-Abgleich statt shipped_at-Flag). Andockpunkte: SalesController::store (in der Transaktion) und PaymentReminderService (Auto-Storno per Cron setzt Status 10 auch bei versendeten Bestellungen). „Verbrauch/Monat" zählt Verkaufs-Abgänge automatisch mit. Self-Shipping bewusst ohne Abzug (kein Warenkorb übertragen). Commit 211aeed. Erkenntnis für künftige HTTP-Tests: mehrere Requests im selben Pest-Test müssen denselben actingAs-User verwenden, sonst endet der Folge-Request als stiller Login-Redirect. |
Neuer SaleStockDeductionTest (10 grün: Positionen, Sets/verschachtelt, Idempotenz, Rückbuchung offen/Storno, Wiederversand, Cron-Pfad, Monatsverbrauch). Suite: 249 grün, 903 Assertions |
| 16.07.2026 | AP-33b (Sammel-Dokumente) | Checkbox-Auswahl im Abwicklungs-Board (Kopf-Checkbox = alle sichtbaren, Zähler in den Buttons) + Sammel-Aktionen „Lieferscheine drucken" / „+ Rechnungen": OrderFulfillmentController::batchDocuments() merged die PDFs der Auswahl (PDFMerger/FPDI) zu einem Druck-PDF (neuer Tab, max. 100). Fehlende Dokumente werden ohne Mailversand erzeugt (InvoiceRepository::create), auf der Platte fehlende Altdateien mit bestehender Nummer regeneriert (update). API-Bestellungen: Checkbox deaktiviert, in gemischter Auswahl übersprungen, reine API-Auswahl ⇒ Fehlermeldung. Nebenbei (Kundenfeedback iPad): Tabelle kompaktiert — 6 Spalten, Kundenname mit Ellipsis, Positionen als schmale letzte Spalte, Aktionen+Positionen sticky rechts, Icon-Buttons unter 1200 px (4c73e64, c21cc63). Commit 1252c30. |
OrderFulfillmentTest +4 (Sammel-PDF inkl. Dokument-Erzeugung, reine API-Auswahl abgelehnt, gemischte Auswahl überspringt API, Validierung). Volle Suite: 272 grün, 1001 Assertions |
| 16.07.2026 | AP-33a (Bestell-Abwicklung: Nav „Bestellungen" + Fulfillment-Board) | Neuer Sidenav-Punkt „Bestellungen" (copyreader) mit Unterpunkten Abwicklung (neu) und API-Bestellungen (umgezogen). Board admin/inventory/orders (OrderFulfillmentController): 4 Filter-Kacheln (heute eingegangen / zu konfektionieren / versandfertig / heute versendet), je Bestellung Packliste aufklappbar (Sets mit Komponenten), Gewicht, Zahlstatus-Pill, Quelle-Badge (API/Shop/Promo), Suche. Versandfertig-Regel (Kundenentscheid): ShoppingOrder::isReadyToShip() — Rechnung (fnc) sofort, Vorkasse (vor) erst bei paid, API (extern) immer, non/failed nie. Dokumente: Erstellen (Rechnung + Lieferschein, ohne Mailversand) über bestehendes InvoiceRepository::create(); Downloads (einzeln/kombiniert via PDFMerger) über eigene copyreader-Routen, da storage_file admin ≥ 7 verlangt; API-Bestellungen bekommen keine Rechnung — externer Link (wp_invoice_path). Statuswechsel: Logik aus SalesController::store() in neuen OrderStatusService::setShipped() extrahiert (Transaktion, Lock, Margin-/Promotion-Folgen, AP-13-Abzug) — Verkäufe-Modal und Board nutzen dieselbe Implementierung; Board nur vorwärts (1/2), nur aus Status 0/1. Sperr-Flag shopping_orders.shipped_status_locked (Migration 2026_07_16_130000, Kundenentscheid Punkt 3): vom Board gesetzte Status werden vom Auto-Wechsel der Detailseiten (offen → in Bearbeitung, 3 Stellen) nicht mehr überschrieben. Commit a367d85. |
Neuer OrderFulfillmentTest (10 grün: Render/Packliste, Rechte, Versandfertig-Regel, Statuswechsel + Lock + Bestandsabzug, nur-vorwärts, Alt-Status unangetastet, Detailseiten-Sperre, Dokumente ohne Mail, API ohne Rechnung). Volle Suite: 268 grün, 987 Assertions |
| 16.07.2026 | AP-13-Nachtrag: Stichtags-Schutz für Alt-Bestellungen | Beim Blick auf Bestellung 1687 (versendet 12/2025) entdeckt: erneutes Speichern des Versandstatus einer vor AP-13 versendeten Bestellung hätte rückwirkend vom heutigen Bestand abgezogen. syncSaleStock() überspringt jetzt Bestellungen mit shipped_at vor dem Stichtag ProductStockService::SALE_STOCK_SINCE (16.07.2026). Kundenentscheid dokumentiert: Self-Shipping ohne Abzug bestätigt. |
SaleStockDeductionTest +1 (11 grün). Suite: 258 grün, 944 Assertions |
| 16.07.2026 | AP-13 (API-Bestellungen — Protokoll + SKU-Fehlermarkierung, Kundenanforderung 16.07.) | Neue Tabelle api_order_logs (Migration 2026_07_16_090000) + Model ApiOrderLog; ShoppingUserController protokolliert jede wp/order-Übertragung — erfolgreiche inkl. Zuordnungsergebnis je Position (SKU, Produkt, Menge, Preis, API-Meldungen), abgelehnte mit Grund (Kunde unbekannt, Bestellung existiert bereits, Speicherfehler). Positionen ohne Produktzuordnung ⇒ has_errors + Zusammenfassung („x von y Positionen ohne Produktzuordnung"). Neuer Menüpunkt „API-Bestellungen" (copyreader, admin/inventory/api-orders): Liste (Limit 500/?all=1, Filter „nur Fehler"), rote Markierung, Modal „Ansehen" mit Positionstabelle (Meldungen deutsch übersetzt, Roh-Payload bei abgelehnten). Dazu SKU-Konsistenz (Plan §4 „mitzulösen"): Warnkarte doppelte WP-Nummern aktiver Produkte, aufklappbare Liste aktive Produkte ohne WP-Nummer. Commit 71d5591. |
Neuer ApiOrderLogTest (8 grün, Ende-zu-Ende über wp/order mit Passport::actingAs). Suite: 257 grün, 940 Assertions |
| 16.07.2026 | Bugfix Logistik-Mail (Kundenmeldung) | Fehlversand an die Rechnungsadresse: die Versand-Mail (MailLogistic) hängte den Lieferschein nur bei White-Label-Bestellungen an. Jetzt wird er immer mitgesendet (unabhängig von Logo/White-Label); fehlt die PDF-Datei auf der Platte, wird sie mit bestehender Rechnungsnummer regeneriert. Dazu tote Duplikat-Klasse app/Mail/MailLogitic.php entfernt (Tippfehler-Dateiname, definierte MailLogistic in einer Altversion ohne Lieferschein-Anhang — Kollisionsrisiko im Classmap-Autoload). Commit 7d77314. Nachtrag: gleiche Regeneration auch für die Rechnungs-PDF — vorher brach build() bei fehlender Rechnungsdatei still ab (Mail ohne Anhänge trotz Erfolgsmeldung im UI). |
Neuer LogisticMailTest (4 grün: Anhang ohne White-Label, Regeneration Lieferschein + Rechnung, Versand-Button). Volle Suite: 276 grün, 1016 Assertions |
| 17.07.2026 | AP-32-Brückenlösung (DHL-Label aus WooCommerce übernehmen) | Bis zur echten DHL-API-Anbindung: Das im Shop erstellte Label (Plugin „DHL for WooCommerce", Meta _pr_shipment_dhl_label_tracking) kommt bei der Bestell-Übertragung mit. POST wp/order akzeptiert wp_label_url + wp_tracking_number (String oder Array); das System lädt die Label-PDF als lokale Kopie in den privaten Storage (wp-labels/, Migration 2026_07_17_090000: shopping_orders.wp_label_url/wp_label_path; Sendungsnummer in bestehender Spalte tracking) — Begründung: Shop-URL ist öffentlich + erratbar (Kundenadresse!), Kopie überlebt Löschung im Shop; Download-Fehler blockieren die Übertragung nie. Neuer Endpoint POST wp/label zum Nachreichen (Connector-Hook pr_save_dhl_label_tracking); wp/status liefert je Bestellung tracking_number zurück (Tracking-Link Endkunde). Anzeige: Abwicklungs-Board + Bestell-Details mit „Label"-Download (lokale Kopie über copyreader-Dokumentroute type=label, Fallback Shop-Link) + Sendungsnummer. Nötiges Connector-Update (WP-Seite) dokumentiert in dev/product management /ap-32-brueckenloesung-connector-update.md. Hinweise-Doku + Testleitfaden (§11) fortgeschrieben. Nebenbei: pint.json schließt dev/ (Fremdcode-Referenzen) von der Formatierung aus. |
Neuer WpLabelTransferTest (10 grün: Übertragung mit Label/Sendungsnr., Download-Fehler + Nicht-PDF blockieren nicht, Mehrpaket-Nummern, Nachreichen, unbekannte Nummer, Status-Poll, Board-Anzeige, Label-Download + 404). Volle Suite: 286 grün, 1052 Assertions |
| offen | AP-32 | Versand im System: DHL-Etiketten per API — neu aufgenommen + Ansatz dokumentiert (§4, 15.07.2026); Klärungspunkte (DHL-Konto, Versandprodukte, Druck-Setup) für nächste Anforderungsrunde notiert. Zwischenstand 17.07.2026: Brückenlösung aktiv (Label aus dem Shop, s. Protokollzeile) — deckt den Versand-Flow ab, bis die DHL-Klärungen da sind | – |
| 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.✅ BLOCK AP-13 + AP-33a/b ABGESCHLOSSEN (16.07.2026). Shop-Bestandsabzug bei Versand (inkl. Sets, Gegenbuchungen, Stichtags-Schutz für Alt-Bestellungen), Menüpunkt „API-Bestellungen" mit SKU-Fehlermarkierung, Abwicklungs-Board „Bestellungen → Abwicklung" (iPad-optimiert) und Sammel-Druck (Lieferscheine/Rechnungen als ein PDF). Alle Kundenentscheide in §4 dokumentiert. Suite: 272 grün, 1001 Assertions.
➡️ TESTPHASE (Kundenentscheid 16.07.2026): Der Block geht jetzt in den Kundentest — Leitfaden dafür:
resources/docs/testleitfaden.md(in der App unter Einstellungen → Leitfaden), Abschnitte 9 (Bestandsabzug + API-Bestellungen) und 10 (Bestell-Abwicklung inkl. Sammel-Druck).Nachgelagert (nicht automatisch beginnen): AP-32 (DHL-Etiketten — Kundenentscheid 16.07.2026: nach der Testphase; Klärungspunkte DHL-Konto/EKP, Versandprodukte, Druckformat, Retourenlabel in §4) → AP-33c (Etiketten-Stapel, optional Automatik als AP-17-Einstellung) → AP-14 (Audit-Trail) → AP-16 (2FA) → AP-15 (Blockrechte) → AP-17 (WaWi-Einstellungen). ✅ Self-Shipping ohne Bestandsabzug am 16.07.2026 vom Kunden bestätigt (§4 Punkt 4). Wiedereinstieg: Test-Feedback des Kunden mit diesem Plan abgleichen (§6 fortschreiben), danach AP-32 starten.