Add manufacturer lot traceability
This commit is contained in:
1 parent
2aadce17bd
commit
19ce29c3d0
10 files changed
+418
-14
No files matched your search
@@ -0,0 +1,69 @@
|
||||
<?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;
|
||||
$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')
|
||||
);
|
||||
json_response(200, ['ok' => true, 'lot' => $updatedLot]);
|
||||
}
|
||||
|
||||
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) {
|
||||
json_response(500, ['ok' => false, 'error' => 'Internal server error: ' . $error->getMessage()]);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/forecast.php';
|
||||
require_once __DIR__ . '/../../system/audit.php';
|
||||
|
||||
function get_default_location_ids(PDO $pdo): array
|
||||
{
|
||||
@@ -76,8 +77,13 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
p.uom,
|
||||
COALESCE(SUM(v.qty_net), 0) AS available_qty,
|
||||
sl.lot_number,
|
||||
sl.supplier_lot_number,
|
||||
sl.id AS lot_id,
|
||||
sl.manufacturer_party_id,
|
||||
sl.manufacturer_lot_number,
|
||||
mp.name AS manufacturer_name,
|
||||
sl.mfg_date,
|
||||
sl.expiry_date,
|
||||
sl.purchase_date,
|
||||
sl.sellout_date,
|
||||
sl.sellout_forecast_status,
|
||||
(
|
||||
@@ -92,11 +98,13 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
LEFT JOIN stock_lot sl
|
||||
ON sl.product_id = p.id
|
||||
AND sl.status = 'current'
|
||||
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 p.status = 'active'
|
||||
GROUP BY p.id, p.name, p.sku, p.uom, sl.lot_number, sl.supplier_lot_number,
|
||||
sl.expiry_date, sl.sellout_date, sl.sellout_forecast_status
|
||||
GROUP BY p.id, p.name, p.sku, p.uom, sl.id, sl.lot_number, sl.manufacturer_party_id,
|
||||
sl.manufacturer_lot_number, mp.name, sl.mfg_date, sl.expiry_date, sl.purchase_date,
|
||||
sl.sellout_date, sl.sellout_forecast_status
|
||||
ORDER BY p.name ASC, p.id ASC"
|
||||
);
|
||||
|
||||
@@ -107,9 +115,14 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
'sku' => (string) $row['sku'],
|
||||
'uom' => (string) $row['uom'],
|
||||
'available_qty' => (float) $row['available_qty'],
|
||||
'lot_id' => $row['lot_id'] === null ? null : (int) $row['lot_id'],
|
||||
'lot_number' => $row['lot_number'],
|
||||
'supplier_lot_number' => $row['supplier_lot_number'],
|
||||
'manufacturer_party_id' => $row['manufacturer_party_id'] === null ? null : (int) $row['manufacturer_party_id'],
|
||||
'manufacturer_lot_number' => $row['manufacturer_lot_number'],
|
||||
'manufacturer_name' => $row['manufacturer_name'],
|
||||
'mfg_date' => $row['mfg_date'],
|
||||
'expiry_date' => $row['expiry_date'],
|
||||
'purchase_date' => $row['purchase_date'],
|
||||
'sellout_date' => $row['sellout_date'],
|
||||
'sellout_forecast_status' => (string) ($row['sellout_forecast_status'] ?? 'no_demand'),
|
||||
'virtual_lot_number' => $row['virtual_lot_number'],
|
||||
@@ -118,6 +131,126 @@ function get_lager_product_overview(PDO $pdo): array
|
||||
);
|
||||
}
|
||||
|
||||
function update_stock_lot_traceability(
|
||||
PDO $pdo,
|
||||
int $lotId,
|
||||
?int $manufacturerPartyId,
|
||||
?string $manufacturerLotNumber,
|
||||
?string $mfgDate,
|
||||
?string $expiryDate,
|
||||
?string $purchaseDate,
|
||||
string $changedBy
|
||||
): array {
|
||||
if ($lotId <= 0) {
|
||||
throw new InvalidArgumentException('Chargen-ID fehlt');
|
||||
}
|
||||
|
||||
$manufacturerLotNumber = $manufacturerLotNumber !== null ? trim($manufacturerLotNumber) : null;
|
||||
if ($manufacturerLotNumber === '') {
|
||||
$manufacturerLotNumber = null;
|
||||
}
|
||||
|
||||
$dates = [
|
||||
'mfg_date' => $mfgDate,
|
||||
'expiry_date' => $expiryDate,
|
||||
'purchase_date' => $purchaseDate,
|
||||
];
|
||||
foreach ($dates as $field => $value) {
|
||||
if ($value !== null && $value !== '') {
|
||||
$parsed = DateTimeImmutable::createFromFormat('!Y-m-d', $value);
|
||||
$errors = DateTimeImmutable::getLastErrors();
|
||||
if ($parsed === false || (is_array($errors) && (($errors['warning_count'] ?? 0) > 0 || ($errors['error_count'] ?? 0) > 0))) {
|
||||
throw new InvalidArgumentException("Ungueltiges Datum fuer {$field}");
|
||||
}
|
||||
}
|
||||
$dates[$field] = $value !== null && $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
$startedTransaction = !$pdo->inTransaction();
|
||||
if ($startedTransaction) {
|
||||
$pdo->beginTransaction();
|
||||
}
|
||||
|
||||
try {
|
||||
$lotStmt = $pdo->prepare(
|
||||
"SELECT id, product_id, lot_number, manufacturer_party_id, manufacturer_lot_number,
|
||||
mfg_date, expiry_date, purchase_date, status
|
||||
FROM stock_lot
|
||||
WHERE id = :id
|
||||
FOR UPDATE"
|
||||
);
|
||||
$lotStmt->execute([':id' => $lotId]);
|
||||
$before = $lotStmt->fetch();
|
||||
if (!is_array($before)) {
|
||||
throw new RuntimeException('Charge nicht gefunden');
|
||||
}
|
||||
if ((string) $before['status'] === 'virtual_shortage') {
|
||||
throw new InvalidArgumentException('Virtuelle Fehlbestandschargen koennen nicht gepflegt werden');
|
||||
}
|
||||
|
||||
if ($manufacturerPartyId !== null) {
|
||||
$manufacturerStmt = $pdo->prepare(
|
||||
"SELECT p.id
|
||||
FROM party p
|
||||
INNER JOIN party_role pr ON pr.party_id = p.id AND pr.role_code = 'manufacturer'
|
||||
WHERE p.id = :id AND p.status = 'active'
|
||||
LIMIT 1"
|
||||
);
|
||||
$manufacturerStmt->execute([':id' => $manufacturerPartyId]);
|
||||
if ($manufacturerStmt->fetchColumn() === false) {
|
||||
throw new InvalidArgumentException('Hersteller ist nicht aktiv oder nicht als Hersteller gepflegt');
|
||||
}
|
||||
}
|
||||
|
||||
if ($manufacturerPartyId === null && $manufacturerLotNumber !== null) {
|
||||
throw new InvalidArgumentException('Hersteller-Chargennummer benoetigt einen Hersteller');
|
||||
}
|
||||
if ($manufacturerPartyId !== null && $manufacturerLotNumber === null && (string) $before['status'] !== 'open') {
|
||||
throw new InvalidArgumentException('Hersteller-Chargennummer fehlt');
|
||||
}
|
||||
|
||||
$updateStmt = $pdo->prepare(
|
||||
"UPDATE stock_lot
|
||||
SET manufacturer_party_id = :manufacturer_party_id,
|
||||
manufacturer_lot_number = :manufacturer_lot_number,
|
||||
mfg_date = :mfg_date,
|
||||
expiry_date = :expiry_date,
|
||||
purchase_date = :purchase_date,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id"
|
||||
);
|
||||
$updateStmt->execute([
|
||||
':manufacturer_party_id' => $manufacturerPartyId,
|
||||
':manufacturer_lot_number' => $manufacturerLotNumber,
|
||||
':mfg_date' => $dates['mfg_date'],
|
||||
':expiry_date' => $dates['expiry_date'],
|
||||
':purchase_date' => $dates['purchase_date'],
|
||||
':id' => $lotId,
|
||||
]);
|
||||
|
||||
$after = $before;
|
||||
$after['manufacturer_party_id'] = $manufacturerPartyId;
|
||||
$after['manufacturer_lot_number'] = $manufacturerLotNumber;
|
||||
$after['mfg_date'] = $dates['mfg_date'];
|
||||
$after['expiry_date'] = $dates['expiry_date'];
|
||||
$after['purchase_date'] = $dates['purchase_date'];
|
||||
system_write_audit_log($pdo, 'stock_lot', (string) $lotId, 'traceability_updated', $changedBy, $before, $after, [
|
||||
'module' => 'erp.lager',
|
||||
]);
|
||||
|
||||
if ($startedTransaction) {
|
||||
$pdo->commit();
|
||||
}
|
||||
|
||||
return $after;
|
||||
} 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(
|
||||
|
||||
Reference in new issue
Block a user