diff --git a/public/userarea/error_log.txt b/public/userarea/error_log.txt
index b6251a2..9619b2d 100644
--- a/public/userarea/error_log.txt
+++ b/public/userarea/error_log.txt
@@ -16,3 +16,14 @@
2026-01-27 15:34:06 - Errore nel recupero dati: HTTP 404, Risposta:
2026-01-27 15:34:10 - Errore nel recupero dati: HTTP 404, Risposta:
2026-01-27 15:35:13 - Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-29 14:33:38 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-29 14:33:39 [MoltiplicatorePrezzo] 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-01-29 14:34:04 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-29 14:37:29 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-29 14:41:55 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-29 14:42:03 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-29 14:42:52 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-29 14:43:00 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
+2026-01-30 10:50:43 [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-01-30 10:50:43 [MoltiplicatorePrezzo] 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-01-30 11:09:22 [MoltiplicatorePrezzo] 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"}
diff --git a/public/userarea/get_fixed_field_data.php b/public/userarea/get_fixed_field_data.php
new file mode 100644
index 0000000..a9fbd28
--- /dev/null
+++ b/public/userarea/get_fixed_field_data.php
@@ -0,0 +1,89 @@
+ 'Parametro "field" mancante']);
+ exit;
+}
+
+$api = VisualLimsApiClient::getInstance();
+$base_url = 'https://93.43.5.102/limsapi/api/odata/';
+
+$data = null;
+$endpoint = null;
+$cache_file = null;
+$options = []; // qui puoi aggiungere filtri/ordering per campo se serve
+
+try {
+ switch ($field) {
+ case 'MoltiplicatorePrezzo':
+ $endpoint = 'MoltiplicatorePrezzi';
+ $cache_file = __DIR__ . '/cache/moltiplicatori_prezzo.json';
+ break;
+
+ case 'AnagraficaCertestObject':
+ $endpoint = 'AnagraficaCertestObject';
+ $cache_file = __DIR__ . '/cache/anagrafica_certest_object.json';
+ break;
+
+ case 'AnagraficaCertestService':
+ $endpoint = 'AnagraficaCertestService';
+ $cache_file = __DIR__ . '/cache/anagrafica_certest_service.json';
+ break;
+
+ case 'ClienteResponsabile':
+ $id_cliente = (int)($_GET['id_cliente'] ?? 0);
+ if ($id_cliente <= 0) {
+ http_response_code(400);
+ echo json_encode(['error' => 'Manca o invalido id_cliente']);
+ exit;
+ }
+ $endpoint = "Cliente($id_cliente)?\$expand=Responsabili";
+ $cache_file = __DIR__ . '/cache/cliente_responsabili_' . $id_cliente . '.json';
+ break;
+
+ // case 'FuturoCampo5':
+ // $endpoint = 'QualcosaElse';
+ // break;
+
+ default:
+ http_response_code(400);
+ echo json_encode(['error' => "Campo non supportato: $field"]);
+ exit;
+ }
+
+ // Opzionale: caching semplice (molto utile se i dati cambiano poco)
+ if ($cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora
+ $data = json_decode(file_get_contents($cache_file), true);
+ } else {
+ $data = $api->get($endpoint, $options);
+
+ if ($cache_file) {
+ file_put_contents($cache_file, json_encode($data, JSON_PRETTY_PRINT));
+ }
+ }
+
+ // Log ultimo URL (opzionale, per debug)
+ $full_url = $base_url . $endpoint . ($options ? '?' . http_build_query($options) : '');
+ file_put_contents(__DIR__ . '/last_urls.log', date('c') . " - $field - $full_url\n", FILE_APPEND);
+
+ echo json_encode($data);
+} catch (Exception $e) {
+ file_put_contents(
+ __DIR__ . '/error_log.txt',
+ date('Y-m-d H:i:s') . " [$field] " . $e->getMessage() . PHP_EOL,
+ FILE_APPEND
+ );
+ http_response_code(500);
+ echo json_encode(['error' => $e->getMessage()]);
+}
diff --git a/public/userarea/import_edit2.php b/public/userarea/import_edit2.php
index e8544ab..7e5e5e4 100644
--- a/public/userarea/import_edit2.php
+++ b/public/userarea/import_edit2.php
@@ -74,6 +74,25 @@ $slugMapping = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$slugMapping[$row['mysql_column_name']] = $row['user_friendly_slug'];
}
+
+// ---------------- FIXED FIELDS (from template_fixed_mapping) ----------------
+$fixedStmt = $pdo->prepare("
+ SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
+ FROM template_fixed_mapping
+ WHERE template_id = ? AND is_visible_import = 1
+ ORDER BY id
+");
+$fixedStmt->execute([$template_id]);
+$fixedFields = $fixedStmt->fetchAll(PDO::FETCH_ASSOC);
+
+// helper default (DATE can use 'today' if you already use it elsewhere)
+function fixedDefaultValue(array $f): string
+{
+ $v = $f['default_value'] ?? '';
+ if ($f['data_type'] === 'DATE' && $v === 'today') return date('Y-m-d');
+ return (string)$v;
+}
+
?>
@@ -456,6 +475,21 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 31px;
}
+
+ .api-fixed-select+.select2-container .select2-selection__rendered {
+ color: #333;
+ }
+
+ .api-fixed-select option[value=""] {
+ color: #999;
+ font-style: italic;
+ }
+
+ .api-fixed-select.required-input:invalid,
+ .api-fixed-select[required]:not([value]):not([data-select2-id]) {
+ background-color: #f8d7da !important;
+ border-color: #dc3545 !important;
+ }
Edit Imported Data - = htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
@@ -577,6 +611,68 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo "";
echo "";
echo "";
+ // ---------------- FIXED FIELDS TOP (propagate) ----------------
+ if (!empty($fixedFields)) {
+ foreach ($fixedFields as $fx => $f) {
+
+ $key = $f['fixed_field_key']; // datadb column
+
+ $val = fixedDefaultValue($f);
+
+ $isRequired = ((int)$f['is_required'] === 1);
+ $inputClass = 'manual-input' . ($isRequired ? ' required-input' : '');
+ $topRequiredClass = ($isRequired && ($val === '' || $val === null)) ? 'missing-required' : '';
+
+ echo "";
+
+ // Forza DATE anche se per errore nel DB è diversa
+ $isDate = ($f['data_type'] === 'DATE' || $key === 'ConsegnaRichiesta');
+
+ if ($isDate) {
+
+ echo "";
+ } else {
+
+ $isApiField = in_array($key, [
+ 'MoltiplicatorePrezzo',
+ 'ClienteResponsabile',
+ 'AnagraficaCertestObject',
+ 'AnagraficaCertestService'
+ ], true);
+
+ if ($isApiField) {
+ $placeholder = ($key === 'ClienteResponsabile') ? 'Seleziona cliente prima...' : 'Seleziona...';
+
+ echo "";
+ } else {
+
+ echo "";
+ }
+ }
+
+ // UNA SOLA freccia
+ echo "";
+ echo "
";
+ }
+ }
+
+
?>
@@ -619,6 +715,22 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$headerIndex++;
echo "";
?>
+ "
+ . htmlspecialchars($label) .
+ "";
+ $headerIndex++;
+ }
+ }
+ ?>
+
$row): ?>
@@ -768,6 +880,56 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
= htmlspecialchars($row['importdate']) ?>
+ ";
+
+ if ($f['data_type'] === 'DATE') {
+ echo "";
+ } else { // INT → diventa select se è uno dei campi API
+ $isApiField = in_array($key, ['MoltiplicatorePrezzo', 'ClienteResponsabile', 'AnagraficaCertestObject', 'AnagraficaCertestService']);
+ $selectClass = $isApiField ? 'api-fixed-select' : '';
+
+ echo "";
+ }
+
+ echo "";
+ $cellIndex++;
+ }
+ }
+ ?>
+
@@ -910,6 +1072,20 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
if (idclientSelect) {
formData.append('idclient', idclientSelect.value);
}
+
+ // ---- FIXED FIELDS (NEW) ----
+ const fixedInputs = row.querySelectorAll(`input[name^="rows[${rowIndex}]["], select[name^="rows[${rowIndex}]["]`);
+ fixedInputs.forEach(inp => {
+ // prendo solo quelli che NON sono details e NON idclient/status/importdate/filename/importreferencecode ecc.
+ // filtro tramite classe fixed-input (che abbiamo messo)
+ if (!inp.classList.contains('fixed-input')) return;
+
+ const m = inp.name.match(/rows\[\d+\]\[([^\]]+)\]/);
+ if (m && m[1]) {
+ formData.append(m[1], inp.value);
+ }
+ });
+
formData.append('iddatadb', iddatadb);
fetch('save_edited_row.php', {
@@ -982,6 +1158,16 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
if (idclientSelect) {
formData.append('idclient', idclientSelect.value);
}
+ // ---- FIXED FIELDS (NEW) ----
+ const fixedInputs = row.querySelectorAll(`input[name^="rows[${rowIndex}]["], select[name^="rows[${rowIndex}]["]`);
+ fixedInputs.forEach(inp => {
+ if (!inp.classList.contains('fixed-input')) return;
+ const m = inp.name.match(/rows\[\d+\]\[([^\]]+)\]/);
+ if (m && m[1]) {
+ formData.append(m[1], inp.value);
+ }
+ });
+
formData.append('iddatadb', iddatadb);
try {
@@ -1093,6 +1279,10 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
populateClientDropdowns();
clientLoadingStatus.textContent = "Clienti caricati.";
+ // ✅ force refresh of header dependent fixed fields
+ $('#clientSelect').trigger('change');
+ $('.grid-top .api-fixed-select[data-fixed-key="ClienteResponsabile"]').trigger('fixed:reload');
+
} catch (error) {
clientLoadingStatus.textContent = "Errore nel caricamento.";
console.error("Errore nel caricamento dei client:", error);
@@ -1251,7 +1441,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
const dropdownData = {};
async function populateDropdowns() {
- const dropdowns = document.querySelectorAll('.dropdown-select:not(.client-select)');
+ const dropdowns = document.querySelectorAll('.dropdown-select:not(.client-select):not(.api-fixed-select)');
if (dropdowns.length === 0) {
return;
}
@@ -1564,6 +1754,227 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
});
});
+
diff --git a/public/userarea/save_edited_row.php b/public/userarea/save_edited_row.php
index 966b43d..47cc4e3 100644
--- a/public/userarea/save_edited_row.php
+++ b/public/userarea/save_edited_row.php
@@ -15,6 +15,44 @@ try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
+ // ---------------- FIXED FIELDS (template_fixed_mapping) ----------------
+
+ // 1) Recupera templateid dalla riga datadb (serve per sapere quali fixed_field_key sono permessi)
+ $stmtTpl = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = ?");
+ $stmtTpl->execute([$iddatadb]);
+ $tplRow = $stmtTpl->fetch(PDO::FETCH_ASSOC);
+
+ $templateId = isset($tplRow['templateid']) ? (int)$tplRow['templateid'] : 0;
+ if ($templateId <= 0) {
+ throw new Exception("Template non trovato per iddatadb=$iddatadb");
+ }
+
+ // 2) Recupera elenco fixed fields visibili per quel template
+ $fxStmt = $pdo->prepare("
+ SELECT fixed_field_key, data_type, is_required, default_value
+ FROM template_fixed_mapping
+ WHERE template_id = ? AND is_visible_import = 1
+");
+ $fxStmt->execute([$templateId]);
+ $fixedList = $fxStmt->fetchAll(PDO::FETCH_ASSOC);
+
+ // 3) Crea whitelist: key => metadata
+ $fixedWhitelist = [];
+ foreach ($fixedList as $fx) {
+ $k = (string)$fx['fixed_field_key'];
+
+ // sicurezza: nome colonna ammesso solo se "safe" (no spazi, no caratteri strani)
+ if (!preg_match('/^[a-zA-Z0-9_]+$/', $k)) {
+ continue;
+ }
+ $fixedWhitelist[$k] = [
+ 'data_type' => (string)$fx['data_type'], // INT | DATE
+ 'is_required' => (int)$fx['is_required'],
+ 'default_value' => $fx['default_value'] ?? null
+ ];
+ }
+
+
$data = $_POST;
$details = [];
@@ -63,22 +101,57 @@ try {
}
}
- // 5. Aggiorna idclient in datadb
+ // 5. Aggiorna datadb: idclient + FIXED FIELDS (whitelisted)
+ $setParts = [];
+ $params = [];
+
+ // 5a) idclient (se presente)
if (isset($idclient)) {
- $updateStmt = $pdo->prepare("
- UPDATE datadb
- SET idclient = :idclient
- WHERE iddatadb = :iddatadb
- ");
- $updateStmt->execute([
- ':idclient' => $idclient,
- ':iddatadb' => $iddatadb
- ]);
- $response['message'] = !empty($changed) ? "Updated details and idclient successfully" : "Updated idclient successfully";
- } else {
- $response['message'] = !empty($changed) ? "Updated details successfully" : "No changes found";
+ $setParts[] = "idclient = ?";
+ $params[] = $idclient;
}
+ // 5b) fixed fields dal POST (solo quelli presenti nella whitelist del template)
+ foreach ($fixedWhitelist as $col => $meta) {
+ if (!array_key_exists($col, $_POST)) {
+ continue; // non inviato dal form
+ }
+
+ $val = $_POST[$col];
+
+ // Normalizzazione per tipo
+ if ($meta['data_type'] === 'DATE') {
+ $val = trim((string)$val);
+ $val = ($val === '') ? null : $val; // atteso formato Y-m-d
+ } else { // INT
+ $val = trim((string)$val);
+ $val = ($val === '') ? null : (int)$val;
+ }
+
+ $setParts[] = "`$col` = ?";
+ $params[] = $val;
+ }
+
+ // esegui update solo se c'è qualcosa da aggiornare
+ if (!empty($setParts)) {
+ $params[] = $iddatadb;
+ $sqlUpd = "UPDATE datadb SET " . implode(", ", $setParts) . " WHERE iddatadb = ?";
+ $updStmt = $pdo->prepare($sqlUpd);
+ $updStmt->execute($params);
+ }
+
+ // Messaggio risposta (mantengo la tua logica ma includo fixed)
+ if (!empty($setParts) && !empty($changed)) {
+ $response['message'] = "Updated details and datadb fields successfully";
+ } elseif (!empty($setParts)) {
+ $response['message'] = "Updated datadb fields successfully";
+ } elseif (!empty($changed)) {
+ $response['message'] = "Updated details successfully";
+ } else {
+ $response['message'] = "No changes found";
+ }
+
+
$response['success'] = true;
$response['changed'] = $changed; // Debug / optional