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:
@@ -71,6 +71,9 @@ vi.mock("./db", async (importOriginal) => {
|
||||
upsertOpexBaseRepartition: vi.fn(),
|
||||
setOpexMontantsEtabBatch: vi.fn(),
|
||||
deleteOpexPoste: vi.fn(),
|
||||
getMasseSalariale: vi.fn(),
|
||||
importMasseSalariale: vi.fn(),
|
||||
setMasseSalarialeEvolution: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -342,3 +345,92 @@ describe("opex.deletePoste", () => {
|
||||
expect(db.deleteOpexPoste).toHaveBeenCalledWith(2027, 42);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Tests Masse salariale (données sensibles, administrateur uniquement) ─────
|
||||
|
||||
describe("masseSalariale", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("retourne les données uniquement pour un administrateur", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const source = { salaries: [], remunerations: [], evolutions: [] };
|
||||
(db.getMasseSalariale as ReturnType<typeof vi.fn>).mockResolvedValue(source);
|
||||
const { ctx } = createAuthCtx({ role: "admin" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.get()).resolves.toEqual(source);
|
||||
});
|
||||
|
||||
it("interdit la consultation à un profil standard", async () => {
|
||||
const { ctx } = createAuthCtx({ role: "standard" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.get()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("importe les données validées uniquement pour un administrateur", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
(db.importMasseSalariale as ReturnType<typeof vi.fn>).mockResolvedValue({ salaries: 1, remunerations: 1 });
|
||||
const { ctx } = createAuthCtx({ role: "admin" });
|
||||
const salaries = [{
|
||||
matricule: "000001",
|
||||
nom: "DUPONT",
|
||||
prenom: "Alice",
|
||||
poste: "Responsable administratif",
|
||||
dateEmbauche: "2024-01-01",
|
||||
}];
|
||||
const remunerations = [{
|
||||
matricule: "000001",
|
||||
annee: 2026,
|
||||
salaireAnnuelBrutHorsPrimesCents: 36_000_00,
|
||||
salaireAnnuelBrutAvecPrimesCents: 40_000_00,
|
||||
salaireMensuelBrutHorsPrimesCents: 3_000_00,
|
||||
tauxChargeBps: 5_500,
|
||||
periodeReference: "2026-08",
|
||||
statut: "cumul_provisoire" as const,
|
||||
source: "bulletin",
|
||||
}];
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.import({ salaries, remunerations }))
|
||||
.resolves.toEqual({ salaries: 1, remunerations: 1 });
|
||||
expect(db.importMasseSalariale).toHaveBeenCalledWith(salaries, remunerations);
|
||||
});
|
||||
|
||||
it("interdit l'import de rémunérations à un profil standard", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ role: "standard" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.import({ salaries: [], remunerations: [] })).rejects.toThrow();
|
||||
expect(db.importMasseSalariale).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("enregistre l'évolution manuelle avec l'identifiant de l'administrateur", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ id: 42, role: "admin" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.setEvolution({
|
||||
salarieId: 7,
|
||||
annee: 2027,
|
||||
evolutionSalarialeBps: 250,
|
||||
})).resolves.toEqual({ success: true });
|
||||
|
||||
expect(db.setMasseSalarialeEvolution).toHaveBeenCalledWith({
|
||||
salarieId: 7,
|
||||
annee: 2027,
|
||||
evolutionSalarialeBps: 250,
|
||||
createdBy: 42,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejette un taux d'évolution hors plage avant l'accès à la base", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ role: "admin" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.setEvolution({
|
||||
salarieId: 7,
|
||||
annee: 2027,
|
||||
evolutionSalarialeBps: 10_001,
|
||||
})).rejects.toThrow();
|
||||
expect(db.setMasseSalarialeEvolution).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user