From 9a9b881d19f046fd7f2f21c55d9bd1fc047627ea Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Tue, 14 Apr 2026 13:25:17 +0200 Subject: [PATCH] feat: add derniere_entree column and filter recent stock entries on 6-month tab - Exclude products with a stock entry in the last 6 months (NOT EXISTS subquery) - Add derniere_entree subquery to SQL (last genremvt 1/2 movement date) - Display derniere_entree column in table and Excel export Co-Authored-By: Claude Sonnet 4.6 --- src/app/(dashboard)/stock-negatif/client.tsx | 10 ++++++---- src/lib/pg-ff-client.ts | 20 ++++++++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/app/(dashboard)/stock-negatif/client.tsx b/src/app/(dashboard)/stock-negatif/client.tsx index c68b9dd..f1e2d36 100644 --- a/src/app/(dashboard)/stock-negatif/client.tsx +++ b/src/app/(dashboard)/stock-negatif/client.tsx @@ -377,15 +377,15 @@ function TabSansVente6Mois({ rows, magasin }: { rows: PgSansVente6MoisRow[]; mag const ExcelJS = (await import("exceljs")).default; const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("Sans vente 6 mois"); - ws.addRow(["CODEIN", "Libellé", "Fournisseur", "Magasin", "Stock actuel", "Dernière vente", "Jours sans vente"]); + ws.addRow(["CODEIN", "Libellé", "Fournisseur", "Magasin", "Stock actuel", "Dernière vente", "Jours sans vente", "Dernière entrée"]); ws.getRow(1).eachCell(cell => { cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FF1E293B" } }; cell.font = { color: { argb: "FFFFFFFF" }, bold: true }; cell.alignment = { vertical: "middle", horizontal: "center" }; }); ws.getRow(1).height = 22; - for (const r of sorted) ws.addRow([r.codein, r.libelle1, r.fournisseur, r.site, r.stock_actuel, fmtDate(r.derniere_vente), r.jours_sans_vente ?? "—"]); - ws.columns = [{ width: 16 }, { width: 30 }, { width: 24 }, { width: 10 }, { width: 12 }, { width: 18 }, { width: 16 }]; + for (const r of sorted) ws.addRow([r.codein, r.libelle1, r.fournisseur, r.site, r.stock_actuel, fmtDate(r.derniere_vente), r.jours_sans_vente ?? "—", fmtDate(r.derniere_entree)]); + ws.columns = [{ width: 16 }, { width: 30 }, { width: 24 }, { width: 10 }, { width: 12 }, { width: 18 }, { width: 16 }, { width: 18 }]; const buffer = await wb.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); const url = URL.createObjectURL(blob); @@ -438,11 +438,12 @@ function TabSansVente6Mois({ rows, magasin }: { rows: PgSansVente6MoisRow[]; mag {th("stock_actuel", "Stock actuel")} {th("derniere_vente", "Dernière vente")} {th("jours_sans_vente", "Jours sans vente")} + {th("derniere_entree", "Dernière entrée")} {sorted.length === 0 && ( - Aucun article trouvé + Aucun article trouvé )} {sorted.map((row, i) => ( @@ -458,6 +459,7 @@ function TabSansVente6Mois({ rows, magasin }: { rows: PgSansVente6MoisRow[]; mag style={{ color: row.jours_sans_vente && row.jours_sans_vente > 365 ? "#ef4444" : "#6b7280" }}> {row.jours_sans_vente != null ? row.jours_sans_vente.toLocaleString("fr-FR") : "—"} + {fmtDate(row.derniere_entree)} ))} diff --git a/src/lib/pg-ff-client.ts b/src/lib/pg-ff-client.ts index c616d9f..7c644a8 100644 --- a/src/lib/pg-ff-client.ts +++ b/src/lib/pg-ff-client.ts @@ -931,12 +931,14 @@ export interface PgSansVente6MoisRow { site: string; stock_actuel: number; derniere_vente: string | null; + derniere_entree: string | null; jours_sans_vente: number | null; } /** * Retourne les articles ayant du stock disponible (qte > 0) - * mais dont la dernière vente remonte à plus de 6 mois (ou n'ont jamais été vendus). + * dont la dernière vente remonte à plus de 6 mois (ou jamais vendus) + * ET dont la dernière entrée en stock remonte aussi à plus de 6 mois. */ export async function pgGetSansVente6Mois(site?: string): Promise { const siteFilter = site ? sql`AND cs.site = ${site}` : sql``; @@ -951,7 +953,14 @@ export async function pgGetSansVente6Mois(site?: string): Promise= CURRENT_DATE - INTERVAL '6 months' + ) ${siteFilter} ORDER BY cs.dernierevente ASC NULLS FIRST `);