Checkpoint: Ajout complet du champ "Destinataire" (recipientName) dans toute l'application : schéma DB (invoices.recipientName + userSettings.recipientKeywords), extraction IA (prompt LLM + interface), services d'import (email, dossier, upload manuel), interface utilisateur (liste des factures, détail, paramètres mots-clés), règles d'automatisme et configuration des champs LLM obligatoires.

This commit is contained in:
Manus
2026-04-12 05:06:06 -04:00
parent d8d2ef14da
commit 604426523d
14 changed files with 1560 additions and 7 deletions

View File

@@ -658,6 +658,7 @@ export async function initializeDefaultLlmFields(userId: number): Promise<void>
{ fieldName: "totalAmount", displayName: "Montant total TTC", isRequired: 1, displayOrder: 4 },
{ fieldName: "deliveryNoteNumber", displayName: "Numéro de bon de livraison", isRequired: 0, displayOrder: 5 },
{ fieldName: "orderNumber", displayName: "Numéro de commande", isRequired: 0, displayOrder: 6 },
{ fieldName: "recipientName", displayName: "Destinataire", isRequired: 0, displayOrder: 7 },
];
const existingFields = await db.select().from(llmFieldsConfig).where(eq(llmFieldsConfig.userId, userId));

View File

@@ -75,6 +75,7 @@ async function processEmailAttachment(
supplier: settings.supplierKeywords,
totalAmount: settings.totalAmountKeywords,
subscription: settings.subscriptionKeywords,
recipient: settings.recipientKeywords,
} : undefined;
const model = settings?.llmModel || "mistral-large-latest";
@@ -153,6 +154,7 @@ async function processEmailAttachment(
deliveryNoteNumber: invoiceData.deliveryNoteNumber,
orderNumber: invoiceData.orderNumber,
totalAmount: invoiceData.totalAmount?.toString(),
recipientName: invoiceData.recipientName,
pageRange: invoiceData.pageRange,
qualityScore: invoiceData.qualityScore,
extractedText: invoiceData.extractedText,

View File

@@ -76,6 +76,7 @@ async function processFolderFile(
supplier: settings.supplierKeywords,
totalAmount: settings.totalAmountKeywords,
subscription: settings.subscriptionKeywords,
recipient: settings.recipientKeywords,
} : undefined;
const model = settings?.llmModel || "mistral-large-latest";
@@ -154,6 +155,7 @@ async function processFolderFile(
deliveryNoteNumber: invoiceData.deliveryNoteNumber,
orderNumber: invoiceData.orderNumber,
totalAmount: invoiceData.totalAmount?.toString(),
recipientName: invoiceData.recipientName,
pageRange: invoiceData.pageRange,
qualityScore: invoiceData.qualityScore,
extractedText: invoiceData.extractedText,

View File

@@ -10,6 +10,7 @@ export interface ExtractedInvoiceData {
deliveryNoteNumber: string | null;
orderNumber: string | null;
totalAmount: number | null;
recipientName: string | null; // Destinataire de la facture
pageRange: string;
qualityScore: number; // 0-100
extractedText: string | null; // Full text extracted from the invoice PDF
@@ -141,6 +142,7 @@ export async function extractInvoicesWithMistral(
supplier?: string | null;
totalAmount?: string | null;
subscription?: string | null;
recipient?: string | null;
}
): Promise<MultiInvoiceResult> {
// Load user's field configuration
@@ -184,6 +186,7 @@ export async function extractInvoicesWithMistral(
if (customKeywords.supplier) hints.push(`Fournisseur: ${customKeywords.supplier}`);
if (customKeywords.totalAmount) hints.push(`Montant total: ${customKeywords.totalAmount}`);
if (customKeywords.subscription) hints.push(`Abonnement: ${customKeywords.subscription}`);
if (customKeywords.recipient) hints.push(`Destinataire: ${customKeywords.recipient}`);
if (hints.length > 0) {
keywordsHint = `\n\nMots-clés personnalisés à rechercher:\n${hints.join("\n")}`;
@@ -200,12 +203,13 @@ export async function extractInvoicesWithMistral(
const prompt = `Tu es un expert en extraction de données de factures. Analyse ce document PDF et extrais toutes les factures qu'il contient.
Pour chaque facture trouvée, extrais les informations suivantes:
- supplierName: Nom du fournisseur/vendeur
- supplierName: Nom du fournisseur/vendeur (émetteur de la facture)
- invoiceNumber: Numéro de la facture
- invoiceDate: Date de la facture (format ISO 8601: YYYY-MM-DD)
- deliveryNoteNumber: Numéro du bon de livraison (si présent)
- orderNumber: Numéro de commande client (si présent)
- totalAmount: Montant total TTC (nombre décimal)
- recipientName: Nom du destinataire/client (société ou personne à qui la facture est adressée)
- pageRange: Plage de pages de cette facture (ex: "1-2" ou "5")
${qualityScoreInstruction}
- extractedText: Texte complet extrait de la facture (tout le texte visible sur les pages de cette facture)
@@ -223,6 +227,7 @@ Réponds UNIQUEMENT avec un objet JSON valide au format suivant:
"deliveryNoteNumber": "...",
"orderNumber": "...",
"totalAmount": 123.45,
"recipientName": "...",
"pageRange": "1-2",
"qualityScore": 85,
"extractedText": "Texte complet de la facture...",
@@ -318,6 +323,7 @@ export function generateMetadataJSON(invoice: ExtractedInvoiceData): string {
deliveryNoteNumber: invoice.deliveryNoteNumber,
orderNumber: invoice.orderNumber,
totalAmount: invoice.totalAmount,
recipientName: invoice.recipientName,
pageRange: invoice.pageRange,
qualityScore: invoice.qualityScore,
extractedAt: new Date().toISOString(),

View File

@@ -185,6 +185,7 @@ export const appRouter = router({
supplier: settings.supplierKeywords,
totalAmount: settings.totalAmountKeywords,
subscription: settings.subscriptionKeywords,
recipient: settings.recipientKeywords,
} : undefined;
const model = settings?.llmModel || "mistral-large-latest";
@@ -260,6 +261,7 @@ export const appRouter = router({
deliveryNoteNumber: invoiceData.deliveryNoteNumber,
orderNumber: invoiceData.orderNumber,
totalAmount: invoiceData.totalAmount?.toString(),
recipientName: invoiceData.recipientName,
pageRange: invoiceData.pageRange,
qualityScore: invoiceData.qualityScore,
extractedText: invoiceData.extractedText,
@@ -350,6 +352,7 @@ export const appRouter = router({
deliveryNoteNumber: z.string().optional(),
orderNumber: z.string().optional(),
totalAmount: z.string().optional(),
recipientName: z.string().optional(),
serviceConcerne: z.string().optional(),
typeAchat: z.enum(["CAPEX", "OPEX"]).optional(),
ventilationComptable: z.string().optional(),
@@ -459,6 +462,7 @@ export const appRouter = router({
supplierKeywords: z.string().optional(),
totalAmountKeywords: z.string().optional(),
subscriptionKeywords: z.string().optional(),
recipientKeywords: z.string().optional(),
sftpHost: z.string().optional(),
sftpPort: z.number().optional(),
sftpUsername: z.string().optional(),