Checkpoint: Ajout du module Masse salariale SANTINOVA : tables de salariés, rémunérations et évolutions manuelles sécurisées par rôle administrateur ; import contrôlé des bulletins 2025 et janvier–août 2026 ; écran tableau 2025–2027 ; tests d'autorisations, d'agrégats et des quatre états UI ; documentation des sources et de la politique d'import.

This commit is contained in:
Manus
2026-08-27 13:10:47 +00:00
parent 2f4b1f2e8e
commit da407db4de
21 changed files with 4390 additions and 4 deletions

View File

@@ -248,6 +248,47 @@ export const appRouter = router({
}),
}),
// Les données de rémunération sont sensibles : aucune procédure de ce
// sous-routeur n'est accessible aux profils standard ou lecture seule.
masseSalariale: router({
get: adminProcedure.query(async () => db.getMasseSalariale()),
import: adminProcedure
.input(z.object({
salaries: z.array(z.object({
matricule: z.string().trim().min(1).max(32),
nom: z.string().trim().min(1).max(150),
prenom: z.string().trim().min(1).max(150),
poste: z.string().trim().min(1).max(255),
dateEmbauche: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date d'embauche invalide"),
})).min(1).max(500),
remunerations: z.array(z.object({
matricule: z.string().trim().min(1).max(32),
annee: z.number().int().min(2000).max(2100),
salaireAnnuelBrutHorsPrimesCents: z.number().int().min(0).max(100_000_000),
salaireAnnuelBrutAvecPrimesCents: z.number().int().min(0).max(100_000_000),
salaireMensuelBrutHorsPrimesCents: z.number().int().min(0).max(10_000_000),
tauxChargeBps: z.number().int().min(0).max(100_000),
periodeReference: z.string().regex(/^\d{4}-\d{2}$/).nullable().optional(),
statut: z.enum(["annuel", "cumul_provisoire"]),
source: z.string().trim().min(1).max(64),
})).max(1_500),
}))
.mutation(async ({ input }) => db.importMasseSalariale(input.salaries, input.remunerations)),
setEvolution: adminProcedure
.input(z.object({
salarieId: z.number().int().positive(),
annee: z.number().int().min(2000).max(2100),
// ±100 % maximum : protège contre les erreurs de virgule dans la saisie.
evolutionSalarialeBps: z.number().int().min(-10_000).max(10_000),
}))
.mutation(async ({ input, ctx }) => {
await db.setMasseSalarialeEvolution({ ...input, createdBy: ctx.user.id });
return { success: true };
}),
}),
inventaire: router({
get: protectedProcedure
.input(z.object({ annee: z.number() }))