mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-15 19:28:52 +02:00
Merge pull request #29 from R0m1k3/claude/project-thread-m5x8ia
Synchro Qlik : message d'échec visible et journal des extractions de nuit
This commit is contained in:
4 files changed
+38
-11
No files matched your search
@@ -260,6 +260,10 @@ async fn traiter_qlik(code: &str, nom: &Option<String>) {
|
|||||||
e.fournisseur_courant = Some(libelle(code, nom));
|
e.fournisseur_courant = Some(libelle(code, nom));
|
||||||
e.etape = Some("qlik");
|
e.etape = Some("qlik");
|
||||||
}
|
}
|
||||||
|
// Journal téléchargeable depuis Paramètres, comme la synchro manuelle : la
|
||||||
|
// sortie du conteneur ne garde pas les milliers de lignes d'une extraction.
|
||||||
|
let journal = format!("sync-nuit_qlik_{}_{}", code, Local::now().format("%Y%m%d-%H%M%S"));
|
||||||
|
crate::logcapture::start_capture(&journal, Some(&format!("synchro de nuit Qlik fournisseur={}", libelle(code, nom))));
|
||||||
let res: anyhow::Result<Option<usize>> = async {
|
let res: anyhow::Result<Option<usize>> = async {
|
||||||
let articles = crate::ff::pg::pg_get_articles_by_fournisseur(code).await?;
|
let articles = crate::ff::pg::pg_get_articles_by_fournisseur(code).await?;
|
||||||
let mut vus = HashSet::new();
|
let mut vus = HashSet::new();
|
||||||
@@ -276,6 +280,11 @@ async fn traiter_qlik(code: &str, nom: &Option<String>) {
|
|||||||
Ok(Some(n))
|
Ok(Some(n))
|
||||||
}
|
}
|
||||||
.await;
|
.await;
|
||||||
|
let pied = match &res {
|
||||||
|
Ok(_) => "statut final=success".to_string(),
|
||||||
|
Err(e) => format!("statut final=error — {e:#}"),
|
||||||
|
};
|
||||||
|
crate::logcapture::stop_capture(&journal, Some(&pied));
|
||||||
let ms = t0.elapsed().as_millis() as i32;
|
let ms = t0.elapsed().as_millis() as i32;
|
||||||
let Ok(pool) = crate::db::pool() else { return };
|
let Ok(pool) = crate::db::pool() else { return };
|
||||||
match res {
|
match res {
|
||||||
|
|||||||
@@ -50,9 +50,11 @@ interface FournisseurRow {
|
|||||||
dernierSqlStatut: string | null;
|
dernierSqlStatut: string | null;
|
||||||
dernierSqlLignes: number | null;
|
dernierSqlLignes: number | null;
|
||||||
dernierSqlMs: number | null;
|
dernierSqlMs: number | null;
|
||||||
|
dernierSqlErreur: string | null;
|
||||||
dernierQlikAt: string | null;
|
dernierQlikAt: string | null;
|
||||||
dernierQlikStatut: string | null;
|
dernierQlikStatut: string | null;
|
||||||
dernierQlikCodes: number | null;
|
dernierQlikCodes: number | null;
|
||||||
|
dernierQlikErreur: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Resume {
|
interface Resume {
|
||||||
@@ -357,9 +359,23 @@ export function SynchronisationClient() {
|
|||||||
{
|
{
|
||||||
id: "remarque",
|
id: "remarque",
|
||||||
header: "Remarque",
|
header: "Remarque",
|
||||||
cell: (r) => (r.desactiveMotif
|
// Le badge « Échec » seul ne dit pas quoi corriger : on affiche le
|
||||||
? <span className="text-[13px] text-[var(--text-secondary)]">{r.desactiveMotif}</span>
|
// message enregistré par la dernière tentative.
|
||||||
: null),
|
cell: (r) => {
|
||||||
|
const erreurs = [
|
||||||
|
r.dernierSqlStatut === "echec" && r.dernierSqlErreur ? `${VENTES} : ${r.dernierSqlErreur}` : null,
|
||||||
|
r.dernierQlikStatut === "echec" && r.dernierQlikErreur ? `${RESEAU} : ${r.dernierQlikErreur}` : null,
|
||||||
|
].filter((e): e is string => e !== null);
|
||||||
|
if (!r.desactiveMotif && erreurs.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<div className="max-w-[480px] space-y-1 text-[13px]">
|
||||||
|
{r.desactiveMotif && <div className="text-[var(--text-secondary)]">{r.desactiveMotif}</div>}
|
||||||
|
{erreurs.map((e) => (
|
||||||
|
<div key={e} className="break-words text-[var(--accent-error)]">{e}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function SyncQlikButton({ codeFournisseur, lastUpdate }: SyncQlikButtonPr
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className="text-xs leading-tight text-right whitespace-nowrap hidden xl:block"
|
className={`text-xs leading-tight text-right ${status === "error" ? "max-w-[420px] line-clamp-2" : "whitespace-nowrap hidden xl:block"}`}
|
||||||
style={{ color: status === "error" ? "var(--accent-error)" : "var(--text-muted)" }}
|
style={{ color: status === "error" ? "var(--accent-error)" : "var(--text-muted)" }}
|
||||||
title={message || undefined}
|
title={message || undefined}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -110,9 +110,10 @@ export function useQlikSyncJob({ target, onSuccess, pollIntervalMs = 3000 }: Use
|
|||||||
if (data.status === "success" || data.status === "error") {
|
if (data.status === "success" || data.status === "error") {
|
||||||
trackingRef.current = false;
|
trackingRef.current = false;
|
||||||
if (data.status === "success") onSuccessRef.current?.();
|
if (data.status === "success") onSuccessRef.current?.();
|
||||||
// Petit délai pour laisser l'utilisateur lire le message vert/rouge.
|
// Le succès s'efface après un court délai ; une erreur reste
|
||||||
|
// affichée jusqu'à la prochaine tentative, sinon elle passe inaperçue.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setStatus((cur) => (cur === "success" || cur === "error" ? "idle" : cur));
|
setStatus((cur) => (cur === "success" ? "idle" : cur));
|
||||||
}, 4000);
|
}, 4000);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -159,10 +160,10 @@ export function useQlikSyncJob({ target, onSuccess, pollIntervalMs = 3000 }: Use
|
|||||||
applyJobToUi(data);
|
applyJobToUi(data);
|
||||||
startPolling();
|
startPolling();
|
||||||
} else if (data.status === "success" || data.status === "error") {
|
} else if (data.status === "success" || data.status === "error") {
|
||||||
// Job terminé avant le montage : on l'affiche brièvement.
|
// Job terminé avant le montage : succès affiché brièvement, erreur conservée.
|
||||||
applyJobToUi(data);
|
applyJobToUi(data);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setStatus((cur) => (cur === "success" || cur === "error" ? "idle" : cur));
|
setStatus((cur) => (cur === "success" ? "idle" : cur));
|
||||||
}, 4000);
|
}, 4000);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -208,12 +209,13 @@ export function useQlikSyncJob({ target, onSuccess, pollIntervalMs = 3000 }: Use
|
|||||||
}
|
}
|
||||||
// Statut terminal direct (cas rare : 0 code → success immédiat).
|
// Statut terminal direct (cas rare : 0 code → success immédiat).
|
||||||
applyJobToUi(data);
|
applyJobToUi(data);
|
||||||
if (data.status === "success") onSuccessRef.current?.();
|
if (data.status === "success") {
|
||||||
setTimeout(() => setStatus("idle"), 4000);
|
onSuccessRef.current?.();
|
||||||
|
setTimeout(() => setStatus("idle"), 4000);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStatus("error");
|
setStatus("error");
|
||||||
setMessage(e instanceof Error ? e.message : String(e));
|
setMessage(e instanceof Error ? e.message : String(e));
|
||||||
setTimeout(() => setStatus("idle"), 6000);
|
|
||||||
}
|
}
|
||||||
}, [applyJobToUi, query, startPolling, status, target.mode]);
|
}, [applyJobToUi, query, startPolling, status, target.mode]);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user