Files
itinova-budget-si/drizzle/schema.ts

204 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
boolean,
decimal,
int,
mysqlEnum,
mysqlTable,
text,
timestamp,
uniqueIndex,
varchar,
} from "drizzle-orm/mysql-core";
// ─────────────────────────────────────────────────────────────────────────────
// USERS — Auth locale (email/password), 3 profils : admin | standard | readonly
// ─────────────────────────────────────────────────────────────────────────────
export const users = mysqlTable("users", {
id: int("id").autoincrement().primaryKey(),
/** Identifiant de connexion (email ou login court comme adminItinova) */
login: varchar("login", { length: 255 }).notNull().unique(),
email: varchar("email", { length: 320 }),
passwordHash: varchar("passwordHash", { length: 255 }).notNull(),
firstName: varchar("firstName", { length: 100 }),
lastName: varchar("lastName", { length: 100 }),
role: mysqlEnum("role", ["admin", "standard", "readonly"]).default("standard").notNull(),
isActive: boolean("isActive").default(true).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
lastSignedIn: timestamp("lastSignedIn"),
});
export type User = typeof users.$inferSelect;
export type InsertUser = typeof users.$inferInsert;
// ─────────────────────────────────────────────────────────────────────────────
// ÉTABLISSEMENTS
// ─────────────────────────────────────────────────────────────────────────────
export const etablissements = mysqlTable("etablissements", {
id: int("id").autoincrement().primaryKey(),
code: varchar("code", { length: 50 }).notNull().unique(),
nom: varchar("nom", { length: 255 }).notNull(),
groupe: varchar("groupe", { length: 100 }),
ville: varchar("ville", { length: 100 }),
actif: boolean("actif").default(true).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type Etablissement = typeof etablissements.$inferSelect;
export type InsertEtablissement = typeof etablissements.$inferInsert;
// Rattachement utilisateur ↔ établissements (pour profil standard/readonly)
export const userEtablissements = mysqlTable("user_etablissements", {
id: int("id").autoincrement().primaryKey(),
userId: int("userId").notNull(),
etablissementCode: varchar("etablissementCode", { length: 50 }).notNull(),
});
// ─────────────────────────────────────────────────────────────────────────────
// PARAMÈTRES APPLICATION
// ─────────────────────────────────────────────────────────────────────────────
export const parametresApp = mysqlTable("parametres_app", {
id: int("id").autoincrement().primaryKey(),
cle: varchar("cle", { length: 100 }).notNull().unique(),
valeur: text("valeur"),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
// ─────────────────────────────────────────────────────────────────────────────
// OPEX — Postes budgétaires
// ─────────────────────────────────────────────────────────────────────────────
export const opexPostes = mysqlTable("opex_postes", {
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull(),
colIdx: int("colIdx").notNull(),
libelle: varchar("libelle", { length: 255 }).notNull(),
libelleCourt: varchar("libelleCourt", { length: 100 }),
libelleDetail: varchar("libelleDetail", { length: 255 }),
fournisseur: varchar("fournisseur", { length: 255 }),
categorie: varchar("categorie", { length: 100 }),
type: varchar("type", { length: 100 }),
facturation: varchar("facturation", { length: 100 }),
modeVentilation: varchar("modeVentilation", { length: 50 }).default("Prorata C 6"),
compte: varchar("compte", { length: 50 }),
detail: text("detail"),
budgetN1: decimal("budgetN1", { precision: 12, scale: 2 }),
montant: decimal("montant", { precision: 12, scale: 2 }),
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(),
}, (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;
// Validation définitive par année
export const opexValidated = mysqlTable("opex_validated", {
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull().unique(),
validatedAt: timestamp("validatedAt").defaultNow().notNull(),
validatedBy: int("validatedBy"),
});
// ─────────────────────────────────────────────────────────────────────────────
// INVENTAIRE PC
// ─────────────────────────────────────────────────────────────────────────────
export const inventairePostes = mysqlTable("inventaire_postes", {
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull(),
etablissementCode: varchar("etablissementCode", { length: 50 }).notNull(),
libelle: varchar("libelle", { length: 255 }),
typePoste: mysqlEnum("typePoste", ["fixe", "portable"]).notNull(),
dateRef: varchar("dateRef", { length: 20 }),
ageAns: decimal("ageAns", { precision: 5, scale: 2 }),
modele: varchar("modele", { length: 255 }),
fabricant: varchar("fabricant", { length: 100 }),
createdAt: timestamp("createdAt").defaultNow().notNull(),
});
export type InventairePoste = typeof inventairePostes.$inferSelect;
export type InsertInventairePoste = typeof inventairePostes.$inferInsert;
// Métadonnées d'import inventaire
export const inventaireMeta = mysqlTable("inventaire_meta", {
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull().unique(),
filename: varchar("filename", { length: 255 }),
dateImport: timestamp("dateImport").defaultNow().notNull(),
nbEtablissements: int("nbEtablissements").default(0),
nbFixes: int("nbFixes").default(0),
nbPortables: int("nbPortables").default(0),
});
// ─────────────────────────────────────────────────────────────────────────────
// 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(),
}, (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;
// ───────────────────────────────────────────────────────────────────────────────
// OPEX — Bases de répartition par établissement et par année
// (charges de classe 6 utilisées pour le prorata)
// ───────────────────────────────────────────────────────────────────────────────
export const opexBasesRepartition = mysqlTable("opex_bases_repartition", {
id: int("id").autoincrement().primaryKey(),
annee: int("annee").notNull(),
etablissementCode: varchar("etablissementCode", { length: 50 }).notNull(),
etablissementNom: varchar("etablissementNom", { length: 255 }),
/** Charges classe 6 — base de répartition standard (tous établissements) */
baseRepartition: decimal("baseRepartition", { precision: 20, scale: 6 }).notNull(),
/** Charges classe 6 — base de répartition pôle HEP uniquement (0 pour les étblissements hors HEP) */
baseRepartitionHep: decimal("baseRepartitionHep", { precision: 20, scale: 6 }).default("0"),
/** Si true, tous les montants OPEX de cet établissement sont saisis manuellement (pas de calcul prorata) */
modeManuel: boolean("modeManuel").default(false).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
}, (t) => ({
uniqAnneeEtab: uniqueIndex("opex_bases_repartition_annee_etab_idx").on(t.annee, t.etablissementCode),
}));
export type OpexBaseRepartition = typeof opexBasesRepartition.$inferSelect;
export type InsertOpexBaseRepartition = typeof opexBasesRepartition.$inferInsert;