Checkpoint: Ajout de l'onglet 'Clés de répartition' dans DSI OPEX : tableau éditable avec base de répartition standard et base /HEP par établissement, import fichier Excel/CSV, info année (OPEX N → base N-2). Calcul automatique du poste SAAS DUI Pôle HEP branché sur la colonne /HEP. Nouvelle table opex_bases_repartition avec colonne baseRepartitionHep. Seed 2026 : 64 établissements dont 19 HEP. 14 tests passants, 0 erreur TypeScript.
This commit is contained in:
25
server/db.ts
25
server/db.ts
@@ -343,6 +343,7 @@ export async function upsertOpexBaseRepartition(input: {
|
||||
etablissementCode: string;
|
||||
etablissementNom?: string | null;
|
||||
baseRepartition: number;
|
||||
baseRepartitionHep?: number;
|
||||
}) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
@@ -353,11 +354,35 @@ export async function upsertOpexBaseRepartition(input: {
|
||||
etablissementCode: input.etablissementCode,
|
||||
etablissementNom: input.etablissementNom ?? null,
|
||||
baseRepartition: String(input.baseRepartition),
|
||||
baseRepartitionHep: String(input.baseRepartitionHep ?? 0),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
baseRepartition: String(input.baseRepartition),
|
||||
baseRepartitionHep: String(input.baseRepartitionHep ?? 0),
|
||||
etablissementNom: input.etablissementNom ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Import en masse des bases de répartition pour une année (supprime et réinsère) */
|
||||
export async function importOpexBasesRepartition(
|
||||
annee: number,
|
||||
rows: Array<{ etablissementCode: string; etablissementNom?: string | null; baseRepartition: number; baseRepartitionHep?: number }>
|
||||
) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
// Supprimer les données existantes pour l'année
|
||||
await db.delete(opexBasesRepartition).where(eq(opexBasesRepartition.annee, annee));
|
||||
if (rows.length === 0) return;
|
||||
// Insérer en batch
|
||||
await db.insert(opexBasesRepartition).values(
|
||||
rows.map(r => ({
|
||||
annee,
|
||||
etablissementCode: r.etablissementCode,
|
||||
etablissementNom: r.etablissementNom ?? null,
|
||||
baseRepartition: String(r.baseRepartition),
|
||||
baseRepartitionHep: String(r.baseRepartitionHep ?? 0),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,11 +179,33 @@ export const appRouter = router({
|
||||
.input(z.object({ annee: z.number() }))
|
||||
.query(async ({ input }) => db.getOpexBasesRepartition(input.annee)),
|
||||
|
||||
// Upsert une base de répartition pour un établissement
|
||||
// Upsert une base de répartition pour un établissement (standard + HEP)
|
||||
setBaseRepartition: writeProcedure
|
||||
.input(z.object({ annee: z.number(), etablissementCode: z.string(), etablissementNom: z.string().optional().nullable(), baseRepartition: z.number() }))
|
||||
.input(z.object({
|
||||
annee: z.number(),
|
||||
etablissementCode: z.string(),
|
||||
etablissementNom: z.string().optional().nullable(),
|
||||
baseRepartition: z.number(),
|
||||
baseRepartitionHep: z.number().optional().default(0),
|
||||
}))
|
||||
.mutation(async ({ input }) => { await db.upsertOpexBaseRepartition(input); return { success: true }; }),
|
||||
|
||||
// Import en masse des bases de répartition (depuis fichier Excel/CSV)
|
||||
importBasesRepartition: writeProcedure
|
||||
.input(z.object({
|
||||
annee: z.number(),
|
||||
rows: z.array(z.object({
|
||||
etablissementCode: z.string(),
|
||||
etablissementNom: z.string().optional().nullable(),
|
||||
baseRepartition: z.number(),
|
||||
baseRepartitionHep: z.number().optional().default(0),
|
||||
})),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
await db.importOpexBasesRepartition(input.annee, input.rows);
|
||||
return { success: true, count: input.rows.length };
|
||||
}),
|
||||
|
||||
// Supprimer toutes les lignes OPEX d'une année
|
||||
deleteAll: writeProcedure
|
||||
.input(z.object({ annee: z.number() }))
|
||||
|
||||
Reference in New Issue
Block a user