diff --git a/db/migrations/0021_stock_lot_documents.sql b/db/migrations/0021_stock_lot_documents.sql deleted file mode 100644 index 5810b55..0000000 --- a/db/migrations/0021_stock_lot_documents.sql +++ /dev/null @@ -1,18 +0,0 @@ -BEGIN; - -CREATE TABLE stock_lot_document ( - id BIGSERIAL PRIMARY KEY, - stock_lot_id BIGINT NOT NULL REFERENCES stock_lot(id) ON DELETE CASCADE, - original_filename TEXT NOT NULL, - stored_filename TEXT NOT NULL, - mime_type TEXT NOT NULL, - size_bytes BIGINT NOT NULL, - comment TEXT, - uploaded_by TEXT NOT NULL, - uploaded_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT chk_stock_lot_document_size CHECK (size_bytes > 0 AND size_bytes <= 52428800) -); - -CREATE INDEX idx_stock_lot_document_lot ON stock_lot_document(stock_lot_id, id); - -COMMIT; diff --git a/modules/erp/lager/api/lot.php b/modules/erp/lager/api/lot.php index 91da6e7..f699b6f 100644 --- a/modules/erp/lager/api/lot.php +++ b/modules/erp/lager/api/lot.php @@ -20,32 +20,6 @@ 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']); @@ -103,16 +77,6 @@ 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'] ?? '')); diff --git a/modules/erp/lager/service.php b/modules/erp/lager/service.php index b728f47..17789a2 100644 --- a/modules/erp/lager/service.php +++ b/modules/erp/lager/service.php @@ -235,32 +235,6 @@ 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; } @@ -277,138 +251,6 @@ 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( diff --git a/modules/shared/auth/ui/home.php b/modules/shared/auth/ui/home.php index 243b943..3e9df45 100644 --- a/modules/shared/auth/ui/home.php +++ b/modules/shared/auth/ui/home.php @@ -254,7 +254,6 @@ function auth_render_lager_table(string $title, array $rows, array $columns, str . ' data-lager-drawer-mfg-date="' . auth_escape_html((string) ($row['mfg_date'] ?? '')) . '"' . ' data-lager-drawer-expiry-date="' . auth_escape_html((string) ($row['expiry_date'] ?? '')) . '"' . ' data-lager-drawer-purchase-date="' . auth_escape_html((string) ($row['purchase_date'] ?? '')) . '"' - . ' data-lager-drawer-documents="' . auth_escape_html((string) json_encode($row['documents'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . '"' . ' data-lager-drawer-virtual-lot-number="' . auth_escape_html((string) ($row['virtual_lot_number'] ?? '')) . '"'; $openLot = $row['open_lots'][0] ?? []; $detailData .= ' data-lager-open-lot-id="' . auth_escape_html((string) ($openLot['lot_id'] ?? '')) . '"' @@ -298,39 +297,6 @@ function auth_format_lager_quantity(float $value): string : number_format($value, 2, ',', '.'); } -function auth_render_lager_documents(array $documents, bool $editable, string $scope): string -{ - $html = '

Dokumente

'; - $html .= '
'; - if ($documents === []) { - $html .= '

Keine Dokumente vorhanden.

'; - } else { - foreach ($documents as $document) { - $documentId = (int) ($document['id'] ?? 0); - $filename = (string) ($document['original_filename'] ?? 'Dokument'); - $comment = trim((string) ($document['comment'] ?? '')); - $html .= '

' . auth_escape_html($filename) . ''; - if ($comment !== '') { - $html .= '
Kommentar: ' . auth_escape_html($comment) . ''; - } - $html .= '

'; - } - } - $html .= '
'; - if ($editable) { - $html .= '
'; - foreach ($documents as $document) { - $documentId = (int) ($document['id'] ?? 0); - $filename = (string) ($document['original_filename'] ?? 'Dokument'); - $comment = (string) ($document['comment'] ?? ''); - $html .= '
'; - } - $html .= '

Noch keine Dateien ausgewählt.

'; - $html .= '
'; - } - return $html . '
'; -} - function auth_render_lager_historical_lots(array $lagerProducts): string { $html = []; @@ -352,7 +318,6 @@ function auth_render_lager_historical_lots(array $lagerProducts): string . 'MHD: ' . auth_format_lager_date($lot['expiry_date'] ?? null) . '
' . 'Hersteller: ' . auth_escape_html((string) ($lot['manufacturer_name'] ?? 'Nicht hinterlegt')) . '
' . 'Hersteller-Chargennummer: ' . auth_escape_html((string) ($lot['manufacturer_lot_number'] ?? 'Nicht hinterlegt')) . '

' - . auth_render_lager_documents($lot['documents'] ?? [], false, 'historical-' . (int) ($lot['lot_id'] ?? 0)) . ''; } $html[] = ''; @@ -380,7 +345,6 @@ function auth_render_lager_open_lots(array $lagerProducts, string $manufacturerO . 'Herstellungsdatum: ' . auth_format_lager_date($lot['mfg_date'] ?? null) . '
' . 'MHD: ' . auth_format_lager_date($lot['expiry_date'] ?? null) . '
' . 'Einkaufsdatum: ' . auth_format_lager_date($lot['purchase_date'] ?? null) . '

' - . auth_render_lager_documents($lot['documents'] ?? [], false, 'open-read-' . $lotId) . '' . '' . ''; @@ -445,7 +408,7 @@ function auth_render_lager_content(array $lagerProducts, array $lagerArtikel, ar . '
' . auth_render_lager_open_lots($lagerProducts, $manufacturerOptions) . '
' . '
' . '
Aktive Charge
' - . '

Naurua-Chargennummer:
Aktueller Bestand:
Eingangsmenge:
Hersteller:
Hersteller-Chargennummer:
Herstellungsdatum:
MHD:
Einkaufsdatum:

' . auth_render_lager_documents([], false, 'current-read') . '
' + . '

Naurua-Chargennummer:
Aktueller Bestand:
Eingangsmenge:
Hersteller:
Hersteller-Chargennummer:
Herstellungsdatum:
MHD:
Einkaufsdatum:

' . '