Checkpoint: Bouton Télécharger toujours visible pour les factures en état Validé dans InvoicesBAP, même après rechargement. Le pdfUrl est récupéré depuis bapHistory via une nouvelle route tRPC getBapPdfUrls. Nommage uniforme Date - Fournisseur - N°Facture.pdf sur tous les points d'export.

This commit is contained in:
Manus
2026-04-12 17:36:09 -04:00
parent bcc421c6a7
commit d1dda66b9a
3 changed files with 57 additions and 3 deletions

View File

@@ -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() {
<CheckCircle2 className="h-4 w-4" />
<span>Validé</span>
</div>
{bapPdfUrls[invoice.id] && (
{allBapPdfUrls[invoice.id] ? (
<Button
size="sm"
variant="outline"
className="h-8 px-2 text-blue-600 border-blue-300 hover:bg-blue-50"
title="Télécharger le PDF annoté BAP"
onClick={() => downloadBapPdf(bapPdfUrls[invoice.id], invoice.supplierName || undefined, invoice.invoiceNumber, invoice.bapValidatedAt)}
onClick={() => downloadBapPdf(allBapPdfUrls[invoice.id], invoice.supplierName || undefined, invoice.invoiceNumber, invoice.bapValidatedAt)}
>
<Download className="h-4 w-4" />
</Button>
) : (
<Button
size="sm"
variant="outline"
className="h-8 px-2 text-gray-400 border-gray-200 cursor-not-allowed"
title="PDF non disponible"
disabled
>
<Download className="h-4 w-4" />
</Button>

View File

@@ -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<void> {
if (!db) return;
await db.delete(invoiceLearnings).where(eq(invoiceLearnings.userId, userId));
}
export async function getBapPdfUrlByInvoiceId(invoiceId: number): Promise<string | undefined> {
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<Record<number, string>> {
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<number, string> = {};
for (const row of results) {
if (row.invoiceId && row.pdfUrl && !map[row.invoiceId]) {
map[row.invoiceId] = row.pdfUrl;
}
}
return map;
}

View File

@@ -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({