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.
609 lines
20 KiB
TypeScript
609 lines
20 KiB
TypeScript
import { eq, and, desc, sql } from "drizzle-orm";
|
|
import { drizzle } from "drizzle-orm/mysql2";
|
|
import {
|
|
InsertUser,
|
|
users,
|
|
sourceFiles,
|
|
InsertSourceFile,
|
|
SourceFile,
|
|
invoices,
|
|
InsertInvoice,
|
|
Invoice,
|
|
userSettings,
|
|
InsertUserSettings,
|
|
UserSettings,
|
|
importLogs,
|
|
InsertImportLog,
|
|
ImportLog,
|
|
llmLogs,
|
|
InsertLlmLog,
|
|
LlmLog,
|
|
importSettings,
|
|
InsertImportSettings,
|
|
ImportSettings,
|
|
departmentList,
|
|
InsertDepartment,
|
|
Department,
|
|
accountingAllocationList,
|
|
InsertAccountingAllocation,
|
|
AccountingAllocation,
|
|
automationRules,
|
|
InsertAutomationRule,
|
|
AutomationRule
|
|
} from "../drizzle/schema";
|
|
import { ENV } from './_core/env';
|
|
|
|
let _db: ReturnType<typeof drizzle> | null = null;
|
|
|
|
export async function getDb() {
|
|
if (!_db && process.env.DATABASE_URL) {
|
|
try {
|
|
_db = drizzle(process.env.DATABASE_URL);
|
|
} catch (error) {
|
|
console.warn("[Database] Failed to connect:", error);
|
|
_db = null;
|
|
}
|
|
}
|
|
return _db;
|
|
}
|
|
|
|
// ============= USER OPERATIONS =============
|
|
|
|
export async function upsertUser(user: InsertUser): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) {
|
|
console.warn("[Database] Cannot upsert user: database not available");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const values: InsertUser = {
|
|
email: user.email,
|
|
loginMethod: user.loginMethod,
|
|
};
|
|
const updateSet: Record<string, unknown> = {};
|
|
|
|
if (user.openId !== undefined) {
|
|
values.openId = user.openId;
|
|
updateSet.openId = user.openId;
|
|
}
|
|
if (user.azureAdId !== undefined) {
|
|
values.azureAdId = user.azureAdId;
|
|
updateSet.azureAdId = user.azureAdId;
|
|
}
|
|
if (user.name !== undefined) {
|
|
values.name = user.name;
|
|
updateSet.name = user.name;
|
|
}
|
|
if (user.passwordHash !== undefined) {
|
|
values.passwordHash = user.passwordHash;
|
|
updateSet.passwordHash = user.passwordHash;
|
|
}
|
|
if (user.isActive !== undefined) {
|
|
values.isActive = user.isActive;
|
|
updateSet.isActive = user.isActive;
|
|
}
|
|
if (user.lastSignedIn !== undefined) {
|
|
values.lastSignedIn = user.lastSignedIn;
|
|
updateSet.lastSignedIn = user.lastSignedIn;
|
|
}
|
|
if (user.role !== undefined) {
|
|
values.role = user.role;
|
|
updateSet.role = user.role;
|
|
} else if (user.openId === ENV.ownerOpenId) {
|
|
values.role = 'admin';
|
|
updateSet.role = 'admin';
|
|
}
|
|
|
|
if (!values.lastSignedIn) {
|
|
values.lastSignedIn = new Date();
|
|
}
|
|
|
|
if (Object.keys(updateSet).length === 0) {
|
|
updateSet.lastSignedIn = new Date();
|
|
}
|
|
|
|
await db.insert(users).values(values).onDuplicateKeyUpdate({
|
|
set: updateSet,
|
|
});
|
|
} catch (error) {
|
|
console.error("[Database] Failed to upsert user:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getUserByOpenId(openId: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function getUserByEmail(email: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(users).where(eq(users.email, email)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function getUserByAzureAdId(azureAdId: string) {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(users).where(eq(users.azureAdId, azureAdId)).limit(1);
|
|
return result.length > 0 ? result[0] : undefined;
|
|
}
|
|
|
|
export async function createLocalUser(email: string, passwordHash: string, name: string, role: "user" | "admin" = "user") {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
await db.insert(users).values({
|
|
email,
|
|
passwordHash,
|
|
name,
|
|
loginMethod: "local",
|
|
role,
|
|
isActive: 1,
|
|
});
|
|
|
|
return getUserByEmail(email);
|
|
}
|
|
|
|
export async function getAllUsers() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(users).orderBy(desc(users.createdAt));
|
|
}
|
|
|
|
export async function updateUserPassword(userId: number, newPasswordHash: string) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.update(users).set({ passwordHash: newPasswordHash }).where(eq(users.id, userId));
|
|
}
|
|
|
|
export async function toggleUserActive(userId: number, isActive: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.update(users).set({ isActive }).where(eq(users.id, userId));
|
|
}
|
|
|
|
export async function deleteUser(userId: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.delete(users).where(eq(users.id, userId));
|
|
}
|
|
|
|
// ============= SOURCE FILE OPERATIONS =============
|
|
|
|
export async function createSourceFile(data: InsertSourceFile): Promise<SourceFile> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(sourceFiles).values(data);
|
|
const insertedId = Number(result[0].insertId);
|
|
|
|
const inserted = await db.select().from(sourceFiles).where(eq(sourceFiles.id, insertedId)).limit(1);
|
|
return inserted[0]!;
|
|
}
|
|
|
|
export async function getSourceFileById(id: number): Promise<SourceFile | undefined> {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(sourceFiles).where(eq(sourceFiles.id, id)).limit(1);
|
|
return result[0];
|
|
}
|
|
|
|
export async function updateSourceFile(id: number, data: Partial<SourceFile>) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.update(sourceFiles).set(data).where(eq(sourceFiles.id, id));
|
|
}
|
|
|
|
export async function getSourceFilesByUserId(userId: number): Promise<SourceFile[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(sourceFiles).where(eq(sourceFiles.userId, userId)).orderBy(desc(sourceFiles.createdAt));
|
|
}
|
|
|
|
// ============= INVOICE OPERATIONS =============
|
|
|
|
export async function createInvoice(data: InsertInvoice): Promise<Invoice> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(invoices).values(data);
|
|
const insertedId = Number(result[0].insertId);
|
|
|
|
const inserted = await db.select().from(invoices).where(eq(invoices.id, insertedId)).limit(1);
|
|
return inserted[0]!;
|
|
}
|
|
|
|
export async function getInvoiceById(id: number): Promise<Invoice | undefined> {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(invoices).where(eq(invoices.id, id)).limit(1);
|
|
return result[0];
|
|
}
|
|
|
|
export async function getInvoicesByUserId(userId: number): Promise<Invoice[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(invoices).where(eq(invoices.userId, userId)).orderBy(desc(invoices.createdAt));
|
|
}
|
|
|
|
export async function getInvoicesByUser(userId: number): Promise<Invoice[]> {
|
|
return getInvoicesByUserId(userId);
|
|
}
|
|
|
|
export async function updateInvoice(id: number, data: Partial<Invoice>) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.update(invoices).set(data).where(eq(invoices.id, id));
|
|
}
|
|
|
|
export async function deleteInvoice(id: number) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.delete(invoices).where(eq(invoices.id, id));
|
|
}
|
|
|
|
export async function searchInvoices(userId: number, query: string): Promise<Invoice[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const searchPattern = `%${query}%`;
|
|
return db.select().from(invoices)
|
|
.where(
|
|
and(
|
|
eq(invoices.userId, userId),
|
|
sql`(${invoices.supplierName} LIKE ${searchPattern} OR ${invoices.invoiceNumber} LIKE ${searchPattern})`
|
|
)
|
|
)
|
|
.orderBy(desc(invoices.createdAt));
|
|
}
|
|
|
|
export async function getInvoiceStats(userId: number) {
|
|
const db = await getDb();
|
|
if (!db) return {
|
|
total: 0,
|
|
completed: 0,
|
|
processing: 0,
|
|
error: 0,
|
|
totalAmount: 0,
|
|
averageScore: 0,
|
|
lastActivity: null,
|
|
weeklyData: [],
|
|
topSuppliers: [],
|
|
suppliersList: []
|
|
};
|
|
|
|
const allInvoices = await getInvoicesByUserId(userId);
|
|
|
|
// Calculate basic stats
|
|
const completed = allInvoices.filter(i => i.status === "completed");
|
|
const totalAmount = completed.reduce((sum, inv) => sum + (Number(inv.totalAmount) || 0), 0);
|
|
const averageScore = completed.length > 0
|
|
? completed.reduce((sum, inv) => sum + (inv.qualityScore || 0), 0) / completed.length
|
|
: 0;
|
|
|
|
// Get last activity
|
|
const sortedByDate = [...allInvoices].sort((a, b) =>
|
|
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
);
|
|
const lastActivity = sortedByDate[0]?.createdAt || null;
|
|
|
|
// Calculate weekly data (last 30 days)
|
|
const now = new Date();
|
|
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
const weeklyMap = new Map<string, number>();
|
|
|
|
allInvoices.forEach(inv => {
|
|
const date = new Date(inv.createdAt);
|
|
if (date >= thirtyDaysAgo) {
|
|
const weekStart = new Date(date);
|
|
weekStart.setDate(date.getDate() - date.getDay()); // Start of week
|
|
const weekKey = `${weekStart.getDate().toString().padStart(2, '0')}/${(weekStart.getMonth() + 1).toString().padStart(2, '0')}`;
|
|
weeklyMap.set(weekKey, (weeklyMap.get(weekKey) || 0) + 1);
|
|
}
|
|
});
|
|
|
|
const weeklyData = Array.from(weeklyMap.entries())
|
|
.map(([week, count]) => ({ week, count }))
|
|
.sort((a, b) => {
|
|
const [dayA, monthA] = a.week.split('/').map(Number);
|
|
const [dayB, monthB] = b.week.split('/').map(Number);
|
|
return (monthA! * 100 + dayA!) - (monthB! * 100 + dayB!);
|
|
});
|
|
|
|
// Calculate top suppliers
|
|
const supplierMap = new Map<string, { count: number; amount: number }>();
|
|
completed.forEach(inv => {
|
|
const supplier = inv.supplierName || 'Inconnu';
|
|
const current = supplierMap.get(supplier) || { count: 0, amount: 0 };
|
|
supplierMap.set(supplier, {
|
|
count: current.count + 1,
|
|
amount: current.amount + (Number(inv.totalAmount) || 0)
|
|
});
|
|
});
|
|
|
|
const topSuppliers = Array.from(supplierMap.entries())
|
|
.map(([name, data]) => ({
|
|
name,
|
|
count: data.count,
|
|
amount: data.amount,
|
|
percentage: completed.length > 0 ? (data.count / completed.length * 100) : 0
|
|
}))
|
|
.sort((a, b) => b.count - a.count)
|
|
.slice(0, 10);
|
|
|
|
const suppliersList = Array.from(supplierMap.entries())
|
|
.map(([name, data]) => ({
|
|
name,
|
|
count: data.count,
|
|
amount: data.amount
|
|
}))
|
|
.sort((a, b) => b.count - a.count);
|
|
|
|
return {
|
|
total: allInvoices.length,
|
|
completed: completed.length,
|
|
processing: allInvoices.filter(i => i.status === "processing").length,
|
|
error: allInvoices.filter(i => i.status === "error").length,
|
|
totalAmount,
|
|
averageScore: Math.round(averageScore),
|
|
lastActivity,
|
|
weeklyData,
|
|
topSuppliers,
|
|
suppliersList
|
|
};
|
|
}
|
|
|
|
export async function findDuplicateInvoice(
|
|
supplierName: string | null,
|
|
invoiceNumber: string | null,
|
|
invoiceDate: Date | null
|
|
): Promise<Invoice | undefined> {
|
|
if (!supplierName || !invoiceNumber || !invoiceDate) return undefined;
|
|
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
|
|
const result = await db.select().from(invoices)
|
|
.where(
|
|
and(
|
|
eq(invoices.supplierName, supplierName),
|
|
eq(invoices.invoiceNumber, invoiceNumber),
|
|
eq(invoices.invoiceDate, invoiceDate)
|
|
)
|
|
)
|
|
.limit(1);
|
|
|
|
return result[0];
|
|
}
|
|
|
|
// ============= USER SETTINGS OPERATIONS =============
|
|
|
|
export async function getUserSettings(userId: number): Promise<UserSettings | undefined> {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const result = await db.select().from(userSettings).where(eq(userSettings.userId, userId)).limit(1);
|
|
return result[0];
|
|
}
|
|
|
|
export async function upsertUserSettings(data: InsertUserSettings) {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const existing = await getUserSettings(data.userId);
|
|
|
|
if (existing) {
|
|
await db.update(userSettings).set(data).where(eq(userSettings.userId, data.userId));
|
|
} else {
|
|
await db.insert(userSettings).values(data);
|
|
}
|
|
}
|
|
|
|
// ============= IMPORT LOG OPERATIONS =============
|
|
|
|
export async function createImportLog(data: InsertImportLog): Promise<ImportLog> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(importLogs).values(data);
|
|
const insertedId = Number(result[0].insertId);
|
|
|
|
const inserted = await db.select().from(importLogs).where(eq(importLogs.id, insertedId)).limit(1);
|
|
return inserted[0]!;
|
|
}
|
|
|
|
export async function getImportLogsByUser(userId: number): Promise<ImportLog[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(importLogs).where(eq(importLogs.userId, userId)).orderBy(desc(importLogs.importedAt));
|
|
}
|
|
|
|
export async function deleteAllImportLogs(userId: number): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.delete(importLogs).where(eq(importLogs.userId, userId));
|
|
}
|
|
|
|
// ============= LLM LOG OPERATIONS =============
|
|
|
|
export async function createLlmLog(data: InsertLlmLog): Promise<LlmLog> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const result = await db.insert(llmLogs).values(data);
|
|
const insertedId = Number(result[0].insertId);
|
|
|
|
const inserted = await db.select().from(llmLogs).where(eq(llmLogs.id, insertedId)).limit(1);
|
|
return inserted[0]!;
|
|
}
|
|
|
|
export async function getLlmLogsBySourceFile(sourceFileId: number): Promise<LlmLog[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(llmLogs).where(eq(llmLogs.sourceFileId, sourceFileId)).orderBy(desc(llmLogs.createdAt));
|
|
}
|
|
|
|
export async function getLlmLogsByInvoice(invoiceId: number): Promise<LlmLog[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(llmLogs).where(eq(llmLogs.invoiceId, invoiceId)).orderBy(desc(llmLogs.createdAt));
|
|
}
|
|
|
|
// ============= IMPORT SETTINGS OPERATIONS =============
|
|
|
|
export async function getImportSettingsByUser(userId: number): Promise<ImportSettings | null> {
|
|
const db = await getDb();
|
|
if (!db) return null;
|
|
const result = await db.select().from(importSettings).where(eq(importSettings.userId, userId)).limit(1);
|
|
return result[0] || null;
|
|
}
|
|
|
|
export async function upsertImportSettings(data: InsertImportSettings): Promise<ImportSettings> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
// Check if settings exist for this user
|
|
const existing = await getImportSettingsByUser(data.userId!);
|
|
|
|
if (existing) {
|
|
// Update existing settings
|
|
await db.update(importSettings)
|
|
.set({
|
|
...data,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(importSettings.userId, data.userId!));
|
|
|
|
const updated = await getImportSettingsByUser(data.userId!);
|
|
return updated!;
|
|
} else {
|
|
// Insert new settings
|
|
const result = await db.insert(importSettings).values(data);
|
|
const insertedId = Number(result[0].insertId);
|
|
|
|
const inserted = await db.select().from(importSettings).where(eq(importSettings.id, insertedId)).limit(1);
|
|
return inserted[0]!;
|
|
}
|
|
}
|
|
|
|
// ============= DEPARTMENT LIST OPERATIONS =============
|
|
|
|
export async function getDepartmentsByUser(userId: number): Promise<Department[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return await db.select().from(departmentList).where(eq(departmentList.userId, userId)).orderBy(departmentList.name);
|
|
}
|
|
|
|
export async function createDepartment(data: InsertDepartment): Promise<Department> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
const [department] = await db.insert(departmentList).values(data).$returningId();
|
|
return await db.select().from(departmentList).where(eq(departmentList.id, department.id)).then(rows => rows[0]);
|
|
}
|
|
|
|
export async function deleteDepartment(id: number): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.delete(departmentList).where(eq(departmentList.id, id));
|
|
}
|
|
|
|
// ============= ACCOUNTING ALLOCATION LIST OPERATIONS =============
|
|
|
|
export async function getAccountingAllocationsByUser(userId: number): Promise<AccountingAllocation[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return await db.select().from(accountingAllocationList).where(eq(accountingAllocationList.userId, userId)).orderBy(accountingAllocationList.name);
|
|
}
|
|
|
|
export async function createAccountingAllocation(data: InsertAccountingAllocation): Promise<AccountingAllocation> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
const [allocation] = await db.insert(accountingAllocationList).values(data).$returningId();
|
|
return await db.select().from(accountingAllocationList).where(eq(accountingAllocationList.id, allocation.id)).then(rows => rows[0]);
|
|
}
|
|
|
|
export async function deleteAccountingAllocation(id: number): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.delete(accountingAllocationList).where(eq(accountingAllocationList.id, id));
|
|
}
|
|
|
|
// ============= AUTOMATION RULES OPERATIONS =============
|
|
|
|
export async function getAutomationRulesByUser(userId: number): Promise<AutomationRule[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return await db.select().from(automationRules).where(eq(automationRules.userId, userId)).orderBy(automationRules.priority);
|
|
}
|
|
|
|
export async function getAutomationRuleById(id: number): Promise<AutomationRule | null> {
|
|
const db = await getDb();
|
|
if (!db) return null;
|
|
const result = await db.select().from(automationRules).where(eq(automationRules.id, id)).limit(1);
|
|
return result[0] || null;
|
|
}
|
|
|
|
export async function createAutomationRule(data: InsertAutomationRule): Promise<AutomationRule> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
const result = await db.insert(automationRules).values(data);
|
|
const insertedId = result[0].insertId;
|
|
const newRule = await getAutomationRuleById(insertedId);
|
|
return newRule!;
|
|
}
|
|
|
|
export async function updateAutomationRule(id: number, data: Partial<InsertAutomationRule>): Promise<AutomationRule> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.update(automationRules).set(data).where(eq(automationRules.id, id));
|
|
const updated = await getAutomationRuleById(id);
|
|
return updated!;
|
|
}
|
|
|
|
export async function deleteAutomationRule(id: number): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
await db.delete(automationRules).where(eq(automationRules.id, id));
|
|
}
|
|
|
|
// ============= INITIALIZE DEFAULT VALUES =============
|
|
|
|
export async function initializeDefaultLists(userId: number): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
// Initialize default departments
|
|
const defaultDepartments = ["DSI", "TRAVAUX", "AUTRE"];
|
|
const existingDepts = await db.select().from(departmentList).where(eq(departmentList.userId, userId));
|
|
const existingDeptNames = new Set(existingDepts.map(d => d.name));
|
|
|
|
for (const name of defaultDepartments) {
|
|
if (!existingDeptNames.has(name)) {
|
|
try {
|
|
await db.insert(departmentList).values({ userId, name });
|
|
} catch (error) {
|
|
// Ignore errors
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize default accounting allocations
|
|
const defaultAllocations = ["TOUS", "PA", "HEP", "SANITAIRE", "AUTRE"];
|
|
const existingAllocs = await db.select().from(accountingAllocationList).where(eq(accountingAllocationList.userId, userId));
|
|
const existingAllocNames = new Set(existingAllocs.map(a => a.name));
|
|
|
|
for (const name of defaultAllocations) {
|
|
if (!existingAllocNames.has(name)) {
|
|
try {
|
|
await db.insert(accountingAllocationList).values({ userId, name });
|
|
} catch (error) {
|
|
// Ignore errors
|
|
}
|
|
}
|
|
}
|
|
}
|