Checkpoint: Module Ventilation FreePro complet : import Excel FreePro, transformation automatique (script/TTC/type XOAUTH2), tableau de ventilation par structure+type, historique par mois, export PDF. Menu Ventilations ajouté dans la navigation.

This commit is contained in:
Manus
2026-06-05 08:02:29 -04:00
parent 4ef130f21d
commit 7f56fa7045
13 changed files with 3123 additions and 3 deletions

View File

@@ -85,6 +85,13 @@ import { drawBapCartouche } from "./bapCartouche";
import { startEmailImportService, stopEmailImportService, isEmailImportServiceRunning, triggerEmailCheck, testImapConnection } from "./emailImportService";
import { startFolderImportService, stopFolderImportService, isFolderImportServiceRunning } from "./folderImportService";
import { TRPCError } from "@trpc/server";
import { processFreeproExcel } from "./freeproService";
import {
createFreeproImport,
getFreeproImportsByUser,
getFreeproImportWithLines,
deleteFreeproImport,
} from "./db";
// Admin-only procedure
const adminProcedure = protectedProcedure.use(({ ctx, next }) => {
@@ -2197,5 +2204,70 @@ export const appRouter = router({
return { success: true };
}),
}),
// ============= FREEPRO VENTILATION =============
freepro: router({
/** Importe un fichier Excel FreePro et calcule la ventilation */
import: protectedProcedure
.input(
z.object({
moisLabel: z.string().regex(/^\d{2}\/\d{4}$/, "Format MM/YYYY requis"),
fileName: z.string(),
fileBase64: z.string(), // fichier Excel encodé en base64
})
)
.mutation(async ({ input, ctx }) => {
const buffer = Buffer.from(input.fileBase64, "base64");
const result = processFreeproExcel(buffer, input.moisLabel);
const importId = await createFreeproImport(
{
userId: ctx.user.id,
moisLabel: result.moisLabel,
annee: result.annee,
mois: result.mois,
refPiece: result.refPiece || null,
fileName: input.fileName,
nbLignes: result.nbLignes,
totalTtc: result.totalTtc.toFixed(2),
},
result.lines.map((l) => ({
structure: l.structure ?? null,
type: l.type,
montantCentimes: Math.round(l.montant * 100),
}))
);
return { importId, ...result };
}),
/** Liste tous les imports FreePro de l'utilisateur */
list: protectedProcedure.query(async ({ ctx }) => {
return getFreeproImportsByUser(ctx.user.id);
}),
/** Récupère un import FreePro avec ses lignes de ventilation */
getById: protectedProcedure
.input(z.object({ id: z.number() }))
.query(async ({ input, ctx }) => {
const data = await getFreeproImportWithLines(input.id);
if (!data) throw new TRPCError({ code: "NOT_FOUND" });
if (data.import.userId !== ctx.user.id)
throw new TRPCError({ code: "FORBIDDEN" });
return data;
}),
/** Supprime un import FreePro */
delete: protectedProcedure
.input(z.object({ id: z.number() }))
.mutation(async ({ input, ctx }) => {
const data = await getFreeproImportWithLines(input.id);
if (!data) throw new TRPCError({ code: "NOT_FOUND" });
if (data.import.userId !== ctx.user.id)
throw new TRPCError({ code: "FORBIDDEN" });
await deleteFreeproImport(input.id);
return { success: true };
}),
}),
});
export type AppRouter = typeof appRouter;