Add documents to stock lots

This commit is contained in:
gitea_admin committed 2026-09-08 11:38:10 +02:00
1 parent e47990dfe0
commit 2bec934732
5 files changed
+294 -2

No files matched your search

+36
View File
@@ -20,6 +20,32 @@ try {
json_response(405, ['ok' => false, 'error' => 'Method Not Allowed']);
}
$contentType = (string) ($_SERVER['CONTENT_TYPE'] ?? '');
if (str_starts_with($contentType, 'multipart/form-data')) {
if (!auth_validate_csrf_token($_POST['csrfToken'] ?? null)) {
json_response(403, ['ok' => false, 'error' => 'Invalid security token']);
}
if (($_POST['action'] ?? '') !== 'upload_document') {
json_response(422, ['ok' => false, 'error' => 'Unsupported action']);
}
$file = $_FILES['document'] ?? null;
if (!is_array($file)) {
throw new InvalidArgumentException('Dokumentdatei fehlt');
}
$document = upload_stock_lot_document(
$pdo,
(int) ($_POST['lotId'] ?? 0),
$file,
(string) ($_POST['comment'] ?? ''),
(string) ($currentUser['username'] ?? $currentUser['email'] ?? 'unknown')
);
json_response(201, ['ok' => true, 'document' => [
'id' => (int) $document['id'],
'original_filename' => (string) $document['original_filename'],
'comment' => $document['comment'] === null ? '' : (string) $document['comment'],
]]);
}
$jsonInput = file_get_contents('php://input');
if ($jsonInput === false || trim($jsonInput) === '') {
json_response(400, ['ok' => false, 'error' => 'Empty payload']);
@@ -77,6 +103,16 @@ try {
json_response(200, ['ok' => true, 'productId' => $productId, 'availableQty' => get_lager_product_available_qty($pdo, $productId)]);
}
if ($action === 'update_document_comment') {
update_stock_lot_document_comment($pdo, (int) ($data['documentId'] ?? 0), (string) ($data['comment'] ?? ''));
json_response(200, ['ok' => true]);
}
if ($action === 'delete_document') {
delete_stock_lot_document($pdo, (int) ($data['documentId'] ?? 0));
json_response(200, ['ok' => true]);
}
if ($action === 'prepare_lot') {
$productId = (int) ($data['productId'] ?? 0);
$lotNumber = trim((string) ($data['lotNumber'] ?? ''));
+158
View File
@@ -235,6 +235,32 @@ function get_lager_product_overview(PDO $pdo): array
}
unset($product);
$lotIds = [];
foreach ($products as $product) {
if (!empty($product['lot_id'])) {
$lotIds[] = (int) $product['lot_id'];
}
foreach ($product['historical_lots'] as $lot) {
$lotIds[] = (int) ($lot['lot_id'] ?? 0);
}
foreach ($product['open_lots'] as $lot) {
$lotIds[] = (int) ($lot['lot_id'] ?? 0);
}
}
$documentsByLot = get_stock_lot_documents_by_lot_ids($pdo, $lotIds);
foreach ($products as &$product) {
$product['documents'] = $documentsByLot[(int) ($product['lot_id'] ?? 0)] ?? [];
foreach ($product['historical_lots'] as &$lot) {
$lot['documents'] = $documentsByLot[(int) ($lot['lot_id'] ?? 0)] ?? [];
}
unset($lot);
foreach ($product['open_lots'] as &$lot) {
$lot['documents'] = $documentsByLot[(int) ($lot['lot_id'] ?? 0)] ?? [];
}
unset($lot);
}
unset($product);
return $products;
}
@@ -251,6 +277,138 @@ function get_lager_product_available_qty(PDO $pdo, int $productId): float
return (float) $stmt->fetchColumn();
}
function get_stock_lot_documents_by_lot_ids(PDO $pdo, array $lotIds): array
{
$lotIds = array_values(array_unique(array_filter(array_map('intval', $lotIds), static fn (int $id): bool => $id > 0)));
if ($lotIds === []) {
return [];
}
$placeholders = implode(',', array_fill(0, count($lotIds), '?'));
$stmt = $pdo->prepare(
"SELECT id, stock_lot_id, original_filename, mime_type, size_bytes, comment, uploaded_at
FROM stock_lot_document
WHERE stock_lot_id IN ({$placeholders})
ORDER BY stock_lot_id, id"
);
$stmt->execute($lotIds);
$documents = [];
foreach ($stmt->fetchAll() as $row) {
$documents[(int) $row['stock_lot_id']][] = [
'id' => (int) $row['id'],
'original_filename' => (string) $row['original_filename'],
'mime_type' => (string) $row['mime_type'],
'size_bytes' => (int) $row['size_bytes'],
'comment' => $row['comment'] === null ? '' : (string) $row['comment'],
'uploaded_at' => (string) $row['uploaded_at'],
];
}
return $documents;
}
function get_stock_lot_document(PDO $pdo, int $documentId): array
{
$stmt = $pdo->prepare(
'SELECT id, stock_lot_id, original_filename, stored_filename, mime_type, size_bytes, comment
FROM stock_lot_document WHERE id = :id'
);
$stmt->execute([':id' => $documentId]);
$document = $stmt->fetch();
if (!is_array($document)) {
throw new RuntimeException('Dokument nicht gefunden');
}
return $document;
}
function update_stock_lot_document_comment(PDO $pdo, int $documentId, string $comment): void
{
$document = get_stock_lot_document($pdo, $documentId);
$statusStmt = $pdo->prepare('SELECT status FROM stock_lot WHERE id = :id');
$statusStmt->execute([':id' => (int) $document['stock_lot_id']]);
$status = $statusStmt->fetchColumn();
if ($status !== 'current' && $status !== 'open') {
throw new InvalidArgumentException('Dokumente vergangener Chargen können nicht bearbeitet werden');
}
$stmt = $pdo->prepare('UPDATE stock_lot_document SET comment = :comment WHERE id = :id');
$stmt->execute([':comment' => trim($comment) === '' ? null : trim($comment), ':id' => $documentId]);
if ($stmt->rowCount() !== 1) {
throw new RuntimeException('Dokument nicht gefunden');
}
}
function delete_stock_lot_document(PDO $pdo, int $documentId): void
{
$document = get_stock_lot_document($pdo, $documentId);
$statusStmt = $pdo->prepare('SELECT status FROM stock_lot WHERE id = :id');
$statusStmt->execute([':id' => (int) $document['stock_lot_id']]);
$status = $statusStmt->fetchColumn();
if ($status !== 'current' && $status !== 'open') {
throw new InvalidArgumentException('Dokumente vergangener Chargen können nicht gelöscht werden');
}
$stmt = $pdo->prepare('DELETE FROM stock_lot_document WHERE id = :id');
$stmt->execute([':id' => $documentId]);
$path = dirname(__DIR__, 3) . '/runtime/uploads/lager/' . basename((string) $document['stored_filename']);
if (is_file($path) && !unlink($path)) {
throw new RuntimeException('Dokumentdatei konnte nicht gelöscht werden');
}
}
function upload_stock_lot_document(PDO $pdo, int $lotId, array $file, string $comment, string $uploadedBy): array
{
if ($lotId <= 0 || !is_uploaded_file((string) ($file['tmp_name'] ?? ''))) {
throw new InvalidArgumentException('Ungültige Dokumentdatei');
}
$error = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
if ($error !== UPLOAD_ERR_OK) {
throw new InvalidArgumentException('Dokument konnte nicht hochgeladen werden');
}
$size = (int) ($file['size'] ?? 0);
if ($size <= 0 || $size > 52428800) {
throw new InvalidArgumentException('Dokument darf maximal 50 MB groß sein');
}
$originalFilename = basename((string) ($file['name'] ?? ''));
if ($originalFilename === '') {
throw new InvalidArgumentException('Dateiname fehlt');
}
$lotStmt = $pdo->prepare("SELECT id FROM stock_lot WHERE id = :id AND status IN ('current', 'open')");
$lotStmt->execute([':id' => $lotId]);
if ($lotStmt->fetchColumn() === false) {
throw new InvalidArgumentException('Charge nicht gefunden');
}
$directory = dirname(__DIR__, 3) . '/runtime/uploads/lager';
if (!is_dir($directory) && !mkdir($directory, 0770, true) && !is_dir($directory)) {
throw new RuntimeException('Dokumentenverzeichnis konnte nicht erstellt werden');
}
$storedFilename = bin2hex(random_bytes(16));
$targetPath = $directory . '/' . $storedFilename;
if (!move_uploaded_file((string) $file['tmp_name'], $targetPath)) {
throw new RuntimeException('Dokument konnte nicht gespeichert werden');
}
$mimeType = (new finfo(FILEINFO_MIME_TYPE))->file($targetPath) ?: 'application/octet-stream';
try {
$stmt = $pdo->prepare(
'INSERT INTO stock_lot_document
(stock_lot_id, original_filename, stored_filename, mime_type, size_bytes, comment, uploaded_by)
VALUES (:lot_id, :original_filename, :stored_filename, :mime_type, :size_bytes, :comment, :uploaded_by)
RETURNING id'
);
$stmt->execute([
':lot_id' => $lotId,
':original_filename' => $originalFilename,
':stored_filename' => $storedFilename,
':mime_type' => $mimeType,
':size_bytes' => $size,
':comment' => trim($comment) === '' ? null : trim($comment),
':uploaded_by' => $uploadedBy,
]);
return get_stock_lot_document($pdo, (int) $stmt->fetchColumn());
} catch (Throwable $error) {
unlink($targetPath);
throw $error;
}
}
function get_lager_product_forecast(PDO $pdo, int $productId): array
{
$stmt = $pdo->prepare(