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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user