added blocco cliente in codice asl
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controllo blocco cliente.
|
||||||
|
*
|
||||||
|
* Il cliente usa il campo CodiceASL dell'anagrafica LIMS come nota di blocco
|
||||||
|
* (es. "BLOCCO PER PAGAMENTO"), perché i campi nativi del Blocco Accettazione
|
||||||
|
* non sono esposti dall'API.
|
||||||
|
*
|
||||||
|
* Regola: se è indicato il cliente di Fatturazione avanzata si controlla quello,
|
||||||
|
* altrimenti il Client.
|
||||||
|
*
|
||||||
|
* NESSUNA chiamata API: legge solo il file di cache scritto da get_clienti.php
|
||||||
|
* (cache/clienti.json), con la sua durata già esistente. Se il file non c'è,
|
||||||
|
* il controllo viene saltato.
|
||||||
|
*
|
||||||
|
* Restituisce null se non c'è blocco o se il controllo non è possibile:
|
||||||
|
* è solo un avviso e non deve mai far fallire l'export o la mail.
|
||||||
|
*/
|
||||||
|
if (!function_exists('getClientBlockAlert')) {
|
||||||
|
function getClientBlockAlert($clienteId, $clienteFatturazione = null): ?array
|
||||||
|
{
|
||||||
|
// Mappa IdCliente => [Nominativo, CodiceASL], letta una sola volta per richiesta
|
||||||
|
static $clienti = null;
|
||||||
|
|
||||||
|
$useFatturazione = !empty($clienteFatturazione);
|
||||||
|
$targetId = $useFatturazione ? (int) $clienteFatturazione : (int) $clienteId;
|
||||||
|
|
||||||
|
if ($targetId <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($clienti === null) {
|
||||||
|
$clienti = [];
|
||||||
|
$cacheFile = dirname(__DIR__) . '/cache/clienti.json';
|
||||||
|
|
||||||
|
if (!is_file($cacheFile)) {
|
||||||
|
error_log("getClientBlockAlert: cache clienti non trovata ({$cacheFile}), controllo saltato");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode((string) file_get_contents($cacheFile), true);
|
||||||
|
foreach (($json['value'] ?? []) as $c) {
|
||||||
|
$clienti[(int) ($c['IdCliente'] ?? 0)] = [
|
||||||
|
'nominativo' => trim((string) ($c['Nominativo'] ?? '')),
|
||||||
|
'codiceASL' => trim((string) ($c['CodiceASL'] ?? '')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cliente = $clienti[$targetId] ?? null;
|
||||||
|
if (!$cliente) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$motivo = $cliente['codiceASL'];
|
||||||
|
if ($motivo === '' || stripos($motivo, 'blocco') === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'idCliente' => $targetId,
|
||||||
|
'nominativo' => $cliente['nominativo'],
|
||||||
|
'motivo' => $motivo,
|
||||||
|
'campo' => $useFatturazione ? 'Fatturazione avanzata' : 'Client',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,23 @@
|
|||||||
}
|
}
|
||||||
window.noPriceBadgeHTML = noPriceBadgeHTML;
|
window.noPriceBadgeHTML = noPriceBadgeHTML;
|
||||||
|
|
||||||
|
// Riquadro "cliente bloccato" per la finestra di export singolo
|
||||||
|
function blockAlertHTML(b) {
|
||||||
|
if (!b || !b.motivo) return "";
|
||||||
|
return (
|
||||||
|
`<div class="alert alert-danger py-2 px-3 mt-3 mb-0 small">` +
|
||||||
|
`<i class="fas fa-ban me-1"></i><strong>Cliente bloccato</strong> (${escHtml(b.campo)}): ` +
|
||||||
|
`${escHtml(b.nominativo)} - ${escHtml(b.idCliente)}<br>` +
|
||||||
|
`<strong>${escHtml(b.motivo)}</strong></div>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badge "Bloccato" per l'elenco commesse dell'Export All
|
||||||
|
function blockBadgeHTML(b) {
|
||||||
|
if (!b || !b.motivo) return "";
|
||||||
|
return `<span class="badge" title="${escHtml(b.nominativo + ": " + b.motivo)}" style="margin-left:6px; font-size:0.85em; cursor:help; vertical-align:middle; background:#f8d7da; color:#842029;"><i class="fas fa-ban"></i> Bloccato</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
function getGridRow(iddatadb) {
|
function getGridRow(iddatadb) {
|
||||||
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
|
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
|
||||||
}
|
}
|
||||||
@@ -267,7 +284,8 @@
|
|||||||
.map((n) => `<li>${escHtml(n)}</li>`)
|
.map((n) => `<li>${escHtml(n)}</li>`)
|
||||||
.join("") +
|
.join("") +
|
||||||
`</ul>`
|
`</ul>`
|
||||||
: "");
|
: "") +
|
||||||
|
blockAlertHTML(data.blockAlert);
|
||||||
label.textContent = "Esportazione Completata";
|
label.textContent = "Esportazione Completata";
|
||||||
} else {
|
} else {
|
||||||
msg.textContent = `Errore: ${data.message}`;
|
msg.textContent = `Errore: ${data.message}`;
|
||||||
@@ -761,6 +779,7 @@
|
|||||||
commessaweb: data.commessaweb,
|
commessaweb: data.commessaweb,
|
||||||
noPriceCount: data.noPriceCount || 0,
|
noPriceCount: data.noPriceCount || 0,
|
||||||
noPriceAnalyses: data.noPriceAnalyses || [],
|
noPriceAnalyses: data.noPriceAnalyses || [],
|
||||||
|
blockAlert: data.blockAlert || null,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
@@ -835,11 +854,17 @@
|
|||||||
const npCommesse = exportedList.filter(
|
const npCommesse = exportedList.filter(
|
||||||
(x) => x.noPriceCount > 0,
|
(x) => x.noPriceCount > 0,
|
||||||
).length;
|
).length;
|
||||||
|
const blockedCommesse = exportedList.filter(
|
||||||
|
(x) => x.blockAlert,
|
||||||
|
).length;
|
||||||
msgEl.innerHTML +=
|
msgEl.innerHTML +=
|
||||||
`<hr class="my-2">` +
|
`<hr class="my-2">` +
|
||||||
(npCommesse > 0
|
(npCommesse > 0
|
||||||
? `<div class="small text-danger fw-semibold mb-1"><i class="fas fa-exclamation-triangle"></i> ${npCommesse} commesse con analisi senza prezzo</div>`
|
? `<div class="small text-danger fw-semibold mb-1"><i class="fas fa-exclamation-triangle"></i> ${npCommesse} commesse con analisi senza prezzo</div>`
|
||||||
: "") +
|
: "") +
|
||||||
|
(blockedCommesse > 0
|
||||||
|
? `<div class="small fw-semibold mb-1" style="color:#842029;"><i class="fas fa-ban"></i> ${blockedCommesse} commesse con cliente bloccato</div>`
|
||||||
|
: "") +
|
||||||
`<div class="small fw-semibold mb-1">Commesse esportate:</div>` +
|
`<div class="small fw-semibold mb-1">Commesse esportate:</div>` +
|
||||||
`<ul class="mb-0 ps-3">` +
|
`<ul class="mb-0 ps-3">` +
|
||||||
exportedList
|
exportedList
|
||||||
@@ -850,6 +875,7 @@
|
|||||||
x.noPriceCount,
|
x.noPriceCount,
|
||||||
x.noPriceAnalyses,
|
x.noPriceAnalyses,
|
||||||
) +
|
) +
|
||||||
|
blockBadgeHTML(x.blockAlert) +
|
||||||
`</li>`,
|
`</li>`,
|
||||||
)
|
)
|
||||||
.join("") +
|
.join("") +
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once "class/VisualLimsApiClient.class.php";
|
require_once "class/VisualLimsApiClient.class.php";
|
||||||
|
require_once "class/block_alert.php";
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
@@ -904,6 +905,19 @@ try {
|
|||||||
|
|
||||||
// Indice di lotto: esito OK di questa commessa
|
// Indice di lotto: esito OK di questa commessa
|
||||||
$writeBatchIndex("OK iddatadb={$iddatadb} commessa={$commessaId} codice={$finalCodiceCommessa} log=" . basename($commessaLogFile));
|
$writeBatchIndex("OK iddatadb={$iddatadb} commessa={$commessaId} codice={$finalCodiceCommessa} log=" . basename($commessaLogFile));
|
||||||
|
// 🔹 STEP 10.2: Controllo blocco cliente (solo avviso, non blocca mai l'export)
|
||||||
|
$blockAlert = null;
|
||||||
|
try {
|
||||||
|
$blockAlert = getClientBlockAlert($clienteId, $clienteFatturazione);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Controllo blocco cliente fallito (iddatadb={$iddatadb}): " . $e->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
|
// 🔹 STEP 11: Prepare final response
|
||||||
$finalCommessa = [
|
$finalCommessa = [
|
||||||
"Cliente" => $clienteId,
|
"Cliente" => $clienteId,
|
||||||
@@ -929,6 +943,7 @@ try {
|
|||||||
"failedAnalyses" => $failedAnalyses,
|
"failedAnalyses" => $failedAnalyses,
|
||||||
"noPriceCount" => count($noPriceAnalyses),
|
"noPriceCount" => count($noPriceAnalyses),
|
||||||
"noPriceAnalyses" => $noPriceAnalyses,
|
"noPriceAnalyses" => $noPriceAnalyses,
|
||||||
|
"blockAlert" => $blockAlert,
|
||||||
"message" => "Export successful",
|
"message" => "Export successful",
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5,
|
"step5_create" => $logFileStep5,
|
||||||
|
|||||||
@@ -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");
|
$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);
|
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
$fakeClients = array_map(fn($id) => [
|
$fakeClients = array_map(fn($id) => [
|
||||||
'IdCliente' => (int) $id,
|
'IdCliente' => (int) $id,
|
||||||
'Nominativo' => "Cliente Simulato {$id}",
|
'Nominativo' => "Cliente Simulato {$id}",
|
||||||
'CodiceCliente' => "SIM_{$id}",
|
'CodiceCliente' => "SIM_{$id}",
|
||||||
|
'CodiceASL' => null, // campo usato come nota di blocco (es. "BLOCCO PER PAGAMENTO")
|
||||||
], $ids);
|
], $ids);
|
||||||
echo json_encode(['value' => $fakeClients]);
|
echo json_encode(['value' => $fakeClients]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parametri OData
|
// 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 = [
|
$params = [
|
||||||
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
|
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo,CodiceASL',
|
||||||
'$orderby' => 'Nominativo asc'
|
'$orderby' => 'Nominativo asc'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -54,10 +57,12 @@ try {
|
|||||||
$errorMessage = $e->getMessage();
|
$errorMessage = $e->getMessage();
|
||||||
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
|
// 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) {
|
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 {
|
try {
|
||||||
// Assumi che VisualLimsApiClient abbia un metodo per il refresh del token
|
$api->refreshToken();
|
||||||
$api->refreshToken(); // Da implementare in VisualLimsApiClient se non esiste
|
|
||||||
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
|
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
|
||||||
} catch (Exception $refreshEx) {
|
} catch (Exception $refreshEx) {
|
||||||
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
|
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");
|
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';
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
readfile($cacheFile);
|
readfile($cacheFile);
|
||||||
@@ -89,12 +94,12 @@ try {
|
|||||||
file_put_contents($cacheFile, $json);
|
file_put_contents($cacheFile, $json);
|
||||||
|
|
||||||
echo $json;
|
echo $json;
|
||||||
} catch (Exception $e) {
|
} catch (Throwable $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
$errorResponse = [
|
$errorResponse = [
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'trace' => $e->getTraceAsString()
|
'trace' => $e->getTraceAsString()
|
||||||
];
|
];
|
||||||
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
|
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
// Script di TEST: recupera TUTTI i campi di pochi clienti (nessun $select, nessuna cache).
|
||||||
|
// Da mettere nella stessa cartella di get_clienti.php.
|
||||||
|
//
|
||||||
|
// Uso:
|
||||||
|
// test_clienti_full.php -> 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);
|
||||||
|
}
|
||||||
@@ -263,6 +263,116 @@
|
|||||||
await resolveClientNames();
|
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 = '<i class="fas fa-exclamation-circle"></i>';
|
||||||
|
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 ───────────────────────────────────────────
|
// ── Fixed field data loading ───────────────────────────────────────────
|
||||||
|
|
||||||
const fixedFieldApiConfig = {
|
const fixedFieldApiConfig = {
|
||||||
@@ -463,6 +573,7 @@
|
|||||||
async function preloadAllData() {
|
async function preloadAllData() {
|
||||||
// 1. Clients
|
// 1. Clients
|
||||||
await loadClientData();
|
await loadClientData();
|
||||||
|
await loadClientBlocks();
|
||||||
|
|
||||||
// 2. Non-dependent fixed fields
|
// 2. Non-dependent fixed fields
|
||||||
const nonDependent = Object.keys(fixedFieldApiConfig).filter(
|
const nonDependent = Object.keys(fixedFieldApiConfig).filter(
|
||||||
@@ -875,6 +986,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyBlockMarker(rowIndex, rowDiv);
|
||||||
|
|
||||||
return rowDiv;
|
return rowDiv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -971,6 +1084,8 @@
|
|||||||
|
|
||||||
cell.classList.add("cell-changed");
|
cell.classList.add("cell-changed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
refreshAllBlockMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSingleRow(rowIndex) {
|
function renderSingleRow(rowIndex) {
|
||||||
@@ -1718,6 +1833,8 @@
|
|||||||
}
|
}
|
||||||
cell.classList.add("cell-changed");
|
cell.classList.add("cell-changed");
|
||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
|
|
||||||
|
if (colType === "idclient") applyBlockMarker(rowIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle SceltaMultipla changes and persist them into gridData.
|
// Handle SceltaMultipla changes and persist them into gridData.
|
||||||
@@ -1769,6 +1886,8 @@
|
|||||||
data[rowIndex]._dirty = true;
|
data[rowIndex]._dirty = true;
|
||||||
cell.classList.add("cell-changed");
|
cell.classList.add("cell-changed");
|
||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
|
|
||||||
|
if (key === "ClienteFatturazione") applyBlockMarker(rowIndex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1978,6 +2097,7 @@
|
|||||||
// Events
|
// Events
|
||||||
attachEvents();
|
attachEvents();
|
||||||
initLazySelect2();
|
initLazySelect2();
|
||||||
|
initBlockTooltips();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start when DOM ready
|
// Start when DOM ready
|
||||||
|
|||||||
@@ -1518,6 +1518,38 @@ $gridMeta = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
/* ── 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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
require_once __DIR__ . '/class/Mailer.class.php';
|
require_once __DIR__ . '/class/Mailer.class.php';
|
||||||
|
require_once __DIR__ . '/class/block_alert.php';
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,6 +159,16 @@ foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
|||||||
}
|
}
|
||||||
$noPriceTotal = array_sum(array_map('count', $noPriceByRow));
|
$noPriceTotal = array_sum(array_map('count', $noPriceByRow));
|
||||||
|
|
||||||
|
// cliente bloccato per ogni iddatadb (CodiceASL con "blocco", letto dalla cache clienti, nessuna chiamata API)
|
||||||
|
$blockByRow = []; // iddatadb => ['idCliente','nominativo','motivo','campo']
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$bl = getClientBlockAlert($r['idclient'] ?? 0, $r['ClienteFatturazione'] ?? null);
|
||||||
|
if ($bl) {
|
||||||
|
$blockByRow[(int)$r['iddatadb']] = $bl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$blockTotal = count($blockByRow);
|
||||||
|
|
||||||
// destinatari: utente/i dell'export che hanno la notifica attiva + admin fisso
|
// destinatari: utente/i dell'export che hanno la notifica attiva + admin fisso
|
||||||
$recipients = [];
|
$recipients = [];
|
||||||
foreach ($rows as $r) {
|
foreach ($rows as $r) {
|
||||||
@@ -242,6 +253,24 @@ foreach ($rows as $r) {
|
|||||||
</td>
|
</td>
|
||||||
</tr>";
|
</tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sotto-riga: cliente bloccato
|
||||||
|
$bl = $blockByRow[(int)$r['iddatadb']] ?? null;
|
||||||
|
if ($bl) {
|
||||||
|
$blCampo = htmlspecialchars($bl['campo']);
|
||||||
|
$blNome = htmlspecialchars($bl['nominativo'] !== '' ? $bl['nominativo'] : (string)$bl['idCliente']);
|
||||||
|
$blMotivo = htmlspecialchars($bl['motivo']);
|
||||||
|
|
||||||
|
$rowsHtml .= "<tr>
|
||||||
|
<td colspan='5' style='padding:8px 12px 12px 24px;border-bottom:1px solid #eee;background:#fdecea;'>
|
||||||
|
<div style='font-size:12px;color:#842029;font-weight:600;'>
|
||||||
|
<span style='display:inline-block;padding:2px 8px;border-radius:10px;background:#842029;color:#fff;font-size:11px;'>Cliente bloccato</span>
|
||||||
|
{$blCampo}: {$blNome}
|
||||||
|
</div>
|
||||||
|
<div style='margin-top:4px;font-size:13px;color:#842029;font-weight:700;'>{$blMotivo}</div>
|
||||||
|
</td>
|
||||||
|
</tr>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hasError) {
|
if ($hasError) {
|
||||||
@@ -280,7 +309,8 @@ $html = "
|
|||||||
<span style='color:#1e7e34;'>Inviate: {$okCount}</span> •
|
<span style='color:#1e7e34;'>Inviate: {$okCount}</span> •
|
||||||
<span style='color:#c0392b;'>Errori: {$errCount}</span>" .
|
<span style='color:#c0392b;'>Errori: {$errCount}</span>" .
|
||||||
($pendingCount > 0 ? " • <span style='color:#8a6d3b;'>Non confermate: {$pendingCount}</span>" : "") .
|
($pendingCount > 0 ? " • <span style='color:#8a6d3b;'>Non confermate: {$pendingCount}</span>" : "") .
|
||||||
($noPriceTotal > 0 ? " • <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</span>" : "") . "
|
($noPriceTotal > 0 ? " • <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</span>" : "") .
|
||||||
|
($blockTotal > 0 ? " • <span style='color:#842029;'>Clienti bloccati: {$blockTotal}</span>" : "") . "
|
||||||
</p>
|
</p>
|
||||||
<table style='width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed;'>
|
<table style='width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed;'>
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
Reference in New Issue
Block a user