704 lines
23 KiB
TypeScript
704 lines
23 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,
|
|
llmFieldsConfig,
|
|
InsertLlmFieldConfig,
|
|
LlmFieldConfig,
|
|
signatures,
|
|
InsertSignature,
|
|
Signature
|
|
} 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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============= LLM FIELDS CONFIG OPERATIONS =============
|
|
|
|
export async function getLlmFieldsConfigByUser(userId: number): Promise<LlmFieldConfig[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(llmFieldsConfig).where(eq(llmFieldsConfig.userId, userId)).orderBy(llmFieldsConfig.displayOrder);
|
|
}
|
|
|
|
export async function upsertLlmFieldConfig(data: InsertLlmFieldConfig): Promise<LlmFieldConfig> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
|
|
const existing = await db.select().from(llmFieldsConfig)
|
|
.where(and(
|
|
eq(llmFieldsConfig.userId, data.userId),
|
|
eq(llmFieldsConfig.fieldName, data.fieldName)
|
|
))
|
|
.limit(1);
|
|
|
|
if (existing.length > 0) {
|
|
await db.update(llmFieldsConfig)
|
|
.set({ ...data, updatedAt: new Date() })
|
|
.where(eq(llmFieldsConfig.id, existing[0].id));
|
|
return (await db.select().from(llmFieldsConfig).where(eq(llmFieldsConfig.id, existing[0].id)))[0];
|
|
} else {
|
|
const result = await db.insert(llmFieldsConfig).values(data);
|
|
const insertedId = (result as any).insertId;
|
|
return (await db.select().from(llmFieldsConfig).where(eq(llmFieldsConfig.id, Number(insertedId))))[0];
|
|
}
|
|
}
|
|
|
|
export async function initializeDefaultLlmFields(userId: number): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
|
|
const defaultFields = [
|
|
{ fieldName: "supplierName", displayName: "Nom du fournisseur", isRequired: 1, displayOrder: 1 },
|
|
{ fieldName: "invoiceNumber", displayName: "Numéro de facture", isRequired: 1, displayOrder: 2 },
|
|
{ fieldName: "invoiceDate", displayName: "Date de facture", isRequired: 1, displayOrder: 3 },
|
|
{ fieldName: "totalAmount", displayName: "Montant total TTC", isRequired: 1, displayOrder: 4 },
|
|
{ fieldName: "deliveryNoteNumber", displayName: "Numéro de bon de livraison", isRequired: 0, displayOrder: 5 },
|
|
{ fieldName: "orderNumber", displayName: "Numéro de commande", isRequired: 0, displayOrder: 6 },
|
|
];
|
|
|
|
const existingFields = await db.select().from(llmFieldsConfig).where(eq(llmFieldsConfig.userId, userId));
|
|
const existingFieldNames = new Set(existingFields.map(f => f.fieldName));
|
|
|
|
for (const field of defaultFields) {
|
|
if (!existingFieldNames.has(field.fieldName)) {
|
|
try {
|
|
await db.insert(llmFieldsConfig).values({ userId, ...field });
|
|
} catch (error) {
|
|
// Ignore errors
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============= SIGNATURES HELPERS =============
|
|
|
|
export async function getSignaturesByUser(userId: number): Promise<Signature[]> {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
return db.select().from(signatures).where(eq(signatures.userId, userId));
|
|
}
|
|
|
|
export async function getSignatureById(id: number): Promise<Signature | undefined> {
|
|
const db = await getDb();
|
|
if (!db) return undefined;
|
|
const results = await db.select().from(signatures).where(eq(signatures.id, id));
|
|
return results[0];
|
|
}
|
|
|
|
export async function createSignature(data: InsertSignature): Promise<Signature> {
|
|
const db = await getDb();
|
|
if (!db) throw new Error("Database not available");
|
|
const result = await db.insert(signatures).values(data);
|
|
const insertId = (result[0] as any).insertId;
|
|
const created = await getSignatureById(insertId);
|
|
if (!created) throw new Error("Failed to retrieve created signature");
|
|
return created;
|
|
}
|
|
|
|
export async function deleteSignature(id: number): Promise<void> {
|
|
const db = await getDb();
|
|
if (!db) return;
|
|
await db.delete(signatures).where(eq(signatures.id, id));
|
|
}
|