Checkpoint: Bug corrigé : ajout contrainte UNIQUE (annee, etablissementCode) sur opex_bases_repartition + déduplication des lignes existantes — le clic ne crée plus de doublon. Nouvelle fonctionnalité : colonne "Tout manuel" (toggle) dans l'onglet Clés de répartition — quand activé pour un établissement, le calcul prorata est ignoré et seuls les montants saisis manuellement dans la vue Établissements sont utilisés. Badge ambre visible dans les deux vues.
This commit is contained in:
@@ -384,6 +384,7 @@ interface BaseRow {
|
|||||||
etablissementNom: string | null;
|
etablissementNom: string | null;
|
||||||
baseRepartition: string;
|
baseRepartition: string;
|
||||||
baseRepartitionHep: string | null;
|
baseRepartitionHep: string | null;
|
||||||
|
modeManuel: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClesRepartitionViewProps {
|
interface ClesRepartitionViewProps {
|
||||||
@@ -432,10 +433,22 @@ function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErr
|
|||||||
etablissementNom: row.etablissementNom,
|
etablissementNom: row.etablissementNom,
|
||||||
baseRepartition,
|
baseRepartition,
|
||||||
baseRepartitionHep,
|
baseRepartitionHep,
|
||||||
|
modeManuel: row.modeManuel,
|
||||||
});
|
});
|
||||||
setEditingCell(null);
|
setEditingCell(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleModeManuel(row: BaseRow) {
|
||||||
|
setBaseRepartition.mutate({
|
||||||
|
annee,
|
||||||
|
etablissementCode: row.etablissementCode,
|
||||||
|
etablissementNom: row.etablissementNom,
|
||||||
|
baseRepartition: parseFloat(row.baseRepartition ?? '0'),
|
||||||
|
baseRepartitionHep: parseFloat(row.baseRepartitionHep ?? '0'),
|
||||||
|
modeManuel: !row.modeManuel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function handleFileImport(file: File) {
|
async function handleFileImport(file: File) {
|
||||||
try {
|
try {
|
||||||
const XLSX = await import('xlsx');
|
const XLSX = await import('xlsx');
|
||||||
@@ -629,12 +642,15 @@ function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErr
|
|||||||
<th className="px-4 py-3 text-right font-semibold text-foreground text-xs uppercase tracking-wide w-48">
|
<th className="px-4 py-3 text-right font-semibold text-foreground text-xs uppercase tracking-wide w-48">
|
||||||
Base répartition {anneeBase} / HEP
|
Base répartition {anneeBase} / HEP
|
||||||
</th>
|
</th>
|
||||||
|
<th className="px-4 py-3 text-center font-semibold text-foreground text-xs uppercase tracking-wide w-36" title="Si activé, tous les montants OPEX de cet établissement sont saisis manuellement (pas de calcul prorata)">
|
||||||
|
Tout manuel
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows.length === 0 && (
|
{rows.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={4} className="px-4 py-12 text-center text-muted-foreground">
|
<td colSpan={5} className="px-4 py-12 text-center text-muted-foreground">
|
||||||
{basesRepartitionRaw.length === 0
|
{basesRepartitionRaw.length === 0
|
||||||
? `Aucune base de répartition pour ${annee}. Importez un fichier ou saisissez les valeurs manuellement.`
|
? `Aucune base de répartition pour ${annee}. Importez un fichier ou saisissez les valeurs manuellement.`
|
||||||
: 'Aucun résultat pour cette recherche.'}
|
: 'Aucun résultat pour cette recherche.'}
|
||||||
@@ -642,9 +658,16 @@ function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErr
|
|||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{rows.map((row, idx) => (
|
{rows.map((row, idx) => (
|
||||||
<tr key={row.etablissementCode} className={`border-b border-border/50 hover:bg-muted/20 transition-colors ${idx % 2 === 0 ? '' : 'bg-muted/10'}`}>
|
<tr key={row.etablissementCode} className={`border-b border-border/50 hover:bg-muted/20 transition-colors ${row.modeManuel ? 'bg-amber-50/60' : idx % 2 === 0 ? '' : 'bg-muted/10'}`}>
|
||||||
<td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{row.etablissementCode}</td>
|
<td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{row.etablissementCode}</td>
|
||||||
<td className="px-4 py-2.5 font-medium text-foreground">{row.etablissementNom ?? row.etablissementCode}</td>
|
<td className="px-4 py-2.5 font-medium text-foreground">
|
||||||
|
{row.etablissementNom ?? row.etablissementCode}
|
||||||
|
{row.modeManuel && (
|
||||||
|
<span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold bg-amber-100 text-amber-700 border border-amber-200">
|
||||||
|
Manuel
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
{/* Colonne Base de répartition */}
|
{/* Colonne Base de répartition */}
|
||||||
<td className="px-4 py-2.5 text-right">
|
<td className="px-4 py-2.5 text-right">
|
||||||
{editingCell?.code === row.etablissementCode && editingCell.col === 'base' ? (
|
{editingCell?.code === row.etablissementCode && editingCell.col === 'base' ? (
|
||||||
@@ -691,18 +714,48 @@ function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErr
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
{/* Colonne Mode Manuel */}
|
||||||
|
<td className="px-4 py-2.5 text-center">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleModeManuel(row)}
|
||||||
|
disabled={setBaseRepartition.isPending}
|
||||||
|
title={row.modeManuel
|
||||||
|
? 'Mode manuel activé — cliquer pour revenir au calcul prorata automatique'
|
||||||
|
: 'Cliquer pour activer le mode tout manuel (montants OPEX saisis manuellement pour cet établissement)'}
|
||||||
|
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary/30 disabled:opacity-50 ${
|
||||||
|
row.modeManuel ? 'bg-amber-500' : 'bg-muted-foreground/30'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`inline-block h-3.5 w-3.5 rounded-full bg-white shadow transition-transform ${
|
||||||
|
row.modeManuel ? 'translate-x-4.5' : 'translate-x-0.5'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr className="bg-muted/40 border-t-2 border-border font-bold">
|
<tr className="bg-muted/40 border-t-2 border-border font-bold">
|
||||||
<td colSpan={2} className="px-4 py-3 text-sm font-semibold text-foreground">TOTAL — {basesRepartitionRaw.length} établissements</td>
|
<td colSpan={2} className="px-4 py-3 text-sm font-semibold text-foreground">TOTAL — {basesRepartitionRaw.length} établissements
|
||||||
|
{basesRepartitionRaw.filter(r => r.modeManuel).length > 0 && (
|
||||||
|
<span className="ml-2 text-xs font-normal text-amber-600">
|
||||||
|
({basesRepartitionRaw.filter(r => r.modeManuel).length} en mode manuel)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
<td className="px-4 py-3 text-right text-sm font-bold text-foreground tabular-nums">
|
<td className="px-4 py-3 text-right text-sm font-bold text-foreground tabular-nums">
|
||||||
{new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(totalBase)} €
|
{new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(totalBase)} €
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right text-sm font-bold text-orange-600 tabular-nums">
|
<td className="px-4 py-3 text-right text-sm font-bold text-orange-600 tabular-nums">
|
||||||
{totalHep > 0 ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(totalHep) + ' €' : '—'}
|
{totalHep > 0 ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(totalHep) + ' €' : '—'}
|
||||||
</td>
|
</td>
|
||||||
|
<td className="px-4 py-3 text-center text-xs text-muted-foreground">
|
||||||
|
{basesRepartitionRaw.filter(r => r.modeManuel).length > 0
|
||||||
|
? `${basesRepartitionRaw.filter(r => r.modeManuel).length} actif${basesRepartitionRaw.filter(r => r.modeManuel).length > 1 ? 's' : ''}`
|
||||||
|
: '—'}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
@@ -1043,12 +1096,14 @@ export default function DsiOpex() {
|
|||||||
nom: b.etablissementNom ?? b.etablissementCode,
|
nom: b.etablissementNom ?? b.etablissementCode,
|
||||||
base_repartition: parseFloat(b.baseRepartition ?? '0') || 0,
|
base_repartition: parseFloat(b.baseRepartition ?? '0') || 0,
|
||||||
base_repartition_hep: parseFloat(b.baseRepartitionHep ?? '0') || 0,
|
base_repartition_hep: parseFloat(b.baseRepartitionHep ?? '0') || 0,
|
||||||
|
modeManuel: b.modeManuel ?? false,
|
||||||
}))
|
}))
|
||||||
: getOpexDataForAnnee(annee).etablissements.map(e => ({
|
: getOpexDataForAnnee(annee).etablissements.map(e => ({
|
||||||
code: e.code,
|
code: e.code,
|
||||||
nom: e.nom,
|
nom: e.nom,
|
||||||
base_repartition: e.base_repartition,
|
base_repartition: e.base_repartition,
|
||||||
base_repartition_hep: 0, // JSON source n'a pas de base HEP
|
base_repartition_hep: 0, // JSON source n'a pas de base HEP
|
||||||
|
modeManuel: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const totalBase = basesFromBdd.reduce((s, e) => s + e.base_repartition, 0);
|
const totalBase = basesFromBdd.reduce((s, e) => s + e.base_repartition, 0);
|
||||||
@@ -1071,7 +1126,11 @@ export default function DsiOpex() {
|
|||||||
(ligne.libelleDetail ?? '').toLowerCase().includes('hep'));
|
(ligne.libelleDetail ?? '').toLowerCase().includes('hep'));
|
||||||
const ratioApplique = isHepPoste ? ratioHep : ratio;
|
const ratioApplique = isHepPoste ? ratioHep : ratio;
|
||||||
let montant: number;
|
let montant: number;
|
||||||
if (isManuel && etabOverrides[ligne.libelle] !== undefined) {
|
if (etab.modeManuel) {
|
||||||
|
// Mode tout manuel : utiliser uniquement les montants saisis manuellement,
|
||||||
|
// sans aucun calcul prorata — si pas de saisie, le montant est 0
|
||||||
|
montant = etabOverrides[ligne.libelle] ?? 0;
|
||||||
|
} else if (isManuel && etabOverrides[ligne.libelle] !== undefined) {
|
||||||
montant = etabOverrides[ligne.libelle];
|
montant = etabOverrides[ligne.libelle];
|
||||||
} else if (!isManuel && ligne.montant > 0) {
|
} else if (!isManuel && ligne.montant > 0) {
|
||||||
montant = Math.round(ligne.montant * ratioApplique);
|
montant = Math.round(ligne.montant * ratioApplique);
|
||||||
@@ -1707,12 +1766,21 @@ export default function DsiOpex() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{sortedEtabs.map((etab, idx) => (
|
{sortedEtabs.map((etab, idx) => {
|
||||||
<tr key={etab.code} className={`border-b border-border/50 hover:bg-muted/20 transition-colors ${idx % 2 === 0 ? '' : 'bg-muted/5'}`}>
|
const rowBg = etab.modeManuel ? 'oklch(0.98 0.02 80)' : (idx % 2 === 0 ? 'oklch(1 0 0)' : 'oklch(0.97 0.003 80)');
|
||||||
<td className="px-3 py-2 whitespace-nowrap" style={{ position: 'sticky', left: 0, zIndex: 20, background: idx % 2 === 0 ? 'oklch(1 0 0)' : 'oklch(0.97 0.003 80)', boxShadow: '2px 0 4px -1px rgba(0,0,0,0.12)', width: '110px', minWidth: '110px', maxWidth: '110px' }}>
|
return (
|
||||||
|
<tr key={etab.code} className={`border-b border-border/50 hover:bg-muted/20 transition-colors ${etab.modeManuel ? 'bg-amber-50/40' : idx % 2 === 0 ? '' : 'bg-muted/5'}`}>
|
||||||
|
<td className="px-3 py-2 whitespace-nowrap" style={{ position: 'sticky', left: 0, zIndex: 20, background: rowBg, boxShadow: '2px 0 4px -1px rgba(0,0,0,0.12)', width: '110px', minWidth: '110px', maxWidth: '110px' }}>
|
||||||
<span className="font-mono bg-muted px-1.5 py-0.5 rounded text-muted-foreground">{etab.code}</span>
|
<span className="font-mono bg-muted px-1.5 py-0.5 rounded text-muted-foreground">{etab.code}</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 font-medium text-foreground whitespace-nowrap overflow-hidden text-ellipsis" style={{ position: 'sticky', left: '110px', zIndex: 20, background: idx % 2 === 0 ? 'oklch(1 0 0)' : 'oklch(0.97 0.003 80)', boxShadow: '2px 0 8px -2px rgba(0,0,0,0.2)', width: '220px', minWidth: '220px', maxWidth: '220px' }} title={etab.nom}>{etab.nom}</td>
|
<td className="px-3 py-2 font-medium text-foreground whitespace-nowrap overflow-hidden text-ellipsis" style={{ position: 'sticky', left: '110px', zIndex: 20, background: rowBg, boxShadow: '2px 0 8px -2px rgba(0,0,0,0.2)', width: '220px', minWidth: '220px', maxWidth: '220px' }} title={etab.nom}>
|
||||||
|
{etab.nom}
|
||||||
|
{etab.modeManuel && (
|
||||||
|
<span className="ml-1.5 inline-flex items-center px-1 py-0.5 rounded text-[10px] font-semibold bg-amber-100 text-amber-700 border border-amber-200" title="Mode tout manuel activé — montants saisis manuellement, pas de calcul prorata">
|
||||||
|
Manuel
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
{lignes.map(ligne => {
|
{lignes.map(ligne => {
|
||||||
const isManuel = ligne.mode_ventilation === 'Manuel' || ligne.isCustom;
|
const isManuel = ligne.mode_ventilation === 'Manuel' || ligne.isCustom;
|
||||||
const montant = etab.montants[ligne.libelle] ?? 0;
|
const montant = etab.montants[ligne.libelle] ?? 0;
|
||||||
@@ -1770,7 +1838,8 @@ export default function DsiOpex() {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr className="bg-muted/40 border-t-2 border-border">
|
<tr className="bg-muted/40 border-t-2 border-border">
|
||||||
|
|||||||
2
drizzle/0003_tense_havok.sql
Normal file
2
drizzle/0003_tense_havok.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `opex_bases_repartition` ADD `modeManuel` boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE `opex_bases_repartition` ADD CONSTRAINT `opex_bases_repartition_annee_etab_idx` UNIQUE(`annee`,`etablissementCode`);
|
||||||
889
drizzle/meta/0003_snapshot.json
Normal file
889
drizzle/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,889 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "mysql",
|
||||||
|
"id": "44abeed3-e7bf-47cd-a29c-59a27bf57f1a",
|
||||||
|
"prevId": "baff50d1-531e-4ba7-a82a-6f9c64aa1186",
|
||||||
|
"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": {},
|
||||||
|
"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": {},
|
||||||
|
"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": {},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,13 @@
|
|||||||
"when": 1781161698458,
|
"when": 1781161698458,
|
||||||
"tag": "0002_faithful_famine",
|
"tag": "0002_faithful_famine",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1781163262493,
|
||||||
|
"tag": "0003_tense_havok",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
mysqlTable,
|
mysqlTable,
|
||||||
text,
|
text,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
uniqueIndex,
|
||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/mysql-core";
|
} from "drizzle-orm/mysql-core";
|
||||||
|
|
||||||
@@ -174,8 +175,12 @@ export const opexBasesRepartition = mysqlTable("opex_bases_repartition", {
|
|||||||
baseRepartition: decimal("baseRepartition", { precision: 20, scale: 6 }).notNull(),
|
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) */
|
/** 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"),
|
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(),
|
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 OpexBaseRepartition = typeof opexBasesRepartition.$inferSelect;
|
||||||
export type InsertOpexBaseRepartition = typeof opexBasesRepartition.$inferInsert;
|
export type InsertOpexBaseRepartition = typeof opexBasesRepartition.$inferInsert;
|
||||||
|
|||||||
@@ -344,6 +344,7 @@ export async function upsertOpexBaseRepartition(input: {
|
|||||||
etablissementNom?: string | null;
|
etablissementNom?: string | null;
|
||||||
baseRepartition: number;
|
baseRepartition: number;
|
||||||
baseRepartitionHep?: number;
|
baseRepartitionHep?: number;
|
||||||
|
modeManuel?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
if (!db) throw new Error("Database not available");
|
if (!db) throw new Error("Database not available");
|
||||||
@@ -355,12 +356,14 @@ export async function upsertOpexBaseRepartition(input: {
|
|||||||
etablissementNom: input.etablissementNom ?? null,
|
etablissementNom: input.etablissementNom ?? null,
|
||||||
baseRepartition: String(input.baseRepartition),
|
baseRepartition: String(input.baseRepartition),
|
||||||
baseRepartitionHep: String(input.baseRepartitionHep ?? 0),
|
baseRepartitionHep: String(input.baseRepartitionHep ?? 0),
|
||||||
|
modeManuel: input.modeManuel ?? false,
|
||||||
})
|
})
|
||||||
.onDuplicateKeyUpdate({
|
.onDuplicateKeyUpdate({
|
||||||
set: {
|
set: {
|
||||||
baseRepartition: String(input.baseRepartition),
|
baseRepartition: String(input.baseRepartition),
|
||||||
baseRepartitionHep: String(input.baseRepartitionHep ?? 0),
|
baseRepartitionHep: String(input.baseRepartitionHep ?? 0),
|
||||||
etablissementNom: input.etablissementNom ?? null,
|
etablissementNom: input.etablissementNom ?? null,
|
||||||
|
modeManuel: input.modeManuel ?? false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export const appRouter = router({
|
|||||||
etablissementNom: z.string().optional().nullable(),
|
etablissementNom: z.string().optional().nullable(),
|
||||||
baseRepartition: z.number(),
|
baseRepartition: z.number(),
|
||||||
baseRepartitionHep: z.number().optional().default(0),
|
baseRepartitionHep: z.number().optional().default(0),
|
||||||
|
modeManuel: z.boolean().optional().default(false),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input }) => { await db.upsertOpexBaseRepartition(input); return { success: true }; }),
|
.mutation(async ({ input }) => { await db.upsertOpexBaseRepartition(input); return { success: true }; }),
|
||||||
|
|
||||||
|
|||||||
5
todo.md
5
todo.md
@@ -56,3 +56,8 @@
|
|||||||
|
|
||||||
- [x] Ajouter un vrai loading/error state pour l'onglet « Clés de répartition » basé sur la requête `opex.getBasesRepartition`
|
- [x] Ajouter un vrai loading/error state pour l'onglet « Clés de répartition » basé sur la requête `opex.getBasesRepartition`
|
||||||
- [x] Rendre l'import des clés de répartition compatible avec le classeur source réel : détection intelligente de la bonne feuille (mots-clés OPEX/DSI/répartition/base), normalisation des accents, validation des colonnes attendues avec message d'erreur détaillé
|
- [x] Rendre l'import des clés de répartition compatible avec le classeur source réel : détection intelligente de la bonne feuille (mots-clés OPEX/DSI/répartition/base), normalisation des accents, validation des colonnes attendues avec message d'erreur détaillé
|
||||||
|
|
||||||
|
## Bugs et nouvelles fonctionnalités (post-checkpoint cfeb5196)
|
||||||
|
|
||||||
|
- [x] Bug : doublon de ligne dans le tableau Clés de répartition au clic (mutation setBaseRepartition provoque un double rendu)
|
||||||
|
- [x] Fonctionnalité : mode "tout manuel" par établissement dans l'onglet Clés de répartition (toutes les colonnes OPEX de cet établissement saisies manuellement, ignorant le calcul prorata)
|
||||||
|
|||||||
Reference in New Issue
Block a user