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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user