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:
112
server/masseSalarialeProjection.ts
Normal file
112
server/masseSalarialeProjection.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Calculs purs de la fenêtre Masse salariale. Les montants sont conservés en
|
||||
* centimes afin que les projections et cumuls ne subissent aucun arrondi
|
||||
* intermédiaire. Les réels ne sont jamais extrapolés : ils cumulent seulement
|
||||
* les liasses mensuelles effectivement archivées.
|
||||
*/
|
||||
|
||||
export type AnnualForecastInput = {
|
||||
annee: number;
|
||||
brutHorsAstreinteReferenceCents: number;
|
||||
brutAvecPrimesReferenceCents: number;
|
||||
primeAstreinteCents: number;
|
||||
primeCents: number;
|
||||
primeReferenceCents: number;
|
||||
evolutionCents: number;
|
||||
evolutionReferenceCents: number;
|
||||
tauxChargeBps: number | null;
|
||||
};
|
||||
|
||||
export type AnnualProjection = {
|
||||
brutHorsAstreinteCents: number;
|
||||
brutAvecPrimesCents: number;
|
||||
brutMensuelHorsAstreinteCents: number;
|
||||
primeAstreinteCents: number;
|
||||
primeComplementaireCents: number;
|
||||
evolutionCents: number;
|
||||
tauxChargeBps: number | null;
|
||||
};
|
||||
|
||||
export type PayrollMonthlyInput = {
|
||||
annee: number;
|
||||
mois: number;
|
||||
matricule: string;
|
||||
brutMensuelCents: number;
|
||||
brutAvecPrimesCents: number;
|
||||
primeAstreinteCents: number;
|
||||
};
|
||||
|
||||
export type PayrollAnnualActual = Omit<AnnualProjection, "evolutionCents" | "tauxChargeBps"> & {
|
||||
moisDisponibles: number[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Restitue exactement la référence importée, puis applique seulement les
|
||||
* écarts manuels des primes et évolutions. Ainsi, un import Excel conserve ses
|
||||
* calculs initiaux tandis que toute correction autorisée est automatiquement
|
||||
* répercutée sur le brut prévisionnel dérivé.
|
||||
*/
|
||||
export function buildAnnualForecasts(inputs: AnnualForecastInput[]): Map<number, AnnualProjection> {
|
||||
const forecasts = new Map<number, AnnualProjection>();
|
||||
for (const current of [...inputs].sort((a, b) => a.annee - b.annee)) {
|
||||
const adjustmentDelta =
|
||||
(current.primeCents - current.primeReferenceCents)
|
||||
+ (current.evolutionCents - current.evolutionReferenceCents);
|
||||
const brutHorsAstreinteCents = current.brutHorsAstreinteReferenceCents + adjustmentDelta;
|
||||
const brutAvecPrimesCents = current.brutAvecPrimesReferenceCents + adjustmentDelta;
|
||||
forecasts.set(current.annee, {
|
||||
brutHorsAstreinteCents,
|
||||
brutAvecPrimesCents,
|
||||
brutMensuelHorsAstreinteCents: Math.round(brutHorsAstreinteCents / 12),
|
||||
primeAstreinteCents: current.primeAstreinteCents,
|
||||
primeComplementaireCents: current.primeCents,
|
||||
evolutionCents: current.evolutionCents,
|
||||
tauxChargeBps: current.tauxChargeBps,
|
||||
});
|
||||
}
|
||||
return forecasts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrège les bulletins archivés par salarié et année. Le brut hors astreinte
|
||||
* ne retire que les primes d'astreinte : les autres éléments compris dans le
|
||||
* brut (activité, responsabilité, vacances…) sont donc préservés.
|
||||
*/
|
||||
export function buildAnnualPayrollActuals(inputs: PayrollMonthlyInput[]): Map<string, Map<number, PayrollAnnualActual>> {
|
||||
const grouped = new Map<string, Map<number, PayrollMonthlyInput[]>>();
|
||||
for (const input of inputs) {
|
||||
const byYear = grouped.get(input.matricule) ?? new Map<number, PayrollMonthlyInput[]>();
|
||||
byYear.set(input.annee, [...(byYear.get(input.annee) ?? []), input]);
|
||||
grouped.set(input.matricule, byYear);
|
||||
}
|
||||
|
||||
const actuals = new Map<string, Map<number, PayrollAnnualActual>>();
|
||||
for (const [matricule, byYear] of Array.from(grouped.entries())) {
|
||||
const projections = new Map<number, PayrollAnnualActual>();
|
||||
for (const [annee, monthlyRows] of Array.from(byYear.entries())) {
|
||||
const brutAvecPrimesCents = monthlyRows.reduce((sum, row) => sum + row.brutAvecPrimesCents, 0);
|
||||
const primeAstreinteCents = monthlyRows.reduce((sum, row) => sum + row.primeAstreinteCents, 0);
|
||||
const brutHorsAstreinteCents = brutAvecPrimesCents - primeAstreinteCents;
|
||||
const primeComplementaireCents = monthlyRows.reduce(
|
||||
(sum, row) => sum + (row.brutAvecPrimesCents - row.brutMensuelCents - row.primeAstreinteCents),
|
||||
0,
|
||||
);
|
||||
const moisDisponibles = Array.from(new Set<number>(monthlyRows.map((row) => row.mois))).sort((a, b) => a - b);
|
||||
projections.set(annee, {
|
||||
brutHorsAstreinteCents,
|
||||
brutAvecPrimesCents,
|
||||
brutMensuelHorsAstreinteCents: Math.round(brutHorsAstreinteCents / moisDisponibles.length),
|
||||
primeAstreinteCents,
|
||||
primeComplementaireCents,
|
||||
moisDisponibles,
|
||||
});
|
||||
}
|
||||
actuals.set(matricule, projections);
|
||||
}
|
||||
return actuals;
|
||||
}
|
||||
|
||||
/** Les ajustements de prévision sont bloqués sur les exercices clôturés. */
|
||||
export function isForecastAdjustmentEditable(annee: number, currentYear: number): boolean {
|
||||
return annee >= currentYear;
|
||||
}
|
||||
Reference in New Issue
Block a user