Checkpoint: Bouton Relancer corrigé (automatismes uniquement, sans LLM). Système d'apprentissage complet : table invoiceLearnings, routes tRPC, déclenchement depuis InvoiceDetail, application lors des imports, page LearningSettings dans Configuration > Apprentissages IA.

This commit is contained in:
Manus
2026-04-12 14:54:29 -04:00
parent 120f56bb23
commit 3ae1e47ca6
10 changed files with 2187 additions and 74 deletions

View File

@@ -41,7 +41,10 @@ import {
ServiceSignature,
bapHistory,
InsertBapHistory,
BapHistory
BapHistory,
invoiceLearnings,
InsertInvoiceLearning,
InvoiceLearning
} from "../drizzle/schema";
import { ENV } from './_core/env';
@@ -811,3 +814,80 @@ export async function deleteBapHistoryEntry(id: number): Promise<void> {
if (!db) return;
await db.delete(bapHistory).where(eq(bapHistory.id, id));
}
// ── Invoice Learnings (corrections manuelles apprises) ────────────────────────
/** Normalise le nom du fournisseur pour la clé de correspondance */
export function normalizeSupplierId(supplierName: string): string {
return supplierName.trim().toLowerCase().replace(/\s+/g, ' ');
}
/** Récupère tous les apprentissages d'un utilisateur */
export async function getLearningsByUser(userId: number): Promise<InvoiceLearning[]> {
const db = await getDb();
if (!db) return [];
return db.select().from(invoiceLearnings).where(eq(invoiceLearnings.userId, userId));
}
/** Récupère les apprentissages pour un fournisseur donné */
export async function getLearningsBySupplier(userId: number, supplierName: string): Promise<InvoiceLearning[]> {
const db = await getDb();
if (!db) return [];
const key = normalizeSupplierId(supplierName);
return db.select().from(invoiceLearnings).where(
and(eq(invoiceLearnings.userId, userId), eq(invoiceLearnings.supplierKey, key))
);
}
/** Enregistre ou met à jour un apprentissage (upsert par userId + supplierKey + fieldName) */
export async function upsertLearning(data: {
userId: number;
supplierName: string;
fieldName: string;
originalValue?: string;
correctedValue: string;
}): Promise<void> {
const db = await getDb();
if (!db) return;
const supplierKey = normalizeSupplierId(data.supplierName);
// Chercher si une entrée existe déjà
const existing = await db.select().from(invoiceLearnings).where(
and(
eq(invoiceLearnings.userId, data.userId),
eq(invoiceLearnings.supplierKey, supplierKey),
eq(invoiceLearnings.fieldName, data.fieldName)
)
);
if (existing.length > 0) {
await db.update(invoiceLearnings)
.set({
correctedValue: data.correctedValue,
originalValue: data.originalValue ?? existing[0].originalValue,
applyCount: (existing[0].applyCount || 1) + 1,
})
.where(eq(invoiceLearnings.id, existing[0].id));
} else {
await db.insert(invoiceLearnings).values({
userId: data.userId,
supplierKey,
fieldName: data.fieldName,
originalValue: data.originalValue,
correctedValue: data.correctedValue,
applyCount: 1,
});
}
}
/** Supprime un apprentissage par ID */
export async function deleteLearning(id: number): Promise<void> {
const db = await getDb();
if (!db) return;
await db.delete(invoiceLearnings).where(eq(invoiceLearnings.id, id));
}
/** Supprime tous les apprentissages d'un utilisateur */
export async function deleteAllLearnings(userId: number): Promise<void> {
const db = await getDb();
if (!db) return;
await db.delete(invoiceLearnings).where(eq(invoiceLearnings.userId, userId));
}