fixed import fogli di lavoro and disegni tecnici
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class AddIsDisegnoToMatriceAttachments extends AbstractMigration
|
||||
{
|
||||
public function change(): void
|
||||
{
|
||||
$this->table('matrice_attachments')
|
||||
->addColumn('is_disegno', 'boolean', [
|
||||
'default' => false,
|
||||
'null' => false,
|
||||
])
|
||||
->update();
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,116 @@
|
||||
});
|
||||
};
|
||||
|
||||
let wsDisegniList = [];
|
||||
|
||||
function wsFileExt(name) {
|
||||
const p = String(name || "").split(".");
|
||||
return p.length > 1 ? p.pop().toLowerCase() : "";
|
||||
}
|
||||
|
||||
function wsIsImage(ext) {
|
||||
return ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext);
|
||||
}
|
||||
|
||||
function showWorksheetDisegno(index) {
|
||||
const f = wsDisegniList[index];
|
||||
if (!f) return;
|
||||
|
||||
$(".ws-disegno-item").removeClass("active");
|
||||
$(`.ws-disegno-item[data-index="${index}"]`).addClass("active");
|
||||
|
||||
const name = f.file_name || "";
|
||||
const path = f.file_url || f.file_path || "";
|
||||
const ext = wsFileExt(name);
|
||||
|
||||
let viewer;
|
||||
if (ext === "pdf") {
|
||||
viewer = `<iframe src="${escapeHtml(path)}#zoom=page-width"></iframe>`;
|
||||
} else if (wsIsImage(ext)) {
|
||||
viewer = `<div class="img-wrap"><img src="${escapeHtml(path)}" alt="${escapeHtml(name)}"></div>`;
|
||||
} else {
|
||||
viewer = `<div class="h-100 d-flex justify-content-center align-items-center text-muted">Anteprima non disponibile per questo formato: usa "Apri in nuova scheda"</div>`;
|
||||
}
|
||||
|
||||
$("#wsDisegnoToolbar").html(`
|
||||
<div>
|
||||
<div class="fw-bold">${escapeHtml(name)}</div>
|
||||
${f.description ? `<div class="small text-muted">${escapeHtml(f.description)}</div>` : ""}
|
||||
</div>
|
||||
<a href="${escapeHtml(path)}" target="_blank" class="worksheet-open-link">
|
||||
<i class="fa-solid fa-arrow-up-right-from-square"></i> Apri in nuova scheda
|
||||
</a>
|
||||
`);
|
||||
$("#wsDisegnoViewer").html(viewer);
|
||||
}
|
||||
|
||||
function loadWorksheetDisegni(idmatrice) {
|
||||
wsDisegniList = [];
|
||||
const $c = $("#wsDisegniContainer");
|
||||
|
||||
if (!idmatrice) {
|
||||
$("#wsDisegniCount").text("0");
|
||||
$c.html(
|
||||
'<div class="text-muted">Nessun profilo collegato a questo foglio.</div>',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("get_matrice_attachments.php?id=" + encodeURIComponent(idmatrice))
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
if (!data.success)
|
||||
throw new Error(
|
||||
data.message || "Errore caricamento allegati",
|
||||
);
|
||||
|
||||
wsDisegniList = (data.attachments || []).filter(
|
||||
(a) => parseInt(a.is_disegno, 10) === 1,
|
||||
);
|
||||
$("#wsDisegniCount").text(wsDisegniList.length);
|
||||
|
||||
if (!wsDisegniList.length) {
|
||||
$c.html(
|
||||
'<div class="text-muted p-3">Nessun disegno tecnico per questo profilo. ' +
|
||||
"Si possono segnare dalla gestione matrici (graffetta → pulsante compasso).</div>",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let listHtml = "";
|
||||
if (wsDisegniList.length > 1) {
|
||||
listHtml =
|
||||
'<div class="ws-disegni-list">' +
|
||||
wsDisegniList
|
||||
.map(
|
||||
(f, i) =>
|
||||
`<button type="button" class="ws-disegno-item" data-index="${i}">📐 ${escapeHtml(f.file_name || "Disegno " + (i + 1))}</button>`,
|
||||
)
|
||||
.join("") +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
$c.html(`
|
||||
${listHtml}
|
||||
<div class="ws-disegno-toolbar" id="wsDisegnoToolbar"></div>
|
||||
<div class="ws-disegno-viewer" id="wsDisegnoViewer"></div>
|
||||
`);
|
||||
|
||||
// Se l'utente ha già aperto il tab mentre caricava, mostro subito il primo
|
||||
if ($('.ws-tab[data-tab="disegni"]').hasClass("active")) {
|
||||
showWorksheetDisegno(0);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
$("#wsDisegniCount").text("!");
|
||||
$c.html(
|
||||
'<div class="text-danger">Errore nel caricamento dei disegni: ' +
|
||||
escapeHtml(err.message || "") +
|
||||
"</div>",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
window.loadWorksheetDetail = function (worksheetId, endpoint) {
|
||||
endpoint = endpoint || "ajax/worksheet-linked-data.php";
|
||||
|
||||
@@ -215,8 +325,8 @@
|
||||
`;
|
||||
}
|
||||
|
||||
const html = `
|
||||
<div class="worksheet-title-box mb-4">
|
||||
const headerHtml = `
|
||||
<div class="worksheet-title-box mb-3">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap">
|
||||
<div>
|
||||
<h4 class="mb-1">${escapeHtml(ws.worksheet_number_label || "—")} / Rev. ${escapeHtml(ws.revision_label || "0")}</h4>
|
||||
@@ -237,6 +347,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
|
||||
const html = `
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<div class="readonly-card">
|
||||
@@ -324,7 +437,31 @@
|
||||
</div>
|
||||
`;
|
||||
|
||||
$("#worksheetDetailContainer").html(html);
|
||||
const fullHtml = `
|
||||
${headerHtml}
|
||||
|
||||
<div class="ws-tabs">
|
||||
<button type="button" class="ws-tab active" data-tab="dati">
|
||||
<i class="fa-solid fa-file-lines me-1"></i> Dati foglio
|
||||
</button>
|
||||
<button type="button" class="ws-tab" data-tab="disegni">
|
||||
📐 Disegni tecnici <span class="ws-tab-count" id="wsDisegniCount">…</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="ws-tab-pane active" data-pane="dati">
|
||||
${html}
|
||||
</div>
|
||||
|
||||
<div class="ws-tab-pane" data-pane="disegni">
|
||||
<div id="wsDisegniContainer">
|
||||
<div class="text-muted">Caricamento disegni tecnici...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$("#worksheetDetailContainer").html(fullHtml);
|
||||
loadWorksheetDisegni(ws.idmatrice);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Catch loadMatriceWorksheets:", err);
|
||||
@@ -349,6 +486,29 @@
|
||||
).show();
|
||||
});
|
||||
|
||||
$(document).on("click", ".ws-tab", function () {
|
||||
const tab = $(this).data("tab");
|
||||
|
||||
$(".ws-tab").removeClass("active");
|
||||
$(this).addClass("active");
|
||||
|
||||
$(".ws-tab-pane").removeClass("active");
|
||||
$(`.ws-tab-pane[data-pane="${tab}"]`).addClass("active");
|
||||
|
||||
// Carico il PDF solo la prima volta che si apre il tab
|
||||
if (
|
||||
tab === "disegni" &&
|
||||
wsDisegniList.length &&
|
||||
$("#wsDisegnoViewer").is(":empty")
|
||||
) {
|
||||
showWorksheetDisegno(0);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", ".ws-disegno-item", function () {
|
||||
showWorksheetDisegno(parseInt($(this).data("index"), 10));
|
||||
});
|
||||
|
||||
$(document).on("click", ".open-worksheet-detail", function () {
|
||||
const worksheetId = $(this).data("id");
|
||||
const endpoint =
|
||||
|
||||
@@ -27,6 +27,7 @@ try {
|
||||
file_type,
|
||||
description,
|
||||
sort_order,
|
||||
is_disegno,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM matrice_attachments
|
||||
@@ -50,6 +51,7 @@ try {
|
||||
'file_type' => $row['file_type'],
|
||||
'description' => $row['description'] ?? '',
|
||||
'sort_order' => (int)($row['sort_order'] ?? 0),
|
||||
'is_disegno' => (int)($row['is_disegno'] ?? 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'])) : ''
|
||||
];
|
||||
|
||||
@@ -237,10 +237,12 @@ function extract_fields($grid)
|
||||
$out['speed_expected_m_h'] = null;
|
||||
$rv = row_by_label($grid, 'velocita di produzione');
|
||||
if ($rv) {
|
||||
$out['speed_expected_kg_h'] = num_or_null(cell($grid, $rv, 'C'));
|
||||
$v = num_or_null(cell($grid, $rv, 'C'));
|
||||
$out['speed_expected_kg_h'] = $v === null ? null : round($v, 2);
|
||||
for ($i = $rv; $i <= $rv + 2; $i++) {
|
||||
if (norm(cell($grid, $i, 'B')) === 'm/h') {
|
||||
$out['speed_expected_m_h'] = num_or_null(cell($grid, $i, 'C'));
|
||||
$v = num_or_null(cell($grid, $i, 'C'));
|
||||
$out['speed_expected_m_h'] = $v === null ? null : round($v, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -267,25 +269,37 @@ function extract_fields($grid)
|
||||
* 3) MATCHER su DB
|
||||
* ========================================================================= */
|
||||
|
||||
function norm_mesc($s)
|
||||
function strip_code($s)
|
||||
{
|
||||
$s = strtolower((string)$s);
|
||||
$s = preg_replace('/\s+/', ' ', $s);
|
||||
return trim($s);
|
||||
$s = str_replace(['à', 'è', 'é', 'ì', 'ò', 'ù'], ['a', 'e', 'e', 'i', 'o', 'u'], $s);
|
||||
return preg_replace('/[^a-z0-9]+/', '', $s); // solo lettere + cifre (ignora spazi e punteggiatura)
|
||||
}
|
||||
|
||||
/** porzione iniziale prima del primo separatore descrittivo (es. "ATES 71 - EPDM..." -> "ATES 71") */
|
||||
function raw_head($s)
|
||||
{
|
||||
$parts = preg_split('/\s*[-–—|(:,]\s*/u', (string)$s, 2);
|
||||
return trim($parts[0] ?? '');
|
||||
}
|
||||
|
||||
function match_mescola($raw, array $mescole)
|
||||
{
|
||||
$r = norm_mesc($raw);
|
||||
if ($r === '') return [null, 'none', null];
|
||||
foreach ($mescole as $m) if (norm_mesc($m['nome']) === $r) return [(int)$m['id'], 'exact', $m['nome']];
|
||||
foreach ($mescole as $m) {
|
||||
foreach (explode('/', $m['nome']) as $part) {
|
||||
if (norm_mesc($part) === $r) return [(int)$m['id'], 'exact-part', $m['nome']];
|
||||
if (trim((string)$raw) === '') return [null, 'none', null];
|
||||
$rawStrip = strip_code($raw);
|
||||
$headStrip = strip_code(raw_head($raw));
|
||||
|
||||
// 1) uguaglianza ignorando spazi/punteggiatura, sul nome intero (gestisce "ATES 71" = "ATES71")
|
||||
foreach ($mescole as $m) if ($rawStrip !== '' && strip_code($m['nome']) === $rawStrip) return [(int)$m['id'], 'exact', $m['nome']];
|
||||
// 2) parte di nome composito (a / b / c)
|
||||
foreach ($mescole as $m) foreach (explode('/', $m['nome']) as $p) if ($rawStrip !== '' && strip_code($p) === $rawStrip) return [(int)$m['id'], 'exact-part', $m['nome']];
|
||||
// 3) nome in uscita
|
||||
foreach ($mescole as $m) if ($rawStrip !== '' && strip_code($m['nomeuscita'] ?? '') === $rawStrip) return [(int)$m['id'], 'exact-uscita', $m['nome']];
|
||||
// 4) codice iniziale prima di " - descrizione" (gestisce "ATES 71 - EPDM...")
|
||||
if ($headStrip !== '' && $headStrip !== $rawStrip) {
|
||||
foreach ($mescole as $m) if (strip_code($m['nome']) === $headStrip) return [(int)$m['id'], 'code', $m['nome']];
|
||||
foreach ($mescole as $m) foreach (explode('/', $m['nome']) as $p) if (strip_code($p) === $headStrip) return [(int)$m['id'], 'code-part', $m['nome']];
|
||||
}
|
||||
}
|
||||
foreach ($mescole as $m) if (norm_mesc($m['nomeuscita'] ?? '') === $r) return [(int)$m['id'], 'exact-uscita', $m['nome']];
|
||||
foreach ($mescole as $m) if ($r !== '' && strpos(norm_mesc($m['nome']), $r) !== false) return [(int)$m['id'], 'contains', $m['nome']];
|
||||
return [null, 'none', null];
|
||||
}
|
||||
|
||||
@@ -309,24 +323,51 @@ function dim_tokens($s)
|
||||
|
||||
function match_packaging($raw, array $packaging, $preferCat = null)
|
||||
{
|
||||
$cands = dim_tokens($raw);
|
||||
if (!$cands) return [null, 'none', null, null];
|
||||
$best = null;
|
||||
$rawStrip = strip_code($raw);
|
||||
if ($rawStrip === '') return [null, 'none', null, null, true];
|
||||
|
||||
// 1) nome/codice ESATTO (categoria solo come spareggio) -> sicuro
|
||||
$exact = [];
|
||||
foreach ($packaging as $p) {
|
||||
$itemForms = array_merge(dim_tokens($p['item_name']), dim_tokens($p['item_code']));
|
||||
foreach ($cands as $c) {
|
||||
if (in_array($c, $itemForms, true)) {
|
||||
if ($preferCat && $p['category'] === $preferCat) {
|
||||
return [(int)$p['id'], 'dimension', $p['item_name'], $p['category']];
|
||||
if (strip_code($p['item_code']) === $rawStrip || strip_code($p['item_name']) === $rawStrip) $exact[] = $p;
|
||||
}
|
||||
if ($best === null) $best = $p;
|
||||
if ($exact) {
|
||||
$pick = $exact[0];
|
||||
foreach ($exact as $p) if ($preferCat && $p['category'] === $preferCat) {
|
||||
$pick = $p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($best) return [(int)$best['id'], 'dimension', $best['item_name'], $best['category']];
|
||||
return [null, 'none', null, null];
|
||||
return [(int)$pick['id'], 'name-code', $pick['item_name'], $pick['category'], false];
|
||||
}
|
||||
|
||||
// 2) DIMENSIONI -> sicuro se unico, proposta se ambiguo
|
||||
$rd = dim_tokens($raw);
|
||||
if ($rd) {
|
||||
$dm = [];
|
||||
foreach ($packaging as $p) {
|
||||
$pf = array_merge(dim_tokens($p['item_name']), dim_tokens($p['item_code']));
|
||||
if (array_intersect($rd, $pf)) $dm[] = $p;
|
||||
}
|
||||
if ($dm) {
|
||||
$pick = $dm[0];
|
||||
foreach ($dm as $p) if ($preferCat && $p['category'] === $preferCat) {
|
||||
$pick = $p;
|
||||
break;
|
||||
}
|
||||
$ambiguous = count($dm) > 1;
|
||||
return [(int)$pick['id'], 'dimension', $pick['item_name'], $pick['category'], $ambiguous];
|
||||
}
|
||||
}
|
||||
|
||||
// 3) ABBREVIAZIONE: testo corto, prefisso UNICO di un nome -> solo proposta
|
||||
if (strlen($rawStrip) <= 4) {
|
||||
$cand = [];
|
||||
foreach ($packaging as $p) if (strpos(strip_code($p['item_name']), $rawStrip) === 0) $cand[] = $p;
|
||||
if (count($cand) === 1) return [(int)$cand[0]['id'], 'abbrev', $cand[0]['item_name'], $cand[0]['category'], true];
|
||||
}
|
||||
|
||||
return [null, 'none', null, null, true];
|
||||
}
|
||||
|
||||
/* ---- match generico su dropdown (ws_lookup_options) ---- */
|
||||
function lk_norm($s)
|
||||
@@ -461,7 +502,7 @@ try {
|
||||
];
|
||||
foreach ($pkgSpecs as $spec) {
|
||||
if ($spec['raw'] === '') continue;
|
||||
[$pid, $pconf, $pname, $pcat] = match_packaging($spec['raw'], $packaging, $spec['cat']);
|
||||
[$pid, $pconf, $pname, $pcat, $pneeds] = match_packaging($spec['raw'], $packaging, $spec['cat']);
|
||||
$packagesOut[] = [
|
||||
'source' => $spec['source'],
|
||||
'raw' => $spec['raw'],
|
||||
@@ -469,7 +510,7 @@ try {
|
||||
'matched_name' => $pname,
|
||||
'category' => $pcat,
|
||||
'confidence' => $pconf,
|
||||
'needs_review' => ($pid === null),
|
||||
'needs_review' => ($pid === null) ? true : $pneeds,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,113 @@
|
||||
<style>
|
||||
.ws-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
border-bottom: 2px solid #dbeafe;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ws-tab {
|
||||
border: 0;
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
font-weight: 700;
|
||||
padding: 10px 18px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.ws-tab.active {
|
||||
background: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ws-tab[data-tab="disegni"].active {
|
||||
background: #6f42c1;
|
||||
}
|
||||
|
||||
.ws-tab-count {
|
||||
display: inline-block;
|
||||
min-width: 24px;
|
||||
padding: 1px 7px;
|
||||
margin-left: 6px;
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 0, 0, .12);
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.ws-tab-pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ws-tab-pane.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ws-disegni-list {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ws-disegno-item {
|
||||
border: 2px solid #6f42c1;
|
||||
background: #fff;
|
||||
color: #6f42c1;
|
||||
font-weight: 700;
|
||||
padding: 8px 14px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ws-disegno-item.active {
|
||||
background: #6f42c1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ws-disegno-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ws-disegno-viewer {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #f8fafc;
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
.ws-disegno-viewer iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ws-disegno-viewer .img-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.ws-disegno-viewer img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- MODALE LISTA FOGLI DI LAVORO -->
|
||||
<div class="modal fade" id="worksheetsListModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-worksheet-list">
|
||||
|
||||
@@ -985,6 +985,22 @@ $isEdit = ($worksheet_id > 0);
|
||||
margin-bottom: 16px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
tr.import-pending td {
|
||||
background: #fff8db !important;
|
||||
}
|
||||
|
||||
.pending-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: #fde68a;
|
||||
color: #92400e;
|
||||
border-radius: 999px;
|
||||
padding: 3px 10px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -1892,6 +1908,69 @@ $isEdit = ($worksheet_id > 0);
|
||||
$(function() {
|
||||
window.importStaged = null;
|
||||
|
||||
function renderPendingPackages(packages) {
|
||||
$('#packageRowsBody tr.import-pending').remove();
|
||||
$('#packageImportNote').remove();
|
||||
const $tb = $('#packageRowsBody');
|
||||
const nonAbbinati = [];
|
||||
|
||||
(packages || []).forEach(function(p) {
|
||||
// righe solo per gli imballi ABBINATI (sicuri o proposti)
|
||||
if (!p.package_code) {
|
||||
nonAbbinati.push('“' + (p.raw || '') + '” (' + (p.source || '') + ')');
|
||||
return;
|
||||
}
|
||||
const stato = p.needs_review ?
|
||||
'<span class="pending-badge">❓ proposto — verifica</span>' :
|
||||
'<span class="pending-badge">⏳ verrà aggiunto al salvataggio</span>';
|
||||
const nome = p.matched_name || '—';
|
||||
const $tr = $(
|
||||
'<tr class="import-pending">' +
|
||||
'<td>—</td>' +
|
||||
'<td>' + (p.category || '—') + '</td>' +
|
||||
'<td><div class="fw-bold">' + nome + '</div>' +
|
||||
'<div class="text-muted small">dal file: ' + (p.raw || '') + ' (' + (p.source || '') + ')</div></td>' +
|
||||
'<td>' + stato + '</td>' +
|
||||
'</tr>'
|
||||
);
|
||||
$tb.prepend($tr);
|
||||
});
|
||||
|
||||
// nota per gli imballi NON abbinati (niente righe vuote)
|
||||
if (nonAbbinati.length) {
|
||||
const $note = $(
|
||||
'<div id="packageImportNote" class="import-hint" style="margin-bottom:10px;">' +
|
||||
'⚠ Dal file non abbinati automaticamente: ' + nonAbbinati.join(', ') +
|
||||
'. Aggiungili a mano qui sotto se servono.' +
|
||||
'</div>'
|
||||
);
|
||||
$('#packageRowsBody').closest('.table-wrap').before($note);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPendingMescole(mescole) {
|
||||
// rimuove eventuali righe provvisorie precedenti
|
||||
$('#tabellaMixRows tbody tr.import-pending').remove();
|
||||
const $tb = $('#tabellaMixRows tbody');
|
||||
(mescole || []).forEach(function(m) {
|
||||
if (!m.idmescola) return; // le non riconosciute passano dal modal, non qui
|
||||
const nome = m.matched_name || m.raw || '-';
|
||||
const $tr = $(
|
||||
'<tr class="import-pending">' +
|
||||
'<td>—</td>' +
|
||||
'<td><div class="fw-bold">' + nome + '</div>' +
|
||||
'<div class="text-muted small">dal file: ' + (m.raw || '') + '</div></td>' +
|
||||
'<td>' + (m.mix_weight_g_m ?? '-') + '</td>' +
|
||||
'<td>' + (m.required_density ?? '-') + '</td>' +
|
||||
'<td>' + (m.required_hardness_shore_a ?? '-') + '</td>' +
|
||||
'<td>' + (m.lubrication_type ?? '-') + '</td>' +
|
||||
'<td><span class="pending-badge">⏳ verrà aggiunta al salvataggio</span></td>' +
|
||||
'</tr>'
|
||||
);
|
||||
$tb.prepend($tr);
|
||||
});
|
||||
}
|
||||
|
||||
function postForm(obj) {
|
||||
return fetch('', {
|
||||
method: 'POST',
|
||||
@@ -1949,22 +2028,35 @@ $isEdit = ($worksheet_id > 0);
|
||||
window.finalizeImportIfAny = async function(newId) {
|
||||
const staged = window.importStaged;
|
||||
if (!staged) return false;
|
||||
const unresolved = [];
|
||||
|
||||
const unresolvedPkgs = [];
|
||||
const matchedMescole = [];
|
||||
const unmatchedMescole = [];
|
||||
|
||||
// imballi: salva solo quelli SICURI; i proposti li segnaliamo da confermare
|
||||
for (const p of (staged.packages || [])) {
|
||||
if (p.package_code) {
|
||||
if (p.package_code && !p.needs_review) {
|
||||
await postForm({
|
||||
ajax: 1,
|
||||
action: 'save_package_row',
|
||||
worksheet_id: newId,
|
||||
package_code: p.package_code
|
||||
});
|
||||
} else if (p.package_code && p.needs_review) {
|
||||
unresolvedPkgs.push('Imballo “' + p.raw + '” (' + p.source + ') → proposto: ' + (p.matched_name || '') + ' — verifica e aggiungi a mano');
|
||||
} else {
|
||||
unresolved.push('Imballo “' + p.raw + '” (' + p.source + ')');
|
||||
unresolvedPkgs.push('Imballo “' + p.raw + '” (' + p.source + ') → nessun abbinamento, aggiungi a mano');
|
||||
}
|
||||
}
|
||||
|
||||
// mescole: separa risolte / non risolte
|
||||
for (const m of (staged.mescole || [])) {
|
||||
if (m.idmescola) {
|
||||
if (m.idmescola) matchedMescole.push(m);
|
||||
else unmatchedMescole.push(m);
|
||||
}
|
||||
|
||||
// salva automaticamente le mescole riconosciute
|
||||
for (const m of matchedMescole) {
|
||||
await postForm({
|
||||
ajax: 1,
|
||||
action: 'save_mix_row',
|
||||
@@ -1976,14 +2068,11 @@ $isEdit = ($worksheet_id > 0);
|
||||
lubrication_type: m.lubrication_type ?? '',
|
||||
lubrication_notes: ''
|
||||
});
|
||||
} else {
|
||||
unresolved.push('Mescola “' + m.raw + '”');
|
||||
}
|
||||
}
|
||||
|
||||
window.importStaged = null;
|
||||
|
||||
// Allega il file Excel di origine al foglio appena creato
|
||||
// allega il file Excel di origine al foglio appena creato
|
||||
if (window.importFile) {
|
||||
try {
|
||||
const fd = new FormData();
|
||||
@@ -1999,10 +2088,44 @@ $isEdit = ($worksheet_id > 0);
|
||||
window.importFile = null;
|
||||
}
|
||||
|
||||
let msg = 'Testata e righe importate salvate correttamente.';
|
||||
if (unresolved.length) msg += '\n\nDa aggiungere a mano dopo il ricaricamento:\n• ' + unresolved.join('\n• ');
|
||||
// mescola NON riconosciuta -> apri il modal già precompilato (il foglio ora esiste)
|
||||
if (unmatchedMescole.length) {
|
||||
const m = unmatchedMescole[0]; // il modulo ha 1 mescola per foglio
|
||||
$('#wsId').val(newId);
|
||||
$('#mixWorksheetId').val(newId);
|
||||
if (!window.location.search.includes('id=')) {
|
||||
window.history.replaceState({}, '', 'manage-worksheet.php?id=' + newId);
|
||||
}
|
||||
|
||||
const extra = unresolvedPkgs.length ? ('\n\nDa aggiungere a mano: ' + unresolvedPkgs.join(', ')) : '';
|
||||
await Swal.fire({
|
||||
icon: unresolved.length ? 'warning' : 'success',
|
||||
icon: 'info',
|
||||
title: 'Mescola da abbinare',
|
||||
text: 'La mescola “' + m.raw + '” non è stata riconosciuta. Selezionala dal menu: peso, densità, durezza e lubrificazione sono già compilati.' + extra
|
||||
});
|
||||
|
||||
// precompila i campi dopo che il modal (e select2) è pronto
|
||||
$('#mixModal').one('shown.bs.modal', function() {
|
||||
$('#mixModalTitle').text('Mescola dal file: ' + m.raw + ' — seleziona la corrispondenza');
|
||||
$('#mixRowId').val(0);
|
||||
$('#mixWorksheetId').val(newId);
|
||||
$('#mix_weight_g_m').val(m.mix_weight_g_m ?? '');
|
||||
$('#required_density').val(m.required_density ?? '');
|
||||
$('#required_hardness_shore_a').val(m.required_hardness_shore_a ?? '');
|
||||
$('#lubrication_type').val(m.lubrication_type ?? '');
|
||||
$('#idmescola').val('').trigger('change');
|
||||
});
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('mixModal'));
|
||||
modal.show();
|
||||
return true; // niente reload: ci pensa il salvataggio del modal (save_mix_row + reload)
|
||||
}
|
||||
|
||||
// tutto risolto: messaggio + reload
|
||||
let msg = 'Testata e righe importate salvate correttamente.';
|
||||
if (unresolvedPkgs.length) msg += '\n\nDa aggiungere a mano dopo il ricaricamento:\n• ' + unresolvedPkgs.join('\n• ');
|
||||
await Swal.fire({
|
||||
icon: unresolvedPkgs.length ? 'warning' : 'success',
|
||||
title: 'Import completato',
|
||||
text: msg
|
||||
});
|
||||
@@ -2031,6 +2154,7 @@ $isEdit = ($worksheet_id > 0);
|
||||
const fd = new FormData();
|
||||
fd.append('xlsx_file', file);
|
||||
|
||||
$('#packageImportNote').remove(); // azzera nota import precedente
|
||||
Swal.fire({
|
||||
title: 'Lettura Excel…',
|
||||
allowOutsideClick: false,
|
||||
@@ -2083,6 +2207,8 @@ $isEdit = ($worksheet_id > 0);
|
||||
warnings: data.warnings || []
|
||||
};
|
||||
renderBanner(data);
|
||||
renderPendingMescole(data.mescole || []);
|
||||
renderPendingPackages(data.packages || []);
|
||||
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
|
||||
+159
-11
@@ -547,6 +547,36 @@ function revisionLabel($rev)
|
||||
color: #fff;
|
||||
opacity: .95;
|
||||
}
|
||||
|
||||
.attachments-existing-list .attachment-existing-row.is-disegno {
|
||||
border: 2px solid #6f42c1;
|
||||
background: #f7f3ff;
|
||||
}
|
||||
|
||||
.badge-disegno {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background: #6f42c1;
|
||||
color: #fff;
|
||||
font-size: .78rem;
|
||||
font-weight: 700;
|
||||
padding: 4px 9px;
|
||||
border-radius: 999px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.btn-toggle-disegno {
|
||||
border-color: #6f42c1;
|
||||
color: #6f42c1;
|
||||
}
|
||||
|
||||
.btn-toggle-disegno:hover,
|
||||
.btn-toggle-disegno.active {
|
||||
background: #6f42c1;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -713,6 +743,7 @@ function revisionLabel($rev)
|
||||
data-nome='" . htmlspecialchars($row['nome'], ENT_QUOTES) . "'
|
||||
data-cliente='" . htmlspecialchars($row['cliente'], ENT_QUOTES) . "'
|
||||
data-data='{$dataIT}'
|
||||
data-photo='" . htmlspecialchars($hasPhoto ? $thumb : '', ENT_QUOTES) . "'
|
||||
data-bs-toggle='tooltip' data-bs-title='Modifica'>
|
||||
<i class='fa-solid fa-pen-to-square'></i>
|
||||
</button>
|
||||
@@ -797,6 +828,19 @@ function revisionLabel($rev)
|
||||
<input type="text" class="form-control" id="dataMatrice" placeholder="dd/mm/yyyy">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="fw-semibold">Foto</label>
|
||||
<input type="file" class="form-control" id="fotoMatrice" accept=".jpg,.jpeg,.png,.gif,.webp,image/*">
|
||||
<input type="hidden" id="removeFotoMatrice" value="0">
|
||||
|
||||
<div id="fotoMatricePreviewWrap" class="mt-2 text-center" style="display:none;">
|
||||
<img id="fotoMatricePreview" src="" style="max-width:100%; max-height:200px; border-radius:10px; border:1px solid #dee2e6;">
|
||||
<div class="mt-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" id="btnRimuoviFoto">🗑️ Rimuovi foto</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button type="submit" class="btn btn-add">💾 Salva</button>
|
||||
</div>
|
||||
@@ -1131,7 +1175,9 @@ function revisionLabel($rev)
|
||||
return;
|
||||
}
|
||||
|
||||
const list = data.attachments || [];
|
||||
const list = (data.attachments || []).sort((a, b) =>
|
||||
(parseInt(b.is_disegno, 10) || 0) - (parseInt(a.is_disegno, 10) || 0)
|
||||
);
|
||||
const container = $("#existingAttachmentsList");
|
||||
container.empty();
|
||||
|
||||
@@ -1149,6 +1195,7 @@ function revisionLabel($rev)
|
||||
const badge = getFileTypeBadge(ext);
|
||||
const createdAt = item.created_at || '';
|
||||
const id = item.id || 0;
|
||||
const isDisegno = (parseInt(item.is_disegno, 10) || 0) === 1;
|
||||
|
||||
const previewHtml = isImageExt(ext) && filePath ?
|
||||
`<img src="${escapeHtml(filePath)}" class="attachment-preview-thumb" alt="">` :
|
||||
@@ -1174,19 +1221,29 @@ function revisionLabel($rev)
|
||||
}
|
||||
|
||||
const html = `
|
||||
<div class="attachment-existing-row">
|
||||
<div class="attachment-existing-row ${isDisegno ? 'is-disegno' : ''}">
|
||||
<div class="d-flex gap-3 align-items-start">
|
||||
<div>${previewHtml}</div>
|
||||
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex justify-content-between align-items-start gap-2">
|
||||
<div>
|
||||
<div class="attachment-meta-title">${escapeHtml(fileName)}</div>
|
||||
<div class="attachment-meta-title">
|
||||
${escapeHtml(fileName)}
|
||||
${isDisegno ? '<span class="badge-disegno"><i class="fa-solid fa-compass-drafting"></i> Disegno tecnico</span>' : ''}
|
||||
</div>
|
||||
<div class="attachment-meta-sub">${badge} ${createdAt ? ' • ' + escapeHtml(createdAt) : ''}</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-1">
|
||||
${openButtonHtml}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-toggle-disegno toggle-disegno ${isDisegno ? 'active' : ''}"
|
||||
data-id="${id}"
|
||||
data-value="${isDisegno ? 1 : 0}"
|
||||
title="${isDisegno ? 'Togli da disegni tecnici' : 'Segna come disegno tecnico'}">
|
||||
<i class="fa-solid fa-compass-drafting"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger delete-existing-file" data-id="${id}">Elimina</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1272,12 +1329,49 @@ function revisionLabel($rev)
|
||||
.attr('placeholder', 'Nome matrice, cliente…')
|
||||
.addClass('form-control');
|
||||
|
||||
function resetFotoMatrice(photoUrl) {
|
||||
$("#fotoMatrice").val('');
|
||||
$("#removeFotoMatrice").val('0');
|
||||
if (photoUrl) {
|
||||
$("#fotoMatricePreview").attr("src", photoUrl);
|
||||
$("#fotoMatricePreviewWrap").show();
|
||||
} else {
|
||||
$("#fotoMatricePreview").attr("src", "");
|
||||
$("#fotoMatricePreviewWrap").hide();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("click", ".edit", function() {
|
||||
$("#idMatriceEdit").val($(this).data("id"));
|
||||
$("#nomeMatrice").val($(this).data("nome"));
|
||||
$("#clienteMatrice").val($(this).data("cliente"));
|
||||
$("#dataMatrice").val($(this).data("data"));
|
||||
new bootstrap.Modal(document.getElementById('addMatriceModal')).show();
|
||||
resetFotoMatrice($(this).data("photo") || "");
|
||||
bootstrap.Modal.getOrCreateInstance(document.getElementById('addMatriceModal')).show();
|
||||
});
|
||||
|
||||
// Anteprima quando si sceglie un file
|
||||
$("#fotoMatrice").on("change", function() {
|
||||
const file = this.files && this.files[0];
|
||||
if (!file) return;
|
||||
$("#removeFotoMatrice").val('0');
|
||||
$("#fotoMatricePreview").attr("src", URL.createObjectURL(file));
|
||||
$("#fotoMatricePreviewWrap").show();
|
||||
});
|
||||
|
||||
// Rimuovi foto
|
||||
$("#btnRimuoviFoto").on("click", function() {
|
||||
$("#fotoMatrice").val('');
|
||||
$("#removeFotoMatrice").val('1');
|
||||
$("#fotoMatricePreview").attr("src", "");
|
||||
$("#fotoMatricePreviewWrap").hide();
|
||||
});
|
||||
|
||||
// Alla chiusura del modale svuota tutto (così "Aggiungi" parte pulito)
|
||||
document.getElementById('addMatriceModal').addEventListener('hidden.bs.modal', function() {
|
||||
$("#addMatriceForm")[0].reset();
|
||||
$("#idMatriceEdit").val('');
|
||||
resetFotoMatrice('');
|
||||
});
|
||||
|
||||
$("#addMatriceForm").on("submit", function(e) {
|
||||
@@ -1289,15 +1383,21 @@ function revisionLabel($rev)
|
||||
let dataIT = $("#dataMatrice").val().trim();
|
||||
let dataSQL = convertToMySQLDate(dataIT);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("id", id);
|
||||
formData.append("nome", nome);
|
||||
formData.append("cliente", cliente);
|
||||
formData.append("data", dataSQL);
|
||||
formData.append("remove_photo", $("#removeFotoMatrice").val());
|
||||
|
||||
const fotoFile = $("#fotoMatrice")[0].files[0];
|
||||
if (fotoFile) {
|
||||
formData.append("photo", fotoFile);
|
||||
}
|
||||
|
||||
fetch("save_matrice.php", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body: "id=" + encodeURIComponent(id) +
|
||||
"&nome=" + encodeURIComponent(nome) +
|
||||
"&cliente=" + encodeURIComponent(cliente) +
|
||||
"&data=" + encodeURIComponent(dataSQL)
|
||||
body: formData
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
@@ -1672,6 +1772,54 @@ function revisionLabel($rev)
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on("click", ".toggle-disegno", function() {
|
||||
const $btn = $(this);
|
||||
const id = $btn.data("id");
|
||||
const newValue = parseInt($btn.data("value"), 10) === 1 ? 0 : 1;
|
||||
const idmatrice = $("#mf_idmatrice").val();
|
||||
|
||||
$btn.prop("disabled", true);
|
||||
|
||||
fetch("toggle_matrice_attachment_disegno.php", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body: "id=" + encodeURIComponent(id) + "&is_disegno=" + newValue
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Errore",
|
||||
text: data.message || ""
|
||||
});
|
||||
$btn.prop("disabled", false);
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
position: "top-end",
|
||||
icon: "success",
|
||||
title: data.message,
|
||||
showConfirmButton: false,
|
||||
timer: 1500
|
||||
});
|
||||
|
||||
loadExistingAttachments(idmatrice);
|
||||
})
|
||||
.catch(() => {
|
||||
Swal.fire({
|
||||
icon: "error",
|
||||
title: "Errore",
|
||||
text: "Errore di comunicazione con il server"
|
||||
});
|
||||
$btn.prop("disabled", false);
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on("click", ".preview-existing-file", function() {
|
||||
const file = $(this).data("file") || "";
|
||||
const name = $(this).data("name") || "Anteprima file";
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 600 KiB |
@@ -1912,7 +1912,9 @@ if (!empty($_GET['ajax'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = r.attachments || [];
|
||||
const files = (r.attachments || []).sort((a, b) =>
|
||||
(parseInt(b.is_disegno, 10) || 0) - (parseInt(a.is_disegno, 10) || 0)
|
||||
);
|
||||
|
||||
if (!files.length) {
|
||||
$("#matriceFilesList").html('<div style="color:#64748b;">Nessun allegato disponibile per questa matrice.</div>');
|
||||
@@ -1929,6 +1931,7 @@ if (!empty($_GET['ajax'])) {
|
||||
const ext = getFileExt(fileName);
|
||||
const icon = getFileIcon(ext);
|
||||
const badge = getFileBadge(ext);
|
||||
const isDisegno = (parseInt(f.is_disegno, 10) || 0) === 1;
|
||||
|
||||
let actionHtml = '';
|
||||
if (isImageExt(ext) || ext === 'pdf') {
|
||||
@@ -1966,7 +1969,9 @@ if (!empty($_GET['ajax'])) {
|
||||
}
|
||||
|
||||
html += `
|
||||
<div style="border:1px solid #e2e8f0; border-radius:14px; padding:12px; background:#fff;">
|
||||
<div style="${isDisegno
|
||||
? 'border:2px solid #6f42c1; background:#f7f3ff;'
|
||||
: 'border:1px solid #e2e8f0; background:#fff;'} border-radius:14px; padding:12px;">
|
||||
<div style="display:flex; gap:14px; align-items:flex-start;">
|
||||
<div>${previewHtml}</div>
|
||||
|
||||
@@ -1974,7 +1979,10 @@ if (!empty($_GET['ajax'])) {
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:12px;">
|
||||
<div>
|
||||
<div style="font-weight:700; color:#1f2d3d;">${escapeHtml(fileName)}</div>
|
||||
<div style="margin-top:4px;">${badge}</div>
|
||||
<div style="margin-top:4px; display:flex; gap:6px; flex-wrap:wrap; align-items:center;">
|
||||
${badge}
|
||||
${isDisegno ? '<span style="background:#6f42c1;color:#fff;padding:3px 10px;border-radius:999px;font-size:0.78rem;font-weight:700;">📐 Disegno tecnico</span>' : ''}
|
||||
</div>
|
||||
${createdAt ? `<div style="margin-top:6px; color:#64748b; font-size:0.88rem;">${escapeHtml(createdAt)}</div>` : ''}
|
||||
</div>
|
||||
<div>${actionHtml}</div>
|
||||
|
||||
@@ -1,28 +1,101 @@
|
||||
<?php
|
||||
require_once("include/headscript.php");
|
||||
ob_start();
|
||||
include('include/headscript.php');
|
||||
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$id = $_POST['id'] ?? null;
|
||||
$nome = $_POST['nome'] ?? '';
|
||||
$cliente = $_POST['cliente'] ?? '';
|
||||
$data = $_POST['data'] ?? null; // formato YYYY-MM-DD
|
||||
|
||||
if (!$nome) {
|
||||
echo json_encode(["success" => false, "message" => "Nome obbligatorio"]);
|
||||
function jsonOut($success, $message = '', $extra = [])
|
||||
{
|
||||
if (ob_get_length()) ob_clean();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(array_merge(['success' => $success, 'message' => $message], $extra));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
// UPDATE
|
||||
$sql = "UPDATE matrice SET nome = ?, cliente = ?, data_produzione = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$ok = $stmt->execute([$nome, $cliente, $data, $id]);
|
||||
} else {
|
||||
// INSERT
|
||||
$sql = "INSERT INTO matrice (nome, cliente, data_produzione) VALUES (?,?,?)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$ok = $stmt->execute([$nome, $cliente, $data]);
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$nome = trim($_POST['nome'] ?? '');
|
||||
$cliente = trim($_POST['cliente'] ?? '');
|
||||
$data = trim($_POST['data'] ?? '');
|
||||
$removePhoto = ($_POST['remove_photo'] ?? '0') === '1';
|
||||
|
||||
if ($nome === '') {
|
||||
jsonOut(false, 'Il nome matrice è obbligatorio');
|
||||
}
|
||||
|
||||
echo json_encode(["success" => $ok]);
|
||||
$data = ($data !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $data)) ? $data : null;
|
||||
$cliente = $cliente !== '' ? $cliente : null;
|
||||
|
||||
$photoDir = __DIR__ . '/photos/matrici/';
|
||||
if (!is_dir($photoDir)) {
|
||||
mkdir($photoDir, 0775, true);
|
||||
}
|
||||
|
||||
// Validazione file (se presente)
|
||||
$hasUpload = isset($_FILES['photo']) && $_FILES['photo']['error'] !== UPLOAD_ERR_NO_FILE;
|
||||
$ext = '';
|
||||
if ($hasUpload) {
|
||||
if ($_FILES['photo']['error'] !== UPLOAD_ERR_OK) {
|
||||
jsonOut(false, 'Errore upload foto (codice ' . $_FILES['photo']['error'] . ')');
|
||||
}
|
||||
if ($_FILES['photo']['size'] > 10 * 1024 * 1024) {
|
||||
jsonOut(false, 'La foto supera i 10 MB');
|
||||
}
|
||||
$ext = strtolower(pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) {
|
||||
jsonOut(false, 'Formato foto non consentito (jpg, jpeg, png, gif, webp)');
|
||||
}
|
||||
if (@getimagesize($_FILES['photo']['tmp_name']) === false) {
|
||||
jsonOut(false, 'Il file caricato non è un\'immagine valida');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$oldPhoto = null;
|
||||
|
||||
if ($id > 0) {
|
||||
$st = $pdo->prepare("SELECT photo FROM matrice WHERE id = ?");
|
||||
$st->execute([$id]);
|
||||
$row = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) {
|
||||
$pdo->rollBack();
|
||||
jsonOut(false, 'Matrice non trovata');
|
||||
}
|
||||
$oldPhoto = $row['photo'];
|
||||
|
||||
$st = $pdo->prepare("UPDATE matrice SET nome = ?, cliente = ?, data_produzione = ? WHERE id = ?");
|
||||
$st->execute([$nome, $cliente, $data, $id]);
|
||||
} else {
|
||||
$st = $pdo->prepare("INSERT INTO matrice (nome, cliente, data_produzione) VALUES (?, ?, ?)");
|
||||
$st->execute([$nome, $cliente, $data]);
|
||||
$id = (int)$pdo->lastInsertId();
|
||||
}
|
||||
|
||||
// Nuova foto
|
||||
if ($hasUpload) {
|
||||
$newName = 'matrice_' . $id . '_' . time() . '.' . $ext;
|
||||
if (!move_uploaded_file($_FILES['photo']['tmp_name'], $photoDir . $newName)) {
|
||||
$pdo->rollBack();
|
||||
jsonOut(false, 'Impossibile salvare la foto sul server');
|
||||
}
|
||||
$pdo->prepare("UPDATE matrice SET photo = ? WHERE id = ?")->execute([$newName, $id]);
|
||||
|
||||
if ($oldPhoto && is_file($photoDir . basename($oldPhoto))) {
|
||||
@unlink($photoDir . basename($oldPhoto));
|
||||
}
|
||||
} elseif ($removePhoto && $oldPhoto) {
|
||||
// Rimozione foto
|
||||
$pdo->prepare("UPDATE matrice SET photo = NULL WHERE id = ?")->execute([$id]);
|
||||
if (is_file($photoDir . basename($oldPhoto))) {
|
||||
@unlink($photoDir . basename($oldPhoto));
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
jsonOut(true, 'Matrice salvata correttamente', ['id' => $id]);
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
jsonOut(false, 'Errore: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
ob_start();
|
||||
include('include/headscript.php');
|
||||
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
function jsonOut($success, $message = '')
|
||||
{
|
||||
if (ob_get_length()) ob_clean();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['success' => $success, 'message' => $message]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$value = ($_POST['is_disegno'] ?? '0') === '1' ? 1 : 0;
|
||||
|
||||
if ($id <= 0) {
|
||||
jsonOut(false, 'ID allegato non valido');
|
||||
}
|
||||
|
||||
try {
|
||||
$st = $pdo->prepare("UPDATE matrice_attachments SET is_disegno = ? WHERE id = ?");
|
||||
$st->execute([$value, $id]);
|
||||
jsonOut(true, $value ? 'Segnato come disegno tecnico' : 'Rimosso dai disegni tecnici');
|
||||
} catch (Throwable $e) {
|
||||
jsonOut(false, 'Errore: ' . $e->getMessage());
|
||||
}
|
||||
Reference in New Issue
Block a user