Ergaenze Chargenformular um Lagerbewegungen

This commit is contained in:
gitea_admin committed 2026-09-06 13:49:10 +02:00
1 parent 1e5ededa00
commit 5908f71a39
3 files changed
+147 -5

No files matched your search

+8 -2
View File
@@ -46,6 +46,9 @@ try {
$manufacturerPartyId = $manufacturerPartyId === null || $manufacturerPartyId === ''
? null
: (int) $manufacturerPartyId;
$receiptQty = isset($data['receiptQty']) && $data['receiptQty'] !== '' ? (float) $data['receiptQty'] : 0.0;
$adjustmentQty = isset($data['adjustmentQty']) && $data['adjustmentQty'] !== '' ? (float) $data['adjustmentQty'] : 0.0;
$outQty = isset($data['outQty']) && $data['outQty'] !== '' ? (float) $data['outQty'] : 0.0;
$updatedLot = update_stock_lot_traceability(
$pdo,
(int) ($data['lotId'] ?? 0),
@@ -54,9 +57,12 @@ try {
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')
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'),
$receiptQty,
$adjustmentQty,
$outQty
);
json_response(200, ['ok' => true, 'lot' => $updatedLot]);
json_response(200, ['ok' => true, 'lot' => $updatedLot, 'balance' => get_stock_lot_balance($pdo, (int) ($data['lotId'] ?? 0))]);
}
json_response(422, ['ok' => false, 'error' => 'Unsupported action']);
+130 -1
View File
@@ -192,7 +192,10 @@ function update_stock_lot_traceability(
?string $mfgDate,
?string $expiryDate,
?string $purchaseDate,
string $changedBy
string $changedBy,
float $receiptQty = 0.0,
float $adjustmentQty = 0.0,
float $outQty = 0.0
): array {
if ($lotId <= 0) {
throw new InvalidArgumentException('Chargen-ID fehlt');
@@ -203,6 +206,16 @@ function update_stock_lot_traceability(
$manufacturerLotNumber = null;
}
if (!is_finite($receiptQty) || !is_finite($adjustmentQty) || !is_finite($outQty)) {
throw new InvalidArgumentException('Ungueltige Bewegungsmenge');
}
if ($receiptQty < 0 || $outQty < 0) {
throw new InvalidArgumentException('Zugang und Abgang duerfen nicht negativ sein');
}
if ($adjustmentQty !== 0.0 && abs($adjustmentQty) < 0.0001) {
$adjustmentQty = 0.0;
}
$dates = [
'mfg_date' => $mfgDate,
'expiry_date' => $expiryDate,
@@ -261,6 +274,9 @@ 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');
}
$updateStmt = $pdo->prepare(
"UPDATE stock_lot
@@ -291,6 +307,10 @@ function update_stock_lot_traceability(
'module' => 'erp.lager',
]);
if ($receiptQty > 0 || $adjustmentQty !== 0.0 || $outQty > 0) {
book_stock_lot_movements($pdo, (int) $before['product_id'], $lotId, $receiptQty, $adjustmentQty, $outQty);
}
if ($startedTransaction) {
$pdo->commit();
}
@@ -333,6 +353,30 @@ function get_current_lot_balance_for_update(PDO $pdo, int $productId): array
];
}
function get_stock_lot_balance(PDO $pdo, int $lotId): array
{
$stmt = $pdo->prepare(
"SELECT
COALESCE(v.qty_net, 0) AS qty_net,
COALESCE(v.qty_in, 0) AS qty_in,
COALESCE(v.qty_out, 0) AS qty_out
FROM stock_lot sl
LEFT JOIN v_stock_lot_balance v ON v.stock_lot_id = sl.id
WHERE sl.id = :lot_id"
);
$stmt->execute([':lot_id' => $lotId]);
$row = $stmt->fetch();
if (!is_array($row)) {
throw new RuntimeException('Charge nicht gefunden');
}
return [
'qty_net' => (float) $row['qty_net'],
'qty_in' => (float) $row['qty_in'],
'qty_out' => (float) $row['qty_out'],
];
}
function insert_stock_move_out(
PDO $pdo,
int $productId,
@@ -401,6 +445,91 @@ function insert_stock_move_in(
return (int) $id;
}
function insert_stock_move_adjustment(
PDO $pdo,
int $productId,
int $lotId,
float $qty,
?int $fromLocationId,
?int $toLocationId,
string $note
): int {
$stmt = $pdo->prepare(
"INSERT INTO stock_move (
product_id, lot_id, from_location_id, to_location_id, qty, move_type, move_date, note, created_at, updated_at
) VALUES (
:product_id, :lot_id, :from_location_id, :to_location_id, :qty, 'adjustment', NOW(), :note, NOW(), NOW()
)
RETURNING id"
);
$stmt->execute([
':product_id' => $productId,
':lot_id' => $lotId,
':from_location_id' => $fromLocationId,
':to_location_id' => $toLocationId,
':qty' => $qty,
':note' => $note,
]);
$id = $stmt->fetchColumn();
if ($id === false) {
throw new RuntimeException('Konnte Bestandskorrektur nicht schreiben');
}
return (int) $id;
}
function book_stock_lot_movements(
PDO $pdo,
int $productId,
int $lotId,
float $receiptQty,
float $adjustmentQty,
float $outQty
): void {
$locations = get_default_location_ids($pdo);
$balanceStmt = $pdo->prepare(
"SELECT COALESCE(
SUM(CASE
WHEN move_type = 'in' THEN qty
WHEN move_type = 'out' THEN -qty
WHEN move_type = 'adjustment' AND to_location_id IS NOT NULL AND from_location_id IS NULL THEN qty
WHEN move_type = 'adjustment' AND from_location_id IS NOT NULL AND to_location_id IS NULL THEN -qty
ELSE 0
END), 0
)
FROM stock_move
WHERE lot_id = :lot_id"
);
$balanceStmt->execute([':lot_id' => $lotId]);
$balance = (float) $balanceStmt->fetchColumn();
if ($receiptQty > 0) {
insert_stock_move_in($pdo, $productId, $lotId, $receiptQty, $locations['storage'], $locations['storage'], 'Manueller Lagerzugang via Chargenformular');
$balance += $receiptQty;
}
if ($adjustmentQty !== 0.0) {
if ($adjustmentQty > 0) {
insert_stock_move_adjustment($pdo, $productId, $lotId, $adjustmentQty, null, $locations['storage'], 'Manuelle Bestandskorrektur via Chargenformular');
} else {
$correctionQty = abs($adjustmentQty);
if ($correctionQty > $balance) {
throw new InvalidArgumentException('Bestandskorrektur wuerde den Chargensaldo negativ machen');
}
insert_stock_move_adjustment($pdo, $productId, $lotId, $correctionQty, $locations['storage'], null, 'Manuelle Bestandskorrektur via Chargenformular');
}
$balance += $adjustmentQty;
}
if ($outQty > $balance) {
throw new InvalidArgumentException('Lagerabgang ist groesser als der aktuelle Chargenbestand');
}
if ($outQty > 0) {
insert_stock_move_out($pdo, $productId, $lotId, $outQty, $locations['storage'], $locations['dispatch'], 'Manueller Lagerabgang via Chargenformular');
}
}
function switch_current_lot(PDO $pdo, int $productId, int $oldCurrentLotId, int $storageLocationId): int
{
$closeStmt = $pdo->prepare(