Checkpoint: Audit de robustesse et maintenabilité : calcul dynamique des tendances OPEX par véritable année N-1, filtrage/validation centralisés des codes établissement, transactions d'import et d'écriture, contraintes uniques CAPEX/OPEX avec déduplication, suppression effective de postes OPEX, nettoyage des gabarits et scripts morts, chargement différé XLSX, documentation métier et 22 tests passants.

This commit is contained in:
Manus
2026-08-17 20:50:33 +00:00
parent 31cdb257fb
commit 615dd46059
29 changed files with 1449 additions and 3307 deletions

View File

@@ -0,0 +1,3 @@
ALTER TABLE `capex_lignes` ADD CONSTRAINT `capex_lignes_annee_etab_cle_idx` UNIQUE(`annee`,`etablissementCode`,`cle`);--> statement-breakpoint
ALTER TABLE `opex_montants_etab` ADD CONSTRAINT `opex_montants_annee_etab_poste_idx` UNIQUE(`annee`,`etablissementCode`,`libellePoste`);--> statement-breakpoint
ALTER TABLE `opex_postes` ADD CONSTRAINT `opex_postes_annee_libelle_idx` UNIQUE(`annee`,`libelle`);

View File

@@ -0,0 +1,918 @@
{
"version": "5",
"dialect": "mysql",
"id": "fcb67a8c-d69f-4eab-b81e-e3256730a27b",
"prevId": "44abeed3-e7bf-47cd-a29c-59a27bf57f1a",
"tables": {
"capex_lignes": {
"name": "capex_lignes",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"annee": {
"name": "annee",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"etablissementCode": {
"name": "etablissementCode",
"type": "varchar(50)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cle": {
"name": "cle",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"montant": {
"name": "montant",
"type": "decimal(12,2)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"capex_lignes_annee_etab_cle_idx": {
"name": "capex_lignes_annee_etab_cle_idx",
"columns": [
"annee",
"etablissementCode",
"cle"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"capex_lignes_id": {
"name": "capex_lignes_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"etablissements": {
"name": "etablissements",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"code": {
"name": "code",
"type": "varchar(50)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"nom": {
"name": "nom",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"groupe": {
"name": "groupe",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ville": {
"name": "ville",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"actif": {
"name": "actif",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"etablissements_id": {
"name": "etablissements_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"etablissements_code_unique": {
"name": "etablissements_code_unique",
"columns": [
"code"
]
}
},
"checkConstraint": {}
},
"inventaire_meta": {
"name": "inventaire_meta",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"annee": {
"name": "annee",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"filename": {
"name": "filename",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"dateImport": {
"name": "dateImport",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"nbEtablissements": {
"name": "nbEtablissements",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": 0
},
"nbFixes": {
"name": "nbFixes",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": 0
},
"nbPortables": {
"name": "nbPortables",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": 0
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"inventaire_meta_id": {
"name": "inventaire_meta_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"inventaire_meta_annee_unique": {
"name": "inventaire_meta_annee_unique",
"columns": [
"annee"
]
}
},
"checkConstraint": {}
},
"inventaire_postes": {
"name": "inventaire_postes",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"annee": {
"name": "annee",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"etablissementCode": {
"name": "etablissementCode",
"type": "varchar(50)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"libelle": {
"name": "libelle",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"typePoste": {
"name": "typePoste",
"type": "enum('fixe','portable')",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"dateRef": {
"name": "dateRef",
"type": "varchar(20)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ageAns": {
"name": "ageAns",
"type": "decimal(5,2)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"modele": {
"name": "modele",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"fabricant": {
"name": "fabricant",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"inventaire_postes_id": {
"name": "inventaire_postes_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"opex_bases_repartition": {
"name": "opex_bases_repartition",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"annee": {
"name": "annee",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"etablissementCode": {
"name": "etablissementCode",
"type": "varchar(50)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"etablissementNom": {
"name": "etablissementNom",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"baseRepartition": {
"name": "baseRepartition",
"type": "decimal(20,6)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"baseRepartitionHep": {
"name": "baseRepartitionHep",
"type": "decimal(20,6)",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'0'"
},
"modeManuel": {
"name": "modeManuel",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
}
},
"indexes": {
"opex_bases_repartition_annee_etab_idx": {
"name": "opex_bases_repartition_annee_etab_idx",
"columns": [
"annee",
"etablissementCode"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"opex_bases_repartition_id": {
"name": "opex_bases_repartition_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"opex_montants_etab": {
"name": "opex_montants_etab",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"annee": {
"name": "annee",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"etablissementCode": {
"name": "etablissementCode",
"type": "varchar(50)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"libellePoste": {
"name": "libellePoste",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"montant": {
"name": "montant",
"type": "decimal(12,2)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"opex_montants_annee_etab_poste_idx": {
"name": "opex_montants_annee_etab_poste_idx",
"columns": [
"annee",
"etablissementCode",
"libellePoste"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"opex_montants_etab_id": {
"name": "opex_montants_etab_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"opex_postes": {
"name": "opex_postes",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"annee": {
"name": "annee",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"colIdx": {
"name": "colIdx",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"libelle": {
"name": "libelle",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"libelleCourt": {
"name": "libelleCourt",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"libelleDetail": {
"name": "libelleDetail",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"fournisseur": {
"name": "fournisseur",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"categorie": {
"name": "categorie",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"type": {
"name": "type",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"facturation": {
"name": "facturation",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"modeVentilation": {
"name": "modeVentilation",
"type": "varchar(50)",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'Prorata C 6'"
},
"compte": {
"name": "compte",
"type": "varchar(50)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"detail": {
"name": "detail",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"budgetN1": {
"name": "budgetN1",
"type": "decimal(12,2)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"montant": {
"name": "montant",
"type": "decimal(12,2)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"isCustom": {
"name": "isCustom",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": false
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {
"opex_postes_annee_libelle_idx": {
"name": "opex_postes_annee_libelle_idx",
"columns": [
"annee",
"libelle"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"opex_postes_id": {
"name": "opex_postes_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"opex_validated": {
"name": "opex_validated",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"annee": {
"name": "annee",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"validatedAt": {
"name": "validatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"validatedBy": {
"name": "validatedBy",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"opex_validated_id": {
"name": "opex_validated_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"opex_validated_annee_unique": {
"name": "opex_validated_annee_unique",
"columns": [
"annee"
]
}
},
"checkConstraint": {}
},
"parametres_app": {
"name": "parametres_app",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"cle": {
"name": "cle",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"valeur": {
"name": "valeur",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"parametres_app_id": {
"name": "parametres_app_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"parametres_app_cle_unique": {
"name": "parametres_app_cle_unique",
"columns": [
"cle"
]
}
},
"checkConstraint": {}
},
"user_etablissements": {
"name": "user_etablissements",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"userId": {
"name": "userId",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"etablissementCode": {
"name": "etablissementCode",
"type": "varchar(50)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_etablissements_id": {
"name": "user_etablissements_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"login": {
"name": "login",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(320)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"passwordHash": {
"name": "passwordHash",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"firstName": {
"name": "firstName",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"lastName": {
"name": "lastName",
"type": "varchar(100)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"role": {
"name": "role",
"type": "enum('admin','standard','readonly')",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'standard'"
},
"isActive": {
"name": "isActive",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"createdAt": {
"name": "createdAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"updatedAt": {
"name": "updatedAt",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"onUpdate": true,
"default": "(now())"
},
"lastSignedIn": {
"name": "lastSignedIn",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"users_id": {
"name": "users_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {
"users_login_unique": {
"name": "users_login_unique",
"columns": [
"login"
]
}
},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}

View File

@@ -29,6 +29,13 @@
"when": 1781163262493,
"tag": "0003_tense_havok",
"breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1786998761879,
"tag": "0004_known_shiva",
"breakpoints": true
}
]
}

View File

@@ -87,21 +87,31 @@ export const opexPostes = mysqlTable("opex_postes", {
isCustom: boolean("isCustom").default(false),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
}, (t) => ({
/** Empêche une initialisation répétée de créer le même poste pour une année. */
uniqAnneeLibelle: uniqueIndex("opex_postes_annee_libelle_idx").on(t.annee, t.libelle),
}));
export type OpexPoste = typeof opexPostes.$inferSelect;
export type InsertOpexPoste = typeof opexPostes.$inferInsert;
// Overrides manuels par établissement × poste × année
export const opexMontantsEtab = mysqlTable("opex_montants_etab", {
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull(),
etablissementCode: varchar("etablissementCode", { length: 50 }).notNull(),
libellePoste: varchar("libellePoste", { length: 255 }).notNull(),
montant: decimal("montant", { precision: 12, scale: 2 }),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull(),
etablissementCode: varchar("etablissementCode", { length: 50 }).notNull(),
libellePoste: varchar("libellePoste", { length: 255 }).notNull(),
montant: decimal("montant", { precision: 12, scale: 2 }),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
}, (t) => ({
/** Une seule surcharge manuelle par année, établissement et poste. */
uniqAnneeEtabPoste: uniqueIndex("opex_montants_annee_etab_poste_idx").on(
t.annee,
t.etablissementCode,
t.libellePoste,
),
}));
export type OpexMontantEtab = typeof opexMontantsEtab.$inferSelect;
export type InsertOpexMontantEtab = typeof opexMontantsEtab.$inferInsert;
@@ -148,16 +158,23 @@ export const inventaireMeta = mysqlTable("inventaire_meta", {
// CAPEX — Lignes budgétaires par établissement
// ─────────────────────────────────────────────────────────────────────────────
export const capexLignes = mysqlTable("capex_lignes", {
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull(),
etablissementCode: varchar("etablissementCode", { length: 50 }).notNull(),
cle: varchar("cle", { length: 100 }).notNull(),
// cles possibles: renouvellement_2027 | machines_supplementaires | appel_malade
// | telephonie | wifi | video_surveillance | copieurs | visio | autres | commentaires
montant: decimal("montant", { precision: 12, scale: 2 }),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull(),
etablissementCode: varchar("etablissementCode", { length: 50 }).notNull(),
cle: varchar("cle", { length: 100 }).notNull(),
// cles possibles: renouvellement_2027 | machines_supplementaires | appel_malade
// | telephonie | wifi | video_surveillance | copieurs | visio | autres | commentaires
montant: decimal("montant", { precision: 12, scale: 2 }),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
}, (t) => ({
/** La sauvegarde CAPEX reste idempotente pour chaque clé métier. */
uniqAnneeEtabCle: uniqueIndex("capex_lignes_annee_etab_cle_idx").on(
t.annee,
t.etablissementCode,
t.cle,
),
}));
export type CapexLigne = typeof capexLignes.$inferSelect;
export type InsertCapexLigne = typeof capexLignes.$inferInsert;