diff --git a/.gitignore b/.gitignore
index 38634bf..9513359 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,10 +40,13 @@ yarn-error.log
/public/userarea/logaspi/
/public/userarea/logs/api/
/public/userarea/logs/api/**
+/public/userarea/logs/
+/public/userarea/logs/**
/public/userarea/photostrf/
/public/userarea/class/*.log
/public/userarea/class/curl_auth_debug.log
/public/userarea/class/curl_request_debug.log
+/public/userarea/schema_dettagli_response.json
# File XLSX temporanei importati
/public/userarea/imported_trf/*.xlsx
diff --git a/public/userarea/class/VisualLimsApiClient.class.php b/public/userarea/class/VisualLimsApiClient.class.php
index 61bd945..369d585 100644
--- a/public/userarea/class/VisualLimsApiClient.class.php
+++ b/public/userarea/class/VisualLimsApiClient.class.php
@@ -255,4 +255,39 @@ class VisualLimsApiClient
{
return $this->baseUrl;
}
+
+ /**
+ * Recupera contenuto binario - Adattato per https://bvcpsitaly-elims.com/limsapi
+ */
+ public function getRaw($endpoint)
+ {
+ $token = $this->getToken();
+
+ // IMPORTANTE: usa /odata/ e NON /api/odata/
+ $url = "{$this->baseUrl}/odata/{$endpoint}";
+
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ "Authorization: Bearer {$token}",
+ "Accept: */*"
+ ]);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+
+ $response = curl_exec($ch);
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $curl_error = curl_error($ch);
+ curl_close($ch);
+
+ if ($response === false) {
+ throw new Exception("Errore cURL: " . $curl_error);
+ }
+
+ if ($http_code !== 200) {
+ throw new Exception("HTTP {$http_code} su endpoint: " . $url);
+ }
+
+ return $response;
+ }
}
diff --git a/public/userarea/download_rapporto_file.php b/public/userarea/download_rapporto_file.php
new file mode 100644
index 0000000..14ea0ae
--- /dev/null
+++ b/public/userarea/download_rapporto_file.php
@@ -0,0 +1,112 @@
+get("RapportoFile({$id})", []);
+
+ file_put_contents(
+ $debugDir . "rapporto_file_{$id}.json",
+ json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
+ );
+
+ /**
+ * Provo vari nomi campo possibili per il contenuto PDF.
+ * Dopo il primo test potremo adattarlo al nome esatto.
+ */
+ $possibleContentFields = [
+ 'File',
+ 'file',
+ 'Content',
+ 'content',
+ 'FileContent',
+ 'fileContent',
+ 'Contenuto',
+ 'contenuto',
+ 'Bytes',
+ 'bytes'
+ ];
+
+ $base64 = null;
+
+ foreach ($possibleContentFields as $field) {
+ if (!empty($data[$field])) {
+ $base64 = $data[$field];
+ break;
+ }
+ }
+
+ if (!$base64) {
+ header('Content-Type: application/json; charset=utf-8');
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Record RapportoFile recuperato, ma non ho trovato un campo base64 del PDF.',
+ 'hint' => 'Apri logs/lims_get_commessa/rapporto_file_' . $id . '.json e controlla il nome reale del campo file.',
+ 'data' => $data
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ exit;
+ }
+
+ /**
+ * Se il contenuto ha prefisso data URI, lo pulisco.
+ */
+ if (strpos($base64, 'base64,') !== false) {
+ $parts = explode('base64,', $base64);
+ $base64 = end($parts);
+ }
+
+ $pdfBinary = base64_decode($base64, true);
+
+ if ($pdfBinary === false) {
+ throw new Exception("Il contenuto trovato non è un base64 valido");
+ }
+
+ $filename = $data['NomeFile']
+ ?? $data['nomeFile']
+ ?? $data['FileName']
+ ?? $data['fileName']
+ ?? "rapporto_{$id}.pdf";
+
+ if (!str_ends_with(strtolower($filename), '.pdf')) {
+ $filename .= '.pdf';
+ }
+
+ header('Content-Type: application/pdf');
+ header('Content-Disposition: inline; filename="' . basename($filename) . '"');
+ header('Content-Length: ' . strlen($pdfBinary));
+
+ echo $pdfBinary;
+} catch (Exception $e) {
+ http_response_code(500);
+ header('Content-Type: application/json; charset=utf-8');
+
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+}
diff --git a/public/userarea/edit_template_xls.php b/public/userarea/edit_template_xls.php
index ef817d2..7915132 100644
--- a/public/userarea/edit_template_xls.php
+++ b/public/userarea/edit_template_xls.php
@@ -25,6 +25,41 @@ if (!$template) {
$stmt = $pdo->prepare("SELECT * FROM routine");
$stmt->execute();
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+$buttonBgPalette = [
+ '#0d6efd' => 'Blue',
+ '#6610f2' => 'Indigo',
+ '#6f42c1' => 'Purple',
+ '#d63384' => 'Pink',
+ '#dc3545' => 'Red',
+ '#fd7e14' => 'Orange',
+ '#ffc107' => 'Yellow',
+ '#198754' => 'Green',
+ '#20c997' => 'Teal',
+ '#0dcaf0' => 'Cyan',
+ '#212529' => 'Dark',
+ '#6c757d' => 'Gray',
+ '#495057' => 'Slate',
+ '#795548' => 'Brown',
+ '#2f5d50' => 'Sage Green',
+];
+
+$buttonTextPalette = [
+ '#ffffff' => 'White',
+ '#6c757d' => 'Gray',
+ '#000000' => 'Black',
+];
+
+$currentButtonBgColor = strtolower($template['button_bg_color'] ?? '#007bff');
+$currentButtonTextColor = strtolower($template['button_text_color'] ?? '#ffffff');
+
+if (!array_key_exists($currentButtonBgColor, array_change_key_case($buttonBgPalette, CASE_LOWER))) {
+ $currentButtonBgColor = '#0d6efd';
+}
+
+if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonTextPalette, CASE_LOWER))) {
+ $currentButtonTextColor = '#ffffff';
+}
?>
@@ -35,6 +70,64 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
@@ -258,6 +385,16 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
sourceType.addEventListener('change', updateSourceFields);
updateSourceFields();
+ function formatClientLabel(client) {
+ const nome = client.Nominativo || "Name not available";
+ const id = client.IdCliente || "";
+ const codiceCliente = (client.CodiceCliente ?? client.codiceCliente ?? "").toString().trim();
+ const suffix = (codiceCliente.split("_")[1] || "").trim();
+ const shortCode = suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
+
+ return `${nome.trim()} - ${shortCode} (ID: ${id})`;
+ }
+
async function loadClients() {
try {
clientLoadingStatus.style.display = 'inline';
@@ -280,9 +417,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
select.innerHTML = '
';
data.value.forEach(client => {
- const nome = client.Nominativo || "Name not available";
const id = client.IdCliente || "";
- const option = new Option(`${nome.trim()} (ID: ${id})`, id);
+ const option = new Option(formatClientLabel(client), id);
if (parseInt(id) === parseInt(selectedClientId)) {
option.selected = true;
diff --git a/public/userarea/error_log.txt b/public/userarea/error_log.txt
index 4c5140a..9d43509 100644
--- a/public/userarea/error_log.txt
+++ b/public/userarea/error_log.txt
@@ -328,3 +328,6 @@
2026-03-26 10:57:41 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
2026-03-26 10:57:56 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
2026-03-26 10:58:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
+2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
+2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
+2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
diff --git a/public/userarea/get_rapporto_full_by_codice.php b/public/userarea/get_rapporto_full_by_codice.php
new file mode 100644
index 0000000..9d311e0
--- /dev/null
+++ b/public/userarea/get_rapporto_full_by_codice.php
@@ -0,0 +1,256 @@
+ "CodiceRapporto eq '{$codiceRapportoSafe}'",
+ '$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
+ '$top' => 1
+ ];
+
+ $searchEndpoint = "Rapporto?" . http_build_query($searchParams);
+ $searchData = $api->get($searchEndpoint);
+
+ $rapporti = $searchData['value'] ?? [];
+
+ if (empty($rapporti)) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
+ 'codice_rapporto' => $codiceRapporto
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+ exit;
+ }
+
+ $rapportoBase = $rapporti[0];
+ $idRapporto = intval($rapportoBase['IdRapporto'] ?? 0);
+
+ if (!$idRapporto) {
+ throw new Exception("IdRapporto non trovato nella risposta.");
+ }
+
+ /**
+ * STEP 2 - Dettaglio Rapporto
+ */
+ $clienteExpandError = null;
+ $detailEndpoint = "Rapporto({$idRapporto})?\$expand=Cliente,RapportiFiles,CampioniDatiRapporto";
+
+ try {
+ $detailData = $api->get($detailEndpoint);
+ } catch (Exception $e) {
+ $clienteExpandError = $e->getMessage();
+ $detailEndpoint = "Rapporto({$idRapporto})?\$expand=RapportiFiles,CampioniDatiRapporto";
+ $detailData = $api->get($detailEndpoint);
+ }
+
+ file_put_contents(
+ $debugDir . "rapporto_{$codiceRapportoSafe}_light.json",
+ json_encode($detailData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
+ );
+
+ $cliente = $detailData['Cliente'] ?? null;
+ $rapportiFiles = $detailData['RapportiFiles'] ?? [];
+ $campioniDatiRapporto = $detailData['CampioniDatiRapporto'] ?? [];
+
+ // ====================== CLIENTE ======================
+ $clienteInfo = [
+ 'found' => is_array($cliente),
+ 'id_cliente' => $cliente['IdCliente'] ?? $cliente['Id'] ?? null,
+ 'codice_cliente' => $cliente['CodiceCliente'] ?? $cliente['Codice'] ?? null,
+ 'nome_cliente' => $cliente['Nominativo'] ?? $cliente['RagioneSociale'] ?? $cliente['Nome'] ?? $cliente['Descrizione'] ?? null,
+ 'partita_iva' => $cliente['PartitaIva'] ?? null,
+ 'codice_fiscale' => $cliente['CodiceFiscale'] ?? null
+ ];
+
+ // ====================== FILE PDF ======================
+ $selectedRapportoFile = null;
+ $idRapportoFile = null;
+ $pdfFileName = $codiceRapporto . '.pdf';
+ $pdfFullPath = $pdfDir . $pdfFileName;
+
+ if (is_array($rapportiFiles) && count($rapportiFiles) > 0) {
+ foreach ($rapportiFiles as $file) {
+ $fileName = $file['FileName'] ?? '';
+ $tipoRapporto = $file['TipoRapporto'] ?? '';
+ $categoria = $file['Categoria'] ?? '';
+
+ if (
+ stripos($fileName, '.pdf') !== false ||
+ stripos($tipoRapporto, 'Rapporto') !== false ||
+ stripos($categoria, 'Rapporti') !== false
+ ) {
+ $selectedRapportoFile = $file;
+ $idRapportoFile = $file['IdRapportoFile'] ?? null;
+ break;
+ }
+ }
+
+ if (!$selectedRapportoFile && count($rapportiFiles) > 0) {
+ $selectedRapportoFile = $rapportiFiles[0];
+ $idRapportoFile = $rapportiFiles[0]['IdRapportoFile'] ?? null;
+ }
+ }
+
+ // ====================== DOWNLOAD PDF - DEBUG AVANZATO ======================
+ $pdfDownloaded = false;
+ $pdfError = null;
+ $endpointUsato = null;
+
+ if ($downloadPdf && $idRapportoFile) {
+
+ $tentativi = [
+ "RapportoFile({$idRapportoFile})/\$value",
+ "RapportoFile({$idRapportoFile})/Contenuto/\$value",
+ "RapportoFile({$idRapportoFile})/File/\$value",
+ "Rapporto({$idRapporto})/RapportiFiles({$idRapportoFile})/\$value",
+ "Rapporto({$idRapporto})/RapportoFile/\$value",
+ "GetRapportoFile?IdRapportoFile={$idRapportoFile}",
+ "RapportoFile/Download?Id={$idRapportoFile}",
+ "Rapporto/DownloadFile?IdRapporto={$idRapporto}"
+ ];
+
+ foreach ($tentativi as $ep) {
+ try {
+ $pdfContent = $api->getRaw($ep);
+
+ if (strlen($pdfContent) > 2000) { // PDF decente deve essere più grande
+ file_put_contents($pdfFullPath, $pdfContent);
+ $pdfDownloaded = true;
+ $endpointUsato = $ep;
+ $pdfError = "SUCCESSO con: " . $ep;
+ break;
+ } else {
+ $pdfError = "Contenuto troppo piccolo con: " . $ep;
+ }
+ } catch (Exception $e) {
+ $pdfError = "Fallito con " . $ep . " → " . $e->getMessage();
+ }
+ }
+ }
+
+ // ====================== CAMPIONI + RATING (ripristinato dal tuo originale) ======================
+ $campioniSintesi = [];
+ $ratingFinale = null;
+ $ratingSource = null;
+ $hasIrregolare = false;
+ $hasPositiveResults = false;
+
+ if (is_array($campioniDatiRapporto)) {
+ foreach ($campioniDatiRapporto as $campione) {
+ $giudizioRapporto = $campione['GiudizioRapporto'] ?? null;
+ $giudizioCertificato = $campione['GiudizioCertificato'] ?? null;
+ $esitoGiudizioLMR = $campione['EsitoGiudizioLMR'] ?? null;
+ $esitoGiudizioImpiego = $campione['EsitoGiudizioImpiego'] ?? null;
+ $risultatiIrregolari = ($campione['RisultatiIrregolari'] ?? false) === true;
+ $risultatiPositivi = ($campione['RisultatiPositivi'] ?? false) === true;
+
+ $campioniSintesi[] = [
+ 'id_campione_dati_rapporto' => $campione['IdCampioneDatiRapporto'] ?? null,
+ 'codice_campione' => $campione['CodiceCampione'] ?? null,
+ 'stato_campione' => $campione['StatoCampione'] ?? null,
+ 'stato_campione_web' => $campione['StatoCampioneWeb'] ?? null,
+ 'matrice' => $campione['Matrice'] ?? null,
+ 'macro_matrice' => $campione['MacroMatrice'] ?? null,
+ 'giudizio_rapporto' => $giudizioRapporto,
+ 'giudizio_certificato' => $giudizioCertificato,
+ 'esito_giudizio_lmr' => $esitoGiudizioLMR,
+ 'esito_giudizio_impiego' => $esitoGiudizioImpiego,
+ 'risultati_positivi' => $risultatiPositivi,
+ 'risultati_irregolari' => $risultatiIrregolari
+ ];
+
+ if (!$ratingFinale && !empty($giudizioRapporto)) {
+ $ratingFinale = $giudizioRapporto;
+ $ratingSource = 'CampioniDatiRapporto.GiudizioRapporto';
+ }
+
+ if (!$ratingFinale && !empty($giudizioCertificato)) {
+ $ratingFinale = $giudizioCertificato;
+ $ratingSource = 'CampioniDatiRapporto.GiudizioCertificato';
+ }
+
+ if ($risultatiIrregolari) $hasIrregolare = true;
+ if ($risultatiPositivi) $hasPositiveResults = true;
+ }
+ }
+
+ // Fallback rating
+ if (!$ratingFinale) {
+ if ($hasIrregolare) {
+ $ratingFinale = 'Irregolare';
+ $ratingSource = 'RisultatiIrregolari';
+ } elseif ($hasPositiveResults || count($campioniSintesi) > 0) {
+ $ratingFinale = 'Regolare';
+ $ratingSource = 'fallback';
+ }
+ }
+
+ // ====================== RISPOSTA FINALE ======================
+ echo json_encode([
+ 'success' => true,
+
+ 'codice_rapporto' => $codiceRapporto,
+ 'id_rapporto' => $idRapporto,
+
+ 'search_endpoint' => $searchEndpoint,
+ 'detail_endpoint' => $detailEndpoint,
+ 'cliente_expand_error' => $clienteExpandError,
+
+ 'rapporto_base' => $rapportoBase,
+ 'cliente' => $clienteInfo,
+
+ 'rating_finale' => $ratingFinale,
+ 'rating_source' => $ratingSource,
+
+ 'rapporti_files_count' => count($rapportiFiles),
+ 'selected_rapporto_file' => $selectedRapportoFile,
+
+ 'pdf_file_name' => $pdfFileName,
+ 'pdf_path' => $pdfDownloaded ? $pdfFullPath : null,
+ 'pdf_downloaded' => $pdfDownloaded,
+ 'pdf_error' => $pdfError,
+
+ 'campioni_count' => count($campioniSintesi),
+ 'campioni_sintesi' => $campioniSintesi,
+
+ 'download_requested' => $downloadPdf
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+}
diff --git a/public/userarea/get_rapporto_lims.php b/public/userarea/get_rapporto_lims.php
new file mode 100644
index 0000000..a3eff2e
--- /dev/null
+++ b/public/userarea/get_rapporto_lims.php
@@ -0,0 +1,118 @@
+ "CodiceCommessa eq '{$commessa}'",
+ 'Commessa_CodiceCommessa' => "Commessa/CodiceCommessa eq '{$commessa}'",
+ 'Commessa_IdCommessa' => is_numeric($commessa) ? "Commessa/IdCommessa eq {$commessa}" : null,
+ 'Codice' => "Codice eq '{$commessa}'"
+ ];
+
+ foreach ($filters as $label => $filter) {
+ if (!$filter) {
+ continue;
+ }
+
+ try {
+ $options = [
+ '$filter' => $filter,
+ '$top' => 10
+ ];
+
+ $data = $api->get('Rapporto', $options);
+
+ $attempts[$label] = [
+ 'success' => true,
+ 'filter' => $filter,
+ 'records' => isset($data['value']) && is_array($data['value']) ? count($data['value']) : null,
+ 'data' => $data
+ ];
+
+ file_put_contents(
+ $debugDir . "rapporto_{$commessa}_{$label}.json",
+ json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
+ );
+ } catch (Exception $e) {
+ $attempts[$label] = [
+ 'success' => false,
+ 'filter' => $filter,
+ 'error' => $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * STEP 2
+ * Prendo solo eventuali rapporti trovati.
+ */
+ $rapportiFound = [];
+
+ foreach ($attempts as $label => $attempt) {
+ if (!$attempt['success']) {
+ continue;
+ }
+
+ $items = $attempt['data']['value'] ?? [];
+
+ if (!is_array($items)) {
+ continue;
+ }
+
+ foreach ($items as $item) {
+ $rapportiFound[] = [
+ 'matched_by' => $label,
+ 'rapporto' => $item
+ ];
+ }
+ }
+
+ echo json_encode([
+ 'success' => true,
+ 'input_commessa' => $commessa,
+ 'message' => 'Ricerca leggera su Rapporto completata. Se trovi un rapporto, poi recuperiamo RapportiFiles solo per quello.',
+ 'rapporti_found_count' => count($rapportiFound),
+ 'rapporti_found' => $rapportiFound,
+ 'attempts_summary' => array_map(function ($a) {
+ return [
+ 'success' => $a['success'],
+ 'filter' => $a['filter'],
+ 'records' => $a['records'] ?? null,
+ 'error' => $a['error'] ?? null
+ ];
+ }, $attempts)
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+} catch (Exception $e) {
+ http_response_code(500);
+
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+}
diff --git a/public/userarea/get_rapporto_prova.php b/public/userarea/get_rapporto_prova.php
index 16e8020..0ae0cd9 100644
--- a/public/userarea/get_rapporto_prova.php
+++ b/public/userarea/get_rapporto_prova.php
@@ -2,25 +2,140 @@
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
-header('Content-Type: application/json');
+header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
- $rapporto_id = 533329;
- // Costruzione manuale dell'endpoint con espansione annidata
- $endpoint = "Rapporto($rapporto_id)?\$expand=CampioniDatiRapporto(\$expand=AnalisiDatiRapporto,CustomFieldsDatiRapporto)";
+ // Esempi:
+ // rapporto_by_codice_expand_step.php?codice=2541111&step=base
+ // rapporto_by_codice_expand_step.php?codice=2541111&step=files
+ // rapporto_by_codice_expand_step.php?codice=2541111&step=campioni
+ // rapporto_by_codice_expand_step.php?codice=2541111&step=files_campioni
- // Non passiamo options, già incluso nell'endpoint
- $data = $api->get($endpoint);
+ $codiceRapporto = trim($_GET['codice'] ?? '');
+ $step = trim($_GET['step'] ?? 'base');
- file_put_contents(__DIR__ . '/rapporto_expanded.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
- echo json_encode($data);
+ if ($codiceRapporto === '') {
+ throw new Exception("Parametro codice mancante. Usa ?codice=2541111");
+ }
+
+ $allowedSteps = [
+ 'base' => '',
+ 'files' => 'RapportiFiles',
+ 'allegati' => 'RapportiAllegati',
+ 'campioni' => 'CampioniDatiRapporto',
+ 'files_campioni' => 'RapportiFiles,CampioniDatiRapporto'
+ ];
+
+ if (!array_key_exists($step, $allowedSteps)) {
+ throw new Exception("Step non valido. Usa: " . implode(', ', array_keys($allowedSteps)));
+ }
+
+ // Escape OData per eventuali apostrofi
+ $codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
+
+ /*
+ * STEP 1 - Trova IdRapporto partendo da CodiceRapporto.
+ * Query leggera, con $select e $top=1.
+ */
+ $searchParams = [
+ '$filter' => "CodiceRapporto eq '{$codiceRapportoSafe}'",
+ '$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
+ '$top' => 1
+ ];
+
+ $searchEndpoint = "Rapporto?" . http_build_query($searchParams);
+
+ $searchData = $api->get($searchEndpoint);
+
+ $items = $searchData['value'] ?? [];
+
+ if (!is_array($items) || count($items) === 0) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
+ 'codice_rapporto' => $codiceRapporto,
+ 'search_endpoint' => $searchEndpoint,
+ 'search_data' => $searchData
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+ exit;
+ }
+
+ $rapportoBase = $items[0];
+ $rapportoId = intval($rapportoBase['IdRapporto'] ?? 0);
+
+ if (!$rapportoId) {
+ throw new Exception("IdRapporto non trovato nella risposta.");
+ }
+
+ /*
+ * STEP 2 - Se step=base, restituisco solo la ricerca base.
+ */
+ if ($step === 'base') {
+ echo json_encode([
+ 'success' => true,
+ 'codice_rapporto' => $codiceRapporto,
+ 'id_rapporto' => $rapportoId,
+ 'step' => $step,
+ 'search_endpoint' => $searchEndpoint,
+ 'rapporto_base' => $rapportoBase
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+ exit;
+ }
+
+ /*
+ * STEP 3 - Espande SOLO il rapporto trovato.
+ */
+ $expandValue = $allowedSteps[$step];
+
+ $detailParams = [
+ '$expand' => $expandValue
+ ];
+
+ $detailEndpoint = "Rapporto({$rapportoId})?" . http_build_query($detailParams);
+
+ file_put_contents(
+ __DIR__ . '/last_rapporto_by_codice_expand_endpoint.txt',
+ '[' . date('Y-m-d H:i:s') . '] SEARCH: ' . $searchEndpoint . PHP_EOL .
+ '[' . date('Y-m-d H:i:s') . '] DETAIL: ' . $detailEndpoint . PHP_EOL,
+ FILE_APPEND
+ );
+
+ $detailData = $api->get($detailEndpoint);
+
+ file_put_contents(
+ __DIR__ . "/rapporto_codice_{$codiceRapportoSafe}_{$step}.json",
+ json_encode([
+ 'search' => $searchData,
+ 'detail' => $detailData
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
+ );
+
+ echo json_encode([
+ 'success' => true,
+ 'codice_rapporto' => $codiceRapporto,
+ 'id_rapporto' => $rapportoId,
+ 'step' => $step,
+ 'search_endpoint' => $searchEndpoint,
+ 'detail_endpoint' => $detailEndpoint,
+ 'rapporto_base' => $rapportoBase,
+ 'data' => $detailData
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
- file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
+ file_put_contents(
+ __DIR__ . '/error_log.txt',
+ date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
+ FILE_APPEND
+ );
+
http_response_code(500);
- echo json_encode(['error' => $e->getMessage()]);
+
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js
index bf762e0..a1d0149 100644
--- a/public/userarea/gridRenderer.js
+++ b/public/userarea/gridRenderer.js
@@ -57,7 +57,20 @@
// ── Client data (AJAX Select2, no bulk loading) ──────────────────────
function formatClientLabel(client) {
- return (client.Nominativo || "").trim();
+ const nome = client.Nominativo || client.text || "Nome non disponibile";
+ const id = client.IdCliente || client.id || "";
+ const codiceCliente = (
+ client.CodiceCliente ??
+ client.codiceCliente ??
+ ""
+ )
+ .toString()
+ .trim();
+ const suffix = (codiceCliente.split("_")[1] || "").trim();
+ const shortCode =
+ suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
+
+ return `${nome.trim()} - ${shortCode} (ID: ${id})`;
}
// Cache of resolved client names: id → name
@@ -102,7 +115,9 @@
);
const json = await resp.json();
const item = (json.results || [])[0];
- if (item) clientNameCache[id] = item.text;
+ if (item) {
+ clientNameCache[id] = formatClientLabel(item);
+ }
} catch (e) {
/* ignore */
}
@@ -125,7 +140,12 @@
return { q: params.term || "", limit: 20 };
},
processResults: function (data) {
- return { results: data.results || [] };
+ const results = (data.results || []).map((item) => ({
+ ...item,
+ text: formatClientLabel(item),
+ }));
+
+ return { results: results };
},
cache: true,
},
diff --git a/public/userarea/mapping_template_xls_scheme2.php b/public/userarea/mapping_template_xls_scheme2.php
index c298501..a9f8d92 100644
--- a/public/userarea/mapping_template_xls_scheme2.php
+++ b/public/userarea/mapping_template_xls_scheme2.php
@@ -7,7 +7,26 @@ if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
$id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
-$stmt = $pdo->prepare("SELECT name, header_row, start_column, target_table, sample_xlsx, idclient, clientname, idschema, schemaname, schemajson, xls_headers FROM excel_templates WHERE id = ?");
+
+$stmt = $pdo->prepare("
+ SELECT
+ name,
+ header_row,
+ start_column,
+ target_table,
+ source_type,
+ sample_xlsx,
+ idclient,
+ clientname,
+ idschema,
+ schemaname,
+ schemajson,
+ xls_headers,
+ api_sample_json,
+ json_nodes
+ FROM excel_templates
+ WHERE id = ?
+");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -15,13 +34,43 @@ if (!$template) {
die("Template not found");
}
+$sourceType = strtoupper($template['source_type'] ?? 'XLS');
+if (!in_array($sourceType, ['XLS', 'API', 'PDF'], true)) {
+ $sourceType = 'XLS';
+}
+
$clientName = $template['clientname'] ?: '';
$schemaName = $template['schemaname'] ?: '';
$schemajson = $template['schemajson'] ? json_decode($template['schemajson'], true) : [];
-$isSchemajsonEmpty = empty(trim($template['schemajson']));
+$isSchemajsonEmpty = empty(trim($template['schemajson'] ?? ''));
// Recupera i campi dalla tabella template_mapping
-$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, auto_value, data_type, is_required, default_value, has_list, length, decimals, min_value, max_value, default_curr_date, tablename, field_label, main_field, is_visible_import, is_visible_parts FROM template_mapping WHERE template_id = ?");
+$stmt = $pdo->prepare("
+ SELECT
+ id,
+ field_id,
+ excel_column,
+ json_node,
+ is_manual,
+ manual_default,
+ auto_value,
+ data_type,
+ is_required,
+ default_value,
+ has_list,
+ length,
+ decimals,
+ min_value,
+ max_value,
+ default_curr_date,
+ tablename,
+ field_label,
+ main_field,
+ is_visible_import,
+ is_visible_parts
+ FROM template_mapping
+ WHERE template_id = ?
+");
$stmt->execute([$id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -37,11 +86,25 @@ $fixedMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
$hasFixedMappings = !empty($fixedMappings);
-// Recupera le colonne già associate nel database
+// Recupera le colonne XLS già associate nel database
$usedColumnsFromDB = array_filter(array_column($mappings, 'excel_column'));
+// Recupera i nodi JSON già associati nel database
+$usedJsonNodesFromDB = array_filter(array_column($mappings, 'json_node'));
+
// Decodifica l'header XLS salvato, se presente
$xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], true) : [];
+if (!is_array($xlsHeaders)) {
+ $xlsHeaders = [];
+}
+
+// Decodifica nodi JSON API salvati, se presenti
+$jsonNodes = $template['json_nodes'] ? json_decode($template['json_nodes'], true) : [];
+if (!is_array($jsonNodes)) {
+ $jsonNodes = [];
+}
+
+$apiSampleJson = $template['api_sample_json'] ?? '';
?>
@@ -163,7 +226,8 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
width: 100% !important;
}
- .xls-columns option.used-option {
+ .xls-columns option.used-option,
+ .json-nodes option.used-option {
background-color: #fff3cd;
color: #856404;
font-weight: 600;
@@ -176,6 +240,11 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
overflow: hidden;
text-overflow: ellipsis;
}
+
+ #apiJsonExample {
+ font-family: Consolas, Monaco, monospace;
+ font-size: 13px;
+ }
@@ -195,30 +264,63 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
Client: |
Schema: |
- Header Row: |
- Start Column:
+ Source:
+
+ |
+ Header Row: |
+ Start Column:
+