diff --git a/docs/modules/erp/lager/processes/main/erp.lager.stock_lot_forecast_refresh.md b/docs/modules/erp/lager/processes/main/erp.lager.stock_lot_forecast_refresh.md index c89238b..8d3f14b 100644 --- a/docs/modules/erp/lager/processes/main/erp.lager.stock_lot_forecast_refresh.md +++ b/docs/modules/erp/lager/processes/main/erp.lager.stock_lot_forecast_refresh.md @@ -23,7 +23,7 @@ Direkt ausgeführter Lager-Prozess mit zwei Triggern: synchron nach jeder erfolg 4. `allocated`- und `reserved`-Mengen nach Bestell- beziehungsweise OTC-Verkaufsdatum je Produkt und Kalendertag aggregieren. 5. Stornierte Auftrags- und Positionsmengen aus der Nachfrage herausrechnen. 6. Den gewichteten Tagesabsatz mit einer Halbwertszeit von 60 Tagen berechnen. -7. Das Leer-Datum der aktuellen Charge und – sofern vorhanden – der nächsten Charge getrennt aus dem jeweiligen physischen Bestand und derselben gewichteten Nachfrage berechnen. +7. Das Leer-Datum der aktuellen Charge aus ihrem physischen Bestand und der gewichteten Nachfrage berechnen. Das Leer-Datum der nächsten Charge wird mit derselben Nachfrage ab dem Leer-Datum der aktuellen Charge weitergerechnet; bei bereits leerer aktueller Charge beginnt es am aktuellen Datum. 8. Jede erfolgreiche Berechnung je Charge in `stock_lot_forecast_history` speichern und den jeweiligen Snapshot in `stock_lot` aktualisieren. 9. Im Cron Fehler einzelner Produkte sammeln und mit den übrigen Produkten fortfahren. diff --git a/modules/erp/lager/forecast.php b/modules/erp/lager/forecast.php index ff3ea50..36a5231 100644 --- a/modules/erp/lager/forecast.php +++ b/modules/erp/lager/forecast.php @@ -124,7 +124,12 @@ function lager_forecast_load_daily_sales(PDO $pdo, DateTimeImmutable $asOfDate, return $sales; } -function lager_forecast_calculate(DateTimeImmutable $asOfDate, float $physicalQty, array $dailySales): array +function lager_forecast_calculate( + DateTimeImmutable $asOfDate, + float $physicalQty, + array $dailySales, + ?DateTimeImmutable $forecastStartDate = null +): array { $lookbackDays = 365; $halfLifeDays = 60; @@ -176,8 +181,9 @@ function lager_forecast_calculate(DateTimeImmutable $asOfDate, float $physicalQt ? 'high' : ($salesDays >= 10 ? 'medium' : 'low'); $daysUntilDepleted = (int) ceil($physicalQty / $dailyDemand); + $forecastStartDate ??= $asOfDate; return [ - 'forecast_date' => $asOfDate->modify('+' . $daysUntilDepleted . ' days')->format('Y-m-d'), + 'forecast_date' => $forecastStartDate->modify('+' . $daysUntilDepleted . ' days')->format('Y-m-d'), 'forecast_status' => 'forecast', 'confidence' => $confidence, 'daily_demand' => $dailyDemand, 'sales_units' => $salesUnits, 'sales_days' => $salesDays, 'observed_span_days' => $observedSpanDays, 'lookback_days' => $lookbackDays, 'half_life_days' => $halfLifeDays, @@ -255,12 +261,17 @@ function refresh_lager_stock_lot_forecast_for_product(PDO $pdo, int $productId): $dailySales[$productId] ?? [] ); lager_forecast_persist($pdo, $lot, $forecast, $asOfDate); - if ($lot['next_lot_id'] !== null) { + if ($lot['next_lot_id'] !== null && $forecast['forecast_date'] !== null) { lager_forecast_persist($pdo, [ 'lot_id' => $lot['next_lot_id'], 'product_id' => $productId, 'physical_qty' => $lot['next_physical_qty'], - ], lager_forecast_calculate($asOfDate, (float) $lot['next_physical_qty'], $dailySales[$productId] ?? []), $asOfDate); + ], lager_forecast_calculate( + $asOfDate, + (float) $lot['next_physical_qty'], + $dailySales[$productId] ?? [], + new DateTimeImmutable($forecast['forecast_date']) + ), $asOfDate); } } @@ -287,12 +298,17 @@ function run_lager_stock_lot_forecast(PDO $pdo): array try { $forecast = lager_forecast_calculate($asOfDate, (float) $lot['physical_qty'], $dailySales[$productId] ?? []); lager_forecast_persist($pdo, $lot, $forecast, $asOfDate); - if ($lot['next_lot_id'] !== null) { + if ($lot['next_lot_id'] !== null && $forecast['forecast_date'] !== null) { lager_forecast_persist($pdo, [ 'lot_id' => $lot['next_lot_id'], 'product_id' => $productId, 'physical_qty' => $lot['next_physical_qty'], - ], lager_forecast_calculate($asOfDate, (float) $lot['next_physical_qty'], $dailySales[$productId] ?? []), $asOfDate); + ], lager_forecast_calculate( + $asOfDate, + (float) $lot['next_physical_qty'], + $dailySales[$productId] ?? [], + new DateTimeImmutable($forecast['forecast_date']) + ), $asOfDate); } $result['processed_products']++; } catch (Throwable $error) {