Reine Optik der Chargen-Erfassung (Produktions-Maske):
(a) Phasenkasten leicht hinterlegt (~3,5 %); der innere Kasten um die
Chargen-Dropdowns entfällt, stattdessen dezente Trennlinie je Rohstoff.
(b) Schriftgrößen vereinheitlicht und schwarz (wie die INCI-Liste) —
Rohstoffname, Soll-Label und Phasenname einheitlich.
(c) iPad/Touch: Eingabefelder (Charge/Menge) mind. 44 px hoch und 16 px
Schrift (verhindert iOS-Auto-Zoom) über @media (pointer: coarse).
(d) Phasenbeschreibung formatiert dargestellt wie auf Produktebene eingegeben
(Zeilenumbrüche erhalten via white-space: pre-wrap; weiterhin .text(),
also kein HTML-Injection-Risiko).
Umsetzung in _form_fields.blade (scoped <style>) + _scripts.blade
(recipe-item statt border-Kasten, recipe-soll-label, recipe-phase-note).
Test: Render-Smoke der Neu-Maske prüft Buttons (AP-43) + Optik-Hooks (AP-44).
Suite 337 grün.
Co-Authored-By: Claude Opus 4.8 <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.: „Waldbröl" gibt es nicht mehr, alles wird Köln; die
Standort-Funktion bleibt für künftige Standorte.
- LocationController::destroy überschrieben: ein belegter Lagerort lässt sich
löschen, indem seine Buchungen (Wareneingänge, Produktionen, Ausschuss)
transaktional auf einen Ziel-Lagerort übernommen werden. Ohne Ziel-Lagerort
bleibt die Sperre bestehen; unbelegte Orte werden direkt gelöscht.
- Lagerort-Liste: Ziel-Auswahl („→ Köln" vorbelegt) im Lösch-Formular.
- Wareneingang: Köln als Standard-Lagerort vorbelegt (wie in der Produktion).
Außerdem AP-49-Folgeanpassung: Test-Helper hängt die Kategorie „Kosmetikrohstoffe"
an, damit Lieferanten im gefilterten INCI-Formular erscheinen; Lagerort-Sperr-Test
prüft die neue Meldung. Suite 322 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>
AP-45: Verpackungsverbrauch (Etikett/Tiegel) wird jetzt aus dem tatsächlichen
Produktionsergebnis gebucht — je Ergebnisprodukt dessen eigene Stückliste ×
Ergebnismenge, über alle Outputs summiert — statt aus der geplanten Stückzahl.
syncPackagingSnapshot liest die production_outputs und läuft daher nach
persistOutputs (store + update umgestellt).
AP-47: Neues Produkt-Kennzeichen „produktionsrelevant" (Boolean, Default true).
Nur so markierte Produkte erscheinen in der Produktions-Auswahl
(ProductionController create/edit/copy) und in der „Enthalten in"-Box des
Rohstoffbestands (blendet z. B. Aloe-Handelsware und Zweitgrößen aus).
Checkbox im Produktformular (mit Hidden-Fallback 0). Migration Default true,
damit Bestandsdaten unverändert bleiben.
Migrationen add_production_relevant auf Dev-/Live-DB ausgeführt. 4 Tests, Suite 314 grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Die INCI-Liste (Hersteller-Rezeptur) in der Produktionsmaske wird jetzt nach
Anteil % absteigend sortiert dargestellt (null-Anteile zuletzt). Reine
Anzeige-Sortierung einer Kopie — die Chargen-Zuordnung unten bleibt in
Phasen-/pos-Reihenfolge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AP-40: INCI-Tabelle der Produktion um zwei Spalten ergänzt — „Bestand" (aktueller
Rohstoffbestand je INCI, global) und „Differenz" (Bestand − Soll; negativ rot =
Fehlmenge, sonst grün). Spaltenkopf „Regal" → „Ablage". Bestand kommt aus
InventoryService::remainingByIngredient und wird als current_stock in den
recipeJson-Payload gehängt (lazy aufgelöst — InventoryService hängt selbst von
ProductionService ab, Konstruktor-Injektion wäre zirkulär).
AP-41: „Verpackung (Vorschau)" direkt unter die INCI-Tabelle verschoben (man sieht
auf einen Blick den Gesamtbedarf). Je Phase die Gesamt-Gramm hinter dem Namen
(„Phase A (345 g)"), aktualisiert sich mit Stückzahl und Füllgewicht-Skala.
Test für den current_stock-Payload ergänzt. Suite 311 grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AP-35 (Mengenberechnung): Die Hersteller-Rezeptur ist in Prozent hinterlegt; die
Sollmengen skalieren jetzt auf das Füllgewicht statt fest auf ~100 g. Neuer
Faktor recipeGramScale = Füllgewicht/100. Basis = neues, optionales Produkt-Feld
"fill_weight" (g); ist es leer, gilt bei ml-Produkten (unit=1) die Füllmenge
(contents_total) als Gramm (1 g/ml). Ohne Massenbasis (z. B. Stück-Produkte)
bleibt es beim Altverhalten (Anteil = Gramm), sodass Bestandsprodukte/Tests
unverändert rechnen. Skala wird an die Maske durchgereicht (recipe_gram_scale),
damit Client und Server identisch rechnen (Service + 4 JS-Stellen).
AP-39 (Abwiegen): Die Ist-Menge je INCI muss nicht mehr aufs Gramm passen —
assertLinesMatchRecipe erlaubt ±5 % (kleiner absoluter Sockel), gebucht wird der
reale Ist-Wert. Status-Ampel in der Maske: Orange = nichts eingetragen,
Grün = eingetragen und ±5 %, Rot = >5 % daneben (Zahlendreher-Schutz).
Migration add_fill_weight_to_products auf Dev-/Live-DB ausgeführt (nullable).
Product fillable/cast + Produktformular-Feld ergänzt. 5 Tests, Suite 310 grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phasen ohne Rohstoffzeile (reine Hinweisphasen wie „Vorbereitung"/„Nachbereitung")
verschwanden in der Produktionsmaske, weil buildPhaseGroups() leere Gruppen
wegfilterte. Filter so angepasst, dass benannte Phasen erhalten bleiben und nur
der namenlose „lose" Sammeltopf verworfen wird. Die Phasen kommen serverseitig
bereits in pos-Reihenfolge (recipePhases orderBy pos), sodass „Vorbereitung"
korrekt über „Phase A" steht. Test sichert den recipeJson-Datenvertrag ab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Das Lösch-Formular für gespeicherte Rezeptur-Inhaltsstoffe war in das
Produkt-Formular verschachtelt; Browser verwerfen das innere <form>-Tag,
wodurch _method=DELETE ins Speichern-Formular leakte. Jetzt Button, der
per JS ein eigenes Formular außerhalb erzeugt und abschickt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Indizes auf shopping_orders.shipped/shipped_at/created_at (Board-Filter,
Tages-Kacheln) und products.wp_number (SKU-Auflösung). Die
API-Bestellungen-Seite lud das komplette JSON-payload für alle 500 Zeilen
und serialisierte es in Inline-Attribute; jetzt persistierte
positions_count, Liste ohne payload, Inhalt on-demand über show-Endpoint.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Der Default-Scope 'recipe' blendet rezepturlose Rohstoffe aus, das
Sidebar-Badge zählt aber alle aktiven kritischen — Badge und Seite
konnten sich widersprechen. Die recipe-Ansicht zeigt jetzt einen Hinweis
mit 'Alle anzeigen', sobald das Badge mehr zählt; Empty-State-Text
scope-abhängig.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bestellnummern-Links auf admin_sales_detail (admin >= 7) waren für
Versand-Mitarbeiter tote Links (stiller Redirect) — jetzt rollenabhängig.
Kachel-Zähler und pick/ready-Filter rechnen über alle offenen
Bestellungen statt über die neuesten 500; gekappt wird nur die Anzeige,
der Hinweis-Banner zeigt 'neueste n von total' korrekt. whereDate durch
indexfähiges whereBetween ersetzt; Packlisten-Toggle über data-order-id;
Gewicht über formatNumber().
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die Brücke hätte live nicht funktioniert: Connector 1.4.0 sendete die
label_url aus dem per 'deny from all' geschützten DHL-Ordner (403), dazu
SSRF-Risiko durch serverseitigen Abruf einer Client-URL und Timeout-
Mismatch (20 s Download vs. 5 s WordPress-Default). Der Connector liest
die PDF jetzt vom Shop-Dateisystem (realpath-Pinning auf wp-uploads) und
sendet sie Base64; storeWpLabel() validiert und speichert ohne Http::get;
tote Shop-Link-Fallbacks aus den Views entfernt; Connector-Timeout 30 s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Checkbox-Auswahl im Abwicklungs-Board (Alle-auswählen, Zähler in den Buttons)
+ zwei Sammel-Aktionen: „Lieferscheine drucken" und „+ Rechnungen" — je ein
zusammengeführtes PDF (PDFMerger/FPDI) im neuen Tab zum Stapeldruck. Fehlende
Dokumente werden dabei ohne Mailversand erzeugt, auf der Platte fehlende
Altdateien mit bestehender Nummer regeneriert. API-Bestellungen sind von der
Auswahl ausgenommen (deaktivierte Checkbox, Rechnung liegt im externen Shop)
und werden in gemischter Auswahl übersprungen; max. 100 je Druck.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kundenname mit Ellipsis begrenzt (150px, iPad 110px; voller Name als Tooltip),
Zellen-Padding reduziert, Positionen-Toggle als schmale letzte Spalte —
zusammen mit den Aktionen rechts angepinnt, beim Scrollen bleibt beides sichtbar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9 → 6 Spalten (Datum unter Bestellnr., Gewicht in die Packliste, Zahlung in
die Status-Spalte); Aktionen-Spalte sticky am rechten Rand (bleibt beim
horizontalen Scrollen sichtbar); unterhalb Desktop-Breite Icon-Buttons ohne
Beschriftung (Tooltip bleibt).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neuer Nav-Punkt „Bestellungen" (Abwicklung + API-Bestellungen als Unterpunkte).
Board für Versand-Mitarbeiter (copyreader): 4 Filter-Kacheln (heute eingegangen /
zu konfektionieren / versandfertig / heute versendet), Packliste je Bestellung
(Sets mit Komponenten), Dokumente (Rechnung/Lieferschein/Kombi-Download über
eigene copyreader-Route, Erstellen ohne Mailversand; API-Bestellungen verlinken
die externe Rechnung), Aktionen Konfektioniert/Versendet (nur vorwärts, Storno
bleibt Verkäufe/admin). Versandfertig-Regel per Kundenentscheid: Rechnung sofort,
Vorkasse nach Zahlungseingang, API immer (ShoppingOrder::isReadyToShip()).
Statuswechsel-Logik in OrderStatusService extrahiert (Verkäufe-Modal + Board
teilen Transaktion/Lock/Margin-Folgen/AP-13-Abzug). Kundenentscheid Punkt 3:
Board-Statuswechsel setzt shipped_status_locked — die Bestell-Detailseiten
ändern den Status dann nicht mehr automatisch (offen → in Bearbeitung).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neuer Navigationspunkt „API-Bestellungen": jede wp/order-Übertragung wird
protokolliert (api_order_logs) — auch abgelehnte (Kunde unbekannt, Bestellung
existiert bereits, Speicherfehler). Positionen ohne Produktzuordnung
(unbekannte WP-Nummer) markieren den Eintrag rot; das Modal zeigt den Inhalt
der Übertragung je Position (SKU, Produkt, Menge, Preis, Meldung übersetzt).
Dazu SKU-Konsistenzprüfung auf der Seite: doppelte WP-Nummern aktiver Produkte
(Warnung) und aktive Produkte ohne WP-Nummer (aufklappbar). Behebt die stille
Lücke, dass „article not found" nur in der API-Antwort unterging und die
Position kommentarlos in Bestellung und Bestandsabzug fehlte (Plan §4).
ShoppingUserController per Pint formatiert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Das Modal zeigt den aktuellen Bestand; ein Ausgang über Bestand fragt
per Bestätigung nach (Inventur-Korrekturen bleiben möglich, Tippfehler
werden abgefangen).
Co-Authored-By: Claude Fable 5 <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>
Chargennummern kommen von extern und können kollidieren. Der Lieferant
gehört jetzt zum Gruppenschlüssel (batchGroupKey b:<supplier>:<lot>) —
gleiche Lot-Nummern verschiedener Lieferanten verschmelzen nicht mehr
(kein gemeinsames min-MHD/Summen-Rest). Batch-Check (AP-27) prüft
zusätzlich den Lieferanten des zu buchenden Eingangs.
Tests: +1 (getrennte Optionen je Lieferant); Chargen-Tests auf neues
Schlüsselformat umgestellt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Anforderung 15.07.2026: Nahezu alle WaWi-Einträge sind jetzt löschbar
(Soft-Delete, deleted_at), mit Abhängigkeitsprüfung und Bestätigungs-
warnung vor jeder Löschung.
- Migration: deleted_at auf 14 WaWi-Tabellen (Stammdaten, ingredients,
stock_entries, stock_disposals, productions); SoftDeletes-Trait in
den Models (suppliers/packaging_items hatten es bereits).
- Neuer App\Services\DeletionGuard: zentrale Abhängigkeitsprüfung je
Entität — referenzierte Einträge sind nicht löschbar, Fehlermeldung
nennt die Verwendungen (behebt auch Audit M-4: kein 500er/stiller
Cascade-Verlust mehr beim Löschen verwendeter Stammdaten).
- SettingCrudController::destroy() prüft Blocker; Supplier-,
PackagingItem-, Ingredient-destroy ebenso (Ingredient: vollständige
Prüfung inkl. Produktion/Ausschuss, Route GET→DELETE, Audit M-6).
- Wareneingänge: auch gebuchte Eingänge löschbar, sofern kein
Produktions-Verbrauch/Ausschuss darauf gebucht ist.
- Ausschuss: löschbar (Menge steht dem Bestand wieder zur Verfügung).
- Produktionen: löschbar (admin >= 7); Produktbestand wird per
Gegenbuchung zurückgeführt (reverseProductionStock, append-only,
withTrashed), Rohstoffverbrauch zählt nicht mehr (Bestandsqueries
schließen gelöschte Produktionen aus).
- lastNetPricePerSupplier: DB::query statt Model-Query (Global Scope
auf Sub-Select-Alias).
- Debug-Log-Reste im PackagingItemController entfernt (Audit N-16).
- Views: Lösch-Buttons mit deutschen Bestätigungswarnungen (Ausschuss,
Produktionen, gebuchte Wareneingänge, INCI als DELETE-Formular).
Tests: neuer InventoryDeletionTest (10 Tests), StorageSettingsTest an
neue Löschregel angepasst. Volle Suite: 214 grün, 783 Assertions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seit 9ce711d rendert layout-2 die scripts-Section zusätzlich zum
Eltern-Layout (application.blade.php:145) — jede Seite band dadurch
alle Event-Handler doppelt. Sichtbares Symptom: „Weitere Charge" in
der Produktionsmaske legte pro Klick zwei Zeilen an.
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>