-
-
Modèle IA (override)
-
setLlmModel(e.target.value)}
- placeholder="mistral-large-latest"
- className="h-11"
- />
-
-
- Nom du modèle Mistral à utiliser pour l'extraction
-
-
+ {aiProvider === "referentiel" ? (
+
+
Modèle décidé par le Référentiel IA
+
La surcharge locale est volontairement désactivée pour garantir un moteur unique, piloté et tracé au niveau Santinova.
+
+ ) : (
+
+
Modèle IA (override)
+
setLlmModel(e.target.value)}
+ placeholder="mistral-large-latest"
+ className="h-11"
+ />
+
+
+ Nom du modèle à utiliser pour l'extraction directe
+
+
+ )}
diff --git a/docker-compose.yml b/docker-compose.yml
index 518d85e..fa93603 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -58,6 +58,9 @@ services:
DATABASE_URL: mysql://root:demat_facturation_db_pass_2026@demat-facturation-db:3306/gestion_facturation
JWT_SECRET: demat-facturation-jwt-secret-recette-2026
MISTRAL_API_KEY: ${MISTRAL_API_KEY:-}
+ # Passerelle centralisée : le jeton reste dans l'environnement serveur.
+ REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-}
+ REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-}
UPLOAD_DIR: /app/uploads
# Chemin unique utilisé par le serveur et vérifié au démarrage.
STORAGE_BASE_PATH: /app/storage
diff --git a/drizzle/0036_broken_rattler.sql b/drizzle/0036_broken_rattler.sql
index ab22549..a98f2aa 100644
--- a/drizzle/0036_broken_rattler.sql
+++ b/drizzle/0036_broken_rattler.sql
@@ -1,4 +1,4 @@
-CREATE TABLE IF NOT EXISTS `webImportSources` (
+CREATE TABLE `webImportSources` (
`id` int AUTO_INCREMENT NOT NULL,
`userId` int NOT NULL,
`name` varchar(100) NOT NULL,
diff --git a/drizzle/0041_acoustic_microchip.sql b/drizzle/0041_acoustic_microchip.sql
new file mode 100644
index 0000000..a5fea61
--- /dev/null
+++ b/drizzle/0041_acoustic_microchip.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `importSettings` MODIFY COLUMN `aiProvider` enum('mistral','manus','gemini','referentiel') NOT NULL DEFAULT 'mistral';--> statement-breakpoint
+ALTER TABLE `userSettings` MODIFY COLUMN `aiProvider` enum('mistral','manus','gemini','referentiel') NOT NULL DEFAULT 'mistral';
\ No newline at end of file
diff --git a/drizzle/meta/0041_snapshot.json b/drizzle/meta/0041_snapshot.json
new file mode 100644
index 0000000..cfb1ed0
--- /dev/null
+++ b/drizzle/meta/0041_snapshot.json
@@ -0,0 +1,2517 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "c522da53-5855-4176-9b96-53b2c437d519",
+ "prevId": "4e265c9d-3f36-4806-a85f-6e84ae91cbcc",
+ "tables": {
+ "accountingAllocationList": {
+ "name": "accountingAllocationList",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "user_allocation_unique": {
+ "name": "user_allocation_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "accountingAllocationList_id": {
+ "name": "accountingAllocationList_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "automationRules": {
+ "name": "automationRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "isActive": {
+ "name": "isActive",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "priority": {
+ "name": "priority",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "conditions": {
+ "name": "conditions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "conditionsLogic": {
+ "name": "conditionsLogic",
+ "type": "enum('AND','OR')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'AND'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "automationRules_id": {
+ "name": "automationRules_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "bapHistory": {
+ "name": "bapHistory",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "invoiceId": {
+ "name": "invoiceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "supplierName": {
+ "name": "supplierName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceNumber": {
+ "name": "invoiceNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceDate": {
+ "name": "invoiceDate",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totalAmount": {
+ "name": "totalAmount",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "typeAchat": {
+ "name": "typeAchat",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "serviceConcerne": {
+ "name": "serviceConcerne",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ventilationComptable": {
+ "name": "ventilationComptable",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "recipientName": {
+ "name": "recipientName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "exportMode": {
+ "name": "exportMode",
+ "type": "enum('browser','folder')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'browser'"
+ },
+ "exportPath": {
+ "name": "exportPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pdfUrl": {
+ "name": "pdfUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "signatureName": {
+ "name": "signatureName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sharepointUploadStatus": {
+ "name": "sharepointUploadStatus",
+ "type": "enum('success','error','skipped')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sharepointUploadPath": {
+ "name": "sharepointUploadPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sharepointUploadError": {
+ "name": "sharepointUploadError",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "validatedAt": {
+ "name": "validatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "bapHistory_id": {
+ "name": "bapHistory_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "deletedInvoices": {
+ "name": "deletedInvoices",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "invoiceNumber": {
+ "name": "invoiceNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "totalAmount": {
+ "name": "totalAmount",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "supplierName": {
+ "name": "supplierName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deletedAt": {
+ "name": "deletedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "deletedInvoices_id": {
+ "name": "deletedInvoices_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "departmentList": {
+ "name": "departmentList",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "user_department_unique": {
+ "name": "user_department_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "departmentList_id": {
+ "name": "departmentList_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "exportAutomationRules": {
+ "name": "exportAutomationRules",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "isActive": {
+ "name": "isActive",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "priority": {
+ "name": "priority",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "conditionField": {
+ "name": "conditionField",
+ "type": "enum('recipientName','ventilationComptable')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "conditionValue": {
+ "name": "conditionValue",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "destinationType": {
+ "name": "destinationType",
+ "type": "enum('local','teams','sharepoint')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "destinationPath": {
+ "name": "destinationPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "openInBrowser": {
+ "name": "openInBrowser",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "export_rule_user_priority_idx": {
+ "name": "export_rule_user_priority_idx",
+ "columns": [
+ "userId",
+ "priority"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "exportAutomationRules_id": {
+ "name": "exportAutomationRules_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "freeproImports": {
+ "name": "freeproImports",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "moisLabel": {
+ "name": "moisLabel",
+ "type": "varchar(10)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "annee": {
+ "name": "annee",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "mois": {
+ "name": "mois",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refPiece": {
+ "name": "refPiece",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "nbLignes": {
+ "name": "nbLignes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "totalTtc": {
+ "name": "totalTtc",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sharepointUploadStatus": {
+ "name": "sharepointUploadStatus",
+ "type": "enum('success','error')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sharepointUploadPath": {
+ "name": "sharepointUploadPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sharepointUploadError": {
+ "name": "sharepointUploadError",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sharepointExportedAt": {
+ "name": "sharepointExportedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "freeproImports_id": {
+ "name": "freeproImports_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "freeproSettings": {
+ "name": "freeproSettings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "portalUrl": {
+ "name": "portalUrl",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'https://pro.free.fr'"
+ },
+ "loginEmail": {
+ "name": "loginEmail",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "loginPassword": {
+ "name": "loginPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "frequency": {
+ "name": "frequency",
+ "type": "enum('manual','daily','weekly','monthly')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'manual'"
+ },
+ "maxAnteriority": {
+ "name": "maxAnteriority",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autoEnabled": {
+ "name": "autoEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "lastSuccessAt": {
+ "name": "lastSuccessAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastStatus": {
+ "name": "lastStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastImportCount": {
+ "name": "lastImportCount",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "freeproSettings_id": {
+ "name": "freeproSettings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "freeproSettings_userId_unique": {
+ "name": "freeproSettings_userId_unique",
+ "columns": [
+ "userId"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "freeproVentilationLines": {
+ "name": "freeproVentilationLines",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "importId": {
+ "name": "importId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "structure": {
+ "name": "structure",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "montantCentimes": {
+ "name": "montantCentimes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "freeproVentilationLines_id": {
+ "name": "freeproVentilationLines_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "importLogs": {
+ "name": "importLogs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sourceFileId": {
+ "name": "sourceFileId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "totalInvoicesDetected": {
+ "name": "totalInvoicesDetected",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "invoicesImported": {
+ "name": "invoicesImported",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "duplicatesIgnored": {
+ "name": "duplicatesIgnored",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "errors": {
+ "name": "errors",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "duplicateDetails": {
+ "name": "duplicateDetails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "errorDetails": {
+ "name": "errorDetails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "warningMessage": {
+ "name": "warningMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "importSource": {
+ "name": "importSource",
+ "type": "enum('file','folder','email')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'file'"
+ },
+ "importTrigger": {
+ "name": "importTrigger",
+ "type": "enum('manual','automatic','unknown')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'unknown'"
+ },
+ "importedAt": {
+ "name": "importedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "importLogs_id": {
+ "name": "importLogs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "importSettings": {
+ "name": "importSettings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "manualImportEnabled": {
+ "name": "manualImportEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "autoImportEnabled": {
+ "name": "autoImportEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "autoImportSourcePath": {
+ "name": "autoImportSourcePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autoImportFrequency": {
+ "name": "autoImportFrequency",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 60
+ },
+ "emailImportEnabled": {
+ "name": "emailImportEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "emailImportAddress": {
+ "name": "emailImportAddress",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailImportPassword": {
+ "name": "emailImportPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailImportHost": {
+ "name": "emailImportHost",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailImportPort": {
+ "name": "emailImportPort",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 993
+ },
+ "emailImportFrequency": {
+ "name": "emailImportFrequency",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 30
+ },
+ "emailImportSinceDate": {
+ "name": "emailImportSinceDate",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailImportAuthMode": {
+ "name": "emailImportAuthMode",
+ "type": "enum('basic','oauth2')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'basic'"
+ },
+ "exportFolder": {
+ "name": "exportFolder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "exportFolderType": {
+ "name": "exportFolderType",
+ "type": "enum('local','teams','sharepoint')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'local'"
+ },
+ "bapExportMode": {
+ "name": "bapExportMode",
+ "type": "enum('browser','folder','both')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'browser'"
+ },
+ "azureTenantId": {
+ "name": "azureTenantId",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "azureClientId": {
+ "name": "azureClientId",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "azureClientSecret": {
+ "name": "azureClientSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "azureSecretExpiresAt": {
+ "name": "azureSecretExpiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "aiProvider": {
+ "name": "aiProvider",
+ "type": "enum('mistral','manus','gemini','referentiel')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'mistral'"
+ },
+ "mistralApiKey": {
+ "name": "mistralApiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "manusForgeApiKey": {
+ "name": "manusForgeApiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "manusForgeApiUrl": {
+ "name": "manusForgeApiUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "geminiApiKey": {
+ "name": "geminiApiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "importSettings_id": {
+ "name": "importSettings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "importSettings_userId_unique": {
+ "name": "importSettings_userId_unique",
+ "columns": [
+ "userId"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "invoiceLearnings": {
+ "name": "invoiceLearnings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "supplierKey": {
+ "name": "supplierKey",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fieldName": {
+ "name": "fieldName",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "originalValue": {
+ "name": "originalValue",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "correctedValue": {
+ "name": "correctedValue",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "applyCount": {
+ "name": "applyCount",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "invoiceLearnings_id": {
+ "name": "invoiceLearnings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "invoices": {
+ "name": "invoices",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sourceFileId": {
+ "name": "sourceFileId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "invoiceIndexInFile": {
+ "name": "invoiceIndexInFile",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileKey": {
+ "name": "fileKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileUrl": {
+ "name": "fileUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "supplierName": {
+ "name": "supplierName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceNumber": {
+ "name": "invoiceNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceDate": {
+ "name": "invoiceDate",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deliveryNoteNumber": {
+ "name": "deliveryNoteNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "orderNumber": {
+ "name": "orderNumber",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totalAmount": {
+ "name": "totalAmount",
+ "type": "decimal(10,2)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "recipientName": {
+ "name": "recipientName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pageRange": {
+ "name": "pageRange",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "qualityScore": {
+ "name": "qualityScore",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadataFileKey": {
+ "name": "metadataFileKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadataFileUrl": {
+ "name": "metadataFileUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('processing','completed','error')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'processing'"
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "exportStatus": {
+ "name": "exportStatus",
+ "type": "enum('not_exported','exported','export_error')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'not_exported'"
+ },
+ "manuallyEdited": {
+ "name": "manuallyEdited",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "serviceConcerne": {
+ "name": "serviceConcerne",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "typeAchat": {
+ "name": "typeAchat",
+ "type": "enum('CAPEX','OPEX')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ventilationComptable": {
+ "name": "ventilationComptable",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autoFilledFields": {
+ "name": "autoFilledFields",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "extractedText": {
+ "name": "extractedText",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "isSubscription": {
+ "name": "isSubscription",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "bapValidated": {
+ "name": "bapValidated",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "bapValidatedAt": {
+ "name": "bapValidatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "exportedAt": {
+ "name": "exportedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "exportMode": {
+ "name": "exportMode",
+ "type": "enum('manual','automatic')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "supplier_invoice_date_unique": {
+ "name": "supplier_invoice_date_unique",
+ "columns": [
+ "supplierName",
+ "invoiceNumber",
+ "invoiceDate"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "invoices_id": {
+ "name": "invoices_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "llmFieldsConfig": {
+ "name": "llmFieldsConfig",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fieldName": {
+ "name": "fieldName",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "displayName": {
+ "name": "displayName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "isRequired": {
+ "name": "isRequired",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "displayOrder": {
+ "name": "displayOrder",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "llmFieldsConfig_id": {
+ "name": "llmFieldsConfig_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "llmLogs": {
+ "name": "llmLogs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sourceFileId": {
+ "name": "sourceFileId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceId": {
+ "name": "invoiceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "operation": {
+ "name": "operation",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resolvedProvider": {
+ "name": "resolvedProvider",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resolvedModel": {
+ "name": "resolvedModel",
+ "type": "varchar(80)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "promptSent": {
+ "name": "promptSent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rawResponse": {
+ "name": "rawResponse",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cleanedResponse": {
+ "name": "cleanedResponse",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "success": {
+ "name": "success",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "processingTimeMs": {
+ "name": "processingTimeMs",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pageRange": {
+ "name": "pageRange",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "llmLogs_id": {
+ "name": "llmLogs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "serviceSignatures": {
+ "name": "serviceSignatures",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "signatureId": {
+ "name": "signatureId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "user_service_unique": {
+ "name": "user_service_unique",
+ "columns": [
+ "userId",
+ "serviceName"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "serviceSignatures_id": {
+ "name": "serviceSignatures_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "signatures": {
+ "name": "signatures",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "firstName": {
+ "name": "firstName",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lastName": {
+ "name": "lastName",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "imageKey": {
+ "name": "imageKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "signatures_id": {
+ "name": "signatures_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "sourceFiles": {
+ "name": "sourceFiles",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileKey": {
+ "name": "fileKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fileUrl": {
+ "name": "fileUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "contentHash": {
+ "name": "contentHash",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totalInvoicesDetected": {
+ "name": "totalInvoicesDetected",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "processingStatus": {
+ "name": "processingStatus",
+ "type": "enum('processing','completed','error')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'processing'"
+ },
+ "processingProgress": {
+ "name": "processingProgress",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "source_file_content_hash_unique": {
+ "name": "source_file_content_hash_unique",
+ "columns": [
+ "contentHash"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "sourceFiles_id": {
+ "name": "sourceFiles_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "userSettings": {
+ "name": "userSettings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "llmModel": {
+ "name": "llmModel",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'mistral-large-latest'"
+ },
+ "orderNumberFormat": {
+ "name": "orderNumberFormat",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoiceNumberKeywords": {
+ "name": "invoiceNumberKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deliveryNoteKeywords": {
+ "name": "deliveryNoteKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "orderNumberKeywords": {
+ "name": "orderNumberKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "supplierKeywords": {
+ "name": "supplierKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "totalAmountKeywords": {
+ "name": "totalAmountKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subscriptionKeywords": {
+ "name": "subscriptionKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "recipientKeywords": {
+ "name": "recipientKeywords",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpRecipientFilter": {
+ "name": "sftpRecipientFilter",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpHost": {
+ "name": "sftpHost",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpPort": {
+ "name": "sftpPort",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 22
+ },
+ "sftpUsername": {
+ "name": "sftpUsername",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpPassword": {
+ "name": "sftpPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sftpRemotePath": {
+ "name": "sftpRemotePath",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'/'"
+ },
+ "sftpAutoExport": {
+ "name": "sftpAutoExport",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "llmLogsRetentionMonths": {
+ "name": "llmLogsRetentionMonths",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 3
+ },
+ "learningConfidenceThreshold": {
+ "name": "learningConfidenceThreshold",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 2
+ },
+ "aiProvider": {
+ "name": "aiProvider",
+ "type": "enum('mistral','manus','gemini','referentiel')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'mistral'"
+ },
+ "mistralApiKey": {
+ "name": "mistralApiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "manusForgeApiKey": {
+ "name": "manusForgeApiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "manusForgeApiUrl": {
+ "name": "manusForgeApiUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "geminiApiKey": {
+ "name": "geminiApiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "userSettings_id": {
+ "name": "userSettings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "userSettings_userId_unique": {
+ "name": "userSettings_userId_unique",
+ "columns": [
+ "userId"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "openId": {
+ "name": "openId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "azureAdId": {
+ "name": "azureAdId",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "passwordHash": {
+ "name": "passwordHash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "loginMethod": {
+ "name": "loginMethod",
+ "type": "enum('manus','local','azure-ad')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "enum('user','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'user'"
+ },
+ "isActive": {
+ "name": "isActive",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ },
+ "lastSignedIn": {
+ "name": "lastSignedIn",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "users_openId_unique": {
+ "name": "users_openId_unique",
+ "columns": [
+ "openId"
+ ]
+ },
+ "users_azureAdId_unique": {
+ "name": "users_azureAdId_unique",
+ "columns": [
+ "azureAdId"
+ ]
+ },
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "webImportSources": {
+ "name": "webImportSources",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "connectorType": {
+ "name": "connectorType",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "portalUrl": {
+ "name": "portalUrl",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "loginEmail": {
+ "name": "loginEmail",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "loginPassword": {
+ "name": "loginPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "frequency": {
+ "name": "frequency",
+ "type": "enum('manual','daily','weekly','monthly')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'monthly'"
+ },
+ "autoEnabled": {
+ "name": "autoEnabled",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "lastSuccessAt": {
+ "name": "lastSuccessAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastStatus": {
+ "name": "lastStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastImportCount": {
+ "name": "lastImportCount",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "webImportSources_id": {
+ "name": "webImportSources_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index a1ff34c..028c0d2 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -288,6 +288,13 @@
"when": 1788942350737,
"tag": "0040_sudden_flatman",
"breakpoints": true
+ },
+ {
+ "idx": 41,
+ "version": "5",
+ "when": 1790785209695,
+ "tag": "0041_acoustic_microchip",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/drizzle/schema.ts b/drizzle/schema.ts
index 67429f6..526b687 100644
--- a/drizzle/schema.ts
+++ b/drizzle/schema.ts
@@ -153,7 +153,7 @@ export const userSettings = mysqlTable("userSettings", {
// Seuil de confiance pour les apprentissages IA
learningConfidenceThreshold: int("learningConfidenceThreshold").default(2).notNull(), // Nombre minimum d'applications pour marquer un apprentissage comme Confirmé
// AI Engine configuration
- aiProvider: mysqlEnum("aiProvider", ["mistral", "manus", "gemini"]).default("mistral").notNull(), // AI provider: 'mistral', 'manus' or 'gemini'
+ aiProvider: mysqlEnum("aiProvider", ["mistral", "manus", "gemini", "referentiel"]).default("mistral").notNull(), // Moteur direct ou sélection centralisée par le Référentiel IA
mistralApiKey: text("mistralApiKey"), // Mistral API key (overrides env MISTRAL_API_KEY)
manusForgeApiKey: text("manusForgeApiKey"), // Manus Forge API key (overrides env BUILT_IN_FORGE_API_KEY)
manusForgeApiUrl: text("manusForgeApiUrl"), // Manus Forge API URL (overrides env BUILT_IN_FORGE_API_URL)
@@ -201,7 +201,7 @@ export const importSettings = mysqlTable("importSettings", {
azureSecretExpiresAt: timestamp("azureSecretExpiresAt"), // Azure AD Client Secret expiration date
// AI Engine settings
- aiProvider: mysqlEnum("aiProvider", ["mistral", "manus", "gemini"]).default("mistral").notNull(), // AI provider for invoice extraction
+ aiProvider: mysqlEnum("aiProvider", ["mistral", "manus", "gemini", "referentiel"]).default("mistral").notNull(), // Moteur direct ou sélection centralisée par le Référentiel IA
mistralApiKey: text("mistralApiKey"), // Mistral API key (overrides env MISTRAL_API_KEY)
manusForgeApiKey: text("manusForgeApiKey"), // Manus Forge API key (overrides env BUILT_IN_FORGE_API_KEY)
manusForgeApiUrl: text("manusForgeApiUrl"), // Manus Forge API URL (overrides env BUILT_IN_FORGE_API_URL)
diff --git a/server/_core/env.ts b/server/_core/env.ts
index 3ee6db0..c887e35 100644
--- a/server/_core/env.ts
+++ b/server/_core/env.ts
@@ -9,4 +9,6 @@ export const ENV = {
forgeApiKey: process.env.BUILT_IN_FORGE_API_KEY ?? "",
mistralApiKey: process.env.MISTRAL_API_KEY ?? "",
geminiApiKey: process.env.GEMINI_API_KEY ?? "",
+ referentielIaUrl: process.env.REFERENTIEL_IA_URL ?? "",
+ referentielIaToken: process.env.REFERENTIEL_IA_TOKEN ?? "",
};
diff --git a/server/_core/llm.ts b/server/_core/llm.ts
index cd9cf0d..e18aff9 100644
--- a/server/_core/llm.ts
+++ b/server/_core/llm.ts
@@ -1,4 +1,5 @@
import { ENV } from "./env";
+import { invokeReferentialIa } from "../referentialIa";
export type Role = "system" | "user" | "assistant" | "tool" | "function";
@@ -97,8 +98,8 @@ export type InvokeResult = {
};
};
-export type LlmProvider = "mistral" | "manus" | "gemini";
-export type LlmTransport = "mistral-api" | "manus-forge" | "gemini-api";
+export type LlmProvider = "mistral" | "manus" | "gemini" | "referentiel";
+export type LlmTransport = "mistral-api" | "manus-forge" | "gemini-api" | "referentiel-gateway";
export type UserLlmSettings = {
aiProvider?: string | null;
@@ -344,6 +345,9 @@ export function resolveLlmTrace(settings?: UserLlmSettings): ResolvedLlmTrace {
if (provider === "gemini") {
return { provider: "gemini", model: "gemini-2.0-flash", transport: "gemini-api" };
}
+ if (provider === "referentiel") {
+ return { provider: "referentiel", model: "referentiel-ia", transport: "referentiel-gateway" };
+ }
return { provider: "mistral", model: "mistral-large-latest", transport: "mistral-api" };
}
@@ -355,14 +359,17 @@ export async function invokeLLMWithUserSettings(
params: InvokeParams,
userSettings?: UserLlmSettings
): Promise {
+ const trace = resolveLlmTrace(userSettings);
+ if (trace.provider === "referentiel") {
+ return { response: await invokeReferentialIa(params), trace };
+ }
+
const apiKey = getApiKeyWithSettings(userSettings);
if (!apiKey || apiKey.trim().length === 0) {
throw new Error("Aucune clé API configurée pour le moteur IA sélectionné");
}
- const trace = resolveLlmTrace(userSettings);
const provider = trace.provider;
-
const {
messages,
tools,
diff --git a/server/deploymentRouting.test.ts b/server/deploymentRouting.test.ts
index 4482fb0..6788439 100644
--- a/server/deploymentRouting.test.ts
+++ b/server/deploymentRouting.test.ts
@@ -35,4 +35,11 @@ describe("routage et persistance de déploiement", () => {
expect(guardScript).toContain('REQUIRE_STORAGE_VOLUME:-false');
expect(guardScript).toContain('mkdir -p "$storage_path"');
});
+
+ it("injecte uniquement les secrets serveur de la passerelle Référentiel IA", () => {
+ const composeFile = readFileSync(join(process.cwd(), "docker-compose.yml"), "utf8");
+
+ expect(composeFile).toContain("REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-}");
+ expect(composeFile).toContain("REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-}");
+ });
});
diff --git a/server/drizzleMigrationHistory.test.ts b/server/drizzleMigrationHistory.test.ts
new file mode 100644
index 0000000..96e530c
--- /dev/null
+++ b/server/drizzleMigrationHistory.test.ts
@@ -0,0 +1,14 @@
+import { createHash } from "node:crypto";
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+import { describe, expect, it } from "vitest";
+
+const migrationPath = resolve(process.cwd(), "drizzle/0036_broken_rattler.sql");
+const recordedHash = "3c636f379884a82b48aea91af65532affc0bdd8f6c56fdac35e583c31ce3a8e3";
+
+describe("historique Drizzle", () => {
+ it("conserve l'empreinte de la migration 0036 déjà enregistrée", () => {
+ const hash = createHash("sha256").update(readFileSync(migrationPath)).digest("hex");
+ expect(hash).toBe(recordedHash);
+ });
+});
diff --git a/server/referentialIa.test.ts b/server/referentialIa.test.ts
new file mode 100644
index 0000000..1917660
--- /dev/null
+++ b/server/referentialIa.test.ts
@@ -0,0 +1,79 @@
+import { describe, expect, it } from "vitest";
+import {
+ invokeReferentialIa,
+ testReferentialIaConnection,
+ type ReferentielIaConnection,
+} from "./referentialIa";
+
+const connection: ReferentielIaConnection = {
+ baseUrl: "https://referentiel-ia.recette.santinova-soft.org",
+ token: "test-token",
+};
+
+function jsonResponse(body: unknown, status = 200) {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { "content-type": "application/json" },
+ });
+}
+
+describe("Référentiel IA", () => {
+ it("transmet une requête OpenAI-compatible à la passerelle sélectionnée", async () => {
+ let receivedUrl = "";
+ let receivedInit: RequestInit | undefined;
+ const result = await invokeReferentialIa(
+ {
+ messages: [{ role: "user", content: "Extrais la facture" }],
+ max_tokens: 1200,
+ },
+ connection,
+ async (url, init) => {
+ receivedUrl = String(url);
+ receivedInit = init;
+ return jsonResponse({
+ id: "ref-1",
+ created: 1,
+ model: "selected-by-reference",
+ choices: [{ index: 0, message: { role: "assistant", content: "{}" }, finish_reason: "stop" }],
+ });
+ },
+ );
+
+ expect(receivedUrl).toBe("https://referentiel-ia.recette.santinova-soft.org/api/integration/v1/invoke");
+ expect(receivedInit?.headers).toMatchObject({ authorization: "Bearer test-token" });
+ expect(JSON.parse(String(receivedInit?.body))).toMatchObject({
+ payload: {
+ messages: [{ role: "user", content: "Extrais la facture" }],
+ max_tokens: 1200,
+ stream: false,
+ },
+ });
+ expect(result.choices[0]?.message.content).toBe("{}");
+ });
+
+ it("retourne un diagnostic non sensible lorsqu'aucun jeton n'est configuré", async () => {
+ await expect(
+ invokeReferentialIa({ messages: [{ role: "user", content: "test" }] }, { baseUrl: "", token: "" }),
+ ).rejects.toThrow("Référentiel IA");
+ });
+
+ it("confirme le moteur choisi sans déclencher d'analyse de facture", async () => {
+ const result = await testReferentialIaConnection(
+ connection,
+ async (_url, init) => {
+ expect(JSON.parse(String(init?.body))).toEqual({
+ useCase: "texte",
+ optimizeCost: true,
+ needsFreeTier: false,
+ includeConnection: false,
+ });
+ return jsonResponse({
+ application: { name: "Démat. Facturation DSI" },
+ decision: { selected: { engine: { name: "Manus Forge", slug: "manus-forge" }, connection: undefined } },
+ });
+ },
+ );
+
+ expect(result).toEqual({ applicationName: "Démat. Facturation DSI", engineName: "Manus Forge", model: null });
+ });
+});
diff --git a/server/referentialIa.ts b/server/referentialIa.ts
new file mode 100644
index 0000000..49b022a
--- /dev/null
+++ b/server/referentialIa.ts
@@ -0,0 +1,128 @@
+import type { InvokeParams, InvokeResult } from "./_core/llm";
+import { ENV } from "./_core/env";
+
+export type ReferentielIaConnection = {
+ baseUrl: string;
+ token: string;
+};
+
+type Fetcher = (input: string | URL, init?: RequestInit) => Promise;
+
+const RECOMMENDATIONS_PATH = "/api/integration/v1/recommendations";
+const INVOCATION_PATH = "/api/integration/v1/invoke";
+
+/**
+ * Le jeton est exclusivement fourni au serveur via l'environnement. Il n'est
+ * jamais exposé à l'interface ni stocké dans les paramètres de l'utilisateur.
+ */
+export function getReferentielIaConnection(): ReferentielIaConnection | null {
+ const baseUrl = ENV.referentielIaUrl.trim().replace(/\/$/, "");
+ const token = ENV.referentielIaToken.trim();
+ if (!baseUrl || !token) return null;
+
+ try {
+ const url = new URL(baseUrl);
+ if (url.protocol !== "https:") return null;
+ return { baseUrl: url.toString().replace(/\/$/, ""), token };
+ } catch {
+ return null;
+ }
+}
+
+function requireConnection(connection?: ReferentielIaConnection | null): ReferentielIaConnection {
+ if (!connection?.baseUrl.trim() || !connection.token.trim()) {
+ throw new Error("Le Référentiel IA n'est pas configuré pour cette application.");
+ }
+ return connection;
+}
+
+async function readJson(response: Response): Promise {
+ const raw = await response.text();
+ try {
+ return JSON.parse(raw) as unknown;
+ } catch {
+ return raw;
+ }
+}
+
+function toGatewayPayload(params: InvokeParams): Record {
+ const payload: Record = {
+ messages: params.messages,
+ max_tokens: params.maxTokens ?? params.max_tokens ?? 32768,
+ stream: false,
+ };
+ if (params.tools?.length) payload.tools = params.tools;
+ if (params.toolChoice ?? params.tool_choice) payload.tool_choice = params.toolChoice ?? params.tool_choice;
+ if (params.outputSchema ?? params.output_schema) payload.response_format = {
+ type: "json_schema",
+ json_schema: params.outputSchema ?? params.output_schema,
+ };
+ if (params.responseFormat ?? params.response_format) payload.response_format = params.responseFormat ?? params.response_format;
+ return payload;
+}
+
+function gatewayError(status: number): Error {
+ if (status === 401 || status === 403) return new Error("Le Référentiel IA refuse l'accès de cette application.");
+ if (status === 422) return new Error("Le moteur choisi dans le Référentiel IA n'est pas opérationnel.");
+ if (status === 429) return new Error("La limite d'usage définie dans le Référentiel IA est atteinte.");
+ return new Error(`Le Référentiel IA est indisponible (HTTP ${status}).`);
+}
+
+/** Exécute l'appel sur le moteur décidé et tracé par le Référentiel IA. */
+export async function invokeReferentialIa(
+ params: InvokeParams,
+ connection = getReferentielIaConnection(),
+ fetcher: Fetcher = fetch,
+): Promise {
+ const config = requireConnection(connection);
+ const response = await fetcher(`${config.baseUrl}${INVOCATION_PATH}`, {
+ method: "POST",
+ headers: {
+ "content-type": "application/json",
+ authorization: `Bearer ${config.token}`,
+ },
+ body: JSON.stringify({ payload: toGatewayPayload(params) }),
+ signal: AbortSignal.timeout(120_000),
+ });
+ if (!response.ok) throw gatewayError(response.status);
+ return (await readJson(response)) as InvokeResult;
+}
+
+export type ReferentielIaConnectionTest = {
+ applicationName: string | null;
+ engineName: string | null;
+ model: string | null;
+};
+
+/** Vérifie l'autorisation et le moteur choisi, sans envoyer de facture au modèle. */
+export async function testReferentialIaConnection(
+ connection = getReferentielIaConnection(),
+ fetcher: Fetcher = fetch,
+): Promise {
+ const config = requireConnection(connection);
+ const response = await fetcher(`${config.baseUrl}${RECOMMENDATIONS_PATH}`, {
+ method: "POST",
+ headers: {
+ "content-type": "application/json",
+ authorization: `Bearer ${config.token}`,
+ },
+ body: JSON.stringify({
+ useCase: "texte",
+ optimizeCost: true,
+ needsFreeTier: false,
+ includeConnection: false,
+ }),
+ signal: AbortSignal.timeout(15_000),
+ });
+ if (!response.ok) throw gatewayError(response.status);
+
+ const data = (await readJson(response)) as {
+ application?: { name?: unknown };
+ decision?: { selected?: { engine?: { name?: unknown }; connection?: { model?: unknown } } | null } | null;
+ };
+ return {
+ applicationName: typeof data.application?.name === "string" ? data.application.name : null,
+ engineName: typeof data.decision?.selected?.engine?.name === "string" ? data.decision.selected.engine.name : null,
+ model: typeof data.decision?.selected?.connection?.model === "string" ? data.decision.selected.connection.model : null,
+ };
+}
diff --git a/server/routers.ts b/server/routers.ts
index 2d379bf..6630cf4 100644
--- a/server/routers.ts
+++ b/server/routers.ts
@@ -103,6 +103,7 @@ import pathSync from "path";
import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor";
import { calculateFileSha256 } from "./fileFingerprint";
import { localStoragePut, generateStorageKey } from "./localStorage";
+import { testReferentialIaConnection } from "./referentialIa";
import { testSftpConnection, exportInvoiceToSftp, getUserSftpConfig } from "./sftpExport";
import { drawBapCartouche } from "./bapCartouche";
import { resolveBapExportDestination } from "./exportDestinationResolver";
@@ -1237,7 +1238,7 @@ export const appRouter = router({
sftpAutoExport: z.number().optional(),
llmLogsRetentionMonths: z.number().optional(),
learningConfidenceThreshold: z.number().min(1).optional(),
- aiProvider: z.enum(["mistral", "manus", "gemini"]).optional(),
+ aiProvider: z.enum(["mistral", "manus", "gemini", "referentiel"]).optional(),
mistralApiKey: z.string().optional(),
manusForgeApiKey: z.string().optional(),
manusForgeApiUrl: z.string().optional(),
@@ -1250,6 +1251,12 @@ export const appRouter = router({
});
return { success: true };
}),
+
+ /** Vérifie le choix centralisé sans transmettre de facture ni de clé au navigateur. */
+ testReferentielIa: protectedProcedure.mutation(async () => {
+ const result = await testReferentialIaConnection();
+ return { success: true, ...result };
+ }),
}),
// ============= ADMIN ROUTES =============
diff --git a/server/webImportSourcesMigration.test.ts b/server/webImportSourcesMigration.test.ts
index caf04b6..616807a 100644
--- a/server/webImportSourcesMigration.test.ts
+++ b/server/webImportSourcesMigration.test.ts
@@ -8,8 +8,9 @@ const migration = readFileSync(
);
describe("webImportSources migration", () => {
- it("peut être appliquée sans échec si la table a déjà été créée", () => {
- expect(migration).toMatch(/CREATE TABLE IF NOT EXISTS `webImportSources`/);
+ it("conserve l'instruction historique enregistrée", () => {
+ expect(migration).toMatch(/CREATE TABLE `webImportSources`/);
+ expect(migration).not.toMatch(/IF NOT EXISTS/);
});
it("définit les colonnes nécessaires au connecteur sans modifier les factures", () => {
diff --git a/todo.md b/todo.md
index c107b0f..d25f493 100644
--- a/todo.md
+++ b/todo.md
@@ -1,5 +1,42 @@
# Invoice Analyzer - TODO
+## Choix du moteur IA — Paramètres — 30/09/2026
+- [x] Analyser la configuration IA locale et le contrat du Référentiel IA
+- [x] Ajouter le choix entre moteur local et moteur sélectionné dans le Référentiel IA
+- [x] Valider la migration, les tests, TypeScript et le build en sandbox
+- [ ] Créer un jeton applicatif limité pour Démat. Facturation DSI et configurer la passerelle Référentiel IA
+- [ ] Vérifier une extraction réelle via la passerelle et sa traçabilité dans le Référentiel IA
+
+## Import manuel des connecteurs web — 30/09/2026
+- [x] Analyser le flux serveur d’import web et définir le déclenchement manuel sécurisé
+- [x] Ajouter l’API de déclenchement, le suivi de résultat et le bouton avec confirmation
+- [ ] Diagnostiquer l’échec réel du connecteur SFR en recette, puis déployer uniquement après validation
+
+## Incident création connecteur web — nouvelle tentative production — 30/09/2026
+- [x] Reproduire l’insertion avec le même profil utilisateur sans exposer d’identifiant
+- [ ] Journaliser un code technique non sensible en cas de nouvel échec SQL
+- [ ] Valider et déployer l’instrumentation sans paramètres sensibles
+
+## Correction création connecteur web en production — 30/09/2026
+- [x] Diagnostiquer l’échec SQL d’insertion sans exposer les identifiants
+- [x] Corriger le schéma ou la persistance du connecteur web
+- [x] Valider de bout en bout la création tRPC, avec connecteur fictif supprimé après le test
+
+## Correction validation connecteur web — 30/09/2026
+- [x] Reproduire l’erreur `portalUrl` invalide et identifier l’écart formulaire/API
+- [x] Corriger la normalisation et la validation de l’URL du portail
+- [x] Valider les tests, puis déployer en recette et production sans toucher à MySQL ni aux volumes
+
+## Diagnostic réception e-mail automatique — 24/09/2026
+- [x] Reproduire le démarrage de la réception automatique et identifier le blocage interface → API → planificateur
+- [x] Corriger la cause sans lancer d’import ni créer de planification non sollicitée, avec sélection explicite du compte source pour les administrateurs
+- [x] Valider les contrôles, les tests et le comportement de démarrage/arrêt
+
+## Diagnostic démarrage OAuth2 après test IMAP concluant — 24/09/2026
+- [x] Reproduire le démarrage OAuth2 sans importer de facture et tracer le résultat API : la réception automatique est désactivée en production pour le compte configuré
+- [ ] Corriger la cause du message générique de démarrage
+- [ ] Vérifier le démarrage et l’arrêt contrôlés avec les tests de non-régression
+
## Phase 1: Base de données et schéma
- [x] Créer table users avec support authentification locale + Azure AD
- [x] Créer table sourceFiles pour fichiers PDF uploadés