From f4256bfc8900c889eb7f098e1d58ede28d0735aa Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Mon, 20 Jul 2026 12:43:20 +0000 Subject: [PATCH] =?UTF-8?q?Warenwirtschaft:=20Review-Fix=20=E2=80=94=20Hin?= =?UTF-8?q?weis=20auf=20kritische=20Rohstoffe=20au=C3=9Ferhalb=20der=20Rez?= =?UTF-8?q?epturen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Inventory/RawMaterialStockController.php | 13 +++++++- ...ntwicklungsplan-aktualisiert-06-07-2026.md | 1 + resources/docs/hinweise.md | 6 ++++ .../raw-material-stock/index.blade.php | 15 ++++++++- tests/Feature/RawMaterialStockTest.php | 32 +++++++++++++++++++ 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php b/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php index 6626062..8ae9aad 100644 --- a/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php +++ b/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php @@ -58,10 +58,21 @@ class RawMaterialStockController extends Controller ]; }); + $criticalCount = $rows->whereIn('status', ['critical', 'critical_ordered'])->count(); + + // Review 20.07.2026: Das Sidebar-Badge zählt ALLE aktiven kritischen Rohstoffe, + // die Standardansicht (Scope „recipe") blendet aber rezepturlose aus. Ohne + // Hinweis widersprächen sich Badge (z. B. „1") und Seite („0 kritisch"). + // Differenz nur im recipe-Scope ausweisen. + $hiddenCriticalCount = $scope === 'recipe' + ? max(0, $this->inventoryService->criticalIngredientCount() - $criticalCount) + : 0; + return view('admin.inventory.raw-material-stock.index', [ 'rows' => $rows, // Audit N-4: gleiche Zählweise wie die Kacheln (kritisch inkl. bereits bestellt). - 'criticalCount' => $rows->whereIn('status', ['critical', 'critical_ordered'])->count(), + 'criticalCount' => $criticalCount, + 'hiddenCriticalCount' => $hiddenCriticalCount, 'horizonOptions' => $this->horizonOptions(), 'defaultHorizon' => 90, 'scope' => $scope, diff --git a/dev/product management /entwicklungsplan-aktualisiert-06-07-2026.md b/dev/product management /entwicklungsplan-aktualisiert-06-07-2026.md index 912ba96..d0ca769 100644 --- a/dev/product management /entwicklungsplan-aktualisiert-06-07-2026.md +++ b/dev/product management /entwicklungsplan-aktualisiert-06-07-2026.md @@ -280,6 +280,7 @@ Der Warenkorb kommt also **per Knopfdruck** (Fulfillment-Übergabe), nicht autom | 20.07.2026 | **Review-Fix: API-Protokoll — falsch-grüne Einträge behoben** | `ApiOrderLog::recordTransfer()` löste die SKU **erneut** auf und markierte nur unbekannte Artikel als Fehler — eine Zeile mit gültiger SKU, aber **fehlender Menge/Preis** wurde von `prepareOrder()` verworfen (Position fehlt in der Bestellung!) und trotzdem grün protokolliert. Jetzt liefert `prepareOrder()` je Zeile `included`/`product_id`/`product_name` mit und das Protokoll konsumiert nur noch diese Wahrheit — `failed` = „Position nicht übernommen" (deckt SKU unbekannt UND Parameter fehlen ab); nebenbei entfallen die N+1-Produkt-Queries je Position. | `ApiOrderLogTest` +1: bekannte SKU ohne Menge ⇒ rot markiert, Position fehlt in Bestellung (9 grün) | | 20.07.2026 | **Review-Fix: PDF-Altdaten-Robustheit + Dedup** | Zwei 500er-Szenarien behoben: **(1)** `document('combined')` (Kombi-Druck im Board) prüfte keine Datei-Existenz — bei fehlender Alt-PDF flog eine `PDFMerger`-Exception statt 404/Regeneration. **(2)** Der Sammel-Druck regenerierte nur bei fehlendem **Lieferschein**; fehlte nur die Rechnungs-PDF, platzte der gesamte Batch (bis 100 Bestellungen). Kern: neue zentrale Methode **`InvoiceRepository::ensureFilesOnDisk()`** (prüft/regeneriert Rechnung UND Lieferschein mit bestehender Nummer) — ersetzt den dreifach kopierten Altdaten-Check (`MailLogistic` 2x, `batchDocuments`) und wird jetzt auch in `document()` genutzt. Dazu die vierfach duplizierte Merge-/Response-Sequenz im `OrderFulfillmentController` zu `mergedPdf()` + `pdfResponse()` zusammengezogen. | `OrderFulfillmentTest` +2: Kombi-Download und Sammel-Druck regenerieren fehlende Rechnungs-PDF (16 grün); `LogisticMailTest` unverändert grün | | 20.07.2026 | **Review-Fix: Abwicklungs-Board (Rechte-Links, 500er-Cap, Kleinigkeiten)** | **(1) Copyreader-Links:** Bestellnummern in Abwicklung + API-Bestellungen verlinkten auf `admin_sales_detail` (admin ≥ 7) — für Versand-Mitarbeiter (copyreader, Kundenentscheid AP-33 Punkt 4) ein stiller Redirect nach `/home`; Link jetzt nur für Admins, sonst reiner Text. **(2) 500er-Cap:** Kacheln „Kommissionieren/Versandfertig" und deren Filter-Listen rechneten auf den neuesten 500 offenen Bestellungen — bei Rückstau wären ausgerechnet die **ältesten versandfertigen unsichtbar** gewesen, und der Hinweis-Banner verglich den Post-Filter-Count (erschien nie). Jetzt: Zähler + Filter über **alle** offenen Bestellungen, gekappt wird nur die Anzeige („neueste :n von :total" + „Alle anzeigen"). `whereDate()` → `whereBetween` (indexfähig, Vorbereitung Index-AP). **(3)** Packlisten-Toggle nutzt `data-order-id` statt Link-Text-Scraping; Gewicht über `formatNumber()`. | `OrderFulfillmentTest` +2 (copyreader ohne Link / admin mit Link; 18 grün) | +| 20.07.2026 | **Review-Fix: Rohstoffbestand — Badge vs. Rezeptur-Scope** | Der 17.07. gedrehte Default-Scope „recipe" blendet rezepturlose Rohstoffe aus, das Sidebar-Badge (`criticalIngredientCount`) zählt aber alle aktiven — ein kritischer Rohstoff außerhalb der Rezepturen ergab Badge „1", Seite „0 kritisch". Jetzt zeigt die recipe-Ansicht einen **Hinweis** („N kritische außerhalb der Herstellerrezepturen — Alle anzeigen"), sobald das Badge mehr zählt als die Liste; Empty-State-Text scope-abhängig korrigiert. Badge-Zählregel bewusst unverändert (global) — der Hinweis schließt die Lücke, ohne dass eine kritische Neuentwicklung ganz aus dem Badge fällt. | `RawMaterialStockTest` +1 (Hinweis im recipe-Scope, keiner im all-Scope; 20 grün) | | _offen_ | AP-32 | Versand im System: DHL-Etiketten per API — **neu aufgenommen + Ansatz dokumentiert (§4, 15.07.2026)**; Klärungspunkte (DHL-Konto, Versandprodukte, Druck-Setup) für nächste Anforderungsrunde notiert. **Zwischenstand 17.07.2026: Brückenlösung aktiv** (Label aus dem Shop, s. Protokollzeile) — deckt den Versand-Flow ab, bis die DHL-Klärungen da sind | – | | _offen_ | AP-14/15/16/17 | Audit-Trail, Blockrechte, 2FA, WaWi-Einstellungen | – | diff --git a/resources/docs/hinweise.md b/resources/docs/hinweise.md index 3b550bd..d08cd99 100644 --- a/resources/docs/hinweise.md +++ b/resources/docs/hinweise.md @@ -87,6 +87,12 @@ Gramm (Rohstoff) bzw. Stück (Verpackung) und Datum. Jeder Ausgang reduziert sofort den Bestand – beim Rohstoffbestand also auch die „auf Null"-Prognose und den Kritisch-Status. +- **Rohstoffbestand — kritische außerhalb der Rezepturen (Hinweis, 20.07.2026):** + Die Standardansicht zeigt „Nur aus Herstellerrezepturen", das Sidebar-Badge + zählt aber **alle** aktiven kritischen Rohstoffe. Liegt ein kritischer + Rohstoff außerhalb der Rezepturen (z. B. Neuentwicklung), erscheint über der + Liste ein Hinweis mit „Alle anzeigen", damit Badge und Liste nicht + auseinanderlaufen. - **Shop-Bestandsabzug beim Versand (neu 16.07.2026):** Wird eine Bestellung auf **„versendet"** oder **„abgeschlossen"** gesetzt, bucht das System je Bestellposition automatisch einen Abgang im **Produktbestand** (Grund diff --git a/resources/views/admin/inventory/raw-material-stock/index.blade.php b/resources/views/admin/inventory/raw-material-stock/index.blade.php index 68dc5c0..e1497f5 100644 --- a/resources/views/admin/inventory/raw-material-stock/index.blade.php +++ b/resources/views/admin/inventory/raw-material-stock/index.blade.php @@ -77,6 +77,19 @@ + @if (($hiddenCriticalCount ?? 0) > 0) + {{-- Review 20.07.2026: Badge zählt alle aktiven Rohstoffe, diese Ansicht nur + die aus Herstellerrezepturen — sonst widersprechen sich Badge und Seite --}} + + @endif +
@@ -156,7 +169,7 @@ diff --git a/tests/Feature/RawMaterialStockTest.php b/tests/Feature/RawMaterialStockTest.php index 644403b..8a823c2 100644 --- a/tests/Feature/RawMaterialStockTest.php +++ b/tests/Feature/RawMaterialStockTest.php @@ -321,6 +321,38 @@ test('modus recipe zeigt nur rohstoffe aus herstellerrezepturen aktiver produkte ->assertSee('AP20-Neuentwicklung'); }); +test('recipe-scope weist auf kritische rohstoffe ausserhalb der rezepturen hin', function () { + // Review 20.07.2026: Badge zählt alle aktiven kritischen Rohstoffe, die + // Standardansicht blendet rezepturlose aus — der Hinweis schließt die Lücke. + $user = ap10MakeUser(1); + $inRecipe = ap10MakeIngredient('AP20-InRezeptur', 500); + // rezepturloser Rohstoff mit Meldebestand, aber ohne Bestand => kritisch + ap10MakeIngredient('AP20-Neuentwicklung', 500); + + $product = Product::query()->create(['name' => 'AP20-Produkt', 'title' => 'AP20-Produkt', 'active' => true]); + ProductIngredient::query()->create([ + 'product_id' => $product->id, + 'ingredient_id' => $inRecipe->id, + 'pos' => 0, + 'gram' => 10, + 'factor' => 1.0, + 'recipe_type' => 'manufacturer', + ]); + + $this->actingAs($user, 'user'); + + // Badge zählt beide, die recipe-Ansicht nur einen => Hinweis auf den versteckten + expect(app(InventoryService::class)->criticalIngredientCount())->toBe(2); + $this->get(route('admin.inventory.raw-material-stock.index')) + ->assertSuccessful() + ->assertSee('außerhalb der Herstellerrezepturen'); + + // Im „alle"-Modus kein Hinweis (alles sichtbar) + $this->get(route('admin.inventory.raw-material-stock.index', ['scope' => 'all'])) + ->assertSuccessful() + ->assertDontSee('außerhalb der Herstellerrezepturen'); +}); + test('letzter netto-preis je lieferant kommt aus dem neuesten einkauf', function () { $user = ap10MakeUser(); $location = Location::factory()->create();
- {{ __('Keine aktiven Rohstoffe vorhanden.') }} + {{ $scope === 'recipe' ? __('Keine Rohstoffe aus Herstellerrezepturen vorhanden.') : __('Keine aktiven Rohstoffe vorhanden.') }}