Split inventory forecast by current and next lot
This commit is contained in:
1 parent
77f6fee5ac
commit
959586ecc9
5 files changed
+164
-59
No files matched your search
@@ -35,15 +35,25 @@ function lager_forecast_load_current_lots(PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->query(
|
||||
"SELECT p.id AS product_id, p.sku, sl.id AS lot_id,
|
||||
COALESCE(v.qty_net, 0) + COALESCE((
|
||||
SELECT SUM(GREATEST(COALESCE(open_v.qty_net, 0), 0))
|
||||
FROM stock_lot open_lot
|
||||
LEFT JOIN v_stock_lot_balance open_v ON open_v.stock_lot_id = open_lot.id
|
||||
WHERE open_lot.product_id = p.id AND open_lot.status = 'open'
|
||||
), 0) AS physical_qty
|
||||
COALESCE(v.qty_net, 0) AS physical_qty,
|
||||
next_sl.id AS next_lot_id,
|
||||
COALESCE(next_v.qty_net, 0) AS next_physical_qty
|
||||
FROM product p
|
||||
LEFT JOIN stock_lot sl ON sl.product_id = p.id AND sl.status = 'current'
|
||||
LEFT JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id
|
||||
LEFT JOIN stock_lot next_sl
|
||||
ON next_sl.product_id = p.id
|
||||
AND next_sl.status = 'open'
|
||||
AND next_sl.next_active = TRUE
|
||||
AND NULLIF(TRIM(next_sl.lot_number), '') IS NOT NULL
|
||||
AND next_sl.manufacturer_party_id IS NOT NULL
|
||||
AND NULLIF(TRIM(next_sl.manufacturer_lot_number), '') IS NOT NULL
|
||||
AND next_sl.mfg_date IS NOT NULL
|
||||
AND next_sl.expiry_date IS NOT NULL
|
||||
AND next_sl.purchase_date IS NOT NULL
|
||||
LEFT JOIN v_stock_lot_balance next_v
|
||||
ON next_v.stock_lot_id = next_sl.id
|
||||
AND next_v.qty_net > 0
|
||||
WHERE p.status = 'active'
|
||||
ORDER BY p.id"
|
||||
);
|
||||
@@ -54,15 +64,25 @@ function lager_forecast_load_current_lot(PDO $pdo, int $productId): ?array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT p.id AS product_id, p.sku, sl.id AS lot_id,
|
||||
COALESCE(v.qty_net, 0) + COALESCE((
|
||||
SELECT SUM(GREATEST(COALESCE(open_v.qty_net, 0), 0))
|
||||
FROM stock_lot open_lot
|
||||
LEFT JOIN v_stock_lot_balance open_v ON open_v.stock_lot_id = open_lot.id
|
||||
WHERE open_lot.product_id = p.id AND open_lot.status = 'open'
|
||||
), 0) AS physical_qty
|
||||
COALESCE(v.qty_net, 0) AS physical_qty,
|
||||
next_sl.id AS next_lot_id,
|
||||
COALESCE(next_v.qty_net, 0) AS next_physical_qty
|
||||
FROM product p
|
||||
LEFT JOIN stock_lot sl ON sl.product_id = p.id AND sl.status = 'current'
|
||||
LEFT JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id
|
||||
LEFT JOIN stock_lot next_sl
|
||||
ON next_sl.product_id = p.id
|
||||
AND next_sl.status = 'open'
|
||||
AND next_sl.next_active = TRUE
|
||||
AND NULLIF(TRIM(next_sl.lot_number), '') IS NOT NULL
|
||||
AND next_sl.manufacturer_party_id IS NOT NULL
|
||||
AND NULLIF(TRIM(next_sl.manufacturer_lot_number), '') IS NOT NULL
|
||||
AND next_sl.mfg_date IS NOT NULL
|
||||
AND next_sl.expiry_date IS NOT NULL
|
||||
AND next_sl.purchase_date IS NOT NULL
|
||||
LEFT JOIN v_stock_lot_balance next_v
|
||||
ON next_v.stock_lot_id = next_sl.id
|
||||
AND next_v.qty_net > 0
|
||||
WHERE p.status = 'active' AND p.id = :product_id
|
||||
LIMIT 1"
|
||||
);
|
||||
@@ -235,6 +255,13 @@ 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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function run_lager_stock_lot_forecast(PDO $pdo): array
|
||||
@@ -260,6 +287,13 @@ 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) {
|
||||
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);
|
||||
}
|
||||
$result['processed_products']++;
|
||||
} catch (Throwable $error) {
|
||||
$result['failed_products']++;
|
||||
|
||||
@@ -75,13 +75,7 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
p.name,
|
||||
p.sku,
|
||||
p.uom,
|
||||
COALESCE(SUM(v.qty_net), 0) + COALESCE((
|
||||
SELECT SUM(open_v.qty_net)
|
||||
FROM stock_lot open_sl
|
||||
INNER JOIN v_stock_lot_balance open_v ON open_v.stock_lot_id = open_sl.id
|
||||
WHERE open_sl.product_id = p.id
|
||||
AND open_sl.status = 'open'
|
||||
), 0) AS available_qty,
|
||||
COALESCE(v.qty_net, 0) AS available_qty,
|
||||
COALESCE(v.qty_net, 0) AS current_qty,
|
||||
COALESCE(v.qty_in, 0) AS qty_in,
|
||||
sl.lot_number,
|
||||
@@ -94,6 +88,11 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
sl.purchase_date,
|
||||
sl.sellout_date,
|
||||
sl.sellout_forecast_status,
|
||||
next_sl.id AS next_lot_id,
|
||||
COALESCE(next_v.qty_net, 0) AS next_qty,
|
||||
next_sl.sellout_date AS next_sellout_date,
|
||||
next_sl.sellout_forecast_status AS next_sellout_forecast_status,
|
||||
next_sl.expiry_date AS next_expiry_date,
|
||||
COALESCE((
|
||||
SELECT SUM(a.qty)
|
||||
FROM sales_order_line_lot_allocation a
|
||||
@@ -117,10 +116,25 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
LEFT JOIN party mp ON mp.id = sl.manufacturer_party_id
|
||||
LEFT JOIN v_stock_lot_balance v
|
||||
ON v.stock_lot_id = sl.id
|
||||
LEFT JOIN stock_lot next_sl
|
||||
ON next_sl.product_id = p.id
|
||||
AND next_sl.status = 'open'
|
||||
AND next_sl.next_active = TRUE
|
||||
AND NULLIF(TRIM(next_sl.lot_number), '') IS NOT NULL
|
||||
AND next_sl.manufacturer_party_id IS NOT NULL
|
||||
AND NULLIF(TRIM(next_sl.manufacturer_lot_number), '') IS NOT NULL
|
||||
AND next_sl.mfg_date IS NOT NULL
|
||||
AND next_sl.expiry_date IS NOT NULL
|
||||
AND next_sl.purchase_date IS NOT NULL
|
||||
LEFT JOIN v_stock_lot_balance next_v
|
||||
ON next_v.stock_lot_id = next_sl.id
|
||||
AND next_v.qty_net > 0
|
||||
WHERE p.status = 'active'
|
||||
GROUP BY p.id, p.name, p.sku, p.uom, sl.id, sl.lot_number, sl.manufacturer_party_id,
|
||||
sl.manufacturer_lot_number, mp.name, sl.mfg_date, sl.expiry_date, sl.purchase_date,
|
||||
sl.sellout_date, sl.sellout_forecast_status, v.qty_net, v.qty_in
|
||||
sl.sellout_date, sl.sellout_forecast_status, v.qty_net, v.qty_in,
|
||||
next_sl.id, next_v.qty_net, next_sl.sellout_date,
|
||||
next_sl.sellout_forecast_status, next_sl.expiry_date
|
||||
ORDER BY p.name ASC, p.id ASC"
|
||||
);
|
||||
|
||||
@@ -143,6 +157,11 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
'purchase_date' => $row['purchase_date'],
|
||||
'sellout_date' => $row['sellout_date'],
|
||||
'sellout_forecast_status' => (string) ($row['sellout_forecast_status'] ?? 'no_demand'),
|
||||
'next_lot_id' => $row['next_lot_id'] === null ? null : (int) $row['next_lot_id'],
|
||||
'next_qty' => (float) ($row['next_qty'] ?? 0),
|
||||
'next_sellout_date' => $row['next_sellout_date'],
|
||||
'next_sellout_forecast_status' => (string) ($row['next_sellout_forecast_status'] ?? 'no_demand'),
|
||||
'next_expiry_date' => $row['next_expiry_date'],
|
||||
'virtual_shortage_qty' => (float) ($row['virtual_shortage_qty'] ?? 0),
|
||||
'virtual_lot_number' => $row['virtual_lot_number'],
|
||||
],
|
||||
@@ -256,8 +275,27 @@ function get_lager_product_available_qty(PDO $pdo, int $productId): float
|
||||
function get_lager_product_forecast(PDO $pdo, int $productId): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT sellout_date, sellout_forecast_status, expiry_date
|
||||
"SELECT sellout_date, sellout_forecast_status, expiry_date,
|
||||
next_lot.sellout_date AS next_sellout_date,
|
||||
next_lot.sellout_forecast_status AS next_sellout_forecast_status,
|
||||
next_lot.expiry_date AS next_expiry_date
|
||||
FROM stock_lot
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT sl.sellout_date, sl.sellout_forecast_status, sl.expiry_date
|
||||
FROM stock_lot sl
|
||||
INNER JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id
|
||||
WHERE sl.product_id = stock_lot.product_id
|
||||
AND sl.status = 'open'
|
||||
AND sl.next_active = TRUE
|
||||
AND NULLIF(TRIM(sl.lot_number), '') IS NOT NULL
|
||||
AND sl.manufacturer_party_id IS NOT NULL
|
||||
AND NULLIF(TRIM(sl.manufacturer_lot_number), '') IS NOT NULL
|
||||
AND sl.mfg_date IS NOT NULL
|
||||
AND sl.expiry_date IS NOT NULL
|
||||
AND sl.purchase_date IS NOT NULL
|
||||
AND v.qty_net > 0
|
||||
LIMIT 1
|
||||
) next_lot ON TRUE
|
||||
WHERE product_id = :product_id
|
||||
AND status = 'current'
|
||||
LIMIT 1"
|
||||
@@ -271,6 +309,18 @@ function get_lager_product_forecast(PDO $pdo, int $productId): array
|
||||
'date' => $forecast['sellout_date'],
|
||||
'status' => (string) $forecast['sellout_forecast_status'],
|
||||
'expiryDate' => $forecast['expiry_date'],
|
||||
'current' => [
|
||||
'date' => $forecast['sellout_date'],
|
||||
'status' => (string) $forecast['sellout_forecast_status'],
|
||||
'expiryDate' => $forecast['expiry_date'],
|
||||
],
|
||||
'next' => $forecast['next_sellout_date'] === null && $forecast['next_expiry_date'] === null
|
||||
? null
|
||||
: [
|
||||
'date' => $forecast['next_sellout_date'],
|
||||
'status' => (string) ($forecast['next_sellout_forecast_status'] ?? 'no_demand'),
|
||||
'expiryDate' => $forecast['next_expiry_date'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user