Checkpoint: Ajout du module Masse salariale SANTINOVA : tables de salariés, rémunérations et évolutions manuelles sécurisées par rôle administrateur ; import contrôlé des bulletins 2025 et janvier–août 2026 ; écran tableau 2025–2027 ; tests d'autorisations, d'agrégats et des quatre états UI ; documentation des sources et de la politique d'import.

This commit is contained in:
Manus
2026-08-27 13:10:47 +00:00
parent 2f4b1f2e8e
commit da407db4de
21 changed files with 4390 additions and 4 deletions

View File

@@ -0,0 +1,30 @@
CREATE TABLE `masse_salariale_remunerations` (
`id` int AUTO_INCREMENT NOT NULL,
`salarieId` int NOT NULL,
`annee` int NOT NULL,
`salaireAnnuelBrutHorsPrimesCents` int NOT NULL,
`salaireAnnuelBrutAvecPrimesCents` int NOT NULL,
`salaireMensuelBrutHorsPrimesCents` int NOT NULL,
`tauxChargeBps` int NOT NULL,
`evolutionSalarialeBps` int,
`periodeReference` varchar(7),
`source` varchar(64) NOT NULL DEFAULT 'bulletin',
`createdAt` timestamp NOT NULL DEFAULT (now()),
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `masse_salariale_remunerations_id` PRIMARY KEY(`id`),
CONSTRAINT `masse_salariale_salarie_annee_idx` UNIQUE(`salarieId`,`annee`)
);
--> statement-breakpoint
CREATE TABLE `masse_salariale_salaries` (
`id` int AUTO_INCREMENT NOT NULL,
`matricule` varchar(32) NOT NULL,
`nom` varchar(150) NOT NULL,
`prenom` varchar(150) NOT NULL,
`poste` varchar(255) NOT NULL,
`dateEmbauche` varchar(10) NOT NULL,
`actif` boolean NOT NULL DEFAULT true,
`createdAt` timestamp NOT NULL DEFAULT (now()),
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `masse_salariale_salaries_id` PRIMARY KEY(`id`),
CONSTRAINT `masse_salariale_salaries_matricule_unique` UNIQUE(`matricule`)
);

View File

@@ -0,0 +1 @@
ALTER TABLE `masse_salariale_remunerations` ADD `statut` varchar(32) DEFAULT 'annuel' NOT NULL;

View File

@@ -0,0 +1,13 @@
CREATE TABLE `masse_salariale_evolutions` (
`id` int AUTO_INCREMENT NOT NULL,
`salarieId` int NOT NULL,
`annee` int NOT NULL,
`evolutionSalarialeBps` int NOT NULL,
`createdBy` int NOT NULL,
`createdAt` timestamp NOT NULL DEFAULT (now()),
`updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT `masse_salariale_evolutions_id` PRIMARY KEY(`id`),
CONSTRAINT `masse_salariale_evolution_salarie_annee_idx` UNIQUE(`salarieId`,`annee`)
);
--> statement-breakpoint
ALTER TABLE `masse_salariale_remunerations` DROP COLUMN `evolutionSalarialeBps`;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,27 @@
"when": 1786998761879,
"tag": "0004_known_shiva",
"breakpoints": true
},
{
"idx": 5,
"version": "5",
"when": 1787833569970,
"tag": "0005_nasty_tattoo",
"breakpoints": true
},
{
"idx": 6,
"version": "5",
"when": 1787833802656,
"tag": "0006_ambitious_shen",
"breakpoints": true
},
{
"idx": 7,
"version": "5",
"when": 1787833910943,
"tag": "0007_rich_tony_stark",
"breakpoints": true
}
]
}

View File

@@ -201,3 +201,67 @@ export const opexBasesRepartition = mysqlTable("opex_bases_repartition", {
export type OpexBaseRepartition = typeof opexBasesRepartition.$inferSelect;
export type InsertOpexBaseRepartition = typeof opexBasesRepartition.$inferInsert;
// ─────────────────────────────────────────────────────────────────────────────
// MASSE SALARIALE SANTINOVA — Données sensibles, accès administrateur uniquement
// ─────────────────────────────────────────────────────────────────────────────
export const masseSalarialeSalaries = mysqlTable("masse_salariale_salaries", {
id: int("id").autoincrement().primaryKey(),
/** Identifiant paie stable, distinct des données personnelles superflues. */
matricule: varchar("matricule", { length: 32 }).notNull().unique(),
nom: varchar("nom", { length: 150 }).notNull(),
prenom: varchar("prenom", { length: 150 }).notNull(),
poste: varchar("poste", { length: 255 }).notNull(),
/** Date civile ISO (YYYY-MM-DD), sans fuseau horaire. */
dateEmbauche: varchar("dateEmbauche", { length: 10 }).notNull(),
actif: boolean("actif").default(true).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type MasseSalarialeSalarie = typeof masseSalarialeSalaries.$inferSelect;
export type InsertMasseSalarialeSalarie = typeof masseSalarialeSalaries.$inferInsert;
/**
* Une ligne par salarié et exercice. Tous les montants sont en centimes et les
* taux en points de base (ex. 42,75 % = 4 275) pour éviter les erreurs d'arrondi.
*/
export const masseSalarialeRemunerations = mysqlTable("masse_salariale_remunerations", {
id: int("id").autoincrement().primaryKey(),
salarieId: int("salarieId").notNull(),
annee: int("annee").notNull(),
salaireAnnuelBrutHorsPrimesCents: int("salaireAnnuelBrutHorsPrimesCents").notNull(),
salaireAnnuelBrutAvecPrimesCents: int("salaireAnnuelBrutAvecPrimesCents").notNull(),
salaireMensuelBrutHorsPrimesCents: int("salaireMensuelBrutHorsPrimesCents").notNull(),
tauxChargeBps: int("tauxChargeBps").notNull(),
/** Mois de paie ayant servi de référence, ex. 2025-12 ou 2026-08. */
periodeReference: varchar("periodeReference", { length: 7 }),
/** `annuel` pour un exercice clos ; `cumul_provisoire` pour une année en cours. */
statut: varchar("statut", { length: 32 }).notNull().default("annuel"),
source: varchar("source", { length: 64 }).notNull().default("bulletin"),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
}, (t) => ({
/** Rend les imports idempotents pour un salarié et un exercice. */
uniqSalarieAnnee: uniqueIndex("masse_salariale_salarie_annee_idx").on(t.salarieId, t.annee),
}));
export type MasseSalarialeRemuneration = typeof masseSalarialeRemunerations.$inferSelect;
export type InsertMasseSalarialeRemuneration = typeof masseSalarialeRemunerations.$inferInsert;
/** Évolution salariale manuelle, stockée même en l'absence de bulletin pour l'année concernée. */
export const masseSalarialeEvolutions = mysqlTable("masse_salariale_evolutions", {
id: int("id").autoincrement().primaryKey(),
salarieId: int("salarieId").notNull(),
annee: int("annee").notNull(),
/** Pourcentage exprimé en points de base, ex. 1,50 % = 150. */
evolutionSalarialeBps: int("evolutionSalarialeBps").notNull(),
createdBy: int("createdBy").notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
}, (t) => ({
uniqSalarieAnnee: uniqueIndex("masse_salariale_evolution_salarie_annee_idx").on(t.salarieId, t.annee),
}));
export type MasseSalarialeEvolution = typeof masseSalarialeEvolutions.$inferSelect;
export type InsertMasseSalarialeEvolution = typeof masseSalarialeEvolutions.$inferInsert;