# MLD — Modele Logique de Donnees > Traduction du MCD en schema relationnel. Scope B (CFA + Agence + PERSONNE pivot). > Implementation Baserow concrete : `15-baserow-mpd.md` (a venir). ## 1. Vue d'ensemble du schema relationnel Decoupe en sous-vues pour eviter le spaghetti d'auto-layout : globale simplifiee + 3 zones + lien pedagogique. ### 1.1 Vue globale (PK/FK seuls) ```mermaid erDiagram personne ||--o{ attribution : "RESTRICT" personne ||--o{ intervention : "RESTRICT" formation ||--o{ bloc : "CASCADE" bloc ||--o{ module : "CASCADE" module ||--o{ attribution : "CASCADE" client ||--o{ projet : "RESTRICT" projet ||--o{ tache : "CASCADE" tache ||--o{ intervention : "CASCADE" projet }o--o| formation : "SET NULL" ``` ### 1.2 Zone CFA — schema relationnel ```mermaid erDiagram formation ||--o{ bloc : "FK bloc_formation_id" bloc ||--o{ module : "FK module_bloc_id" module ||--o{ attribution : "FK attribution_module_id" personne ||--o{ attribution : "FK attribution_personne_id" formation { INT formation_id PK VARCHAR formation_nom UK ENUM formation_filiere DECIMAL heures_totales ENUM statut } bloc { INT bloc_id PK INT bloc_formation_id FK VARCHAR bloc_nom DECIMAL heures_prevues } module { INT module_id PK INT module_bloc_id FK VARCHAR module_nom DECIMAL heures_prevues ENUM statut } attribution { INT attribution_id PK INT attribution_module_id FK INT attribution_personne_id FK DECIMAL heures_attribuees ENUM statut } personne { INT personne_id PK VARCHAR nom_prenom } ``` ### 1.3 Zone Agence — schema relationnel ```mermaid erDiagram client ||--o{ projet : "FK projet_client_id" projet ||--o{ tache : "FK tache_projet_id" tache ||--o{ intervention : "FK intervention_tache_id" personne ||--o{ intervention : "FK intervention_personne_id" client { INT client_id PK VARCHAR client_nom UK ENUM statut } projet { INT projet_id PK INT projet_client_id FK VARCHAR projet_nom ENUM type DECIMAL charge_heures ENUM statut } tache { INT tache_id PK INT tache_projet_id FK VARCHAR titre DECIMAL charge_heures ENUM statut } intervention { INT intervention_id PK INT intervention_tache_id FK INT intervention_personne_id FK DECIMAL heures DATE intervention_date } personne { INT personne_id PK VARCHAR nom_prenom } ``` ### 1.4 Zone Personne pivot — table & FK sortantes ```mermaid erDiagram personne ||--o{ attribution : "FK personne_id" personne ||--o{ intervention : "FK personne_id" personne { INT personne_id PK VARCHAR personne_nom VARCHAR personne_prenom VARCHAR personne_email UK DECIMAL capacite_annuelle DECIMAL split_formation_pct DECIMAL split_agence_pct TEXT roles "multi-select" ENUM statut } attribution { INT attribution_id PK INT module_id FK INT personne_id FK DECIMAL heures_attribuees } intervention { INT intervention_id PK INT tache_id FK INT personne_id FK DECIMAL heures } ``` ### 1.5 Lien pedagogique cross-zone ```mermaid erDiagram projet }o--o| formation : "FK projet_formation_id (SET NULL)" projet { INT projet_id PK INT projet_formation_id FK "nullable" } formation { INT formation_id PK VARCHAR formation_nom } ``` ### Vue flowchart — navigation FK ```mermaid flowchart LR P[personne]:::pivot F[formation] --> B[bloc] --> M[module] --> A[attribution] P --> A C[client] --> Pr[projet] --> T[tache] --> I[intervention] P --> I Pr -.optionnel.-> F classDef pivot fill:#FF825C,stroke:#333,color:#fff classDef cfa fill:#FFB347,stroke:#333,color:#000 classDef agence fill:#5CB3FF,stroke:#333,color:#fff class F,B,M,A cfa class C,Pr,T,I agence ``` ## 2. Regles de passage MCD → MLD (rappel) 1. Chaque entite → table. 2. Relation 1,N → la PK cote (1,1) devient FK cote (1,N). 3. Relation N,N porteuse → table associative avec PK composite (ou PK auto + UNIQUE composite). 4. Champs calcules → soit caches via triggers, soit recalcules par Baserow rollup. ## 3. Tables — definitions DDL ### Table `personne` ``` personne ( personne_id INT PK AUTO, personne_nom VARCHAR(100) NOT NULL, personne_prenom VARCHAR(100) NOT NULL, personne_email VARCHAR(254) NOT NULL UNIQUE, personne_telephone VARCHAR(20), personne_capacite_annuelle DECIMAL(6,2) NOT NULL DEFAULT 0, personne_split_formation_pct DECIMAL(4,1) NOT NULL DEFAULT 50.0, personne_split_agence_pct DECIMAL(4,1) NOT NULL DEFAULT 50.0, personne_roles VARCHAR(200) NOT NULL, -- csv ou table N:N selon Baserow personne_statut ENUM NOT NULL DEFAULT 'actif' ) CHECK (personne_split_formation_pct + personne_split_agence_pct = 100) CHECK (personne_email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') INDEX idx_personne_statut ON personne(personne_statut) ``` ### Tables CFA (formation, bloc, module, attribution) ``` formation ( formation_id INT PK AUTO, formation_nom VARCHAR(200) NOT NULL UNIQUE, formation_description TEXT, formation_filiere ENUM('dev','graphisme','marketing','iot','cybersec'), formation_heures_totales DECIMAL(6,2) NOT NULL DEFAULT 0, formation_statut ENUM('draft','actif','termine','archive') NOT NULL DEFAULT 'draft', formation_date_debut DATE, formation_date_fin DATE, formation_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), formation_updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) CHECK (formation_date_fin >= formation_date_debut OR formation_date_fin IS NULL) bloc ( bloc_id INT PK AUTO, bloc_formation_id INT NOT NULL FK → formation(formation_id) ON DELETE CASCADE, bloc_nom VARCHAR(200) NOT NULL, bloc_description TEXT, bloc_heures_prevues DECIMAL(6,2) NOT NULL DEFAULT 0, bloc_ordre INT NOT NULL DEFAULT 0, UNIQUE (bloc_formation_id, bloc_nom) ) INDEX idx_bloc_formation ON bloc(bloc_formation_id) module ( module_id INT PK AUTO, module_bloc_id INT NOT NULL FK → bloc(bloc_id) ON DELETE CASCADE, module_nom VARCHAR(200) NOT NULL, module_description TEXT, module_heures_prevues DECIMAL(5,2) NOT NULL DEFAULT 0, module_statut ENUM('a_attribuer','attribue','en_cours','realise','annule') NOT NULL DEFAULT 'a_attribuer' ) INDEX idx_module_bloc ON module(module_bloc_id) INDEX idx_module_statut ON module(module_statut) attribution ( attribution_id INT PK AUTO, attribution_module_id INT NOT NULL FK → module(module_id) ON DELETE CASCADE, attribution_personne_id INT NOT NULL FK → personne(personne_id) ON DELETE RESTRICT, attribution_heures_attribuees DECIMAL(5,2) NOT NULL, attribution_heures_realisees DECIMAL(5,2) NOT NULL DEFAULT 0, attribution_date_debut DATE, attribution_date_fin DATE, attribution_statut ENUM('planifie','en_cours','realise','annule') NOT NULL DEFAULT 'planifie' ) CHECK (attribution_heures_attribuees > 0) CHECK (attribution_heures_realisees >= 0) INDEX idx_attribution_module ON attribution(attribution_module_id) INDEX idx_attribution_personne ON attribution(attribution_personne_id) INDEX idx_attribution_statut ON attribution(attribution_statut) UNIQUE (attribution_module_id, attribution_personne_id, attribution_date_debut) WHERE attribution_statut != 'annule' -- index partiel ``` ### Tables Agence (client, projet, tache, intervention) ``` client ( client_id INT PK AUTO, client_nom VARCHAR(200) NOT NULL UNIQUE, client_contact_principal VARCHAR(200), client_contact_email VARCHAR(254), client_contact_telephone VARCHAR(20), client_secteur VARCHAR(100), client_notes TEXT, client_statut ENUM('prospect','actif','inactif','archive') NOT NULL DEFAULT 'prospect', client_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) projet ( projet_id INT PK AUTO, projet_client_id INT NOT NULL FK → client(client_id) ON DELETE RESTRICT, projet_nom VARCHAR(200) NOT NULL, projet_description TEXT, projet_type ENUM('site_web','app_mobile','api','infra','audit','support','autre'), projet_charge_heures DECIMAL(7,2) NOT NULL DEFAULT 0, projet_date_debut DATE, projet_date_fin_prevue DATE, projet_date_livraison DATE, projet_statut ENUM('devis','en_cours','livre','cloture','abandonne') NOT NULL DEFAULT 'devis', projet_formation_id INT FK → formation(formation_id) ON DELETE SET NULL, projet_url VARCHAR(500), projet_repository VARCHAR(500), UNIQUE (projet_client_id, projet_nom) ) INDEX idx_projet_client ON projet(projet_client_id) INDEX idx_projet_statut ON projet(projet_statut) INDEX idx_projet_formation ON projet(projet_formation_id) tache ( tache_id INT PK AUTO, tache_projet_id INT NOT NULL FK → projet(projet_id) ON DELETE CASCADE, tache_titre VARCHAR(200) NOT NULL, tache_description TEXT, tache_charge_heures DECIMAL(5,2) NOT NULL DEFAULT 0, tache_priorite ENUM('faible','normale','haute','critique'), tache_statut ENUM('todo','in_progress','review','done','abandoned') NOT NULL DEFAULT 'todo', tache_date_debut DATE, tache_date_fin_prevue DATE ) INDEX idx_tache_projet ON tache(tache_projet_id) INDEX idx_tache_statut ON tache(tache_statut) intervention ( intervention_id INT PK AUTO, intervention_tache_id INT NOT NULL FK → tache(tache_id) ON DELETE CASCADE, intervention_personne_id INT NOT NULL FK → personne(personne_id) ON DELETE RESTRICT, intervention_heures DECIMAL(5,2) NOT NULL, intervention_date DATE NOT NULL DEFAULT CURRENT_DATE, intervention_notes TEXT, intervention_statut ENUM('planifie','realise','annule') NOT NULL DEFAULT 'realise' ) CHECK (intervention_heures > 0) INDEX idx_intervention_tache ON intervention(intervention_tache_id) INDEX idx_intervention_personne ON intervention(intervention_personne_id) INDEX idx_intervention_date ON intervention(intervention_date) ``` ## 4. Comportement ON DELETE | Relation | ON DELETE | Justification | |----------|-----------|---------------| | formation → bloc | CASCADE | Cycle de vie partage | | bloc → module | CASCADE | Cycle de vie partage | | module → attribution | CASCADE | Si module supprime, attributions deviennent orphelines | | client → projet | RESTRICT | Empeche suppression client ayant projets | | projet → tache | CASCADE | Cycle de vie partage | | tache → intervention | CASCADE | Idem | | personne → attribution / intervention | RESTRICT | Force a archiver `personne_statut = inactif` plutot que supprimer | | projet → formation (lien pedagogique) | SET NULL | Suppression formation laisse le projet exister | ## 5. Mapping vers Baserow | Concept SQL | Baserow equivalent | |-------------|--------------------| | Table | Database → Table | | FK | `Link to table` (gere bidirec auto) | | ENUM | `Single select` | | MULTI_ENUM (personne_roles) | `Multiple select` | | FK ON DELETE CASCADE | UI Baserow ou webhook → bridge | | INDEX | Implicite sur `Link to table` | | CHECK CONSTRAINT | Validation cote bridge ou formules de validation | | Calculated rollup/formula | `Lookup` + `Formula` + `Count` | Voir `15-baserow-mpd.md` (a venir) pour la traduction concrete table-par-table. ## 6. Questions ouvertes - [ ] Materialiser les calculs ou recalculer a la lecture ? Baserow rollups = cache materialise auto. OK pour notre volumetrie. - [ ] Soft-delete ou hard-delete ? Si Qualiopi exige tracabilite, soft-delete obligatoire (ajouter `*_deleted_at TIMESTAMPTZ NULLABLE` partout). - [ ] Audit log par row ? Baserow a `Last modified by` + `Last modified time` natifs. Suffisant ou il faut un journal d'evenements separe ? - [ ] Multi-tenant futur ? Pour l'instant Acadenice mono-instance. Si rachat / scaling, ajouter `tenant_id` a toutes les tables.