From dcebf74f31c318befeb1991c105bf43b009c1cfe Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Thu, 19 Mar 2026 18:53:49 +0100 Subject: [PATCH] perf: replace N ranking API calls with 1 bulk fetch per supplier getRankingByArticles now uses GET /api/ranking?foucentrale= to fetch all rankings for a supplier in a single request instead of making one request per article. For a 500-article supplier this goes from ~500 HTTP calls to 2 (count + bulk), eliminating the timeout that caused the client-side crash on large suppliers. Falls back to per-article requests if no codeFournisseur is provided or if the bulk endpoint fails. Co-Authored-By: Claude Sonnet 4.6 --- src/features/grid/api/get-product-rows.ts | 2 +- src/lib/api-ff-client.ts | 61 +++++++++++++++++------ 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/features/grid/api/get-product-rows.ts b/src/features/grid/api/get-product-rows.ts index c266542..bbd8a45 100644 --- a/src/features/grid/api/get-product-rows.ts +++ b/src/features/grid/api/get-product-rows.ts @@ -35,7 +35,7 @@ export async function getProductRows(input: GetProductRowsInput): Promise&limit= → tous les rankings du fournisseur + * + * Fallback par articles individuels si foucentrale n'est pas disponible. */ export async function getRankingByArticles( articles: { codein: string; gtin?: string }[], - batchSize = 20 + codeFournisseur?: string, ): Promise { const rankings = new Map(); if (articles.length === 0) return { rankings, totalRankedProducts: 0 }; - // 1. Récupérer le nombre total de produits classés (1 seul appel) + // 1. Nombre total de produits classés let totalRankedProducts = 0; try { - const countUrl = `${FF_API_BASE}/api/ranking?limit=1`; - const countRes = await fetch(countUrl, { cache: "no-store" }); + const countRes = await fetch(`${FF_API_BASE}/api/ranking?limit=1`, { cache: "no-store" }); if (countRes.ok) { const countData = await countRes.json(); totalRankedProducts = countData?.count ?? 0; @@ -476,7 +477,43 @@ export async function getRankingByArticles( console.warn(`[api-ff] Failed to get ranking total count:`, err); } - // 2. Récupérer le ranking de chaque article + // 2. Bulk fetch par fournisseur (1 seul appel au lieu de N) + if (codeFournisseur) { + try { + const limit = Math.max(articles.length + 50, 500); + const url = `${FF_API_BASE}/api/ranking?foucentrale=${encodeURIComponent(codeFournisseur)}&limit=${limit}`; + const res = await fetch(url, { cache: "no-store" }); + if (res.ok) { + const data = await res.json(); + const rankingList: unknown[] = data?.ranking ?? []; + console.log(`[api-ff] Ranking bulk: ${rankingList.length} entrées pour fournisseur ${codeFournisseur}`); + if (rankingList.length > 0) { + console.log(`[api-ff] Ranking sample keys: ${Object.keys(rankingList[0] as object).join(",")}`); + } + for (const entry of rankingList as Record[]) { + const codein = entry.codein; + if (!codein) continue; + rankings.set(codein, { + ranking_ca: parseRankNum(entry.ranking_ca), + ranking_qte: parseRankNum(entry.ranking_qte), + ranking_mag_ca: parseRankNum(entry.ranking_mag_ca), + ranking_mag_qte: parseRankNum(entry.ranking_mag_qte), + ranking_mag_marge: parseRankNum(entry.ranking_mag_marge), + pv_calcule: parseRankNum(entry.pv_calcule), + pv_mag: parseRankNum(entry.pv_mag), + pv_cen: parseRankNum(entry.pv_cen), + }); + } + console.log(`[api-ff] Ranking: ${rankings.size}/${articles.length} articles enrichis`); + return { rankings, totalRankedProducts }; + } + } catch (err) { + console.warn(`[api-ff] Bulk ranking failed, falling back to per-article:`, err); + } + } + + // Fallback : 1 appel par article (si pas de codeFournisseur ou si bulk échoue) + const batchSize = 20; for (let i = 0; i < articles.length; i += batchSize) { const batch = articles.slice(i, i + batchSize); await Promise.all( @@ -486,15 +523,9 @@ export async function getRankingByArticles( const res = await fetch(url, { cache: "no-store" }); if (!res.ok) return; const data = await res.json(); - const rankingList = data?.ranking; if (!Array.isArray(rankingList) || rankingList.length === 0) return; - - if (rankings.size === 0) { - console.log(`[api-ff] Ranking sample keys: ${Object.keys(rankingList[0]).join(",")}`); - } - - const entry = rankingList[0]; + const entry = rankingList[0] as Record; rankings.set(art.codein, { ranking_ca: parseRankNum(entry.ranking_ca), ranking_qte: parseRankNum(entry.ranking_qte),