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:
Manus
2026-08-31 10:58:02 +00:00
parent 5c9618b204
commit 28570234d0
17 changed files with 2415 additions and 204 deletions

View File

@@ -1,4 +1,4 @@
import { and, asc, desc, eq } from "drizzle-orm";
import { and, asc, desc, eq, inArray } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import {
capexLignes,
@@ -478,12 +478,20 @@ export type MasseSalarialeImportRemuneration = Omit<
export async function getMasseSalariale() {
const db = await getDb();
if (!db) throw new Error("Database not available");
const [salaries, remunerations, evolutions] = await Promise.all([
const [salaries, remunerations, evolutions, bulletinsMensuels] = await Promise.all([
db.select().from(masseSalarialeSalaries).orderBy(asc(masseSalarialeSalaries.nom), asc(masseSalarialeSalaries.prenom)),
db.select().from(masseSalarialeRemunerations),
db.select().from(masseSalarialeEvolutions),
db.select({
annee: salairesLiasses.annee,
mois: salairesLiasses.mois,
matricule: salairesBulletins.matricule,
brutMensuelCents: salairesBulletins.brutMensuelCents,
brutAvecPrimesCents: salairesBulletins.brutAvecPrimesCents,
primeAstreinteCents: salairesBulletins.primeAstreinteCents,
}).from(salairesBulletins).innerJoin(salairesLiasses, eq(salairesBulletins.liasseId, salairesLiasses.id)),
]);
return { salaries, remunerations, evolutions };
return { salaries, remunerations, evolutions, bulletinsMensuels };
}
/**
@@ -494,6 +502,7 @@ export async function getMasseSalariale() {
export async function importMasseSalariale(
salaries: MasseSalarialeImportSalarie[],
remunerations: MasseSalarialeImportRemuneration[],
options: { replaceForecastYears?: number[] } = {},
) {
const db = await getDb();
if (!db) throw new Error("Database not available");
@@ -520,6 +529,13 @@ export async function importMasseSalariale(
.from(masseSalarialeSalaries);
const idsByMatricule = new Map(persistedSalaries.map((row) => [row.matricule, row.id]));
if (options.replaceForecastYears?.length) {
await tx.delete(masseSalarialeRemunerations).where(and(
inArray(masseSalarialeRemunerations.annee, options.replaceForecastYears),
eq(masseSalarialeRemunerations.nature, "previsionnel"),
));
}
for (const remuneration of remunerations) {
const salarieId = idsByMatricule.get(remuneration.matricule);
if (!salarieId) {
@@ -535,6 +551,12 @@ export async function importMasseSalariale(
salaireAnnuelBrutAvecPrimesCents: values.salaireAnnuelBrutAvecPrimesCents,
salaireMensuelBrutHorsPrimesCents: values.salaireMensuelBrutHorsPrimesCents,
tauxChargeBps: values.tauxChargeBps,
nature: values.nature,
primeAstreinteCents: values.primeAstreinteCents,
primeCents: values.primeCents,
evolutionCents: values.evolutionCents,
primeReferenceCents: values.primeReferenceCents,
evolutionReferenceCents: values.evolutionReferenceCents,
periodeReference: values.periodeReference,
statut: values.statut,
source: values.source,
@@ -545,6 +567,25 @@ export async function importMasseSalariale(
});
}
/** Met à jour uniquement les deux hypothèses qui peuvent être modifiées. */
export async function setMasseSalarialeForecastAdjustments(input: {
salarieId: number;
annee: number;
primeCents: number;
evolutionCents: number;
}) {
const db = await getDb();
if (!db) throw new Error("Database not available");
await db.update(masseSalarialeRemunerations).set({
primeCents: input.primeCents,
evolutionCents: input.evolutionCents,
}).where(and(
eq(masseSalarialeRemunerations.salarieId, input.salarieId),
eq(masseSalarialeRemunerations.annee, input.annee),
eq(masseSalarialeRemunerations.nature, "previsionnel"),
));
}
/** Sauvegarde l'évolution manuelle sans dépendre de l'existence d'un bulletin. */
export async function setMasseSalarialeEvolution(
input: Pick<InsertMasseSalarialeEvolution, "salarieId" | "annee" | "evolutionSalarialeBps" | "createdBy">,