feat: implement bulk PostgreSQL client for FF Nancy data retrieval and configure local environment permissions

This commit is contained in:
Michael committed 2026-04-24 12:09:17 +02:00
1 parent 0ad403651d
commit 26ba285c0e
2 files changed
+43 -31

No files matched your search

+2 -1
View File
@@ -18,7 +18,8 @@
"Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ console.log\\(JSON.stringify\\(JSON.parse\\(d\\), null, 2\\)\\); }\\)\")", "Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ console.log\\(JSON.stringify\\(JSON.parse\\(d\\), null, 2\\)\\); }\\)\")",
"Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ const j=JSON.parse\\(d\\); const rows=Array.isArray\\(j\\)?j:\\(j.propositions??j.data??j.items??[]\\); console.log\\('top-level keys:', Object.keys\\(j\\)\\); if\\(rows.length\\){ const s=rows[0]; console.log\\('row keys:', Object.keys\\(s\\).join\\(', '\\)\\); console.log\\('franco_ht:', s.franco_ht, '| franco:', s.franco, '| franco_atteint:', s.franco_atteint\\); } else console.log\\('empty'\\); }\\)\")", "Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ const j=JSON.parse\\(d\\); const rows=Array.isArray\\(j\\)?j:\\(j.propositions??j.data??j.items??[]\\); console.log\\('top-level keys:', Object.keys\\(j\\)\\); if\\(rows.length\\){ const s=rows[0]; console.log\\('row keys:', Object.keys\\(s\\).join\\(', '\\)\\); console.log\\('franco_ht:', s.franco_ht, '| franco:', s.franco, '| franco_atteint:', s.franco_atteint\\); } else console.log\\('empty'\\); }\\)\")",
"Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ const j=JSON.parse\\(d\\); console.log\\('top-level keys:', Object.keys\\(j\\)\\); const resume=j.resume; console.log\\('resume type:', Array.isArray\\(resume\\)?'array':'object'\\); if\\(Array.isArray\\(resume\\)&&resume.length\\){ console.log\\('resume[0]:', JSON.stringify\\(resume[0],null,2\\)\\); } else { console.log\\('resume:', JSON.stringify\\(resume,null,2\\)\\); } }\\)\")", "Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ const j=JSON.parse\\(d\\); console.log\\('top-level keys:', Object.keys\\(j\\)\\); const resume=j.resume; console.log\\('resume type:', Array.isArray\\(resume\\)?'array':'object'\\); if\\(Array.isArray\\(resume\\)&&resume.length\\){ console.log\\('resume[0]:', JSON.stringify\\(resume[0],null,2\\)\\); } else { console.log\\('resume:', JSON.stringify\\(resume,null,2\\)\\); } }\\)\")",
"Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ const j=JSON.parse\\(d\\); console.log\\('top-level keys:', Object.keys\\(j\\)\\); const resume=j.resume; console.log\\('resume type:', Array.isArray\\(resume\\)?'array':'object'\\); if\\(Array.isArray\\(resume\\)&&resume.length\\){ console.log\\('resume[0]:', JSON.stringify\\(resume[0]\\)\\); } else { console.log\\('resume:', JSON.stringify\\(resume\\)\\); } }\\)\")" "Bash(node -e \"let d=''; process.stdin.on\\('data',c=>d+=c\\); process.stdin.on\\('end',\\(\\)=>{ const j=JSON.parse\\(d\\); console.log\\('top-level keys:', Object.keys\\(j\\)\\); const resume=j.resume; console.log\\('resume type:', Array.isArray\\(resume\\)?'array':'object'\\); if\\(Array.isArray\\(resume\\)&&resume.length\\){ console.log\\('resume[0]:', JSON.stringify\\(resume[0]\\)\\); } else { console.log\\('resume:', JSON.stringify\\(resume\\)\\); } }\\)\")",
"Bash(node -e ' *)"
] ]
} }
} }
+41 -30
View File
@@ -1143,48 +1143,59 @@ export async function pgGetCommandesAuto(): Promise<PgCommandeAutoRow[]> {
const mapped = (rows as any[]).map(mapCommandeAutoRow); const mapped = (rows as any[]).map(mapCommandeAutoRow);
// Enrichissement : pour les fournisseurs sans franco dans la réponse globale, // 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é. // on appelle GET /api/commandes-auto/{codefou} (sans filtre site — franco est une
const missingFranco = mapped.filter(r => r.franco === 0 && r.codefou && r.site); // propriété du fournisseur, pas du site) et on lit resume[0].franco_ht.
const missingFranco = mapped.filter(r => r.franco === 0 && r.codefou);
if (missingFranco.length > 0) { if (missingFranco.length > 0) {
console.log(`[pg-ff] pgGetCommandesAuto: ${missingFranco.length} lignes sans franco → enrichissement via detail endpoint`); console.log(`[pg-ff] pgGetCommandesAuto: ${missingFranco.length} lignes sans franco → enrichissement`);
// Regrouper par (codefou, site) pour éviter les doublons // Dédupliquer par codefou (franco identique quel que soit le site)
const pairs = Array.from( const uniqueCodeFous = [...new Set(missingFranco.map(r => r.codefou))];
new Map(missingFranco.map(r => [`${r.codefou}|${r.site}`, r])).values()
); // eslint-disable-next-line @typescript-eslint/no-explicit-any
const extractFranco = (detail: any): number => {
if (!detail || detail.error) return 0;
const resumeRaw = detail?.resume ?? detail?.summary ?? detail;
const resumeItem = Array.isArray(resumeRaw) ? resumeRaw[0] : resumeRaw;
return Number(resumeItem?.franco_ht ?? resumeItem?.franco ?? 0);
};
const details = await Promise.allSettled( const details = await Promise.allSettled(
pairs.map(r => uniqueCodeFous.map(async codefou => {
fetch(`${FF_API_BASE}/api/commandes-auto/${encodeURIComponent(r.codefou)}?site=${encodeURIComponent(r.site)}`, { cache: "no-store" }) // 1. Essai sans filtre site
.then(res2 => res2.ok ? res2.json() : null) try {
.then(detail => { const res2 = await fetch(`${FF_API_BASE}/api/commandes-auto/${encodeURIComponent(codefou)}`, { cache: "no-store" });
if (!detail) return null; if (res2.ok) {
// Le detail endpoint retourne { resume: [...] } (tableau) ou { resume: {} } const detail = await res2.json();
// ou directement les champs du résumé const f = extractFranco(detail);
const resumeRaw = detail?.resume ?? detail?.summary ?? detail; if (f > 0) return { codefou, franco: f };
// eslint-disable-next-line @typescript-eslint/no-explicit-any }
const resumeItem: any = Array.isArray(resumeRaw) ? resumeRaw[0] : resumeRaw; } catch { /* ignore */ }
const francoVal = Number(resumeItem?.franco_ht ?? resumeItem?.franco ?? 0); // 2. Fallback site=000
return { codefou: r.codefou, site: r.site, franco: francoVal }; try {
}) const res3 = await fetch(`${FF_API_BASE}/api/commandes-auto/${encodeURIComponent(codefou)}?site=000`, { cache: "no-store" });
.catch(() => null) if (res3.ok) {
) const detail = await res3.json();
const f = extractFranco(detail);
if (f > 0) return { codefou, franco: f };
}
} catch { /* ignore */ }
return { codefou, franco: 0 };
})
); );
// Construire un map (codefou|site) → franco enrichi // Map codefou → franco enrichi
const francoMap = new Map<string, number>(); const francoMap = new Map<string, number>();
for (const result of details) { for (const result of details) {
if (result.status === "fulfilled" && result.value && result.value.franco > 0) { if (result.status === "fulfilled" && result.value.franco > 0) {
francoMap.set(`${result.value.codefou}|${result.value.site}`, result.value.franco); francoMap.set(result.value.codefou, result.value.franco);
} }
} }
console.log(`[pg-ff] pgGetCommandesAuto: francos enrichis:`, Object.fromEntries(francoMap)); console.log(`[pg-ff] pgGetCommandesAuto: francos enrichis:`, Object.fromEntries(francoMap));
// Appliquer les francos enrichis
return mapped.map(r => { return mapped.map(r => {
if (r.franco === 0) { if (r.franco === 0) {
const enrichedFranco = francoMap.get(`${r.codefou}|${r.site}`); const enriched = francoMap.get(r.codefou);
if (enrichedFranco && enrichedFranco > 0) { if (enriched && enriched > 0) return { ...r, franco: enriched };
return { ...r, franco: enrichedFranco };
}
} }
return r; return r;
}); });