Merge remote-tracking branch 'origin/fix/export-duplicate-guard'
This commit is contained in:
@@ -297,11 +297,12 @@ $rowsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Reinvia: richiama export_to_lims.php da capo (create incluso)
|
// Reinvia: richiama export_to_lims.php. Se la commessa era già stata creata,
|
||||||
|
// il CHECK PRELIMINARE la verifica nel LIMS e non ne crea una seconda.
|
||||||
$(document).on('click', '.resend-btn', function() {
|
$(document).on('click', '.resend-btn', function() {
|
||||||
const $btn = $(this);
|
const $btn = $(this);
|
||||||
const iddatadb = $btn.data('iddatadb');
|
const iddatadb = $btn.data('iddatadb');
|
||||||
if (!confirm('Reinviare questa commessa al LIMS? Verrà creata una nuova commessa nel LIMS.')) return;
|
if (!confirm('Reinviare questa commessa al LIMS? Se risulta già nel LIMS verrà solo riallineata, altrimenti ne verrà creata una nuova.')) return;
|
||||||
|
|
||||||
const original = $btn.html();
|
const original = $btn.html();
|
||||||
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
|
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
|
||||||
|
|||||||
@@ -169,15 +169,28 @@
|
|||||||
formData.append("iddatadb", iddatadb);
|
formData.append("iddatadb", iddatadb);
|
||||||
if (batchUuid) formData.append("batch_uuid", batchUuid);
|
if (batchUuid) formData.append("batch_uuid", batchUuid);
|
||||||
|
|
||||||
|
// Marca la riga come "in export" nei dati: se il grid virtuale la ri-renderizza
|
||||||
|
// durante l'attesa, il pulsante Export resta disabilitato.
|
||||||
|
const startIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
|
if (startIdx >= 0) window.gridData[startIdx]._exporting = true;
|
||||||
|
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
const response = await fetch("export_to_lims.php", {
|
const response = await fetch("export_to_lims.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
if (!response.ok)
|
if (!response.ok)
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
const data = await response.json();
|
data = await response.json();
|
||||||
|
} finally {
|
||||||
|
const endIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
|
if (endIdx >= 0) delete window.gridData[endIdx]._exporting;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
// Rifiutato perché la riga risulta già esportata (pagina non aggiornata):
|
||||||
|
// allinea la griglia allo stato reale invece di lasciarla esportabile.
|
||||||
|
if (data.success || (data.rejected && data.status === "l")) {
|
||||||
// Update gridData
|
// Update gridData
|
||||||
const idx = getRowIndexByIddatadb(iddatadb);
|
const idx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
@@ -444,8 +457,9 @@
|
|||||||
data.message || "Unknown error",
|
data.message || "Unknown error",
|
||||||
);
|
);
|
||||||
showExportResult(data);
|
showExportResult(data);
|
||||||
// Mail di riepilogo per la singola commessa
|
// Mail di riepilogo per la singola commessa (non per un export rifiutato:
|
||||||
sendExportSummaryMail([iddatadb]);
|
// non è stato inviato nulla e la riga può risultare già 'l')
|
||||||
|
if (!data.rejected) sendExportSummaryMail([iddatadb]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (gridRow) {
|
if (gridRow) {
|
||||||
setRowExporting(gridRow, false);
|
setRowExporting(gridRow, false);
|
||||||
@@ -453,6 +467,9 @@
|
|||||||
}
|
}
|
||||||
showRowError(gridRow, iddatadb, error.message);
|
showRowError(gridRow, iddatadb, error.message);
|
||||||
showExportResult({ success: false, message: error.message });
|
showExportResult({ success: false, message: error.message });
|
||||||
|
// Timeout/errore di rete: il server può aver completato lo stesso.
|
||||||
|
// La mail riporta lo stato reale della riga in DB.
|
||||||
|
sendExportSummaryMail([iddatadb]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -692,7 +709,8 @@
|
|||||||
const total = eligibleRows.length;
|
const total = eligibleRows.length;
|
||||||
let processed = 0,
|
let processed = 0,
|
||||||
succeeded = 0,
|
succeeded = 0,
|
||||||
failed = 0;
|
failed = 0,
|
||||||
|
skipped = 0;
|
||||||
const exportedList = []; // { commessaweb, noPriceCount, noPriceAnalyses }
|
const exportedList = []; // { commessaweb, noPriceCount, noPriceAnalyses }
|
||||||
const processedIds = []; // iddatadb processati (ok o errore) per la mail
|
const processedIds = []; // iddatadb processati (ok o errore) per la mail
|
||||||
|
|
||||||
@@ -728,11 +746,16 @@
|
|||||||
const gridRow = row || getGridRow(iddatadb);
|
const gridRow = row || getGridRow(iddatadb);
|
||||||
if (gridRow) setRowExporting(gridRow, true);
|
if (gridRow) setRowExporting(gridRow, true);
|
||||||
|
|
||||||
processedIds.push(iddatadb);
|
|
||||||
try {
|
try {
|
||||||
const data = await sendExport(iddatadb, batchUuid);
|
const data = await sendExport(iddatadb, batchUuid);
|
||||||
processed++;
|
processed++;
|
||||||
if (data.success) {
|
if (data.rejected) {
|
||||||
|
// Già esportata / export in corso altrove: niente inviato, fuori dalla mail
|
||||||
|
skipped++;
|
||||||
|
if (data.status !== "l" && gridRow)
|
||||||
|
showRowError(gridRow, iddatadb, data.message);
|
||||||
|
} else if (data.success) {
|
||||||
|
processedIds.push(iddatadb);
|
||||||
succeeded++;
|
succeeded++;
|
||||||
exportedList.push({
|
exportedList.push({
|
||||||
commessaweb: data.commessaweb,
|
commessaweb: data.commessaweb,
|
||||||
@@ -741,6 +764,7 @@
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
|
processedIds.push(iddatadb);
|
||||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (errIdx >= 0)
|
if (errIdx >= 0)
|
||||||
window.gridData[errIdx]._exportError =
|
window.gridData[errIdx]._exportError =
|
||||||
@@ -755,6 +779,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
processed++;
|
processed++;
|
||||||
failed++;
|
failed++;
|
||||||
|
processedIds.push(iddatadb);
|
||||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (errIdx >= 0)
|
if (errIdx >= 0)
|
||||||
window.gridData[errIdx]._exportError = error.message;
|
window.gridData[errIdx]._exportError = error.message;
|
||||||
@@ -793,13 +818,18 @@
|
|||||||
if (batchCancelled) {
|
if (batchCancelled) {
|
||||||
labelEl.textContent = "Export All — Cancelled";
|
labelEl.textContent = "Export All — Cancelled";
|
||||||
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong><br>Not processed: <strong>${total - processed}</strong>`;
|
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong><br>Not processed: <strong>${total - processed}</strong>`;
|
||||||
} else if (failed === 0) {
|
} else if (failed === 0 && skipped === 0) {
|
||||||
labelEl.textContent = "Export All — Complete";
|
labelEl.textContent = "Export All — Complete";
|
||||||
msgEl.innerHTML = `All <strong>${succeeded}</strong> rows exported successfully.`;
|
msgEl.innerHTML = `All <strong>${succeeded}</strong> rows exported successfully.`;
|
||||||
} else {
|
} else {
|
||||||
labelEl.textContent = "Export All — Completed with errors";
|
labelEl.textContent =
|
||||||
|
failed > 0
|
||||||
|
? "Export All — Completed with errors"
|
||||||
|
: "Export All — Complete";
|
||||||
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
|
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
|
||||||
}
|
}
|
||||||
|
if (skipped > 0)
|
||||||
|
msgEl.innerHTML += `<br>Skipped (already exported / in progress — reload the page): <strong>${skipped}</strong>`;
|
||||||
|
|
||||||
if (exportedList.length) {
|
if (exportedList.length) {
|
||||||
const npCommesse = exportedList.filter(
|
const npCommesse = exportedList.filter(
|
||||||
|
|||||||
@@ -143,6 +143,100 @@ function importaCommessaWithRetry($api, $commessaId, array $payload, $maxRetries
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔹 CHECK PRELIMINARE + LOCK: una sola esportazione per riga, mai una seconda commessa.
|
||||||
|
// Protegge da pagine non aggiornate (due schede, pagina Errors vecchia, doppio click,
|
||||||
|
// LIMS lento con richiesta ancora in corso).
|
||||||
|
$chkId = (int)($_POST['iddatadb'] ?? 0);
|
||||||
|
if ($chkId > 0) {
|
||||||
|
$rejectExport = function ($reason, $cur = null) use ($chkId, $commessaLogFile, $writeBatchIndex, $batchUuid) {
|
||||||
|
file_put_contents(
|
||||||
|
$commessaLogFile,
|
||||||
|
"\n[EXPORT RIFIUTATO] " . date('Y-m-d H:i:s') . " | {$reason}" .
|
||||||
|
" | user_id=" . ($GLOBALS['iduserlogin'] ?? 'n/d') .
|
||||||
|
" | IP=" . ($_SERVER['REMOTE_ADDR'] ?? '-') .
|
||||||
|
" | batch=" . ($batchUuid ?? '-') . "\n",
|
||||||
|
FILE_APPEND
|
||||||
|
);
|
||||||
|
$writeBatchIndex("SKIP iddatadb={$chkId} :: {$reason}");
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"rejected" => true,
|
||||||
|
"status" => $cur['status'] ?? null,
|
||||||
|
"commessaweb" => $cur['commessaweb'] ?? null,
|
||||||
|
"message" => "Commessa non inviata (riga {$chkId}): {$reason}. Ricarica la pagina.",
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lock per riga: tenuto fino a fine script (rilasciato da MySQL alla chiusura connessione)
|
||||||
|
$gotLock = (int)$pdo->query("SELECT GET_LOCK(" . $pdo->quote("lims_export_{$chkId}") . ", 0)")->fetchColumn();
|
||||||
|
if ($gotLock !== 1) {
|
||||||
|
$rejectExport("esportazione già in corso");
|
||||||
|
}
|
||||||
|
|
||||||
|
$chk = $pdo->prepare("SELECT status, idcommessaweb, commessaweb FROM datadb WHERE iddatadb = ?");
|
||||||
|
$chk->execute([$chkId]);
|
||||||
|
$cur = $chk->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$cur) {
|
||||||
|
$rejectExport("riga non trovata");
|
||||||
|
}
|
||||||
|
if (!in_array($cur['status'], ['i', 'x'], true)) {
|
||||||
|
$rejectExport("già esportata (status={$cur['status']}, commessa=" . ($cur['commessaweb'] ?: '-') . ")", $cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingId = (int)($cur['idcommessaweb'] ?? 0);
|
||||||
|
if ($existingId > 0) {
|
||||||
|
// Commessa già creata da un export precedente (fallito, o interrotto da un crash/timeout PHP):
|
||||||
|
// controlla lo stato nel LIMS invece di crearne una nuova (sarebbe un duplicato).
|
||||||
|
// NB: il revert a 'i' azzera idcommessaweb, quindi qui non arrivano le righe riportate a mano.
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
try {
|
||||||
|
$existing = $api->get("CommessaWeb({$existingId})");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$existing = null;
|
||||||
|
if (strpos($e->getMessage(), 'HTTP 404') === false) {
|
||||||
|
$rejectExport("impossibile verificare la commessa {$existingId} nel LIMS: " . $e->getMessage(), $cur);
|
||||||
|
}
|
||||||
|
// 404: la commessa non esiste più nel LIMS → si può esportare da capo
|
||||||
|
}
|
||||||
|
|
||||||
|
$stato = $existing['StatoCommessaWeb'] ?? null;
|
||||||
|
$notSentYet = $existing !== null && $stato === 'Nuova' && empty($existing['DataInviatoWeb']);
|
||||||
|
|
||||||
|
$writeLog(null, "IdCommessa esistente: {$existingId}\nStatoCommessaWeb: " . ($stato ?? '404') .
|
||||||
|
"\nDataInviatoWeb: " . ($existing['DataInviatoWeb'] ?? '-'), "PRECHECK - commessa esistente (iddatadb={$chkId})");
|
||||||
|
|
||||||
|
if ($stato === 'Elaborata') {
|
||||||
|
// Già importata nel LIMS: riallinea la riga, nessuna nuova commessa
|
||||||
|
$codice = substr(trim((string)($existing['CodiceCommessa'] ?? '')), 0, 30);
|
||||||
|
$pdo->prepare("UPDATE datadb SET status = 'l', commessaweb = :c, log_file = :lf WHERE iddatadb = :id")
|
||||||
|
->execute(['c' => $codice, 'lf' => basename($commessaLogFile), 'id' => $chkId]);
|
||||||
|
|
||||||
|
$writeLog(null, "Riga riallineata: status='l', commessaweb={$codice}", "PRECHECK - RECOVERED (iddatadb={$chkId})");
|
||||||
|
$writeBatchIndex("RECOVERED iddatadb={$chkId} commessa={$existingId} codice={$codice}");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"recovered" => true,
|
||||||
|
"idcommessaweb" => $existingId,
|
||||||
|
"commessaweb" => $codice,
|
||||||
|
"message" => "Commessa già presente nel LIMS: riga riallineata senza crearne una nuova",
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing !== null && !$notSentYet) {
|
||||||
|
// Inviata ma non (ancora) importata: rifare l'export creerebbe un duplicato → verifica manuale
|
||||||
|
$rejectExport("commessa {$existingId} già inviata al LIMS ma non ancora importata (stato: {$stato}). " .
|
||||||
|
"Riprova tra qualche minuto o verifica nel LIMS", $cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 404 o mai inviata al laboratorio: si ricrea da capo (la vecchia CommessaWeb resta orfana in "Nuova")
|
||||||
|
$writeBatchIndex("RECREATE iddatadb={$chkId} old_commessa={$existingId} stato=" . ($stato ?? '404'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$iddatadb = $_POST['iddatadb'] ?? null;
|
$iddatadb = $_POST['iddatadb'] ?? null;
|
||||||
if (!$iddatadb) {
|
if (!$iddatadb) {
|
||||||
@@ -348,6 +442,11 @@ try {
|
|||||||
$commessaId = $commessaWeb["IdCommessa"];
|
$commessaId = $commessaWeb["IdCommessa"];
|
||||||
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
|
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
|
||||||
|
|
||||||
|
// 🔹 STEP 5.1: salva SUBITO l'id della commessa creata: se l'export fallisce più avanti,
|
||||||
|
// il CHECK PRELIMINARE sa che nel LIMS esiste già e non ne crea una seconda.
|
||||||
|
$pdo->prepare("UPDATE datadb SET idcommessaweb = :idc, commessaweb = :cw WHERE iddatadb = :id")
|
||||||
|
->execute(['idc' => $commessaId, 'cw' => $commessaWebCode, 'id' => $iddatadb]);
|
||||||
|
|
||||||
// 🔹 STEP 6: Create Campioni (Samples) for each part
|
// 🔹 STEP 6: Create Campioni (Samples) for each part
|
||||||
$campioni = [];
|
$campioni = [];
|
||||||
$logContentStep6 = "";
|
$logContentStep6 = "";
|
||||||
@@ -768,6 +867,13 @@ try {
|
|||||||
$logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt";
|
$logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt";
|
||||||
$writeLog($logFileStep91, $logContentStep91, "STEP 9.5 - ImportaCommessa (commessa={$commessaId}, succeeded=" . ($importSucceeded ? '1' : '0') . ")");
|
$writeLog($logFileStep91, $logContentStep91, "STEP 9.5 - ImportaCommessa (commessa={$commessaId}, succeeded=" . ($importSucceeded ? '1' : '0') . ")");
|
||||||
|
|
||||||
|
// Import non confermato → errore ('x'), non 'l'. Prima veniva comunque segnata 'l'
|
||||||
|
// perché CodiceCommessa (codice web) non è mai vuoto. Al reinvio il CHECK PRELIMINARE
|
||||||
|
// ricontrolla lo stato nel LIMS: se nel frattempo è "Elaborata" la riga viene solo riallineata.
|
||||||
|
if (!$importSucceeded) {
|
||||||
|
throw new Exception("ImportaCommessa non confermata dal LIMS (stato: " . ($importStato ?? 'null') . ")");
|
||||||
|
}
|
||||||
|
|
||||||
// 🔹 STEP 10: GET di controllo post-PATCH
|
// 🔹 STEP 10: GET di controllo post-PATCH
|
||||||
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
||||||
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
||||||
@@ -834,7 +940,7 @@ try {
|
|||||||
"step10_get" => $logFileStep10
|
"step10_get" => $logFileStep10
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
} catch (Exception $e) {
|
} catch (Throwable $e) {
|
||||||
// --- LOG ERRORE DEDICATO (accanto agli altri log in logs/api/) ---
|
// --- LOG ERRORE DEDICATO (accanto agli altri log in logs/api/) ---
|
||||||
$errIddatadb = $iddatadb ?? 'unknown';
|
$errIddatadb = $iddatadb ?? 'unknown';
|
||||||
$errCommessaId = $commessaId ?? null;
|
$errCommessaId = $commessaId ?? null;
|
||||||
|
|||||||
@@ -755,7 +755,8 @@
|
|||||||
|
|
||||||
function renderActionButtons(rowIndex) {
|
function renderActionButtons(rowIndex) {
|
||||||
const row = data[rowIndex];
|
const row = data[rowIndex];
|
||||||
const isExported = row.status === "l";
|
// _exporting: export in corso per questa riga (la riga può essere ri-renderizzata durante l'attesa)
|
||||||
|
const isExported = row.status === "l" || row._exporting === true;
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = "grid-cell button-cell";
|
div.className = "grid-cell button-cell";
|
||||||
div.style.flex = "0 0 auto";
|
div.style.flex = "0 0 auto";
|
||||||
|
|||||||
@@ -24,8 +24,11 @@ try {
|
|||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
// Aggiorna solo se attualmente è in stato 'l' (To LIMS)
|
// Aggiorna solo se attualmente è in stato 'l' (To LIMS).
|
||||||
$stmt = $pdo->prepare("UPDATE datadb SET status = ? WHERE iddatadb = ? AND status = 'l'");
|
// Azzera idcommessaweb: il revert è una scelta esplicita di riesportare, altrimenti il
|
||||||
|
// CHECK PRELIMINARE di export_to_lims.php riallineerebbe la riga alla vecchia commessa.
|
||||||
|
// commessaweb resta come traccia del codice precedente.
|
||||||
|
$stmt = $pdo->prepare("UPDATE datadb SET status = ?, idcommessaweb = NULL WHERE iddatadb = ? AND status = 'l'");
|
||||||
$stmt->execute([$newStatus, $iddatadb]);
|
$stmt->execute([$newStatus, $iddatadb]);
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
if ($stmt->rowCount() > 0) {
|
||||||
|
|||||||
@@ -91,9 +91,10 @@ $sql = "
|
|||||||
FROM datadb d
|
FROM datadb d
|
||||||
LEFT JOIN auth_users u ON d.user_id = u.id
|
LEFT JOIN auth_users u ON d.user_id = u.id
|
||||||
WHERE d.iddatadb IN ($ph)
|
WHERE d.iddatadb IN ($ph)
|
||||||
|
ORDER BY FIELD(d.iddatadb, $ph)
|
||||||
";
|
";
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute($ids);
|
$stmt->execute(array_merge($ids, $ids));
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if (empty($rows)) {
|
if (empty($rows)) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Righe non trovate']);
|
echo json_encode(['success' => false, 'message' => 'Righe non trovate']);
|
||||||
@@ -174,14 +175,18 @@ if (empty($recipients)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// conteggi
|
// 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;
|
$okCount = 0;
|
||||||
$errCount = 0;
|
$errCount = 0;
|
||||||
|
$pendingCount = 0;
|
||||||
foreach ($rows as $r) {
|
foreach ($rows as $r) {
|
||||||
if ($r['status'] === 'l') $okCount++;
|
if ($r['status'] === 'l') $okCount++;
|
||||||
elseif ($r['status'] === 'x') $errCount++;
|
elseif ($r['status'] === 'x') $errCount++;
|
||||||
|
else $pendingCount++;
|
||||||
}
|
}
|
||||||
$hasError = $errCount > 0;
|
$hasError = $errCount > 0;
|
||||||
|
$hasPending = $pendingCount > 0;
|
||||||
|
|
||||||
// --- tabella righe ---
|
// --- tabella righe ---
|
||||||
$rowsHtml = '';
|
$rowsHtml = '';
|
||||||
@@ -196,7 +201,7 @@ foreach ($rows as $r) {
|
|||||||
$bg = '#fdecea';
|
$bg = '#fdecea';
|
||||||
$col = '#c0392b';
|
$col = '#c0392b';
|
||||||
} else {
|
} else {
|
||||||
$label = htmlspecialchars((string)$st);
|
$label = 'NON CONFERMATA';
|
||||||
$bg = '#fff8e1';
|
$bg = '#fff8e1';
|
||||||
$col = '#8a6d3b';
|
$col = '#8a6d3b';
|
||||||
}
|
}
|
||||||
@@ -239,10 +244,24 @@ foreach ($rows as $r) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$headerColor = $hasError ? '#c0392b' : '#1e7e34';
|
if ($hasError) {
|
||||||
$headerText = $hasError ? 'Export completato con errori' : 'Export completato con successo';
|
$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);
|
$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 = "
|
$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='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;'>
|
<div style='background:{$headerColor};color:#fff;padding:18px 24px;'>
|
||||||
@@ -250,9 +269,9 @@ $html = "
|
|||||||
<p style='margin:6px 0 0;font-size:13px;opacity:.9;'>Riepilogo esportazione verso LIMS" .
|
<p style='margin:6px 0 0;font-size:13px;opacity:.9;'>Riepilogo esportazione verso LIMS" .
|
||||||
($templateName !== '' ? " · Template: <strong>" . htmlspecialchars($templateName) . "</strong>" : "") . "</p>
|
($templateName !== '' ? " · Template: <strong>" . htmlspecialchars($templateName) . "</strong>" : "") . "</p>
|
||||||
<p style='margin:6px 0 0;font-size:13px;opacity:.95;'>
|
<p style='margin:6px 0 0;font-size:13px;opacity:.95;'>
|
||||||
Committente: <strong>" . $clientLabel($rows[0]['idclient'] ?? 0) . "</strong>
|
Committente: <strong>" . $headerClient('idclient') . "</strong>
|
||||||
·
|
·
|
||||||
FatturazioneAvanzata: <strong>" . $clientLabel($rows[0]['ClienteFatturazione'] ?? 0) . "</strong>
|
FatturazioneAvanzata: <strong>" . $headerClient('ClienteFatturazione') . "</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div style='padding:20px 24px;'>
|
<div style='padding:20px 24px;'>
|
||||||
@@ -260,6 +279,7 @@ $html = "
|
|||||||
Totale commesse: <strong>{$total}</strong> •
|
Totale commesse: <strong>{$total}</strong> •
|
||||||
<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>" : "") .
|
||||||
($noPriceTotal > 0 ? " • <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</span>" : "") . "
|
($noPriceTotal > 0 ? " • <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</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;'>
|
||||||
@@ -274,15 +294,16 @@ $html = "
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>{$rowsHtml}</tbody>
|
<tbody>{$rowsHtml}</tbody>
|
||||||
</table>
|
</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>" : "") . "
|
" . ($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>
|
||||||
<div style='background:#fafafa;padding:14px 24px;font-size:12px;color:#999;border-top:1px solid #eee;'>
|
<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.
|
Messaggio automatico da SmartTRF — non rispondere a questa email.
|
||||||
</div>
|
</div>
|
||||||
</div>";
|
</div>";
|
||||||
|
|
||||||
$subjectPrefix = $hasError ? '⚠️ Errori Export LIMS' : '✅ Export LIMS OK';
|
$subjectPrefix = $hasError ? '⚠️ Errori Export LIMS' : ($hasPending ? '⏳ Export LIMS da verificare' : '✅ Export LIMS OK');
|
||||||
$subject = "{$subjectPrefix} — {$total} commesse ({$okCount} ok, {$errCount} err)";
|
$subject = "{$subjectPrefix} — {$total} commesse ({$okCount} ok, {$errCount} err" . ($pendingCount > 0 ? ", {$pendingCount} non confermate" : "") . ")";
|
||||||
|
|
||||||
$res = Mailer::send($recipients, $subject, $html);
|
$res = Mailer::send($recipients, $subject, $html);
|
||||||
|
|
||||||
@@ -290,5 +311,5 @@ echo json_encode([
|
|||||||
'success' => $res['ok'],
|
'success' => $res['ok'],
|
||||||
'message' => $res['ok'] ? 'Mail inviata' : ('Mail non inviata: ' . $res['error']),
|
'message' => $res['ok'] ? 'Mail inviata' : ('Mail non inviata: ' . $res['error']),
|
||||||
'recipients' => $recipients,
|
'recipients' => $recipients,
|
||||||
'counts' => ['ok' => $okCount, 'err' => $errCount, 'total' => $total],
|
'counts' => ['ok' => $okCount, 'err' => $errCount, 'pending' => $pendingCount, 'total' => $total],
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user