52 KiB
Warenwirtschaft: Prüfung der Umsetzung vom 08.09.2026
Prüfergebnis: Das Modul ist weitgehend umgesetzt, aber die Aussage „vollständig erledigt“ in Plan V7 bedeutet noch keine fehlerfreie fachliche Abnahme. Die bestehende Testsuite ist grün. Zusätzlich wurden konkrete Fehler in Produktionsentwürfen, Ergebnis-Auswahl, Bestandsdarstellung, Verpackungsverbrauch und Berechtigungen festgestellt. Besonders die Kombination der jüngsten Erweiterungen benötigt Nacharbeit.
Prüfstand: Git 912dce65d4608739db34e5f794b65220f083375c plus das beim Prüfungsbeginn vorhandene Arbeitsverzeichnis. Keine Änderungen am Anwendungscode, an bestehenden Tests, Konfigurationen oder Migrationen. Ausschließlich diese neue Dokumentation wurde im Projekt ergänzt.
1. Umfang und Aussagekraft
Geprüft wurden Anforderungen und Umsetzung der Produkt-/Rezepturverwaltung, Stammdaten, Einkauf/Wareneingang, Produktion, Rohstoff-/Produkt-/Verpackungsbestände, Ausschuss, Löschregeln sowie die angrenzenden Versand- und WooCommerce-Abläufe.
Maßgebliche Anforderungsquellen:
- Projektübersicht.
- Finales Konzept, ursprüngliches Feedback und Briefing-Anpassungen im Ordner
00-konzept. - Versionierte Entwicklungspläne, insbesondere V4 vom 02.06.2026, V6 vom 06.07.2026 und V7 vom 26.08.2026, einschließlich der später eingetragenen Kundenentscheide.
- Kundenfeedback vom 26.08.2026.
- Vorheriger Audit vom 15.07.2026, Leistungsübersicht, DHL-Brücke und Deckel-Varianten-Vorschlag im Ordner
03-status-und-vorschlaege. docs/testleitfaden-wawi-2026-07-06.mdsowie vorhandene Feature-Tests.
Neuere, ausdrücklich dokumentierte Kundenentscheide haben Vorrang vor dem Ursprungskonzept. Beispielsweise sind negative Produktbestände beim Versand und Self-Shipping ohne Abzug ausdrücklich akzeptiert. Diese Eigenschaften werden hier nicht als Fehler gewertet.
Prüfmethoden
- Statische Prüfung der relevanten Controller, FormRequests, Services, Repositories, Modelle, Migrationen, Routen und Blade-/JavaScript-Abläufe.
- Ausführung der gesamten vorhandenen Testsuite auf SQLite
:memory:. - 14 zusätzliche Diagnosefälle in einer temporären Datei außerhalb des Repositories, ebenfalls ausschließlich mit SQLite
:memory:und synthetischen Daten. Darunter HTTP-Requests durch die Laravel-Testumgebung und gerenderte Blade-Antworten. - Abgleich von Anforderungsstatus, fehlenden Teilfunktionen und Testabdeckung.
Grenzen: Kein Zugriff auf die produktive Datenbank, kein Deployment, kein realer E-Mail-Versand, keine echten Bestellungen oder externen API-Schreibzugriffe. WooCommerce-Connector nur anhand des lokalen Codes geprüft. Der tatsächliche Plugin-Rollout und der Live-Cron wurden nicht verifiziert. Kein Browser-/iPad-End-to-End-Test und kein MySQL-Parallelitätstest. Aussagen über JavaScript-Interaktion und konkurrierende Requests sind entsprechend als statisch hergeleitet gekennzeichnet. Die abgelegten Bilder wurden nicht als aktueller Laufzeitnachweis verwendet.
Beim Start bereits vorhanden waren Änderungen an app/Repositories/InvoiceRepository.php, app/Services/Invoice.php, resources/lang/de/email.php sowie unversionierte .claude/, AGENTS.md und app/Mail/MailCancellation.php. Diese wurden nicht verändert. Das Testergebnis bezieht sich auf diesen Arbeitsstand, nicht ausschließlich auf den Commit.
Prioritäten und Nachweisarten
- P1 – hoch: Kernablauf blockiert, Arbeitsdaten gehen verloren oder historische Bestände ändern sich unerwartet.
- P2 – mittel: fachlich falsche Anzeigen/Buchungen, Berechtigungslücke oder relevanter Randfall.
- P3 – niedrig: Bedienungs-/Dokumentationsabweichung mit begrenzter unmittelbarer Auswirkung.
- D: Verhalten mit isolierter Diagnose reproduziert.
- S: aus konkretem Codepfad nachvollzogen; keine entsprechende Browser-/Parallelitätsreproduktion.
Datei-/Zeilenangaben beziehen sich auf den genannten Prüfstand und sind Suchanker. Repository-Pfade beginnen am Projektstamm /var/www/html.
2. Befundübersicht
| ID | Priorität | Befund | Bezug | Nachweis |
|---|---|---|---|---|
| F-01 | P1 | Entwürfe reduzieren verfügbare Chargen, obwohl der Gesamtbestand unverändert bleibt | AP-43/27 | D |
| F-02 | P1 | „Nicht produktionsrelevant“ entfernt auch benötigte Ergebnisprodukte aus dem Formular | AP-47/28 | D |
| F-03 | P1 | Zwischenspeichern verwirft Eingaben; eine ungültige Charge kann alle Chargenzeilen verlieren | AP-43 | D/S |
| F-04 | P1 | Wiederholter Rohstoff in mehreren Phasen wird beim Wiederöffnen mehrfach vorbelegt | AP-23/28/43 | S |
| F-05 | P1 | Bearbeitung einer alten Produktion ersetzt den Verpackungs-Snapshot durch die heutige Stückliste | AP-45/Konzept | D |
| F-06 | P2 | Produktionsformular stellt Chargeneingaben nach Validierungsfehlern nicht wieder her | AP-28/39/43 | D |
| F-07 | P2 | Verpackungsvorschau hängt weiterhin an Planung und bleibt bei Änderungen teilweise veraltet | AP-41/45 | S |
| F-08 | P2 | Füllgewicht kann keine Nachkommastellen korrekt darstellen | AP-35 | D/S |
| F-09 | P2 | Ausschuss ohne Charge prüft den Gesamtbestand statt des Standorts und lässt Chargen unverändert | AP-12/27 | D |
| F-10 | P2 | Versandabschluss trotz nicht versandfertiger/fehlgeschlagener Zahlung möglich | AP-33a | D |
| F-11 | P2 | Einkaufspreise sind über die Rohstoff-Detailseite für Copyreader sichtbar | Rollen/Preisschutz | D |
| F-12 | P2 | Verkaufs-Gegenbuchungen werden in „Verbrauch pro Monat“ nicht berücksichtigt | AP-13/22 | D |
| F-13 | P2 | Produkthistorie wird still auf 500 Buchungen abgeschnitten | AP-11 | S |
| F-14 | P2 | „Enthalten in“ zeigt Prozentwerte als g/Stück und übernimmt beliebige Rezepturzeilen | AP-35/37 | S |
| F-15 | P2 | Kopieren eines ml-Einkaufs verliert Erfassungseinheit und Dichte | AP-08/42 | D |
| F-16 | P2 | Parallelitätsschutz umfasst nicht alle entscheidenden Prüfungen | Audit M-9/AP-33 | S, Risiko |
| F-17 | P3 | Köln ist nicht in allen Erfassungsmasken voreingestellt | AP-50 | S |
| F-18 | P3 | iPad-Vergrößerung und mehrere Zwischenspeicher-Buttons nur teilweise umgesetzt | AP-43/44 | S |
Zählung: 5 Befunde P1, 11 Befunde P2, 2 Befunde P3. Weitere fehlende oder bewusst nachgelagerte Funktionen stehen getrennt in Abschnitt 4; Optimierungen und zusätzliche Risiken in Abschnitt 5.
3. Detailbefunde und empfohlene Korrekturen
F-01 – Entwürfe blockieren Chargenbestand
Fundstellen: app/Services/ProductionService.php:449 (consumedByStockEntry, insbesondere Join ab Zeile 455); Gegenbeispiel app/Services/InventoryService.php:41 (remainingByIngredient mit Ausschluss von draft). Weitere Konsumenten: availableStockEntriesForIngredient, chargesWithRemainingForIngredient, Batch-Check und Ausschussvalidierung.
Ursache: Die Gesamtbestandsabfragen schließen Produktionsentwürfe ausdrücklich aus. consumedByStockEntry() filtert dagegen nur gelöschte Produktionen, nicht den Entwurfsstatus. Gespeicherte Entwurfszeilen werden dadurch als echter Chargenverbrauch behandelt.
Reproduktion D-01: Wareneingang 1.000 g; Entwurf mit 10 g dieser Charge speichern. Gesamtbestand bleibt 1.000 g, verfügbare Charge zeigt 990 g. Bei einem Entwurf über die ganze Restmenge verschwindet die Charge aus der Auswahl anderer Produktionen. Auch die zulässige Ausschussmenge wird dadurch zu klein.
Empfehlung: Gemeinsame Definition „bestandswirksame Produktion“ in sämtlichen Aggregationen verwenden. Regression muss Gesamt-, Standort- und Chargenbestand, Batch-Check sowie Ausschussprüfung nach Anlegen, Ändern, Abschluss und Löschen eines Entwurfs vergleichen. Der vorhandene ProductionDraftTest prüft nur den Gesamtbestand und erkennt diesen Fehler deshalb nicht.
F-02 – Ergebnisprodukte werden durch den Planungsfilter ausgeblendet
Fundstellen: app/Http/Controllers/Admin/Inventory/ProductionController.php:49, :163, :222; resources/views/admin/inventory/productions/_form_fields.blade.php:71 und :185.
Ursache: Planung und Produktionsergebnis verwenden dieselbe Variable $products. Diese enthält seit AP-47 nur production_relevant = true. Das Kundenfeedback verlangt den Filter ausdrücklich für die Planung oben: Die 5-ml-Variante soll weiterhin aus der für 50 ml geplanten Masse entstehen können.
Reproduktion D-02: Aktives 50-ml-Produkt produktionsrelevant lassen; aktives 5-ml-Produkt auf nicht produktionsrelevant stellen. Das gerenderte Template für Ergebniszeilen enthält die 5-ml-Variante nicht mehr. Auch das Bearbeiten vorhandener Produktionen mit diesem Output ist betroffen: Der Controller ergänzt nur das geplante Produkt, nicht sämtliche bereits gespeicherten Ergebnisprodukte.
Empfehlung: Unterschiedliche Kataloge für Planungsprodukte und Ergebnisprodukte. Bestehende Output-Produkte beim Bearbeiten erhalten. Regression: 50 ml planen, 40 × 50 ml plus 50 × 5 ml buchen, erneut öffnen und ohne Änderungen speichern, während 5 ml nicht produktionsrelevant ist.
F-03 – Zwischenspeichern schützt nicht den gesamten Arbeitsstand
Fundstellen: app/Http/Controllers/Admin/Inventory/ProductionController.php:94; app/Services/ProductionService.php:748; app/Http/Requests/Inventory/StoreProductionRequest.php:34 und :112.
Ursache: Entwürfe werden bereits beim Speichern auf konkrete Wareneingänge aufgelöst. Scheitert eine einzige batch_key-Zeile, fängt expandLines() die Ausnahme für die gesamte Liste ab und behält nur Zeilen mit stock_entry_id. Das normale Browserformular sendet aber batch_key. Zusätzlich werden unvollständige Chargen- und Ergebniszeilen gezielt herausgefiltert.
Reproduktion D-03: Zwei Entwurfszeilen senden: eine existierende Charge mit 10 g, eine inzwischen nicht mehr verfügbare Charge mit 5 g. Der HTTP-Request endet ohne Validierungsfehler und speichert einen Entwurf mit null Chargenzeilen. Auch die erste, gültige Eingabe geht verloren.
Reproduktion D-05: Entwurf mit bereits gewähltem Ergebnisprodukt und vorläufiger Stückzahl 0: HTTP-Validierungsfehler bei outputs.0.quantity, kein Entwurf gespeichert. Ein leerer Wert kann zwar passieren; die pauschale Dokumentationsaussage „ohne Pflichtfeld-Prüfung / sichert den aktuellen Stand“ stimmt dennoch nicht. Auch Produkt, Standort, Datum und Planmenge bleiben Pflichtfelder.
Empfehlung: Entwurfsdaten einschließlich unfertiger Zeilen verlustfrei speichern; Buchungsauflösung und strenge fachliche Validierung beim Abschluss. Bei einer nicht mehr verfügbaren Charge die betroffene Zeile markieren, nicht die übrigen Zeilen verwerfen. Erfolgsmeldung erst bei tatsächlich gesichertem Arbeitsstand.
F-04 – Mehrfach verwendete Rohstoffe werden beim Wiederöffnen vervielfacht
Fundstellen: resources/views/admin/inventory/productions/edit.blade.php:32, create.blade.php:24; _scripts.blade.php:237 und :257; app/Models/ProductionIngredient.php ohne Bezug auf Rezepturzeile/Phase.
Ursache: Die Daten werden beim Laden nur nach ingredient_id und Charge gruppiert. Die UI rendert jedoch jede Rezepturzeile jeder Phase einzeln. Für jede Wiederholung desselben Rohstoffs werden erneut alle vorhandenen Chargenzeilen dieses Rohstoffs eingesetzt.
Statisches Ablaufbeispiel: Derselbe Rohstoff wird in Phase A mit 6 g und Phase B mit 4 g verwendet. Nach dem Speichern ergibt das Laden eine gruppierte 10-g-Zeile. Die UI setzt 10 g in Phase A und 10 g in Phase B ein. Erneutes Abschließen scheitert an 20 g statt 10 g; beim Entwurf kann der vervielfachte Arbeitsstand erneut gespeichert werden. Der ursprüngliche Anteil je Phase ist nicht mehr rekonstruierbar.
Empfehlung: Verbrauchszeilen mit einer stabilen Rezepturzeilen-/Phasenidentität speichern oder die Chargenerfassung ausdrücklich rohstoffweise aggregiert gestalten. Ein Test nur der serverseitigen Soll-Summierung reicht nicht; erforderlich ist Erstellen → Wiederöffnen → unverändert Speichern mit demselben Rohstoff in zwei Phasen.
F-05 – Historischer Verpackungsverbrauch ändert sich bei Notizkorrektur
Fundstellen: app/Services/ProductionService.php:177 und :313; Konzept-final, Abschnitt „Warum ein Snapshot?“.
Ursache: updateProduction() löscht bei jeder Bearbeitung die Verpackungszeilen und baut sie aus der aktuell hinterlegten Stückliste aller Ergebnisprodukte neu auf. Das hebelt den Zweck des historischen Snapshots aus, sobald eine alte Produktion nochmals gespeichert wird.
Reproduktion D-08: Produktion mit einem Ergebnisstück und Stückliste „1 Tiegel“. Danach Produkt-Stückliste auf „2 Tiegel“ ändern. In der alten Produktion nur die Notiz ändern und speichern. Ihr historischer Verpackungsverbrauch steigt von 1 auf 2 Tiegel, obwohl Ergebnisprodukt und Menge gleich bleiben.
Empfehlung: Bei unveränderten Ergebnisdaten vorhandenen Snapshot erhalten. Für echte Mengen-/Produktkorrekturen eine ausdrückliche Regel für historische Stücklisten und Differenzbuchungen festlegen. Änderungen der aktuellen Stückliste allein dürfen alte Produktionsbestände nicht verändern.
F-06 – Chargeneingaben gehen nach einem Validierungsfehler aus dem Formular verloren
Fundstellen: resources/views/admin/inventory/productions/create.blade.php:24, edit.blade.php:32; Controller store/update mit withInput().
Ursache: Der Controller speichert den fehlgeschlagenen Request korrekt als alte Formulareingabe. Die Templates lesen für Chargenzeilen ausschließlich das Modell bzw. eine leere Liste. old('ingredient_lines') wird nicht verwendet. Kopfdaten und Outputs verwenden dagegen old().
Reproduktion D-04: Neue Produktion mit alten Eingaben für eine Charge und 9,8 g rendern. Im resultierenden JavaScript steht trotzdem var existingLines = [];. Im Alltag tritt dies nach einer serverseitigen Mengen-, Chargen- oder Ergebnisvalidierung auf. Beim Bearbeiten erscheinen die früher gespeicherten Werte statt der letzten Korrekturen.
Empfehlung: Alte Chargeneingaben priorisieren und ungeklärte Chargen ebenfalls sichtbar erhalten. Regression: Ein absichtlich fehlerhafter Abschluss darf keine der bereits eingetragenen Ist-Mengen aus dem Formular entfernen.
F-07 – Verpackungsvorschau entspricht nicht dem gebuchten Ergebnis
Fundstellen: app/Services/ProductionService.php:679 ($product->packagings, $perUnit * $qty); resources/views/admin/inventory/productions/_scripts.blade.php:124, :346, :478, :484.
Anforderung: V7/AP-45 verlangt ausdrücklich: „Vorschau-Box (AP-41) entsprechend an Outputs koppeln.“
Ist-Verhalten: Der Server liefert Verpackung für das Planungsprodukt und die Planmenge. Änderungen an Ergebnisprodukt oder Ergebnismenge aktualisieren die Vorschau nicht. Bei Rezepturprodukten aktualisiert eine Änderung der Planmenge im schnellen JS-Pfad lediglich die Rohstoff-Sollmengen, ebenfalls nicht die Verpackung.
Beispiel: Mit Planmenge 50 geladen; Ergebnis auf 40 große und 50 kleine Tiegel ändern. Die Vorschau zeigt weiterhin Verpackung für 50 große Tiegel. Die abschließende Buchung ist für neue Produktionen bereits outputbasiert und zieht andere Artikel/Mengen ab.
Empfehlung: Vorschau aus denselben Output-Stücklisten berechnen wie die Buchung; auf Hinzufügen, Entfernen sowie Mengen-/Produktwechsel reagieren. Mit Nullrezeptur- und Mehrfachoutput-Produkten im Browser testen.
F-08 – Füllgewicht verliert Nachkommastellen
Fundstellen: database/migrations/2026_08_26_120000_add_fill_weight_to_products_table.php:18 (integer); app/Models/Product.php:197 (int); resources/views/admin/product/form.blade.php:535 (freies Textfeld); app/Http/Controllers/ProductController.php:103 ff. (keine passende Gewichtsvalidierung).
Reproduktion D-10: Modell mit fill_weight = '39,5': gelesener Wert 39, Rezepturskala 0,39. Auch 39.5 ist mit dem Integer-Datenmodell nicht als präzises Füllgewicht abbildbar. Der konkrete SQL-Speicherpfad mit deutscher Dezimalzeichenfolge wurde nicht auf MySQL getestet; dort können zusätzlich Validierungs-/SQL-Fehler entstehen.
Auswirkung: Gerade die mit AP-35 bezweckte Korrektur für abweichende Dichten braucht häufig gebrochene Grammwerte, z. B. 50 ml × 0,79 = 39,5 g. Die Skalierung weicht dann vom tatsächlichen Füllgewicht ab.
Empfehlung: Dezimalspalte und passenden Modell-Cast verwenden; deutsches Zahlenformat normalisieren und positive Werte serverseitig validieren. Regression: leerer Wert mit dokumentiertem Fallback sowie 39,5 g, 4,5 g, ungültiger Text und negative Werte.
F-09 – Ausschuss ohne Charge ist nicht standort- und chargenkonsistent
Fundstellen: app/Http/Requests/Inventory/StoreStockDisposalRequest.php:90; app/Services/ProductionService.php:470; app/Services/InventoryService.php:106.
Reproduktion D-06: Lager A enthält 1.000 g, Lager B nichts. 500 g Ausschuss ohne Charge auf Lager B buchen. Die Prüfung verwendet den globalen Bestand und akzeptiert die Buchung; Lager B steht danach bei −500 g.
Reproduktion D-07: Die vorhandenen 1.000 g vollständig ohne Chargenbezug entsorgen. Gesamtbestand: 0 g. Verfügbare Charge in der Produktion: weiterhin 1.000 g, weil die Chargenberechnung nur Ausschuss mit stock_entry_id zuordnen kann.
Einordnung: Die optionale Charge ist ausdrücklich vorgesehen. Gerade deshalb muss eine fachliche Regel für die Verteilung bzw. eine sichtbare Einschränkung bestehen. Der frühere Audit behob den Gesamtkopfwert, nicht die vollständige Übereinstimmung der Chargenkapazitäten.
Empfehlung: Ohne Charge mindestens gegen den gewählten Standort prüfen. Ungebundenen Verbrauch nachvollziehbar auf Chargen verteilen oder eine getrennte Korrekturmenge in der Verfügbarkeitsprüfung berücksichtigen. Die globale Menge darf nicht als noch frei verfügbare Einzelcharge erneut angeboten werden.
F-10 – Zahlungsfreigabe verhindert Versandabschluss nicht
Fundstellen: app/Models/ShoppingOrder.php:379 (isReadyToShip); app/Http/Controllers/Admin/Inventory/OrderFulfillmentController.php:82; resources/views/admin/inventory/orders/index.blade.php:230.
Reproduktion D-13: Bestellung shipped = 0, txaction = failed, paid = false; isReadyToShip() liefert false. Ein Copyreader kann trotzdem über die Board-Route shipped = 2 setzen. Ergebnis: Bestellung versendet und Produktbestand −1. Der Versandbutton ist in der ungefilterten offenen Liste ebenfalls nicht an isReadyToShip() gebunden.
Einordnung: Der dokumentierte Kundenentscheid definiert Versandfertigkeit über Zahlung, mit Ausnahmen für Rechnung und API. Der Code verwendet diese Definition nur zur Filterung/Kachelbildung. Falls das Buchen tatsächlicher Sendungen ohne Zahlung bewusst erlaubt bleiben soll, fehlt eine ausdrücklich geregelte Ausnahme für diesen Ablauf; eine pauschale Freigabe für Versandmitarbeiter ist derzeit möglich.
Empfehlung: Die fachliche Freigabe im serverseitigen Board-Statuswechsel prüfen; zulässige Ausnahmen getrennt und nachvollziehbar behandeln. Rechnung/API weiterhin gemäß Kundenentscheid zulassen. Eine reine Button-Sperre genügt nicht.
F-11 – Copyreader können Einkaufspreise lesen
Fundstellen: routes/web.php, Copyreader-Route raw-material-stock/{ingredient}; app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php:132; resources/views/admin/inventory/raw-material-stock/show.blade.php:132 und :157.
Reproduktion D-11: Rohstoff mit Lieferant und letztem Einkaufspreis 123,45 €/kg. Als Benutzer mit admin = 1 dessen Rohstoff-Detailseite öffnen: HTTP 200, 123,45 steht im HTML.
Ursache: Einkauf/Wareneingang blendet Preise korrekt mittels isAdmin() aus. Die alternative Rohstoffansicht lädt und rendert die letzten Einkaufspreise ohne dieselbe Schranke. Das verletzt bereits den bestehenden Preisschutz; blockbasierte Rechte aus AP-15 sind dafür keine Voraussetzung.
Empfehlung: Preise nur für berechtigte Rollen laden/ausgeben. HTTP-Regression für Rohstoffübersicht, Detail, Wareneingang und weitere Preis-Endpunkte mit Admin- und Copyreader-Benutzern.
F-12 – Storno und erneuter Versand erhöhen die Verbrauchsstatistik künstlich
Fundstelle: app/Services/ProductStockService.php:297 (monthlyConsumptionByProduct).
Reproduktion D-09: 60 Stück Verkaufsabgang, 60 Stück Gegenbuchung, erneut 60 Stück Verkaufsabgang im selben Fenster. Tatsächlicher Nettoabgang: 60. Die Monatsstatistik summiert beide Ausgänge: 120 / 6 = 20 Stück/Monat, statt 60 / 6 = 10.
Ursache: Es werden nur direction = out-Zeilen summiert. Die Korrektur-Eingänge des idempotenten Verkaufsabgleichs neutralisieren zwar den Bestand, aber nicht diese Statistik.
Empfehlung: Technische Verkaufsrücknahmen von tatsächlichem Verbrauch unterscheiden und netto auswerten. Fachlich regeln, wie echte Retouren und Korrekturen außerhalb des Zeitfensters behandelt werden. Nicht einfach sämtliche manuellen Eingänge vom Verbrauch abziehen.
F-13 – Produkthistorie verschweigt ältere Buchungen
Fundstelle: app/Http/Controllers/Admin/Inventory/ProductStockController.php:127 (limit(500)->get()).
Verhalten: Nach Anwendung der Filter werden nur die neuesten 500 Datensätze geladen. Es fehlen Pagination und ein „Alle anzeigen“-Pfad wie bei Produktion/Einkauf. Bei 501 Buchungen im selben Filterbereich ist die älteste über diese Auswahl nicht sichtbar. Die Datenbank verliert keine Datensätze; betroffen sind Nachvollziehbarkeit und Abgleich der sichtbaren Historie mit dem Bestand.
Empfehlung: Serverseitige Pagination mit Gesamtzahl und bei Bedarf Export. Regression mit mehr als 500 Bewegungen für dasselbe Produkt im selben Monat, einschließlich älterer Initialbuchung/Gegenbuchung.
F-14 – Rezeptur-Anteile werden im Rohstoffdetail falsch bezeichnet und ausgewählt
Fundstellen: resources/views/admin/inventory/raw-material-stock/show.blade.php:98 und :108; app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php:103; app/Models/Ingredient.php:137 (products).
Ursache: Die Tabelle nennt die Spalte „Rezeptur-Anteil (g/Stück)“, zeigt aber unverändert pivot->gram. Das ist seit der Prozentrezeptur ein Prozentwert; Füllgewicht und Faktor fehlen. Zudem liefert products() beide Rezepturtypen und potenziell mehrere Phasenzeilen. unique('id') entfernt Duplikate, übernimmt aber nur den ersten Pivot-Wert statt einer definierten Hersteller-Summe.
Beispiel: 20 % bei einem 50-g-Produkt werden als 20 g/Stück angezeigt. Richtig wären 10 g ohne Faktor bzw. z. B. 11 g inklusive Faktor 1,1. Bei mehrfacher Verwendung desselben Rohstoffs bleibt nur eine Teilmenge sichtbar.
Empfehlung: Herstellerrezeptur explizit auswählen, alle zugehörigen Rohstoffzeilen summieren, Skalierung zentral wiederverwenden und fachlich eindeutig beschriften (Anteil %, Netto-g/Stück oder Verbrauch-g/Stück).
F-15 – ml-Einkäufe verlieren beim Kopieren ihre Einheit
Fundstellen: app/Http/Controllers/Admin/Inventory/StockEntryController.php:145; app/Repositories/StockEntryRepository.php:50.
Reproduktion D-12: Originalbestellung 100 ml bei Dichte 0,79, kanonisch 79 g gespeichert. „Kopieren“ erzeugt 79 g mit purchase_unit = gram, density_used = null, statt die Bestellung als 100 ml weiterbearbeitbar zu erhalten.
Auswirkung: Die ursprüngliche Masse ist zunächst gleich, aber die Wiederholungsbestellung wird in einer anderen Einheit bearbeitet und angenommen. Trägt der Nutzer beim Wareneingang wie gewohnt die gelieferte Zahl 100 ein, werden 100 g statt 79 g gebucht.
Empfehlung: Einheit und ursprüngliche Bestellmenge in der Kopie erhalten. Bestehenden Dichte-Snapshot bzw. eine ausdrücklich angezeigte Neubewertung verwenden. Regression über Kopieren → Bearbeiten → Wareneingang, nicht nur Repository-Erstanlage.
F-16 – Sperren schützen nicht sämtliche konkurrierenden Übergänge
Fundstellen: app/Http/Controllers/Admin/Inventory/OrderFulfillmentController.php:88; app/Services/OrderStatusService.php:19; app/Http/Controllers/Admin/Inventory/StockEntryController.php:193; app/Repositories/StockEntryRepository.php:131; app/Http/Controllers/Admin/Inventory/StockDisposalController.php:74.
Statisch belegte Risiken:
- Das Board prüft den bisherigen Status vor der Transaktion des Statusservices. Zwei Requests können beide „offen“ lesen; „versendet“ wird zuerst gebucht, der zuvor geprüfte „konfektioniert“-Request danach. Der Service sperrt zwar die Zeile, prüft den erlaubten Ausgangsstatus aber nicht erneut. Er setzt dann Status 1 und führt den Verkaufsabgang zurück. Ähnlich kann ein inzwischen erfolgtes Storno überschrieben werden.
- Wareneingang prüft
isPending()ohne Sperre/bedingtes Update. Zwei zuvor gestartete Requests können nacheinander unterschiedliche Eingangsmengen auf denselben Eintrag schreiben. - Ausschuss-Bestandsprüfung erfolgt im FormRequest, Buchung anschließend separat ohne Transaktion/Sperre. Zwei parallele Entnahmen können beide denselben Restbestand bestehen und zusammen darüber liegen.
Einordnung: Kein tatsächlicher Parallelitätsfehler im Betrieb nachgewiesen. SQLite-Tests können die relevante MySQL-Zeilensperrsemantik nicht bestätigen. Die alte Aussage, Parallelität sei durch Locks abgesichert, ist für diese Pfade zu weitgehend. Geringe Benutzerzahl reduziert das Auftreten, ersetzt die Absicherung aber nicht.
Empfehlung: Fachliche Vorbedingungen innerhalb der gesperrten Transaktion nochmals prüfen bzw. atomare bedingte Updates verwenden. Für Bestandsentnahmen ein gemeinsames Sperrobjekt/Lock-Protokoll festlegen. Integrationstest mit zwei unabhängigen MySQL-Verbindungen für die beschriebenen Abläufe.
F-17 – Köln-Default fehlt beim Ausschuss
Fundstellen: app/Http/Controllers/Admin/Inventory/StockDisposalController.php:59; resources/views/admin/inventory/stock-disposals/create.blade.php:74.
Verhalten: Einkauf und neue Produktion suchen Köln als Standard. Das Ausschussformular setzt nur old('location_id'), ohne Standard. Ohne gewählte Charge bleibt die Standortauswahl leer. Damit ist „Köln überall als Default“ aus AP-50 nur teilweise umgesetzt.
Empfehlung: Gemeinsame Standardstandort-Ermittlung für alle Erfassungen. Dabei aktive Standorte berücksichtigen: Die aktuelle Köln-Suche in Produktion/Einkauf filtert Köln nicht selbst nach active, obwohl die Auswahlliste nur aktive Standorte enthält.
F-18 – Komfortanforderungen aus AP-43/44 nur teilweise erfüllt
Fundstellen: resources/views/admin/inventory/productions/create.blade.php:14, edit.blade.php:21; _form_fields.blade.php:49 und :160.
Abweichungen: Es gibt pro Produktionsformular nur einen Zwischenspeicher-Button unten. Gefordert waren zwei bis drei über die Seite verteilte Buttons. Die Touch-Regeln vergrößern Eingaben innerhalb von #recipe-ingredient-lines; sie implementieren keine Vergrößerung des Notiz-Textfeldes mit dem Finger. Dieses liegt außerhalb des Containers. Größere Touch-Ziele sind umgesetzt, der konkrete Resize-Wunsch ist damit nicht nachgewiesen erfüllt.
Empfehlung: Zusätzliche Speicheraktionen oben/in langen Rezepturen; für längere Texte automatische Höhenanpassung oder gut erreichbare Vergrößerungsfunktion. Auf einem echten iPad abnehmen. Der vorhandene Test auf den Text pointer: coarse belegt keine funktionierende Touch-Bedienung.
4. Anforderungsabgleich und fehlende Funktionen
4.1 Aktuelle Erweiterungen AP-34 bis AP-51
| AP | Prüfstatus | Einordnung |
|---|---|---|
| 34 – Varianten-SKU | Im lokalen Code vorhanden | Connector nutzt get_product()->get_sku(); Import versucht vollständige Nummer und danach Kernnummer. Bestehende Tests grün. Shop-Rollout separat offen zu verifizieren. |
| 35 – Füllmenge | Teilweise korrekt | Skalierung im Service/JS vorhanden; Dezimalgewicht und Rohstoff-Detailanzeige fehlerhaft, F-08/F-14. |
| 36 – Textphasen | Vorhanden | Phasen ohne Rohstoffe werden gespeichert und im JS beibehalten. |
| 37 – Doppelte Produkte | Oberflächlich behoben | Produktzeilen werden dedupliziert; der angezeigte Anteil ist dadurch nicht fachlich korrekt aggregiert, F-14. |
| 38 – Woo-Ampel | Codeänderung vorhanden, Live-Zustand ungeprüft | Cron-Selbstheilung und Statusmapping lokal vorhanden. Keine eigene Bestätigung des dokumentierten Live-Fixes/Rollouts. |
| 39 – Abwiegen ±5 % | Vorhanden mit Einschränkungen | Serverseitige/JS-Toleranz vorhanden; Validierungsrückkehr verliert Eingaben, F-06. Absoluter Sockel von 0,05 g siehe Abschnitt 5. |
| 40 – Bestand/Differenz/Ablage | Vorhanden | Bestand ist global; mögliche irreführende Standort-/Editdarstellung siehe Abschnitt 5. |
| 41 – Vorschau oben/Phasensumme | Layout vorhanden | Phasensumme implementiert, Verpackungsvorschau fachlich nicht aktuell, F-07. |
| 42 – Rohstoffdichte/ml | Grundablauf vorhanden | Grammnormalisierung und Dichte-Snapshot vorhanden; Kopieren verliert Einheit, F-15. |
| 43 – Entwurf | Wesentliche Fehler | F-01/F-03/F-04/F-06/F-18; nicht als vollständig abgenommen bewerten. |
| 44 – Rezeptur/iPad | Teilweise vorhanden | Hintergrund, reduzierte Umrandung und Zeilenumbrüche vorhanden; Touch-Resize nicht umgesetzt/nachgewiesen, F-18. |
| 45 – Verpackung aus Ergebnis | Neue Buchung vorhanden, unvollständig | Snapshot-Änderungen und Vorschau fehlerhaft, F-05/F-07. |
| 46 – INCI-Sortierung | Vorhanden | JS sortiert die obere Anzeige absteigend nach Anteil; Phasenfolge bleibt separat. |
| 47 – Produktionsrelevanz | Filter vorhanden, zu weit angewendet | F-02. |
| 48 – Buchungsgründe | Vorhanden | Getrennte aktive Listen für Ein-/Ausgang und CRUD; Tests grün. |
| 49 – Rohstofflieferanten | Vorhanden | Kategorienfilter im INCI-Kontext, bestehende Tests grün. |
| 50 – Standorte | Umhängen vorhanden, Default unvollständig | Transaktion für Umhängen nicht gelöschter Datensätze; F-17, Historienaspekt siehe Abschnitt 5. |
| 51 – Lagerplatz | Vorhanden | Felder, Relationen, CRUD-Schutz und Anzeige an Produkt-/Verpackungslisten; Tests grün. |
4.2 Ältere Kernpakete
| Bereich | Ergebnis |
|---|---|
| INCI/Produkt-/Herstellerrezeptur, Kopieren, Phasen | Strukturen und Tests vorhanden. Transaktion beim Hersteller-Neuaufbau verhindert den früheren vollständigen Rezepturverlust. Wiederöffnung mehrphasiger Produktionen bleibt problematisch. |
| Einkauf/Wareneingang | Zweistufiger Ablauf, Netto/Brutto, USt-Snapshot, Chargen/MHD, ml/Dichte und Kopieren vorhanden. Einschränkungen F-15/F-16. |
| Rohstoffübersicht, Chargenzusammenführung, FEFO | Vorhanden. Entwurfs-/Ausschusskonsistenz nicht vollständig, F-01/F-09. |
| Produktbestand, manuelle Buchung, Kacheln | Vorhanden. Historie/Verbrauchsstatistik mit F-12/F-13. |
| Produktionsergebnisse, Sets, Gegenbuchungen | Output-Buchung und Set-Auflösung grundsätzlich vorhanden. Idempotenz und Rücknahme sind getestet; F-02/F-04/F-05/F-16 schränken die Gesamtbewertung ein. |
| Löschregeln | SoftDeletes und zentrale Abhängigkeitsprüfung vorhanden; kein vollständiger Audit-Trail. |
| API-Bestellprotokoll, Versandboard, Sammel-PDF, Label-Brücke | Vorhanden und bestehende Tests grün; Versandfreigabe/Parallelität sowie externer Rollout gesondert behandeln. |
4.3 Bewusst nachgelagert – kein neuer Implementierungsfehler
Diese Funktionen sind in der Projektdokumentation ausdrücklich zurückgestellt und im aktuellen Code nicht als vollständige Funktionen vorhanden:
| Paket | Fehlender Umfang / Abgrenzung |
|---|---|
| AP-14 | Lückenloses Änderungsprotokoll mit Vorher-/Nachher-Werten und Benutzerbezug für alle WaWi-Änderungen. product_stock_movements bildet nur einen Teil ab. |
| AP-15 | Blockbasierte Rechte „sehen/bearbeiten“ je Bereich. Derzeit hierarchische Rollen/Einzelprüfungen. |
| AP-16 | Admin-2FA. |
| AP-17 | Zentrale WaWi-Konfiguration, u. a. konfigurierbare Alarmadresse/Standardstandort. Die Stammdaten-Seite „Allgemein“ existiert bereits, ersetzt dieses Paket aber nicht. |
| AP-32 vollständig | DHL-Labels direkt per API im Partnercenter erzeugen. Aktuell nur Übernahme aus WooCommerce. |
| AP-33c | Label-Stapelerstellung/-druck und entsprechende Automatik. Rechnung-/Lieferschein-Sammeldruck ist bereits vorhanden. |
| Deckel-Varianten | Eigenständiger Bestand je Deckelfarbe sowie optionale Packlisten-Ergänzung sind Vorschläge, keine abschließend freigegebene Vollfunktion. |
| LUCID | Im Ursprungskonzept ausdrücklich spätere Erweiterung. Kein vollständiger Report nachgewiesen. |
4.4 Zusätzliche Lücken, die im Projektstatus deutlicher werden sollten
- Verpackungsbestand als nutzbare Übersicht:
InventoryService::remainingByPackagingItem()berechnet ihn, wird aber innerhalb vonapp/undresources/von keiner anderen Stelle aufgerufen.PackagingItemController::index()und seine View zeigen Stammdaten, keinen verfügbaren Bestand, keinen Abgleich gegenmin_stock_alertund keinen entsprechenden Bestandsalarm. Die im Ursprungskonzept beschriebenen Verpackungs-/Versandbestandsansichten sind damit nicht durch diese Stammdatenliste erfüllt. Spätere Pläne konzentrieren sich auf Rohstoff-/Produktbestand; diese Umfangsreduzierung sollte explizit als offen oder bewusst entfallen dokumentiert werden. - Automatische Bestandsalarm-E-Mail: In
app/Consoleund der geprüften Provider-Integration kein entsprechender Bestandsscheduler/-Mailpfad. Die sichtbaren Badges sind vorhanden. Mit AP-17/Alarmfunktion nachführen; nicht aus einem roten Badge auf vorhandenen E-Mail-Alarm schließen. - Board-Filter: AP-33a nennt 7-Tage-/Quellen-/Zahlstatusfilter. Der Controller akzeptiert die vier Kachelfilter bzw. den Standardbestand; eine entsprechende serverseitige Filterung dieser zusätzlichen Dimensionen fehlt. Als reduzierten Funktionsumfang dokumentieren oder ergänzen.
- Produktentwicklung: Der Menüpunkt ist nach den älteren Plänen ausdrücklich ein Platzhalter. Daraus darf keine vollständige Entwicklungs-/Rezepturfreigabeverwaltung abgeleitet werden.
5. Weitere Risiken und Optimierungsmöglichkeiten
Diese Punkte sind von den reproduzierten Hauptbefunden getrennt. Einige benötigen eine fachliche Entscheidung oder einen gezielten Integrationstest.
5.1 Historie, Validierung und einheitliche Regeln
- Produkt speichern ist nicht insgesamt atomar.
ProductRepository::update()speichert zunächst Produktdaten und mehrere Relationen. ErstupdateManufacturerIngredients()öffnet eine eigene Transaktion. Scheitert dort ein unbekannter Rohstoff, bleiben die vorherigen Änderungen bestehen, obwohl der Formularvorgang fehlschlägt. Gesamten fachlichen Speichervorgang transaktional und vollständig vorvalidiert ausführen. - Produkt-/Rezepturvalidierung vereinheitlichen.
ProductController::store()validiert im Wesentlichen Name, WP-Nummer und Sets. Neue Zahlen-/FK-Felder gelangen überwiegend ungeprüft ins Repository.parseNullableDecimal()/parseFactor()bevorzugenis_numeric(), während Produktions- und Einkaufsfelder andere Regeln nutzen. Diagnose D-14 zeigt:mfg_gram = '1.000'wird als 1,0 gespeichert; die normale WaWi-Eingabenormalisierung interpretiert dieselbe Zeichenfolge als Tausenderdarstellung. Bei Prozentrezepturen sind zugleich drei Nachkommastellen relevant. Deshalb zuerst eine eindeutige, feldbezogene Eingabekonvention festlegen, statt blind einen Parser auf alle Felder anzuwenden. - Rezepturhistorie fehlt. Produktion speichert tatsächliche Mengen, jedoch keine unveränderliche vollständige Rezepturversion mit Phasen, Anweisungen, Faktoren und Füllgewicht. Das Bearbeiten verwendet die heutige Rezeptur. Nach einer Rezepturänderung kann selbst eine alte Notizkorrektur an neuen Sollmengen scheitern. Mit AP-14 bzw. Rezepturversionierung behandeln.
- Standort-Löschung hängt nur nicht gelöschte Buchungen um. Die Queries in
LocationController::destroy()verwenden die normalen SoftDelete-Scopes. Historische, softgelöschte Produktionen/Eingänge bleiben am gelöschten Standort. Für spätere Auditansichten/Restores muss ausdrücklich geregelt sein, ob auch diese umzuhängen sind oder den alten Standort inklusive Name weiter anzeigen sollen. - ±5 % hat eine absolute Ausnahme.
max(req * 0.05, 0.05)erlaubt bei Sollmengen unter 1 g mehr als 5 %. Beispiel 0,01 g Soll: 0,06 g liegt noch am absoluten Toleranzrand. Als Kleinmengenregel fachlich bestätigen und im Hinweistext erklären oder an die tatsächliche Waagenauflösung anpassen. - Historische Set-Zusammensetzung:
syncSaleStock()löst bei jedem erneuten Abgleich die aktuelle Set-Stückliste auf. Bei Statuswechsel von versendet auf abgeschlossen nach einer Set-Änderung kann eine andere Komponentenzuordnung gebucht werden. Ein unveränderlicher Komponenten-Snapshot pro Bestellung vermeidet diese Abhängigkeit. - Hauptproduktvarianten: Varianten buchen laut Dokumentation vorerst auf sich selbst. Die Produktbestandsübersicht filtert
main_product_id IS NULL. Für tatsächlich verwendete untergeordnete Produkte können deshalb gebuchte Bestände in der Standardübersicht fehlen. Nicht pauschal auf Hauptprodukte umbuchen: V7 unterscheidet ausdrücklich verschiedene physische Produkte. Anzeige und aktuelle Nutzung dieses alten Modells gesondert abgleichen.
5.2 Darstellung und Bedienung
- Bestand/Differenz in der Produktionsplanung ist global. Chargenauswahl dagegen standortgebunden. Material ausschließlich in einem anderen Lager kann deshalb eine grüne Differenz erzeugen, obwohl keine Charge auswählbar ist. Beim Bearbeiten wird außerdem die eigene abgeschlossene Produktion aus der Chargenverfügbarkeit ausgeschlossen, nicht aus dem globalen Vergleichswert. Gewünschte Bedeutung eindeutig beschriften oder dieselbe Verfügbarkeitsbasis verwenden.
- Asynchrone Rezepturabfragen können sich überholen.
_scripts.blade.php::loadRecipe()bricht vorherige Requests nicht ab und prüft bei Antwort keinen Request-Schlüssel. Schneller Produkt-/Standortwechsel kann eine alte Antwort über die neue Auswahl rendern. Requests abbrechen oder nur die Antwort zum zuletzt angeforderten Schlüssel übernehmen. - Chargensplit-Zeilen lassen sich nicht entfernen. Es existiert „Weitere Charge“, aber keine entsprechende Entfernen-Aktion für
.recipe-row. Eine versehentlich zusätzliche leere Pflichtzeile kann den Abschluss blockieren. Mit Browserregression absichern. - Feldfehler dynamischer Zeilen sind nicht durchgehend sichtbar. Die Produktion rendert
@error('ingredient_lines')bzw.@error('outputs'); verschachtelte Fehler wieoutputs.0.quantitywerden dort nicht gezielt zugeordnet. Fehler pro Zeile anzeigen, damit eine abgelehnte Speicherung korrigierbar ist.
5.3 Laufzeit und Wartbarkeit
- Rezeptur-JSON erzeugt Abfragen pro Rohstoff.
buildRecipePayload()ruft in der SchleifeavailableStockEntriesForIngredient()auf; diese lädt Wareneingänge und mehrere Verbrauchssummen. Rohstoffe/Wareneingänge/Verbrauch für alle benötigten IDs bündeln. Die zusätzlichen Metadaten werden bereits sinnvoll eager geladen. - MHD-Prüfung lädt jeden Wareneingang erneut.
computeMhdWarning()macht Einzelabfragen, obwohl die Eingänge zuvor schon validiert wurden. Im Buchungsvorgang einmal laden und weiterreichen. - Navigation berechnet Bestände auf jeder Seite neu.
AppServiceProvider::boot()aggregiert Rohstoff- und Produktbestand bei jedem Sidenav-Rendering. Request-lokales Memoizing oder ein kurzlebiger, bei Buchungen invalidierter Cache kann helfen. Kein Cache mit unklarer Aktualität für verbindliche Buchungsprüfungen. - Fulfillment-Limit spart keine grundlegende Ladezeit. Das Board lädt alle offenen Bestellungen mit Positionen/Relationen und schneidet erst in PHP auf 500 ab. Zähler und Listen getrennt abfragen; Filterung/Pagination möglichst in SQL. Auch die übrigen
?all=1-Listen benötigen bei wachsender Historie echte Pagination. - Gemeinsame Requests/Defaults konsolidieren.
StoreStockEntryRequestundUpdateStockEntryRequestsind weitgehend identisch; Standort-Defaults und Zahlenregeln sind mehrfach verteilt. Die vorhandeneSettingCrudController-Basisklasse zeigt bereits ein geeignetes Muster für überschaubare gemeinsame Logik. - Build und PHPUnit-Konfiguration: Die genannten Mix-Einstiegspunkte
resources/js/app.jsundresources/sass/app.scssfehlen weiterhin. Kein Build ausgeführt, der öffentlich ausgelieferte Assets verändern würde. PHPUnit meldet ein veraltetes XML-Schema. Beides separat als Entwicklungsinfrastruktur nachziehen.
5.4 Connector und Betrieb
- Rollout v1.6.0 bleibt ein Betriebsnachweis. Lokaler Code und README belegen nur, dass die Änderungen vorhanden sind. Echte Variantenbestellung, Versionsstand im Shop, Cron-Event und Labelübernahme im Zielsystem dokumentiert abnehmen.
- Endzustände werden nicht mehr nachverfolgt.
dev/partnercenter-api/public/woocommerce/order-status.php, Methodepartnercenter_api_fetch_orders_status(), entferntsent,close,pick_up,cancelaus der Poll-Liste. Eine spätere ERP-Rücksetzung/Stornierung einer bereits grün synchronisierten Bestellung wird dadurch ohne erneute Aufnahme nicht automatisch im Shop angezeigt. Als Einschränkung dokumentieren oder einen erneuten Abgleich ermöglichen. - Poll-Liste ist ein gemeinsamer Read-Modify-Write-Wert. Gleichzeitige Weiterleitung und Cron-Abgleich können Änderungen an
_partnercenter_api_unclear_order_idsüberschreiben. Nicht in WordPress reproduziert; bei erneut orange hängenbleibenden Bestellungen neben Cron/Auth auch diesen Datenpfad prüfen. - API-Zuordnungsfehler sind protokolliert, verhindern aber nicht jeden Folgeablauf.
prepareOrder()lässt unbekannte Artikel aus den tatsächlich angelegten Positionen weg; das Protokoll meldet sie. Der Versandablauf muss deshalb fehlerhafte Übertragungen zuverlässig zur manuellen Klärung bringen. Eine allgemeine Aussage „jede Shopposition wird automatisch abgezogen“ gilt nur bei erfolgreicher Zuordnung.
6. Testprotokoll
6.1 Vorhandene Suite
| Ausführung | Ergebnis |
|---|---|
php artisan test --no-interaction |
Nicht unterstützt: Unknown option "--no-interaction". Kein fachlicher Testfehler. |
php artisan test |
338 bestanden, 1.255 Assertions, gemeldete Laufzeit 5,08 s. |
| Umgebung | PHP 8.4.1, Testdatenbank SQLite :memory:, keine gecachte Anwendungskonfiguration vorhanden. |
Die vorhandenen Testdateien decken bereits viele wichtige Regeln ab: Produktionsabschluss, Entwurfs-Gesamtbestand, Set-Abzug und Storno, Bestandskorrekturen, Rezepturrobustheit, Chargen-Merge, Dichte, Lagerplatz, Löschschutz, Versandstatus, Labeltransfer und Dokumenterstellung.
6.2 Zusätzliche Diagnosefälle
Ausgeführt über eine temporäre PHPUnit-Klasse /tmp/WawiAuditProbeTest.php mit RefreshDatabase und expliziten Assertions auf die In-Memory-Datenbank-Konfiguration. Befehl:
vendor/bin/phpunit -c phpunit.xml --display-phpunit-deprecations /tmp/WawiAuditProbeTest.php
Ergebnis: 14 Diagnosefälle / 64 Assertions erfolgreich, Laufzeit 0,605 s. Eine PHPUnit-Deprecation betrifft das alte XML-Konfigurationsschema.
Wichtig: Diese Diagnosen prüfen das beobachtete fehlerhafte Verhalten zur Beweissicherung. „Erfolgreich“ bedeutet hier, dass der Befund reproduziert wurde, nicht dass die gewünschte Funktion korrekt wäre. Sie sind kein Bestandteil der 338 regulären Tests und wurden nicht in tests/ aufgenommen.
| Diagnose | Aufbau / geprüftes Ist-Ergebnis | Befund |
|---|---|---|
| D-01 | 1.000 g Eingang + Entwurf 10 g → global 1.000 g, Charge 990 g | F-01 |
| D-02 | Aktives, nicht produktionsrelevantes Ergebnisprodukt fehlt im gerenderten Output-Template | F-02 |
| D-03 | Gültige + ungültige Batch-Zeile im Entwurf → erfolgreiche Speicherung mit null Chargenzeilen | F-03 |
| D-04 | Session enthält alte Chargeneingaben → im gerenderten JS trotzdem existingLines = [] |
F-06 |
| D-05 | Entwurf mit Ergebnisstückzahl 0 → Validierungsfehler, kein Entwurf gespeichert | F-03 |
| D-06 | 1.000 g nur in A; 500 g ungebundener Ausschuss in B → B = −500 g | F-09 |
| D-07 | 1.000 g Eingang und 1.000 g Ausschuss ohne Charge → global 0 g, Charge 1.000 g | F-09 |
| D-08 | Stückliste nach Produktion von 1 auf 2 ändern; nur Notiz korrigieren → Snapshot steigt auf 2 | F-05 |
| D-09 | Verkauf 60, Gegenbuchung 60, Verkauf 60 → Nettobestand −60, Monatsverbrauch 20 statt 10 | F-12 |
| D-10 | Füllgewicht 39,5 am Modell → 39 und Rezepturskala 0,39 |
F-08 |
| D-11 | Copyreader admin=1 sieht 123,45 €/kg im HTML der Rohstoff-Detailseite |
F-11 |
| D-12 | Kopie von 100 ml bei Dichte 0,79 → 79 g, Einheit gram, kein Dichte-Snapshot | F-15 |
| D-13 | Copyreader setzt Bestellung mit txaction=failed trotz isReadyToShip=false auf versendet; Produkt −1 |
F-10 |
| D-14 | Rezepturparser speichert Eingabe 1.000 als 1,0 |
Abschnitt 5.1 |
Die temporären Dateien sind Arbeitsartefakte der Prüfung, keine dauerhaft vorausgesetzten Projektdateien. Die fachlichen Aufbauten und Ergebnisse sind deshalb hier dokumentiert; die jeweiligen Detailabschnitte enthalten Schritte für eine spätere unabhängige Reproduktion.
6.3 Warum die grüne Suite diese Fehler nicht erkennt
- Entwurfstests prüfen überwiegend den Gesamtbestand, nicht alle Konsumenten der Chargenberechnung.
- Produktionsfiltertests betrachten die Planung, nicht den Ergebnis-Katalog derselben View.
- Viele UI-Tests suchen Texte/CSS-Selektoren im HTML, führen jedoch keine Interaktion im Browser aus.
- Produktionskorrekturtests verwenden typischerweise dieselbe Rezeptur/Stückliste wie bei der Anlage.
- Dichte-Tests prüfen Erstanlage und Wareneingang, nicht die Kombination mit dem Kopierablauf.
- Versandbereitschaft wird für Filter geprüft; daraus folgt kein Test der Schreibroute bei gesperrter Zahlung.
- Die SQLite-Suite belegt keine MySQL-Locking- oder Typkonvertierungssemantik und keinen echten WooCommerce-/iPad-Betrieb.
7. Empfohlene Reihenfolge der Nacharbeit
- Produktionsdaten sichern: F-01 bis F-06, besonders Entwurfsverlust, phasenübergreifende Mehrfachrohstoffe und historische Verpackungssnapshots. Diese Fälle vor weiterer intensiver Produktionserfassung lösen.
- Fachliche Freigaben und Bestände absichern: F-09 bis F-11 sowie F-16; Zahlungsfreigabe/Preisrechte und atomare Buchungsprüfungen.
- Anzeigen und Zahlen korrigieren: F-07/F-08/F-12 bis F-15, einschließlich Ausgabe unterschiedlicher Produktgrößen und ml-Kopierablauf.
- V7 vollständig abnehmen: F-17/F-18 plus reale Browser-/iPad-Abläufe und Connector-Rollout. Fehlende Verpackungsbestandsansicht und zusätzliche Board-Filter mit dem freigegebenen Umfang abgleichen.
- Nachgelagerte Pakete separat planen: AP-14/15/16/17/32/33c; nicht mit Bugbehebung als bereits erledigt verrechnen.
Eine spätere Abnahme sollte mindestens folgende zusammenhängende Abläufe enthalten: Produktion 50 ml planen → 5-ml-Output ergänzen → teilweise abwiegen → Entwurf speichern → wieder öffnen → abschließen → unverändert bearbeiten; denselben Rohstoff in zwei Phasen verwenden; Verpackungsstückliste nach einer Produktion ändern; ml-Einkauf kopieren und annehmen; Ausschuss mit/ohne Charge je Standort; Verkauf → Rücksetzung/Storno → Wiederversand; dieselben Berechtigungen über alle alternativen Ansichten prüfen.
8. Dokumentationsstand bereinigen
- Die Projekt-README nennt 303 Tests, V7 338; für diesen Prüfstand sind 338 / 1.255 Assertions bestätigt.
- V7 enthält gleichzeitig „noch nichts umgesetzt“ und „AP-34…AP-51 vollständig“. Historische Planungstexte klar als solche kennzeichnen; aktueller Status sollte die hier belegten Restfehler enthalten.
- Die README bezeichnet noch V6 als maßgeblichen Plan und nennt eine abweichende Dateinamensschreibweise. V7 sollte der eindeutige Einstieg für das August-Feedback sein.
docs/testleitfaden-wawi-2026-07-06.mdnennt Shop-Bestandsabzug weiterhin nachgelagert, obwohl AP-13 im Code vorhanden ist. Ein durchgängiger aktueller Testleitfaden fehlt damit.- „Revisionssichere Historie“ derzeit präziser auf additive Produktbestandsbuchungen begrenzen. Eine vollständige unveränderliche Änderungshistorie sämtlicher Rezeptur-/Produktions-/Ausschuss-/Standortänderungen ist nicht umgesetzt.
- Der Ordner heißt tatsächlich
product managementmit abschließendem Leerzeichen. Name im Rahmen dieser Prüfung beibehalten; bei einer späteren Umbenennung sämtliche Verweise berücksichtigen.
Abschluss der Prüfung: Die beschriebenen Korrekturen sind Empfehlungen. Keine davon wurde am Anwendungscode umgesetzt. Bestehende fremde Änderungen wurden beibehalten.
9. Stand der Nacharbeit (08.09.2026)
Alle 18 Befunde wurden am 08.09.2026 gegen den Code verifiziert (alle bestätigt) und anschließend umgesetzt — je Befund ein Commit mit Regressionstest. Suite danach: 362 Tests grün, 1.432 Assertions.
| Befund | Status | Umsetzung |
|---|---|---|
| F-01 | ✅ 9e745d1 |
Entwürfe zählen nicht mehr als Chargenverbrauch |
| F-02 | ✅ 7351289 |
getrennter Produktkatalog für Ergebniszeilen |
| F-03 | ✅ 859b1f0 |
Entwurf sichert Chargenzeilen verlustfrei |
| F-04 | ✅ 104671e |
Rezeptur-Phase an Verbrauchszeilen, keine Vervielfachung beim Wiederöffnen |
| F-05 | ✅ a803e95 |
historischer Verpackungs-Snapshot bleibt bei Notizkorrektur erhalten |
| F-06 | ✅ 122d28b |
Chargenzeilen nach Validierungsfehler aus old() wiederherstellen |
| F-07 | ✅ fd1572d |
Verpackungsvorschau aus dem Produktionsergebnis |
| F-08 | ✅ 2796b11 |
Füllgewicht mit Nachkommastellen |
| F-09 | ✅ db27f04 |
Ausschuss ohne Charge standort- und chargenkonsistent |
| F-10 | ✅ f93b16c |
Versandabschluss nur mit Zahlungsfreigabe |
| F-11 | ✅ 691f763 |
Einkaufspreise auf der Rohstoff-Detailseite nur für Admins |
| F-12 | ✅ 8c460aa |
Verkaufs-Gegenbuchungen im Monatsverbrauch netto verrechnen |
| F-13 | ✅ 1e7ebfb |
Produkthistorie zeigt Kappung mit Gesamtzahl und „Alle anzeigen“ |
| F-14 | ✅ 8808ff5 |
„Enthalten in“ mit Anteil (%) und Verbrauch je Stück |
| F-15 | ✅ c2a1783 |
ml-Einkauf bleibt beim Kopieren ml-Bestellung |
| F-16 | ✅ 731b246 |
fachliche Vorbedingungen innerhalb der Sperre prüfen |
| F-17 | ✅ 2859aa0 |
Köln-Default zentral (Location::defaultId), auch beim Ausschuss, nur aktive Standorte |
| F-18 | ✅ aa2fcc4 |
Zwischenspeichern oben/mittig/unten, Notizfeld für Touch vergrößerbar und auto-wachsend |
Entscheidungen bei der Umsetzung (fachlich relevant):
- F-03: Entwürfe speichern Chargenzeilen roh (
production_ingredients.batch_key,stock_entry_idnullable, Migration2026_09_08_100000). Auflösung auf Wareneingänge und Bestandsprüfung erst beim Abschluss. Ergebniszeilen dürfen im Entwurf ohne Stückzahl bleiben. Nicht mehr verfügbare Chargen werden im Formular markiert. - F-04:
production_ingredients.recipe_phase_id(Migration2026_09_08_110000); Altbestand ohne Phase wird beim Wiederöffnen nur einmal (erstes Vorkommen) eingesetzt. - F-05: Regel: Snapshot bleibt, solange Ergebnisprodukte und Stückzahlen unverändert sind; bei echter Mengen-/Produktkorrektur wird er mit der aktuellen Stückliste neu gebildet.
- F-08:
products.fill_weight→decimal(10,3)(Migration2026_09_08_120000). Migrationen auf Dev-/Live-DB ausführen. - F-09: Ausschuss ohne Charge wird FEFO auf die Chargen des Lagerorts verteilt und je Charge gebucht (wie Produktionsverbrauch). Alt-Ausschuss ohne Chargenbezug bleibt unverändert.
- F-12: Netto-Verrechnung nur für
source=sale, direction=in(technische Gegenbuchungen); manuelle Eingänge zählen weiterhin nicht gegen den Verbrauch. - F-16: Parallelität bleibt ohne MySQL-Integrationstest; die Vorbedingungen werden jetzt innerhalb der Sperren erneut geprüft (Board-Status, Wareneingang, Ausschuss).
Nachprüfung: Die Nachprüfung der Befundbehebung vom 08.09.2026 bestätigte 10 Befunde als behoben und fand bei F-03/F-04/F-05/F-07/F-09/F-15 Restfehler (N-01…N-06). Diese sind ebenfalls umgesetzt (Abschnitt 6 dort; Suite 368 grün). Die Tabelle oben ist zusammen mit dieser Nachprüfung zu lesen.
Weiterhin offen (nicht Teil der Nacharbeit): Abschnitt 4.3 (AP-14/15/16/17, AP-32 voll, AP-33c, Deckel-Varianten, LUCID), Abschnitt 4.4 (Verpackungsbestand als Übersicht, Bestandsalarm-E-Mail, zusätzliche Board-Filter), Abschnitt 5 (Optimierungen) sowie der Connector-Rollout v1.6.0 und die Browser-/iPad-Abnahme.