113 lines
4.5 KiB
TypeScript
113 lines
4.5 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|