Ergaenze offene Chargen und Fehlbestands-Hinweis

This commit is contained in:
gitea_admin committed 2026-09-06 14:09:32 +02:00
1 parent 0401a7ae2a
commit 43cb4b9885
3 files changed
+173 -4

No files matched your search

+27
View File
@@ -65,6 +65,33 @@ try {
json_response(200, ['ok' => true, 'lot' => $updatedLot, 'balance' => get_stock_lot_balance($pdo, (int) ($data['lotId'] ?? 0))]);
}
if ($action === 'prepare_lot') {
$productId = (int) ($data['productId'] ?? 0);
$lotNumber = trim((string) ($data['lotNumber'] ?? ''));
if ($lotNumber === '') {
throw new InvalidArgumentException('Naurua-Chargennummer fehlt');
}
$lotId = get_or_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;
$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,
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'),
$receiptQty,
0.0,
0.0,
$lotNumber
);
json_response(200, ['ok' => true, 'lot' => $preparedLot, 'balance' => get_stock_lot_balance($pdo, $lotId)]);
}
json_response(422, ['ok' => false, 'error' => 'Unsupported action']);
} catch (JsonException) {
json_response(400, ['ok' => false, 'error' => 'Invalid JSON payload']);
+93 -4
View File
@@ -87,6 +87,14 @@ function get_lager_product_overview(PDO $pdo): array
sl.purchase_date,
sl.sellout_date,
sl.sellout_forecast_status,
COALESCE((
SELECT SUM(a.qty)
FROM sales_order_line_lot_allocation a
INNER JOIN stock_lot virtual_lot ON virtual_lot.id = a.lot_id
WHERE virtual_lot.product_id = p.id
AND virtual_lot.status = 'virtual_shortage'
AND a.allocation_status = 'reserved'
), 0) AS virtual_shortage_qty,
(
SELECT virtual_lot.lot_number
FROM stock_lot virtual_lot
@@ -127,6 +135,7 @@ 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'),
'virtual_shortage_qty' => (float) ($row['virtual_shortage_qty'] ?? 0),
'virtual_lot_number' => $row['virtual_lot_number'],
],
$stmt->fetchAll()
@@ -176,8 +185,44 @@ function get_lager_product_overview(PDO $pdo): array
];
}
$openStmt = $pdo->query(
"SELECT
sl.product_id,
sl.id AS lot_id,
sl.lot_number,
sl.manufacturer_party_id,
sl.manufacturer_lot_number,
mp.name AS manufacturer_name,
sl.mfg_date,
sl.expiry_date,
sl.purchase_date,
COALESCE(v.qty_in, 0) AS qty_in,
COALESCE(v.qty_net, 0) AS qty_net
FROM stock_lot sl
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
WHERE sl.status = 'open'
ORDER BY sl.product_id, sl.id"
);
$openLotsByProduct = [];
foreach ($openStmt->fetchAll() as $openLot) {
$openLotsByProduct[(int) $openLot['product_id']][] = [
'lot_id' => (int) $openLot['lot_id'],
'lot_number' => $openLot['lot_number'],
'manufacturer_party_id' => $openLot['manufacturer_party_id'] === null ? null : (int) $openLot['manufacturer_party_id'],
'manufacturer_lot_number' => $openLot['manufacturer_lot_number'],
'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_in'],
'qty_net' => (float) $openLot['qty_net'],
];
}
foreach ($products as &$product) {
$product['historical_lots'] = $historicalLotsByProduct[$product['id']] ?? [];
$product['open_lots'] = $openLotsByProduct[$product['id']] ?? [];
}
unset($product);
@@ -195,7 +240,8 @@ function update_stock_lot_traceability(
string $changedBy,
float $receiptQty = 0.0,
float $adjustmentQty = 0.0,
float $outQty = 0.0
float $outQty = 0.0,
?string $lotNumber = null
): array {
if ($lotId <= 0) {
throw new InvalidArgumentException('Chargen-ID fehlt');
@@ -205,6 +251,10 @@ function update_stock_lot_traceability(
if ($manufacturerLotNumber === '') {
$manufacturerLotNumber = null;
}
$lotNumber = $lotNumber !== null ? trim($lotNumber) : null;
if ($lotNumber === '') {
throw new InvalidArgumentException('Naurua-Chargennummer fehlt');
}
if (!is_finite($receiptQty) || !is_finite($adjustmentQty) || !is_finite($outQty)) {
throw new InvalidArgumentException('Ungueltige Bewegungsmenge');
@@ -256,6 +306,9 @@ function update_stock_lot_traceability(
if ((string) $before['status'] === 'virtual_shortage') {
throw new InvalidArgumentException('Virtuelle Fehlbestandschargen koennen nicht gepflegt werden');
}
if ($lotNumber !== null && (string) $before['status'] !== 'open') {
throw new InvalidArgumentException('Nur offene Chargen koennen neu angelegt werden');
}
if ($manufacturerPartyId !== null) {
$manufacturerStmt = $pdo->prepare(
@@ -277,13 +330,14 @@ function update_stock_lot_traceability(
if ($manufacturerPartyId !== null && $manufacturerLotNumber === null && (string) $before['status'] !== 'open') {
throw new InvalidArgumentException('Hersteller-Chargennummer fehlt');
}
if (($receiptQty > 0 || $adjustmentQty !== 0.0 || $outQty > 0) && (string) $before['status'] !== 'current') {
throw new InvalidArgumentException('Lagerbewegungen koennen nur auf der aktiven Charge gebucht werden');
if (($adjustmentQty !== 0.0 || $outQty > 0) && (string) $before['status'] !== 'current') {
throw new InvalidArgumentException('Korrekturen und Abgaenge koennen nur auf der aktiven Charge gebucht werden');
}
$updateStmt = $pdo->prepare(
"UPDATE stock_lot
SET manufacturer_party_id = :manufacturer_party_id,
SET lot_number = COALESCE(:lot_number, lot_number),
manufacturer_party_id = :manufacturer_party_id,
manufacturer_lot_number = :manufacturer_lot_number,
mfg_date = :mfg_date,
expiry_date = :expiry_date,
@@ -292,6 +346,7 @@ function update_stock_lot_traceability(
WHERE id = :id"
);
$updateStmt->execute([
':lot_number' => $lotNumber,
':manufacturer_party_id' => $manufacturerPartyId,
':manufacturer_lot_number' => $manufacturerLotNumber,
':mfg_date' => $dates['mfg_date'],
@@ -301,6 +356,9 @@ function update_stock_lot_traceability(
]);
$after = $before;
if ($lotNumber !== null) {
$after['lot_number'] = $lotNumber;
}
$after['manufacturer_party_id'] = $manufacturerPartyId;
$after['manufacturer_lot_number'] = $manufacturerLotNumber;
$after['mfg_date'] = $dates['mfg_date'];
@@ -681,6 +739,37 @@ 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
{
$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())
RETURNING id"
);
$insert->execute([':product_id' => $productId]);
$newLotId = $insert->fetchColumn();
if ($newLotId === false) {
throw new RuntimeException('Offene Charge konnte nicht angelegt werden');
}
return (int) $newLotId;
}
function replace_virtual_lot_allocation(
PDO $pdo,
int $allocationId,