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> { const rules = await getAutomationRulesByUser(userId); const updates: Partial = {}; const autoFilledFieldsList: string[] = []; // 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"; autoFilledFieldsList.push("typeAchat"); } if (actions.serviceConcerne && !updates.serviceConcerne) { updates.serviceConcerne = actions.serviceConcerne; autoFilledFieldsList.push("serviceConcerne"); } if (actions.ventilationComptable && !updates.ventilationComptable) { updates.ventilationComptable = actions.ventilationComptable; autoFilledFieldsList.push("ventilationComptable"); } } } catch (error) { console.error(`[AutomationEngine] Error processing rule ${rule.id}:`, error); // Continue with next rule on error } } // Add the list of auto-filled fields to the updates if (autoFilledFieldsList.length > 0) { updates.autoFilledFields = JSON.stringify(autoFilledFieldsList); } return updates; } /** * Apply automation rules to multiple invoices */ export async function applyAutomationRulesToBatch( userId: number, invoices: Invoice[] ): Promise>> { const results = new Map>(); for (const invoice of invoices) { const updates = await applyAutomationRules(userId, invoice); if (Object.keys(updates).length > 0) { results.set(invoice.id, updates); } } return results; }