diff --git a/src/app/api/diag/route.ts b/src/app/api/diag/route.ts index f6b34a0..ac86a4f 100644 --- a/src/app/api/diag/route.ts +++ b/src/app/api/diag/route.ts @@ -1,69 +1,65 @@ import { NextRequest, NextResponse } from "next/server"; import { db } from "@/db"; -import { ventesProduits } from "@/db/schema"; -import { sql, eq } from "drizzle-orm"; +import { sql } from "drizzle-orm"; + +const FF_API_BASE = process.env.FF_API_BASE_URL ?? "https://api.ffnancy.fr"; export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url); - const codeFournisseur = searchParams.get("fournisseur"); + const target = searchParams.get("target") ?? "all"; - if (!codeFournisseur) { - return NextResponse.json({ - error: "Paramètre 'fournisseur' manquant.", - usage: "/api/diag?fournisseur=VOTRE_CODE" - }, { status: 400 }); + const result: Record = { + timestamp: new Date().toISOString(), + env: { + FF_API_BASE_URL: process.env.FF_API_BASE_URL ?? "(not set — using default)", + FF_API_BASE_resolved: FF_API_BASE, + NODE_ENV: process.env.NODE_ENV, + }, + }; + + // ── Test FF Nancy API ──────────────────────────────────────────────────── + if (target === "all" || target === "ff") { + const ffTests: Record = {}; + + // Test fournisseurs + try { + const url = `${FF_API_BASE}/api/fournisseurs?limit=5`; + const res = await fetch(url, { cache: "no-store" }); + const text = await res.text(); + let parsed: unknown = null; + try { parsed = JSON.parse(text); } catch { parsed = text.slice(0, 500); } + ffTests.fournisseurs = { status: res.status, url, raw: parsed }; + } catch (e) { + ffTests.fournisseurs = { error: String(e) }; + } + + // Test articles (avec un code fournisseur de test) + const codefou = searchParams.get("codefou"); + if (codefou) { + try { + const url = `${FF_API_BASE}/api/articles?codefou=${codefou}&limit=3`; + const res = await fetch(url, { cache: "no-store" }); + const text = await res.text(); + let parsed: unknown = null; + try { parsed = JSON.parse(text); } catch { parsed = text.slice(0, 500); } + ffTests.articles = { status: res.status, url, raw: parsed }; + } catch (e) { + ffTests.articles = { error: String(e) }; + } + } + + result.ffNancyApi = ffTests; } - try { - console.log(`\n--- [DIAG API] Starting check for supplier: ${codeFournisseur} ---`); - - // 1. Raw Count - const countAll = await db.execute(sql` - SELECT COUNT(*) as count FROM ventes_produits - WHERE code_fournisseur = ${codeFournisseur} - `); - const totalRows = Number(countAll.rows[0].count); - - // 2. Distinct Products - const countDistinctTotal = await db.execute(sql` - SELECT COUNT(DISTINCT codein) as count FROM ventes_produits - WHERE code_fournisseur = ${codeFournisseur} - `); - const totalDistinct = Number(countDistinctTotal.rows[0].count); - - // 3. Sample of data (to see if it's truncated by DB query) - const sample = await db.execute(sql` - SELECT codein, libelle1, magasin, periode FROM ventes_produits - WHERE code_fournisseur = ${codeFournisseur} - LIMIT 5 - `); - - console.log(`- DB sees ${totalRows} total rows.`); - console.log(`- DB sees ${totalDistinct} distinct products.`); - - return NextResponse.json({ - timestamp: new Date().toISOString(), - supplier: codeFournisseur, - database: { - totalRowsInTable: totalRows, - distinctProducts: totalDistinct, - sampleData: sample.rows - }, - serverConfig: { - nextVersion: "16.1.6", - nodeEnv: process.env.NODE_ENV, - bodySizeLimit: "50mb" - }, - message: totalDistinct > 1000 - ? "La base de données contient plus de 1000 produits. Si la grille en affiche 999, le problème est dans le transport (Application/RSC)." - : "La base de données renvoie moins de 1000 produits uniques. Le problème vient de l'import ou du filtrage SQL." - }); - - } catch (error: any) { - console.error("[DIAG API] Error:", error); - return NextResponse.json({ - error: "Failed to fetch diagnostic data", - details: error.message - }, { status: 500 }); + // ── Test PostgreSQL ────────────────────────────────────────────────────── + if (target === "all" || target === "db") { + try { + const countResult = await db.execute(sql`SELECT COUNT(*) as count FROM session_snapshots`); + result.postgresql = { ok: true, snapshots: Number(countResult.rows[0]?.count ?? 0) }; + } catch (e) { + result.postgresql = { ok: false, error: String(e) }; + } } + + return NextResponse.json(result, { status: 200 }); } diff --git a/src/lib/api-ff-client.ts b/src/lib/api-ff-client.ts index 57f6cd7..4e19ebb 100644 --- a/src/lib/api-ff-client.ts +++ b/src/lib/api-ff-client.ts @@ -80,9 +80,24 @@ export interface AggregatedStock { // Pagination générique // --------------------------------------------------------------------------- +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function extractList(data: unknown): any[] { + if (Array.isArray(data)) return data; + const d = data as Record; + // Essaie les wrappers courants + for (const key of ["items", "data", "result", "results", "records", "list"]) { + if (Array.isArray(d[key])) return d[key] as unknown[]; + } + // Cherche la première propriété qui est un tableau + for (const val of Object.values(d)) { + if (Array.isArray(val)) return val as unknown[]; + } + return []; +} + async function fetchAllPages( buildUrl: (page: number) => string, - extractItems: (data: unknown) => T[], + _extractItems: (data: unknown) => T[], // conservé pour compatibilité, mais on utilise extractList pageSize = 500 ): Promise { const all: T[] = []; @@ -90,13 +105,17 @@ async function fetchAllPages( while (true) { const url = buildUrl(page); + console.log(`[api-ff] GET ${url}`); const res = await fetch(url, { cache: "no-store" }); if (!res.ok) { console.error(`[api-ff] HTTP ${res.status} — ${url}`); break; } const data = await res.json(); - const items = extractItems(data); + const items = extractList(data) as T[]; + if (page === 1) { + console.log(`[api-ff] page 1: ${items.length} items, keys: ${items[0] ? Object.keys(items[0] as object).join(",") : "empty"}`); + } all.push(...items); if (items.length < pageSize) break; page++; @@ -134,6 +153,16 @@ export function dateToYYYYMM(isoDate: string): string { // Fournisseurs // --------------------------------------------------------------------------- +/** Extrait le code fournisseur depuis n'importe quelle forme de réponse API */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function extractFouCode(f: any): string { + return f.codefou ?? f.CodeFou ?? f.code_fournisseur ?? f.codeFournisseur ?? f.code ?? String(f.id ?? ""); +} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function extractFouNom(f: any): string { + return f.nomfou ?? f.NomFou ?? f.nom_fournisseur ?? f.nomFournisseur ?? f.nom ?? f.libelle ?? f.name ?? "Inconnu"; +} + export async function getFournisseursFromApi( search?: string ): Promise<{ code: string; nom: string }[]> { @@ -142,13 +171,18 @@ export async function getFournisseursFromApi( ? `${FF_API_BASE}/api/fournisseurs?search=${encodeURIComponent(search)}&limit=500` : `${FF_API_BASE}/api/fournisseurs?limit=500`; + console.log(`[api-ff] GET ${url}`); const res = await fetch(url, { cache: "no-store" }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); + if (!res.ok) throw new Error(`HTTP ${res.status} on ${url}`); const data = await res.json(); - // L'API peut renvoyer { items: [...] } ou directement [...] - const list: FfFournisseur[] = Array.isArray(data) ? data : (data.items ?? data.data ?? []); - return list.map(f => ({ code: f.codefou, nom: f.nomfou })); + // Log premier item pour diagnostic + const rawList = extractList(data); + console.log(`[api-ff] fournisseurs: ${rawList.length} items, sample keys:`, rawList[0] ? Object.keys(rawList[0] as object) : "empty"); + + return rawList + .map((f: unknown) => ({ code: extractFouCode(f), nom: extractFouNom(f) })) + .filter((f: { code: string; nom: string }) => f.code); } catch (err) { console.error("[api-ff] getFournisseursFromApi error:", err); return []; @@ -162,11 +196,21 @@ export async function getFournisseursFromApi( export async function getArticlesByFournisseur( codefou: string ): Promise { - return fetchAllPages( + const raw = await fetchAllPages( (page) => `${FF_API_BASE}/api/articles?codefou=${encodeURIComponent(codefou)}&page=${page}&limit=500`, - (data) => Array.isArray(data) ? data : ((data as any).items ?? (data as any).data ?? []), + extractList, 500 ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return raw.map((r: any): FfArticle => ({ + codein: r.codein ?? r.Codein ?? r.code_article ?? r.codeArticle ?? String(r.id ?? ""), + libelle1: r.libelle1 ?? r.Libelle1 ?? r.libelle ?? r.designation ?? r.nom ?? "", + codefou: r.codefou ?? r.Codefou ?? r.code_fournisseur ?? r.codeFournisseur ?? codefou, + nomfou: r.nomfou ?? r.Nomfou ?? r.nom_fournisseur ?? r.nomFournisseur ?? "", + pcb: r.pcb ?? r.Pcb ?? r.conditionnement ?? r.colisage, + pv_central: r.pv_central ?? r.PvCentral ?? r.pv ?? r.prixVente, + noid: r.noid ?? r.NoId ?? r.no_id, + })).filter(a => a.codein); } // --------------------------------------------------------------------------- @@ -178,12 +222,23 @@ export async function getMouvementsByFournisseur( dateDebut: string, dateFin: string ): Promise { - return fetchAllPages( + const raw = await fetchAllPages( (page) => `${FF_API_BASE}/api/mouvements/articles?codefou=${encodeURIComponent(codefou)}&datedeb=${dateDebut}&datefin=${dateFin}&page=${page}&limit=1000`, - (data) => Array.isArray(data) ? data : ((data as any).items ?? (data as any).data ?? []), + extractList, 1000 ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return raw.map((r: any): FfMouvement => ({ + codein: r.codein ?? r.Codein ?? r.code_article ?? "", + genremvt: Number(r.genremvt ?? r.GenreMvt ?? r.genre_mvt ?? r.type ?? 0), + datemvt: r.datemvt ?? r.DateMvt ?? r.date_mvt ?? r.date ?? "", + qte: Number(r.qte ?? r.Qte ?? r.quantite ?? 0), + montant: Number(r.montant ?? r.Montant ?? r.montant_mvt ?? r.ca ?? 0), + marge: Number(r.marge ?? r.Marge ?? r.marge_mvt ?? 0), + qtestock: Number(r.qtestock ?? r.QteStock ?? r.qte_stock ?? r.stock ?? 0), + site: r.site ?? r.Site ?? r.magasin ?? r.code_magasin ?? r.codesite ?? "", + })).filter(m => m.codein && m.datemvt); } // --------------------------------------------------------------------------- @@ -207,8 +262,22 @@ export async function getStockByFournisseur( ); if (!res.ok) return; const data = await res.json(); - const sites: FfStock[] = Array.isArray(data) ? data : ((data as any).items ?? (data as any).data ?? []); - if (sites.length === 0) return; + const rawSites = extractList(data); + if (rawSites.length === 0) return; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sites: FfStock[] = rawSites.map((s: any) => ({ + codein: s.codein ?? s.Codein ?? art.codein, + site: s.site ?? s.Site ?? s.codesite ?? s.magasin ?? "", + stockdispo: Number(s.stockdispo ?? s.StockDispo ?? s.stock_dispo ?? s.stock ?? 0), + qte: Number(s.qte ?? s.Qte ?? s.quantite ?? 0), + valstock: Number(s.valstock ?? s.ValStock ?? s.val_stock ?? s.valeur ?? 0), + prmp: Number(s.prmp ?? s.Prmp ?? s.pa ?? s.prix_achat ?? 0) || undefined, + pv: Number(s.pv ?? s.Pv ?? s.prix_vente ?? s.pv_central ?? 0) || undefined, + dernieresventes: s.dernieresventes ?? s.DernVentes ?? s.derniere_vente ?? s.last_sale ?? "", + dernierereception: s.dernierereception ?? s.DernReception ?? s.derniere_reception ?? s.last_reception ?? "", + nbjoursdernsventes: Number(s.nbjoursdernsventes ?? s.NbJoursDernVentes ?? s.nb_jours ?? 0) || undefined, + })); const agg: AggregatedStock = { stockActuel: 0,