From 462eef182e79fb40537db7623f182922e72834e3 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 13 Apr 2026 15:31:25 +0200 Subject: [PATCH] feat: enhance FrancoCell logic and add mapping function for PgCommandeAutoRow --- .claude/settings.local.json | 7 +- src/app/(dashboard)/commandes-auto/client.tsx | 13 ++- src/lib/pg-ff-client.ts | 97 ++++++++++++++++--- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3f582e9..d560613 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,12 @@ { "permissions": { "allow": [ - "Bash(npx tsc:*)" + "Bash(npx tsc:*)", + "Bash(grep -rn \"franco\" /c/Users/Michael/GIT/Collectflow/CollectFlow/*.md)", + "Bash(rtk grep:*)", + "Bash(ls \"c:/Users/Michael/GIT/Collectflow/CollectFlow/.env\"*)", + "Bash(node_modules/.bin/tsc --noEmit)", + "Bash(./node_modules/.bin/tsc --noEmit)" ] } } diff --git a/src/app/(dashboard)/commandes-auto/client.tsx b/src/app/(dashboard)/commandes-auto/client.tsx index 6e2fe41..f433855 100644 --- a/src/app/(dashboard)/commandes-auto/client.tsx +++ b/src/app/(dashboard)/commandes-auto/client.tsx @@ -26,13 +26,24 @@ type SK = keyof PgCommandeAutoRow; const NUMERIC: SK[] = ["franco", "nb_articles", "montant_cde"]; function FrancoCell({ atteint, ecart, franco }: { atteint: boolean | null; ecart: number; franco: number }) { - if (franco === 0 || atteint === null) { + // franco === 0 : aucune donnée franco disponible + // atteint === null ET franco === 0 : pas de franco (N/A ou données manquantes) + // atteint === null ET franco > 0 : franco connu mais statut non déterminé → on affiche quand même le montant + if (franco === 0) { return ( Pas de franco ); } + if (atteint === null) { + // Franco connu mais statut non calculé par l'API + return ( + + N/A ({fmt(franco)}) + + ); + } if (atteint) { return ( diff --git a/src/lib/pg-ff-client.ts b/src/lib/pg-ff-client.ts index 9b1ee8f..6a12c80 100644 --- a/src/lib/pg-ff-client.ts +++ b/src/lib/pg-ff-client.ts @@ -935,9 +935,31 @@ export interface PgCommandeAutoRow { ecart_franco: number; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function mapCommandeAutoRow(r: any): PgCommandeAutoRow { + return { + site: String(r.site ?? ""), + codefou: String(r.codefou ?? ""), + nomfou: String(r.nom_fou ?? r.nomfou ?? ""), + franco: Number(r.franco_ht ?? r.franco ?? 0), + nb_articles: Number(r.nb_articles ?? 0), + montant_cde: Number(r.montant_propo_ht ?? r.montant_cde ?? 0), + franco_atteint: r.franco_atteint === "OUI" ? true + : r.franco_atteint === "NON" ? false + : r.franco_atteint === "N/A" ? null + : r.franco_atteint != null ? Boolean(r.franco_atteint) + : null, + ecart_franco: Number(r.ecart_franco ?? 0), + }; +} + /** * Retourne la liste des fournisseurs en commande automatique via l'API Hostinger. * GET https://api.ffnancy.fr/api/commandes-auto + * + * Pour les fournisseurs dont le franco est absent de la réponse globale (franco_ht null), + * on enrichit via le detail endpoint /api/commandes-auto/:codefou?site= qui contient + * toujours le résumé franco du fournisseur. */ export async function pgGetCommandesAuto(): Promise { const FF_API_BASE = process.env.FF_API_BASE_URL ?? "https://api.ffnancy.fr"; @@ -945,26 +967,71 @@ export async function pgGetCommandesAuto(): Promise { const res = await fetch(`${FF_API_BASE}/api/commandes-auto`, { cache: "no-store" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); - // L'API peut retourner directement un tableau ou un objet wrappé - // L'API retourne { count: N, propositions: [...] } + // L'API retourne { count: N, propositions: [...] } ou un tableau direct const rows: unknown[] = Array.isArray(data) ? data : (data?.propositions ?? data?.data ?? data?.items ?? data?.results ?? []); + + // Diagnostic : log les clés du premier row pour détecter les changements de format + if (rows.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sample = rows[0] as any; + console.log(`[pg-ff] pgGetCommandesAuto sample keys:`, Object.keys(sample)); + console.log(`[pg-ff] pgGetCommandesAuto sample franco fields: franco_ht=${sample.franco_ht} franco=${sample.franco} franco_atteint=${sample.franco_atteint}`); + } + console.log(`[pg-ff] pgGetCommandesAuto: ${rows.length} lignes (API Hostinger)`); // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (rows as any[]).map((r): PgCommandeAutoRow => ({ - site: String(r.site ?? ""), - codefou: String(r.codefou ?? ""), - nomfou: String(r.nom_fou ?? r.nomfou ?? ""), - franco: Number(r.franco_ht ?? r.franco ?? 0), - nb_articles: Number(r.nb_articles ?? 0), - montant_cde: Number(r.montant_propo_ht ?? r.montant_cde ?? 0), - franco_atteint: r.franco_atteint === "OUI" ? true - : r.franco_atteint === "NON" ? false - : r.franco_atteint != null ? Boolean(r.franco_atteint) - : null, - ecart_franco: Number(r.ecart_franco ?? 0), - })); + const mapped = (rows as any[]).map(mapCommandeAutoRow); + + // Enrichissement : pour les fournisseurs sans franco dans la réponse globale, + // on appelle le detail endpoint qui contient toujours franco_ht dans son résumé. + const missingFranco = mapped.filter(r => r.franco === 0 && r.codefou && r.site); + if (missingFranco.length > 0) { + console.log(`[pg-ff] pgGetCommandesAuto: ${missingFranco.length} lignes sans franco → enrichissement via detail endpoint`); + // Regrouper par (codefou, site) pour éviter les doublons + const pairs = Array.from( + new Map(missingFranco.map(r => [`${r.codefou}|${r.site}`, r])).values() + ); + const details = await Promise.allSettled( + pairs.map(r => + fetch(`${FF_API_BASE}/api/commandes-auto/${encodeURIComponent(r.codefou)}?site=${encodeURIComponent(r.site)}`, { cache: "no-store" }) + .then(res2 => res2.ok ? res2.json() : null) + .then(detail => { + if (!detail) return null; + // Le detail endpoint retourne { resume: { franco_ht, ... }, articles: [...] } + // ou directement les champs du résumé + const resume = detail?.resume ?? detail?.summary ?? detail; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const francoVal = Number((resume as any)?.franco_ht ?? (resume as any)?.franco ?? 0); + return { codefou: r.codefou, site: r.site, franco: francoVal }; + }) + .catch(() => null) + ) + ); + + // Construire un map (codefou|site) → franco enrichi + const francoMap = new Map(); + for (const result of details) { + if (result.status === "fulfilled" && result.value && result.value.franco > 0) { + francoMap.set(`${result.value.codefou}|${result.value.site}`, result.value.franco); + } + } + console.log(`[pg-ff] pgGetCommandesAuto: francos enrichis:`, Object.fromEntries(francoMap)); + + // Appliquer les francos enrichis + return mapped.map(r => { + if (r.franco === 0) { + const enrichedFranco = francoMap.get(`${r.codefou}|${r.site}`); + if (enrichedFranco && enrichedFranco > 0) { + return { ...r, franco: enrichedFranco }; + } + } + return r; + }); + } + + return mapped; } catch (e) { console.error("[pg-ff] pgGetCommandesAuto error:", (e as Error).message?.slice(0, 300)); return [];