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:
@@ -97,6 +97,14 @@ export default function InvoicesBAP() {
|
|||||||
const { data: allInvoices, isLoading } = trpc.invoices.list.useQuery();
|
const { data: allInvoices, isLoading } = trpc.invoices.list.useQuery();
|
||||||
// Filter for BAP invoices only (Abonnement = NON, isSubscription = 0)
|
// Filter for BAP invoices only (Abonnement = NON, isSubscription = 0)
|
||||||
const invoices = allInvoices?.filter(inv => inv.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: departments } = trpc.departments.getByUser.useQuery();
|
||||||
const { data: allocations } = trpc.accountingAllocations.getByUser.useQuery();
|
const { data: allocations } = trpc.accountingAllocations.getByUser.useQuery();
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
@@ -779,13 +787,23 @@ export default function InvoicesBAP() {
|
|||||||
<CheckCircle2 className="h-4 w-4" />
|
<CheckCircle2 className="h-4 w-4" />
|
||||||
<span>Validé</span>
|
<span>Validé</span>
|
||||||
</div>
|
</div>
|
||||||
{bapPdfUrls[invoice.id] && (
|
{allBapPdfUrls[invoice.id] ? (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 px-2 text-blue-600 border-blue-300 hover:bg-blue-50"
|
className="h-8 px-2 text-blue-600 border-blue-300 hover:bg-blue-50"
|
||||||
title="Télécharger le PDF annoté BAP"
|
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" />
|
<Download className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
30
server/db.ts
30
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 { drizzle } from "drizzle-orm/mysql2";
|
||||||
import {
|
import {
|
||||||
InsertUser,
|
InsertUser,
|
||||||
@@ -891,3 +891,31 @@ export async function deleteAllLearnings(userId: number): Promise<void> {
|
|||||||
if (!db) return;
|
if (!db) return;
|
||||||
await db.delete(invoiceLearnings).where(eq(invoiceLearnings.userId, userId));
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ import {
|
|||||||
upsertLearning,
|
upsertLearning,
|
||||||
deleteLearning,
|
deleteLearning,
|
||||||
deleteAllLearnings,
|
deleteAllLearnings,
|
||||||
|
getBapPdfUrlsByInvoiceIds,
|
||||||
} from "./db";
|
} from "./db";
|
||||||
import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured, generateToken } from "./auth";
|
import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured, generateToken } from "./auth";
|
||||||
import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor";
|
import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor";
|
||||||
@@ -896,6 +897,13 @@ export const appRouter = router({
|
|||||||
}
|
}
|
||||||
return { success: true, processed, errors, results };
|
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 ─────────────────
|
// ── Relancer les automatismes sur une sélection ─────────────────
|
||||||
reprocessSelected: protectedProcedure
|
reprocessSelected: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user