From d1dda66b9ae6ba06fb2adf11563a8f3c7114ee8e Mon Sep 17 00:00:00 2001 From: Manus Date: Sun, 12 Apr 2026 17:36:09 -0400 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Bouton=20T=C3=A9l=C3=A9charger=20?= =?UTF-8?q?toujours=20visible=20pour=20les=20factures=20en=20=C3=A9tat=20V?= =?UTF-8?q?alid=C3=A9=20dans=20InvoicesBAP,=20m=C3=AAme=20apr=C3=A8s=20rec?= =?UTF-8?q?hargement.=20Le=20pdfUrl=20est=20r=C3=A9cup=C3=A9r=C3=A9=20depu?= =?UTF-8?q?is=20bapHistory=20via=20une=20nouvelle=20route=20tRPC=20getBapP?= =?UTF-8?q?dfUrls.=20Nommage=20uniforme=20Date=20-=20Fournisseur=20-=20N?= =?UTF-8?q?=C2=B0Facture.pdf=20sur=20tous=20les=20points=20d'export.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/InvoicesBAP.tsx | 22 ++++++++++++++++++++-- server/db.ts | 30 +++++++++++++++++++++++++++++- server/routers.ts | 8 ++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/client/src/pages/InvoicesBAP.tsx b/client/src/pages/InvoicesBAP.tsx index 6a3cc6b..d11e6d9 100644 --- a/client/src/pages/InvoicesBAP.tsx +++ b/client/src/pages/InvoicesBAP.tsx @@ -97,6 +97,14 @@ export default function InvoicesBAP() { const { data: allInvoices, isLoading } = trpc.invoices.list.useQuery(); // Filter for BAP invoices only (Abonnement = NON, isSubscription = 0) const invoices = allInvoices?.filter(inv => inv.isSubscription === 0); + // IDs des factures déjà validées BAP (pour charger leurs pdfUrl depuis bapHistory) + const validatedInvoiceIds = (invoices || []).filter(inv => inv.bapValidated === 1).map(inv => inv.id); + const { data: persistedBapPdfUrls } = trpc.invoices.getBapPdfUrls.useQuery( + { invoiceIds: validatedInvoiceIds }, + { enabled: validatedInvoiceIds.length > 0 } + ); + // Fusionner les pdfUrl persistés (depuis bapHistory) avec ceux en mémoire (validation en cours) + const allBapPdfUrls = { ...(persistedBapPdfUrls || {}), ...bapPdfUrls }; const { data: departments } = trpc.departments.getByUser.useQuery(); const { data: allocations } = trpc.accountingAllocations.getByUser.useQuery(); const utils = trpc.useUtils(); @@ -779,13 +787,23 @@ export default function InvoicesBAP() { Validé - {bapPdfUrls[invoice.id] && ( + {allBapPdfUrls[invoice.id] ? ( + ) : ( + diff --git a/server/db.ts b/server/db.ts index 6e91aa5..303434f 100644 --- a/server/db.ts +++ b/server/db.ts @@ -1,4 +1,4 @@ -import { eq, and, desc, sql } from "drizzle-orm"; +import { eq, and, desc, sql, inArray } from "drizzle-orm"; import { drizzle } from "drizzle-orm/mysql2"; import { InsertUser, @@ -891,3 +891,31 @@ export async function deleteAllLearnings(userId: number): Promise { if (!db) return; await db.delete(invoiceLearnings).where(eq(invoiceLearnings.userId, userId)); } + +export async function getBapPdfUrlByInvoiceId(invoiceId: number): Promise { + const db = await getDb(); + if (!db) return undefined; + const results = await db.select({ pdfUrl: bapHistory.pdfUrl }) + .from(bapHistory) + .where(eq(bapHistory.invoiceId, invoiceId)) + .orderBy(desc(bapHistory.validatedAt)) + .limit(1); + return results[0]?.pdfUrl ?? undefined; +} + +export async function getBapPdfUrlsByInvoiceIds(invoiceIds: number[]): Promise> { + const db = await getDb(); + if (!db || invoiceIds.length === 0) return {}; + const results = await db.select({ invoiceId: bapHistory.invoiceId, pdfUrl: bapHistory.pdfUrl, validatedAt: bapHistory.validatedAt }) + .from(bapHistory) + .where(inArray(bapHistory.invoiceId, invoiceIds)) + .orderBy(desc(bapHistory.validatedAt)); + // Keep only the most recent pdfUrl per invoiceId + const map: Record = {}; + for (const row of results) { + if (row.invoiceId && row.pdfUrl && !map[row.invoiceId]) { + map[row.invoiceId] = row.pdfUrl; + } + } + return map; +} diff --git a/server/routers.ts b/server/routers.ts index 9457e47..31bfe3e 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -71,6 +71,7 @@ import { upsertLearning, deleteLearning, deleteAllLearnings, + getBapPdfUrlsByInvoiceIds, } from "./db"; import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured, generateToken } from "./auth"; import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor"; @@ -896,6 +897,13 @@ export const appRouter = router({ } return { success: true, processed, errors, results }; }), + // ── Récupérer les pdfUrl BAP pour une liste de factures validées ───────────────── + getBapPdfUrls: protectedProcedure + .input(z.object({ invoiceIds: z.array(z.number()) })) + .query(async ({ input }) => { + if (input.invoiceIds.length === 0) return {}; + return getBapPdfUrlsByInvoiceIds(input.invoiceIds); + }), // ── Relancer les automatismes sur une sélection ───────────────── reprocessSelected: protectedProcedure .input(z.object({