Checkpoint: Ajout route tRPC invoices.reprocessSelected et bouton Relancer (violet) dans la barre d'outils de Factures BAP

This commit is contained in:
Manus
2026-04-12 14:20:05 -04:00
parent e9c727d679
commit 8acfb4eae1
2 changed files with 133 additions and 1 deletions

View File

@@ -864,6 +864,106 @@ export const appRouter = router({
}
return { success: true, processed, errors, results };
}),
// ── Relancer les automatismes sur une sélection ─────────────────
reprocessSelected: protectedProcedure
.input(z.object({
invoiceIds: z.array(z.number()).min(1),
}))
.mutation(async ({ input, ctx }) => {
const { applyAutomationRules } = await import("./automationEngine");
const { localStoragePut, generateStorageKey } = await import('./localStorage');
const path = await import('path');
const fs = await import('fs/promises');
const STORAGE_BASE_PATH = process.env.STORAGE_BASE_PATH || path.join(process.cwd(), 'storage');
const allInvoices = await getInvoicesByUser(ctx.user.id);
const selected = allInvoices.filter(inv => input.invoiceIds.includes(inv.id));
if (selected.length === 0) throw new TRPCError({ code: 'NOT_FOUND', message: 'Aucune facture trouvée' });
const userSettings = await getUserSettings(ctx.user.id);
const model = userSettings?.llmModel || 'mistral-large-latest';
const customKeywords = {
invoiceNumber: userSettings?.invoiceNumberKeywords || null,
deliveryNote: userSettings?.deliveryNoteKeywords || null,
orderNumber: userSettings?.orderNumberKeywords || null,
supplier: userSettings?.supplierKeywords || null,
totalAmount: userSettings?.totalAmountKeywords || null,
subscription: userSettings?.subscriptionKeywords || null,
recipient: userSettings?.recipientKeywords || null,
};
let processed = 0;
let errors = 0;
const results: Array<{ id: number; success: boolean; qualityScore?: number; error?: string }> = [];
for (const invoice of selected) {
try {
// Lire le PDF source
let pdfBuffer: Buffer;
const localPath = path.join(STORAGE_BASE_PATH, invoice.fileKey);
try {
pdfBuffer = await fs.readFile(localPath);
} catch (_) {
const fileUrl = invoice.fileUrl;
if (!fileUrl) throw new Error('Fichier PDF introuvable');
let absoluteUrl = fileUrl;
if (fileUrl.startsWith('/')) {
const baseUrl = process.env.APP_BASE_URL || `http://localhost:${process.env.PORT || 3000}`;
absoluteUrl = `${baseUrl}${fileUrl}`;
}
const resp = await fetch(absoluteUrl);
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
pdfBuffer = Buffer.from(await resp.arrayBuffer());
}
// Relancer l'extraction LLM
const result = await extractInvoicesWithMistral(pdfBuffer, ctx.user.id, invoice.sourceFileId!, model, customKeywords);
// Prendre la facture correspondante dans le résultat (par index ou la première)
const idx = (invoice.invoiceIndexInFile || 1) - 1;
const extracted = result.invoices[idx] || result.invoices[0];
if (!extracted) throw new Error('Extraction vide');
// Mettre à jour les champs extraits
const metadataJson = generateMetadataJSON(extracted);
const metadataKey = generateStorageKey(ctx.user.id, `${invoice.fileName}-reprocess-metadata.json`);
const { url: metadataUrl } = await localStoragePut(metadataKey, Buffer.from(metadataJson), 'application/json');
await updateInvoice(invoice.id, {
supplierName: extracted.supplierName,
invoiceNumber: extracted.invoiceNumber,
invoiceDate: extracted.invoiceDate,
deliveryNoteNumber: extracted.deliveryNoteNumber,
orderNumber: extracted.orderNumber,
totalAmount: extracted.totalAmount?.toString(),
recipientName: extracted.recipientName,
qualityScore: extracted.qualityScore,
extractedText: extracted.extractedText,
isSubscription: extracted.isSubscription ? 1 : 0,
metadataFileKey: metadataKey,
metadataFileUrl: metadataUrl,
});
// Réappliquer les règles d'automatisme
const updatedInvoice = await getInvoiceById(invoice.id);
if (updatedInvoice) {
const automationUpdates = await applyAutomationRules(ctx.user.id, updatedInvoice);
if (Object.keys(automationUpdates).length > 0) {
await updateInvoice(invoice.id, automationUpdates);
}
}
results.push({ id: invoice.id, success: true, qualityScore: extracted.qualityScore });
processed++;
} catch (err: any) {
console.error(`[Reprocess] Error on invoice ${invoice.id}:`, err);
results.push({ id: invoice.id, success: false, error: err.message });
errors++;
}
}
return { success: true, processed, errors, results };
}),
// // ── Historique BAP ────────────────────
search: protectedProcedure
.input(z.object({ query: z.string() }))