Bisher ließen sich nur offene Bestellungen bearbeiten; ein Tippfehler in
einem gebuchten Wareneingang war nicht korrigierbar (Anforderung 14.09.2026).
- Bearbeiten/Korrigieren für gebuchte Eingänge (Admin): Bestell- und
Eingangsdaten (Menge, Datum, Charge, MHD, Qualität, Preise, Lieferant,
Lagerort). Menge wirkt sofort auf den Bestand; ml-Eingänge werden in ml
korrigiert und über die gespeicherte Dichte in Gramm gebucht.
- Schutz: Menge nicht unter den bereits verbrauchten Anteil (Rohstoff je
Eingang, Verpackung je Artikel); bei Verbrauch sind Art und Rohstoff/
Artikel gesperrt (Formular + Server).
- Änderungsprotokoll (stock_entry_changes): wer/wann/Grund + geänderte
Felder alt → neu; Anzeige auf der Detailseite. Grund bei gebuchten
Eingängen Pflicht, bei offenen optional. Speichern ohne Änderung
erzeugt keine Zeile.
- Tests (6 neue), In-App-Doku (Hinweise + Leitfaden 3.2) nachgezogen.
Migration: 2026_09_14_110000_create_stock_entry_changes_table.
Suite: 396 grün.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Einheit hing bisher als Anhang rechts am Mengenfeld — auf breiten
Monitoren außer Sicht, und „g" war stiller Default. Jetzt:
- Eigenes Pflichtfeld „Einheit der bestellten Menge" direkt VOR der Menge
mit „Bitte wählen"; beim Neuanlegen muss bewusst Gramm oder Milliliter
gewählt werden, beim Bearbeiten ist die gespeicherte Einheit vorbelegt.
- Hinweis unter der Einheit sagt, ob ml möglich ist (Dichte hinterlegt,
Wert wird genannt) oder erst gepflegt werden muss; ml bleibt ohne Dichte
gesperrt. Mengen-Hinweis und ml→g-Live-Vorschau folgen der Auswahl.
- Server: Store-/UpdateStockEntryRequest weisen Rohstoff-Einkäufe ohne
Einheit ab (Verpackung bleibt ohne Einheit). Bestehende Tests ergänzt,
neuer Test für die Pflicht.
- In-App-Doku (Hinweise, Testleitfaden) nachgezogen.
Suite: 390 grün.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prüfbericht pruefung-ap53-mengenpraezision-2026-09-10.md, alle Punkte umgesetzt:
- AP53-01: Toleranz nur noch in ProductionService (TOLERANCE_RATIO 0,05, TOLERANCE_FLOOR 0,005, COMPARE_EPSILON); das Produktions-JS rendert die Konstanten aus dem Service statt eigener Zahlen (Ampel hatte noch Sockel 0,05 g).
- AP53-02: withinTolerance() vergleicht mit Float-Ausgleich 1e-9 — 0,025 g auf 0,020 g Soll ist wieder zulässig, 0,026 g nicht; Sollwerte werden nicht gerundet.
- AP53-03: Chargen-Hauptzeile und Standortsumme im Rohstoff-Detail mit formatWawiQuantity(…, 0) (0,125 g statt 0 g).
- AP53-04: Ausschuss-Fehlermeldungen (Request ohne/mit Charge, Controller-Wiederholung) mit formatWawiQuantity().
- 4.1: Obergrenze decimal(12,3) bewusst akzeptiert (Live max. 49.000 g) und als InventoryService::QUANTITY_MAX validiert; Bereichsprüfung + Rollback-Hinweis in der Deploy-Checkliste.
- 4.2: normalizeWawiQuantity() (parsen + drei Stellen) in Bestell-/Wareneingangs-/Ausschuss-Requests, parseQuantity() rundet kanonisch für batch_key- und stock_entry_id-Zeilen; Untergrenze 0,001 g; ml-Eingaben, die umgerechnet 0,000 g ergeben, werden abgelehnt (Store/Update/Receive).
- 4.3: Einkaufs-JS trennt formatQuantity (3), formatPrice (2, wie vor AP-53) und formatDensity (4).
- 4.4: Hinweise präzisiert (Übersichten ganzzahlig, Rundungs-/Untergrenzregel), Leitfaden §26 mit Sockelrand-, Hauptzeilen- und Meldungs-Prüfschritten, Plan V7 und README aktualisiert, Prüfbericht Abschnitt 6 mit Nacharbeitsstand.
Tests: QuantityPrecisionTest +6 (AP53-01…04, 4.2 Rundung/Untergrenze/Obergrenze, ml→0 g). Suite 384 grün / 1.592 Assertions.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Kundennachtrag 09.09.2026: Ist-Mengen je Charge in der Produktion sollen auf das Milligramm eingegeben werden können (z. B. 0,125 g). Bisher rundeten Datenbank (decimal 12,2), Bestandslogik, Anzeige und Toleranzsockel auf zwei Stellen; „0,125" wurde still zu 0,13, „0.125" nach der Punkt-Regel zu 125 g.
- Migration 2026_09_09_100000: production_ingredients.quantity_used, stock_entries.ordered_quantity/received_quantity, stock_disposals.quantity → decimal(12,3); Casts decimal:3. Auf der Dev-DB (Live-Kopie) ausgeführt, 53/78/7 Zeilen unverändert.
- InventoryService::QUANTITY_DECIMALS = 3; roundWawiQuantity() ersetzt alle round(…, 2) in InventoryService, ProductionService, StockDisposalController/-Request, StockEntryRepository/-Controller, RawMaterialStockController.
- formatWawiQuantity($v, $min = 2): bis drei Stellen, mindestens zwei — Produktionsdetail, Einkauf Liste/Detail/Formular, Ausschuss-Liste, Chargen-Rest im Rohstoff-Detail.
- Eingaberegel (reFormatWawiNumber, JS parseGerman/parseNumber): zusätzlich „0.xxx" als Dezimalzahl; „1.500" bleibt 1500. Produktions-JS: fmt() bis drei Stellen, fmtInput() schreibt gespeicherte Zahlen deutsch ins Feld (1,125 statt 1.125 = 1125).
- Soll-Ist-Toleranz: Sockel 0,05 → 0,005 g; Ausschuss min 0,001.
- Doku: Hinweise (Neu/geändert), Leitfaden Version 3.1 mit Abschnitt 26, Plan V7 AP-53, README (Deploy-Checkliste, Suite 378).
Tests: QuantityPrecisionTest (6) — Parsing, Anzeige, Produktion 0,125 g exakt + Rest 9,875 g, Entwurf 1,125 wiederöffnen, Toleranz Kleinmengen, Ausschuss 0,005 g.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Formular, Update-Validierung und Repository nutzen bei unverändertem Rohstoff den
Snapshot als Rückfall; bei Rohstoffwechsel wird keine fremde Dichte übernommen.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- N-01: Ist-Menge ohne gewählte Charge bleibt im Entwurf erhalten (Filter in
Request und Service, formBatchKey liefert '' für chargenlose Zeilen).
- N-03: outputSignature summiert Stückzahlen je Produkt — ein Chargensplit
ersetzt den historischen Verpackungs-Snapshot nicht mehr.
- N-05: Ausschuss ohne Chargenbezug (Altbestand) wird für die Verfügbarkeit je
Standort FEFO auf die Chargen angerechnet (applyUnboundDisposals, lesend);
Chargen-Ausschussprüfung nutzt dieselbe Verfügbarkeit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ohne Charge wird gegen den Restbestand des gewählten Lagerorts geprüft und die
Menge FEFO auf die dort verfügbaren Chargen verteilt (je Charge gebucht, in
einer Transaktion) — wie der Produktionsverbrauch. Gesamt-, Standort- und
Chargenbestand stimmen damit überein.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
production_ingredients.recipe_phase_id; Formular sendet die Phase je Zeile,
Vorbelegung je Rohstoff|Phase. Altbestand ohne Phase wird nur einmal eingesetzt.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Entwürfe speichern batch_key roh (stock_entry_id nullable), keine Auflösung
und keine Filterung unfertiger Zeilen mehr beim Zwischenspeichern. Eine nicht
mehr verfügbare Charge wird im Formular markiert statt alle Zeilen zu verwerfen.
Ergebniszeilen dürfen im Entwurf ohne Stückzahl bleiben. Auflösung, Prüfung und
Buchung erst beim Abschluss.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Der Speicher-Button heißt jetzt „Produktion abschließen"; daneben gibt es
„Zwischenspeichern (Entwurf)". Ein Entwurf sichert den Stand ohne Pflichtfeld-
und ohne Bestandsprüfung und bucht KEINEN Bestand (Roh-/Produkt-/Verpackung).
Erst „abschließen" prüft vollständig und bucht.
- Migration: productions.status (default 'completed'; Bestand ist bereits gebucht).
- Production-Model: STATUS_DRAFT/COMPLETED, isDraft(), scopeBooked().
- InventoryService: Entwürfe fallen aus allen Verbrauchs-/Bestandsjoins heraus
(status != draft an den 4 productions-Joins).
- ProductionService store()/updateProduction() bekommen $draft: Entwurf
überspringt Rezeptur-/Mengen-/Bestandsprüfung + Buchungen, sichert nur
vollständige Chargen-Zeilen (filterCompleteLines) und die angegebenen Outputs.
Ein abgeschlossene Produktion kann NICHT zurück in den Entwurf (sonst
verwaisen die Buchungen) — Guard wirft ValidationException.
- StoreProductionRequest: save_mode=draft => nur Kopfdaten Pflicht, Chargen/
Ergebnis optional; validatedPayload filtert unvollständige Zeilen/Outputs.
- Controller: Entwurf löst Chargen tolerant auf (nicht auflösbare Chargen werden
übersprungen, Entwurf ist immer speicherbar) und leitet zur Bearbeitung zurück.
- Views: create/edit „abschließen" + „Zwischenspeichern"-Button (nur solange
Entwurf); Entwurf-Badge in Liste + Bearbeiten-Kopf.
Tests: 5 neue (kein Produktbestand, kein Rohstoffverbrauch, Abschluss bucht,
Rück-Entwurf verboten, HTTP-Entwurf ohne Ergebnis ok / Abschluss erzwingt
Ergebnis). Migration auf Dev-/Live-DB. Suite 336 grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lagerplatz-Zuordnung (Raum/Regal/Fach) gab es bisher nur bei Rohstoffen. Jetzt
dieselbe Storage-Hierarchie (StorageRoom/Shelf/Slot) auch für Produkte und
Verpackungsartikel, inkl. Anzeige an den passenden Stellen.
- Migration: storage_room_id/shelf/slot an products + packaging_items
(nullable, FK constrained, nullOnDelete).
- Models Product + PackagingItem: fillable + storageRoom/Shelf/Slot-Relationen +
storageLabel() („Raum 1 | Regal 3 | A").
- Produktformular (Inventar-Abschnitt) + Verpackungsformular: Auswahl-Zeile
Raum/Regal/Fach; Optionen kommen aktiv/sortiert aus den zentral pflegbaren
Bausteinen (Einstellungen → Allgemein).
- Speichern: ProductRepository normalisiert leere Auswahl -> null (FK);
PackagingItem-Requests + Repository um die drei Felder erweitert.
- Anzeige: Produktbestand (Sub-Zeile mit Marker), Produktverpackung-Übersicht
(neue Spalte „Lagerplatz").
- DeletionGuard: Lagerbaustein ist nicht löschbar, solange Produkte/Verpackung
darauf verweisen (analog Rohstoffe).
Tests: 4 neue (Produkt speichern + leer->null, Verpackung speichern + Anzeige,
Löschsperre, freier Baustein löschbar). Migration auf Dev-/Live-DB gelaufen.
Suite 331 grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kundenentscheid 26.08. (D-5): Eingang und Ausgang haben eigene, pflegbare
Gründe-Listen statt einer festen Liste im Controller.
- Neues Modell/Tabelle stock_movement_reasons (label, direction in/out, pos,
active, SoftDeletes) inkl. Seed der bisherigen Gründe sinnvoll auf Ein-/Ausgang
aufgeteilt.
- CRUD wie DisposalReason (SettingCrudController + FormRequest + Formular mit
Richtungs-Auswahl); zwei Listen (Eingangs-/Ausgangs-Gründe) auf der Seite
Einstellungen → Allgemeines, „Neu anlegen" mit vorbelegter Richtung.
- Produktbestand-Maske: das Grund-Dropdown wird jetzt je nach Eingang/Ausgang
per JS aus den passenden, gepflegten Gründen befüllt (manualReasons entfernt).
- DeletionGuard: StockMovementReason immer löschbar.
Migration create_stock_movement_reasons auf Dev-/Live-DB. 4 Tests, Suite 326 grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kundenentscheid 26.08.: Rohstoffe werden in Gramm geführt, dürfen aber in ml
erfasst werden; über die Dichte ergeben sich die Gramm (100 ml Propolistinktur
× 0,79 = 79 g).
- Neues Feld ingredients.density (g/ml, optional) inkl. Feld im INCI-Formular.
- Wareneingang: Einheiten-Umschalter g/ml am Mengenfeld (nur wenn Dichte
hinterlegt; Live-Hinweis mit Gramm-Umrechnung, Dichte kommt aus der
Rohstoff-Suche mit). Erfassung in ml wird über die Dichte in Gramm
umgerechnet und kanonisch als Gramm gespeichert — InventoryService und alle
Aggregationen bleiben unverändert. purchase_unit + density_used (Snapshot)
protokollieren die ml-Erfassung für Anzeige/Bearbeitung; Detailseite zeigt
„790 g (= 1.000 ml)". Wareneingang-Buchung rechnet gelieferte ml ebenfalls um.
- Serverseitige Validierung: ml nur mit hinterlegter Dichte.
Migrationen add_density_* auf Dev-/Live-DB. 4 Tests, Suite 318 grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neuer Helper reFormatWawiNumber(): '5.1'/'5.12' werden als 5,1/5,12
gelesen (kommt beim Kopieren von Daten vor); '1.500' bleibt Tausender.
Gilt nur für die WaWi-Eingaben (Mengen, Preise, Gewichte, Rezeptur) —
reFormatNumber() app-weit unverändert. JS-Parser (parseGerman,
parseDeNumber) auf dieselbe Regel vereinheitlicht; parseDeNumber
behandelte '1.500' bisher sogar als 1,5.
Tests: +1 (Parsing-Matrix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- H-2: quantity_used muss > 0 sein (FormRequest-Regel + Service-Guard) —
negative Zeilen konnten sich in der Summenprüfung aufheben und
Chargen-Restbestände aufblähen.
- H-3: expandBatchLines() dekrementiert die gecachten Restmengen —
zwei Zeilen derselben Charge belasten nicht mehr denselben Einkauf
doppelt (FEFO korrekt über beide Zeilen).
- M-1: Ausschuss validiert Chargen-Konsistenz (gehört zum Rohstoff,
received) und Restbestand (je Charge bzw. gesamt).
- M-9: lockForUpdate auf die Produktionszeile bei Update und Löschen.
Tests: neuer ProductionHardeningTest (4). Suite: 223 grün, 806 Assertions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Chargen-Existenz-Check beim Buchen (JSON-Endpoint batch-check): Hinweis
„Charge bereits im Haus" + Vorbelegung MHD/Qualität aus dem letzten Eingang
- Rohstoffbestand-Detail: Chargen je batch_number summiert
(InventoryService::chargeGroupsForIngredient), Einkäufe als Unterzeilen
- Produktion: Charge-Dropdown je Charge mit Summen-Rest (batches im
Recipe-Payload), Verbrauch wird per expandBatchLines FEFO auf die
Einzel-Einkäufe der Charge verteilt; Service-API bleibt je stock_entry
- Jeder Einkauf bleibt eigener stock_entry (Preis/Datum/Audit)
- Hinweise-Doku + Plan V6.0 §6 fortgeschrieben
Tests: StockEntryChargeMergeTest (7 neu); Suite 193 grün, 698 Assertions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ingredients.exclude_from_stock (Checkbox im INCI-Formular, z. B. Allergene):
erscheint nie im Rohstoffbestand und nicht im Kritisch-Badge
- Rohstoffbestand-Uebersicht: Umschalter "Alle" / "Nur aus Herstellerrezepturen"
(?scope=recipe, nutzt scopeUsedInActiveRecipes aus AP-21)
- Fix: AP-21-Migrationen hatten identischen Timestamp (add_storage lief vor
create_storage und scheiterte auf MySQL am FK) - umbenannt auf 120753,
Teilreste bereinigt, Migrationen sauber ausgefuehrt
- Hinweise-Doku fortgeschrieben
Tests: RawMaterialStockTest +2 (volle Suite 172 gruen, 624 Assertions)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Abstrakte SettingCrudController-Basis für die kleinen Stammdaten-CRUDs
- TaxRate/DeliveryTime/DisposalReason/MaterialQuality/Location/SupplierCategory/PackagingMaterial auf ~25-Zeilen-Konfiguration reduziert
- Identische Update*Request-Klassen entfernt (Update nutzt Store-Request, via updateRequestClass() überschreibbar)
- Bilanz -441/+73 Zeilen; Grundlage für die Lagerort-CRUDs in AP-21
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>