197 lines
9.1 KiB
PHP
197 lines
9.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../../shared/db.php';
|
|
require_once __DIR__ . '/../../../shared/auth/service.php';
|
|
require_once __DIR__ . '/../../kontakte/service.php';
|
|
require_once __DIR__ . '/../service.php';
|
|
|
|
$env = expand_env_values(parse_env_file(__DIR__ . '/../../../../.env'));
|
|
|
|
try {
|
|
$pdo = connect_database($env);
|
|
auth_bootstrap_session();
|
|
$currentUser = auth_current_user($pdo);
|
|
if ($currentUser === null) {
|
|
json_response(401, ['ok' => false, 'error' => 'Unauthorized']);
|
|
}
|
|
|
|
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
|
json_response(405, ['ok' => false, 'error' => 'Method Not Allowed']);
|
|
}
|
|
|
|
$jsonInput = file_get_contents('php://input');
|
|
if ($jsonInput === false || trim($jsonInput) === '') {
|
|
json_response(400, ['ok' => false, 'error' => 'Empty payload']);
|
|
}
|
|
|
|
$data = json_decode($jsonInput, true, 512, JSON_THROW_ON_ERROR);
|
|
if (!is_array($data) || !auth_validate_csrf_token($data['csrfToken'] ?? null)) {
|
|
json_response(403, ['ok' => false, 'error' => 'Invalid security token']);
|
|
}
|
|
|
|
$action = trim((string) ($data['action'] ?? ''));
|
|
if ($action === 'create_manufacturer') {
|
|
$manufacturer = create_manufacturer(
|
|
$pdo,
|
|
(string) ($data['name'] ?? ''),
|
|
isset($data['email']) ? (string) $data['email'] : null,
|
|
isset($data['phone']) ? (string) $data['phone'] : null
|
|
);
|
|
json_response(201, ['ok' => true, 'manufacturer' => $manufacturer]);
|
|
}
|
|
|
|
if ($action === 'update_lot') {
|
|
$manufacturerPartyId = $data['manufacturerPartyId'] ?? null;
|
|
$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),
|
|
$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,
|
|
$adjustmentQty,
|
|
$outQty
|
|
);
|
|
$productId = (int) $updatedLot['product_id'];
|
|
if (($updatedLot['status'] ?? '') === 'open') {
|
|
sync_open_lot_activation_selection($pdo, $productId);
|
|
$updatedLot['next_active'] = is_open_lot_selected_for_activation($pdo, (int) $updatedLot['id']);
|
|
} else {
|
|
ensure_current_lot_and_reconcile_virtual_shortage(
|
|
$pdo,
|
|
$productId,
|
|
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'),
|
|
'Verrechnung nach Pflege der aktiven Charge'
|
|
);
|
|
}
|
|
refresh_lager_stock_lot_forecast_for_product($pdo, $productId);
|
|
$forecast = get_lager_product_forecast($pdo, $productId);
|
|
$currentLot = get_current_lot_snapshot($pdo, $productId);
|
|
json_response(200, ['ok' => true, 'productId' => $productId, 'lot' => $currentLot, 'balance' => [
|
|
'qty_net' => $currentLot['qty_net'],
|
|
'qty_in' => $currentLot['qty_in'],
|
|
'qty_out' => $currentLot['qty_out'],
|
|
], 'forecast' => $forecast]);
|
|
}
|
|
|
|
if ($action === 'delete_open_lot') {
|
|
$productId = delete_open_stock_lot(
|
|
$pdo,
|
|
(int) ($data['lotId'] ?? 0),
|
|
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown')
|
|
);
|
|
sync_open_lot_activation_selection($pdo, $productId);
|
|
ensure_current_lot_and_reconcile_virtual_shortage(
|
|
$pdo,
|
|
$productId,
|
|
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'),
|
|
'Verrechnung nach Loeschung einer offenen Charge'
|
|
);
|
|
json_response(200, ['ok' => true, 'productId' => $productId, 'currentLot' => get_current_lot_snapshot($pdo, $productId), 'availableQty' => get_lager_product_available_qty($pdo, $productId)]);
|
|
}
|
|
|
|
if ($action === 'prepare_lot') {
|
|
$productId = (int) ($data['productId'] ?? 0);
|
|
$lotNumber = trim((string) ($data['lotNumber'] ?? ''));
|
|
$lotId = (int) ($data['lotId'] ?? 0);
|
|
$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;
|
|
$pdo->beginTransaction();
|
|
if ($lotId > 0) {
|
|
$lotStmt = $pdo->prepare(
|
|
"SELECT id
|
|
FROM stock_lot
|
|
WHERE id = :lot_id
|
|
AND product_id = :product_id
|
|
AND status = 'open'"
|
|
);
|
|
$lotStmt->execute([':lot_id' => $lotId, ':product_id' => $productId]);
|
|
if ($lotStmt->fetchColumn() === false) {
|
|
throw new InvalidArgumentException('Offene Charge nicht gefunden');
|
|
}
|
|
} else {
|
|
$lotId = create_open_lot($pdo, $productId);
|
|
}
|
|
$preparedLot = update_stock_lot_traceability(
|
|
$pdo,
|
|
$lotId,
|
|
$manufacturerPartyId,
|
|
$manufacturerLotNumber,
|
|
$mfgDate,
|
|
$expiryDate,
|
|
$purchaseDate,
|
|
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'),
|
|
$receiptQty,
|
|
0.0,
|
|
0.0,
|
|
$lotNumber,
|
|
$targetQty,
|
|
$lotNumber === ''
|
|
);
|
|
sync_open_lot_activation_selection($pdo, $productId);
|
|
if (promote_open_lot_when_virtual_shortage_is_current($pdo, $productId, $lotId, (string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'))) {
|
|
$preparedLot['status'] = 'current';
|
|
$preparedLot['next_active'] = false;
|
|
} else {
|
|
$preparedLot['next_active'] = is_open_lot_selected_for_activation($pdo, $lotId);
|
|
}
|
|
refresh_lager_stock_lot_forecast_for_product($pdo, $productId);
|
|
$forecast = get_lager_product_forecast($pdo, $productId);
|
|
$pdo->commit();
|
|
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);
|
|
ensure_current_lot_and_reconcile_virtual_shortage(
|
|
$pdo,
|
|
$productId,
|
|
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown'),
|
|
'Verrechnung nach Auswahl der naechsten aktiven Charge'
|
|
);
|
|
refresh_lager_stock_lot_forecast_for_product($pdo, $productId);
|
|
$currentLot = get_current_lot_snapshot($pdo, $productId);
|
|
json_response(200, ['ok' => true, 'productId' => $productId, 'selectedLotId' => $lotId, 'currentLot' => $currentLot, 'forecast' => get_lager_product_forecast($pdo, $productId)]);
|
|
}
|
|
|
|
json_response(422, ['ok' => false, 'error' => 'Unsupported action']);
|
|
} catch (JsonException) {
|
|
json_response(400, ['ok' => false, 'error' => 'Invalid JSON payload']);
|
|
} catch (InvalidArgumentException $error) {
|
|
json_response(422, ['ok' => false, 'error' => $error->getMessage()]);
|
|
} catch (Throwable $error) {
|
|
if (isset($pdo) && $pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
try {
|
|
system_write_audit_log($pdo, 'lager_api', 'lager-charge.php', 'request_failed', $currentUser['username'] ?? $currentUser['email'] ?? 'unknown', [], [], [
|
|
'action' => $action ?? '',
|
|
'product_id' => $data['productId'] ?? null,
|
|
'lot_id' => $data['lotId'] ?? null,
|
|
'exception_class' => get_class($error),
|
|
'exception_message' => $error->getMessage(),
|
|
]);
|
|
} catch (Throwable) {
|
|
error_log('lager-charge API failure audit logging failed');
|
|
}
|
|
json_response(500, ['ok' => false, 'error' => 'Interner Serverfehler']);
|
|
}
|