Warenwirtschaft: Anforderungsrunde 12.06. — Plan V5.0 + AP-26/AP-25/AP-22
Neue Anforderungen (docs/) interpretiert und als Entwicklungsplan V5.0 (AP-20 bis AP-28) aufgenommen; erste drei Pakete umgesetzt: AP-26 Ausschuss-Gründe konfigurierbar: - Stammdaten-Tabelle disposal_reasons + CRUD unter Einstellungen → Allgemein - StockDisposalController liest aktive DB-Gründe statt hartkodierter Liste - Seeder übernimmt die bisherigen 6 Gründe idempotent AP-25 Lieferbestand — Datum statt Tage: - "Nicht vorrätig" wird über Datepicker "Wieder lieferbar ab" gepflegt; Resttage-Hinweis zählt täglich automatisch herunter - Interne Bestellliste wieder kaufbar: Hinweis erscheint zusätzlich zu den Mengen-Buttons (VP entscheidet selbst) AP-22 Produktbestand-Erweiterungen: - Default-Sortierung nach Dringlichkeit, Status-Kopf toggelt - Alle vier Status-Kacheln als Filter klickbar - Neue Spalte "Verbrauch/Monat" (Ø Abgänge der letzten 6 Monate) - Produkt-Flag "Im Produktbestand anzeigen" (products.show_in_product_stock) Tests: 77 grün (DisposalReasonSettings 8, ProductOutOfStock 8, ProductStock 13 + Regression). Hinweise-Doku + Plan-Protokoll fortgeschrieben; nächster Schritt laut Plan: AP-21 (INCI-Erweiterungen). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a8f6fef38e
commit
e53201f229
32 changed files with 1377 additions and 94 deletions
|
|
@ -106,6 +106,37 @@ class ProductStockService
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Durchschnittlicher Verbrauch pro Monat je Produkt (Ø der letzten X Monate, Default 6).
|
||||
*
|
||||
* „Verbrauch" = alle Abgänge (direction=out) außer Produktions-Gegenbuchungen
|
||||
* (source=production sind Korrekturen der Eingangs-Buchung, kein echter Abgang).
|
||||
* Mit AP-13 fließen Verkaufs-Abgänge (source=sale) automatisch mit ein.
|
||||
*
|
||||
* @param array<int, int>|null $productIds
|
||||
* @return array<int, float> [product_id => Stück/Monat]
|
||||
*/
|
||||
public function monthlyConsumptionByProduct(?array $productIds = null, int $months = 6): array
|
||||
{
|
||||
$months = max(1, $months);
|
||||
|
||||
$rows = ProductStockMovement::query()
|
||||
->when($productIds !== null, fn ($q) => $q->whereIn('product_id', $productIds))
|
||||
->where('direction', 'out')
|
||||
->where('source', '!=', 'production')
|
||||
->where('created_at', '>=', now()->subMonths($months))
|
||||
->selectRaw('product_id, SUM(quantity) as consumed')
|
||||
->groupBy('product_id')
|
||||
->pluck('consumed', 'product_id');
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $id => $consumed) {
|
||||
$result[(int) $id] = round(((int) $consumed) / $months, 1);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestands-Status: "critical" (rot) ≤ kritischer Schwellwert, "warning" (gelb) ≤ Meldebestand, sonst "ok".
|
||||
*/
|
||||
|
|
@ -129,6 +160,7 @@ class ProductStockService
|
|||
$products = Product::query()
|
||||
->where('active', true)
|
||||
->where('is_set', false)
|
||||
->where('show_in_product_stock', true)
|
||||
->whereNull('main_product_id')
|
||||
->whereNotNull('critical_product_stock')
|
||||
->get(['id', 'critical_product_stock']);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue