From 9a0770391ea434a76f6871ddd9b813184d8fd9aa Mon Sep 17 00:00:00 2001
From: solocla
` +
+ `${escHtml(b.motivo)}
` +
(npCommesse > 0
? `` +
exportedList
@@ -850,6 +875,7 @@
x.noPriceCount,
x.noPriceAnalyses,
) +
+ blockBadgeHTML(x.blockAlert) +
``,
)
.join("") +
diff --git a/public/userarea/export_to_lims.php b/public/userarea/export_to_lims.php
index 084cbd67..35ccb7e1 100644
--- a/public/userarea/export_to_lims.php
+++ b/public/userarea/export_to_lims.php
@@ -1,5 +1,6 @@
getMessage());
+ }
+ $writeLog(
+ null,
+ $blockAlert ? json_encode($blockAlert, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : "Nessun blocco",
+ "STEP 10.2 - Controllo blocco cliente (iddatadb={$iddatadb})"
+ );
+
// 🔹 STEP 11: Prepare final response
$finalCommessa = [
"Cliente" => $clienteId,
@@ -929,6 +943,7 @@ try {
"failedAnalyses" => $failedAnalyses,
"noPriceCount" => count($noPriceAnalyses),
"noPriceAnalyses" => $noPriceAnalyses,
+ "blockAlert" => $blockAlert,
"message" => "Export successful",
"logFiles" => [
"step5_create" => $logFileStep5,
diff --git a/public/userarea/get_clienti.php b/public/userarea/get_clienti.php
index 917b520d..0357a30a 100644
--- a/public/userarea/get_clienti.php
+++ b/public/userarea/get_clienti.php
@@ -20,17 +20,20 @@ try {
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$fakeClients = array_map(fn($id) => [
- 'IdCliente' => (int) $id,
- 'Nominativo' => "Cliente Simulato {$id}",
+ 'IdCliente' => (int) $id,
+ 'Nominativo' => "Cliente Simulato {$id}",
'CodiceCliente' => "SIM_{$id}",
+ 'CodiceASL' => null, // campo usato come nota di blocco (es. "BLOCCO PER PAGAMENTO")
], $ids);
echo json_encode(['value' => $fakeClients]);
exit;
}
// Parametri OData
+ // CodiceASL viene usato dal cliente come nota di blocco (es. "BLOCCO PER PAGAMENTO"),
+ // perché i campi nativi del Blocco Accettazione non sono esposti dall'API.
$params = [
- '$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
+ '$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo,CodiceASL',
'$orderby' => 'Nominativo asc'
];
@@ -54,10 +57,12 @@ try {
$errorMessage = $e->getMessage();
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
if (strpos($errorMessage, 'HTTP 400') !== false && strpos($errorMessage, 'Cannot persist the object') !== false) {
- // Forza il refresh del token
+ // Forza il refresh del token, solo se la classe lo supporta
+ if (!method_exists($api, 'refreshToken')) {
+ throw $e;
+ }
try {
- // Assumi che VisualLimsApiClient abbia un metodo per il refresh del token
- $api->refreshToken(); // Da implementare in VisualLimsApiClient se non esiste
+ $api->refreshToken();
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
} catch (Exception $refreshEx) {
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
@@ -74,7 +79,7 @@ try {
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
}
- // Cache file (1 hour TTL)
+ // Cache file (15 minuti TTL: così un blocco impostato nel LIMS compare entro 15 minuti)
$cacheFile = __DIR__ . '/cache/clienti.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
readfile($cacheFile);
@@ -89,12 +94,12 @@ try {
file_put_contents($cacheFile, $json);
echo $json;
-} catch (Exception $e) {
+} catch (Throwable $e) {
http_response_code(500);
$errorResponse = [
'error' => $e->getMessage(),
- 'file' => $e->getFile(),
- 'line' => $e->getLine(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
diff --git a/public/userarea/get_clienti_fields.php b/public/userarea/get_clienti_fields.php
new file mode 100644
index 00000000..cfe3cb6e
--- /dev/null
+++ b/public/userarea/get_clienti_fields.php
@@ -0,0 +1,65 @@
+ primi 3 clienti
+// test_clienti_full.php?top=5 -> primi 5 clienti
+// test_clienti_full.php?id=123 -> solo il cliente con IdCliente = 123
+// test_clienti_full.php?expand=XYZ -> include anche la relazione XYZ
+
+require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
+require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
+
+header('Content-Type: application/json; charset=utf-8');
+ini_set('display_errors', '0');
+error_reporting(E_ALL);
+
+try {
+ $api = VisualLimsApiClient::getInstance();
+
+ $top = isset($_GET['top']) ? max(1, min(20, (int) $_GET['top'])) : 3;
+
+ $params = [
+ '$top' => $top,
+ '$orderby' => 'Nominativo asc',
+ ];
+
+ if (isset($_GET['id']) && ctype_digit($_GET['id'])) {
+ $params['$filter'] = 'IdCliente eq ' . (int) $_GET['id'];
+ }
+
+ if (!empty($_GET['expand']) && preg_match('/^[A-Za-z0-9_,\/()$=;]+$/', $_GET['expand'])) {
+ $params['$expand'] = $_GET['expand'];
+ }
+
+ // PHP_QUERY_RFC3986 codifica gli spazi come %20 (più compatibile con OData)
+ $endpoint = 'Cliente?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
+
+ $data = $api->get($endpoint);
+
+ // Copia su file per consultarla con calma
+ file_put_contents(
+ __DIR__ . '/clienti_full_response.json',
+ json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
+ );
+
+ // Elenco dei nomi dei campi trovati, utile per capire cosa è disponibile
+ $campi = [];
+ foreach ($data['value'] ?? [] as $row) {
+ $campi = array_unique(array_merge($campi, array_keys($row)));
+ }
+
+ echo json_encode([
+ 'endpoint' => $endpoint,
+ 'campi' => array_values($campi),
+ 'dati' => $data,
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+} catch (Throwable $e) {
+ http_response_code(500);
+ echo json_encode([
+ 'error' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+}
diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js
index b4fc60fd..716efae7 100644
--- a/public/userarea/gridRenderer.js
+++ b/public/userarea/gridRenderer.js
@@ -263,6 +263,116 @@
await resolveClientNames();
}
+ // ── Blocco clienti (CodiceASL contenente "blocco") ─────────────────────
+
+ const clientBlockMap = {}; // IdCliente -> testo completo di CodiceASL
+
+ async function loadClientBlocks() {
+ try {
+ const resp = await fetch("get_clienti.php", { cache: "no-store" });
+ const json = await resp.json();
+ (json.value || []).forEach((c) => {
+ const txt = (c.CodiceASL || "").trim();
+ if (txt && /blocco/i.test(txt)) {
+ clientBlockMap[String(c.IdCliente)] = txt;
+ }
+ });
+ console.log(
+ "[gridRenderer] clienti bloccati:",
+ Object.keys(clientBlockMap).length,
+ );
+ } catch (e) {
+ console.error(
+ "[gridRenderer] errore caricamento blocchi clienti",
+ e,
+ );
+ }
+ }
+
+ // Regola: se "Fatturazione avanzata" è compilata si controlla quella, altrimenti il Client
+ function getRowBlockInfo(rowIndex) {
+ const row = data[rowIndex];
+ if (!row) return null;
+ const fatt =
+ (row.fixedFields && row.fixedFields.ClienteFatturazione) || "";
+ if (fatt) {
+ return {
+ colKey: "ClienteFatturazione",
+ reason: clientBlockMap[String(fatt)] || "",
+ };
+ }
+ return {
+ colKey: "idclient",
+ reason: clientBlockMap[String(row.idclient || "")] || "",
+ };
+ }
+
+ function hideBlockTooltip(el) {
+ if (window.bootstrap && bootstrap.Tooltip) {
+ const t = bootstrap.Tooltip.getInstance(el);
+ if (t) t.dispose();
+ }
+ }
+
+ function applyBlockMarker(rowIndex, rowEl) {
+ const row = data[rowIndex];
+ if (!row) return;
+ rowEl =
+ rowEl ||
+ (rowContainer &&
+ rowContainer.querySelector(
+ `.grid-row[data-id="${row.iddatadb}"]`,
+ ));
+ if (!rowEl) return;
+
+ // Rimuove marcatori precedenti (sia su Client sia su Fatturazione avanzata)
+ rowEl
+ .querySelectorAll(".grid-cell.client-blocked")
+ .forEach((c) => c.classList.remove("client-blocked"));
+ rowEl.querySelectorAll(".client-block-icon").forEach((i) => {
+ hideBlockTooltip(i);
+ i.remove();
+ });
+
+ const info = getRowBlockInfo(rowIndex);
+ if (!info || !info.reason) return;
+
+ const cell = rowEl.querySelector(
+ `.grid-cell[data-col="${info.colKey}"]`,
+ );
+ if (!cell) return;
+
+ cell.classList.add("client-blocked");
+ const icon = document.createElement("span");
+ icon.className = "client-block-icon";
+ icon.innerHTML = '';
+ icon.dataset.reason = info.reason;
+ icon.setAttribute("aria-label", info.reason);
+ cell.appendChild(icon);
+ }
+
+ function refreshAllBlockMarkers() {
+ data.forEach((_, i) => applyBlockMarker(i));
+ }
+
+ function initBlockTooltips() {
+ $(document).on("mouseenter", ".client-block-icon", function () {
+ if (!(window.bootstrap && bootstrap.Tooltip)) {
+ this.title = this.dataset.reason;
+ return;
+ }
+ bootstrap.Tooltip.getOrCreateInstance(this, {
+ title: this.dataset.reason,
+ placement: "top",
+ container: "body",
+ trigger: "manual",
+ }).show();
+ });
+ $(document).on("mouseleave", ".client-block-icon", function () {
+ hideBlockTooltip(this);
+ });
+ }
+
// ── Fixed field data loading ───────────────────────────────────────────
const fixedFieldApiConfig = {
@@ -463,6 +573,7 @@
async function preloadAllData() {
// 1. Clients
await loadClientData();
+ await loadClientBlocks();
// 2. Non-dependent fixed fields
const nonDependent = Object.keys(fixedFieldApiConfig).filter(
@@ -875,6 +986,8 @@
}
}
+ applyBlockMarker(rowIndex, rowDiv);
+
return rowDiv;
}
@@ -971,6 +1084,8 @@
cell.classList.add("cell-changed");
});
+
+ refreshAllBlockMarkers();
}
function renderSingleRow(rowIndex) {
@@ -1718,6 +1833,8 @@
}
cell.classList.add("cell-changed");
updateDirtyIndicator();
+
+ if (colType === "idclient") applyBlockMarker(rowIndex);
});
// Handle SceltaMultipla changes and persist them into gridData.
@@ -1769,6 +1886,8 @@
data[rowIndex]._dirty = true;
cell.classList.add("cell-changed");
updateDirtyIndicator();
+
+ if (key === "ClienteFatturazione") applyBlockMarker(rowIndex);
}
});
@@ -1978,6 +2097,7 @@
// Events
attachEvents();
initLazySelect2();
+ initBlockTooltips();
}
// Start when DOM ready
diff --git a/public/userarea/imported.php b/public/userarea/imported.php
index 6ae3bd1c..510bf8ea 100644
--- a/public/userarea/imported.php
+++ b/public/userarea/imported.php
@@ -1518,6 +1518,38 @@ $gridMeta = [
}
+
+ /* ── Cliente bloccato (CodiceASL contiene "blocco") ── */
+ .grid-container .grid-cell.client-blocked {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .grid-container .grid-cell.client-blocked>select,
+ .grid-container .grid-cell.client-blocked .select2-container {
+ flex: 1 1 auto;
+ min-width: 0;
+ width: auto !important;
+ }
+
+ .grid-container .grid-cell.client-blocked select,
+ .grid-container .grid-cell.client-blocked .select2-container--default .select2-selection--single {
+ background-color: #f8d7da !important;
+ border-color: #f1aeb5 !important;
+ }
+
+ .grid-container .grid-cell.client-blocked .select2-selection__rendered {
+ color: #842029 !important;
+ }
+
+ .client-block-icon {
+ flex: 0 0 auto;
+ color: #dc3545;
+ font-size: 16px;
+ line-height: 1;
+ cursor: help;
+ }
+ ";
+ }
}
if ($hasError) {
@@ -280,7 +309,8 @@ $html = "
Inviate: {$okCount} •
Errori: {$errCount}" .
($pendingCount > 0 ? " • Non confermate: {$pendingCount}" : "") .
- ($noPriceTotal > 0 ? " • Analisi No price: {$noPriceTotal}" : "") . "
+ ($noPriceTotal > 0 ? " • Analisi No price: {$noPriceTotal}" : "") .
+ ($blockTotal > 0 ? " • Clienti bloccati: {$blockTotal}" : "") . "
+
+