Files
demat-facturation/server/automationEngine.ts
Manus 72c3f503a3 Checkpoint: Système de gestion des automatismes pour remplissage automatique des factures
Nouvelle fonctionnalité majeure :
 Page "Automatismes" complète pour gérer les règles de remplissage automatique
 Système de règles conditionnelles SI...ALORS pour automatiser le remplissage des champs métier
 Support de conditions multiples avec logique AND/OR
 Application automatique des règles lors de l'import de factures
 Gestion de la priorité des règles (ordre d'exécution)
 Activation/désactivation des règles individuelles

Architecture technique :
- drizzle/schema.ts : Table automationRules avec conditions JSON et actions JSON
- server/db.ts : Fonctions CRUD complètes pour les règles (create, update, delete, list)
- server/routers.ts : Routes tRPC pour gérer les automatismes
- server/automationEngine.ts : Moteur d'évaluation et d'application des règles
- client/src/pages/AutomationRules.tsx : Interface complète de gestion des règles
- Migration 0009_regular_warhawk.sql appliquée avec succès

Fonctionnement des règles :
1. **Conditions (SI)** : Définissez des critères basés sur les champs de la facture
   - Champs disponibles : Fournisseur, N° Facture, Montant, Date, N° Commande, N° Bon de livraison
   - Opérateurs : contient, égal à, commence par, finit par, >, <, >=, <=
   - Logique : AND (toutes les conditions) ou OR (au moins une condition)

2. **Actions (ALORS)** : Définissez les champs à remplir automatiquement
   - Type d'achat (CAPEX/OPEX)
   - Service concerné (depuis la liste des services)
   - Ventilation comptable (depuis la liste des ventilations)

3. **Priorité** : Les règles sont appliquées dans l'ordre de priorité
   - Une règle de priorité supérieure ne peut pas être écrasée par une règle de priorité inférieure

4. **Application automatique** : Les règles s'appliquent automatiquement lors de l'import de factures

Exemples d'utilisation :
- SI Fournisseur contient "Microsoft" ET Montant > 1000 ALORS Type d'achat = CAPEX, Service = DSI
- SI Fournisseur contient "EDF" ALORS Type d'achat = OPEX, Ventilation = TOUS
- SI N° Facture commence par "AB" ALORS Service = Administration

Interface utilisateur :
- Liste des règles avec nom, conditions, actions et statut
- Formulaire de création/édition avec interface intuitive
- Boutons d'activation/désactivation rapide
- Suppression avec confirmation
- Affichage lisible des conditions et actions

Cette fonctionnalité permet de gagner un temps considérable en automatisant le remplissage des champs métier selon des règles métier prédéfinies.
2026-02-11 12:35:26 -05:00

138 lines
3.7 KiB
TypeScript

import { getAutomationRulesByUser } from "./db";
import { Invoice } from "../drizzle/schema";
interface Condition {
field: string;
operator: string;
value: string;
}
interface Actions {
typeAchat?: string;
serviceConcerne?: string;
ventilationComptable?: string;
}
/**
* Evaluate a single condition against an invoice
*/
function evaluateCondition(invoice: Invoice, condition: Condition): boolean {
const fieldValue = invoice[condition.field as keyof Invoice];
if (fieldValue === null || fieldValue === undefined) {
return false;
}
const valueStr = String(fieldValue).toLowerCase();
const conditionValue = condition.value.toLowerCase();
switch (condition.operator) {
case "contains":
return valueStr.includes(conditionValue);
case "equals":
return valueStr === conditionValue;
case "startsWith":
return valueStr.startsWith(conditionValue);
case "endsWith":
return valueStr.endsWith(conditionValue);
case ">":
return Number(fieldValue) > Number(condition.value);
case "<":
return Number(fieldValue) < Number(condition.value);
case ">=":
return Number(fieldValue) >= Number(condition.value);
case "<=":
return Number(fieldValue) <= Number(condition.value);
default:
return false;
}
}
/**
* Evaluate all conditions for a rule based on the logic (AND/OR)
*/
function evaluateConditions(
invoice: Invoice,
conditions: Condition[],
logic: "AND" | "OR"
): boolean {
if (conditions.length === 0) {
return false;
}
if (logic === "AND") {
return conditions.every((condition) => evaluateCondition(invoice, condition));
} else {
return conditions.some((condition) => evaluateCondition(invoice, condition));
}
}
/**
* Apply automation rules to an invoice and return the fields to update
* Returns an object with the fields that should be updated based on matching rules
*/
export async function applyAutomationRules(
userId: number,
invoice: Invoice
): Promise<Partial<Invoice>> {
const rules = await getAutomationRulesByUser(userId);
const updates: Partial<Invoice> = {};
// Process rules in priority order (already sorted by priority in getAutomationRulesByUser)
for (const rule of rules) {
// Skip inactive rules
if (rule.isActive !== 1) {
continue;
}
try {
const conditions: Condition[] = JSON.parse(rule.conditions);
const actions: Actions = JSON.parse(rule.actions);
// Evaluate conditions
const conditionsMatch = evaluateConditions(
invoice,
conditions,
rule.conditionsLogic
);
if (conditionsMatch) {
// Apply actions (only if the field is not already set by a higher priority rule)
if (actions.typeAchat && !updates.typeAchat) {
updates.typeAchat = actions.typeAchat as "CAPEX" | "OPEX";
}
if (actions.serviceConcerne && !updates.serviceConcerne) {
updates.serviceConcerne = actions.serviceConcerne;
}
if (actions.ventilationComptable && !updates.ventilationComptable) {
updates.ventilationComptable = actions.ventilationComptable;
}
}
} catch (error) {
console.error(`[AutomationEngine] Error processing rule ${rule.id}:`, error);
// Continue with next rule on error
}
}
return updates;
}
/**
* Apply automation rules to multiple invoices
*/
export async function applyAutomationRulesToBatch(
userId: number,
invoices: Invoice[]
): Promise<Map<number, Partial<Invoice>>> {
const results = new Map<number, Partial<Invoice>>();
for (const invoice of invoices) {
const updates = await applyAutomationRules(userId, invoice);
if (Object.keys(updates).length > 0) {
results.set(invoice.id, updates);
}
}
return results;
}