diff --git a/client/src/App.tsx b/client/src/App.tsx index a75f062..5533ba3 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -19,6 +19,7 @@ import Santinova from "./pages/Santinova"; import SoinsSante from "./pages/SoinsSante"; import StExupery from "./pages/StExupery"; import TableauBordFinance from "./pages/TableauBordFinance"; +import MasseSalariale from "./pages/MasseSalariale"; import { useAuth } from "./_core/hooks/useAuth"; import { DashboardLayoutSkeleton } from "./components/DashboardLayoutSkeleton"; @@ -40,6 +41,24 @@ function ProtectedRoute({ component: Component }: { component: React.ComponentTy return ; } +/** + * Protection d'interface complémentaire aux contrôles tRPC administrateur. Elle + * redirige les profils non autorisés avant tout rendu de données salariales. + */ +function AdminRoute({ component: Component }: { component: React.ComponentType }) { + const { user, loading } = useAuth(); + const [, navigate] = useLocation(); + + useEffect(() => { + if (!loading && !user) navigate("/login"); + if (!loading && user?.role !== "admin") navigate("/"); + }, [loading, user, navigate]); + + if (loading) return ; + if (!user || user.role !== "admin") return null; + return ; +} + function Router() { return ( @@ -74,6 +93,9 @@ function Router() { + + + diff --git a/client/src/components/AppSidebar.tsx b/client/src/components/AppSidebar.tsx index 8ad0f10..4ac7e7e 100644 --- a/client/src/components/AppSidebar.tsx +++ b/client/src/components/AppSidebar.tsx @@ -31,6 +31,7 @@ import { TableProperties, Upload, LayoutDashboard, + Banknote, } from 'lucide-react'; // Palette de couleurs par section principale @@ -102,6 +103,8 @@ interface NavLeaf { label: string; icon: React.ElementType; path: string; + /** Les données de paie ne doivent jamais apparaître aux profils non admin. */ + adminOnly?: boolean; } interface NavSection { @@ -129,7 +132,10 @@ const NAV_SECTIONS: NavSection[] = [ id: 'santinova', label: 'SANTINOVA', icon: Heart, - path: '/santinova', + children: [ + { id: 'santinova', label: 'Vue SANTINOVA', icon: Heart, path: '/santinova' }, + { id: 'masse-salariale', label: 'Masse salariale', icon: Banknote, path: '/masse-salariale', adminOnly: true }, + ], }, { id: 'soins-sante', @@ -228,6 +234,9 @@ export function AppSidebar({ collapsed = false, onToggle }: AppSidebarProps) { const isActive = sectionHasActive(section, location); const isOpen = openMenus.has(section.id); const SectionIcon = section.icon; + const visibleChildren = section.children?.filter( + (leaf) => !leaf.adminOnly || user?.role === 'admin', + ); // Section sans sous-menu (feuille directe) if (!section.children) { @@ -283,9 +292,9 @@ export function AppSidebar({ collapsed = false, onToggle }: AppSidebarProps) { {/* Items directs — un seul niveau */} - {!collapsed && isOpen && ( + {!collapsed && isOpen && visibleChildren && visibleChildren.length > 0 && (
- {section.children.map(leaf => { + {visibleChildren.map(leaf => { const leafActive = isLeafActive(leaf.path!, location); const LeafIcon = leaf.icon; return ( diff --git a/client/src/components/MasseSalarialeDataState.test.tsx b/client/src/components/MasseSalarialeDataState.test.tsx new file mode 100644 index 0000000..68af2b9 --- /dev/null +++ b/client/src/components/MasseSalarialeDataState.test.tsx @@ -0,0 +1,27 @@ +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; +import { MasseSalarialeDataState } from './MasseSalarialeDataState'; + +describe('MasseSalarialeDataState', () => { + it.each([ + ['loading', 'Chargement des données salariales…'], + ['error', "Les données de masse salariale n'ont pas pu être chargées."], + ['empty', "Aucune donnée salariale n'est encore importée."], + ] as const)('rend réellement le message %s', (state, expectedText) => { + const html = renderToStaticMarkup( + {'TABLEAU NE DOIT PAS ÊTRE VISIBLE'}, + ); + // Le rendu statique encode les apostrophes en entités HTML ; la + // normalisation permet de tester le texte effectivement affiché. + const visibleText = html.replaceAll(''', "'"); + expect(visibleText).toContain(expectedText); + expect(html).not.toContain('TABLEAU NE DOIT PAS ÊTRE VISIBLE'); + }); + + it('rend réellement le contenu du tableau lorsque les données sont prêtes', () => { + const html = renderToStaticMarkup( +

TABLEAU PRÊT

, + ); + expect(html).toContain('TABLEAU PRÊT'); + }); +}); diff --git a/client/src/components/MasseSalarialeDataState.tsx b/client/src/components/MasseSalarialeDataState.tsx new file mode 100644 index 0000000..423971d --- /dev/null +++ b/client/src/components/MasseSalarialeDataState.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from 'react'; +import type { MasseSalarialeUiState } from '../lib/masseSalariale'; +import { Loader2 } from 'lucide-react'; + +/** + * Rend les états de données de la page de paie. Ce composant isolé permet de + * vérifier le rendu effectif de chaque état sans exposer de donnée salariale. + */ +export function MasseSalarialeDataState({ + state, + children, +}: { + state: MasseSalarialeUiState; + children: ReactNode; +}) { + if (state === 'loading') { + return ( +
+ + Chargement des données salariales… +
+ ); + } + if (state === 'error') { + return
Les données de masse salariale n'ont pas pu être chargées.
; + } + if (state === 'empty') { + return
Aucune donnée salariale n'est encore importée.
; + } + return <>{children}; +} diff --git a/client/src/lib/masseSalariale.test.ts b/client/src/lib/masseSalariale.test.ts new file mode 100644 index 0000000..874050b --- /dev/null +++ b/client/src/lib/masseSalariale.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; +import { getMasseSalarialeUiState, getMasseSalarialeYearSummary } from './masseSalariale'; + +describe('getMasseSalarialeUiState', () => { + it('distingue chargement, erreur, absence de données et affichage prêt', () => { + expect(getMasseSalarialeUiState(true, false, 0)).toBe('loading'); + expect(getMasseSalarialeUiState(false, true, 0)).toBe('error'); + expect(getMasseSalarialeUiState(false, false, 0)).toBe('empty'); + expect(getMasseSalarialeUiState(false, false, 1)).toBe('ready'); + }); +}); + +describe('getMasseSalarialeYearSummary', () => { + const rows = [ + { annee: 2025, salaireAnnuelBrutAvecPrimesCents: 4_000_000, statut: 'annuel' }, + { annee: 2025, salaireAnnuelBrutAvecPrimesCents: 5_500_000, statut: 'annuel' }, + { annee: 2026, salaireAnnuelBrutAvecPrimesCents: 2_000_000, statut: 'cumul_provisoire' }, + ]; + + it('calcule le total et le statut annuel pour un exercice clôturé', () => { + expect(getMasseSalarialeYearSummary(rows, 2025)).toEqual({ + salaryCount: 2, + annualGrossWithBonusesCents: 9_500_000, + status: 'annuel', + }); + }); + + it('préserve le statut provisoire pour une année en cours', () => { + expect(getMasseSalarialeYearSummary(rows, 2026)).toMatchObject({ + salaryCount: 1, + annualGrossWithBonusesCents: 2_000_000, + status: 'cumul_provisoire', + }); + }); + + it('retourne null et non zéro pour une année sans rémunération importée', () => { + expect(getMasseSalarialeYearSummary(rows, 2027)).toEqual({ + salaryCount: 0, + annualGrossWithBonusesCents: null, + status: 'empty', + }); + }); +}); diff --git a/client/src/lib/masseSalariale.ts b/client/src/lib/masseSalariale.ts new file mode 100644 index 0000000..d3e94c4 --- /dev/null +++ b/client/src/lib/masseSalariale.ts @@ -0,0 +1,59 @@ +/** Fonctions pures partagées par l'interface Masse salariale et ses tests. */ + +export type RemunerationForSummary = { + annee: number; + salaireAnnuelBrutAvecPrimesCents: number; + statut: string; +}; + +export type MasseSalarialeUiState = 'loading' | 'error' | 'empty' | 'ready'; + +export type YearSummary = { + salaryCount: number; + annualGrossWithBonusesCents: number | null; + status: 'annuel' | 'cumul_provisoire' | 'mixed' | 'empty'; +}; + +/** + * Centralise les quatre états visuels afin que le tableau ne puisse jamais + * présenter une absence de données comme un état chargé à zéro. + */ +export function getMasseSalarialeUiState( + isLoading: boolean, + isError: boolean, + salaryCount: number, +): MasseSalarialeUiState { + if (isLoading) return 'loading'; + if (isError) return 'error'; + return salaryCount === 0 ? 'empty' : 'ready'; +} + +/** + * Agrège uniquement les rémunérations réellement importées d'un exercice. + * L'absence de ligne renvoie `null` pour distinguer « non renseigné » de 0 €. + */ +export function getMasseSalarialeYearSummary( + remunerations: RemunerationForSummary[], + annee: number, +): YearSummary { + const rows = remunerations.filter((row) => row.annee === annee); + if (rows.length === 0) { + return { salaryCount: 0, annualGrossWithBonusesCents: null, status: 'empty' }; + } + + const statuses = new Set(rows.map((row) => row.statut)); + const status = statuses.size === 1 && statuses.has('annuel') + ? 'annuel' + : statuses.size === 1 && statuses.has('cumul_provisoire') + ? 'cumul_provisoire' + : 'mixed'; + + return { + salaryCount: rows.length, + annualGrossWithBonusesCents: rows.reduce( + (sum, row) => sum + row.salaireAnnuelBrutAvecPrimesCents, + 0, + ), + status, + }; +} diff --git a/client/src/pages/MasseSalariale.tsx b/client/src/pages/MasseSalariale.tsx new file mode 100644 index 0000000..deada39 --- /dev/null +++ b/client/src/pages/MasseSalariale.tsx @@ -0,0 +1,267 @@ +// MasseSalariale.tsx — Données de rémunération SANTINOVA (accès administrateur) + +import { Fragment, useMemo, useState } from 'react'; +import { AppSidebar } from '../components/AppSidebar'; +import { MasseSalarialeDataState } from '../components/MasseSalarialeDataState'; +import { trpc } from '../lib/trpc'; +import { getMasseSalarialeUiState, getMasseSalarialeYearSummary } from '../lib/masseSalariale'; +import { toast } from 'sonner'; +import { + Banknote, + CalendarDays, + CircleAlert, + ShieldCheck, + Users, +} from 'lucide-react'; + +const ANNEES = [2025, 2026, 2027] as const; + +function formatEuros(cents?: number | null) { + if (cents === undefined || cents === null) return '—'; + return new Intl.NumberFormat('fr-FR', { + style: 'currency', + currency: 'EUR', + maximumFractionDigits: 0, + }).format(cents / 100); +} + +function formatPercent(bps?: number | null) { + if (bps === undefined || bps === null) return '—'; + return `${(bps / 100).toLocaleString('fr-FR', { maximumFractionDigits: 2 })} %`; +} + +function formatDate(isoDate?: string | null) { + if (!isoDate) return '—'; + const [year, month, day] = isoDate.split('-'); + return year && month && day ? `${day}/${month}/${year}` : isoDate; +} + +function evolutionKey(salarieId: number, annee: number) { + return `${salarieId}-${annee}`; +} + +/** + * Les revenus annuels sont strictement ceux présents dans les bulletins + * importés. L'évolution, elle, reste une hypothèse utilisateur distincte et + * n'engendre donc jamais un montant 2027 artificiel. + */ +export default function MasseSalariale() { + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + const [editingKey, setEditingKey] = useState(null); + const [evolutionDraft, setEvolutionDraft] = useState(''); + const utils = trpc.useUtils(); + const { data, isLoading, isError } = trpc.masseSalariale.get.useQuery(); + const setEvolution = trpc.masseSalariale.setEvolution.useMutation({ + onSuccess: async () => { + await utils.masseSalariale.get.invalidate(); + setEditingKey(null); + setEvolutionDraft(''); + toast.success('Évolution salariale enregistrée'); + }, + onError: () => toast.error("L'évolution salariale n'a pas pu être enregistrée"), + }); + + const remunerationByKey = useMemo( + () => new Map((data?.remunerations ?? []).map((row) => [evolutionKey(row.salarieId, row.annee), row])), + [data?.remunerations], + ); + const evolutionByKey = useMemo( + () => new Map((data?.evolutions ?? []).map((row) => [evolutionKey(row.salarieId, row.annee), row])), + [data?.evolutions], + ); + + const uiState = getMasseSalarialeUiState(isLoading, isError, data?.salaries.length ?? 0); + const totalsByYear = useMemo( + () => new Map(ANNEES.map((annee) => [ + annee, + getMasseSalarialeYearSummary(data?.remunerations ?? [], annee), + ])), + [data?.remunerations], + ); + + const startEvolutionEdit = (salarieId: number, annee: number) => { + const key = evolutionKey(salarieId, annee); + const current = evolutionByKey.get(key)?.evolutionSalarialeBps; + setEditingKey(key); + setEvolutionDraft(current === undefined || current === null ? '' : String(current / 100)); + }; + + const saveEvolution = (salarieId: number, annee: number) => { + const normalized = evolutionDraft.trim().replace(',', '.'); + const percentage = Number(normalized); + if (!normalized || !Number.isFinite(percentage) || percentage < -100 || percentage > 100) { + toast.error('Saisissez une évolution comprise entre -100 % et 100 %.'); + return; + } + setEvolution.mutate({ salarieId, annee, evolutionSalarialeBps: Math.round(percentage * 100) }); + }; + + return ( +
+ setSidebarCollapsed((value) => !value)} /> + +
+
+
+
+
+ +
+
+

+ Masse salariale — SANTINOVA +

+

+ Rémunérations 2025–2027 et évolutions salariales manuelles +

+
+
+
+ + Accès administrateur +
+
+
+ +
+
+ {ANNEES.map((annee) => { + const total = totalsByYear.get(annee); + const isProvisional = total?.status === 'cumul_provisoire'; + return ( +
+
+
+ + {annee} +
+ + {total?.status === 'annuel' ? 'Année clôturée' : isProvisional ? 'Cumul au dernier bulletin' : 'À renseigner'} + +
+

+ {formatEuros(total?.annualGrossWithBonusesCents)} +

+

+ Brut avec primes — {total?.salaryCount ?? 0} salarié{(total?.salaryCount ?? 0) > 1 ? 's' : ''} +

+
+ ); + })} +
+ +
+ +

+ 2025 est issu des cumuls annuels de décembre. 2026 est un cumul provisoire au dernier bulletin disponible pour chaque salarié (janvier à août). Aucun salaire 2027 n’est calculé automatiquement ; seules les évolutions peuvent être saisies. +

+
+ +
+
+
+ +
+
+

Détail par salarié

+

Cliquez sur une évolution pour la saisir ou la modifier.

+
+
+ + +
+ + + + + + + {ANNEES.map((annee) => )} + + + {ANNEES.flatMap((annee) => [ + , + , + , + , + , + ])} + + + + {data?.salaries.map((salarie) => ( + + + + + {ANNEES.map((annee) => { + const remuneration = remunerationByKey.get(evolutionKey(salarie.id, annee)); + const evolution = evolutionByKey.get(evolutionKey(salarie.id, annee)); + const key = evolutionKey(salarie.id, annee); + return ( + + + + + + + + ); + })} + + ))} + +
SalariéPosteEmbauche{annee}
Annuel hors primesAnnuel avec primesMensuel hors primesTaux chargesÉvolution
+

{salarie.nom} {salarie.prenom}

+

{salarie.matricule}

+
{salarie.poste}{formatDate(salarie.dateEmbauche)}{formatEuros(remuneration?.salaireAnnuelBrutHorsPrimesCents)}{formatEuros(remuneration?.salaireAnnuelBrutAvecPrimesCents)}{formatEuros(remuneration?.salaireMensuelBrutHorsPrimesCents)}{formatPercent(remuneration?.tauxChargeBps)} + {editingKey === key ? ( +
+ setEvolutionDraft(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') saveEvolution(salarie.id, annee); + if (event.key === 'Escape') setEditingKey(null); + }} + className="w-20 rounded border border-emerald-400 bg-white px-2 py-1 text-right text-xs focus:outline-none focus:ring-2 focus:ring-emerald-300" + autoFocus + disabled={setEvolution.isPending} + /> + +
+ ) : ( + + )} +
+
+
+
+
+
+
+ ); +} diff --git a/docs/masse-salariale.md b/docs/masse-salariale.md new file mode 100644 index 0000000..38768dd --- /dev/null +++ b/docs/masse-salariale.md @@ -0,0 +1,67 @@ +# Module Masse salariale Santinova + +## Périmètre et accès + +Le module stocke des **données de rémunération à caractère sensible**. L'accès +au menu, à la page et à toutes les procédures de lecture ou d'écriture est +réservé au rôle `admin`. Les sources de paie importées ne sont pas conservées +dans le code source ni dans le stockage applicatif ; seuls les champs métier +nécessaires au tableau sont enregistrés en base. + +## Champs du tableau + +Chaque salarié est identifié par son matricule paie. Le tableau comprend le +nom, le prénom, le poste et la date d'embauche ainsi que, pour 2025, 2026 et +2027 : salaire annuel brut hors primes, salaire annuel brut avec primes, +salaire mensuel brut hors primes, taux de charge et évolution salariale. + +| Champ | Origine | Règle de traitement | +|---|---|---| +| Salaire mensuel brut hors primes | Ligne « Salaire de base » | Montant de la paie source. | +| Salaire annuel brut hors primes | Salaire mensuel brut hors primes | Calcul : montant mensuel × 12. | +| Salaire annuel brut avec primes | Synthèse « Annuel — Brut » | Montant cumulé annuel fourni par la paie. Pour une année non clôturée, le statut indique le mois de référence. | +| Taux de charge | Synthèse « Annuel — Charges patronales » | Calcul : charges patronales ÷ brut annuel avec primes. | +| Évolution salariale | Saisie utilisateur | Pourcentage manuel, stocké séparément des montants importés ; il peut donc être renseigné pour 2027 sans créer de rémunération fictive. | + +## Sources analysées + +Les bulletins de décembre 2025 fournissent un cumul annuel complet. Les +bulletins 2026 reçus couvrent janvier à août ; le relevé le plus récent par +salarié sera associé à son mois de référence afin de ne pas présenter un cumul +partiel comme une année clôturée (`cumul_provisoire`). Les années ou salariés +sans bulletin ne seront jamais créés avec des données fictives : le tableau +affichera une valeur absente et un statut de donnée à compléter. + +## Règles de sécurité et d'intégrité + +Le couple `(matricule, année)` est unique. Une importation est idempotente : la +dernière source validée remplace les valeurs extraites du même salarié pour la +même année, sans écraser une évolution salariale saisie manuellement. Les +montants sont stockés en centimes et les pourcentages en points de base pour +éviter toute erreur d'arrondi. + +## Contrôle de l'interface + +La page de connexion est disponible sur l'environnement de développement. Le +contrôle du tableau Masse salariale est réalisé avec un compte administrateur, +afin de vérifier simultanément l'affichage des données et leur cloisonnement +par rôle. + +Un accès direct à `/masse-salariale` sans session est redirigé vers `/login`. +La connexion avec le compte administrateur de test est acceptée et le menu +SANTINOVA devient accessible dans la barre latérale. + +Le contrôle visuel confirme que le tableau s'affiche sous SANTINOVA avec les +19 salariés importés, les cumuls 2025 et 2026 correctement signalés, ainsi +qu'une colonne 2027 vide plutôt qu'un total salarial fictif. +Une cellule d'évolution 2027 devient éditable au clic et peut être annulée par +la touche Échap, sans modifier la base de données. + +## Modalité d'import + +L'import initial des bulletins transmis est réalisé par un outil contrôlé hors +de l'interface. Cette mesure évite d'exposer des documents de paie ou leurs +données brutes dans le navigateur. Les procédures d'import tRPC existent et +sont réservées aux administrateurs pour une future intégration d'import interne +après validation du format de fichier et des règles de conservation. Aucun +fichier de paie n'est versionné dans le dépôt applicatif. diff --git a/drizzle/0005_nasty_tattoo.sql b/drizzle/0005_nasty_tattoo.sql new file mode 100644 index 0000000..64b5601 --- /dev/null +++ b/drizzle/0005_nasty_tattoo.sql @@ -0,0 +1,30 @@ +CREATE TABLE `masse_salariale_remunerations` ( + `id` int AUTO_INCREMENT NOT NULL, + `salarieId` int NOT NULL, + `annee` int NOT NULL, + `salaireAnnuelBrutHorsPrimesCents` int NOT NULL, + `salaireAnnuelBrutAvecPrimesCents` int NOT NULL, + `salaireMensuelBrutHorsPrimesCents` int NOT NULL, + `tauxChargeBps` int NOT NULL, + `evolutionSalarialeBps` int, + `periodeReference` varchar(7), + `source` varchar(64) NOT NULL DEFAULT 'bulletin', + `createdAt` timestamp NOT NULL DEFAULT (now()), + `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `masse_salariale_remunerations_id` PRIMARY KEY(`id`), + CONSTRAINT `masse_salariale_salarie_annee_idx` UNIQUE(`salarieId`,`annee`) +); +--> statement-breakpoint +CREATE TABLE `masse_salariale_salaries` ( + `id` int AUTO_INCREMENT NOT NULL, + `matricule` varchar(32) NOT NULL, + `nom` varchar(150) NOT NULL, + `prenom` varchar(150) NOT NULL, + `poste` varchar(255) NOT NULL, + `dateEmbauche` varchar(10) NOT NULL, + `actif` boolean NOT NULL DEFAULT true, + `createdAt` timestamp NOT NULL DEFAULT (now()), + `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `masse_salariale_salaries_id` PRIMARY KEY(`id`), + CONSTRAINT `masse_salariale_salaries_matricule_unique` UNIQUE(`matricule`) +); diff --git a/drizzle/0006_ambitious_shen.sql b/drizzle/0006_ambitious_shen.sql new file mode 100644 index 0000000..4521767 --- /dev/null +++ b/drizzle/0006_ambitious_shen.sql @@ -0,0 +1 @@ +ALTER TABLE `masse_salariale_remunerations` ADD `statut` varchar(32) DEFAULT 'annuel' NOT NULL; \ No newline at end of file diff --git a/drizzle/0007_rich_tony_stark.sql b/drizzle/0007_rich_tony_stark.sql new file mode 100644 index 0000000..7c7e6e1 --- /dev/null +++ b/drizzle/0007_rich_tony_stark.sql @@ -0,0 +1,13 @@ +CREATE TABLE `masse_salariale_evolutions` ( + `id` int AUTO_INCREMENT NOT NULL, + `salarieId` int NOT NULL, + `annee` int NOT NULL, + `evolutionSalarialeBps` int NOT NULL, + `createdBy` int NOT NULL, + `createdAt` timestamp NOT NULL DEFAULT (now()), + `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT `masse_salariale_evolutions_id` PRIMARY KEY(`id`), + CONSTRAINT `masse_salariale_evolution_salarie_annee_idx` UNIQUE(`salarieId`,`annee`) +); +--> statement-breakpoint +ALTER TABLE `masse_salariale_remunerations` DROP COLUMN `evolutionSalarialeBps`; \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..577358c --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,1123 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "3f16aeeb-a342-40c3-be2c-cb0d9d5fedd7", + "prevId": "fcb67a8c-d69f-4eab-b81e-e3256730a27b", + "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": { + "capex_lignes_annee_etab_cle_idx": { + "name": "capex_lignes_annee_etab_cle_idx", + "columns": [ + "annee", + "etablissementCode", + "cle" + ], + "isUnique": true + } + }, + "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": {} + }, + "masse_salariale_remunerations": { + "name": "masse_salariale_remunerations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "salarieId": { + "name": "salarieId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "annee": { + "name": "annee", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireAnnuelBrutHorsPrimesCents": { + "name": "salaireAnnuelBrutHorsPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireAnnuelBrutAvecPrimesCents": { + "name": "salaireAnnuelBrutAvecPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireMensuelBrutHorsPrimesCents": { + "name": "salaireMensuelBrutHorsPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tauxChargeBps": { + "name": "tauxChargeBps", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "evolutionSalarialeBps": { + "name": "evolutionSalarialeBps", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "periodeReference": { + "name": "periodeReference", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'bulletin'" + }, + "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": { + "masse_salariale_salarie_annee_idx": { + "name": "masse_salariale_salarie_annee_idx", + "columns": [ + "salarieId", + "annee" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "masse_salariale_remunerations_id": { + "name": "masse_salariale_remunerations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "masse_salariale_salaries": { + "name": "masse_salariale_salaries", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "matricule": { + "name": "matricule", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nom": { + "name": "nom", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prenom": { + "name": "prenom", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "poste": { + "name": "poste", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dateEmbauche": { + "name": "dateEmbauche", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "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": { + "masse_salariale_salaries_id": { + "name": "masse_salariale_salaries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "masse_salariale_salaries_matricule_unique": { + "name": "masse_salariale_salaries_matricule_unique", + "columns": [ + "matricule" + ] + } + }, + "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": { + "opex_montants_annee_etab_poste_idx": { + "name": "opex_montants_annee_etab_poste_idx", + "columns": [ + "annee", + "etablissementCode", + "libellePoste" + ], + "isUnique": true + } + }, + "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": { + "opex_postes_annee_libelle_idx": { + "name": "opex_postes_annee_libelle_idx", + "columns": [ + "annee", + "libelle" + ], + "isUnique": true + } + }, + "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": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..5939a44 --- /dev/null +++ b/drizzle/meta/0006_snapshot.json @@ -0,0 +1,1131 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "23c93054-c989-4cf4-8ab8-2ee5bc79326c", + "prevId": "3f16aeeb-a342-40c3-be2c-cb0d9d5fedd7", + "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": { + "capex_lignes_annee_etab_cle_idx": { + "name": "capex_lignes_annee_etab_cle_idx", + "columns": [ + "annee", + "etablissementCode", + "cle" + ], + "isUnique": true + } + }, + "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": {} + }, + "masse_salariale_remunerations": { + "name": "masse_salariale_remunerations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "salarieId": { + "name": "salarieId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "annee": { + "name": "annee", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireAnnuelBrutHorsPrimesCents": { + "name": "salaireAnnuelBrutHorsPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireAnnuelBrutAvecPrimesCents": { + "name": "salaireAnnuelBrutAvecPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireMensuelBrutHorsPrimesCents": { + "name": "salaireMensuelBrutHorsPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tauxChargeBps": { + "name": "tauxChargeBps", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "evolutionSalarialeBps": { + "name": "evolutionSalarialeBps", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "periodeReference": { + "name": "periodeReference", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "statut": { + "name": "statut", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'annuel'" + }, + "source": { + "name": "source", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'bulletin'" + }, + "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": { + "masse_salariale_salarie_annee_idx": { + "name": "masse_salariale_salarie_annee_idx", + "columns": [ + "salarieId", + "annee" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "masse_salariale_remunerations_id": { + "name": "masse_salariale_remunerations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "masse_salariale_salaries": { + "name": "masse_salariale_salaries", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "matricule": { + "name": "matricule", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nom": { + "name": "nom", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prenom": { + "name": "prenom", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "poste": { + "name": "poste", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dateEmbauche": { + "name": "dateEmbauche", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "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": { + "masse_salariale_salaries_id": { + "name": "masse_salariale_salaries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "masse_salariale_salaries_matricule_unique": { + "name": "masse_salariale_salaries_matricule_unique", + "columns": [ + "matricule" + ] + } + }, + "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": { + "opex_montants_annee_etab_poste_idx": { + "name": "opex_montants_annee_etab_poste_idx", + "columns": [ + "annee", + "etablissementCode", + "libellePoste" + ], + "isUnique": true + } + }, + "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": { + "opex_postes_annee_libelle_idx": { + "name": "opex_postes_annee_libelle_idx", + "columns": [ + "annee", + "libelle" + ], + "isUnique": true + } + }, + "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": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0007_snapshot.json b/drizzle/meta/0007_snapshot.json new file mode 100644 index 0000000..58a4403 --- /dev/null +++ b/drizzle/meta/0007_snapshot.json @@ -0,0 +1,1202 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "7c1a4a03-f900-42db-8845-2ae5a712b39a", + "prevId": "23c93054-c989-4cf4-8ab8-2ee5bc79326c", + "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": { + "capex_lignes_annee_etab_cle_idx": { + "name": "capex_lignes_annee_etab_cle_idx", + "columns": [ + "annee", + "etablissementCode", + "cle" + ], + "isUnique": true + } + }, + "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": {} + }, + "masse_salariale_evolutions": { + "name": "masse_salariale_evolutions", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "salarieId": { + "name": "salarieId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "annee": { + "name": "annee", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "evolutionSalarialeBps": { + "name": "evolutionSalarialeBps", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdBy": { + "name": "createdBy", + "type": "int", + "primaryKey": false, + "notNull": true, + "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": { + "masse_salariale_evolution_salarie_annee_idx": { + "name": "masse_salariale_evolution_salarie_annee_idx", + "columns": [ + "salarieId", + "annee" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "masse_salariale_evolutions_id": { + "name": "masse_salariale_evolutions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "masse_salariale_remunerations": { + "name": "masse_salariale_remunerations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "salarieId": { + "name": "salarieId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "annee": { + "name": "annee", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireAnnuelBrutHorsPrimesCents": { + "name": "salaireAnnuelBrutHorsPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireAnnuelBrutAvecPrimesCents": { + "name": "salaireAnnuelBrutAvecPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salaireMensuelBrutHorsPrimesCents": { + "name": "salaireMensuelBrutHorsPrimesCents", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tauxChargeBps": { + "name": "tauxChargeBps", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "periodeReference": { + "name": "periodeReference", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "statut": { + "name": "statut", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'annuel'" + }, + "source": { + "name": "source", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'bulletin'" + }, + "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": { + "masse_salariale_salarie_annee_idx": { + "name": "masse_salariale_salarie_annee_idx", + "columns": [ + "salarieId", + "annee" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "masse_salariale_remunerations_id": { + "name": "masse_salariale_remunerations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "masse_salariale_salaries": { + "name": "masse_salariale_salaries", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "matricule": { + "name": "matricule", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nom": { + "name": "nom", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prenom": { + "name": "prenom", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "poste": { + "name": "poste", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dateEmbauche": { + "name": "dateEmbauche", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "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": { + "masse_salariale_salaries_id": { + "name": "masse_salariale_salaries_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "masse_salariale_salaries_matricule_unique": { + "name": "masse_salariale_salaries_matricule_unique", + "columns": [ + "matricule" + ] + } + }, + "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": { + "opex_montants_annee_etab_poste_idx": { + "name": "opex_montants_annee_etab_poste_idx", + "columns": [ + "annee", + "etablissementCode", + "libellePoste" + ], + "isUnique": true + } + }, + "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": { + "opex_postes_annee_libelle_idx": { + "name": "opex_postes_annee_libelle_idx", + "columns": [ + "annee", + "libelle" + ], + "isUnique": true + } + }, + "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": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index bd86042..a882c8d 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,27 @@ "when": 1786998761879, "tag": "0004_known_shiva", "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1787833569970, + "tag": "0005_nasty_tattoo", + "breakpoints": true + }, + { + "idx": 6, + "version": "5", + "when": 1787833802656, + "tag": "0006_ambitious_shen", + "breakpoints": true + }, + { + "idx": 7, + "version": "5", + "when": 1787833910943, + "tag": "0007_rich_tony_stark", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts index f4de265..dead1eb 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -201,3 +201,67 @@ export const opexBasesRepartition = mysqlTable("opex_bases_repartition", { export type OpexBaseRepartition = typeof opexBasesRepartition.$inferSelect; export type InsertOpexBaseRepartition = typeof opexBasesRepartition.$inferInsert; + +// ───────────────────────────────────────────────────────────────────────────── +// MASSE SALARIALE SANTINOVA — Données sensibles, accès administrateur uniquement +// ───────────────────────────────────────────────────────────────────────────── +export const masseSalarialeSalaries = mysqlTable("masse_salariale_salaries", { + id: int("id").autoincrement().primaryKey(), + /** Identifiant paie stable, distinct des données personnelles superflues. */ + matricule: varchar("matricule", { length: 32 }).notNull().unique(), + nom: varchar("nom", { length: 150 }).notNull(), + prenom: varchar("prenom", { length: 150 }).notNull(), + poste: varchar("poste", { length: 255 }).notNull(), + /** Date civile ISO (YYYY-MM-DD), sans fuseau horaire. */ + dateEmbauche: varchar("dateEmbauche", { length: 10 }).notNull(), + actif: boolean("actif").default(true).notNull(), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), +}); + +export type MasseSalarialeSalarie = typeof masseSalarialeSalaries.$inferSelect; +export type InsertMasseSalarialeSalarie = typeof masseSalarialeSalaries.$inferInsert; + +/** + * Une ligne par salarié et exercice. Tous les montants sont en centimes et les + * taux en points de base (ex. 42,75 % = 4 275) pour éviter les erreurs d'arrondi. + */ +export const masseSalarialeRemunerations = mysqlTable("masse_salariale_remunerations", { + id: int("id").autoincrement().primaryKey(), + salarieId: int("salarieId").notNull(), + annee: int("annee").notNull(), + salaireAnnuelBrutHorsPrimesCents: int("salaireAnnuelBrutHorsPrimesCents").notNull(), + salaireAnnuelBrutAvecPrimesCents: int("salaireAnnuelBrutAvecPrimesCents").notNull(), + salaireMensuelBrutHorsPrimesCents: int("salaireMensuelBrutHorsPrimesCents").notNull(), + tauxChargeBps: int("tauxChargeBps").notNull(), + /** Mois de paie ayant servi de référence, ex. 2025-12 ou 2026-08. */ + periodeReference: varchar("periodeReference", { length: 7 }), + /** `annuel` pour un exercice clos ; `cumul_provisoire` pour une année en cours. */ + statut: varchar("statut", { length: 32 }).notNull().default("annuel"), + source: varchar("source", { length: 64 }).notNull().default("bulletin"), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), +}, (t) => ({ + /** Rend les imports idempotents pour un salarié et un exercice. */ + uniqSalarieAnnee: uniqueIndex("masse_salariale_salarie_annee_idx").on(t.salarieId, t.annee), +})); + +export type MasseSalarialeRemuneration = typeof masseSalarialeRemunerations.$inferSelect; +export type InsertMasseSalarialeRemuneration = typeof masseSalarialeRemunerations.$inferInsert; + +/** Évolution salariale manuelle, stockée même en l'absence de bulletin pour l'année concernée. */ +export const masseSalarialeEvolutions = mysqlTable("masse_salariale_evolutions", { + id: int("id").autoincrement().primaryKey(), + salarieId: int("salarieId").notNull(), + annee: int("annee").notNull(), + /** Pourcentage exprimé en points de base, ex. 1,50 % = 150. */ + evolutionSalarialeBps: int("evolutionSalarialeBps").notNull(), + createdBy: int("createdBy").notNull(), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), +}, (t) => ({ + uniqSalarieAnnee: uniqueIndex("masse_salariale_evolution_salarie_annee_idx").on(t.salarieId, t.annee), +})); + +export type MasseSalarialeEvolution = typeof masseSalarialeEvolutions.$inferSelect; +export type InsertMasseSalarialeEvolution = typeof masseSalarialeEvolutions.$inferInsert; diff --git a/server/auth.local.test.ts b/server/auth.local.test.ts index d989d2c..29cf0b4 100644 --- a/server/auth.local.test.ts +++ b/server/auth.local.test.ts @@ -71,6 +71,9 @@ vi.mock("./db", async (importOriginal) => { upsertOpexBaseRepartition: vi.fn(), setOpexMontantsEtabBatch: vi.fn(), deleteOpexPoste: vi.fn(), + getMasseSalariale: vi.fn(), + importMasseSalariale: vi.fn(), + setMasseSalarialeEvolution: vi.fn(), }; }); @@ -342,3 +345,92 @@ describe("opex.deletePoste", () => { expect(db.deleteOpexPoste).toHaveBeenCalledWith(2027, 42); }); }); + +// ─── Tests Masse salariale (données sensibles, administrateur uniquement) ───── + +describe("masseSalariale", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("retourne les données uniquement pour un administrateur", async () => { + const { db } = await import("./db").then(m => ({ db: m })); + const source = { salaries: [], remunerations: [], evolutions: [] }; + (db.getMasseSalariale as ReturnType).mockResolvedValue(source); + const { ctx } = createAuthCtx({ role: "admin" }); + + await expect(appRouter.createCaller(ctx).masseSalariale.get()).resolves.toEqual(source); + }); + + it("interdit la consultation à un profil standard", async () => { + const { ctx } = createAuthCtx({ role: "standard" }); + + await expect(appRouter.createCaller(ctx).masseSalariale.get()).rejects.toThrow(); + }); + + it("importe les données validées uniquement pour un administrateur", async () => { + const { db } = await import("./db").then(m => ({ db: m })); + (db.importMasseSalariale as ReturnType).mockResolvedValue({ salaries: 1, remunerations: 1 }); + const { ctx } = createAuthCtx({ role: "admin" }); + const salaries = [{ + matricule: "000001", + nom: "DUPONT", + prenom: "Alice", + poste: "Responsable administratif", + dateEmbauche: "2024-01-01", + }]; + const remunerations = [{ + matricule: "000001", + annee: 2026, + salaireAnnuelBrutHorsPrimesCents: 36_000_00, + salaireAnnuelBrutAvecPrimesCents: 40_000_00, + salaireMensuelBrutHorsPrimesCents: 3_000_00, + tauxChargeBps: 5_500, + periodeReference: "2026-08", + statut: "cumul_provisoire" as const, + source: "bulletin", + }]; + + await expect(appRouter.createCaller(ctx).masseSalariale.import({ salaries, remunerations })) + .resolves.toEqual({ salaries: 1, remunerations: 1 }); + expect(db.importMasseSalariale).toHaveBeenCalledWith(salaries, remunerations); + }); + + it("interdit l'import de rémunérations à un profil standard", async () => { + const { db } = await import("./db").then(m => ({ db: m })); + const { ctx } = createAuthCtx({ role: "standard" }); + + await expect(appRouter.createCaller(ctx).masseSalariale.import({ salaries: [], remunerations: [] })).rejects.toThrow(); + expect(db.importMasseSalariale).not.toHaveBeenCalled(); + }); + + it("enregistre l'évolution manuelle avec l'identifiant de l'administrateur", async () => { + const { db } = await import("./db").then(m => ({ db: m })); + const { ctx } = createAuthCtx({ id: 42, role: "admin" }); + + await expect(appRouter.createCaller(ctx).masseSalariale.setEvolution({ + salarieId: 7, + annee: 2027, + evolutionSalarialeBps: 250, + })).resolves.toEqual({ success: true }); + + expect(db.setMasseSalarialeEvolution).toHaveBeenCalledWith({ + salarieId: 7, + annee: 2027, + evolutionSalarialeBps: 250, + createdBy: 42, + }); + }); + + it("rejette un taux d'évolution hors plage avant l'accès à la base", async () => { + const { db } = await import("./db").then(m => ({ db: m })); + const { ctx } = createAuthCtx({ role: "admin" }); + + await expect(appRouter.createCaller(ctx).masseSalariale.setEvolution({ + salarieId: 7, + annee: 2027, + evolutionSalarialeBps: 10_001, + })).rejects.toThrow(); + expect(db.setMasseSalarialeEvolution).not.toHaveBeenCalled(); + }); +}); diff --git a/server/db.ts b/server/db.ts index 2d150ef..b37fb79 100644 --- a/server/db.ts +++ b/server/db.ts @@ -5,11 +5,17 @@ import { etablissements, InsertCapexLigne, InsertEtablissement, + InsertMasseSalarialeEvolution, + InsertMasseSalarialeRemuneration, + InsertMasseSalarialeSalarie, InsertOpexMontantEtab, InsertOpexPoste, InsertUser, inventaireMeta, inventairePostes, + masseSalarialeEvolutions, + masseSalarialeRemunerations, + masseSalarialeSalaries, opexBasesRepartition, opexMontantsEtab, opexPostes, @@ -448,3 +454,108 @@ export async function importOpexBasesRepartition( ); }); } + +// ───────────────────────────────────────────────────────────────────────────── +// MASSE SALARIALE — accès exclusivement appelés par des procédures admin +// ───────────────────────────────────────────────────────────────────────────── + +export type MasseSalarialeImportSalarie = Pick< + InsertMasseSalarialeSalarie, + "matricule" | "nom" | "prenom" | "poste" | "dateEmbauche" +>; + +export type MasseSalarialeImportRemuneration = Omit< + InsertMasseSalarialeRemuneration, + "id" | "salarieId" | "createdAt" | "updatedAt" +> & { matricule: string }; + +/** + * Retourne les trois sources normalisées séparément. Le routeur réalise la + * jointure logique afin d'éviter une matrice SQL qui dupliquerait les années. + */ +export async function getMasseSalariale() { + const db = await getDb(); + if (!db) throw new Error("Database not available"); + const [salaries, remunerations, evolutions] = await Promise.all([ + db.select().from(masseSalarialeSalaries).orderBy(asc(masseSalarialeSalaries.nom), asc(masseSalarialeSalaries.prenom)), + db.select().from(masseSalarialeRemunerations), + db.select().from(masseSalarialeEvolutions), + ]); + return { salaries, remunerations, evolutions }; +} + +/** + * Import idempotent : les salaires et leurs rémunérations annuelles sont + * remplacés dans une transaction, sans modifier les évolutions saisies à la + * main qui résident dans une table distincte. + */ +export async function importMasseSalariale( + salaries: MasseSalarialeImportSalarie[], + remunerations: MasseSalarialeImportRemuneration[], +) { + const db = await getDb(); + if (!db) throw new Error("Database not available"); + if (salaries.length === 0) return { salaries: 0, remunerations: 0 }; + + return db.transaction(async (tx) => { + for (const salarie of salaries) { + await tx + .insert(masseSalarialeSalaries) + .values({ ...salarie, actif: true }) + .onDuplicateKeyUpdate({ + set: { + nom: salarie.nom, + prenom: salarie.prenom, + poste: salarie.poste, + dateEmbauche: salarie.dateEmbauche, + actif: true, + }, + }); + } + + const persistedSalaries = await tx + .select({ id: masseSalarialeSalaries.id, matricule: masseSalarialeSalaries.matricule }) + .from(masseSalarialeSalaries); + const idsByMatricule = new Map(persistedSalaries.map((row) => [row.matricule, row.id])); + + for (const remuneration of remunerations) { + const salarieId = idsByMatricule.get(remuneration.matricule); + if (!salarieId) { + throw new Error(`Salarié introuvable lors de l'import : ${remuneration.matricule}`); + } + const { matricule: _matricule, ...values } = remuneration; + await tx + .insert(masseSalarialeRemunerations) + .values({ ...values, salarieId }) + .onDuplicateKeyUpdate({ + set: { + salaireAnnuelBrutHorsPrimesCents: values.salaireAnnuelBrutHorsPrimesCents, + salaireAnnuelBrutAvecPrimesCents: values.salaireAnnuelBrutAvecPrimesCents, + salaireMensuelBrutHorsPrimesCents: values.salaireMensuelBrutHorsPrimesCents, + tauxChargeBps: values.tauxChargeBps, + periodeReference: values.periodeReference, + statut: values.statut, + source: values.source, + }, + }); + } + return { salaries: salaries.length, remunerations: remunerations.length }; + }); +} + +/** Sauvegarde l'évolution manuelle sans dépendre de l'existence d'un bulletin. */ +export async function setMasseSalarialeEvolution( + input: Pick, +) { + const db = await getDb(); + if (!db) throw new Error("Database not available"); + await db + .insert(masseSalarialeEvolutions) + .values(input) + .onDuplicateKeyUpdate({ + set: { + evolutionSalarialeBps: input.evolutionSalarialeBps, + createdBy: input.createdBy, + }, + }); +} diff --git a/server/routers.ts b/server/routers.ts index e4bfa1f..c9c9ddd 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -248,6 +248,47 @@ export const appRouter = router({ }), }), + // Les données de rémunération sont sensibles : aucune procédure de ce + // sous-routeur n'est accessible aux profils standard ou lecture seule. + masseSalariale: router({ + get: adminProcedure.query(async () => db.getMasseSalariale()), + + import: adminProcedure + .input(z.object({ + salaries: z.array(z.object({ + matricule: z.string().trim().min(1).max(32), + nom: z.string().trim().min(1).max(150), + prenom: z.string().trim().min(1).max(150), + poste: z.string().trim().min(1).max(255), + dateEmbauche: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date d'embauche invalide"), + })).min(1).max(500), + remunerations: z.array(z.object({ + matricule: z.string().trim().min(1).max(32), + annee: z.number().int().min(2000).max(2100), + salaireAnnuelBrutHorsPrimesCents: z.number().int().min(0).max(100_000_000), + salaireAnnuelBrutAvecPrimesCents: z.number().int().min(0).max(100_000_000), + salaireMensuelBrutHorsPrimesCents: z.number().int().min(0).max(10_000_000), + tauxChargeBps: z.number().int().min(0).max(100_000), + periodeReference: z.string().regex(/^\d{4}-\d{2}$/).nullable().optional(), + statut: z.enum(["annuel", "cumul_provisoire"]), + source: z.string().trim().min(1).max(64), + })).max(1_500), + })) + .mutation(async ({ input }) => db.importMasseSalariale(input.salaries, input.remunerations)), + + setEvolution: adminProcedure + .input(z.object({ + salarieId: z.number().int().positive(), + annee: z.number().int().min(2000).max(2100), + // ±100 % maximum : protège contre les erreurs de virgule dans la saisie. + evolutionSalarialeBps: z.number().int().min(-10_000).max(10_000), + })) + .mutation(async ({ input, ctx }) => { + await db.setMasseSalarialeEvolution({ ...input, createdBy: ctx.user.id }); + return { success: true }; + }), + }), + inventaire: router({ get: protectedProcedure .input(z.object({ annee: z.number() })) diff --git a/todo.md b/todo.md index 92fdbc1..76401e7 100644 --- a/todo.md +++ b/todo.md @@ -105,3 +105,24 @@ - [x] Constater l'échec du commit 3b52527 puis confirmer le redéploiement du correctif ec597da - [x] Diagnostiquer et corriger le routage du webhook vers le déploiement Budget SI - [x] Déclencher et valider le workflow CI requis avant le redéploiement + +## Module Masse salariale Santinova +- [x] Analyser les bulletins 2025 et 2026 fournis et relever les champs fiables +- [x] Consolider les bulletins complémentaires de janvier à avril 2026 avec ceux de mai à août +- [x] Résoudre les bulletins multiples d'un même matricule sur une période par une règle métier documentée +- [x] Produire un jeu consolidé 2026 sans doublon par salarié et par année +- [x] Créer le schéma BDD sécurisé pour les salariés et leurs rémunérations annuelles +- [x] Restreindre le module Masse salariale aux administrateurs +- [x] Créer les procédures tRPC de consultation, import et modification de l'évolution salariale +- [x] Ajouter l'entrée de menu et la page Masse salariale avec tableau 2025–2027 +- [x] Importer les données contrôlées depuis les bulletins de salaire +- [x] Tester le rendu réel des états d'interface : chargement, erreur, vide et affichage prêt +- [x] Ajouter des tests d'agrégation : totaux annuels, statut annuel/provisoire et absence de valeur fictive 2027 +- [x] Documenter que l'import de bulletins est un import initial contrôlé hors interface, et prévoir son extension future + +## Vérification des exportations de factures +- [x] Vérifier le projet Budget SI : aucun flux ou chemin d'export de factures BAP ou d'abonnement n'y est configuré +- [x] Annulé : demande posée dans une autre application + +## Reprise du module Masse salariale +- [x] Finaliser les validations et la documentation du module Masse salariale diff --git a/vitest.config.ts b/vitest.config.ts index 643de32..940f922 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,6 +5,11 @@ const templateRoot = path.resolve(import.meta.dirname); export default defineConfig({ root: templateRoot, + // Le build Vite utilise la transformation JSX automatique de React 19. + // Vitest doit appliquer la même convention aux composants de test TSX. + esbuild: { + jsx: "automatic", + }, resolve: { alias: { "@": path.resolve(templateRoot, "client", "src"), @@ -14,6 +19,12 @@ export default defineConfig({ }, test: { environment: "node", - include: ["server/**/*.test.ts", "server/**/*.spec.ts", "client/**/*.test.ts", "shared/**/*.test.ts"], + include: [ + "server/**/*.test.ts", + "server/**/*.spec.ts", + "client/**/*.test.ts", + "client/**/*.test.tsx", + "shared/**/*.test.ts", + ], }, });