From 1951b9b9d11fe704ff2e7dfbd8cbcf5689b2cc33 Mon Sep 17 00:00:00 2001 From: Manus Date: Mon, 13 Jul 2026 08:13:49 -0400 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Les=20admins=20voient=20d=C3=A9so?= =?UTF-8?q?rmais=20toutes=20les=20factures=20(tous=20userId)=20:=20getById?= =?UTF-8?q?,=20update,=20delete,=20validateBAP,=20devalidateBAP,=20validat?= =?UTF-8?q?eBAPBulk,=20reprocessSelected,=20regenerateBapPdf,=20search,=20?= =?UTF-8?q?exportToExcel,=20exportToPdf,=20exportSFTP,=20bapHistory.delete?= =?UTF-8?q?/regenerate=20=E2=80=94=20tous=20corrig=C3=A9s=20pour=20les=20a?= =?UTF-8?q?dmins.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db.ts | 8 +++++++- server/routers.ts | 33 ++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/server/db.ts b/server/db.ts index de22243..1ce044a 100644 --- a/server/db.ts +++ b/server/db.ts @@ -268,11 +268,17 @@ export async function deleteInvoice(id: number) { await db.delete(invoices).where(eq(invoices.id, id)); } -export async function searchInvoices(userId: number, query: string): Promise { +export async function searchInvoices(userId: number | null, query: string): Promise { const db = await getDb(); if (!db) return []; const searchPattern = `%${query}%`; + if (userId === null) { + // Admin : recherche globale sans filtre userId + return db.select().from(invoices) + .where(sql`(${invoices.supplierName} LIKE ${searchPattern} OR ${invoices.invoiceNumber} LIKE ${searchPattern})`) + .orderBy(desc(invoices.createdAt)); + } return db.select().from(invoices) .where( and( diff --git a/server/routers.ts b/server/routers.ts index 3c1d00b..5bd094e 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -399,7 +399,7 @@ export const appRouter = router({ .input(z.object({ id: z.number() })) .query(async ({ input, ctx }) => { const invoice = await getInvoiceById(input.id); - if (!invoice || invoice.userId !== ctx.user.id) { + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) { throw new TRPCError({ code: "NOT_FOUND" }); } return invoice; @@ -425,7 +425,7 @@ export const appRouter = router({ })) .mutation(async ({ input, ctx }) => { const invoice = await getInvoiceById(input.id); - if (!invoice || invoice.userId !== ctx.user.id) { + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) { throw new TRPCError({ code: "NOT_FOUND" }); } @@ -441,7 +441,7 @@ export const appRouter = router({ .input(z.object({ id: z.number() })) .mutation(async ({ input, ctx }) => { const invoice = await getInvoiceById(input.id); - if (!invoice || invoice.userId !== ctx.user.id) { + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) { throw new TRPCError({ code: "NOT_FOUND" }); } @@ -453,7 +453,7 @@ export const appRouter = router({ .input(z.object({ id: z.number() })) .mutation(async ({ input, ctx }) => { const invoice = await getInvoiceById(input.id); - if (!invoice || invoice.userId !== ctx.user.id) { + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) { throw new TRPCError({ code: "NOT_FOUND" }); } // Vérifier les critères de validation BAP @@ -666,7 +666,7 @@ export const appRouter = router({ let processed = 0; for (const id of input.invoiceIds) { const invoice = await getInvoiceById(id); - if (!invoice || invoice.userId !== ctx.user.id) continue; + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) continue; await updateInvoice(id, { bapValidated: 0, bapValidatedAt: null, @@ -680,7 +680,7 @@ export const appRouter = router({ // ── Validation BAP en masse ────────────────────────────── validateBAPBulk: protectedProcedure .mutation(async ({ ctx }) => { - const allInvoices = await getInvoicesByUser(ctx.user.id); + const allInvoices = ctx.user.role === 'admin' ? await getAllInvoices() : await getInvoicesByUser(ctx.user.id); // Filtrer les factures éligibles (non déjà validées) const eligible = allInvoices.filter((inv: any) => (inv.qualityScore || 0) >= 100 && @@ -862,7 +862,7 @@ export const appRouter = router({ .mutation(async ({ input, ctx }) => { // Relance UNIQUEMENT les automatismes (sans re-extraction LLM) const { applyAutomationRules } = await import("./automationEngine"); - const allInvoices = await getInvoicesByUser(ctx.user.id); + const allInvoices = ctx.user.role === 'admin' ? await getAllInvoices() : await getInvoicesByUser(ctx.user.id); const selected = allInvoices.filter(inv => input.invoiceIds.includes(inv.id)); if (selected.length === 0) throw new TRPCError({ code: 'NOT_FOUND', message: 'Aucune facture trouvée' }); @@ -893,7 +893,7 @@ export const appRouter = router({ .input(z.object({ invoiceId: z.number() })) .mutation(async ({ input, ctx }) => { const invoice = await getInvoiceById(input.invoiceId); - if (!invoice || invoice.userId !== ctx.user.id) { + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Facture introuvable' }); } if (!invoice.bapValidated) { @@ -983,7 +983,7 @@ export const appRouter = router({ const { url } = await localStoragePut(bapKey, Buffer.from(signedPdfBytes), 'application/pdf'); // Mettre à jour la dernière entrée bapHistory de cette facture avec le nouveau pdfUrl - const allEntries = await getBapHistoryByUser(ctx.user.id); + const allEntries = ctx.user.role === 'admin' ? await getAllBapHistory() : await getBapHistoryByUser(ctx.user.id); const latestEntry = allEntries .filter(e => e.invoiceId === input.invoiceId) .sort((a, b) => new Date(b.validatedAt).getTime() - new Date(a.validatedAt).getTime())[0]; @@ -1000,6 +1000,9 @@ export const appRouter = router({ search: protectedProcedure .input(z.object({ query: z.string() })) .query(async ({ input, ctx }) => { + if (ctx.user.role === 'admin') { + return searchInvoices(null, input.query); + } return searchInvoices(ctx.user.id, input.query); }), @@ -1020,7 +1023,7 @@ export const appRouter = router({ delete: protectedProcedure .input(z.object({ id: z.number() })) .mutation(async ({ input, ctx }) => { - const entries = await getBapHistoryByUser(ctx.user.id); + const entries = ctx.user.role === 'admin' ? await getAllBapHistory() : await getBapHistoryByUser(ctx.user.id); const entry = entries.find(e => e.id === input.id); if (!entry) throw new TRPCError({ code: 'NOT_FOUND' }); await deleteBapHistoryEntry(input.id); @@ -1031,12 +1034,12 @@ export const appRouter = router({ regenerate: protectedProcedure .input(z.object({ id: z.number() })) .mutation(async ({ input, ctx }) => { - const entries = await getBapHistoryByUser(ctx.user.id); + const entries = ctx.user.role === 'admin' ? await getAllBapHistory() : await getBapHistoryByUser(ctx.user.id); const entry = entries.find(e => e.id === input.id); if (!entry) throw new TRPCError({ code: 'NOT_FOUND' }); const invoice = await getInvoiceById(entry.invoiceId); - if (!invoice || invoice.userId !== ctx.user.id) { + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Facture source introuvable' }); } @@ -1250,7 +1253,7 @@ export const appRouter = router({ ); const validInvoices = invoices.filter( - inv => inv && inv.userId === ctx.user.id + inv => inv && (ctx.user.role === 'admin' || inv.userId === ctx.user.id) ); // Return invoice data for Excel generation on client side @@ -1301,7 +1304,7 @@ export const appRouter = router({ ); const invalidInvoices = invoices.filter( - inv => !inv || inv.userId !== ctx.user.id || (inv.qualityScore || 0) < 100 + inv => !inv || (ctx.user.role !== 'admin' && inv.userId !== ctx.user.id) || (inv.qualityScore || 0) < 100 ); if (invalidInvoices.length > 0) { @@ -1560,7 +1563,7 @@ export const appRouter = router({ for (const invoiceId of input.invoiceIds) { try { const invoice = await getInvoiceById(invoiceId); - if (!invoice || invoice.userId !== ctx.user.id) { + if (!invoice || (ctx.user.role !== 'admin' && invoice.userId !== ctx.user.id)) { errorCount++; continue; }