Add documents to stock lots
This commit is contained in:
1 parent
e47990dfe0
commit
2bec934732
5 files changed
+294
-2
No files matched your search
@@ -0,0 +1,18 @@
|
|||||||
|
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;
|
||||||
@@ -20,6 +20,32 @@ try {
|
|||||||
json_response(405, ['ok' => false, 'error' => 'Method Not Allowed']);
|
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');
|
$jsonInput = file_get_contents('php://input');
|
||||||
if ($jsonInput === false || trim($jsonInput) === '') {
|
if ($jsonInput === false || trim($jsonInput) === '') {
|
||||||
json_response(400, ['ok' => false, 'error' => 'Empty payload']);
|
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)]);
|
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') {
|
if ($action === 'prepare_lot') {
|
||||||
$productId = (int) ($data['productId'] ?? 0);
|
$productId = (int) ($data['productId'] ?? 0);
|
||||||
$lotNumber = trim((string) ($data['lotNumber'] ?? ''));
|
$lotNumber = trim((string) ($data['lotNumber'] ?? ''));
|
||||||
|
|||||||
@@ -235,6 +235,32 @@ function get_lager_product_overview(PDO $pdo): array
|
|||||||
}
|
}
|
||||||
unset($product);
|
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;
|
return $products;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +277,138 @@ function get_lager_product_available_qty(PDO $pdo, int $productId): float
|
|||||||
return (float) $stmt->fetchColumn();
|
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
|
function get_lager_product_forecast(PDO $pdo, int $productId): array
|
||||||
{
|
{
|
||||||
$stmt = $pdo->prepare(
|
$stmt = $pdo->prepare(
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ 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-mfg-date="' . auth_escape_html((string) ($row['mfg_date'] ?? '')) . '"'
|
||||||
. ' data-lager-drawer-expiry-date="' . auth_escape_html((string) ($row['expiry_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-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'] ?? '')) . '"';
|
. ' data-lager-drawer-virtual-lot-number="' . auth_escape_html((string) ($row['virtual_lot_number'] ?? '')) . '"';
|
||||||
$openLot = $row['open_lots'][0] ?? [];
|
$openLot = $row['open_lots'][0] ?? [];
|
||||||
$detailData .= ' data-lager-open-lot-id="' . auth_escape_html((string) ($openLot['lot_id'] ?? '')) . '"'
|
$detailData .= ' data-lager-open-lot-id="' . auth_escape_html((string) ($openLot['lot_id'] ?? '')) . '"'
|
||||||
@@ -297,6 +298,39 @@ function auth_format_lager_quantity(float $value): string
|
|||||||
: number_format($value, 2, ',', '.');
|
: number_format($value, 2, ',', '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function auth_render_lager_documents(array $documents, bool $editable, string $scope): string
|
||||||
|
{
|
||||||
|
$html = '<section class="sg-form-sections-card__chapter" data-lager-documents="' . auth_escape_html($scope) . '"><h2 class="sg-strong sg-form-sections-card__chapter-title">Dokumente</h2>';
|
||||||
|
$html .= '<div data-lager-documents-read="' . auth_escape_html($scope) . '">';
|
||||||
|
if ($documents === []) {
|
||||||
|
$html .= '<p class="sg-body">Keine Dokumente vorhanden.</p>';
|
||||||
|
} else {
|
||||||
|
foreach ($documents as $document) {
|
||||||
|
$documentId = (int) ($document['id'] ?? 0);
|
||||||
|
$filename = (string) ($document['original_filename'] ?? 'Dokument');
|
||||||
|
$comment = trim((string) ($document['comment'] ?? ''));
|
||||||
|
$html .= '<p class="sg-body" data-lager-document-row="' . $documentId . '"><a class="sg-hyperlink" href="/api/lager-document.php?id=' . $documentId . '" data-component="hyperlink">' . auth_escape_html($filename) . '</a>';
|
||||||
|
if ($comment !== '') {
|
||||||
|
$html .= '<br><span data-lager-document-comment>Kommentar: ' . auth_escape_html($comment) . '</span>';
|
||||||
|
}
|
||||||
|
$html .= '</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
if ($editable) {
|
||||||
|
$html .= '<div data-lager-documents-edit="' . auth_escape_html($scope) . '">';
|
||||||
|
foreach ($documents as $document) {
|
||||||
|
$documentId = (int) ($document['id'] ?? 0);
|
||||||
|
$filename = (string) ($document['original_filename'] ?? 'Dokument');
|
||||||
|
$comment = (string) ($document['comment'] ?? '');
|
||||||
|
$html .= '<div class="sg-form-sections-card__field-group" data-lager-document-edit-row="' . $documentId . '"><label class="sg-labeled-input-row"><span class="sg-label">' . auth_escape_html($filename) . '</span><input class="sg-interaction-element sg-input-single-line" type="text" value="' . auth_escape_html($comment) . '" data-lager-document-comment-input="' . $documentId . '"></label><button class="sg-interaction-element sg-button sg-button--warning" type="button" data-lager-document-delete="' . $documentId . '">Löschen</button><article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" role="dialog" aria-modal="true" aria-labelledby="lager-document-delete-title-' . $documentId . '" data-lager-document-confirmation="' . $documentId . '" hidden><div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body"><p class="sg-body sg-delete-confirmation-pattern__text" id="lager-document-delete-title-' . $documentId . '"><strong>Dokument löschen?</strong></p><p class="sg-body sg-delete-confirmation-pattern__text">Bestätige durch Eingabe von <span class="sg-delete-confirmation-pattern__code">DELETE</span>.</p><label class="sg-labeled-input-row"><span class="sg-label">Bestätigung</span><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-document-delete-input="' . $documentId . '"></label><div class="sg-delete-confirmation-pattern__actions"><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-document-delete-cancel="' . $documentId . '">Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled data-lager-document-delete-confirm="' . $documentId . '">Löschen</button></div></div></article></div>';
|
||||||
|
}
|
||||||
|
$html .= '<form class="sg-file-upload" data-pattern="file-upload" data-lager-document-upload-form="' . auth_escape_html($scope) . '"><div class="sg-file-upload__dropzone sg-labeled-input-row"><label class="sg-label" for="lager-document-file-' . auth_escape_html($scope) . '">Dateien auswählen</label><input class="sg-file-input__control" id="lager-document-file-' . auth_escape_html($scope) . '" type="file" multiple aria-describedby="lager-document-file-status-' . auth_escape_html($scope) . '" data-lager-document-file><button class="sg-interaction-element sg-button sg-button--active sg-file-upload__trigger" type="button">Dateien auswählen</button></div><label class="sg-labeled-input-row"><span class="sg-label">Kommentar</span><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-document-comment></label><p class="sg-body sg-file-upload__status" id="lager-document-file-status-' . auth_escape_html($scope) . '" data-lager-document-status aria-live="polite">Noch keine Dateien ausgewählt.</p><button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="submit" disabled aria-disabled="true">Hochladen</button></form>';
|
||||||
|
$html .= '</div>';
|
||||||
|
}
|
||||||
|
return $html . '</section>';
|
||||||
|
}
|
||||||
|
|
||||||
function auth_render_lager_historical_lots(array $lagerProducts): string
|
function auth_render_lager_historical_lots(array $lagerProducts): string
|
||||||
{
|
{
|
||||||
$html = [];
|
$html = [];
|
||||||
@@ -318,6 +352,7 @@ function auth_render_lager_historical_lots(array $lagerProducts): string
|
|||||||
. '<strong>MHD:</strong> ' . auth_format_lager_date($lot['expiry_date'] ?? null) . '<br>'
|
. '<strong>MHD:</strong> ' . auth_format_lager_date($lot['expiry_date'] ?? null) . '<br>'
|
||||||
. '<strong>Hersteller:</strong> ' . auth_escape_html((string) ($lot['manufacturer_name'] ?? 'Nicht hinterlegt')) . '<br>'
|
. '<strong>Hersteller:</strong> ' . auth_escape_html((string) ($lot['manufacturer_name'] ?? 'Nicht hinterlegt')) . '<br>'
|
||||||
. '<strong>Hersteller-Chargennummer:</strong> ' . auth_escape_html((string) ($lot['manufacturer_lot_number'] ?? 'Nicht hinterlegt')) . '</p>'
|
. '<strong>Hersteller-Chargennummer:</strong> ' . auth_escape_html((string) ($lot['manufacturer_lot_number'] ?? 'Nicht hinterlegt')) . '</p>'
|
||||||
|
. auth_render_lager_documents($lot['documents'] ?? [], false, 'historical-' . (int) ($lot['lot_id'] ?? 0))
|
||||||
. '</div></article>';
|
. '</div></article>';
|
||||||
}
|
}
|
||||||
$html[] = '</div>';
|
$html[] = '</div>';
|
||||||
@@ -345,6 +380,7 @@ function auth_render_lager_open_lots(array $lagerProducts, string $manufacturerO
|
|||||||
. '<strong>Herstellungsdatum:</strong> <span data-lager-open-card-mfg-date>' . auth_format_lager_date($lot['mfg_date'] ?? null) . '</span><br>'
|
. '<strong>Herstellungsdatum:</strong> <span data-lager-open-card-mfg-date>' . auth_format_lager_date($lot['mfg_date'] ?? null) . '</span><br>'
|
||||||
. '<strong>MHD:</strong> <span data-lager-open-card-expiry-date>' . auth_format_lager_date($lot['expiry_date'] ?? null) . '</span><br>'
|
. '<strong>MHD:</strong> <span data-lager-open-card-expiry-date>' . auth_format_lager_date($lot['expiry_date'] ?? null) . '</span><br>'
|
||||||
. '<strong>Einkaufsdatum:</strong> <span data-lager-open-card-purchase-date>' . auth_format_lager_date($lot['purchase_date'] ?? null) . '</span></p></div>'
|
. '<strong>Einkaufsdatum:</strong> <span data-lager-open-card-purchase-date>' . auth_format_lager_date($lot['purchase_date'] ?? null) . '</span></p></div>'
|
||||||
|
. auth_render_lager_documents($lot['documents'] ?? [], false, 'open-read-' . $lotId)
|
||||||
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-lager-open-edit-form hidden><div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Offene Charge bearbeiten"><form class="sg-form-sections-card" data-lager-open-edit-form-element><div class="sg-form-sections-card__body" data-pattern-part="form-body"><h2 class="sg-strong sg-form-sections-card__title">Offene Charge bearbeiten</h2>'
|
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-lager-open-edit-form hidden><div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Offene Charge bearbeiten"><form class="sg-form-sections-card" data-lager-open-edit-form-element><div class="sg-form-sections-card__body" data-pattern-part="form-body"><h2 class="sg-strong sg-form-sections-card__title">Offene Charge bearbeiten</h2>'
|
||||||
. '<section class="sg-form-sections-card__chapter" aria-labelledby="lager-open-charge-data-' . $lotId . '"><h2 id="lager-open-charge-data-' . $lotId . '" class="sg-strong sg-form-sections-card__chapter-title">Chargendaten</h2>'
|
. '<section class="sg-form-sections-card__chapter" aria-labelledby="lager-open-charge-data-' . $lotId . '"><h2 id="lager-open-charge-data-' . $lotId . '" class="sg-strong sg-form-sections-card__chapter-title">Chargendaten</h2>'
|
||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Naurua-Chargennummer</span><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-open-lot-number value="' . auth_escape_html((string) ($lot['lot_number'] ?? '')) . '"></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Naurua-Chargennummer</span><input class="sg-interaction-element sg-input-single-line" type="text" data-lager-open-lot-number value="' . auth_escape_html((string) ($lot['lot_number'] ?? '')) . '"></label></div>'
|
||||||
@@ -354,6 +390,7 @@ function auth_render_lager_open_lots(array $lagerProducts, string $manufacturerO
|
|||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">MHD</span><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-open-expiry-date value="' . auth_escape_html((string) ($lot['expiry_date'] ?? '')) . '"></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">MHD</span><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-open-expiry-date value="' . auth_escape_html((string) ($lot['expiry_date'] ?? '')) . '"></label></div>'
|
||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Einkaufsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-open-purchase-date value="' . auth_escape_html((string) ($lot['purchase_date'] ?? '')) . '"></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Einkaufsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" data-lager-open-purchase-date value="' . auth_escape_html((string) ($lot['purchase_date'] ?? '')) . '"></label></div>'
|
||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Menge</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="1" inputmode="numeric" data-lager-open-target-qty value="' . auth_escape_html((string) ($lot['qty_net'] ?? 0)) . '"></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Menge</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="1" inputmode="numeric" data-lager-open-target-qty value="' . auth_escape_html((string) ($lot['qty_net'] ?? 0)) . '"></label></div>'
|
||||||
|
. auth_render_lager_documents($lot['documents'] ?? [], true, 'open-edit-' . $lotId)
|
||||||
. '</section><footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment"><div class="sg-form-sections-card__actions" data-pattern-part="form-actions"><button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button" data-lager-open-edit-close>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-form-sections-card__action" type="submit">Speichern</button></div></footer><p class="sg-body" data-lager-open-edit-status aria-live="polite"></p></div></form></div></div></article>'
|
. '</section><footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment"><div class="sg-form-sections-card__actions" data-pattern-part="form-actions"><button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button" data-lager-open-edit-close>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-form-sections-card__action" type="submit">Speichern</button></div></footer><p class="sg-body" data-lager-open-edit-status aria-live="polite"></p></div></form></div></div></article>'
|
||||||
. '<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Leere Felder bestätigen" role="dialog" aria-modal="true" aria-labelledby="lager-open-confirmation-title-' . $lotId . '" data-lager-open-confirmation-card hidden><div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body"><p class="sg-body sg-delete-confirmation-pattern__text" id="lager-open-confirmation-title-' . $lotId . '"><strong>Leere Felder speichern?</strong></p><p class="sg-body sg-delete-confirmation-pattern__text">Die folgenden Felder werden leer gespeichert:</p><div class="sg-delete-confirmation-pattern__list" data-lager-open-confirmation-empty-fields aria-label="Leer zu speichernde Felder"></div><div class="sg-delete-confirmation-pattern__actions"><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-open-confirmation-cancel>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process" type="button" data-lager-open-confirmation-submit>Speichern</button></div></div></article>'
|
. '<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Leere Felder bestätigen" role="dialog" aria-modal="true" aria-labelledby="lager-open-confirmation-title-' . $lotId . '" data-lager-open-confirmation-card hidden><div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body"><p class="sg-body sg-delete-confirmation-pattern__text" id="lager-open-confirmation-title-' . $lotId . '"><strong>Leere Felder speichern?</strong></p><p class="sg-body sg-delete-confirmation-pattern__text">Die folgenden Felder werden leer gespeichert:</p><div class="sg-delete-confirmation-pattern__list" data-lager-open-confirmation-empty-fields aria-label="Leer zu speichernde Felder"></div><div class="sg-delete-confirmation-pattern__actions"><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-open-confirmation-cancel>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process" type="button" data-lager-open-confirmation-submit>Speichern</button></div></div></article>'
|
||||||
. '<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Löschbestätigung" role="dialog" aria-modal="true" aria-labelledby="lager-open-delete-title-' . $lotId . '" data-overlay-dialog="delete" data-lager-open-delete-confirmation hidden><div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body"><p class="sg-body sg-delete-confirmation-pattern__text" id="lager-open-delete-title-' . $lotId . '"><strong>Möchtest du diese offene Charge wirklich löschen?</strong></p><p class="sg-body sg-delete-confirmation-pattern__text">Du kannst das nicht rückgängig machen. Bestätige durch Eingabe von <span class="sg-delete-confirmation-pattern__code">DELETE</span>.</p><label class="sg-labeled-input-row sg-delete-confirmation-pattern__input-row"><span class="sg-label">Bestätigung</span><input class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable" type="text" placeholder="DELETE" aria-label="Löschbestätigung durch DELETE" data-lager-open-delete-confirmation-input></label><p class="sg-body" data-lager-open-delete-status aria-live="polite"></p><div class="sg-delete-confirmation-pattern__actions"><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-open-delete-confirmation-cancel>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled aria-disabled="true" data-lager-open-delete-confirmation-submit>Löschen</button></div></div></article></div>';
|
. '<article class="sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card" aria-label="Löschbestätigung" role="dialog" aria-modal="true" aria-labelledby="lager-open-delete-title-' . $lotId . '" data-overlay-dialog="delete" data-lager-open-delete-confirmation hidden><div class="sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body"><p class="sg-body sg-delete-confirmation-pattern__text" id="lager-open-delete-title-' . $lotId . '"><strong>Möchtest du diese offene Charge wirklich löschen?</strong></p><p class="sg-body sg-delete-confirmation-pattern__text">Du kannst das nicht rückgängig machen. Bestätige durch Eingabe von <span class="sg-delete-confirmation-pattern__code">DELETE</span>.</p><label class="sg-labeled-input-row sg-delete-confirmation-pattern__input-row"><span class="sg-label">Bestätigung</span><input class="sg-interaction-element sg-input-single-line sg-input-single-line--inactive-selectable sg-form-inactive-selectable" type="text" placeholder="DELETE" aria-label="Löschbestätigung durch DELETE" data-lager-open-delete-confirmation-input></label><p class="sg-body" data-lager-open-delete-status aria-live="polite"></p><div class="sg-delete-confirmation-pattern__actions"><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-open-delete-confirmation-cancel>Abbrechen</button><button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive" type="button" disabled aria-disabled="true" data-lager-open-delete-confirmation-submit>Löschen</button></div></div></article></div>';
|
||||||
@@ -408,7 +445,7 @@ function auth_render_lager_content(array $lagerProducts, array $lagerArtikel, ar
|
|||||||
. '<div data-lager-drawer-topic="open-lots" aria-label="Offene Chargen">' . auth_render_lager_open_lots($lagerProducts, $manufacturerOptions) . '</div>'
|
. '<div data-lager-drawer-topic="open-lots" aria-label="Offene Chargen">' . auth_render_lager_open_lots($lagerProducts, $manufacturerOptions) . '</div>'
|
||||||
. '<article class="sg-card sg-card--content-card" data-component="content-card" data-lager-drawer-topic="lot" aria-label="Aktive Charge">'
|
. '<article class="sg-card sg-card--content-card" data-component="content-card" data-lager-drawer-topic="lot" aria-label="Aktive Charge">'
|
||||||
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen sg-object-card__header" data-component-part="card-header"><div class="sg-strong sg-text-on-dark">Aktive Charge</div><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-edit-open>Bearbeiten</button></div>'
|
. '<div class="sg-card-segment sg-card-segment--header sg-card-segment--darkgreen sg-object-card__header" data-component-part="card-header"><div class="sg-strong sg-text-on-dark">Aktive Charge</div><button class="sg-interaction-element sg-button sg-button--active" type="button" data-lager-edit-open>Bearbeiten</button></div>'
|
||||||
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-lager-read-view><p class="sg-body"><strong>Naurua-Chargennummer:</strong> <span data-lager-drawer-lot-number></span><br><strong>Aktueller Bestand:</strong> <span data-lager-drawer-current-qty></span><br><strong>Eingangsmenge:</strong> <span data-lager-drawer-qty-in></span><br><strong>Hersteller:</strong> <span data-lager-drawer-manufacturer-name></span><br><strong>Hersteller-Chargennummer:</strong> <span data-lager-drawer-manufacturer-lot-number></span><br><strong>Herstellungsdatum:</strong> <span data-lager-drawer-mfg-date></span><br><strong>MHD:</strong> <span data-lager-drawer-expiry-date></span><br><strong>Einkaufsdatum:</strong> <span data-lager-drawer-purchase-date></span></p></div>'
|
. '<div class="sg-card-segment sg-card-segment--body" data-component-part="card-body" data-lager-read-view><p class="sg-body"><strong>Naurua-Chargennummer:</strong> <span data-lager-drawer-lot-number></span><br><strong>Aktueller Bestand:</strong> <span data-lager-drawer-current-qty></span><br><strong>Eingangsmenge:</strong> <span data-lager-drawer-qty-in></span><br><strong>Hersteller:</strong> <span data-lager-drawer-manufacturer-name></span><br><strong>Hersteller-Chargennummer:</strong> <span data-lager-drawer-manufacturer-lot-number></span><br><strong>Herstellungsdatum:</strong> <span data-lager-drawer-mfg-date></span><br><strong>MHD:</strong> <span data-lager-drawer-expiry-date></span><br><strong>Einkaufsdatum:</strong> <span data-lager-drawer-purchase-date></span></p>' . auth_render_lager_documents([], false, 'current-read') . '</div>'
|
||||||
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-lager-edit-form hidden><div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Aktive Charge bearbeiten">'
|
. '<div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-lager-edit-form hidden><div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Aktive Charge bearbeiten">'
|
||||||
. '<form class="sg-form-sections-card" data-lager-edit-form-element><div class="sg-form-sections-card__body" data-pattern-part="form-body"><h2 class="sg-strong sg-form-sections-card__title">Aktive Charge bearbeiten</h2>'
|
. '<form class="sg-form-sections-card" data-lager-edit-form-element><div class="sg-form-sections-card__body" data-pattern-part="form-body"><h2 class="sg-strong sg-form-sections-card__title">Aktive Charge bearbeiten</h2>'
|
||||||
. '<section class="sg-form-sections-card__chapter" aria-labelledby="lager-charge-traceability-title"><h2 id="lager-charge-traceability-title" class="sg-strong sg-form-sections-card__chapter-title">Rückverfolgbarkeit</h2>'
|
. '<section class="sg-form-sections-card__chapter" aria-labelledby="lager-charge-traceability-title"><h2 id="lager-charge-traceability-title" class="sg-strong sg-form-sections-card__chapter-title">Rückverfolgbarkeit</h2>'
|
||||||
@@ -418,6 +455,7 @@ function auth_render_lager_content(array $lagerProducts, array $lagerArtikel, ar
|
|||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Herstellungsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-mfg-date" data-lager-mfg-date></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Herstellungsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-mfg-date" data-lager-mfg-date></label></div>'
|
||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">MHD</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-expiry-date" data-lager-expiry-date></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">MHD</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-expiry-date" data-lager-expiry-date></label></div>'
|
||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Einkaufsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-purchase-date" data-lager-purchase-date></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Einkaufsdatum</span><input class="sg-interaction-element sg-input-single-line" type="date" id="lager-purchase-date" data-lager-purchase-date></label></div>'
|
||||||
|
. auth_render_lager_documents([], true, 'current-edit')
|
||||||
. '</section><section class="sg-form-sections-card__chapter" aria-labelledby="lager-charge-movements-title"><h2 id="lager-charge-movements-title" class="sg-strong sg-form-sections-card__chapter-title">Lagerbewegungen</h2>'
|
. '</section><section class="sg-form-sections-card__chapter" aria-labelledby="lager-charge-movements-title"><h2 id="lager-charge-movements-title" class="sg-strong sg-form-sections-card__chapter-title">Lagerbewegungen</h2>'
|
||||||
. '<p class="sg-form-sections-card__sentence">Buchungen werden zusätzlich zur Rückverfolgbarkeit als Lagerbewegung gespeichert. Positive Werte erhöhen den Bestand, negative Werte reduzieren ihn.</p>'
|
. '<p class="sg-form-sections-card__sentence">Buchungen werden zusätzlich zur Rückverfolgbarkeit als Lagerbewegung gespeichert. Positive Werte erhöhen den Bestand, negative Werte reduzieren ihn.</p>'
|
||||||
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Zugang buchen</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="0.01" inputmode="decimal" data-lager-receipt-qty placeholder="Menge"></label></div>'
|
. '<div class="sg-form-sections-card__field-group"><label class="sg-labeled-input-row"><span class="sg-label">Zugang buchen</span><input class="sg-interaction-element sg-input-single-line" type="number" min="0" step="0.01" inputmode="decimal" data-lager-receipt-qty placeholder="Menge"></label></div>'
|
||||||
@@ -808,8 +846,11 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " drawer.dataset.open = String(open); drawer.setAttribute('aria-hidden', String(!open)); backdrop.dataset.open = String(open); backdrop.setAttribute('aria-hidden', String(!open)); document.body.classList.toggle('sg-card-list-page-drawer-open', open);";
|
echo " drawer.dataset.open = String(open); drawer.setAttribute('aria-hidden', String(!open)); backdrop.dataset.open = String(open); backdrop.setAttribute('aria-hidden', String(!open)); document.body.classList.toggle('sg-card-list-page-drawer-open', open);";
|
||||||
echo " };";
|
echo " };";
|
||||||
echo " const getLagerWarningTitle = (trigger) => trigger.dataset.lagerDrawerForecastStatus === 'depleted' ? 'Warnung: Charge ist aufgebraucht' : (trigger.dataset.lagerDrawerForecastMhdWarning === 'true' ? 'Warnung: MHD wird vor Chargenende erreicht' : (trigger.dataset.lagerDrawerForecastWarning === 'true' ? 'Warnung: Charge wird innerhalb von drei Monaten aufgebraucht sein' : 'Keine Warnung'));";
|
echo " const getLagerWarningTitle = (trigger) => trigger.dataset.lagerDrawerForecastStatus === 'depleted' ? 'Warnung: Charge ist aufgebraucht' : (trigger.dataset.lagerDrawerForecastMhdWarning === 'true' ? 'Warnung: MHD wird vor Chargenende erreicht' : (trigger.dataset.lagerDrawerForecastWarning === 'true' ? 'Warnung: Charge wird innerhalb von drei Monaten aufgebraucht sein' : 'Keine Warnung'));";
|
||||||
|
echo " const escapeLagerHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' }[character] || character));";
|
||||||
|
echo " const renderLagerDocumentRows = (documents, editable) => { if (!documents.length) { return '<p class=\"sg-body\">Keine Dokumente vorhanden.</p>'; } return documents.map((document) => { const id = Number(document.id || 0); const filename = escapeLagerHtml(document.original_filename || 'Dokument'); const comment = String(document.comment || '').trim(); if (!editable) { return '<p class=\"sg-body\" data-lager-document-row=\"' + id + '\"><a class=\"sg-hyperlink\" href=\"/api/lager-document.php?id=' + id + '\" data-component=\"hyperlink\">' + filename + '</a>' + (comment ? '<br><span data-lager-document-comment>Kommentar: ' + escapeLagerHtml(comment) + '</span>' : '') + '</p>'; } return '<div class=\"sg-form-sections-card__field-group\" data-lager-document-edit-row=\"' + id + '\"><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">' + filename + '</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" value=\"' + escapeLagerHtml(comment) + '\" data-lager-document-comment-input=\"' + id + '\"></label><button class=\"sg-interaction-element sg-button sg-button--warning\" type=\"button\" data-lager-document-delete=\"' + id + '\">Löschen</button></div>'; }).join(''); };";
|
||||||
|
echo " const syncLagerCurrentDocuments = (trigger) => { const drawer = getDrawer(); if (!drawer) { return; } let documents = []; try { documents = JSON.parse(trigger.dataset.lagerDrawerDocuments || '[]'); } catch (error) { documents = []; } const read = drawer.querySelector('[data-lager-documents-read=\"current-read\"]'); const edit = drawer.querySelector('[data-lager-documents-edit=\"current-edit\"]'); if (read) { read.innerHTML = renderLagerDocumentRows(documents, false); } if (edit) { edit.innerHTML = renderLagerDocumentRows(documents, true) + edit.querySelector('[data-lager-document-upload-form]')?.outerHTML || ''; } };";
|
||||||
echo " const closeDrawer = () => { setDrawerState(false); contentRoot.querySelectorAll('[data-lager-drawer-open]').forEach((trigger) => trigger.setAttribute('aria-expanded', 'false')); };";
|
echo " const closeDrawer = () => { setDrawerState(false); contentRoot.querySelectorAll('[data-lager-drawer-open]').forEach((trigger) => trigger.setAttribute('aria-expanded', 'false')); };";
|
||||||
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt') { return; } const drawer = getDrawer(); const forecast = drawer?.querySelector('[data-lager-drawer-forecast-value]'); if (forecast) { forecast.textContent = trigger.dataset.lagerDrawerForecast || 'Keine Prognose'; } });";
|
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt') { return; } const drawer = getDrawer(); const forecast = drawer?.querySelector('[data-lager-drawer-forecast-value]'); if (forecast) { forecast.textContent = trigger.dataset.lagerDrawerForecast || 'Keine Prognose'; } syncLagerCurrentDocuments(trigger); });";
|
||||||
echo " const getLagerForecastWarningText = (forecast, trigger, text) => { if (forecast?.status === 'depleted' || trigger?.dataset.lagerDrawerForecastStatus === 'depleted') { return 'Die Charge ist aufgebraucht. Voraussichtlich leer: ' + text + '.'; } if (trigger?.dataset.lagerDrawerForecastMhdWarning === 'true') { const expiry = trigger.dataset.lagerDrawerExpiryDate ? new Date(trigger.dataset.lagerDrawerExpiryDate + 'T00:00:00').toLocaleDateString('de-CH') : 'dem MHD'; return 'Das MHD wird am ' + expiry + ' erreicht, bevor das Lager leer ist. Voraussichtlich leer: ' + text + '.'; } if (trigger?.dataset.lagerDrawerForecastWarning === 'true') { return 'Voraussichtlich leer: ' + text + '.'; } return 'Keine akute Warnung.'; };";
|
echo " const getLagerForecastWarningText = (forecast, trigger, text) => { if (forecast?.status === 'depleted' || trigger?.dataset.lagerDrawerForecastStatus === 'depleted') { return 'Die Charge ist aufgebraucht. Voraussichtlich leer: ' + text + '.'; } if (trigger?.dataset.lagerDrawerForecastMhdWarning === 'true') { const expiry = trigger.dataset.lagerDrawerExpiryDate ? new Date(trigger.dataset.lagerDrawerExpiryDate + 'T00:00:00').toLocaleDateString('de-CH') : 'dem MHD'; return 'Das MHD wird am ' + expiry + ' erreicht, bevor das Lager leer ist. Voraussichtlich leer: ' + text + '.'; } if (trigger?.dataset.lagerDrawerForecastWarning === 'true') { return 'Voraussichtlich leer: ' + text + '.'; } return 'Keine akute Warnung.'; };";
|
||||||
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt') { return; } const drawer = getDrawer(); const warningValue = drawer?.querySelector('[data-lager-drawer-warning-value]'); if (warningValue) { warningValue.textContent = getLagerForecastWarningText(null, trigger, trigger.dataset.lagerDrawerForecast || 'Keine Prognose'); } });";
|
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt') { return; } const drawer = getDrawer(); const warningValue = drawer?.querySelector('[data-lager-drawer-warning-value]'); if (warningValue) { warningValue.textContent = getLagerForecastWarningText(null, trigger, trigger.dataset.lagerDrawerForecast || 'Keine Prognose'); } });";
|
||||||
echo " const syncLagerForecast = (forecast, productId) => { const drawer = getDrawer(); const trigger = contentRoot.querySelector('[data-lager-drawer-open][aria-expanded=\"true\"]') || contentRoot.querySelector('[data-lager-row][data-lager-id=\"' + (productId || '') + '\"] [data-lager-drawer-open]'); if (!forecast || !drawer) { return; } const forecastDate = forecast.date ? new Date(forecast.date + 'T00:00:00') : null; const expiryDate = forecast.expiryDate ? new Date(forecast.expiryDate + 'T00:00:00') : null; const mhdWarning = Boolean(forecastDate && expiryDate && forecastDate > expiryDate); const nearExpiryWarning = Boolean(forecastDate && expiryDate && forecastDate >= new Date(new Date(expiryDate).setMonth(expiryDate.getMonth() - 3)) && forecastDate <= expiryDate); const depleted = forecast.status === 'depleted'; const forecastWarning = depleted || nearExpiryWarning; const date = forecastDate ? forecastDate.toLocaleDateString('de-CH') : 'Keine Prognose'; const text = depleted ? date + ' (CHARGE ENDE)' : (nearExpiryWarning || mhdWarning ? date + ' (MHD: ' + expiryDate.toLocaleDateString('de-CH') + ')' : date); const value = drawer.querySelector('[data-lager-drawer-forecast-value]'); if (value) { value.textContent = text; } const header = drawer.querySelector('[data-lager-drawer-warning-header]'); const title = drawer.querySelector('[data-lager-drawer-warning-title]'); const warningValue = drawer.querySelector('[data-lager-drawer-warning-value]'); if (trigger) { trigger.dataset.lagerDrawerForecast = text; trigger.dataset.lagerDrawerForecastStatus = forecast.status || 'no_demand'; trigger.dataset.lagerDrawerForecastWarning = forecastWarning ? 'true' : 'false'; trigger.dataset.lagerDrawerForecastMhdWarning = mhdWarning ? 'true' : 'false'; if (header) { header.classList.toggle('sg-card-segment--darkgreen', !forecastWarning && !mhdWarning); header.classList.toggle('sg-card-segment--signal-red', forecastWarning); header.classList.toggle('sg-card-segment--signal-yellow', mhdWarning); } if (title) { title.textContent = getLagerWarningTitle(trigger); } if (warningValue) { warningValue.textContent = forecastWarning || mhdWarning ? text : 'Keine akute Warnung.'; warningValue.classList.toggle('sg-strong', forecastWarning || mhdWarning); warningValue.classList.toggle('lager-forecast-warning', forecastWarning); warningValue.classList.toggle('lager-forecast-mhd-warning', mhdWarning); } const row = trigger.closest('[data-lager-row]'); const cells = row?.querySelectorAll('[role=\"cell\"]'); const forecastCell = cells?.[cells.length - 1]; if (forecastCell && cells.length === 3) { forecastCell.textContent = text; } } };";
|
echo " const syncLagerForecast = (forecast, productId) => { const drawer = getDrawer(); const trigger = contentRoot.querySelector('[data-lager-drawer-open][aria-expanded=\"true\"]') || contentRoot.querySelector('[data-lager-row][data-lager-id=\"' + (productId || '') + '\"] [data-lager-drawer-open]'); if (!forecast || !drawer) { return; } const forecastDate = forecast.date ? new Date(forecast.date + 'T00:00:00') : null; const expiryDate = forecast.expiryDate ? new Date(forecast.expiryDate + 'T00:00:00') : null; const mhdWarning = Boolean(forecastDate && expiryDate && forecastDate > expiryDate); const nearExpiryWarning = Boolean(forecastDate && expiryDate && forecastDate >= new Date(new Date(expiryDate).setMonth(expiryDate.getMonth() - 3)) && forecastDate <= expiryDate); const depleted = forecast.status === 'depleted'; const forecastWarning = depleted || nearExpiryWarning; const date = forecastDate ? forecastDate.toLocaleDateString('de-CH') : 'Keine Prognose'; const text = depleted ? date + ' (CHARGE ENDE)' : (nearExpiryWarning || mhdWarning ? date + ' (MHD: ' + expiryDate.toLocaleDateString('de-CH') + ')' : date); const value = drawer.querySelector('[data-lager-drawer-forecast-value]'); if (value) { value.textContent = text; } const header = drawer.querySelector('[data-lager-drawer-warning-header]'); const title = drawer.querySelector('[data-lager-drawer-warning-title]'); const warningValue = drawer.querySelector('[data-lager-drawer-warning-value]'); if (trigger) { trigger.dataset.lagerDrawerForecast = text; trigger.dataset.lagerDrawerForecastStatus = forecast.status || 'no_demand'; trigger.dataset.lagerDrawerForecastWarning = forecastWarning ? 'true' : 'false'; trigger.dataset.lagerDrawerForecastMhdWarning = mhdWarning ? 'true' : 'false'; if (header) { header.classList.toggle('sg-card-segment--darkgreen', !forecastWarning && !mhdWarning); header.classList.toggle('sg-card-segment--signal-red', forecastWarning); header.classList.toggle('sg-card-segment--signal-yellow', mhdWarning); } if (title) { title.textContent = getLagerWarningTitle(trigger); } if (warningValue) { warningValue.textContent = forecastWarning || mhdWarning ? text : 'Keine akute Warnung.'; warningValue.classList.toggle('sg-strong', forecastWarning || mhdWarning); warningValue.classList.toggle('lager-forecast-warning', forecastWarning); warningValue.classList.toggle('lager-forecast-mhd-warning', mhdWarning); } const row = trigger.closest('[data-lager-row]'); const cells = row?.querySelectorAll('[role=\"cell\"]'); const forecastCell = cells?.[cells.length - 1]; if (forecastCell && cells.length === 3) { forecastCell.textContent = text; } } };";
|
||||||
@@ -817,11 +858,20 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
|||||||
echo " document.addEventListener('click', (event) => {";
|
echo " document.addEventListener('click', (event) => {";
|
||||||
echo " const trigger = event.target.closest('[data-lager-drawer-open]');";
|
echo " const trigger = event.target.closest('[data-lager-drawer-open]');";
|
||||||
echo " if (trigger) { event.preventDefault(); if (window.matchMedia('(max-width: 767px)').matches) { openLagerMobileDetail(trigger); return; } const drawer = getDrawer(); if (!drawer) { return; } const type = trigger.dataset.lagerDrawerType || 'Lager'; const name = trigger.dataset.lagerDrawerName || ''; const value = trigger.dataset.lagerDrawerValue || ''; drawer.querySelector('[data-lager-drawer-title]').textContent = type + ': ' + name; drawer.querySelector('[data-lager-drawer-name-value]').textContent = name; const valueRow = drawer.querySelector('[data-lager-drawer-value-row]'); if (valueRow) { valueRow.hidden = false; } drawer.querySelector('[data-lager-drawer-value-label]').textContent = type === 'Produkt' ? 'Anzahl im Lager:' : 'Artikelnummer:'; drawer.querySelector('[data-lager-drawer-value-value]').textContent = value; drawer.querySelectorAll('[data-lager-drawer-topic]').forEach((card) => { card.hidden = type !== 'Produkt'; }); if (type === 'Produkt') { const forecast = trigger.dataset.lagerDrawerForecast || 'Keine Prognose'; const warning = trigger.dataset.lagerDrawerForecastWarning === 'true' || trigger.dataset.lagerDrawerForecastMhdWarning === 'true'; const warningValue = drawer.querySelector('[data-lager-drawer-warning-value]'); if (warningValue) { warningValue.textContent = warning ? forecast : 'Keine akute Warnung.'; warningValue.classList.toggle('sg-strong', warning); warningValue.classList.toggle('lager-forecast-warning', trigger.dataset.lagerDrawerForecastWarning === 'true'); warningValue.classList.toggle('lager-forecast-mhd-warning', trigger.dataset.lagerDrawerForecastMhdWarning === 'true'); } drawer.querySelector('[data-lager-drawer-lot-number]').textContent = trigger.dataset.lagerDrawerLotNumber || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-manufacturer-name]').textContent = trigger.dataset.lagerDrawerManufacturerName || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-manufacturer-lot-number]').textContent = trigger.dataset.lagerDrawerManufacturerLotNumber || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-mfg-date]').textContent = trigger.dataset.lagerDrawerMfgDate || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-expiry-date]').textContent = trigger.dataset.lagerDrawerExpiryDate || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-purchase-date]').textContent = trigger.dataset.lagerDrawerPurchaseDate || 'Nicht hinterlegt'; const virtualLot = trigger.dataset.lagerDrawerVirtualLotNumber || ''; drawer.querySelector('[data-lager-drawer-virtual-lot-value]').textContent = virtualLot ? 'Virtuelle Charge: ' + virtualLot : 'Keine virtuelle Fehlbestandscharge vorhanden.'; const manufacturer = drawer.querySelector('[data-lager-manufacturer]'); if (manufacturer) { manufacturer.value = trigger.dataset.lagerDrawerManufacturerPartyId || ''; } const manufacturerLot = drawer.querySelector('[data-lager-manufacturer-lot]'); if (manufacturerLot) { manufacturerLot.value = trigger.dataset.lagerDrawerManufacturerLotNumber || ''; } const mfgDate = drawer.querySelector('[data-lager-mfg-date]'); if (mfgDate) { mfgDate.value = trigger.dataset.lagerDrawerMfgDate || ''; } const expiryDate = drawer.querySelector('[data-lager-expiry-date]'); if (expiryDate) { expiryDate.value = trigger.dataset.lagerDrawerExpiryDate || ''; } const purchaseDate = drawer.querySelector('[data-lager-purchase-date]'); if (purchaseDate) { purchaseDate.value = trigger.dataset.lagerDrawerPurchaseDate || ''; } const editForm = drawer.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = true; } drawer.dataset.lagerActiveLotId = trigger.dataset.lagerDrawerLotId || ''; } setDrawerState(true); contentRoot.querySelectorAll('[data-lager-drawer-open]').forEach((button) => button.setAttribute('aria-expanded', String(button === trigger))); return; }";
|
echo " if (trigger) { event.preventDefault(); if (window.matchMedia('(max-width: 767px)').matches) { openLagerMobileDetail(trigger); return; } const drawer = getDrawer(); if (!drawer) { return; } const type = trigger.dataset.lagerDrawerType || 'Lager'; const name = trigger.dataset.lagerDrawerName || ''; const value = trigger.dataset.lagerDrawerValue || ''; drawer.querySelector('[data-lager-drawer-title]').textContent = type + ': ' + name; drawer.querySelector('[data-lager-drawer-name-value]').textContent = name; const valueRow = drawer.querySelector('[data-lager-drawer-value-row]'); if (valueRow) { valueRow.hidden = false; } drawer.querySelector('[data-lager-drawer-value-label]').textContent = type === 'Produkt' ? 'Anzahl im Lager:' : 'Artikelnummer:'; drawer.querySelector('[data-lager-drawer-value-value]').textContent = value; drawer.querySelectorAll('[data-lager-drawer-topic]').forEach((card) => { card.hidden = type !== 'Produkt'; }); if (type === 'Produkt') { const forecast = trigger.dataset.lagerDrawerForecast || 'Keine Prognose'; const warning = trigger.dataset.lagerDrawerForecastWarning === 'true' || trigger.dataset.lagerDrawerForecastMhdWarning === 'true'; const warningValue = drawer.querySelector('[data-lager-drawer-warning-value]'); if (warningValue) { warningValue.textContent = warning ? forecast : 'Keine akute Warnung.'; warningValue.classList.toggle('sg-strong', warning); warningValue.classList.toggle('lager-forecast-warning', trigger.dataset.lagerDrawerForecastWarning === 'true'); warningValue.classList.toggle('lager-forecast-mhd-warning', trigger.dataset.lagerDrawerForecastMhdWarning === 'true'); } drawer.querySelector('[data-lager-drawer-lot-number]').textContent = trigger.dataset.lagerDrawerLotNumber || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-manufacturer-name]').textContent = trigger.dataset.lagerDrawerManufacturerName || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-manufacturer-lot-number]').textContent = trigger.dataset.lagerDrawerManufacturerLotNumber || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-mfg-date]').textContent = trigger.dataset.lagerDrawerMfgDate || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-expiry-date]').textContent = trigger.dataset.lagerDrawerExpiryDate || 'Nicht hinterlegt'; drawer.querySelector('[data-lager-drawer-purchase-date]').textContent = trigger.dataset.lagerDrawerPurchaseDate || 'Nicht hinterlegt'; const virtualLot = trigger.dataset.lagerDrawerVirtualLotNumber || ''; drawer.querySelector('[data-lager-drawer-virtual-lot-value]').textContent = virtualLot ? 'Virtuelle Charge: ' + virtualLot : 'Keine virtuelle Fehlbestandscharge vorhanden.'; const manufacturer = drawer.querySelector('[data-lager-manufacturer]'); if (manufacturer) { manufacturer.value = trigger.dataset.lagerDrawerManufacturerPartyId || ''; } const manufacturerLot = drawer.querySelector('[data-lager-manufacturer-lot]'); if (manufacturerLot) { manufacturerLot.value = trigger.dataset.lagerDrawerManufacturerLotNumber || ''; } const mfgDate = drawer.querySelector('[data-lager-mfg-date]'); if (mfgDate) { mfgDate.value = trigger.dataset.lagerDrawerMfgDate || ''; } const expiryDate = drawer.querySelector('[data-lager-expiry-date]'); if (expiryDate) { expiryDate.value = trigger.dataset.lagerDrawerExpiryDate || ''; } const purchaseDate = drawer.querySelector('[data-lager-purchase-date]'); if (purchaseDate) { purchaseDate.value = trigger.dataset.lagerDrawerPurchaseDate || ''; } const editForm = drawer.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = true; } drawer.dataset.lagerActiveLotId = trigger.dataset.lagerDrawerLotId || ''; } setDrawerState(true); contentRoot.querySelectorAll('[data-lager-drawer-open]').forEach((button) => button.setAttribute('aria-expanded', String(button === trigger))); return; }";
|
||||||
|
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (trigger?.dataset.lagerDrawerType === 'Produkt') { syncLagerCurrentDocuments(trigger); } });";
|
||||||
echo " if (event.target.closest('[data-lager-drawer-close]') || event.target.closest('[data-lager-drawer-backdrop]')) { closeDrawer(); }";
|
echo " if (event.target.closest('[data-lager-drawer-close]') || event.target.closest('[data-lager-drawer-backdrop]')) { closeDrawer(); }";
|
||||||
echo " });";
|
echo " });";
|
||||||
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt' || window.matchMedia('(max-width: 767px)').matches) { return; } const header = contentRoot.querySelector('[data-lager-drawer-warning-header]'); const title = contentRoot.querySelector('[data-lager-drawer-warning-title]'); if (!header || !title) { return; } const forecastWarning = trigger.dataset.lagerDrawerForecastWarning === 'true'; const mhdWarning = trigger.dataset.lagerDrawerForecastMhdWarning === 'true'; title.textContent = getLagerWarningTitle(trigger); header.classList.toggle('sg-card-segment--darkgreen', !forecastWarning && !mhdWarning); header.classList.toggle('sg-card-segment--signal-red', forecastWarning); header.classList.toggle('sg-card-segment--signal-yellow', mhdWarning); });";
|
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt' || window.matchMedia('(max-width: 767px)').matches) { return; } const header = contentRoot.querySelector('[data-lager-drawer-warning-header]'); const title = contentRoot.querySelector('[data-lager-drawer-warning-title]'); if (!header || !title) { return; } const forecastWarning = trigger.dataset.lagerDrawerForecastWarning === 'true'; const mhdWarning = trigger.dataset.lagerDrawerForecastMhdWarning === 'true'; title.textContent = getLagerWarningTitle(trigger); header.classList.toggle('sg-card-segment--darkgreen', !forecastWarning && !mhdWarning); header.classList.toggle('sg-card-segment--signal-red', forecastWarning); header.classList.toggle('sg-card-segment--signal-yellow', mhdWarning); });";
|
||||||
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt' || window.matchMedia('(max-width: 767px)').matches) { return; } setLagerEditMode(false); const drawer = getDrawer(); const currentQty = drawer?.querySelector('[data-lager-drawer-current-qty]'); const qtyIn = drawer?.querySelector('[data-lager-drawer-qty-in]'); const lotNumber = drawer?.querySelector('[data-lager-lot-number]'); if (currentQty) { currentQty.textContent = trigger.dataset.lagerDrawerCurrentQty || '0'; } if (qtyIn) { qtyIn.textContent = trigger.dataset.lagerDrawerQtyIn || '0'; } if (lotNumber) { lotNumber.value = trigger.dataset.lagerDrawerLotNumber || ''; } const productId = trigger.closest('[data-lager-row]')?.dataset.lagerId || ''; contentRoot.querySelectorAll('[data-lager-historical-product]').forEach((product) => { product.hidden = product.dataset.lagerHistoricalProduct !== productId; }); });";
|
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-lager-drawer-open]'); if (!trigger || trigger.dataset.lagerDrawerType !== 'Produkt' || window.matchMedia('(max-width: 767px)').matches) { return; } setLagerEditMode(false); const drawer = getDrawer(); const currentQty = drawer?.querySelector('[data-lager-drawer-current-qty]'); const qtyIn = drawer?.querySelector('[data-lager-drawer-qty-in]'); const lotNumber = drawer?.querySelector('[data-lager-lot-number]'); if (currentQty) { currentQty.textContent = trigger.dataset.lagerDrawerCurrentQty || '0'; } if (qtyIn) { qtyIn.textContent = trigger.dataset.lagerDrawerQtyIn || '0'; } if (lotNumber) { lotNumber.value = trigger.dataset.lagerDrawerLotNumber || ''; } const productId = trigger.closest('[data-lager-row]')?.dataset.lagerId || ''; contentRoot.querySelectorAll('[data-lager-historical-product]').forEach((product) => { product.hidden = product.dataset.lagerHistoricalProduct !== productId; }); });";
|
||||||
echo " const postLagerChange = async (payload) => { const response = await fetch('/api/lager-charge.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...payload, csrfToken: contentRoot.querySelector('[data-lager-csrf-token]')?.dataset.lagerCsrfToken || '' }) }); const result = await response.json(); if (!response.ok || result.ok !== true) { throw new Error(result.error || 'Speichern fehlgeschlagen'); } syncLagerForecast(result.forecast, result.productId); const trigger = contentRoot.querySelector('[data-lager-drawer-open][aria-expanded=\"true\"]') || contentRoot.querySelector('[data-lager-row][data-lager-id=\"' + (result.productId || '') + '\"] [data-lager-drawer-open]'); const drawer = getDrawer(); const warningValue = drawer?.querySelector('[data-lager-drawer-warning-value]'); if (warningValue) { warningValue.textContent = getLagerForecastWarningText(result.forecast, trigger, trigger?.dataset.lagerDrawerForecast || 'Keine Prognose'); } return result; };";
|
echo " const postLagerChange = async (payload) => { const response = await fetch('/api/lager-charge.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...payload, csrfToken: contentRoot.querySelector('[data-lager-csrf-token]')?.dataset.lagerCsrfToken || '' }) }); const result = await response.json(); if (!response.ok || result.ok !== true) { throw new Error(result.error || 'Speichern fehlgeschlagen'); } syncLagerForecast(result.forecast, result.productId); const trigger = contentRoot.querySelector('[data-lager-drawer-open][aria-expanded=\"true\"]') || contentRoot.querySelector('[data-lager-row][data-lager-id=\"' + (result.productId || '') + '\"] [data-lager-drawer-open]'); const drawer = getDrawer(); const warningValue = drawer?.querySelector('[data-lager-drawer-warning-value]'); if (warningValue) { warningValue.textContent = getLagerForecastWarningText(result.forecast, trigger, trigger?.dataset.lagerDrawerForecast || 'Keine Prognose'); } return result; };";
|
||||||
|
echo " const getLagerDocumentLotId = (form) => form.closest('[data-lager-open-card-wrapper]')?.dataset.lagerOpenCardWrapper || getDrawer()?.dataset.lagerActiveLotId || '';";
|
||||||
|
echo " document.addEventListener('click', (event) => { const trigger = event.target.closest('[data-component=\"file-input\"] .sg-file-input__trigger, .sg-file-upload__trigger'); if (trigger) { trigger.closest('[data-lager-document-upload-form]')?.querySelector('[data-lager-document-file]')?.click(); } });";
|
||||||
|
echo " document.addEventListener('change', (event) => { const input = event.target.closest('[data-lager-document-file]'); if (!input) { return; } const form = input.closest('[data-lager-document-upload-form]'); const submit = form?.querySelector('[type=\"submit\"]'); const status = form?.querySelector('[data-lager-document-status]'); const count = input.files?.length || 0; if (status) { status.textContent = count ? count + ' Datei(en) ausgewählt.' : 'Noch keine Dateien ausgewählt.'; } if (submit) { submit.disabled = count === 0; submit.setAttribute('aria-disabled', String(count === 0)); submit.classList.toggle('sg-button--process-inactive', count === 0); } });";
|
||||||
|
echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-lager-document-upload-form]'); if (!form) { return; } event.preventDefault(); const input = form.querySelector('[data-lager-document-file]'); const submit = form.querySelector('[type=\"submit\"]'); const status = form.querySelector('[data-lager-document-status]'); const files = Array.from(input?.files || []); if (!files.length) { return; } if (files.some((file) => file.size > 52428800)) { if (status) { status.textContent = 'Dokument darf maximal 50 MB groß sein.'; } return; } if (submit) { submit.disabled = true; } if (status) { status.textContent = 'Upload läuft …'; } try { for (const file of files) { const data = new FormData(); data.append('action', 'upload_document'); data.append('csrfToken', contentRoot.querySelector('[data-lager-csrf-token]')?.dataset.lagerCsrfToken || ''); data.append('lotId', getLagerDocumentLotId(form)); data.append('comment', form.querySelector('[data-lager-document-comment]')?.value || ''); data.append('document', file); const response = await fetch('/api/lager-charge.php', { method: 'POST', body: data }); const result = await response.json(); if (!response.ok || result.ok !== true) { throw new Error(result.error || 'Upload fehlgeschlagen'); } } window.location.reload(); } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Upload fehlgeschlagen'; } if (submit) { submit.disabled = false; } } });";
|
||||||
|
echo " document.addEventListener('blur', async (event) => { const input = event.target.closest('[data-lager-document-comment-input]'); if (!input) { return; } try { await postLagerChange({ action: 'update_document_comment', documentId: input.dataset.lagerDocumentCommentInput, comment: input.value }); } catch (error) { input.setCustomValidity(error instanceof Error ? error.message : 'Kommentar konnte nicht gespeichert werden'); input.reportValidity(); } }, true);";
|
||||||
|
echo " document.addEventListener('click', (event) => { const open = event.target.closest('[data-lager-document-delete]'); if (!open) { return; } const row = open.closest('[data-lager-document-edit-row]'); const id = open.dataset.lagerDocumentDelete; if (row?.querySelector('[data-lager-document-confirmation]')) { return; } const confirmation = document.createElement('article'); confirmation.className = 'sg-card sg-card--overlay-host sg-delete-confirmation-pattern__floating-card'; confirmation.setAttribute('role', 'dialog'); confirmation.setAttribute('aria-modal', 'true'); confirmation.dataset.lagerDocumentConfirmation = id; confirmation.innerHTML = '<div class=\"sg-card-segment sg-card-segment--body sg-delete-confirmation-pattern__body\"><p class=\"sg-body sg-delete-confirmation-pattern__text\"><strong>Dokument löschen?</strong></p><p class=\"sg-body sg-delete-confirmation-pattern__text\">Bestätige durch Eingabe von <span class=\"sg-delete-confirmation-pattern__code\">DELETE</span>.</p><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Bestätigung</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" data-lager-document-delete-input=\"' + id + '\"></label><div class=\"sg-delete-confirmation-pattern__actions\"><button class=\"sg-interaction-element sg-button sg-button--active\" type=\"button\" data-lager-document-delete-cancel=\"' + id + '\">Abbrechen</button><button class=\"sg-interaction-element sg-button sg-button--process sg-button--process-inactive\" type=\"button\" disabled data-lager-document-delete-confirm=\"' + id + '\">Löschen</button></div></div>'; row.appendChild(confirmation); });";
|
||||||
|
echo " document.addEventListener('input', (event) => { const input = event.target.closest('[data-lager-document-delete-input]'); if (!input) { return; } const button = input.closest('[data-lager-document-confirmation]')?.querySelector('[data-lager-document-delete-confirm]'); const valid = input.value === 'DELETE'; if (button) { button.disabled = !valid; button.setAttribute('aria-disabled', String(!valid)); button.classList.toggle('sg-button--process-inactive', !valid); } });";
|
||||||
|
echo " document.addEventListener('click', async (event) => { const open = event.target.closest('[data-lager-document-delete]'); if (open) { const id = open.dataset.lagerDocumentDelete; const confirmation = open.closest('[data-lager-document-edit-row]')?.querySelector('[data-lager-document-confirmation=\"' + id + '\"]'); if (confirmation) { confirmation.hidden = false; } return; } const cancel = event.target.closest('[data-lager-document-delete-cancel]'); if (cancel) { const confirmation = cancel.closest('[data-lager-document-confirmation]'); if (confirmation) { confirmation.hidden = true; } return; } const confirm = event.target.closest('[data-lager-document-delete-confirm]'); if (!confirm || confirm.disabled) { return; } const confirmation = confirm.closest('[data-lager-document-confirmation]'); const id = confirm.dataset.lagerDocumentDeleteConfirm; confirm.disabled = true; try { await postLagerChange({ action: 'delete_document', documentId: id }); window.location.reload(); } catch (error) { confirm.disabled = false; const input = confirmation?.querySelector('[data-lager-document-delete-input]'); if (input) { input.setCustomValidity(error instanceof Error ? error.message : 'Dokument konnte nicht gelöscht werden'); input.reportValidity(); } } });";
|
||||||
echo " document.addEventListener('click', (event) => { const editButton = event.target.closest('[data-lager-edit-open]'); if (editButton) { const drawer = getDrawer(); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = false; } return; } const closeButton = event.target.closest('[data-lager-edit-close]'); if (closeButton) { const drawer = getDrawer(); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = true; } } });";
|
echo " document.addEventListener('click', (event) => { const editButton = event.target.closest('[data-lager-edit-open]'); if (editButton) { const drawer = getDrawer(); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = false; } return; } const closeButton = event.target.closest('[data-lager-edit-close]'); if (closeButton) { const drawer = getDrawer(); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (editForm) { editForm.hidden = true; } } });";
|
||||||
echo " const setLagerEditMode = (editing) => { const drawer = getDrawer(); const readView = drawer?.querySelector('[data-lager-read-view]'); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (readView) { readView.hidden = editing; } if (editForm) { editForm.hidden = !editing; } };";
|
echo " const setLagerEditMode = (editing) => { const drawer = getDrawer(); const readView = drawer?.querySelector('[data-lager-read-view]'); const editForm = drawer?.querySelector('[data-lager-edit-form]'); if (readView) { readView.hidden = editing; } if (editForm) { editForm.hidden = !editing; } };";
|
||||||
echo " document.addEventListener('click', (event) => { if (event.target.closest('[data-lager-edit-open]')) { setLagerEditMode(true); } if (event.target.closest('[data-lager-edit-close]')) { clearLagerMovementInputs(); setLagerEditMode(false); } });";
|
echo " document.addEventListener('click', (event) => { if (event.target.closest('[data-lager-edit-open]')) { setLagerEditMode(true); } if (event.target.closest('[data-lager-edit-close]')) { clearLagerMovementInputs(); setLagerEditMode(false); } });";
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../modules/shared/db.php';
|
||||||
|
require_once __DIR__ . '/../../modules/shared/auth/service.php';
|
||||||
|
require_once __DIR__ . '/../../modules/erp/lager/service.php';
|
||||||
|
|
||||||
|
$env = expand_env_values(parse_env_file(__DIR__ . '/../../../.env'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = connect_database($env);
|
||||||
|
auth_bootstrap_session();
|
||||||
|
if (auth_current_user($pdo) === null) {
|
||||||
|
http_response_code(401);
|
||||||
|
exit('Unauthorized');
|
||||||
|
}
|
||||||
|
$document = get_stock_lot_document($pdo, (int) ($_GET['id'] ?? 0));
|
||||||
|
$path = dirname(__DIR__, 2) . '/runtime/uploads/lager/' . basename((string) $document['stored_filename']);
|
||||||
|
if (!is_file($path)) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('Not Found');
|
||||||
|
}
|
||||||
|
header('Content-Type: application/octet-stream');
|
||||||
|
header('Content-Length: ' . (string) filesize($path));
|
||||||
|
header("Content-Disposition: attachment; filename*=UTF-8''" . rawurlencode((string) $document['original_filename']));
|
||||||
|
readfile($path);
|
||||||
|
} catch (Throwable) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('Not Found');
|
||||||
|
}
|
||||||
Reference in new issue
Block a user