mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-23 06:58:10 +02:00
feat: enhance FrancoCell logic and add mapping function for PgCommandeAutoRow
This commit is contained in:
1 parent
fdc2d0ed39
commit
462eef182e
3 files changed
+100
-17
No files matched your search
@@ -1,7 +1,12 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"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)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,13 +26,24 @@ type SK = keyof PgCommandeAutoRow;
|
|||||||
const NUMERIC: SK[] = ["franco", "nb_articles", "montant_cde"];
|
const NUMERIC: SK[] = ["franco", "nb_articles", "montant_cde"];
|
||||||
|
|
||||||
function FrancoCell({ atteint, ecart, franco }: { atteint: boolean | null; ecart: number; franco: number }) {
|
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 (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500">
|
<span className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500">
|
||||||
<Minus className="w-3 h-3" /> Pas de franco
|
<Minus className="w-3 h-3" /> Pas de franco
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (atteint === null) {
|
||||||
|
// Franco connu mais statut non calculé par l'API
|
||||||
|
return (
|
||||||
|
<span className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500">
|
||||||
|
<Minus className="w-3 h-3" /> N/A ({fmt(franco)})
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (atteint) {
|
if (atteint) {
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">
|
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">
|
||||||
|
|||||||
+82
-15
@@ -935,9 +935,31 @@ export interface PgCommandeAutoRow {
|
|||||||
ecart_franco: number;
|
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.
|
* Retourne la liste des fournisseurs en commande automatique via l'API Hostinger.
|
||||||
* GET https://api.ffnancy.fr/api/commandes-auto
|
* 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<PgCommandeAutoRow[]> {
|
export async function pgGetCommandesAuto(): Promise<PgCommandeAutoRow[]> {
|
||||||
const FF_API_BASE = process.env.FF_API_BASE_URL ?? "https://api.ffnancy.fr";
|
const FF_API_BASE = process.env.FF_API_BASE_URL ?? "https://api.ffnancy.fr";
|
||||||
@@ -945,26 +967,71 @@ export async function pgGetCommandesAuto(): Promise<PgCommandeAutoRow[]> {
|
|||||||
const res = await fetch(`${FF_API_BASE}/api/commandes-auto`, { cache: "no-store" });
|
const res = await fetch(`${FF_API_BASE}/api/commandes-auto`, { cache: "no-store" });
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
// L'API peut retourner directement un tableau ou un objet wrappé
|
// L'API retourne { count: N, propositions: [...] } ou un tableau direct
|
||||||
// L'API retourne { count: N, propositions: [...] }
|
|
||||||
const rows: unknown[] = Array.isArray(data)
|
const rows: unknown[] = Array.isArray(data)
|
||||||
? data
|
? data
|
||||||
: (data?.propositions ?? data?.data ?? data?.items ?? data?.results ?? []);
|
: (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)`);
|
console.log(`[pg-ff] pgGetCommandesAuto: ${rows.length} lignes (API Hostinger)`);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (rows as any[]).map((r): PgCommandeAutoRow => ({
|
const mapped = (rows as any[]).map(mapCommandeAutoRow);
|
||||||
site: String(r.site ?? ""),
|
|
||||||
codefou: String(r.codefou ?? ""),
|
// Enrichissement : pour les fournisseurs sans franco dans la réponse globale,
|
||||||
nomfou: String(r.nom_fou ?? r.nomfou ?? ""),
|
// on appelle le detail endpoint qui contient toujours franco_ht dans son résumé.
|
||||||
franco: Number(r.franco_ht ?? r.franco ?? 0),
|
const missingFranco = mapped.filter(r => r.franco === 0 && r.codefou && r.site);
|
||||||
nb_articles: Number(r.nb_articles ?? 0),
|
if (missingFranco.length > 0) {
|
||||||
montant_cde: Number(r.montant_propo_ht ?? r.montant_cde ?? 0),
|
console.log(`[pg-ff] pgGetCommandesAuto: ${missingFranco.length} lignes sans franco → enrichissement via detail endpoint`);
|
||||||
franco_atteint: r.franco_atteint === "OUI" ? true
|
// Regrouper par (codefou, site) pour éviter les doublons
|
||||||
: r.franco_atteint === "NON" ? false
|
const pairs = Array.from(
|
||||||
: r.franco_atteint != null ? Boolean(r.franco_atteint)
|
new Map(missingFranco.map(r => [`${r.codefou}|${r.site}`, r])).values()
|
||||||
: null,
|
);
|
||||||
ecart_franco: Number(r.ecart_franco ?? 0),
|
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<string, number>();
|
||||||
|
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) {
|
} catch (e) {
|
||||||
console.error("[pg-ff] pgGetCommandesAuto error:", (e as Error).message?.slice(0, 300));
|
console.error("[pg-ff] pgGetCommandesAuto error:", (e as Error).message?.slice(0, 300));
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
Reference in new issue
Block a user