Checkpoint: Fenêtre administrateur Salaires finalisée : import multipart limité, chiffrement AES-256-GCM avant stockage persistant hors conteneur, métadonnées/index métier minimisés en base, consultation PDF déchiffrée côté serveur avec no-store, filtres année/mois et comparaison M-1 expliquée. Réindexation sûre des archives partielles sans écrasement du PDF. Validation réelle sur deux liasses mensuelles : index partiel signalé sans invention de données, comparaison affichée et lecture après redémarrage. 55 tests Vitest, TypeScript et build de production validés.
Some checks failed
Validation applicative / TypeScript, tests et build (push) Failing after 1m54s
Some checks failed
Validation applicative / TypeScript, tests et build (push) Failing after 1m54s
This commit is contained in:
80
server/payrollParser.test.ts
Normal file
80
server/payrollParser.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { extractPayrollBulletins } from "./payrollParser";
|
||||
|
||||
const SAMPLE_BULLETIN = `
|
||||
##BULLETIN##05-2026##009999##DUPONT##TEST##
|
||||
Matricule : 009999
|
||||
Emploi : RESPONSABLE APPLICATIF Monsieur TEST DUPONT
|
||||
Salaire de base (Forfait 218 jours) 3 650.00
|
||||
Prime d'astreinte 200.00
|
||||
Prime d'astreinte 200.00
|
||||
Prime d'activité 150.00
|
||||
Salaire brut 4 200.00
|
||||
N° SS : ne-pas-extraire
|
||||
Iban / Rib : ne-pas-extraire
|
||||
Net à payer avant impôt sur le revenu 2 000.00
|
||||
`;
|
||||
|
||||
describe("extractPayrollBulletins", () => {
|
||||
it("n'extrait que les champs métier autorisés et additionne les astreintes", () => {
|
||||
const result = extractPayrollBulletins(SAMPLE_BULLETIN);
|
||||
|
||||
expect(result).toMatchObject({ statutExtraction: "ready", erreurExtraction: null });
|
||||
expect(result.bulletins).toEqual([
|
||||
expect.objectContaining({
|
||||
matricule: "009999",
|
||||
nom: "DUPONT",
|
||||
prenom: "TEST",
|
||||
poste: "RESPONSABLE APPLICATIF",
|
||||
brutMensuelCents: 365000,
|
||||
brutAvecPrimesCents: 420000,
|
||||
primeAstreinteCents: 40000,
|
||||
}),
|
||||
]);
|
||||
expect(Object.keys(result.bulletins[0]!)).not.toContain("iban");
|
||||
expect(JSON.stringify(result)).not.toContain("ne-pas-extraire");
|
||||
});
|
||||
|
||||
it("signale une liasse non indexable sans prétendre perdre le PDF", () => {
|
||||
expect(extractPayrollBulletins("contenu PDF non reconnu")).toEqual({
|
||||
bulletins: [],
|
||||
statutExtraction: "a_controler",
|
||||
erreurExtraction: "Aucun bulletin n’a pu être indexé automatiquement. Le PDF reste conservé et consultable.",
|
||||
});
|
||||
});
|
||||
|
||||
it("indexe chaque salarié lorsque le texte PDF concatène les pages sans séparateur", () => {
|
||||
const secondBulletin = SAMPLE_BULLETIN
|
||||
.replace("##009999##DUPONT##TEST##", "##008888##MARTIN##EXEMPLE##")
|
||||
.replaceAll("009999", "008888")
|
||||
.replaceAll("TEST DUPONT", "EXEMPLE MARTIN");
|
||||
|
||||
const result = extractPayrollBulletins(`${SAMPLE_BULLETIN}\n${secondBulletin}`);
|
||||
expect(result.bulletins).toHaveLength(2);
|
||||
expect(result.bulletins.map((bulletin) => bulletin.matricule)).toEqual(["009999", "008888"]);
|
||||
});
|
||||
|
||||
it("accepte un emploi suivi de la civilité par un seul espace, comme certains PDF extraits", () => {
|
||||
const compacted = SAMPLE_BULLETIN.replace(" Monsieur", " Monsieur");
|
||||
|
||||
expect(extractPayrollBulletins(compacted).bulletins).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("lit un poste sur sa seule ligne lorsque la civilité est rendue à la ligne suivante", () => {
|
||||
const separateCivilite = SAMPLE_BULLETIN.replace(" Monsieur TEST DUPONT", "\nMonsieur TEST DUPONT");
|
||||
|
||||
expect(extractPayrollBulletins(separateCivilite).bulletins).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("refuse une extraction d'emploi répartie sur plusieurs lignes pour ne jamais persister de donnée personnelle", () => {
|
||||
const unsafeLayout = SAMPLE_BULLETIN.replace(
|
||||
/Emploi\s*:[^\n]*/,
|
||||
"Emploi :\nAdresse personnelle à ignorer\nRESPONSABLE APPLICATIF\nMonsieur TEST DUPONT",
|
||||
);
|
||||
|
||||
expect(extractPayrollBulletins(unsafeLayout)).toMatchObject({
|
||||
bulletins: [],
|
||||
statutExtraction: "a_controler",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user