98 lines
4.3 KiB
TypeScript
98 lines
4.3 KiB
TypeScript
import type { MasseSalarialeImportRemuneration, MasseSalarialeImportSalarie } from "./db";
|
|
|
|
type SpreadsheetValue = string | number | Date | null | undefined;
|
|
|
|
function cents(value: SpreadsheetValue): number {
|
|
if (typeof value === "number") return Math.round(value * 100);
|
|
if (typeof value !== "string") return 0;
|
|
const normalized = value.replace(/[\s\u00a0]/g, "").replace(",", ".");
|
|
const parsed = Number(normalized);
|
|
return Number.isFinite(parsed) ? Math.round(parsed * 100) : 0;
|
|
}
|
|
|
|
function bps(value: SpreadsheetValue): number | null {
|
|
if (value === null || value === undefined || value === "") return null;
|
|
const numeric = typeof value === "number" ? value : Number(String(value).replace(",", "."));
|
|
return Number.isFinite(numeric) ? Math.round(numeric * 10_000) : null;
|
|
}
|
|
|
|
function isoDate(value: SpreadsheetValue): string | null {
|
|
if (value instanceof Date && !Number.isNaN(value.valueOf())) return value.toISOString().slice(0, 10);
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
const excelDate = new Date(Date.UTC(1899, 11, 30) + Math.round(value * 86_400_000));
|
|
return Number.isNaN(excelDate.valueOf()) ? null : excelDate.toISOString().slice(0, 10);
|
|
}
|
|
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
|
if (typeof value === "string" && /^\d{2}\/\d{2}\/\d{4}$/.test(value)) {
|
|
const [day, month, year] = value.split("/");
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function text(value: SpreadsheetValue): string {
|
|
return typeof value === "string" || typeof value === "number" ? String(value).trim() : "";
|
|
}
|
|
|
|
export type MasseSalarialeWorkbookImport = {
|
|
salaries: MasseSalarialeImportSalarie[];
|
|
remunerations: MasseSalarialeImportRemuneration[];
|
|
};
|
|
|
|
/**
|
|
* Convertit les lignes de la feuille modèle (index Excel 1, en-tête sur la
|
|
* ligne 5) sans évaluer de formules côté application : les valeurs calculées
|
|
* et sauvegardées par le fichier fourni servent de référence importée.
|
|
*/
|
|
export function parseMasseSalarialeWorkbookRows(rows: SpreadsheetValue[][]): MasseSalarialeWorkbookImport {
|
|
const salaries: MasseSalarialeImportSalarie[] = [];
|
|
const remunerations: MasseSalarialeImportRemuneration[] = [];
|
|
|
|
for (const row of rows.slice(5)) {
|
|
// SheetJS supprime la cellule A vide de certaines lignes. On repère donc
|
|
// la présence effective de cette cellule plutôt que de supposer un index.
|
|
const originalColumn = (excelColumn: number) => row[excelColumn + (row[0] === null || row[0] === undefined ? 0 : -1)];
|
|
const matricule = text(originalColumn(1));
|
|
const nom = text(originalColumn(2));
|
|
const prenom = text(originalColumn(3));
|
|
const poste = text(originalColumn(4));
|
|
const dateEmbauche = isoDate(originalColumn(5));
|
|
if (!matricule || !nom || !prenom || !poste || !dateEmbauche) continue;
|
|
|
|
salaries.push({ matricule, nom, prenom, poste, dateEmbauche });
|
|
const addForecast = (annee: number, startIndex: number, tauxChargeBps: number | null, periodeReference: string) => {
|
|
const brutHorsAstreinte = cents(originalColumn(startIndex));
|
|
const brutAvecPrimes = cents(originalColumn(startIndex + 1));
|
|
const primeAstreinte = cents(originalColumn(startIndex + 2));
|
|
const brutMensuel = cents(originalColumn(startIndex + 3));
|
|
const evolution = cents(originalColumn(startIndex + 5));
|
|
const prime = annee === 2027 ? cents(originalColumn(startIndex + 6)) : 0;
|
|
if (brutHorsAstreinte === 0 && brutAvecPrimes === 0 && brutMensuel === 0) return;
|
|
|
|
remunerations.push({
|
|
matricule,
|
|
annee,
|
|
nature: "previsionnel",
|
|
salaireAnnuelBrutHorsPrimesCents: brutHorsAstreinte,
|
|
salaireAnnuelBrutAvecPrimesCents: brutAvecPrimes,
|
|
salaireMensuelBrutHorsPrimesCents: brutMensuel,
|
|
tauxChargeBps,
|
|
primeAstreinteCents: primeAstreinte,
|
|
primeCents: prime,
|
|
evolutionCents: evolution,
|
|
primeReferenceCents: prime,
|
|
evolutionReferenceCents: evolution,
|
|
periodeReference,
|
|
statut: annee === 2025 ? "annuel" : "previsionnel",
|
|
source: "fichier_modele_2026-08-28",
|
|
});
|
|
};
|
|
|
|
addForecast(2025, 6, bps(originalColumn(10)), "2025-12");
|
|
addForecast(2026, 12, bps(originalColumn(16)), "2026-08");
|
|
addForecast(2027, 18, null, "2027-01");
|
|
}
|
|
|
|
return { salaries, remunerations };
|
|
}
|