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:
36
drizzle/0008_living_silver_surfer.sql
Normal file
36
drizzle/0008_living_silver_surfer.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE `salaires_bulletins` (
|
||||
`id` int AUTO_INCREMENT NOT NULL,
|
||||
`liasseId` int NOT NULL,
|
||||
`matricule` varchar(32) NOT NULL,
|
||||
`nom` varchar(150) NOT NULL,
|
||||
`prenom` varchar(150) NOT NULL,
|
||||
`poste` varchar(255) NOT NULL,
|
||||
`brutMensuelCents` int NOT NULL,
|
||||
`brutAvecPrimesCents` int NOT NULL,
|
||||
`primeAstreinteCents` int NOT NULL DEFAULT 0,
|
||||
`explicationEcart` text,
|
||||
`numeroPage` int,
|
||||
`createdAt` timestamp NOT NULL DEFAULT (now()),
|
||||
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT `salaires_bulletins_id` PRIMARY KEY(`id`),
|
||||
CONSTRAINT `salaires_bulletins_liasse_matricule_idx` UNIQUE(`liasseId`,`matricule`)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `salaires_liasses` (
|
||||
`id` int AUTO_INCREMENT NOT NULL,
|
||||
`annee` int NOT NULL,
|
||||
`mois` int NOT NULL,
|
||||
`nomFichier` varchar(255) NOT NULL,
|
||||
`stockageKey` varchar(512) NOT NULL,
|
||||
`empreinteSha256` varchar(64) NOT NULL,
|
||||
`tailleOctets` int NOT NULL,
|
||||
`ivBase64` varchar(32) NOT NULL,
|
||||
`authTagBase64` varchar(32) NOT NULL,
|
||||
`versionCle` int NOT NULL DEFAULT 1,
|
||||
`importePar` int NOT NULL,
|
||||
`importeLe` timestamp NOT NULL DEFAULT (now()),
|
||||
CONSTRAINT `salaires_liasses_id` PRIMARY KEY(`id`),
|
||||
CONSTRAINT `salaires_liasses_stockageKey_unique` UNIQUE(`stockageKey`),
|
||||
CONSTRAINT `salaires_liasses_empreinteSha256_unique` UNIQUE(`empreinteSha256`),
|
||||
CONSTRAINT `salaires_liasses_periode_empreinte_idx` UNIQUE(`annee`,`mois`,`empreinteSha256`)
|
||||
);
|
||||
1
drizzle/0009_white_stardust.sql
Normal file
1
drizzle/0009_white_stardust.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `salaires_liasses` ADD CONSTRAINT `salaires_liasses_periode_idx` UNIQUE(`annee`,`mois`);
|
||||
2
drizzle/0010_brainy_oracle.sql
Normal file
2
drizzle/0010_brainy_oracle.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `salaires_liasses` ADD `statutExtraction` varchar(32) DEFAULT 'ready' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `salaires_liasses` ADD `erreurExtraction` text;
|
||||
1449
drizzle/meta/0008_snapshot.json
Normal file
1449
drizzle/meta/0008_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1457
drizzle/meta/0009_snapshot.json
Normal file
1457
drizzle/meta/0009_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1472
drizzle/meta/0010_snapshot.json
Normal file
1472
drizzle/meta/0010_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,27 @@
|
||||
"when": 1787833910943,
|
||||
"tag": "0007_rich_tony_stark",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "5",
|
||||
"when": 1788040817404,
|
||||
"tag": "0008_living_silver_surfer",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "5",
|
||||
"when": 1788041511204,
|
||||
"tag": "0009_white_stardust",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "5",
|
||||
"when": 1788041740554,
|
||||
"tag": "0010_brainy_oracle",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -265,3 +265,64 @@ export const masseSalarialeEvolutions = mysqlTable("masse_salariale_evolutions",
|
||||
|
||||
export type MasseSalarialeEvolution = typeof masseSalarialeEvolutions.$inferSelect;
|
||||
export type InsertMasseSalarialeEvolution = typeof masseSalarialeEvolutions.$inferInsert;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// SALAIRES MENSUELS — Liasses PDF chiffrées et données extraites
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Une liasse représente un dépôt mensuel. Le fichier est chiffré avant envoi
|
||||
* vers le stockage persistant ; la base ne conserve ni octet de PDF ni clé.
|
||||
*/
|
||||
export const salairesLiasses = mysqlTable("salaires_liasses", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
annee: int("annee").notNull(),
|
||||
mois: int("mois").notNull(),
|
||||
nomFichier: varchar("nomFichier", { length: 255 }).notNull(),
|
||||
stockageKey: varchar("stockageKey", { length: 512 }).notNull().unique(),
|
||||
empreinteSha256: varchar("empreinteSha256", { length: 64 }).notNull().unique(),
|
||||
tailleOctets: int("tailleOctets").notNull(),
|
||||
/** Paramètres AES-256-GCM nécessaires au déchiffrement côté serveur. */
|
||||
ivBase64: varchar("ivBase64", { length: 32 }).notNull(),
|
||||
authTagBase64: varchar("authTagBase64", { length: 32 }).notNull(),
|
||||
versionCle: int("versionCle").notNull().default(1),
|
||||
/** `ready`, `partiel` ou `a_controler` : une liasse est conservée même si l'extraction est incomplète. */
|
||||
statutExtraction: varchar("statutExtraction", { length: 32 }).notNull().default("ready"),
|
||||
erreurExtraction: text("erreurExtraction"),
|
||||
importePar: int("importePar").notNull(),
|
||||
importeLe: timestamp("importeLe").defaultNow().notNull(),
|
||||
}, (t) => ({
|
||||
/** Une liasse unique par mois : toutes les fiches mensuelles restent ainsi cohérentes. */
|
||||
uniqPeriode: uniqueIndex("salaires_liasses_periode_idx").on(t.annee, t.mois),
|
||||
/** Empêche de traiter deux fois le même fichier sous une période distincte. */
|
||||
uniqPeriodeEmpreinte: uniqueIndex("salaires_liasses_periode_empreinte_idx").on(t.annee, t.mois, t.empreinteSha256),
|
||||
}));
|
||||
|
||||
export type SalaireLiasse = typeof salairesLiasses.$inferSelect;
|
||||
export type InsertSalaireLiasse = typeof salairesLiasses.$inferInsert;
|
||||
|
||||
/**
|
||||
* Données utiles par bulletin. `explicationEcart` conserve uniquement les
|
||||
* rubriques de rémunération nécessaires à expliquer une variation mensuelle.
|
||||
*/
|
||||
export const salairesBulletins = mysqlTable("salaires_bulletins", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
liasseId: int("liasseId").notNull(),
|
||||
matricule: varchar("matricule", { length: 32 }).notNull(),
|
||||
nom: varchar("nom", { length: 150 }).notNull(),
|
||||
prenom: varchar("prenom", { length: 150 }).notNull(),
|
||||
poste: varchar("poste", { length: 255 }).notNull(),
|
||||
brutMensuelCents: int("brutMensuelCents").notNull(),
|
||||
brutAvecPrimesCents: int("brutAvecPrimesCents").notNull(),
|
||||
primeAstreinteCents: int("primeAstreinteCents").notNull().default(0),
|
||||
explicationEcart: text("explicationEcart"),
|
||||
numeroPage: int("numeroPage"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
}, (t) => ({
|
||||
/** Un seul bulletin indexé par matricule dans une liasse donnée. */
|
||||
uniqLiasseMatricule: uniqueIndex("salaires_bulletins_liasse_matricule_idx").on(t.liasseId, t.matricule),
|
||||
}));
|
||||
|
||||
export type SalaireBulletin = typeof salairesBulletins.$inferSelect;
|
||||
export type InsertSalaireBulletin = typeof salairesBulletins.$inferInsert;
|
||||
|
||||
Reference in New Issue
Block a user