Checkpoint: Historique des imports enrichi avec le compte, la source, le déclencheur, les résultats, les erreurs et les informations du fichier source. Les administrateurs peuvent déclencher une lecture e-mail ponctuelle pour une boîte configurée, avec confirmation explicite, sans activer ni créer de planification. Migration importTrigger appliquée en sandbox ; TypeScript, 48 tests, build et rendus bureau/mobile validés.

This commit is contained in:
Manus
2026-09-02 11:43:23 +00:00
parent f53bb82256
commit 0b2f1b781d
12 changed files with 2834 additions and 421 deletions

View File

@@ -1,4 +1,4 @@
import { eq, and, desc, sql, inArray } from "drizzle-orm";
import { eq, and, desc, sql, inArray, getTableColumns } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import {
InsertUser,
@@ -538,6 +538,44 @@ export async function getAllImportLogs(): Promise<ImportLog[]> {
return db.select().from(importLogs).orderBy(desc(importLogs.importedAt));
}
/**
* Retourne les informations nécessaires à l'audit d'un import sans exposer
* les paramètres sensibles de messagerie. L'administrateur peut ainsi relier
* chaque import à son compte, fichier source et déclencheur.
*/
export async function getImportLogsWithDetailsByUser(userId: number) {
const db = await getDb();
if (!db) return [];
return db.select({
...getTableColumns(importLogs),
userName: users.name,
userEmail: users.email,
sourceCreatedAt: sourceFiles.createdAt,
sourceStatus: sourceFiles.processingStatus,
})
.from(importLogs)
.leftJoin(users, eq(importLogs.userId, users.id))
.leftJoin(sourceFiles, eq(importLogs.sourceFileId, sourceFiles.id))
.where(eq(importLogs.userId, userId))
.orderBy(desc(importLogs.importedAt));
}
export async function getAllImportLogsWithDetails() {
const db = await getDb();
if (!db) return [];
return db.select({
...getTableColumns(importLogs),
userName: users.name,
userEmail: users.email,
sourceCreatedAt: sourceFiles.createdAt,
sourceStatus: sourceFiles.processingStatus,
})
.from(importLogs)
.leftJoin(users, eq(importLogs.userId, users.id))
.leftJoin(sourceFiles, eq(importLogs.sourceFileId, sourceFiles.id))
.orderBy(desc(importLogs.importedAt));
}
export async function deleteAllImportLogs(userId: number | null): Promise<void> {
const db = await getDb();
if (!db) throw new Error("Database not available");
@@ -611,6 +649,24 @@ export async function upsertImportSettings(data: InsertImportSettings): Promise<
}
}
/** Liste administrative des comptes e-mail configurés, sans secret IMAP ni Azure AD. */
export async function getEmailImportAccounts() {
const db = await getDb();
if (!db) return [];
return db.select({
userId: importSettings.userId,
userName: users.name,
userEmail: users.email,
emailAddress: importSettings.emailImportAddress,
authMode: importSettings.emailImportAuthMode,
automaticEnabled: importSettings.emailImportEnabled,
isConfigured: sql<number>`CASE WHEN ${importSettings.emailImportAddress} IS NOT NULL AND ${importSettings.emailImportAddress} <> '' AND ${importSettings.emailImportHost} IS NOT NULL AND ${importSettings.emailImportHost} <> '' THEN 1 ELSE 0 END`.as("isConfigured"),
})
.from(importSettings)
.innerJoin(users, eq(importSettings.userId, users.id))
.orderBy(users.name, users.email);
}
// ============= DEPARTMENT LIST OPERATIONS =============
export async function getDepartmentsByUser(_userId?: number): Promise<Department[]> {