Auto-seed new current lot on lot switch to avoid allocation loop
This commit is contained in:
@@ -994,7 +994,7 @@ function get_current_lot_balance_for_update(PDO $pdo, int $productId): array
|
||||
];
|
||||
}
|
||||
|
||||
function switch_current_lot(PDO $pdo, int $productId, int $oldCurrentLotId): int
|
||||
function switch_current_lot(PDO $pdo, int $productId, int $oldCurrentLotId, int $storageLocationId): int
|
||||
{
|
||||
$closeStmt = $pdo->prepare(
|
||||
"UPDATE public.stock_lot
|
||||
@@ -1030,6 +1030,23 @@ function switch_current_lot(PDO $pdo, int $productId, int $oldCurrentLotId): int
|
||||
':auto_lot_number' => 'AUTO-' . $productId . '-' . (int) $newCurrentLotId,
|
||||
]);
|
||||
|
||||
$balStmt = $pdo->prepare('SELECT qty_net FROM public.v_stock_lot_balance WHERE stock_lot_id = :lot_id');
|
||||
$balStmt->execute([':lot_id' => (int) $newCurrentLotId]);
|
||||
$newCurrentQty = $balStmt->fetchColumn();
|
||||
$newCurrentQty = $newCurrentQty === false ? 0.0 : (float) $newCurrentQty;
|
||||
|
||||
// Auto-seed newly promoted current lot so allocation can continue without manual stock-in.
|
||||
if ($newCurrentQty <= 0.0000001) {
|
||||
insert_stock_move_in(
|
||||
$pdo,
|
||||
$productId,
|
||||
(int) $newCurrentLotId,
|
||||
200.0,
|
||||
$storageLocationId,
|
||||
"auto-seed-current-lot:product={$productId}:lot=" . (int) $newCurrentLotId
|
||||
);
|
||||
}
|
||||
|
||||
$createOpenStmt = $pdo->prepare(
|
||||
"INSERT INTO public.stock_lot (product_id, lot_number, status, created_at, updated_at)
|
||||
VALUES (:product_id, NULL, 'open', NOW(), NOW())"
|
||||
@@ -1152,6 +1169,24 @@ function reverse_existing_allocations_for_order(PDO $pdo, int $orderId, int $fal
|
||||
];
|
||||
}
|
||||
|
||||
function has_available_stock_for_product(PDO $pdo, int $productId, float $epsilon = 0.0000001): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT 1
|
||||
FROM public.stock_lot sl
|
||||
JOIN public.v_stock_lot_balance v ON v.stock_lot_id = sl.id
|
||||
WHERE sl.product_id = :product_id
|
||||
AND v.qty_net > :epsilon
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->execute([
|
||||
':product_id' => $productId,
|
||||
':epsilon' => $epsilon,
|
||||
]);
|
||||
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
function allocate_components_for_line(
|
||||
PDO $pdo,
|
||||
int $orderId,
|
||||
@@ -1169,6 +1204,9 @@ function allocate_components_for_line(
|
||||
];
|
||||
}
|
||||
|
||||
$savepoint = 'sp_alloc_line_' . $lineId;
|
||||
$pdo->exec("SAVEPOINT {$savepoint}");
|
||||
|
||||
$allocationInsert = $pdo->prepare(
|
||||
"INSERT INTO public.sales_order_line_lot_allocation (
|
||||
sales_order_line_id, product_id, lot_id, qty, allocation_status, stock_move_id, created_at, updated_at
|
||||
@@ -1188,7 +1226,13 @@ function allocate_components_for_line(
|
||||
while ($remaining > 0.0000001) {
|
||||
$guard++;
|
||||
if ($guard > 100) {
|
||||
throw new RuntimeException("Inventory allocation loop exceeded guard for product {$productId}");
|
||||
$pdo->exec("ROLLBACK TO SAVEPOINT {$savepoint}");
|
||||
$pdo->exec("RELEASE SAVEPOINT {$savepoint}");
|
||||
return [
|
||||
'allocated' => false,
|
||||
'reason' => "loop_guard_exceeded:product={$productId}",
|
||||
'allocations' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$current = get_current_lot_balance_for_update($pdo, $productId);
|
||||
@@ -1196,7 +1240,7 @@ function allocate_components_for_line(
|
||||
$available = (float) $current['qty_net'];
|
||||
|
||||
if ($available <= 0.0000001) {
|
||||
switch_current_lot($pdo, $productId, $lotId);
|
||||
switch_current_lot($pdo, $productId, $lotId, (int) $locations['storage']);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1231,6 +1275,8 @@ function allocate_components_for_line(
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->exec("RELEASE SAVEPOINT {$savepoint}");
|
||||
|
||||
return [
|
||||
'allocated' => true,
|
||||
'reason' => '',
|
||||
|
||||
Reference in New Issue
Block a user