gruene-seele/dev/product management /entwicklungsplan.md

878 lines
43 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Entwicklungsplan: Warenwirtschaft & Produktion
> **Version:** 2.0 - Stand 10.04.2026
> **Referenz:** `konzept-final.md` (V1.1), `feedback.md` (Kundin, April 2026)
> **Geschaetzte Gesamtdauer:** 12-15 Wochen (1 Entwickler)
---
## Umsetzungsprotokoll (Ist-Stand)
| Datum | Phase | Kurzbeschreibung |
|-------|-------|------------------|
| 27.03.2026 | **0** | Bugfixes Produkt/INCI, Sync-Logik, HTMLHelper-Sortierung, Kopie `type_id`; Pest-Tests `tests/Feature/ProductPhase0Test.php`; zwei Migrationskorrekturen (siehe Phase 0). |
| 27.03.2026 | **1** | Migrationen Rezeptur/Haltbarkeit/Stammdaten INCI; UI Produktformular SortableJS (CDN) + Select2; Pivot `pos`/`gram`/`factor`; Warenwirtschaft Haltbarkeit; `product_inci_sync_sent`; Ingredient-Formular `default_factor`/`min_stock_alert`; Tests `ProductPhase1Test.php`. |
| 27.03.2026 | **2** | Stammdaten-CRUD unter `admin/inventory`: Models/Relations, Repositories `SupplierRepository`/`PackagingItemRepository`, Resource-Controller + `App\Http\Requests\Inventory\*`, Views DataTables + Select2, Sidenav; Migrationen Pivot-Unique kurz (`supplier_supplier_cat_unique`), `packaging_items.product_id` als `unsignedInteger` (Kompatibilitaet `products.id`); Seeder `InventoryStammdatenSeeder`; Tests `InventoryPhase2Test.php`; `layouts.layout-2` ergaenzt `@yield('scripts')`. |
| 27.03.2026 | **2** | Erweiterung `InventoryPhase2Test.php`: vollstaendiges CRUD je Stammdaten-Entitaet (inkl. Soft-Delete Lieferant/Verpackungsartikel, n:m Lieferant-Kategorien). |
| 27.03.2026 | **3** | Wareneingang: `stock_entries`, `StockEntry`, `StockEntryRepository`, `StockEntryController`, FormRequests, Routen + API-Suche (Select2), Views index/create/edit/show + `_scripts` (Select2/toggle), Sidenav-Link fuer CopyReader; Tests `InventoryPhase3Test.php`. |
| 27.03.2026 | **4** | Pivot `product_packagings` (quantity, pos), `Product::packagings()` / `PackagingItem::products()`, `ProductRepository::updatePackagings` + Kopie; Produktformular Abschnitt „Verpackung & Material“ mit Modal, SortableJS; Tests `ProductPhase4Test.php`. |
| 27.03.2026 | **5** | Produktion: Tabellen `productions`, `production_ingredients`, `production_packagings`; `ProductionService` (Transaktion, Soll/Ist Gramm, MHD-Warnung, Packaging-Snapshot); `ProductionController` + Rezept-API; Views index/create/show; Sidenav; Tests `ProductionPhase5Test.php`. |
| | 69 | *noch offen* |
| 10.04.2026 | **5.1a** | Feedback Menue & Stammdaten: Sidenav-Umbenennungen (Rohstoffqualitaet, Verpackungsmaterial, Produktverpackung, Versandverpackung, Stammdaten→Lieferanten/Kategorien); View-Titel; Lieferant Default-Land DE; PackagingItem URL-Feld + Kategorie-Split. |
| 10.04.2026 | **5.1b** | Feedback Rezeptur & INCI: Ingredient `material_quality_id` + Anzeige im Produktformular; Rezeptur Gramm→Prozent (3 Nachkomma) mit 100%-Summe + Warnung; Hersteller-Rezeptur Kasten. |
| 10.04.2026 | **5.1c** | Feedback Produktion: edit/update + copy-Funktion; nur aktive Produkte; Default-Lagerort Koeln. |
|| | 69 | *noch offen* |
*Hinweis:* Diese Tabelle bei jeder abgeschlossenen Teil-Lieferung um eine Zeile ergaenzen.
---
## Uebersicht der Phasen
| Phase | Titel | Status | Geschaetzte Dauer | Abhaengigkeit |
|-------|-------|--------|-------------------|---------------|
| **0** | Bugfixes & Vorbereitung | **erledigt** (27.03.2026) | 2-3 Tage | - |
| **1** | INCI-Management Erweiterung | **erledigt** (27.03.2026) | 4-5 Tage | Phase 0 |
| **2** | Stammdaten-Module | **umgesetzt** (CRUD/Stammdaten, 27.03.2026) | 5-6 Tage | - |
| **3** | Wareneingang | **umgesetzt** (27.03.2026) | 5-7 Tage | Phase 2 |
| **4** | Rezeptur, BOM & Produkterweiterung | **umgesetzt** (27.03.2026) | 3-4 Tage | Phase 1 + 2 |
| **5** | Produktion & Bestandsabzug | **umgesetzt** (MVP 27.03.2026) | 6-8 Tage | Phase 3 + 4 |
| **5.1** | Feedback-Korrekturen (Menue, Rezeptur, Produktion) | **umgesetzt** (10.04.2026) | 5-7 Tage | Phase 1-5 |
| **6** | Bestandsuebersicht & Alarme | offen | 4-5 Tage | Phase 3 + 5 |
| **7** | Ausgang / Ausschuss | offen | 2-3 Tage | Phase 2 + 6 |
| **8** | Rollen, Audit-Trail & Feinschliff | offen | 3-4 Tage | Alle Phasen |
| **9** | Einstellungen & Konfiguration | offen | 1-2 Tage | Phase 6 |
```
Phase 0 --> Phase 1 --> Phase 4 --> Phase 5 --> Phase 6 --> Phase 8
/ / /
Phase 2 --> Phase 3 ------------- Phase 7 + 9 -------
```
---
## Phase 0: Bugfixes & Vorbereitung (2-3 Tage)
> **Ziel:** Solide Grundlage schaffen, bestehende Fehler beseitigen.
>
> **Status:** **Abgeschlossen** (Umsetzung 27.03.2026)
### Aufgaben
#### 0.1 - Bugfix: Product/Ingredient hasMany-Relationen — [x] erledigt
- **Dateien:** `app/Models/Product.php`, `app/Models/Ingredient.php`
- **Problem:** `product_ingredients()` hasMany hatte einen falschen Foreign-Key-Namen (`'product_ingredients'` / `'id'` statt echter FK-Spalte).
- **Ist-Umsetzung:** `hasMany(ProductIngredient::class, 'product_id')` bzw. `hasMany(..., 'ingredient_id')`.
#### 0.2 - Bugfix: updateIngredients() Sync-Logik — [x] erledigt
- **Datei:** `app/Repositories/ProductRepository.php`
- **Problem:** Nur Hinzufuegen, kein Entfernen von INCIs.
- **Ist-Umsetzung:** `$this->model->p_ingredients()->sync($ids)` mit gefilterten Integer-IDs (nur positive IDs).
#### 0.3 - Bugfix: Alphabetische Sortierung Dropdown — [x] erledigt
- **Datei:** `app/Services/HTMLHelper.php` (Klasse `HTMLHelper` liegt im Projekt unter **Services**, nicht unter `Libraries`)
- **Ist-Umsetzung:** `Ingredient::where('active', 1)->orderBy('name')->get()` in `getProductIngredientsOptions()`.
#### 0.4 - Bugfix: Attribute-Kopierung — [x] erledigt
- **Datei:** `app/Repositories/ProductRepository.php` (Methode `copy()`)
- **Ist-Umsetzung:** `type_id` aus der Quell-Zeile `ProductAttribute`: `$attribute->type_id` (statt nicht existierendem Feld am `Product`).
#### 0.5 - Migrationskorrekturen (Tests / Schema-Sauberkeit) — [x] erledigt
> Nicht im urspruenglichen Plan; noetig damit `php artisan test` mit SQLite (In-Memory) und frische Installationen konsistent laufen.
| Migration | Problem | Ist-Umsetzung |
|-----------|---------|---------------|
| `2018_09_29_145909_create_countries_table.php` | Spalte `active` war doppelt definiert | Duplikat entfernt |
| `2021_06_15_112357_create_product_buys_table.php` | FK auf `user_id`, Spalte heisst `auth_user_id` | FK auf `auth_user_id` korrigiert |
*Hinweis fuer bestehende Produktions-DBs:* Diese Dateien wurden bei laufenden Systemen typischerweise schon einmal migriert; die Korrekturen gelten vor allem fuer **neue** Deployments und die Test-Pipeline.
### Tests — [x] umgesetzt
**Datei:** `tests/Feature/ProductPhase0Test.php` (Pest)
| Geplanter Aspekt | Ist-Abdeckung |
|------------------|---------------|
| Relation `Product``product_ingredients` | Test: korrekte FK-Verknuepfung |
| Relation `Ingredient``product_ingredients` | Test: korrekte FK-Verknuepfung |
| INCIs entfernen (Sync) | Test: `updateIngredients` mit zwei, dann einer ID |
| Dropdown alphabetisch | Test: Reihenfolge Alpha vor Zebra im HTML-Output |
| Produktkopie Attribute | Test: `type_id` der `ProductAttribute`-Zeile bleibt erhalten |
Nicht als separater HTTP-Feature-Test umgesetzt (optional spaeter): Formular-POST `admin/product/store` mit INCI-Auswahl — funktional durch Repository-Sync abgedeckt.
### Deliverable
Bereinigte Codebasis ohne die in Phase 0 adressierten Bugs im Produkt/INCI-Bereich; automatisierte Regressionstests fuer die genannten Punkte.
---
## Phase 1: INCI-Management Erweiterung (4-5 Tage)
> **Ziel:** INCI-Verwaltung laut Briefing: Sortierung, Gramm, Faktor, Haltbarkeit, Kopierfunktion.
>
> **Status:** **Abgeschlossen** (Umsetzung 27.03.2026)
>
> **Vorgaben Umsetzung (abgestimmt):** SortableJS per **CDN** in `resources/views/admin/product/edit.blade.php` (`@section('scripts')`); **Select2** fuer Einzel-Auswahl „Inhaltsstoff hinzufuegen“ (Zeilen werden per JS angehaengt; Reihenfolge per Drag & Drop).
### Aufgaben
#### 1.1 - Migration: product_ingredients erweitern — [x] erledigt
- **Datei:** `database/migrations/2026_03_27_111352_add_recipe_fields_to_product_ingredients_table.php`
- Spalten: `pos`, `gram` (nullable), `factor` (Default 1.10)
#### 1.2 - Migration: ingredients erweitern — [x] erledigt
- **Datei:** `database/migrations/2026_03_27_111354_add_inventory_fields_to_ingredients_table.php`
- Spalten: `default_factor`, `min_stock_alert`
#### 1.3 - Migration: products erweitern (Haltbarkeit) — [x] erledigt
- **Datei:** `database/migrations/2026_03_27_111353_add_shelf_life_to_products_table.php`
- Spalten: `shelf_life_type` (ENUM `pao`/`fixed`, nullable), `shelf_life_months` (nullable)
#### 1.4 - Model: ProductIngredient aktualisieren — [x] erledigt
- `fillable` + `casts` fuer `pos`, `gram`, `factor`
- `Product::p_ingredients()` / `Ingredient::products()`: `withPivot('id','pos','gram','factor')`, `orderByPivot('pos')`
#### 1.5 - Model: Ingredient aktualisieren — [x] erledigt
- `fillable` + `casts`; **UI:** `resources/views/admin/ingredient/form.blade.php` + Speicherlogik in `IngredientController::store()` (Zahlen mit `reFormatNumber`)
#### 1.6 - Frontend: SortableJS Drag & Drop — [x] erledigt
- **Datei:** `resources/views/admin/product/edit.blade.php` (CDN SortableJS 1.15.2), Ziel `#ingredient-sortable-rows`
#### 1.7 - Frontend: Select2 — [x] erledigt
- Einzel-Select `#ingredient-add-select`; Katalog kommt aus `ProductController::edit()` als `ingredient_catalog` (alphabetisch)
#### 1.8 - Frontend: Gramm & Faktor Felder — [x] erledigt
- Pro Zeile `pi_gram[]`, `pi_factor[]`; Effektiv-Spalte per jQuery; Default-Faktor beim Hinzufuegen aus JSON-Katalog (`default_factor`), **ohne** zusaetzlichen AJAX-Endpoint
#### 1.9 - Frontend: Haltbarkeits-Auswahl — [x] erledigt
- Karte **Warenwirtschaft** in `admin/product/form.blade.php`: Radios keine Angabe / PAO / festes MHD; Monats-Dropdown nur bei „festes MHD“ (JS-Toggle)
#### 1.10 - Backend: ProductRepository aktualisieren — [x] erledigt
- `updateIngredients()`: parallele Arrays `pi_ingredient_id`, `pi_gram`, `pi_factor`; `sync()` mit Pivot; **Hidden** `product_inci_sync_sent`: verhindert, dass Speichern ohne INCI-Bereich alle INCIs loescht; Legacy-Pfad `product_ingredients[]` (ohne Hidden) fuer Tests/Altkunden
- `update()`: Normalisierung `shelf_life_type` / `shelf_life_months`
- `copy()`: kopiert `pos`, `gram`, `factor` sowie Haltbarkeit (via `replicate()`)
### Tests — [x] umgesetzt
**Dateien:** `tests/Feature/ProductPhase1Test.php`, ergaenzend `ProductPhase0Test.php`
| Geplanter Aspekt | Ist-Abdeckung |
|------------------|---------------|
| Gramm/Faktor/Haltbarkeit speichern | Test mit `product_inci_sync_sent` + `reFormatNumber`-Eingaben |
| Produkt-Kopie pos/gram/factor (+ PAO) | Test `ProductPhase1Test` zweiter Fall |
Nicht separat als E2E-Test: reine Reihenfolge nur Drag & Drop im Browser (Speicherung erfolgt ueber Reihenfolge der `pi_*`-Arrays).
### Deliverable
Erweitertes INCI-Management mit Rezeptur-Spalten, sortierbarer Tabelle, Haltbarkeit am Produkt und angepasstem Inhaltsstoff-Stammdaten-Formular.
---
## Phase 2: Stammdaten-Module (5-6 Tage)
> **Ziel:** Alle Stammdaten-Tabellen und CRUD-Oberflaechen fuer die Warenwirtschaft.
>
> **Status:** **Stammdaten-CRUD & Rechte** umgesetzt (27.03.2026). Routen liegen in `routes/web.php` innerhalb der bestehenden `auth:user`-Gruppe, verschachtelt mit `superadmin` bzw. `admin` (analog zum Plan, nur ohne separaten aeusseren `auth:user`-Wrapper).
### Aufgaben
#### 2.1 - Modelle & Migrations erstellen
```bash
php artisan make:model Location -mf --no-interaction
php artisan make:model SupplierCategory -mf --no-interaction
php artisan make:model Supplier -mf --no-interaction
php artisan make:model MaterialQuality -mf --no-interaction
php artisan make:model PackagingMaterial -mf --no-interaction
php artisan make:model PackagingItem -mf --no-interaction
```
- Tabellenstrukturen laut `konzept-final.md` Abschnitt 3
- Factories mit sinnvollen Faker-Daten
- **Pivot-Tabelle** `supplier_supplier_category` fuer n:m Lieferant-Kategorie-Beziehung
- Supplier: `belongsToMany(SupplierCategory::class)` statt `belongsTo`
#### 2.2 - Seeder: Vorbelegte Stammdaten
```bash
php artisan make:seeder InventoryStammdatenSeeder --no-interaction
```
- Lagerorte: Koeln, Waldboel
- Qualitaeten: konventionell, bio kaltgepresst, bio raffiniert, konventionell kaltgepresst, konventionell raffiniert
- Verpackungs-Materialien: Glas, Holz/Bambus, Pappe/Papier, Kunststoff
#### 2.3 - Repositories erstellen
- `SupplierRepository` - CRUD fuer Lieferanten inkl. Kategorie-Sync (n:m)
- `PackagingItemRepository` - CRUD fuer Verpackungs-Artikel
- Einfache Stammdaten (Locations, Qualitaeten etc.) koennen direkt im Controller verarbeitet werden (kein eigenes Repository noetig bei reinem CRUD)
#### 2.4 - Controller & FormRequests
```bash
php artisan make:controller Admin/Inventory/LocationController --resource --no-interaction
php artisan make:controller Admin/Inventory/SupplierCategoryController --resource --no-interaction
php artisan make:controller Admin/Inventory/SupplierController --resource --no-interaction
php artisan make:controller Admin/Inventory/MaterialQualityController --resource --no-interaction
php artisan make:controller Admin/Inventory/PackagingMaterialController --resource --no-interaction
php artisan make:controller Admin/Inventory/PackagingItemController --resource --no-interaction
```
- Pro Controller eine `StoreRequest` und `UpdateRequest` FormRequest-Klasse
- SupplierController nutzt `SupplierRepository`
#### 2.5 - Routes
- Neue Route-Gruppe in `routes/web.php`:
```php
// Warenwirtschaft - Stammdaten (SuperAdmin)
Route::middleware(['auth:user', 'superadmin'])->prefix('admin/inventory')->group(function () {
Route::resource('locations', LocationController::class);
Route::resource('material-qualities', MaterialQualityController::class);
Route::resource('packaging-materials', PackagingMaterialController::class);
});
// Warenwirtschaft - Stammdaten (Admin)
Route::middleware(['auth:user', 'admin'])->prefix('admin/inventory')->group(function () {
Route::resource('supplier-categories', SupplierCategoryController::class);
Route::resource('suppliers', SupplierController::class);
Route::resource('packaging-items', PackagingItemController::class);
});
```
#### 2.6 - Blade Views
- Fuer jede Stammdaten-Entitaet: `index.blade.php` (DataTable) + `form.blade.php` (Create/Edit)
- Pfad: `resources/views/admin/inventory/`
- Layout: `@extends('layouts.layout-2')` (konsistent mit bestehendem Admin)
- Styling: Bootstrap 4 (konsistent)
- DataTables: jQuery DataTables (konsistent)
- Supplier-Formular: Select2 Mehrfachauswahl fuer Kategorien
#### 2.7 - Menue-Integration (Sidenav)
- **Datei:** `resources/views/layouts/includes/layout-sidenav.blade.php`
- Neuer Menue-Block "Warenwirtschaft" nach "Produkte"
- Bedingte Anzeige je nach Rolle
- Zunaechst nur Stammdaten-Links (weitere in spaeteren Phasen)
### Tests
- [x] `tests/Feature/InventoryPhase2Test.php`: je Stammdaten-Entitaet **CRUD** (Index/Create/Store/Edit/Update/Destroy) mit passender Rolle (SuperAdmin bzw. Admin); Lieferant inkl. **n:m**-Kategorien und Soft-Delete; Verpackungsartikel mit Produkt-/Lieferanten-Zuordnung und Leeren optionaler FKs beim Update; zusaetzlich **Rechte** (Admin kein SuperAdmin-Inventory, CopyReader kein Lieferanten-Index, Admin kein `locations.store`).
### Deliverable
Alle Stammdaten-Module mit vollstaendiger CRUD-Funktionalitaet und korrekter Rechtevergabe.
---
## Phase 3: Wareneingang (5-7 Tage)
> **Ziel:** Zweistufiges Eingangs-System (Pending -> Received).
### Aufgaben
#### 3.1 - Model & Migration
```bash
php artisan make:model StockEntry -mf --no-interaction
```
- Tabellenstruktur laut `konzept-final.md`
- Inkl. `unit` ENUM('gram','piece') Spalte
#### 3.2 - Repository
- `StockEntryRepository` - CRUD + Stufe-2-Buchung (`receive()`)
- Methoden: `create()`, `update()`, `receive()`, `getByStatus()`, `getForIngredient()`
#### 3.3 - Controller & FormRequests
```bash
php artisan make:controller Admin/Inventory/StockEntryController --resource --no-interaction
```
- `StoreStockEntryRequest` - Validierung Stufe 1 (Einkauf)
- `ReceiveStockEntryRequest` - Validierung Stufe 2 (Eingang)
- Separate Route/Methode `receive()` fuer die Stufe-2-Buchung
#### 3.4 - Routes
```php
Route::middleware(['auth:user', 'copyreader'])->prefix('admin/inventory')->group(function () {
Route::resource('stock-entries', StockEntryController::class);
Route::put('stock-entries/{stockEntry}/receive', [StockEntryController::class, 'receive'])
->name('stock-entries.receive');
});
```
> **Hinweis:** Einkauf anlegen (`store`) wird ueber zusaetzliche Autorisierung auf `admin` beschraenkt, da CopyReader (Ivonne) nur Stufe 2 (buchen) darf. Umsetzung im Controller via `$this->authorize()` oder Gate.
#### 3.5 - Views
- `index.blade.php` - Liste aller Eingaenge mit Status-Badges (orange/gruen)
- `create.blade.php` - Stufe-1-Formular (dynamische Felder je nach Kategorie via JS)
- `show.blade.php` - Detail-Ansicht + Stufe-2-Formular (Eingangsbuchung)
- Inhaltsstoff-Feld: Select2 Autosuggest (AJAX-Suche)
#### 3.6 - API-Endpoint: Ingredienten-Suche
- Route: `GET /admin/inventory/api/ingredients/search?q=shea`
- Gibt JSON mit matching Ingredients zurueck
- Fuer Select2 AJAX-Anbindung
### Tests
- [x] Feature-Test: Stufe 1 - Einkauf erstellen (Status pending) — `InventoryPhase3Test.php`
- [x] Feature-Test: Stufe 2 - Wareneingang buchen (Status received)
- [x] Feature-Test: unit wird korrekt gesetzt (gram/piece)
- [x] Feature-Test: CopyReader kann Stufe 2, aber nicht Stufe 1 (Create-Form Redirect)
- [x] Feature-Test: Admin kann Stufe 1 (store)
- [x] Feature-Test: Sortierung: Pending oben, dann Received (`listForIndex`)
### Deliverable
Funktionierender Wareneingang mit Zwei-Stufen-Workflow und rollenbasiertem Zugriff.
---
## Phase 4: Rezeptur, BOM & Produkterweiterung (3-4 Tage)
> **Ziel:** Verpackung am Produkt zuweisen (Bill of Materials).
>
> **Status:** **Umgesetzt** (27.03.2026) Pivot `product_packagings` mit `pos`; UI wie INCI (Modal + SortableJS, kein separates Select2-Feld).
### Aufgaben
#### 4.1 - Migration: product_packagings
```bash
php artisan make:model ProductPackaging -m --no-interaction
```
- Spalten: `product_id`, `packaging_item_id`, `quantity` (Default 1)
#### 4.2 - Model-Relationen
- `Product::packagings()` -> `belongsToMany(PackagingItem::class, 'product_packagings')->withPivot('quantity')`
- `PackagingItem::products()` -> inverse Relation
#### 4.3 - Frontend: Produkt-Formular erweitern
- **Datei:** `resources/views/admin/product/form.blade.php`
- Neuer Abschnitt "Verpackung & Material" nach den INCIs
- Select2-Dropdown: Packaging-Artikel auswaehlen
- Pro Zeile: Artikelname, Material (readonly), Gewicht (readonly), Menge (editierbar)
- Sortierbar per SortableJS
#### 4.4 - Backend: ProductRepository erweitern
- Neue Methode `updatePackagings($data)` - Sync-Logik fuer `product_packagings`
- `copy()` erweitern: Packaging-Zuweisungen mitkopieren
### Tests
- [x] Feature-Test: Packaging am Produkt zuweisen und speichern — `ProductPhase4Test.php`
- [x] Feature-Test: Produkt-Kopie uebernimmt Packaging
- [x] Feature-Test: Menge pro Packaging-Artikel wird gespeichert
### Deliverable
Produkte haben eine vollstaendige Stueckliste (Rezeptur + Verpackung).
---
## Phase 5: Produktion & Bestandsabzug (6-8 Tage)
> **Ziel:** Produktionslauf erfassen, Chargen zuordnen, Bestaende abziehen.
>
> **Status:** **MVP umgesetzt** (27.03.2026). Bestandsabzug ist rechnerisch ueber gespeicherte Verbrauchswerte vorbereitet; vollstaendige `InventoryService`-Bilanz folgt Phase 6.
### Aufgaben
#### 5.1 - Modelle & Migrations
```bash
php artisan make:model Production -mf --no-interaction
php artisan make:model ProductionIngredient -m --no-interaction
php artisan make:model ProductionPackaging -m --no-interaction
```
- `production_packagings`: Snapshot-Tabelle fuer Packaging-Verbrauch pro Produktion
(Spalten: `production_id`, `packaging_item_id`, `quantity_used`)
#### 5.2 - Repository & Service
- `ProductionRepository` - CRUD fuer Produktions-Eintraege
- `ProductionService` - Orchestriert den Produktions-Workflow:
1. `production_ingredients`-Eintraege erstellen
2. `production_packagings`-Snapshot erstellen
3. MHD-Check: Warnung wenn Rohstoff-MHD < Produkt-MHD
- Transaktionssicher (`DB::transaction()`)
#### 5.3 - Controller & FormRequests
```bash
php artisan make:controller Admin/Inventory/ProductionController --resource --no-interaction
```
- `StoreProductionRequest` - Validierung aller Felder inkl. Chargen-Zuordnung
#### 5.4 - Routes
```php
Route::middleware(['auth:user', 'copyreader'])->prefix('admin/inventory')->group(function () {
Route::resource('productions', ProductionController::class);
});
```
#### 5.5 - AJAX-API: Produkt-Daten laden
- Route: `GET /admin/inventory/api/products/{id}/recipe`
- Gibt JSON zurueck: INCIs (mit Gramm, Faktor), Packagings, letzte 3 Chargen pro INCI am Standort
#### 5.6 - Views
- `index.blade.php` - Liste vergangener Produktionen (DataTable)
- `create.blade.php` - Produktions-Formular:
- Produkt-Dropdown
- Datum (Default heute)
- Stueckzahl
- Standort-Dropdown
- INCI-Liste (automatisch geladen): Pro INCI Chargen-Dropdown (letzte 3, Mehrfachauswahl)
- Packaging-Liste (automatisch geladen, read-only)
- `show.blade.php` - Detail-Ansicht einer Produktion
#### 5.7 - Chargen-Splitting UI
- Wenn ein User zwei Chargen fuer denselben INCI waehlt, erscheinen zwei Mengen-Felder
- Validierung: Summe der Teilmengen = Gesamtverbrauch (gram x factor x stueckzahl)
### Tests
- [x] Feature-Test: Produktion erstellen mit Chargen-Zuordnung `ProductionPhase5Test.php`
- [ ] Feature-Test: Bestandsabzug nach Produktion korrekt berechnet (Rohstoffe) Phase 6 / `InventoryService`
- [x] Feature-Test: Packaging-Snapshot wird korrekt erstellt
- [ ] Feature-Test: Chargen-Splitting (2 Chargen fuer 1 INCI) UI unterstuetzt Weitere Charge“; separater Assertions-Test optional
- [x] Feature-Test: MHD-Warnung wird ausgeloest
- [ ] Feature-Test: Packaging wird korrekt abgezogen Bestandslogik Phase 6
- [ ] Feature-Test: BOM-Aenderung nach Produktion aendert historische Bestaende nicht Snapshot-Tabelle abgedeckt; Integrations-Test optional
- [x] Unit-/Service-Test: ProductionService (Soll/Ist, API-Rezept)
### Deliverable
Vollstaendiger Produktions-Workflow mit Chargen-Tracking, Packaging-Snapshots und automatischem Bestandsabzug.
---
## Phase 5.1: Feedback-Korrekturen (5-7 Tage)
> **Ziel:** Alle Kundenfeedback-Punkte aus `feedback.md` umsetzen, bevor Phase 6 beginnt.
>
> **Status:** **Umgesetzt** (10.04.2026)
>
> **Quelle:** Feedback der Kundin (April 2026) zu den bisherigen Umsetzungen Phase 0-5.
### 5.1a — Menue & Stammdaten-Korrekturen
#### 5.1a.1 — Menustruktur Sidenav umbenennen — [x] erledigt
- **Datei:** `resources/views/layouts/includes/layout-sidenav.blade.php`
- Aenderungen:
- Materialqualitaeten" Rohstoffqualitaet"
- Verpackungsmaterialien" Verpackungsmaterial" (Singular)
- Admin-Untermenue Stammdaten" Lieferanten"; erster Punkt Lieferanten-Kategorien" Kategorien"
- Verpackungsartikel" Produktverpackung"
- Neuer Menuepunkt Versandverpackung" (filtert auf Kategorie `shipping`)
#### 5.1a.2 — View-Titel anpassen — [x] erledigt
- **Dateien:** Views unter `resources/views/admin/inventory/`
- `material-qualities/index.blade.php` + `form.blade.php`: Materialqualitaet" Rohstoffqualitaet"
- `packaging-materials/index.blade.php` + `form.blade.php`: Verpackungsmaterialien" Verpackungsmaterial"
- `packaging-items/index.blade.php` + `form.blade.php`: Verpackungsartikel" Produktverpackung" / Versandverpackung"
#### 5.1a.3 — PackagingItem Kategorien bereinigen — [x] erledigt
- **Problem:** Aktuell ENUM `packaging|label|shipping_office`. Feedback: Etikett" und Versand/Buero" gehoeren nicht unter Produktverpackung.
- **Loesung:** Kategorien aufteilen in zwei Gruppen:
- Produktverpackung: `packaging` (nur Verpackungsartikel fuer das Produkt)
- Versandverpackung: `shipping` (Versand- & Bueromaterial, Etiketten)
- **Migration:** ENUM aendern auf `packaging|shipping`; bestehende `label` und `shipping_office` auf `shipping` migrieren
- **Routing:** Separate Index-Routen fuer Produktverpackung (`?category=packaging`) und Versandverpackung (`?category=shipping`)
#### 5.1a.4 — PackagingItem URL-Feld — [x] erledigt
- **Migration:** Spalte `url` (nullable string) an `packaging_items` anfuegen
- **Model:** `url` in `$fillable`
- **Formular:** Neues Textfeld URL (Onlineshop)" in `packaging-items/form.blade.php`
- **Validierung:** `nullable|url|max:500`
#### 5.1a.5 — Lieferant: Default Land Deutschland — [x] erledigt
- **Datei:** `resources/views/admin/inventory/suppliers/form.blade.php`
- Default-Wert fuer `country_id` beim Erstellen: ID von Deutschland (`Country::where('code', 'DE')->first()->id`)
- Controller `create()`: `$defaultCountryId` mitgeben
#### 5.1a.6 — Verpackungsartikel Produkt-Zuordnung — [x] erledigt
- **Feedback:** Dropdown unten (Produkt) ist optional; wenn behalten Mehrfachauswahl
- **Loesung:** `product_id` (FK Singular) durch n:m-Relation ersetzen ODER komplett entfernen
- **Empfehlung:** Entfernen, da Produkt-Verpackungs-Zuordnung bereits ueber `product_packagings` (Phase 4) abgebildet ist; `product_id` am PackagingItem ist redundant
### 5.1b — Rezeptur & INCI-Korrekturen
#### 5.1b.1 — INCI: Qualitaet (material_quality_id) — [x] erledigt
- **Migration:** Spalte `material_quality_id` (nullable FK) an `ingredients`-Tabelle
- **Model:** `Ingredient` erhaelt `belongsTo(MaterialQuality::class)` Relation; `material_quality_id` in `$fillable`
- **Formular:** `resources/views/admin/ingredient/form.blade.php` neues Select2-Dropdown Rohstoffqualitaet" (Daten aus `material_qualities`)
- **Produktformular:** Modal + Select2 zeigt dritte Spalte Qualitaet" bei der INCI-Auswahl (z.B. Sonnenblumenoel bio kaltgepresst")
- **Katalog JSON:** `ingredient_catalog` in `ProductController::edit()` um `quality_name` erweitern
- **Anzeige Rezeptur-Tabelle:** Qualitaet als Spalte neben INCI-Name anzeigen
#### 5.1b.2 — Rezeptur: Gramm → Prozent (3 Nachkommastellen) — [x] erledigt
- **Migration:** Spalte `gram` in `product_ingredients` umbenennen zu `percentage` (decimal 8,3)
- **Model:** `ProductIngredient` Cast + Fillable anpassen
- **Frontend:** Label Gramm" Anteil (%)", Input mit `step="0.001"`, 3 Nachkommastellen
- **Effektiv-Berechnung:** `Effektiv = Prozent × Faktor`
- **Summenzeile:** Am Ende der Rezeptur-Tabelle eine Zeile Gesamt" mit Summe aller Prozentwerte
- Wenn Summe == 100.000 gruen
- Wenn Summe != 100.000 rot + Hinweis Die Gesamtrezeptur ergibt nicht 100 %!"
- **Backend:** `ProductRepository::updateIngredients()` + Validierung anpassen
#### 5.1b.3 — Hersteller-Rezeptur (zweiter Kasten) — [x] erledigt
- **Konzept:** Zweiter identischer INCI-Kasten im Produktformular fuer die Hersteller-Rezeptur"
- **Migration:** Neue Spalte `recipe_type` ENUM(`product`, `manufacturer`) an `product_ingredients` mit Default `product`
- **Model:** Zwei Scopes am `Product`: `productIngredients()` und `manufacturerIngredients()` mit Filter auf `recipe_type`
- **Frontend:** Zweite sortierbare Tabelle Hersteller Rezeptur" unter der Produkt-Rezeptur (gleiche Funktionalitaet: Drag & Drop, Prozent, Faktor, 100%-Summe)
- **Backend:** `ProductRepository::updateIngredients()` verarbeitet beide Rezepturen getrennt (separate Hidden-Felder `manufacturer_inci_sync_sent`)
- **Kopie:** `copy()` kopiert beide Rezepturen
### 5.1c — Produktion-Korrekturen
#### 5.1c.1 — Produktion bearbeitbar (edit/update) — [x] erledigt
- **Controller:** `ProductionController::edit()` und `update()` implementieren
- **FormRequest:** `UpdateProductionRequest` erstellen
- **View:** `resources/views/admin/inventory/productions/edit.blade.php` (analog create, vorbelegt)
- **Route:** `Route::resource('productions', ...)` deckt edit/update bereits ab (wenn Standard-Resource)
- **Hinweis:** Bereits abgeschlossene Produktionen editierbar machen (Fehlerkorrektur)
#### 5.1c.2 — Produktion kopierbar — [x] erledigt
- **Controller:** `ProductionController::copy(Production $production)` Zeigt create-Formular vorbelegt mit Daten der Quell-Produktion (Produkt, Standort, Chargen-Zuordnungen)
- **Route:** `GET admin/inventory/productions/{production}/copy`
- **View:** Nutzt `create.blade.php` mit vorbelegten Werten
- **Use Case:** Gleiche Rezeptur fuer verschiedene Groessen (z.B. Deobalm 50ml 5ml)
#### 5.1c.3 — Nur aktive Produkte im Dropdown — [x] erledigt
- **Controller:** `ProductionController::create()` + `edit()`: `Product::where('active', 1)->get()`
- **API:** `recipeJson()` auch fuer inaktive zulassen (fuer bestehende Produktionen)
#### 5.1c.4 — Default Lagerort Koeln — [x] erledigt
- **Controller:** `ProductionController::create()`: Default `location_id` = Koeln (aus `locations`-Tabelle)
- **View:** Dropdown vorselektiert
### Tests Phase 5.1
- [ ] Feature-Test: Menue-Labels korrekt angezeigt (Rohstoffqualitaet etc.)
- [ ] Feature-Test: PackagingItem mit URL speichern
- [ ] Feature-Test: Lieferant Default-Land = DE
- [ ] Feature-Test: INCI mit Qualitaet speichern + Anzeige im Produktformular
- [ ] Feature-Test: Rezeptur in Prozent speichern (3 Nachkomma)
- [ ] Feature-Test: 100%-Summen-Validierung
- [ ] Feature-Test: Hersteller-Rezeptur separat speichern
- [ ] Feature-Test: Produktion bearbeiten (edit/update)
- [ ] Feature-Test: Produktion kopieren
- [ ] Feature-Test: Nur aktive Produkte im Dropdown
### Deliverable
Alle Feedback-Punkte der Kundin umgesetzt; Menustruktur, Rezeptur und Produktion entsprechen den Erwartungen.
---
## Phase 6: Bestandsuebersicht & Alarme (4-5 Tage)
> **Ziel:** Vier Bestandsseiten mit Echtzeit-Berechnung und Alarm-System.
### Aufgaben
#### 6.1 - Service: InventoryService
- **Datei:** `app/Services/InventoryService.php`
- Methoden:
- `getIngredientStock(?int $locationId = null)` - Rohstoff-Bestaende
- `getPackagingStock(string $category, ?int $locationId = null)` - Packaging/Versand
- `getItemsBelowThreshold()` - Alle Artikel unter Meldebestand
- `calculateDynamicThreshold(int $ingredientId)` - **NEU (Feedback):** Automatische Berechnung
- Bestandsberechnung Rohstoffe: `SUM(stock_entries.received_quantity) - SUM(production_ingredients.quantity_used) - SUM(stock_disposals.quantity)`
- Bestandsberechnung Packaging: `SUM(stock_entries.received_quantity) - SUM(production_packagings.quantity_used) - SUM(stock_disposals.quantity)`
##### 6.1.1 - Dynamische Meldebestands-Berechnung (Feedback)
- **Algorithmus:** Durchschnittlicher Tagesverbrauch = Mittelwert aus:
1. Durchschnittsverbrauch der letzten 12 Monate (pro Tag)
2. Durchschnittsverbrauch der letzten 3 Monate (pro Tag)
- Daraus ergibt sich ein gewichteter Tagesverbrauch, der Wachstum beruecksichtigt
- **Meldebestand** = Tagesverbrauch × konfigurierbare Vorlaufzeit (Default: 14 Tage)
- **Startphase:** Solange keine ausreichende Produktionshistorie (< 3 Monate) vorhanden ist, wird `ingredients.min_stock_alert` (manueller Wert) als Fallback verwendet
- **Umschaltung:** Ab Live-Schaltung mit genuegend Daten uebernimmt die Formel automatisch
- Meldebestand-Quelle: Dynamisch berechnet ODER `ingredients.min_stock_alert` / `packaging_items.min_stock_alert` als Fallback
#### 6.2 - Controller
```bash
php artisan make:controller Admin/Inventory/StockController --no-interaction
```
- Methoden: `ingredients()`, `packaging()`, `labels()`, `shippingOffice()`
- Jede Methode liefert Bestandsdaten pro Lagerort + Gesamt
#### 6.3 - Views
- `resources/views/admin/inventory/stock/ingredients.blade.php`
- `resources/views/admin/inventory/stock/packaging.blade.php`
- `resources/views/admin/inventory/stock/labels.blade.php`
- `resources/views/admin/inventory/stock/shipping-office.blade.php`
- Jede View: DataTable mit **dynamischen** Spalten (Lagerorte aus `locations`-Tabelle), Gesamt, Meldebestand, Status
- Rote Hervorhebung bei Unterschreitung
- Neue Lagerorte -> automatisch neue Spalten (kein Hardcoding)
#### 6.4 - View Composer: Menue-Badges
- **Datei:** `app/Http/ViewComposers/InventoryBadgeComposer.php`
- Registrierung in `AppServiceProvider::boot()`:
```php
View::composer('layouts.includes.layout-sidenav', InventoryBadgeComposer::class);
```
- Zaehlt Artikel unter Meldebestand pro Kategorie
- Ergebnis wird im Sidenav als rote Badges dargestellt
- **Performance:** Query mit Caching (5 Minuten) um Seitenlade-Performance nicht zu beeintraechtigen
#### 6.5 - Cron-Job: Bestands-Alarm E-Mail
```bash
php artisan make:command InventoryAlertCommand --no-interaction
```
- Registrierung in `app/Console/Kernel.php`: `->dailyAt('07:00')`
- Prueft alle Bestaende gegen Meldebestand
- Sendet zusammenfassende E-Mail an konfigurierbare Adresse (Setting: `inventory_alert_email`)
#### 6.6 - E-Mail: Mailable
```bash
php artisan make:mail InventoryAlertMail --no-interaction
```
- Blade-Template mit Liste der Artikel unter Meldebestand
### Tests
- [ ] Unit-Test: InventoryService Bestandsberechnung (Rohstoffe)
- [ ] Unit-Test: InventoryService Bestandsberechnung (Packaging via Snapshots)
- [ ] Unit-Test: InventoryService Threshold-Berechnung
- [ ] Feature-Test: Bestandsseite zeigt korrekte Werte pro Lagerort
- [ ] Feature-Test: Dynamische Spalten bei neuem Lagerort
- [ ] Feature-Test: Badge-Composer liefert korrekte Zahlen
- [ ] Feature-Test: Cron-Job sendet E-Mail bei Unterschreitung
### Deliverable
Vier Bestandsseiten mit Live-Berechnung, dynamischen Lager-Spalten, Menue-Badges und taeglichem E-Mail-Alarm.
---
## Phase 7: Ausgang / Ausschuss (2-3 Tage)
> **Ziel:** Entsorgungen und Ausschuss erfassen.
### Aufgaben
#### 7.1 - Model & Migration
```bash
php artisan make:model StockDisposal -mf --no-interaction
```
- Inkl. `unit` ENUM('gram','piece') Spalte
#### 7.2 - Repository
- `StockDisposalRepository` - CRUD
#### 7.3 - Controller & FormRequest
```bash
php artisan make:controller Admin/Inventory/StockDisposalController --resource --no-interaction
```
#### 7.4 - Views
- `index.blade.php` - DataTable mit allen Entsorgungen (filterbar nach Typ + Zeitraum)
- `create.blade.php` - Formular: Typ, Artikel (dynamisch), Charge (optional), Lagerort, Menge, Grund, Datum
- `unit` wird automatisch gesetzt je nach Typ
#### 7.5 - Integration in Bestandsberechnung
- `InventoryService` beruecksichtigt `stock_disposals` bereits in der Formel (Phase 6)
- Sicherstellen, dass Ausgang korrekt abgezogen wird
### Tests
- [ ] Feature-Test: Ausgang erstellen
- [ ] Feature-Test: Bestand wird nach Ausgang korrekt reduziert
- [ ] Feature-Test: unit wird korrekt gesetzt
- [ ] Feature-Test: Grund ist Pflichtfeld
### Deliverable
Vollstaendige Ausschuss-Verwaltung mit Auswirkung auf Bestaende.
---
## Phase 8: Rollen, Audit-Trail & Feinschliff (3-4 Tage)
> **Ziel:** Audit-Trail, Feinschliff Berechtigungen, Code-Qualitaet.
### Aufgaben
#### 8.1 - Migration: inventory_logs
```bash
php artisan make:model InventoryLog -m --no-interaction
```
- Polymorphe Struktur: `loggable_type`, `loggable_id`, `action`, `user_id`, `changes` (JSON)
#### 8.2 - Observer: Automatisches Logging
- **Dateien:** `app/Observers/StockEntryObserver.php`, `ProductionObserver.php`, `StockDisposalObserver.php`
- Registrierung in `AppServiceProvider::boot()`
- Bei `created`, `updated`: Eintrag in `inventory_logs` mit alten/neuen Werten
#### 8.3 - Berechtigungs-Feinschliff
- Preis-Felder (`price_per_kg`, `price_total`) nur fuer admin >= 7 sichtbar
- Stammdaten-Bearbeitung nur fuer SuperAdmin
- Gates oder Policy-Klassen nach Bedarf
- Ueberpruefung aller Controller auf korrekte Middleware/Autorisierung
#### 8.4 - Code-Qualitaet
- `vendor/bin/pint --dirty` auf alle geaenderten Dateien
- Alle Feature-Tests durchlaufen lassen
- Review aller Views auf konsistentes UI (Bootstrap 4)
#### 8.5 - Menue-Integration: Finalisierung
- Alle Menuepunkte mit korrekten Badges
- Korrekte `active`-Klassen fuer Routing
- Uebersetzungen in `lang/navigation.php`
#### 8.6 - Doku: Inline-Kommentare & PHPDoc
- PHPDoc-Bloecke fuer alle neuen Services, Repositories, Models
- Array-Shape-Definitionen fuer komplexe Rueckgabewerte
### Tests
- [ ] Feature-Test: Audit-Log wird bei jeder Bestandsveraenderung erstellt
- [ ] Feature-Test: CopyReader sieht keine Preise
- [ ] Feature-Test: SuperAdmin kann alle Stammdaten verwalten
- [ ] Alle bestehenden Tests laufen weiterhin gruen
### Deliverable
Produktionsreifes System mit Audit-Trail, sauberer Rechtevergabe und vollstaendiger Test-Abdeckung.
---
## Phase 9: Einstellungen & Konfiguration (1-2 Tage)
> **Ziel:** Konfigurierbare Warenwirtschafts-Einstellungen ueber bestehendes Setting-System.
### Aufgaben
#### 9.1 - Controller
```bash
php artisan make:controller Admin/Inventory/InventorySettingController --no-interaction
```
- Analog zum bestehenden `SettingController` (`app/Http/Controllers/SettingController.php`)
- Nutzt `Setting::getContentBySlug()` / `Setting::setContentBySlug()`
#### 9.2 - View
- `resources/views/admin/inventory/settings.blade.php`
- Felder:
- E-Mail-Adresse fuer Alarme (Text, Default: `service@gruene-seele.bio`)
- Alarm aktiv? (Bool, Default: true)
- Standard-Lagerort (Dropdown aus locations, Default: erster Lagerort)
#### 9.3 - Routes
```php
Route::middleware(['auth:user', 'superadmin'])->prefix('admin/inventory')->group(function () {
Route::get('settings', [InventorySettingController::class, 'index'])->name('inventory.settings');
Route::post('settings', [InventorySettingController::class, 'store'])->name('inventory.settings.store');
});
```
#### 9.4 - Integration
- `InventoryAlertCommand` liest E-Mail aus `Setting::getContentBySlug('inventory_alert_email')`
- `StockEntryController` nutzt `Setting::getContentBySlug('inventory_default_location')` als Default
### Tests
- [ ] Feature-Test: Settings speichern und laden
- [ ] Feature-Test: Nur SuperAdmin hat Zugriff
### Deliverable
Konfigurierbare Einstellungen fuer die Warenwirtschaft.
---
## Zusammenfassung: Dateien & Artefakte
### Neue Models (13)
| Model | Tabelle | Phase |
|-------|---------|-------|
| Location | locations | 2 |
| SupplierCategory | supplier_categories | 2 |
| Supplier | suppliers | 2 |
| MaterialQuality | material_qualities | 2 |
| PackagingMaterial | packaging_materials | 2 |
| PackagingItem | packaging_items | 2 |
| StockEntry | stock_entries | 3 |
| ProductPackaging | product_packagings | 4 |
| Production | productions | 5 |
| ProductionIngredient | production_ingredients | 5 |
| ProductionPackaging | production_packagings | 5 |
| StockDisposal | stock_disposals | 7 |
| InventoryLog | inventory_logs | 8 |
Plus Pivot-Tabelle: `supplier_supplier_category` (kein eigenes Model noetig)
### Geaenderte Models (3)
| Model | Aenderung | Phase |
|-------|----------|-------|
| Product | + shelf_life_type, shelf_life_months, Relationen | 1 + 4 |
| ProductIngredient | + pos, gram, factor | 1 |
| Ingredient | + default_factor, min_stock_alert | 1 |
### Neue Controller (11)
Alle unter `app/Http/Controllers/Admin/Inventory/`:
| Controller | Phase |
|-----------|-------|
| LocationController | 2 |
| SupplierCategoryController | 2 |
| SupplierController | 2 |
| MaterialQualityController | 2 |
| PackagingMaterialController | 2 |
| PackagingItemController | 2 |
| StockEntryController | 3 |
| ProductionController | 5 |
| StockController | 6 |
| StockDisposalController | 7 |
| InventorySettingController | 9 |
### Neue Repositories (5)
| Repository | Phase |
|-----------|-------|
| SupplierRepository | 2 |
| PackagingItemRepository | 2 |
| StockEntryRepository | 3 |
| ProductionRepository | 5 |
| StockDisposalRepository | 7 |
### Neue Services (2)
| Service | Phase |
|---------|-------|
| `app/Services/ProductionService.php` | 5 |
| `app/Services/InventoryService.php` | 6 |
### Neue Views (~27)
Alle unter `resources/views/admin/inventory/`:
- Stammdaten: je `index.blade.php` + `form.blade.php` (12 Views)
- Eingang: index, create, show (3 Views)
- Produktion: index, create, show (3 Views)
- Bestand: 4 Views (je Kategorie)
- Ausgang: index, create (2 Views)
- Einstellungen: 1 View
- Angepasste Views: `admin/product/form.blade.php` (INCI + Packaging)
### Neue Artisan Commands (1)
- `app/Console/Commands/InventoryAlertCommand.php`
### Tests (Phase 0 & 1 umgesetzt)
- `tests/Feature/ProductPhase0Test.php` Relationen, Legacy-Sync `product_ingredients`, Dropdown-Sortierung, Attribut-Kopie
- `tests/Feature/ProductPhase1Test.php` Pivot Gramm/Faktor/Haltbarkeit, Produktkopie INCI-Pivot
### Geaenderte Dateien
- `routes/web.php` - Neue Route-Gruppen
- `resources/views/layouts/includes/layout-sidenav.blade.php` - Menuepunkt + Badges
- `resources/views/admin/product/form.blade.php` - INCI-Tabelle, Warenwirtschaft Haltbarkeit (Phase 1); Packaging spaeter Phase 4
- `resources/views/admin/product/edit.blade.php` - SortableJS + INCI-JavaScript (Phase 1)
- `resources/views/admin/ingredient/form.blade.php` - `default_factor`, `min_stock_alert` (Phase 1)
- `app/Http/Controllers/ProductController.php` - `ingredient_catalog` fuer Produkt-Edit (Phase 1)
- `app/Http/Controllers/IngredientController.php` - Speichern der neuen Ingredient-Felder (Phase 1)
- `app/Repositories/ProductRepository.php` - Bugfixes + Erweiterungen
- `app/Services/HTMLHelper.php` - Sortierung INCI-Dropdown (Phase 0)
- `app/Providers/AppServiceProvider.php` - View Composer + Observer
- `app/Console/Kernel.php` - Neuer Cron-Job
- `app/Models/Product.php` - Relationen + Bugfix
- `app/Models/Ingredient.php` - Neue Felder + Bugfix (ab Phase 1); Phase 0: nur hasMany-Fix
- `app/Models/ProductIngredient.php` - Neue Felder (Phase 1)
- `lang/de/navigation.php` (o.ae.) - Uebersetzungen
- `database/migrations/2018_09_29_145909_create_countries_table.php` - Phase 0: doppelte Spalte `active` entfernt
- `database/migrations/2021_06_15_112357_create_product_buys_table.php` - Phase 0: FK `auth_user_id` korrigiert
- `database/migrations/2026_03_27_111352_add_recipe_fields_to_product_ingredients_table.php` - Phase 1
- `database/migrations/2026_03_27_111353_add_shelf_life_to_products_table.php` - Phase 1
- `database/migrations/2026_03_27_111354_add_inventory_fields_to_ingredients_table.php` - Phase 1
---
## Risiken & Empfehlungen
| Risiko | Mitigation |
|--------|-----------|
| Performance bei Bestandsberechnung (viele Summen-Queries) | Caching der Badge-Counts (5 Min), InventoryService mit optimierten Aggregat-Queries |
| Komplexitaet Chargen-Splitting UI | JS-Prototyp frueh bauen, mit Kundin testen |
| Multi-Lager erweitert sich auf > 2 Standorte | Datenmodell + Views sind bereits dynamisch (n Lagerorte) |
| BOM-Aenderung nach Produktion | Geloest durch `production_packagings` Snapshot-Tabelle |
| LUCID-Report nicht im MVP | Als Phase 10 planbar, Datengrundlage ist vorhanden |
| Bestehende Tests brechen durch Migrations | Tests nutzen SQLite in-memory - Migrations laufen automatisch |
---
## Empfohlene Reihenfolge fuer sofortigen Nutzen
1. **Phase 0 + 1** zuerst - Behebt Bugs und macht die INCI-Verwaltung sofort besser (Quick Wins)
2. **Phase 2** parallel beginnen - Stammdaten sind unabhaengig
3. **Phase 3-5** sequentiell - bilden den Kern der Warenwirtschaft
4. **Phase 6** liefert den groessten Wow-Effekt (Bestandsuebersicht mit Badges)
5. **Phase 7 + 8 + 9** zum Abschluss - Ausschuss + Audit-Trail + Settings runden ab