Checkpoint: Quand l'utilisateur active le toggle "Tout manuel" pour un établissement dans l'onglet Clés de répartition, les montants calculés automatiquement (prorata) sont d'abord sauvegardés en BDD via la nouvelle procédure batchSetMontantsEtab, puis le flag modeManuel est activé. L'établissement part donc avec les valeurs précalculées comme base de saisie manuelle.
This commit is contained in:
@@ -394,9 +394,11 @@ interface ClesRepartitionViewProps {
|
||||
isErrorBases: boolean;
|
||||
setBaseRepartition: ReturnType<typeof trpc.opex.setBaseRepartition.useMutation>;
|
||||
importBasesRepartition: ReturnType<typeof trpc.opex.importBasesRepartition.useMutation>;
|
||||
batchSetMontantsEtab: ReturnType<typeof trpc.opex.batchSetMontantsEtab.useMutation>;
|
||||
etablissementsRecalcules: Array<{ code: string; nom: string; montants: Record<string, number>; total: number }>;
|
||||
}
|
||||
|
||||
function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErrorBases, setBaseRepartition, importBasesRepartition }: ClesRepartitionViewProps) {
|
||||
function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErrorBases, setBaseRepartition, importBasesRepartition, batchSetMontantsEtab, etablissementsRecalcules }: ClesRepartitionViewProps) {
|
||||
const anneeBase = annee - 2; // La base de répartition correspond à l'année OPEX - 2
|
||||
const [editingCell, setEditingCell] = useState<{ code: string; col: 'base' | 'hep' } | null>(null);
|
||||
const [editValue, setEditValue] = useState('');
|
||||
@@ -439,13 +441,44 @@ function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErr
|
||||
}
|
||||
|
||||
function toggleModeManuel(row: BaseRow) {
|
||||
const activating = !row.modeManuel;
|
||||
if (activating) {
|
||||
// Avant d'activer le mode manuel, on pré-remplit les montants avec les valeurs
|
||||
// calculées automatiquement (prorata) pour cet établissement
|
||||
const etabCalc = etablissementsRecalcules.find(e => e.code === row.etablissementCode);
|
||||
if (etabCalc && Object.keys(etabCalc.montants).length > 0) {
|
||||
const montantsASeeder = Object.entries(etabCalc.montants)
|
||||
.filter(([, v]) => v > 0)
|
||||
.map(([libellePoste, v]) => ({ libellePoste, montant: String(v) }));
|
||||
if (montantsASeeder.length > 0) {
|
||||
batchSetMontantsEtab.mutate(
|
||||
{ annee, etablissementCode: row.etablissementCode, montants: montantsASeeder },
|
||||
{
|
||||
onSettled: () => {
|
||||
// Activer le flag modeManuel après la sauvegarde des montants
|
||||
setBaseRepartition.mutate({
|
||||
annee,
|
||||
etablissementCode: row.etablissementCode,
|
||||
etablissementNom: row.etablissementNom,
|
||||
baseRepartition: parseFloat(row.baseRepartition ?? '0'),
|
||||
baseRepartitionHep: parseFloat(row.baseRepartitionHep ?? '0'),
|
||||
modeManuel: !row.modeManuel,
|
||||
modeManuel: true,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cas normal : désactivation ou activation sans montants à seeder
|
||||
setBaseRepartition.mutate({
|
||||
annee,
|
||||
etablissementCode: row.etablissementCode,
|
||||
etablissementNom: row.etablissementNom,
|
||||
baseRepartition: parseFloat(row.baseRepartition ?? '0'),
|
||||
baseRepartitionHep: parseFloat(row.baseRepartitionHep ?? '0'),
|
||||
modeManuel: activating,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -818,6 +851,10 @@ export default function DsiOpex() {
|
||||
onSuccess: (data) => { utils.opex.getBasesRepartition.invalidate({ annee }); toast.success(`${data.count} bases de répartition importées`); },
|
||||
onError: (err) => toast.error('Erreur import', { description: err.message }),
|
||||
});
|
||||
const batchSetMontantsEtab = trpc.opex.batchSetMontantsEtab.useMutation({
|
||||
onSuccess: () => utils.opex.getMontantsEtab.invalidate({ annee }),
|
||||
onError: (err) => toast.error('Erreur sauvegarde montants', { description: err.message }),
|
||||
});
|
||||
|
||||
// ── Mapper les données BDD vers le format interne ──────────────────────────
|
||||
const lignes: LigneOpex[] = useMemo(() => {
|
||||
@@ -1904,6 +1941,8 @@ export default function DsiOpex() {
|
||||
isErrorBases={isErrorBases}
|
||||
setBaseRepartition={setBaseRepartition}
|
||||
importBasesRepartition={importBasesRepartition}
|
||||
batchSetMontantsEtab={batchSetMontantsEtab}
|
||||
etablissementsRecalcules={etablissementsRecalcules}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
25
scripts/list-non-standard-codes.mjs
Normal file
25
scripts/list-non-standard-codes.mjs
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createConnection } from 'mysql2/promise';
|
||||
import { config } from 'dotenv';
|
||||
config();
|
||||
|
||||
const conn = await createConnection(process.env.DATABASE_URL);
|
||||
|
||||
// Lister toutes les lignes dont le code n'est pas un code établissement standard (7 chiffres + 2 lettres)
|
||||
const [rows] = await conn.execute(
|
||||
`SELECT id, annee, etablissementCode, etablissementNom, baseRepartition
|
||||
FROM opex_bases_repartition
|
||||
ORDER BY etablissementCode`
|
||||
);
|
||||
|
||||
console.log('=== Codes non-standard (parasites potentiels) ===');
|
||||
for (const r of rows) {
|
||||
const code = r.etablissementCode ?? '';
|
||||
// Code standard = 7 chiffres + 2 lettres majuscules (ex: 1001BPT)
|
||||
const isStandard = /^\d{4,7}[A-Z]{2,3}$/.test(code);
|
||||
if (!isStandard) {
|
||||
console.log(`id=${r.id} annee=${r.annee} code="${code}" nom="${r.etablissementNom}" base=${r.baseRepartition}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal lignes: ${rows.length}`);
|
||||
await conn.end();
|
||||
@@ -140,6 +140,28 @@ export const appRouter = router({
|
||||
.input(z.object({ annee: z.number(), etablissementCode: z.string(), libellePoste: z.string(), montant: z.string().nullable() }))
|
||||
.mutation(async ({ input }) => { await db.setOpexMontantEtab(input as Parameters<typeof db.setOpexMontantEtab>[0]); return { success: true }; }),
|
||||
|
||||
// Sauvegarder en masse les montants par établissement (utilisé lors du passage en mode tout manuel)
|
||||
batchSetMontantsEtab: writeProcedure
|
||||
.input(z.object({
|
||||
annee: z.number(),
|
||||
etablissementCode: z.string(),
|
||||
montants: z.array(z.object({
|
||||
libellePoste: z.string(),
|
||||
montant: z.string(),
|
||||
}))
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
for (const m of input.montants) {
|
||||
await db.setOpexMontantEtab({
|
||||
annee: input.annee,
|
||||
etablissementCode: input.etablissementCode,
|
||||
libellePoste: m.libellePoste,
|
||||
montant: m.montant,
|
||||
});
|
||||
}
|
||||
return { success: true, count: input.montants.length };
|
||||
}),
|
||||
|
||||
getValidated: protectedProcedure
|
||||
.input(z.object({ annee: z.number() }))
|
||||
.query(async ({ input }) => db.getOpexValidated(input.annee)),
|
||||
|
||||
Reference in New Issue
Block a user