Files
trf_certest/public/userarea/send_export_summary.php
T

346 lines
15 KiB
PHP

<?php
include('include/headscript.php');
require_once __DIR__ . '/class/Mailer.class.php';
require_once __DIR__ . '/class/block_alert.php';
header('Content-Type: application/json');
/**
* Risolve [IdCliente => Nominativo] leggendo la stessa cache di search_clienti.php.
* Non chiama l'API: usa cache/clienti.json (già mantenuto aggiornato dalla ricerca clienti).
*/
function resolveClientNames(array $ids): array
{
$ids = array_values(array_filter(array_map('intval', $ids), fn($v) => $v > 0));
if (empty($ids)) return [];
$cacheFile = __DIR__ . '/cache/clienti.json';
if (!file_exists($cacheFile)) return [];
$data = json_decode((string)@file_get_contents($cacheFile), true);
$clients = $data['value'] ?? [];
if (empty($clients)) return [];
// indicizza per IdCliente
$byId = [];
foreach ($clients as $c) {
$cid = (int)($c['IdCliente'] ?? 0);
if ($cid > 0) {
$byId[$cid] = trim((string)($c['Nominativo'] ?? ''));
}
}
$out = [];
foreach ($ids as $id) {
if (isset($byId[$id]) && $byId[$id] !== '') {
$out[$id] = $byId[$id];
}
}
return $out;
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// input: solo POST (endpoint che invia email — niente attivazione via URL)
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Metodo non consentito']);
exit;
}
$ids = $_POST['ids'] ?? [];
if (!is_array($ids)) $ids = explode(',', (string)$ids);
$ids = array_values(array_filter(array_map('intval', $ids), fn($v) => $v > 0));
$templateId = (int)($_POST['template_id'] ?? 0);
if (empty($ids)) {
echo json_encode(['success' => false, 'message' => 'Nessun id']);
exit;
}
// Fallback: se il template_id non arriva, ricavalo dalla prima commessa
if ($templateId <= 0) {
$tq = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = ? LIMIT 1");
$tq->execute([$ids[0]]);
$templateId = (int)$tq->fetchColumn();
}
// tutti i main field del template (possono essere fino a 2), in ordine
$mainMappingIds = [];
$templateName = '';
if ($templateId > 0) {
$s = $pdo->prepare("
SELECT id FROM template_mapping
WHERE template_id = ? AND main_field = '1' AND is_visible_import = 1
ORDER BY field_order ASC, id ASC
");
$s->execute([$templateId]);
$mainMappingIds = array_map('intval', $s->fetchAll(PDO::FETCH_COLUMN));
// nome del template per l'intestazione mail
$t = $pdo->prepare("SELECT name FROM excel_templates WHERE id = ? LIMIT 1");
$t->execute([$templateId]);
$templateName = (string)$t->fetchColumn();
}
// esiti delle commesse
$ph = implode(',', array_fill(0, count($ids), '?'));
$sql = "
SELECT d.iddatadb, d.commessaweb, d.status, d.user_id,
d.idclient, d.ClienteFatturazione,
CONCAT(u.first_name,' ',u.last_name) AS user_name,
u.email AS user_email, u.notify_export_email
FROM datadb d
LEFT JOIN auth_users u ON d.user_id = u.id
WHERE d.iddatadb IN ($ph)
ORDER BY FIELD(d.iddatadb, $ph)
";
$stmt = $pdo->prepare($sql);
$stmt->execute(array_merge($ids, $ids));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)) {
echo json_encode(['success' => false, 'message' => 'Righe non trovate']);
exit;
}
// --- Risoluzione nomi cliente (idclient + ClienteFatturazione) ---
$clientIdsToResolve = [];
foreach ($rows as $r) {
if (!empty($r['idclient'])) $clientIdsToResolve[(int)$r['idclient']] = true;
if (!empty($r['ClienteFatturazione'])) $clientIdsToResolve[(int)$r['ClienteFatturazione']] = true;
}
$clientNames = resolveClientNames(array_keys($clientIdsToResolve)); // [id => nome]
// helper: id cliente → nome, con fallback all'id se non risolto
$clientLabel = function ($id) use ($clientNames) {
$id = (int)$id;
if ($id <= 0) return '—';
return htmlspecialchars($clientNames[$id] ?? (string)$id);
};
// valori dei main field per ogni iddatadb — concatena i main valorizzati
$mainValues = [];
if (!empty($mainMappingIds)) {
$mph = implode(',', array_fill(0, count($mainMappingIds), '?'));
$s = $pdo->prepare("
SELECT id AS iddatadb, mapping_id, field_value
FROM import_data_details
WHERE id IN ($ph) AND mapping_id IN ($mph)
");
$s->execute(array_merge($ids, $mainMappingIds));
// raggruppa per iddatadb, mantenendo l'ordine dei main
$byRow = []; // iddatadb => [mapping_id => value]
foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $r) {
$byRow[(int)$r['iddatadb']][(int)$r['mapping_id']] = $r['field_value'];
}
foreach ($byRow as $idd => $vals) {
$parts = [];
foreach ($mainMappingIds as $mid) { // ordine dei main
$v = trim((string)($vals[$mid] ?? ''));
if ($v !== '') $parts[] = $v;
}
$mainValues[$idd] = implode(' — ', $parts); // es. "descrizione — style code"
}
}
// analisi senza prezzo (NULL o 0) per ogni iddatadb
$noPriceByRow = []; // iddatadb => [ [part_number, part_description, analysis_name], ... ]
$s = $pdo->prepare("
SELECT a.iddatadb, MAX(a.analysis_name) AS analysis_name
FROM identification_parts_analyses a
WHERE a.iddatadb IN ($ph)
AND (a.analysis_price IS NULL OR a.analysis_price <= 0)
GROUP BY a.iddatadb, a.analysis_recordkey
ORDER BY a.iddatadb, analysis_name
");
$s->execute($ids);
foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $r) {
$noPriceByRow[(int)$r['iddatadb']][] = $r;
}
$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
$recipients = [];
foreach ($rows as $r) {
if (!empty($r['user_email']) && (int)($r['notify_export_email'] ?? 1) === 1) {
$recipients[$r['user_email']] = true;
}
}
$adminEmail = env('EXPORT_SUMMARY_ADMIN', 'admin@cesoft.io'); // sempre, indipendente dalla preferenza
if ($adminEmail) $recipients[$adminEmail] = true;
$recipients = array_keys($recipients);
// Se dopo il filtro non resta nessuno, non inviare
if (empty($recipients)) {
echo json_encode(['success' => true, 'message' => 'Nessun destinatario (notifiche disattivate)', 'recipients' => []]);
exit;
}
// conteggi. La mail riflette lo stato in DB al momento dell'invio: una riga ancora 'i'
// (export interrotto da timeout/errore di rete, o ancora in corso) NON è un successo.
$okCount = 0;
$errCount = 0;
$pendingCount = 0;
foreach ($rows as $r) {
if ($r['status'] === 'l') $okCount++;
elseif ($r['status'] === 'x') $errCount++;
else $pendingCount++;
}
$hasError = $errCount > 0;
$hasPending = $pendingCount > 0;
// --- tabella righe ---
$rowsHtml = '';
foreach ($rows as $r) {
$st = $r['status'];
if ($st === 'l') {
$label = 'Inviata';
$bg = '#e6f4ea';
$col = '#1e7e34';
} elseif ($st === 'x') {
$label = 'ERRORE';
$bg = '#fdecea';
$col = '#c0392b';
} else {
$label = 'NON CONFERMATA';
$bg = '#fff8e1';
$col = '#8a6d3b';
}
$commessa = htmlspecialchars($r['commessaweb'] ?: '—');
$main = htmlspecialchars($mainValues[(int)$r['iddatadb']] ?? '—');
$committente = $clientLabel($r['idclient'] ?? 0);
$fatturazione = $clientLabel($r['ClienteFatturazione'] ?? 0);
$rowsHtml .= "<tr>
<td style='padding:10px 10px;border-bottom:1px solid #eee;font-family:monospace;white-space:nowrap;vertical-align:top;'>{$commessa}</td>
<td style='padding:10px 10px;border-bottom:1px solid #eee;word-break:break-word;vertical-align:top;'>{$committente}</td>
<td style='padding:10px 10px;border-bottom:1px solid #eee;word-break:break-word;vertical-align:top;'>{$fatturazione}</td>
<td style='padding:10px 10px;border-bottom:1px solid #eee;word-break:break-word;vertical-align:top;'>{$main}</td>
<td style='padding:10px 10px;border-bottom:1px solid #eee;text-align:center;white-space:nowrap;vertical-align:top;'>
<span style='display:inline-block;padding:3px 10px;border-radius:12px;background:{$bg};color:{$col};font-weight:600;font-size:13px;'>{$label}</span>
</td>
</tr>";
// sotto-riga: solo le analisi No Price di questa commessa
$np = $noPriceByRow[(int)$r['iddatadb']] ?? [];
if (!empty($np)) {
$items = '';
foreach ($np as $n) {
$analysisName = htmlspecialchars((string)($n['analysis_name'] ?: 'Unnamed analysis'));
$items .= "<li style='margin:2px 0;'>{$analysisName}</li>";
}
$npCount = count($np);
$rowsHtml .= "<tr>
<td colspan='5' style='padding:8px 12px 12px 24px;border-bottom:1px solid #eee;background:#fff5f5;'>
<div style='margin-bottom:4px;font-size:12px;color:#c0392b;font-weight:600;'>
<span style='display:inline-block;padding:2px 8px;border-radius:10px;background:#c0392b;color:#fff;font-size:11px;'>No price</span>
&nbsp;Analisi senza prezzo ({$npCount})
</div>
<ul style='margin:0;padding-left:18px;font-size:13px;color:#555;'>{$items}</ul>
</td>
</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>
&nbsp;{$blCampo}: {$blNome}
</div>
<div style='margin-top:4px;font-size:13px;color:#842029;font-weight:700;'>{$blMotivo}</div>
</td>
</tr>";
}
}
if ($hasError) {
$headerColor = '#c0392b';
$headerText = 'Export completato con errori';
} elseif ($hasPending) {
$headerColor = '#b7791f';
$headerText = 'Export da verificare';
} else {
$headerColor = '#1e7e34';
$headerText = 'Export completato con successo';
}
$total = count($rows);
// Committente / Fatturazione in testata solo se uguali per tutte le righe (batch misti → "vari")
$headerClient = function ($col) use ($rows, $clientLabel) {
$vals = array_unique(array_map(fn($r) => (int)($r[$col] ?? 0), $rows));
return count($vals) === 1 ? $clientLabel(reset($vals)) : 'vari';
};
$html = "
<div style='font-family:Arial,Helvetica,sans-serif;max-width:820px;margin:0 auto;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;'>
<div style='background:{$headerColor};color:#fff;padding:18px 24px;'>
<h2 style='margin:0;font-size:18px;'>{$headerText}</h2>
<p style='margin:6px 0 0;font-size:13px;opacity:.9;'>Riepilogo esportazione verso LIMS" .
($templateName !== '' ? " &middot; Template: <strong>" . htmlspecialchars($templateName) . "</strong>" : "") . "</p>
<p style='margin:6px 0 0;font-size:13px;opacity:.95;'>
Committente: <strong>" . $headerClient('idclient') . "</strong>
&nbsp;&middot;&nbsp;
FatturazioneAvanzata: <strong>" . $headerClient('ClienteFatturazione') . "</strong>
</p>
</div>
<div style='padding:20px 24px;'>
<p style='margin:0 0 14px;font-size:14px;color:#333;'>
Totale commesse: <strong>{$total}</strong> &nbsp;•&nbsp;
<span style='color:#1e7e34;'>Inviate: {$okCount}</span> &nbsp;•&nbsp;
<span style='color:#c0392b;'>Errori: {$errCount}</span>" .
($pendingCount > 0 ? " &nbsp;•&nbsp; <span style='color:#8a6d3b;'>Non confermate: {$pendingCount}</span>" : "") .
($noPriceTotal > 0 ? " &nbsp;•&nbsp; <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</span>" : "") .
($blockTotal > 0 ? " &nbsp;•&nbsp; <span style='color:#842029;'>Clienti bloccati: {$blockTotal}</span>" : "") . "
</p>
<table style='width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed;'>
<thead>
<tr style='background:#f5f5f5;'>
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:14%;'>Commessa</th>
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:24%;'>Committente</th>
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:24%;'>Fatturazione</th>
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:24%;'>Riferimento</th>
<th style='padding:10px 10px;text-align:center;border-bottom:2px solid #ddd;width:14%;'>Status</th>
</tr>
</thead>
<tbody>{$rowsHtml}</tbody>
</table>
" . ($hasError ? "<p style='margin:18px 0 0;padding:12px;background:#fdecea;border-left:4px solid #c0392b;color:#c0392b;font-size:13px;border-radius:4px;'>⚠️ Alcune commesse non sono state inviate al LIMS. Controlla la sezione <strong>Errors</strong> nell'applicazione per i dettagli e per rilanciarle.</p>" : "") .
($hasPending ? "<p style='margin:18px 0 0;padding:12px;background:#fff8e1;border-left:4px solid #b7791f;color:#8a6d3b;font-size:13px;border-radius:4px;'>⚠️ Per alcune commesse l'esito non è ancora confermato (timeout o errore di rete durante l'invio). <strong>Non rilanciarle</strong>: ricarica la pagina tra qualche minuto e verifica lo stato.</p>" : "") . "
</div>
<div style='background:#fafafa;padding:14px 24px;font-size:12px;color:#999;border-top:1px solid #eee;'>
Messaggio automatico da SmartTRF — non rispondere a questa email.
</div>
</div>";
$subjectPrefix = $hasError ? '⚠️ Errori Export LIMS' : ($hasPending ? '⏳ Export LIMS da verificare' : '✅ Export LIMS OK');
$subject = "{$subjectPrefix} — {$total} commesse ({$okCount} ok, {$errCount} err" . ($pendingCount > 0 ? ", {$pendingCount} non confermate" : "") . ")";
$res = Mailer::send($recipients, $subject, $html);
echo json_encode([
'success' => $res['ok'],
'message' => $res['ok'] ? 'Mail inviata' : ('Mail non inviata: ' . $res['error']),
'recipients' => $recipients,
'counts' => ['ok' => $okCount, 'err' => $errCount, 'pending' => $pendingCount, 'total' => $total],
]);