153 lines
4.4 KiB
TypeScript
153 lines
4.4 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;
|
|
isSubscription?: 0 | 1;
|
|
}
|
|
|
|
/**
|
|
* 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> = {};
|
|
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");
|
|
}
|
|
// 0 est une valeur métier valide : ne jamais la tester par vérité.
|
|
if (actions.isSubscription !== undefined && updates.isSubscription === undefined) {
|
|
updates.isSubscription = actions.isSubscription;
|
|
autoFilledFieldsList.push("isSubscription");
|
|
}
|
|
}
|
|
} 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<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;
|
|
}
|