diff --git a/public/userarea/delete_matrice_attachment.php b/public/userarea/delete_matrice_attachment.php new file mode 100644 index 0000000..4150ade --- /dev/null +++ b/public/userarea/delete_matrice_attachment.php @@ -0,0 +1,61 @@ + false, + 'message' => '' +]; + +try { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + throw new Exception('Metodo non consentito'); + } + + if (!isset($_POST['id']) || !is_numeric($_POST['id'])) { + throw new Exception('ID allegato non valido'); + } + + $id = (int)$_POST['id']; + + $db = DBHandlerSelect::getInstance(); + $pdo = $db->getConnection(); + + $stmt = $pdo->prepare("SELECT id, file_path FROM matrice_attachments WHERE id = :id LIMIT 1"); + $stmt->execute([':id' => $id]); + $attachment = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$attachment) { + throw new Exception('Allegato non trovato'); + } + + $filePathRelative = ltrim((string)$attachment['file_path'], '/\\'); + $filePathAbsolute = __DIR__ . '/' . $filePathRelative; + + $pdo->beginTransaction(); + + $deleteStmt = $pdo->prepare("DELETE FROM matrice_attachments WHERE id = :id"); + $deleteStmt->execute([':id' => $id]); + + if ($deleteStmt->rowCount() <= 0) { + throw new Exception('Impossibile eliminare il record allegato'); + } + + if (!empty($filePathRelative) && file_exists($filePathAbsolute) && is_file($filePathAbsolute)) { + @unlink($filePathAbsolute); + } + + $pdo->commit(); + + $response['success'] = true; + $response['message'] = 'Allegato eliminato correttamente'; +} catch (Throwable $e) { + if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) { + $pdo->rollBack(); + } + $response['message'] = $e->getMessage(); +} + +echo json_encode($response, JSON_UNESCAPED_UNICODE); +exit; diff --git a/public/userarea/delete_packaging_stock_lot.php b/public/userarea/delete_packaging_stock_lot.php new file mode 100644 index 0000000..633b043 --- /dev/null +++ b/public/userarea/delete_packaging_stock_lot.php @@ -0,0 +1,20 @@ + false, 'message' => 'Invalid id']); + exit; +} + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + $stmt = $pdo->prepare("DELETE FROM packaging_stock_lots WHERE id = ?"); + $stmt->execute([$id]); + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/get_matrice_attachments.php b/public/userarea/get_matrice_attachments.php new file mode 100644 index 0000000..7333915 --- /dev/null +++ b/public/userarea/get_matrice_attachments.php @@ -0,0 +1,65 @@ + false, + 'attachments' => [], + 'message' => '' +]; + +try { + if (!isset($_GET['id']) || !is_numeric($_GET['id'])) { + throw new Exception('ID matrice non valido'); + } + + $idmatrice = (int)$_GET['id']; + + $db = DBHandlerSelect::getInstance(); + $pdo = $db->getConnection(); + + $sql = "SELECT + id, + matrice_id, + file_name, + file_path, + file_type, + description, + sort_order, + created_at, + updated_at + FROM matrice_attachments + WHERE matrice_id = :matrice_id + ORDER BY sort_order ASC, id DESC"; + + $stmt = $pdo->prepare($sql); + $stmt->execute([':matrice_id' => $idmatrice]); + + $attachments = []; + + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $relativePath = ltrim((string)$row['file_path'], '/\\'); + + $attachments[] = [ + 'id' => (int)$row['id'], + 'matrice_id' => (int)$row['matrice_id'], + 'file_name' => $row['file_name'], + 'file_path' => $relativePath, + 'file_url' => $relativePath, + 'file_type' => $row['file_type'], + 'description' => $row['description'] ?? '', + 'sort_order' => (int)($row['sort_order'] ?? 0), + 'created_at' => !empty($row['created_at']) ? date('d/m/Y H:i', strtotime($row['created_at'])) : '', + 'updated_at' => !empty($row['updated_at']) ? date('d/m/Y H:i', strtotime($row['updated_at'])) : '' + ]; + } + + $response['success'] = true; + $response['attachments'] = $attachments; +} catch (Throwable $e) { + $response['message'] = $e->getMessage(); +} + +echo json_encode($response, JSON_UNESCAPED_UNICODE); +exit; diff --git a/public/userarea/get_packaging_stock_lots.php b/public/userarea/get_packaging_stock_lots.php new file mode 100644 index 0000000..033a226 --- /dev/null +++ b/public/userarea/get_packaging_stock_lots.php @@ -0,0 +1,30 @@ + false, 'message' => 'Invalid id']); + exit; +} + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +$sql = "SELECT + psl.id, + psl.idsupplier, + s.supplier_name, + psl.lot_code, + psl.expiry_date, + psl.qty + FROM packaging_stock_lots psl + INNER JOIN suppliers s ON s.idsupplier = psl.idsupplier + WHERE psl.idpackaging_item = ? + ORDER BY psl.id DESC"; + +$stmt = $pdo->prepare($sql); +$stmt->execute([$id]); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + +echo json_encode(['success' => true, 'rows' => $rows]); diff --git a/public/userarea/matrici.php b/public/userarea/matrici.php index 0d75b61..7cf3ca8 100644 --- a/public/userarea/matrici.php +++ b/public/userarea/matrici.php @@ -9,7 +9,7 @@ Gestione Matrici - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> - + @@ -17,7 +17,6 @@ - @@ -50,6 +49,14 @@ padding: 10px 20px; } + .btn-files { + background-color: #0dcaf0; + color: #0b2e38; + border-radius: 8px; + padding: 10px 18px; + font-weight: 600; + } + .table thead { background-color: #b9ebc7; text-align: center; @@ -79,13 +86,16 @@ color: #ff9800; } + .files { + color: #0dcaf0; + } + .thumb-img:hover { transform: scale(1.05); transition: 0.2s; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } - /* === DataTables search: make it stand out === */ .dataTables_wrapper .dataTables_filter { position: relative; margin-bottom: 12px; @@ -98,25 +108,21 @@ align-items: center; gap: 10px; justify-content: flex-end; - /* search allineato a destra */ } - /* input vero e proprio */ .dataTables_wrapper .dataTables_filter input { width: 340px !important; - /* più grande */ max-width: 100%; padding: 10px 14px !important; + padding-right: 42px !important; border-radius: 12px !important; border: 2px solid #198754 !important; - /* verde evidenza */ background: #fff !important; box-shadow: 0 6px 18px rgba(25, 135, 84, 0.18); outline: none !important; transition: all .15s ease; } - /* focus ancora più evidente */ .dataTables_wrapper .dataTables_filter input:focus { border-color: #146c43 !important; box-shadow: 0 0 0 0.2rem rgba(25, 135, 84, 0.25), 0 10px 22px rgba(25, 135, 84, 0.22) !important; @@ -133,15 +139,8 @@ font-size: 16px; } - /* spazio a destra per non “toccare” l’icona */ - .dataTables_wrapper .dataTables_filter input { - padding-right: 42px !important; - } - - /* --- FIX larghezze tabella matrici --- */ #tabellaMatrici { table-layout: fixed; - /* rende fisse le larghezze */ width: 100% !important; } @@ -150,55 +149,147 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - /* evita che il testo allarghi */ vertical-align: middle; } - /* Foto: più stretta */ #tabellaMatrici th:nth-child(1), #tabellaMatrici td:nth-child(1) { width: 90px; max-width: 90px; } - /* Nome: più largo */ #tabellaMatrici th:nth-child(2), #tabellaMatrici td:nth-child(2) { - width: 320px; - max-width: 320px; + width: 280px; + max-width: 280px; } - /* Cliente: più largo */ #tabellaMatrici th:nth-child(3), #tabellaMatrici td:nth-child(3) { - width: 320px; - max-width: 320px; + width: 260px; + max-width: 260px; } - /* Linee: media */ #tabellaMatrici th:nth-child(4), #tabellaMatrici td:nth-child(4) { width: 220px; max-width: 220px; } - - /* Azioni: fissa */ #tabellaMatrici th:nth-child(5), #tabellaMatrici td:nth-child(5) { - width: 170px; - max-width: 170px; + width: 220px; + max-width: 220px; } - /* Immagine: non “sfora” mai nella cella */ #tabellaMatrici td:nth-child(1) img { width: 70px; - /* puoi scendere a 60 */ height: 60px; object-fit: cover; } - + .dropzone-attachments { + border: 2px dashed #86b7fe; + border-radius: 16px; + background: #f8fbff; + min-height: 150px; + padding: 24px; + text-align: center; + transition: all .2s ease; + cursor: pointer; + } + + .dropzone-attachments.dragover { + background: #eef6ff; + border-color: #0d6efd; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.12); + } + + .dropzone-title { + font-weight: 700; + color: #1f2d3d; + font-size: 1.05rem; + } + + .dropzone-subtitle { + color: #6c757d; + margin-top: 6px; + font-size: 0.95rem; + } + + .attachments-selected-list .attachment-upload-row, + .attachments-existing-list .attachment-existing-row { + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 12px; + background: #fff; + margin-bottom: 10px; + } + + .attachment-file-icon { + font-size: 1.3rem; + min-width: 28px; + text-align: center; + } + + .attachment-meta-title { + font-weight: 700; + color: #1f2d3d; + word-break: break-word; + } + + .attachment-meta-sub { + font-size: 0.88rem; + color: #6c757d; + } + + .attachment-preview-thumb { + width: 54px; + height: 54px; + border-radius: 10px; + object-fit: cover; + border: 1px solid #dee2e6; + } + + .existing-attachments-wrap { + max-height: 500px; + overflow-y: auto; + padding-right: 4px; + } + + .selected-attachments-wrap { + max-height: 420px; + overflow-y: auto; + padding-right: 4px; + } + + .badge-type { + font-size: 0.78rem; + } + + .file-desc-input { + font-size: 0.95rem; + } + + .modal-xl-custom { + max-width: 1380px; + width: 95vw; + } + + .section-box { + background: #f8fafc; + border: 1px solid #e9ecef; + border-radius: 14px; + padding: 16px; + } + + .empty-attachments { + color: #6c757d; + font-style: italic; + text-align: center; + padding: 18px 8px; + } + @@ -222,7 +313,6 @@ -
@@ -236,7 +326,6 @@ - getConnection(); @@ -265,11 +354,11 @@ JOIN mescole ms ON ms.id = mm.idmescola GROUP BY mm.idmatrice ) mg ON mg.idmatrice = m.id - ORDER BY m.id DESC + ORDER BY + CASE WHEN TRIM(COALESCE(m.nome, '')) = '' THEN 1 ELSE 0 END, + m.nome ASC "); - - function formatDateIT($d) { if (!$d || $d == '0000-00-00') return ''; @@ -280,54 +369,45 @@ echo ""; } else { while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - $dataIT = formatDateIT($row['data_produzione']); - // gestione foto $foto = $row['photo'] ?? ''; $pathFoto = "photos/matrici/" . $foto; $placeholder = "assets/images/no-photo.png"; if ($foto && file_exists($pathFoto)) { - $thumb = $pathFoto; + $thumb = $pathFoto; $hasPhoto = true; } else { - $thumb = $placeholder; + $thumb = $placeholder; $hasPhoto = false; } echo ""; - // colonna FOTO - // colonna FOTO (robusta: NP appare solo se l'immagine non si carica) - $imgSrc = $hasPhoto ? $thumb : ''; // se non c'è foto, src vuoto → trigger onerror + $imgSrc = $hasPhoto ? $thumb : ''; echo ""; + + NP + "; - - // colonna NOME (con tooltip) echo ""; - // colonna CLIENTE (con tooltip) echo ""; - - // colonna LINEE $lineeTxt = $row['linee_associate'] ?? '—'; $mescoleTxt = $row['mescole_associate'] ?? ''; $mescoleCount = (int)($row['mescole_count'] ?? 0); @@ -335,12 +415,12 @@ $btnMescole = ''; if ($mescoleCount > 0) { $btnMescole = " "; + class='btn btn-sm btn-outline-secondary ms-2 show-mescole' + data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "' + data-mescole='" . htmlspecialchars($mescoleTxt, ENT_QUOTES) . "' + data-bs-toggle='tooltip' data-bs-title='Vedi mescole associate'> + + "; } echo ""; - - // colonna AZIONI echo ""; + + "; echo ""; } } - - ?> -
Nessuna matrice presente
- - NP - " . htmlspecialchars($row['nome']) . "" . htmlspecialchars($row['cliente']) . "" @@ -348,8 +428,6 @@ . $btnMescole . " - -
+ @@ -439,6 +520,7 @@ + + + + + + + + + + + - - - - - @@ -375,6 +505,7 @@ + \ No newline at end of file diff --git a/public/userarea/production_line_view2.php b/public/userarea/production_line_view2.php index 8f9e722..fc39c95 100644 --- a/public/userarea/production_line_view2.php +++ b/public/userarea/production_line_view2.php @@ -268,6 +268,7 @@ if (!empty($_GET['ajax'])) { // --- RECORD IN PRODUZIONE (2,7,8) $sql = "SELECT p.*, + m.id AS matrice_id, m.nome AS matrice, m.photo AS matrice_photo, ( @@ -329,6 +330,7 @@ if (!empty($_GET['ajax'])) { // --- RECORD IN STATO 6 ORDINATI PER PRIORITY $sql2 = "SELECT p.*, + m.id AS matrice_id, m.nome AS matrice, m.photo AS matrice_photo, ( @@ -930,6 +932,10 @@ if (!empty($_GET['ajax'])) { margin-left: auto; /* la porta tutta a destra */ } + + .show-matrice-files i { + font-size: 1.5rem; + } @@ -1277,8 +1283,219 @@ if (!empty($_GET['ajax'])) { loadRecords(); }); + function escapeHtml(str) { + return String(str || '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function getFileExt(filename) { + const parts = String(filename || '').split('.'); + return parts.length > 1 ? parts.pop().toLowerCase() : ''; + } + + function isImageExt(ext) { + return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif'].includes(ext); + } + + function getFileIcon(ext) { + if (isImageExt(ext)) return '🖼️'; + if (ext === 'pdf') return '📕'; + if (['doc', 'docx'].includes(ext)) return '📘'; + return '📎'; + } + + function getFileBadge(ext) { + if (isImageExt(ext)) return 'Immagine'; + if (ext === 'pdf') return 'PDF'; + if (['doc', 'docx'].includes(ext)) return 'DOC'; + return 'File'; + } + + function openFilePreview(filePath, fileName) { + const ext = getFileExt(fileName); + $("#filePreviewTitle").text(fileName || 'Anteprima file'); + + let html = ''; + + if (isImageExt(ext)) { + html = ` +
+ ${escapeHtml(fileName)} +
+ `; + } else if (ext === 'pdf') { + html = ` + + `; + } else { + html = ` +
+
${getFileIcon(ext)}
+
Anteprima non disponibile per questo formato
+ + Apri file + +
+ `; + } + + $("#filePreviewContainer").html(html); + $("#filePreviewModal").addClass("active"); + } + + function loadMatriceAttachments(matriceId, matriceNome) { + $("#mf_matrice_name").text(matriceNome || ''); + $("#matriceFilesList").html('
Caricamento file...
'); + + $.getJSON("get_matrice_attachments.php", { + id: matriceId + }, function(r) { + if (!r.success) { + $("#matriceFilesList").html('
Errore nel caricamento degli allegati.
'); + return; + } + + const files = r.attachments || []; + + if (!files.length) { + $("#matriceFilesList").html('
Nessun allegato disponibile per questa matrice.
'); + return; + } + + let html = ''; + + files.forEach(f => { + const fileName = f.file_name || ''; + const filePath = f.file_url || f.file_path || ''; + const desc = f.description || ''; + const createdAt = f.created_at || ''; + const ext = getFileExt(fileName); + const icon = getFileIcon(ext); + const badge = getFileBadge(ext); + + let actionHtml = ''; + if (isImageExt(ext) || ext === 'pdf') { + actionHtml = ` + + `; + } else { + actionHtml = ` + + Apri + + `; + } + + let previewHtml = ''; + if (isImageExt(ext)) { + previewHtml = ` + ${escapeHtml(fileName)} + `; + } else { + previewHtml = ` +
+ ${icon} +
+ `; + } + + html += ` +
+
+
${previewHtml}
+ +
+
+
+
${escapeHtml(fileName)}
+
${badge}
+ ${createdAt ? `
${escapeHtml(createdAt)}
` : ''} +
+
${actionHtml}
+
+ +
+ ${desc ? escapeHtml(desc) : 'Nessuna descrizione'} +
+
+
+
+ `; + }); + + $("#matriceFilesList").html(html); + }).fail(function() { + $("#matriceFilesList").html('
Errore di comunicazione col server.
'); + }); + } + $(document).on("click", ".show-matrice-files", function(e) { + e.stopPropagation(); + + const matriceId = $(this).data("matrice-id"); + const matriceNome = $(this).data("matrice-nome") || ""; + + if (!matriceId) { + alert("Nessuna matrice associata a questo record."); + return; + } + + loadMatriceAttachments(matriceId, matriceNome); + $("#matriceFilesModal").addClass("active"); + }); + + $(document).on("click", "#closeMatriceFilesModal", function() { + $("#matriceFilesModal").removeClass("active"); + }); + + $(document).on("click", "#matriceFilesModal", function(e) { + if ($(e.target).is("#matriceFilesModal")) { + $("#matriceFilesModal").removeClass("active"); + } + }); + + $(document).on("click", ".open-matrice-file-preview", function(e) { + e.stopPropagation(); + + const file = $(this).data("file") || ""; + const name = $(this).data("name") || "Anteprima file"; + openFilePreview(file, name); + }); + + $(document).on("click", "#closeFilePreviewModal", function() { + $("#filePreviewModal").removeClass("active"); + $("#filePreviewContainer").html(""); + }); + + $(document).on("click", "#filePreviewModal", function(e) { + if ($(e.target).is("#filePreviewModal")) { + $("#filePreviewModal").removeClass("active"); + $("#filePreviewContainer").html(""); + } + }); + + $(document).on("click", "#filePreviewModal .modal-content", function(e) { + e.stopPropagation(); + }); // --- CARICA RECORD --- function loadRecords() { @@ -1879,12 +2096,45 @@ if (!empty($_GET['ajax'])) { }); + + + + \ No newline at end of file diff --git a/public/userarea/render_production_card.php b/public/userarea/render_production_card.php index 8523ae0..0499e28 100644 --- a/public/userarea/render_production_card.php +++ b/public/userarea/render_production_card.php @@ -205,18 +205,27 @@ if ($matricePhoto) { + + + + background:#0d6efd; + color:#ffffff; + font-size:0.78rem; + font-weight:600; + border-radius:999px; + padding:0.30rem 0.85rem; + white-space:nowrap; + "> diff --git a/public/userarea/save_packaging_item.php b/public/userarea/save_packaging_item.php new file mode 100644 index 0000000..f72dcbb --- /dev/null +++ b/public/userarea/save_packaging_item.php @@ -0,0 +1,24 @@ + false, 'message' => 'Invalid input']); + exit; +} + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + $stmt = $pdo->prepare("INSERT INTO packaging_items (category, item_name, item_code) VALUES (?,?,?)"); + $stmt->execute([$category, $item_name, $item_code]); + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/save_packaging_stock_lot.php b/public/userarea/save_packaging_stock_lot.php new file mode 100644 index 0000000..18bc290 --- /dev/null +++ b/public/userarea/save_packaging_stock_lot.php @@ -0,0 +1,40 @@ + false, 'message' => 'Missing required fields']); + exit; +} +if ($qty < 0) { + echo json_encode(['success' => false, 'message' => 'Quantity cannot be negative']); + exit; +} +if ($expiry_date === '') $expiry_date = null; + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + $stmt = $pdo->prepare("INSERT INTO packaging_stock_lots + (idpackaging_item, idsupplier, lot_code, expiry_date, qty) + VALUES (?,?,?,?,?)"); + + $stmt->execute([ + $idpackaging_item, + $idsupplier, + ($lot_code !== '' ? $lot_code : null), + $expiry_date, + $qty + ]); + + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/toggle_mescola_active.php b/public/userarea/toggle_mescola_active.php new file mode 100644 index 0000000..d6c6146 --- /dev/null +++ b/public/userarea/toggle_mescola_active.php @@ -0,0 +1,28 @@ + false, 'message' => 'Invalid id']); + exit; +} + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + // flip 1 <-> 0 + $stmt = $pdo->prepare("UPDATE mescole SET is_active = IF(is_active=1,0,1) WHERE id = ?"); + $stmt->execute([$id]); + + // verifica righe aggiornate + if ($stmt->rowCount() === 0) { + echo json_encode(['success' => false, 'message' => 'No rows updated (id not found?)']); + exit; + } + + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/toggle_packaging_item_active.php b/public/userarea/toggle_packaging_item_active.php new file mode 100644 index 0000000..6259ca2 --- /dev/null +++ b/public/userarea/toggle_packaging_item_active.php @@ -0,0 +1,24 @@ + false, 'message' => 'Invalid id']); + exit; +} + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + $stmt = $pdo->prepare("UPDATE packaging_items SET is_active = IF(is_active=1,0,1) WHERE id = ?"); + $stmt->execute([$id]); + if ($stmt->rowCount() === 0) { + echo json_encode(['success' => false, 'message' => 'No rows updated']); + exit; + } + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/update_mescola_lot_qty.php b/public/userarea/update_mescola_lot_qty.php new file mode 100644 index 0000000..21346df --- /dev/null +++ b/public/userarea/update_mescola_lot_qty.php @@ -0,0 +1,33 @@ + false, 'message' => 'Invalid input']); + exit; +} + +if ($qty < 0) { + echo json_encode(['success' => false, 'message' => 'Quantity cannot be negative']); + exit; +} + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + $stmt = $pdo->prepare("UPDATE mescole_supplier_lots SET qty = ? WHERE id = ?"); + $stmt->execute([$qty, $id]); + + if ($stmt->rowCount() === 0) { + echo json_encode(['success' => false, 'message' => 'No rows updated (id not found?)']); + exit; + } + + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/update_packaging_stock_lot.php b/public/userarea/update_packaging_stock_lot.php new file mode 100644 index 0000000..dd3eec3 --- /dev/null +++ b/public/userarea/update_packaging_stock_lot.php @@ -0,0 +1,42 @@ + false, 'message' => 'Invalid input']); + exit; +} +if ($qty < 0) { + echo json_encode(['success' => false, 'message' => 'Quantity cannot be negative']); + exit; +} +if ($expiry_date === '') $expiry_date = null; + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + $stmt = $pdo->prepare("UPDATE packaging_stock_lots + SET idsupplier=?, lot_code=?, expiry_date=?, qty=? + WHERE id=? AND idpackaging_item=?"); + + $stmt->execute([ + $idsupplier, + ($lot_code !== '' ? $lot_code : null), + $expiry_date, + $qty, + $id, + $idpackaging_item + ]); + + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/update_packaging_stock_qty.php b/public/userarea/update_packaging_stock_qty.php new file mode 100644 index 0000000..8029736 --- /dev/null +++ b/public/userarea/update_packaging_stock_qty.php @@ -0,0 +1,33 @@ + false, 'message' => 'Invalid input']); + exit; +} + +if ($qty < 0) { + echo json_encode(['success' => false, 'message' => 'Quantity cannot be negative']); + exit; +} + +$db = DBHandlerSelect::getInstance(); +$pdo = $db->getConnection(); + +try { + $stmt = $pdo->prepare("UPDATE packaging_stock_lots SET qty = ? WHERE id = ?"); + $stmt->execute([$qty, $id]); + + if ($stmt->rowCount() === 0) { + echo json_encode(['success' => false, 'message' => 'No rows updated (id not found?)']); + exit; + } + + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/upload_matrice_attachments.php b/public/userarea/upload_matrice_attachments.php new file mode 100644 index 0000000..e43abe2 --- /dev/null +++ b/public/userarea/upload_matrice_attachments.php @@ -0,0 +1,189 @@ + false, + 'message' => '', + 'uploaded' => [], + 'errors' => [] +]; + +try { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + throw new Exception('Metodo non consentito'); + } + + if (!isset($_POST['idmatrice']) || !is_numeric($_POST['idmatrice'])) { + throw new Exception('ID matrice non valido'); + } + + if (!isset($_FILES['files'])) { + throw new Exception('Nessun file ricevuto'); + } + + $idmatrice = (int)$_POST['idmatrice']; + $descriptions = isset($_POST['descriptions']) && is_array($_POST['descriptions']) ? $_POST['descriptions'] : []; + + $db = DBHandlerSelect::getInstance(); + $pdo = $db->getConnection(); + + // Verifica esistenza matrice + $checkStmt = $pdo->prepare("SELECT id FROM matrice WHERE id = :id LIMIT 1"); + $checkStmt->execute([':id' => $idmatrice]); + if (!$checkStmt->fetchColumn()) { + throw new Exception('Matrice non trovata'); + } + + $uploadDirRelative = 'photos/matrici/allegati/'; + $uploadDirAbsolute = __DIR__ . '/' . $uploadDirRelative; + + if (!is_dir($uploadDirAbsolute)) { + if (!mkdir($uploadDirAbsolute, 0775, true) && !is_dir($uploadDirAbsolute)) { + throw new Exception('Impossibile creare la cartella di upload'); + } + } + + $allowedExtensions = [ + 'pdf', + 'jpg', + 'jpeg', + 'png', + 'gif', + 'bmp', + 'webp', + 'heic', + 'heif', + 'doc', + 'docx' + ]; + + $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic', 'heif']; + + $fileNames = $_FILES['files']['name'] ?? []; + $tmpNames = $_FILES['files']['tmp_name'] ?? []; + $errors = $_FILES['files']['error'] ?? []; + $sizes = $_FILES['files']['size'] ?? []; + + if (!is_array($fileNames) || count($fileNames) === 0) { + throw new Exception('Nessun file valido ricevuto'); + } + + $maxFileSize = 20 * 1024 * 1024; // 20 MB per file + + $pdo->beginTransaction(); + + $sortStmt = $pdo->prepare("SELECT COALESCE(MAX(sort_order), 0) FROM matrice_attachments WHERE matrice_id = :matrice_id"); + $sortStmt->execute([':matrice_id' => $idmatrice]); + $nextSort = (int)$sortStmt->fetchColumn(); + + $insertSql = "INSERT INTO matrice_attachments + (matrice_id, file_name, file_path, file_type, description, sort_order, created_at, updated_at) + VALUES + (:matrice_id, :file_name, :file_path, :file_type, :description, :sort_order, NOW(), NOW())"; + $insertStmt = $pdo->prepare($insertSql); + + foreach ($fileNames as $index => $originalName) { + $originalName = trim((string)$originalName); + $tmpName = $tmpNames[$index] ?? ''; + $errorCode = $errors[$index] ?? UPLOAD_ERR_NO_FILE; + $size = (int)($sizes[$index] ?? 0); + $description = isset($descriptions[$index]) ? trim((string)$descriptions[$index]) : ''; + + if ($errorCode === UPLOAD_ERR_NO_FILE) { + continue; + } + + if ($errorCode !== UPLOAD_ERR_OK) { + $response['errors'][] = "Errore upload file: {$originalName}"; + continue; + } + + if (!is_uploaded_file($tmpName)) { + $response['errors'][] = "File non valido: {$originalName}"; + continue; + } + + if ($size <= 0) { + $response['errors'][] = "File vuoto: {$originalName}"; + continue; + } + + if ($size > $maxFileSize) { + $response['errors'][] = "File troppo grande (max 20 MB): {$originalName}"; + continue; + } + + $safeOriginalName = preg_replace('/[^\w.\- ]+/u', '_', $originalName); + $extension = strtolower(pathinfo($safeOriginalName, PATHINFO_EXTENSION)); + + if (!in_array($extension, $allowedExtensions, true)) { + $response['errors'][] = "Formato non ammesso: {$originalName}"; + continue; + } + + if (in_array($extension, $imageExtensions, true)) { + $fileType = 'image'; + } elseif ($extension === 'pdf') { + $fileType = 'pdf'; + } elseif (in_array($extension, ['doc', 'docx'], true)) { + $fileType = 'doc'; + } else { + $fileType = 'other'; + } + + $uniqueName = 'matrice_' . $idmatrice . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $extension; + $destinationAbsolute = $uploadDirAbsolute . $uniqueName; + $destinationRelative = $uploadDirRelative . $uniqueName; + + if (!move_uploaded_file($tmpName, $destinationAbsolute)) { + $response['errors'][] = "Impossibile salvare il file: {$originalName}"; + continue; + } + + $nextSort++; + + $insertStmt->execute([ + ':matrice_id' => $idmatrice, + ':file_name' => $safeOriginalName, + ':file_path' => $destinationRelative, + ':file_type' => $fileType, + ':description' => $description, + ':sort_order' => $nextSort + ]); + + $response['uploaded'][] = [ + 'id' => (int)$pdo->lastInsertId(), + 'file_name' => $safeOriginalName, + 'file_path' => $destinationRelative, + 'file_type' => $fileType, + 'description' => $description + ]; + } + + if (empty($response['uploaded']) && !empty($response['errors'])) { + $pdo->rollBack(); + $response['message'] = 'Nessun file caricato'; + echo json_encode($response, JSON_UNESCAPED_UNICODE); + exit; + } + + $pdo->commit(); + + $response['success'] = true; + + if (!empty($response['errors'])) { + $response['message'] = 'Upload completato con alcuni avvisi'; + } else { + $response['message'] = 'File caricati correttamente'; + } +} catch (Throwable $e) { + if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) { + $pdo->rollBack(); + } + $response['message'] = $e->getMessage(); +} + +echo json_encode($response, JSON_UNESCAPED_UNICODE); +exit; diff --git a/public/userarea/warehouse_dashboard.php b/public/userarea/warehouse_dashboard.php new file mode 100644 index 0000000..132e988 --- /dev/null +++ b/public/userarea/warehouse_dashboard.php @@ -0,0 +1,191 @@ + + + + + + + + + + Dashboard Magazzino - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> + + + + + + + + +
+ + + +
+
+ +

Magazzino

+ + + + +
Materiali
+
+ + + + + +
+ + +
Setup (in espansione)
+
+ +
+ +
+
+ + + +
+ + + \ No newline at end of file diff --git a/public/userarea/warehouse_imballaggi.php b/public/userarea/warehouse_imballaggi.php new file mode 100644 index 0000000..b5ec475 --- /dev/null +++ b/public/userarea/warehouse_imballaggi.php @@ -0,0 +1,409 @@ + + + + + + + + + + Magazzino Imballaggi - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
Magazzino - Imballaggi
+ +
+ +
+ + +
+
+ + + + + + +
+ + +
+
+ +
+ Cerca per codice, nome, fornitore o lotto +
+
+ + getConnection(); + + $cat = $_GET['cat'] ?? 'all'; + $allowedCats = ['all', 'PACKAGING_TYPE', 'BOX', 'PALLET', 'OTHER']; + if (!in_array($cat, $allowedCats, true)) $cat = 'all'; + + $active = $_GET['active'] ?? '1'; + if (!in_array($active, ['all', '0', '1'], true)) $active = '1'; + + $where = []; + $params = []; + + if ($cat !== 'all') { + $where[] = "pi.category = ?"; + $params[] = $cat; + } + + if ($active !== 'all') { + $where[] = "pi.is_active = ?"; + $params[] = (int)$active; + } + + $whereSql = $where ? ("WHERE " . implode(" AND ", $where)) : ""; + + $sql = " + SELECT + psl.id AS stock_id, + pi.id AS item_id, + pi.category, + pi.item_name, + pi.item_code, + pi.is_active, + s.supplier_name, + psl.lot_code, + psl.expiry_date, + psl.qty + FROM packaging_stock_lots psl + INNER JOIN packaging_items pi ON pi.id = psl.idpackaging_item + INNER JOIN suppliers s ON s.idsupplier = psl.idsupplier + $whereSql + ORDER BY pi.category ASC, pi.item_name ASC, s.supplier_name ASC, psl.lot_code ASC, psl.id DESC + "; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $today = date('Y-m-d'); + + function catLabel($c) + { + return match ($c) { + 'PACKAGING_TYPE' => 'Tipo Confezione', + 'BOX' => 'Scatola', + 'PALLET' => 'Pallet', + default => $c + }; + } + ?> + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + "; + } else { + foreach ($rows as $r) { + $expiry = $r['expiry_date'] ?? ''; + $isExpired = ($expiry !== '' && $expiry < $today); + $isActiveItem = ((int)$r['is_active'] === 1); + + $trClass = []; + if ($isExpired) $trClass[] = 'expired'; + if (!$isActiveItem) $trClass[] = 'inactive-item'; + $trClassStr = $trClass ? " class='" . implode(' ', $trClass) . "'" : ""; + + $qtyVal = number_format((float)$r['qty'], 3, '.', ''); + $expiryShow = $expiry ? htmlspecialchars($expiry) : '-'; + $lotShow = htmlspecialchars($r['lot_code'] ?? ''); + + echo " + + + + + + + + + + "; + } + } + ?> + +
IDCategoriaNomeCodiceFornitoreLottoScadenzaQ.tàSalva
-Nessuna riga presente-------
{$r['stock_id']}" . htmlspecialchars(catLabel($r['category'])) . "" . htmlspecialchars($r['item_name']) . "" . htmlspecialchars($r['item_code']) . "" . htmlspecialchars($r['supplier_name']) . "{$lotShow}{$expiryShow} + + + +
+
+ +
+ * Riga rossa = scaduta. * Riga opaca = item disattivo (ma stock ancora presente). +
+ +
+
+
+
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/public/userarea/warehouse_mescole.php b/public/userarea/warehouse_mescole.php new file mode 100644 index 0000000..c98ad6b --- /dev/null +++ b/public/userarea/warehouse_mescole.php @@ -0,0 +1,378 @@ + + + + + + + + + + Magazzino Mescole - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
Magazzino - Mescole
+
+ +
+
+ +
+ + +
+
+ + + +
+ + +
+
+ +
+ Suggerimento: usa la ricerca tabella per trovare lotto/fornitore +
+
+ + getConnection(); + + // filtro mescole attive/inattive + $activeFilter = $_GET['active'] ?? '1'; + if (!in_array($activeFilter, ['all', '0', '1'], true)) { + $activeFilter = '1'; + } + + $where = ""; + $params = []; + if ($activeFilter !== 'all') { + $where = "WHERE m.is_active = ?"; + $params[] = (int)$activeFilter; + } + + // Query: tutte le righe lotto-fornitore con nome uscita + $sql = " + SELECT + msl.id AS lot_id, + m.id AS idmescola, + m.nomeuscita, + m.is_active, + s.supplier_name, + msl.supplier_mix_name, + msl.lot_code, + msl.expiry_date, + msl.qty + FROM mescole_supplier_lots msl + INNER JOIN mescole m ON m.id = msl.idmescola + INNER JOIN suppliers s ON s.idsupplier = msl.idsupplier + $where + ORDER BY m.nomeuscita ASC, s.supplier_name ASC, msl.lot_code ASC, msl.id DESC + "; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $today = date('Y-m-d'); + ?> + +
+ + + + + + + + + + + + + + + + + + + + + + + + "; + } else { + foreach ($rows as $r) { + $expiry = $r['expiry_date'] ?? ''; + $isExpired = ($expiry !== '' && $expiry < $today); + $isActiveMix = ((int)$r['is_active'] === 1); + + $trClass = []; + if ($isExpired) $trClass[] = 'expired'; + if (!$isActiveMix) $trClass[] = 'inactive-mix'; + $trClassStr = $trClass ? " class='" . implode(' ', $trClass) . "'" : ""; + + $qtyVal = number_format((float)$r['qty'], 3, '.', ''); + $expiryShow = $expiry ? htmlspecialchars($expiry) : '-'; + + echo " + + + + + + + + + "; + } + } + ?> + +
IDNome UscitaFornitoreNome FornitoreLottoScadenzaQ.tàSalva
-Nessuna riga presente------
{$r['lot_id']}" . htmlspecialchars($r['nomeuscita']) . "" . htmlspecialchars($r['supplier_name']) . "" . htmlspecialchars($r['supplier_mix_name']) . "" . htmlspecialchars($r['lot_code'] ?? '') . "{$expiryShow} + + + +
+
+ +
+ * Riga rossa = scaduta. * Riga opaca = mescola disattiva (ma lotto ancora presente). +
+ +
+
+
+
+ + +
+ + + + + + + + \ No newline at end of file