added additional field in parts template e layout

This commit is contained in:
Claudio 2026-03-02 14:37:20 +01:00
parent 9af0df3cca
commit c9122774b1
15 changed files with 741 additions and 196 deletions

File diff suppressed because one or more lines are too long

View File

@ -312,3 +312,5 @@
2026-02-25 15:23:12 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"} 2026-02-25 15:23:12 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
2026-02-26 15:01:32 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"} 2026-02-26 15:01:32 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
2026-02-28 21:01:27 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
2026-03-01 19:11:58 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}

View File

@ -0,0 +1,38 @@
<?php
require_once __DIR__ . '/include/headscript.php'; // o il tuo bootstrap standard
header('Content-Type: application/json');
try {
if (!isset($_GET['iddatadb']) || !is_numeric($_GET['iddatadb'])) {
echo json_encode(['success' => true, 'field' => null]);
exit;
}
$iddatadb = (int)$_GET['iddatadb'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// 1) prendo templateid da datadb
// 2) cerco max 1 record in template_mapping con is_visible_parts=1
$sql = "
SELECT tm.field_id, tm.field_label, tm.data_type, tm.has_list
FROM datadb d
JOIN template_mapping tm ON tm.template_id = d.templateid
WHERE d.iddatadb = ?
AND tm.is_visible_parts = 1
ORDER BY tm.id ASC
LIMIT 1
";
$stmt = $pdo->prepare($sql);
$stmt->execute([$iddatadb]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'field' => $row ? $row : null
]);
} catch (Throwable $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}

View File

@ -14,11 +14,57 @@ if (!$iddatadb) {
} }
try { try {
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description, idmatrice, note, dateexpiry FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC"); // 1) prendo templateid da datadb
$stmtTpl = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = :iddatadb LIMIT 1");
$stmtTpl->execute([':iddatadb' => $iddatadb]);
$templateid = $stmtTpl->fetchColumn();
// 2) prendo (max 1) field_id visibile in parts
$extraFieldId = null;
if ($templateid) {
$stmtEF = $pdo->prepare("SELECT field_id FROM template_mapping WHERE template_id = :templateid AND is_visible_parts = 1 ORDER BY id ASC LIMIT 1");
$stmtEF->execute([':templateid' => $templateid]);
$extraFieldId = $stmtEF->fetchColumn();
if ($extraFieldId !== false) $extraFieldId = (int)$extraFieldId;
else $extraFieldId = null;
}
// 3) carico parts + join su tabella figlia
if ($extraFieldId) {
$stmt = $pdo->prepare("
SELECT
p.id, p.iddatadb, p.part_number, p.part_description, p.idmatrice, p.note, p.dateexpiry,
cf.value_id AS extra_value_id,
cf.value_text AS extra_value_text
FROM identification_parts p
LEFT JOIN identification_parts_customfields cf
ON cf.part_id = p.id AND cf.field_id = :extraFieldId
WHERE p.iddatadb = :iddatadb
ORDER BY p.part_number ASC
");
$stmt->execute([
':iddatadb' => $iddatadb,
':extraFieldId' => $extraFieldId
]);
} else {
$stmt = $pdo->prepare("
SELECT id, iddatadb, part_number, part_description, idmatrice, note, dateexpiry,
NULL AS extra_value_id, NULL AS extra_value_text
FROM identification_parts
WHERE iddatadb = :iddatadb
ORDER BY part_number ASC
");
$stmt->execute([':iddatadb' => $iddatadb]); $stmt->execute([':iddatadb' => $iddatadb]);
}
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC); $parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'parts' => $parts]); echo json_encode([
'success' => true,
'parts' => $parts,
'extra_field_id' => $extraFieldId,
'debug_sql' => ($extraFieldId ? 'WITH_CF_JOIN' : 'NO_CF')
]);
} catch (PDOException $e) { } catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]); echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
} }

View File

@ -0,0 +1,40 @@
CAMPIONE #0
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 95833,
"Matrice": 8413,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "aaa"
}'
RESPONSE:
{
"IdCampione": 14445,
"Commessa": 95833,
"Matrice": 8413
}
---
CAMPIONE #1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 95833,
"Matrice": 3879,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "bbb"
}'
RESPONSE:
{
"IdCampione": 15750,
"Commessa": 95833,
"Matrice": 3879
}
---

View File

@ -0,0 +1,9 @@
curl --location --request GET 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)?$expand=CommesseCustomFields($expand=CustomField)' \
--header 'Authorization: Bearer ••••••'
RESPONSE:
{
"IdCommessa": 95833,
"CodiceCommessa": "SIM-95833",
"CommesseCustomFields": []
}

View File

@ -0,0 +1,9 @@
curl --location --request GET 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)?$expand=CommesseCustomFields($expand=CustomField)' \
--header 'Authorization: Bearer ••••••'
RESPONSE:
{
"IdCommessa": 95833,
"CodiceCommessa": "SIM-95833",
"CommesseCustomFields": []
}

View File

@ -0,0 +1,10 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)/ImportaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{}'
RESPONSE:
{
"simulated": true,
"endpoint": "CommessaWeb(95833)\/ImportaCommessa"
}

View File

@ -0,0 +1,15 @@
Photos for CommessaWeb 95833 (iddatadb=1237):
Total photos found: 1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/AllegatoCommessaWeb' \
--header 'Authorization: Bearer ••••••' \
--form 'IdCommessa=95833' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1237-20260228192737-Blue and White Simple Daily Vlogger YouTube Banner (15) (1) (1).png'
RESPONSE:
{
"simulated": true,
"file": "1237-20260228192737-Blue and White Simple Daily Vlogger YouTube Banner (15) (1) (1).png"
}
---

View File

@ -0,0 +1,22 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb",
"ClienteResponsabile": 3,
"MoltiplicatorePrezzo": 1,
"AnagraficaCertestObject": 2,
"AnagraficaCertestService": 1,
"ClienteFornitore": null
}'
RESPONSE:
{
"IdCommessa": 95833,
"CodiceCommessa": "SIM-95833",
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb"
}

View File

@ -913,12 +913,22 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
cb.checked = cb.dataset.originalChecked === 'true'; cb.checked = cb.dataset.originalChecked === 'true';
}); });
}); });
} else if (event.target.classList.contains('visible-import-checkbox')) { } else if (event.target.classList.contains('visible-parts-checkbox')) {
const checkbox = event.target; const checkbox = event.target;
const mappingId = checkbox.dataset.mappingId; const mappingId = checkbox.dataset.mappingId;
const value = checkbox.checked ? 1 : 0; const value = checkbox.checked ? 1 : 0;
fetch('update_visible_import.php', { // salva stato per rollback
const prevChecked = checkbox.checked;
// ✅ UI: se sto mettendo a 1, tolgo la spunta a tutti gli altri SUBITO
if (value === 1) {
document.querySelectorAll('.visible-parts-checkbox').forEach(cb => {
if (cb !== checkbox) cb.checked = false;
});
}
fetch('update_visible_parts.php', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -929,16 +939,25 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
value: value value: value
}) })
}) })
.then(response => response.json()) .then(r => r.json())
.then(data => { .then(data => {
if (!data.success) { if (!data.success) {
console.error("❌ Error updating is_visible_import:", data.message); console.error("❌ Error updating is_visible_parts:", data.message);
checkbox.checked = !checkbox.checked;
// rollback UI
checkbox.checked = !prevChecked;
// se avevo tolto le spunte agli altri, ricarico per riallineare la UI al DB
// (semplice e safe)
location.reload();
} }
}) })
.catch(error => { .catch(error => {
console.error("❌ Fetch error:", error); console.error("❌ Fetch error:", error);
checkbox.checked = !checkbox.checked;
// rollback UI
checkbox.checked = !prevChecked;
location.reload();
}); });
} else if (event.target.classList.contains('visible-parts-checkbox')) { } else if (event.target.classList.contains('visible-parts-checkbox')) {
const checkbox = event.target; const checkbox = event.target;

View File

@ -1,5 +1,5 @@
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true"> <div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 80% !important;"> <div class="modal-dialog modal-xl" style="max-width: 95vw !important;">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5> <h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5>
@ -34,7 +34,7 @@
<table class="table table-striped table-sm" id="partsTable"> <table class="table table-striped table-sm" id="partsTable">
<thead> <thead>
<tr> <tr>
<th style="width: 80px;">Numero</th> <th style="width: 55px;">Num</th>
<th>Descrizione</th> <th>Descrizione</th>
<th style="width: 200px;">Matrice</th> <th style="width: 200px;">Matrice</th>
<th style="width: 150px;"> <th style="width: 150px;">
@ -51,7 +51,7 @@
</thead> </thead>
<tbody id="partsTableBody"> <tbody id="partsTableBody">
<tr data-part-id="new"> <tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 80px;"></td> <td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 55px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td> <td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td>
<td> <td>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
@ -287,9 +287,13 @@
/* Colonna Descrizione (2ª colonna) = 420px */ /* Colonna Descrizione (2ª colonna) = 420px */
#partsTable th:nth-child(2), #partsTable th:nth-child(2),
#partsTable td:nth-child(2) { #partsTable td:nth-child(2) {
width: 350 !important; width: 300px !important;
min-width: 350px !important; min-width: 300px !important;
max-width: 350px !important; max-width: 300px !important;
}
#partsTable .part-number {
text-align: center;
} }
#partsTable td:nth-child(2) .part-description { #partsTable td:nth-child(2) .part-description {

View File

@ -7,6 +7,8 @@ $(document).ready(function () {
let matrici = []; let matrici = [];
let macroMatrici = []; let macroMatrici = [];
let quotations = []; let quotations = [];
let partsExtraField = null; // {field_id, field_label} oppure null
let extraFieldOptions = []; // [{id,label}]
// --- ROW ID helpers: niente più cache impazzita di jQuery .data() --- // --- ROW ID helpers: niente più cache impazzita di jQuery .data() ---
function getPartId($row) { function getPartId($row) {
@ -29,6 +31,148 @@ $(document).ready(function () {
return id === "new" || id === "" || id === null ? null : id; return id === "new" || id === "" || id === null ? null : id;
} }
function loadPartsExtraField(iddatadb, done) {
partsExtraField = null;
if (!iddatadb) return done();
$.ajax({
url: "get_parts_extra_field.php",
method: "GET",
dataType: "json",
data: { iddatadb },
success: function (res) {
partsExtraField =
res && res.success && res.field ? res.field : null;
if (
partsExtraField &&
(partsExtraField.data_type || "").toLowerCase() ===
"sceltamultipla"
) {
loadExtraFieldOptions(
String(partsExtraField.field_id),
function () {
applyExtraFieldColumn();
done();
},
);
} else {
applyExtraFieldColumn();
done();
}
},
error: function () {
partsExtraField = null;
applyExtraFieldColumn();
done();
},
});
}
function loadExtraFieldOptions(fieldId, done) {
extraFieldOptions = [];
if (!fieldId) return done();
$.ajax({
url: "get_customfield_values.php",
method: "GET",
dataType: "json",
data: { field_ids: fieldId },
success: function (res) {
const arr = res && res[fieldId] ? res[fieldId] : [];
extraFieldOptions = (arr || []).map((x) => ({
id: x.IdCustomFieldsValue,
label: x.Valore,
}));
done();
},
error: function () {
extraFieldOptions = [];
done();
},
});
}
function buildExtraFieldCellHtml($row = null) {
if (!partsExtraField) return "";
const selectedValueId = $row ? $row.data("extra-value-id") || "" : "";
// SceltaMultipla -> select + hidden value id
if (
(partsExtraField.data_type || "").toLowerCase() === "sceltamultipla"
) {
const opts = [`<option value="">Select…</option>`]
.concat(
extraFieldOptions.map(
(o) => `<option value="${o.id}">${o.label}</option>`,
),
)
.join("");
return `
<td class="extra-field-td" style="width:200px;">
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
<input type="hidden" class="part-extra-value-id" value="">
<select class="form-control form-control-sm part-extra-select" data-selected="${selectedValueId || ""}">${opts}</select>
</td>`;
}
// Testo -> input + hidden field_id
return `
<td class="extra-field-td" style="width:200px;">
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
<input type="text" class="form-control form-control-sm part-extra-field" data-type="${partsExtraField.data_type || ""}">
</td>`;
}
$(document).on("change", ".part-extra-select", function () {
const valId = $(this).val() || "";
const $row = $(this).closest("tr");
// salva nello stato della riga (così sopravvive ai re-render)
$row.data("extra-value-id", valId);
$(this).closest("td").find(".part-extra-value-id").val(valId);
saveRow($row);
});
function applyExtraFieldColumn() {
const $theadRow = $("#partsTable thead tr");
// 1) rimuovi header extra
$theadRow.find("th.extra-field-th").remove();
// 2) rimuovi celle extra esistenti
$("#partsTableBody tr").each(function () {
$(this).find("td.extra-field-td").remove();
});
// 3) se non c'è campo extra -> fine
if (!partsExtraField) return;
// 4) inserisci header prima di "Azioni" (ultima colonna)
$theadRow
.find("th:last")
.before(
`<th class="extra-field-th" style="width:200px;">${partsExtraField.field_label}</th>`,
);
// 5) aggiungi cella a ogni riga già presente (una sola volta)
$("#partsTableBody tr").each(function () {
const $row = $(this);
$row.find("td:last").before(buildExtraFieldCellHtml($row));
// ripristina selezione se già presente nello stato riga
const selected = ($row.data("extra-value-id") || "").toString();
if (selected) {
$row.find(".part-extra-value-id").val(selected);
$row.find(".part-extra-select").val(selected);
}
});
}
function setPartId($row, id) { function setPartId($row, id) {
if (!id) return; if (!id) return;
// Sincronizza TUTTO: attributo + cache jQuery + nostra cache // Sincronizza TUTTO: attributo + cache jQuery + nostra cache
@ -153,15 +297,19 @@ $(document).ready(function () {
matrici = data.value || []; matrici = data.value || [];
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPartsExtraField(iddatadb, function () {
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations, callback); loadExistingParts(iddatadb, idquotations, callback);
});
}, },
error: function (xhr, status, error) { error: function (xhr, status, error) {
matrici = []; matrici = [];
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPartsExtraField(iddatadb, function () {
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations, callback); loadExistingParts(iddatadb, idquotations, callback);
});
const errorMsg = $( const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle matrici: ' + '<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle matrici: ' +
error + error +
@ -180,12 +328,16 @@ $(document).ready(function () {
} else { } else {
loadMacroMatrici(); loadMacroMatrici();
initializeGlobalSelect2(); initializeGlobalSelect2();
loadPartsExtraField(iddatadb, function () {
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations, callback); loadExistingParts(iddatadb, idquotations, callback);
});
} }
} else { } else {
loadPartsExtraField(iddatadb, function () {
loadPhoto(iddatadb, idquotations); loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations, callback); loadExistingParts(iddatadb, idquotations, callback);
});
} }
} }
@ -514,6 +666,11 @@ $(document).ready(function () {
if (response.new_id) return response.new_id; if (response.new_id) return response.new_id;
if (response.partId) return response.partId; if (response.partId) return response.partId;
// ✅ RISPOSTA DI save_parts.php: { success:true, results:[{part_id:...}] }
if (Array.isArray(response.results) && response.results[0]) {
if (response.results[0].part_id) return response.results[0].part_id;
if (response.results[0].id) return response.results[0].id;
}
return null; return null;
} }
@ -527,6 +684,13 @@ $(document).ready(function () {
const iddatadb = $("#partsModal").data("iddatadb"); const iddatadb = $("#partsModal").data("iddatadb");
const idquotations = $("#partsModal").data("idquotations"); const idquotations = $("#partsModal").data("idquotations");
const isMix = partDescription.startsWith("Mix") ? "Y" : "N"; const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
// EXTRA FIELD (0/1)
const extra_field_id = $row.find(".part-extra-field-id").val() || null;
const extra_value_id = $row.find(".part-extra-value-id").val() || null;
const extra_value_text = $row.find(".part-extra-field").length
? ($row.find(".part-extra-field").val() || "").trim()
: null;
let partId = getPartId($row); let partId = getPartId($row);
if (partId === "new") partId = null; // difesa extra (non dovrebbe più servire, ma sicura) if (partId === "new") partId = null; // difesa extra (non dovrebbe più servire, ma sicura)
const endpoint = idquotations const endpoint = idquotations
@ -559,8 +723,14 @@ $(document).ready(function () {
part_number: partNumber, part_number: partNumber,
part_description: partDescription, part_description: partDescription,
mix: isMix, mix: isMix,
idmatrice: $row.find(".part-matrice").val() || null,
dateexpiry: dateexpiry || null, dateexpiry: dateexpiry || null,
note: note, note: note,
// extra custom field
extra_field_id: extra_field_id,
extra_value_id: extra_value_id,
extra_value_text: extra_value_text,
}, },
], ],
}), }),
@ -591,7 +761,8 @@ $(document).ready(function () {
$saveStatus.show(); $saveStatus.show();
setTimeout(() => $saveStatus.hide(), 2000); setTimeout(() => $saveStatus.hide(), 2000);
if (!$("#quotationeBtn").hasClass('d-none')) $("#quotationeBtn").addClass("d-none"); if (!$("#quotationeBtn").hasClass("d-none"))
$("#quotationeBtn").addClass("d-none");
} else { } else {
const errorMsg = $( const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' + '<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' +
@ -721,6 +892,7 @@ $(document).ready(function () {
</div> </div>
</td> </td>
<td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td> <td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td>
${partsExtraField ? buildExtraFieldCellHtml(null) : ``}
<td> <td>
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button> <button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button>
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button> <button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
@ -1154,9 +1326,13 @@ $(document).ready(function () {
} }
}); });
$(document).on("blur", ".part-description, .part-number", function () { $(document).on(
"blur",
".part-description, .part-number, .part-extra-field",
function () {
saveRow($(this).closest("tr")); saveRow($(this).closest("tr"));
}); },
);
function loadExistingParts(iddatadb, idquotations, callback = null) { function loadExistingParts(iddatadb, idquotations, callback = null) {
const endpoint = idquotations const endpoint = idquotations
@ -1209,6 +1385,7 @@ $(document).ready(function () {
</div> </div>
</td> </td>
<td><input type="date" class="form-control form-control-sm part-dateexpiry" value="${part.dateexpiry || ""}" style="width: 130px;"></td> <td><input type="date" class="form-control form-control-sm part-dateexpiry" value="${part.dateexpiry || ""}" style="width: 130px;"></td>
${partsExtraField ? buildExtraFieldCellHtml() : ``}
<td> <td>
<button type="button" class="btn btn-light btn-sm note-btn ${part.note ? "has-note" : ""}" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button> <button type="button" class="btn btn-light btn-sm note-btn ${part.note ? "has-note" : ""}" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button>
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button> <button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
@ -1218,6 +1395,20 @@ $(document).ready(function () {
</td> </td>
</tr>`; </tr>`;
$("#partsTableBody").append(newRow); $("#partsTableBody").append(newRow);
const $row = $(
`#partsTableBody tr[data-part-id="${part.id}"]`,
);
if (
part.extra_value_id !== undefined &&
part.extra_value_id !== null
) {
const vid = String(part.extra_value_id);
$row.data("extra-value-id", vid);
$row.find(".part-extra-value-id").val(vid);
$row.find(".part-extra-select").val(vid);
}
const $select = $("#partsTableBody").find( const $select = $("#partsTableBody").find(
`tr[data-part-id="${part.id}"] .part-matrice`, `tr[data-part-id="${part.id}"] .part-matrice`,
); );
@ -1345,7 +1536,7 @@ $(document).ready(function () {
partId, partId,
currentValue, currentValue,
selectedMacro, selectedMacro,
true true,
); );
}); });
}); });
@ -1422,7 +1613,7 @@ $(document).ready(function () {
partId, partId,
idmatrice, idmatrice,
selectedMacro = null, selectedMacro = null,
fromFilter = false fromFilter = false,
) { ) {
if (typeof $.fn.select2 === "undefined") { if (typeof $.fn.select2 === "undefined") {
$select.replaceWith( $select.replaceWith(
@ -1809,7 +2000,11 @@ $(document).ready(function () {
reloadQuotations(); reloadQuotations();
}, },
error: function (xhr, status, error) { error: function (xhr, status, error) {
console.error("Errore AJAX caricamento quotations:", error, xhr.responseText); console.error(
"Errore AJAX caricamento quotations:",
error,
xhr.responseText,
);
let message = ` let message = `
<div class="alert alert-danger temp-alert" role="alert"> <div class="alert alert-danger temp-alert" role="alert">
Errore nel caricamento delle quotations: ${error} (${xhr.status}) Errore nel caricamento delle quotations: ${error} (${xhr.status})
@ -1823,7 +2018,7 @@ $(document).ready(function () {
$(this).remove(); $(this).remove();
}); });
}, 5000); }, 5000);
} },
}); });
} }
}); });
@ -1831,7 +2026,10 @@ $(document).ready(function () {
$(document).on("click", "#addQuotationBtn", function () { $(document).on("click", "#addQuotationBtn", function () {
const quotationId = $("#addQuotationSelect").val(); const quotationId = $("#addQuotationSelect").val();
if (quotationId && confirm("Confermo di collegare la quotazione al campione?")) { if (
quotationId &&
confirm("Confermo di collegare la quotazione al campione?")
) {
$("#addQuotationModal").modal("hide"); $("#addQuotationModal").modal("hide");
loadParts(null, quotationId, () => { loadParts(null, quotationId, () => {
@ -1842,12 +2040,17 @@ $(document).ready(function () {
$(this).find(".save-loading").show(); $(this).find(".save-loading").show();
}); });
let photoList = $('#photoSelector option').map(function () { let photoList = $("#photoSelector option")
return this.value?.split('/')?.pop(); .map(function () {
}).get(); return this.value?.split("/")?.pop();
})
.get();
if (!photoList.length) { if (!photoList.length) {
let path = $('#samplePhoto').attr("src")?.split('/')?.pop(); let path = $("#samplePhoto")
.attr("src")
?.split("/")
?.pop();
if (path) photoList = [path]; if (path) photoList = [path];
} }
@ -1858,9 +2061,11 @@ $(document).ready(function () {
data: JSON.stringify({ data: JSON.stringify({
iddatadb: $("#partsModal").data("iddatadb"), iddatadb: $("#partsModal").data("iddatadb"),
photoList, photoList,
partIds: $('#partsTableBody tr').map(function () { partIds: $("#partsTableBody tr")
.map(function () {
return $(this).data("part-id"); return $(this).data("part-id");
}).get(), })
.get(),
}), }),
success: function () { success: function () {
$("#partsTableBody tr").each(function () { $("#partsTableBody tr").each(function () {
@ -1873,7 +2078,8 @@ $(document).ready(function () {
setTimeout(() => $saveStatus.hide(), 2000); setTimeout(() => $saveStatus.hide(), 2000);
}); });
}, error: function (xhr, status, error) { },
error: function (xhr, status, error) {
let message = ` let message = `
<div class="alert alert-danger temp-alert" role="alert"> <div class="alert alert-danger temp-alert" role="alert">
Errore di salvataggio: ${error} (${xhr.status}) Errore di salvataggio: ${error} (${xhr.status})
@ -1887,7 +2093,7 @@ $(document).ready(function () {
$(this).remove(); $(this).remove();
}); });
}, 5000); }, 5000);
} },
}); });
}, 100); }, 100);
}); });
@ -1898,11 +2104,15 @@ $(document).ready(function () {
if (quotations.length > 0) { if (quotations.length > 0) {
$("#addQuotationSelect").empty(); $("#addQuotationSelect").empty();
$("#addQuotationSelect").append("<option value=''>Seleziona Quotation</option>"); $("#addQuotationSelect").append(
"<option value=''>Seleziona Quotation</option>",
);
quotations.forEach(quotation => { quotations.forEach((quotation) => {
if (quotation?.description) { if (quotation?.description) {
$("#addQuotationSelect").append(`<option value='${quotation?.id}'>${quotation?.description}</option>`); $("#addQuotationSelect").append(
`<option value='${quotation?.id}'>${quotation?.description}</option>`,
);
} }
}); });
@ -2136,15 +2346,21 @@ $(document).on("click", ".save-common-note-btn", function () {
$(document).on("click", "#showHideImageBtn", function () { $(document).on("click", "#showHideImageBtn", function () {
let mainRow = $(this).closest(".parts-row"); let mainRow = $(this).closest(".parts-row");
let photoContainer = mainRow.find(".col-md-3"); let photoContainer = mainRow.find(".col-md-3");
let tableContainer = mainRow.find("#partsTable").closest("div[class*='col-md']"); let tableContainer = mainRow
.find("#partsTable")
.closest("div[class*='col-md']");
if (photoContainer.hasClass("d-none")) { if (photoContainer.hasClass("d-none")) {
photoContainer.removeClass("d-none"); photoContainer.removeClass("d-none");
tableContainer.removeClass("col-md-12").addClass("col-md-9"); tableContainer.removeClass("col-md-12").addClass("col-md-9");
$(this).html("<i class='fas fa-eye-slash' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>"); $(this).html(
"<i class='fas fa-eye-slash' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>",
);
} else { } else {
photoContainer.addClass("d-none"); photoContainer.addClass("d-none");
tableContainer.removeClass("col-md-9").addClass("col-md-12"); tableContainer.removeClass("col-md-9").addClass("col-md-12");
$(this).html("<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>"); $(this).html(
"<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>",
);
} }
}); });

View File

@ -4,6 +4,7 @@ include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance(); $dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection(); $pdo = $dbHandler->getConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$data = json_decode(file_get_contents('php://input'), true); $data = json_decode(file_get_contents('php://input'), true);
@ -19,6 +20,21 @@ try {
$pdo->beginTransaction(); $pdo->beginTransaction();
$results = []; $results = [];
// Custom fields statements (child table)
$stmtUpsertCF = $pdo->prepare("
INSERT INTO identification_parts_customfields (part_id, field_id, value_id, value_text)
VALUES (:part_id, :field_id, :value_id, :value_text)
ON DUPLICATE KEY UPDATE
value_id = VALUES(value_id),
value_text = VALUES(value_text),
updated_at = NOW()
");
$stmtDeleteCF = $pdo->prepare("
DELETE FROM identification_parts_customfields
WHERE part_id = :part_id AND field_id = :field_id
");
foreach ($parts as $part) { foreach ($parts as $part) {
$partId = $part['id'] ?? null; $partId = $part['id'] ?? null;
$partNumber = $part['part_number'] ?? null; $partNumber = $part['part_number'] ?? null;
@ -28,9 +44,23 @@ try {
$note = $part['note'] ?? null; $note = $part['note'] ?? null;
$dateexpiry = $part['dateexpiry'] ?? null; $dateexpiry = $part['dateexpiry'] ?? null;
if ($partDescription || $note || $dateexpiry) { // Extra field (0/1)
$extraFieldId = $part['extra_field_id'] ?? null;
$extraValueId = $part['extra_value_id'] ?? null;
$extraValueText = $part['extra_value_text'] ?? null;
// Normalizza vuoti
if ($extraFieldId !== null && $extraFieldId !== '') $extraFieldId = (int)$extraFieldId;
else $extraFieldId = null;
if ($extraValueId !== null && $extraValueId !== '') $extraValueId = (int)$extraValueId;
else $extraValueId = null;
if ($extraValueText !== null) {
$extraValueText = trim((string)$extraValueText);
if ($extraValueText === '') $extraValueText = null;
}
if ($partId) { if ($partId) {
// UPDATE se la parte esiste // UPDATE se la parte esiste (sempre)
$stmt = $pdo->prepare("UPDATE identification_parts $stmt = $pdo->prepare("UPDATE identification_parts
SET part_number = :part_number, SET part_number = :part_number,
part_description = :part_description, part_description = :part_description,
@ -49,9 +79,39 @@ try {
':note' => $note, ':note' => $note,
':dateexpiry' => $dateexpiry, ':dateexpiry' => $dateexpiry,
]); ]);
$results[] = ['part_id' => $partId, 'part_number' => $partNumber, 'message' => 'Parte aggiornata con successo'];
// Save extra custom field (if provided)
if ($extraFieldId !== null) {
if ($extraValueId === null && $extraValueText === null) {
$stmtDeleteCF->execute([
':part_id' => $partId,
':field_id' => $extraFieldId,
]);
} else { } else {
// INSERT per nuova parte $stmtUpsertCF->execute([
':part_id' => $partId,
':field_id' => $extraFieldId,
':value_id' => $extraValueId,
':value_text' => $extraValueText,
]);
}
}
$cf_row = null;
if ($extraFieldId !== null) {
$chk = $pdo->prepare("SELECT id, value_id, value_text FROM identification_parts_customfields WHERE part_id = ? AND field_id = ?");
$chk->execute([$partId, $extraFieldId]);
$cf_row = $chk->fetch(PDO::FETCH_ASSOC);
}
$results[] = [
'part_id' => $partId,
'part_number' => $partNumber,
'message' => 'Parte aggiornata con successo',
'cf_row' => $cf_row
];
} else if ($partDescription || $note || $dateexpiry) {
// INSERT per nuova parte (solo se ha contenuto)
$stmt = $pdo->prepare("INSERT INTO identification_parts $stmt = $pdo->prepare("INSERT INTO identification_parts
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at) (iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
VALUES (:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())"); VALUES (:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())");
@ -64,14 +124,43 @@ try {
':note' => $note, ':note' => $note,
':dateexpiry' => $dateexpiry, ':dateexpiry' => $dateexpiry,
]); ]);
$newId = $pdo->lastInsertId(); $newId = (int)$pdo->lastInsertId();
$results[] = ['part_id' => $newId, 'part_number' => $partNumber, 'message' => 'Parte salvata con successo'];
if ($extraFieldId !== null) {
if ($extraValueId === null && $extraValueText === null) {
$stmtDeleteCF->execute([
':part_id' => $newId,
':field_id' => $extraFieldId,
]);
} else {
$stmtUpsertCF->execute([
':part_id' => $newId,
':field_id' => $extraFieldId,
':value_id' => $extraValueId,
':value_text' => $extraValueText,
]);
} }
} }
$results[] = [
'part_id' => $newId,
'part_number' => $partNumber,
'message' => 'Parte salvata con successo'
];
}
} }
$pdo->commit(); $pdo->commit();
echo json_encode(['success' => true, 'results' => $results]); echo json_encode([
'success' => true,
'results' => $results,
'debug_last' => [
'extra_field_id' => $extraFieldId ?? null,
'extra_value_id' => $extraValueId ?? null,
'extra_value_text' => $extraValueText ?? null,
'part_id' => $partId ?? ($newId ?? null),
]
]);
} catch (PDOException $e) { } catch (PDOException $e) {
$pdo->rollBack(); $pdo->rollBack();
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]); echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);

View File

@ -21,11 +21,37 @@ $value = ($value === 1) ? 1 : 0;
$db = DBHandlerSelect::getInstance(); $db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection(); $pdo = $db->getConnection();
$stmt = $pdo->prepare("UPDATE template_mapping SET is_visible_parts = ? WHERE id = ? AND template_id = ?"); try {
$result = $stmt->execute([$value, $mapping_id, $template_id]); $pdo->beginTransaction();
if ($result) { if ($value === 1) {
echo json_encode(['success' => true]); // 1) Force ONLY ONE row visible_parts per template:
} else { // set all to 0 for this template
echo json_encode(['success' => false, 'message' => 'Failed to update is_visible_parts']); $stmtReset = $pdo->prepare("
UPDATE template_mapping
SET is_visible_parts = 0
WHERE template_id = ?
");
$stmtReset->execute([$template_id]);
}
// 2) Set requested mapping to 1 or 0
$stmtSet = $pdo->prepare("
UPDATE template_mapping
SET is_visible_parts = ?
WHERE id = ? AND template_id = ?
");
$stmtSet->execute([$value, $mapping_id, $template_id]);
$pdo->commit();
echo json_encode([
'success' => true,
'template_id' => $template_id,
'mapping_id' => $mapping_id,
'value' => $value
]);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
} }