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() {
|
||||
const $btn = $(this);
|
||||
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();
|
||||
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
|
||||
|
||||
@@ -169,15 +169,28 @@
|
||||
formData.append("iddatadb", iddatadb);
|
||||
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", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
if (!response.ok)
|
||||
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
|
||||
const idx = getRowIndexByIddatadb(iddatadb);
|
||||
if (idx >= 0) {
|
||||
@@ -444,8 +457,9 @@
|
||||
data.message || "Unknown error",
|
||||
);
|
||||
showExportResult(data);
|
||||
// Mail di riepilogo per la singola commessa
|
||||
sendExportSummaryMail([iddatadb]);
|
||||
// Mail di riepilogo per la singola commessa (non per un export rifiutato:
|
||||
// non è stato inviato nulla e la riga può risultare già 'l')
|
||||
if (!data.rejected) sendExportSummaryMail([iddatadb]);
|
||||
} catch (error) {
|
||||
if (gridRow) {
|
||||
setRowExporting(gridRow, false);
|
||||
@@ -453,6 +467,9 @@
|
||||
}
|
||||
showRowError(gridRow, iddatadb, 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;
|
||||
let processed = 0,
|
||||
succeeded = 0,
|
||||
failed = 0;
|
||||
failed = 0,
|
||||
skipped = 0;
|
||||
const exportedList = []; // { commessaweb, noPriceCount, noPriceAnalyses }
|
||||
const processedIds = []; // iddatadb processati (ok o errore) per la mail
|
||||
|
||||
@@ -728,11 +746,16 @@
|
||||
const gridRow = row || getGridRow(iddatadb);
|
||||
if (gridRow) setRowExporting(gridRow, true);
|
||||
|
||||
processedIds.push(iddatadb);
|
||||
try {
|
||||
const data = await sendExport(iddatadb, batchUuid);
|
||||
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++;
|
||||
exportedList.push({
|
||||
commessaweb: data.commessaweb,
|
||||
@@ -741,6 +764,7 @@
|
||||
});
|
||||
} else {
|
||||
failed++;
|
||||
processedIds.push(iddatadb);
|
||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||
if (errIdx >= 0)
|
||||
window.gridData[errIdx]._exportError =
|
||||
@@ -755,6 +779,7 @@
|
||||
} catch (error) {
|
||||
processed++;
|
||||
failed++;
|
||||
processedIds.push(iddatadb);
|
||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||
if (errIdx >= 0)
|
||||
window.gridData[errIdx]._exportError = error.message;
|
||||
@@ -793,13 +818,18 @@
|
||||
if (batchCancelled) {
|
||||
labelEl.textContent = "Export All — Cancelled";
|
||||
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";
|
||||
msgEl.innerHTML = `All <strong>${succeeded}</strong> rows exported successfully.`;
|
||||
} 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>`;
|
||||
}
|
||||
if (skipped > 0)
|
||||
msgEl.innerHTML += `<br>Skipped (already exported / in progress — reload the page): <strong>${skipped}</strong>`;
|
||||
|
||||
if (exportedList.length) {
|
||||
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 {
|
||||
$iddatadb = $_POST['iddatadb'] ?? null;
|
||||
if (!$iddatadb) {
|
||||
@@ -348,6 +442,11 @@ try {
|
||||
$commessaId = $commessaWeb["IdCommessa"];
|
||||
$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
|
||||
$campioni = [];
|
||||
$logContentStep6 = "";
|
||||
@@ -768,6 +867,13 @@ try {
|
||||
$logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt";
|
||||
$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
|
||||
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
||||
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
||||
@@ -834,7 +940,7 @@ try {
|
||||
"step10_get" => $logFileStep10
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
// --- LOG ERRORE DEDICATO (accanto agli altri log in logs/api/) ---
|
||||
$errIddatadb = $iddatadb ?? 'unknown';
|
||||
$errCommessaId = $commessaId ?? null;
|
||||
|
||||
@@ -755,7 +755,8 @@
|
||||
|
||||
function renderActionButtons(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");
|
||||
div.className = "grid-cell button-cell";
|
||||
div.style.flex = "0 0 auto";
|
||||
|
||||
@@ -24,8 +24,11 @@ try {
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
// Aggiorna solo se attualmente è in stato 'l' (To LIMS)
|
||||
$stmt = $pdo->prepare("UPDATE datadb SET status = ? WHERE iddatadb = ? AND status = 'l'");
|
||||
// Aggiorna solo se attualmente è in stato 'l' (To LIMS).
|
||||
// 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]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
|
||||
@@ -91,9 +91,10 @@ $sql = "
|
||||
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($ids);
|
||||
$stmt->execute(array_merge($ids, $ids));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (empty($rows)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Righe non trovate']);
|
||||
@@ -174,14 +175,18 @@ if (empty($recipients)) {
|
||||
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;
|
||||
$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 = '';
|
||||
@@ -196,7 +201,7 @@ foreach ($rows as $r) {
|
||||
$bg = '#fdecea';
|
||||
$col = '#c0392b';
|
||||
} else {
|
||||
$label = htmlspecialchars((string)$st);
|
||||
$label = 'NON CONFERMATA';
|
||||
$bg = '#fff8e1';
|
||||
$col = '#8a6d3b';
|
||||
}
|
||||
@@ -239,10 +244,24 @@ foreach ($rows as $r) {
|
||||
}
|
||||
}
|
||||
|
||||
$headerColor = $hasError ? '#c0392b' : '#1e7e34';
|
||||
$headerText = $hasError ? 'Export completato con errori' : 'Export completato con successo';
|
||||
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;'>
|
||||
@@ -250,9 +269,9 @@ $html = "
|
||||
<p style='margin:6px 0 0;font-size:13px;opacity:.9;'>Riepilogo esportazione verso LIMS" .
|
||||
($templateName !== '' ? " · Template: <strong>" . htmlspecialchars($templateName) . "</strong>" : "") . "</p>
|
||||
<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>
|
||||
</div>
|
||||
<div style='padding:20px 24px;'>
|
||||
@@ -260,6 +279,7 @@ $html = "
|
||||
Totale commesse: <strong>{$total}</strong> •
|
||||
<span style='color:#1e7e34;'>Inviate: {$okCount}</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>" : "") . "
|
||||
</p>
|
||||
<table style='width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed;'>
|
||||
@@ -274,15 +294,16 @@ $html = "
|
||||
</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>" : "") . "
|
||||
" . ($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' : '✅ Export LIMS OK';
|
||||
$subject = "{$subjectPrefix} — {$total} commesse ({$okCount} ok, {$errCount} err)";
|
||||
$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);
|
||||
|
||||
@@ -290,5 +311,5 @@ echo json_encode([
|
||||
'success' => $res['ok'],
|
||||
'message' => $res['ok'] ? 'Mail inviata' : ('Mail non inviata: ' . $res['error']),
|
||||
'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