Checkpoint: Ajout de la projection annuelle prévisionnel-réel dans Masse salariale : import contrôlé du fichier modèle 2025-2027, réel 2026 provenant uniquement des liasses Salaires, ajustements administrateur limités aux primes et évolutions de l'année courante/future, conservation des centimes, modèle de calcul testé et documentation. 72 tests, TypeScript et build validés.
This commit is contained in:
@@ -74,6 +74,7 @@ vi.mock("./db", async (importOriginal) => {
|
||||
getMasseSalariale: vi.fn(),
|
||||
importMasseSalariale: vi.fn(),
|
||||
setMasseSalarialeEvolution: vi.fn(),
|
||||
setMasseSalarialeForecastAdjustments: vi.fn(),
|
||||
getSalairesMensuels: vi.fn(),
|
||||
};
|
||||
});
|
||||
@@ -356,11 +357,16 @@ describe("masseSalariale", () => {
|
||||
|
||||
it("retourne les données uniquement pour un administrateur", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const source = { salaries: [], remunerations: [], evolutions: [] };
|
||||
const source = { salaries: [], remunerations: [], evolutions: [], bulletinsMensuels: [] };
|
||||
(db.getMasseSalariale as ReturnType<typeof vi.fn>).mockResolvedValue(source);
|
||||
const { ctx } = createAuthCtx({ role: "admin" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.get()).resolves.toEqual(source);
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.get()).resolves.toEqual({
|
||||
salaries: [],
|
||||
forecasts: [],
|
||||
actuals: [],
|
||||
currentYear: new Date().getFullYear(),
|
||||
});
|
||||
});
|
||||
|
||||
it("interdit la consultation à un profil standard", async () => {
|
||||
@@ -369,6 +375,43 @@ describe("masseSalariale", () => {
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.get()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("construit le réel annuel uniquement depuis les bulletins mensuels archivés", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
(db.getMasseSalariale as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
salaries: [{ id: 7, matricule: "000001", nom: "NOM", prenom: "PRENOM", poste: "Poste", dateEmbauche: "2024-01-01" }],
|
||||
remunerations: [{
|
||||
salarieId: 7,
|
||||
annee: 2026,
|
||||
salaireAnnuelBrutHorsPrimesCents: 1_000_000,
|
||||
salaireAnnuelBrutAvecPrimesCents: 1_200_000,
|
||||
salaireMensuelBrutHorsPrimesCents: 83_333,
|
||||
tauxChargeBps: 4_500,
|
||||
primeAstreinteCents: 200_000,
|
||||
primeCents: 0,
|
||||
primeReferenceCents: 0,
|
||||
evolutionCents: 0,
|
||||
evolutionReferenceCents: 0,
|
||||
}],
|
||||
evolutions: [],
|
||||
bulletinsMensuels: [
|
||||
{ annee: 2026, mois: 1, matricule: "000001", brutMensuelCents: 100_000, brutAvecPrimesCents: 120_000, primeAstreinteCents: 20_000 },
|
||||
{ annee: 2026, mois: 2, matricule: "000001", brutMensuelCents: 100_000, brutAvecPrimesCents: 110_000, primeAstreinteCents: 0 },
|
||||
],
|
||||
});
|
||||
const { ctx } = createAuthCtx({ role: "admin" });
|
||||
|
||||
const result = await appRouter.createCaller(ctx).masseSalariale.get();
|
||||
|
||||
expect(result.actuals).toEqual([expect.objectContaining({
|
||||
salarieId: 7,
|
||||
annee: 2026,
|
||||
brutAvecPrimesCents: 230_000,
|
||||
brutHorsAstreinteCents: 210_000,
|
||||
moisDisponibles: [1, 2],
|
||||
origine: "liasses",
|
||||
})]);
|
||||
});
|
||||
|
||||
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 });
|
||||
@@ -394,7 +437,15 @@ describe("masseSalariale", () => {
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.import({ salaries, remunerations }))
|
||||
.resolves.toEqual({ salaries: 1, remunerations: 1 });
|
||||
expect(db.importMasseSalariale).toHaveBeenCalledWith(salaries, remunerations);
|
||||
expect(db.importMasseSalariale).toHaveBeenCalledWith(salaries, [{
|
||||
...remunerations[0],
|
||||
nature: "previsionnel",
|
||||
primeAstreinteCents: 0,
|
||||
primeCents: 0,
|
||||
evolutionCents: 0,
|
||||
primeReferenceCents: 0,
|
||||
evolutionReferenceCents: 0,
|
||||
}]);
|
||||
});
|
||||
|
||||
it("interdit l'import de rémunérations à un profil standard", async () => {
|
||||
@@ -434,6 +485,52 @@ describe("masseSalariale", () => {
|
||||
})).rejects.toThrow();
|
||||
expect(db.setMasseSalarialeEvolution).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("autorise un administrateur à modifier uniquement les primes et évolutions de l'exercice courant", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ role: "admin" });
|
||||
const anneeCourante = new Date().getFullYear();
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.setForecastAdjustments({
|
||||
salarieId: 7,
|
||||
annee: anneeCourante,
|
||||
primeCents: 12_500,
|
||||
evolutionCents: 20_000,
|
||||
})).resolves.toEqual({ success: true });
|
||||
|
||||
expect(db.setMasseSalarialeForecastAdjustments).toHaveBeenCalledWith({
|
||||
salarieId: 7,
|
||||
annee: anneeCourante,
|
||||
primeCents: 12_500,
|
||||
evolutionCents: 20_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("interdit l'édition prévisionnelle d'un exercice antérieur", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ role: "admin" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.setForecastAdjustments({
|
||||
salarieId: 7,
|
||||
annee: new Date().getFullYear() - 1,
|
||||
primeCents: 12_500,
|
||||
evolutionCents: 20_000,
|
||||
})).rejects.toThrow();
|
||||
expect(db.setMasseSalarialeForecastAdjustments).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("interdit l'édition prévisionnelle à un profil standard", async () => {
|
||||
const { db } = await import("./db").then(m => ({ db: m }));
|
||||
const { ctx } = createAuthCtx({ role: "standard" });
|
||||
|
||||
await expect(appRouter.createCaller(ctx).masseSalariale.setForecastAdjustments({
|
||||
salarieId: 7,
|
||||
annee: new Date().getFullYear(),
|
||||
primeCents: 0,
|
||||
evolutionCents: 0,
|
||||
})).rejects.toThrow();
|
||||
expect(db.setMasseSalarialeForecastAdjustments).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Tests Salaires : sonde de chiffrement et confidentialité ─────────────────
|
||||
|
||||
Reference in New Issue
Block a user