Correct independent stock lot activation flow
This commit is contained in:
1 parent
9eb9cd6e0c
commit
3f15756596
5 files changed
+324
-48
No files matched your search
@@ -74,6 +74,7 @@ try {
|
||||
(int) ($data['lotId'] ?? 0),
|
||||
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown')
|
||||
);
|
||||
sync_open_lot_activation_selection($pdo, $productId);
|
||||
json_response(200, ['ok' => true, 'productId' => $productId, 'availableQty' => get_lager_product_available_qty($pdo, $productId)]);
|
||||
}
|
||||
|
||||
@@ -81,8 +82,22 @@ try {
|
||||
$productId = (int) ($data['productId'] ?? 0);
|
||||
$lotNumber = trim((string) ($data['lotNumber'] ?? ''));
|
||||
$lotId = (int) ($data['lotId'] ?? 0);
|
||||
if ($lotNumber === '' && $lotId <= 0) {
|
||||
throw new InvalidArgumentException('Naurua-Chargennummer fehlt');
|
||||
$manufacturerPartyId = $data['manufacturerPartyId'] ?? null;
|
||||
$manufacturerPartyId = $manufacturerPartyId === null || $manufacturerPartyId === '' ? null : (int) $manufacturerPartyId;
|
||||
$manufacturerLotNumber = trim((string) ($data['manufacturerLotNumber'] ?? ''));
|
||||
$mfgDate = trim((string) ($data['mfgDate'] ?? ''));
|
||||
$expiryDate = trim((string) ($data['expiryDate'] ?? ''));
|
||||
$purchaseDate = trim((string) ($data['purchaseDate'] ?? ''));
|
||||
$receiptQty = isset($data['receiptQty']) && $data['receiptQty'] !== '' ? (float) $data['receiptQty'] : 0.0;
|
||||
$targetQty = isset($data['targetQty']) && $data['targetQty'] !== '' ? (float) $data['targetQty'] : null;
|
||||
if ($lotNumber === '' || $manufacturerPartyId === null || $manufacturerLotNumber === '' || $mfgDate === '' || $expiryDate === '' || $purchaseDate === '') {
|
||||
throw new InvalidArgumentException('Alle Chargenfelder sind Pflichtfelder');
|
||||
}
|
||||
if ($lotId <= 0 && $receiptQty <= 0) {
|
||||
throw new InvalidArgumentException('Eingangsmenge muss groesser als 0 sein');
|
||||
}
|
||||
if ($lotId > 0 && $targetQty !== null && $targetQty <= 0) {
|
||||
throw new InvalidArgumentException('Menge muss groesser als 0 sein');
|
||||
}
|
||||
if ($lotId > 0) {
|
||||
$lotStmt = $pdo->prepare(
|
||||
@@ -97,20 +112,16 @@ try {
|
||||
throw new InvalidArgumentException('Offene Charge nicht gefunden');
|
||||
}
|
||||
} else {
|
||||
$lotId = get_or_create_open_lot($pdo, $productId);
|
||||
$lotId = create_open_lot($pdo, $productId);
|
||||
}
|
||||
$manufacturerPartyId = $data['manufacturerPartyId'] ?? null;
|
||||
$manufacturerPartyId = $manufacturerPartyId === null || $manufacturerPartyId === '' ? null : (int) $manufacturerPartyId;
|
||||
$receiptQty = isset($data['receiptQty']) && $data['receiptQty'] !== '' ? (float) $data['receiptQty'] : 0.0;
|
||||
$targetQty = isset($data['targetQty']) && $data['targetQty'] !== '' ? (float) $data['targetQty'] : null;
|
||||
$preparedLot = update_stock_lot_traceability(
|
||||
$pdo,
|
||||
$lotId,
|
||||
$manufacturerPartyId,
|
||||
isset($data['manufacturerLotNumber']) ? (string) $data['manufacturerLotNumber'] : null,
|
||||
isset($data['mfgDate']) ? (string) $data['mfgDate'] : null,
|
||||
isset($data['expiryDate']) ? (string) $data['expiryDate'] : null,
|
||||
isset($data['purchaseDate']) ? (string) $data['purchaseDate'] : null,
|
||||
$manufacturerLotNumber,
|
||||
$mfgDate,
|
||||
$expiryDate,
|
||||
$purchaseDate,
|
||||
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'),
|
||||
$receiptQty,
|
||||
0.0,
|
||||
@@ -119,11 +130,19 @@ try {
|
||||
$targetQty,
|
||||
$lotId > 0 && $lotNumber === ''
|
||||
);
|
||||
sync_open_lot_activation_selection($pdo, $productId);
|
||||
refresh_lager_stock_lot_forecast_for_product($pdo, $productId);
|
||||
$forecast = get_lager_product_forecast($pdo, $productId);
|
||||
json_response(200, ['ok' => true, 'productId' => $productId, 'lot' => $preparedLot, 'balance' => get_stock_lot_balance($pdo, $lotId), 'availableQty' => get_lager_product_available_qty($pdo, $productId), 'forecast' => $forecast]);
|
||||
}
|
||||
|
||||
if ($action === 'select_open_lot') {
|
||||
$productId = (int) ($data['productId'] ?? 0);
|
||||
$lotId = (int) ($data['lotId'] ?? 0);
|
||||
select_open_lot_for_activation($pdo, $productId, $lotId);
|
||||
json_response(200, ['ok' => true, 'productId' => $productId, 'selectedLotId' => $lotId]);
|
||||
}
|
||||
|
||||
json_response(422, ['ok' => false, 'error' => 'Unsupported action']);
|
||||
} catch (JsonException) {
|
||||
json_response(400, ['ok' => false, 'error' => 'Invalid JSON payload']);
|
||||
|
||||
+145
-19
@@ -205,6 +205,7 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
sl.mfg_date,
|
||||
sl.expiry_date,
|
||||
sl.purchase_date,
|
||||
sl.next_active,
|
||||
COALESCE(v.qty_in, 0) AS qty_in,
|
||||
COALESCE(v.qty_net, 0) AS qty_net
|
||||
FROM stock_lot sl
|
||||
@@ -223,9 +224,10 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
'manufacturer_name' => $openLot['manufacturer_name'],
|
||||
'mfg_date' => $openLot['mfg_date'],
|
||||
'expiry_date' => $openLot['expiry_date'],
|
||||
'purchase_date' => $openLot['purchase_date'],
|
||||
'qty_in' => (float) $openLot['qty_net'],
|
||||
'qty_net' => (float) $openLot['qty_net'],
|
||||
'purchase_date' => $openLot['purchase_date'],
|
||||
'qty_in' => (float) $openLot['qty_net'],
|
||||
'qty_net' => (float) $openLot['qty_net'],
|
||||
'next_active' => (bool) $openLot['next_active'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -466,6 +468,145 @@ function update_stock_lot_traceability(
|
||||
}
|
||||
}
|
||||
|
||||
function is_qualified_open_lot(PDO $pdo, int $lotId): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM stock_lot sl
|
||||
INNER JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id
|
||||
WHERE sl.id = :lot_id
|
||||
AND sl.status = 'open'
|
||||
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
|
||||
)"
|
||||
);
|
||||
$stmt->execute([':lot_id' => $lotId]);
|
||||
return (bool) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
function sync_open_lot_activation_selection(PDO $pdo, int $productId): void
|
||||
{
|
||||
$qualifiedStmt = $pdo->prepare(
|
||||
"SELECT sl.id
|
||||
FROM stock_lot sl
|
||||
INNER JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id
|
||||
WHERE sl.product_id = :product_id
|
||||
AND sl.status = 'open'
|
||||
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
|
||||
ORDER BY sl.id
|
||||
FOR UPDATE"
|
||||
);
|
||||
$qualifiedStmt->execute([':product_id' => $productId]);
|
||||
$qualifiedIds = array_map('intval', $qualifiedStmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
if (count($qualifiedIds) === 1) {
|
||||
$selectedId = $qualifiedIds[0];
|
||||
$clearStmt = $pdo->prepare(
|
||||
"UPDATE stock_lot
|
||||
SET next_active = FALSE, updated_at = NOW()
|
||||
WHERE product_id = :product_id AND status = 'open'"
|
||||
);
|
||||
$clearStmt->execute([':product_id' => $productId]);
|
||||
$selectStmt = $pdo->prepare(
|
||||
"UPDATE stock_lot
|
||||
SET next_active = TRUE, updated_at = NOW()
|
||||
WHERE id = :lot_id"
|
||||
);
|
||||
$selectStmt->execute([':lot_id' => $selectedId]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($qualifiedIds === []) {
|
||||
$clearStmt = $pdo->prepare(
|
||||
"UPDATE stock_lot
|
||||
SET next_active = FALSE, updated_at = NOW()
|
||||
WHERE product_id = :product_id AND status = 'open'"
|
||||
);
|
||||
$clearStmt->execute([':product_id' => $productId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$selectedStmt = $pdo->prepare(
|
||||
"SELECT id
|
||||
FROM stock_lot
|
||||
WHERE product_id = :product_id
|
||||
AND status = 'open'
|
||||
AND next_active = TRUE
|
||||
LIMIT 1
|
||||
FOR UPDATE"
|
||||
);
|
||||
$selectedStmt->execute([':product_id' => $productId]);
|
||||
$selectedId = $selectedStmt->fetchColumn();
|
||||
if ($selectedId !== false && in_array((int) $selectedId, $qualifiedIds, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$clearStmt = $pdo->prepare(
|
||||
"UPDATE stock_lot
|
||||
SET next_active = FALSE, updated_at = NOW()
|
||||
WHERE product_id = :product_id AND status = 'open'"
|
||||
);
|
||||
$clearStmt->execute([':product_id' => $productId]);
|
||||
}
|
||||
|
||||
function select_open_lot_for_activation(PDO $pdo, int $productId, int $lotId): void
|
||||
{
|
||||
if ($productId <= 0 || $lotId <= 0 || !is_qualified_open_lot($pdo, $lotId)) {
|
||||
throw new InvalidArgumentException('Nur eine qualifizierte offene Charge kann ausgewaehlt werden');
|
||||
}
|
||||
|
||||
$lotStmt = $pdo->prepare(
|
||||
"SELECT product_id, status
|
||||
FROM stock_lot
|
||||
WHERE id = :lot_id
|
||||
FOR UPDATE"
|
||||
);
|
||||
$lotStmt->execute([':lot_id' => $lotId]);
|
||||
$lot = $lotStmt->fetch();
|
||||
if (!is_array($lot) || (int) $lot['product_id'] !== $productId || (string) $lot['status'] !== 'open') {
|
||||
throw new InvalidArgumentException('Offene Charge nicht gefunden');
|
||||
}
|
||||
|
||||
$startedTransaction = !$pdo->inTransaction();
|
||||
if ($startedTransaction) {
|
||||
$pdo->beginTransaction();
|
||||
}
|
||||
try {
|
||||
$clearStmt = $pdo->prepare(
|
||||
"UPDATE stock_lot
|
||||
SET next_active = FALSE, updated_at = NOW()
|
||||
WHERE product_id = :product_id AND status = 'open'"
|
||||
);
|
||||
$clearStmt->execute([':product_id' => $productId]);
|
||||
$selectStmt = $pdo->prepare(
|
||||
"UPDATE stock_lot
|
||||
SET next_active = TRUE, updated_at = NOW()
|
||||
WHERE id = :lot_id"
|
||||
);
|
||||
$selectStmt->execute([':lot_id' => $lotId]);
|
||||
if ($startedTransaction) {
|
||||
$pdo->commit();
|
||||
}
|
||||
} catch (Throwable $error) {
|
||||
if ($startedTransaction && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
function get_current_lot_balance_for_update(PDO $pdo, int $productId): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
@@ -820,23 +961,8 @@ function get_or_create_virtual_shortage_lot(PDO $pdo, int $productId): int
|
||||
return (int) $newLotId;
|
||||
}
|
||||
|
||||
function get_or_create_open_lot(PDO $pdo, int $productId): int
|
||||
function create_open_lot(PDO $pdo, int $productId): int
|
||||
{
|
||||
$select = $pdo->prepare(
|
||||
"SELECT id
|
||||
FROM stock_lot
|
||||
WHERE product_id = :product_id
|
||||
AND status = 'open'
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
FOR UPDATE"
|
||||
);
|
||||
$select->execute([':product_id' => $productId]);
|
||||
$lotId = $select->fetchColumn();
|
||||
if ($lotId !== false) {
|
||||
return (int) $lotId;
|
||||
}
|
||||
|
||||
$insert = $pdo->prepare(
|
||||
"INSERT INTO stock_lot (product_id, status, created_at, updated_at)
|
||||
VALUES (:product_id, 'open', NOW(), NOW())
|
||||
|
||||
Reference in new issue
Block a user