Wiki/docs/12-uml-class-diagram.md
Corentin JOGUET 668576cdc4 chore: initial commit — formation-hub conception phase
Conception complete (Phase 0) pour formation-hub Acadenice :

- 19 docs Merise Agile + UML + GitOps + plans (tests/deploy/ops/api)
  cf docs/00-readme.md pour l'index complet
- Stack Docker compose (Docmost + Baserow + Postgres + Redis + MinIO local FS)
  compose.yml + compose.staging.yml + compose.prod.yml
- CI/CD GitHub Actions skeleton (ci, deploy-staging, deploy-prod)
- Bridge service skeleton (Hono + TS + Biome + Vitest + zod + pino)
- Templates GitHub : PR + 3 issue types + CODEOWNERS + dependabot.yml
- Scripts ops : healthcheck, backup quotidien, smoke-test post-deploy
- LICENSE AGPL-3.0 + SECURITY.md + CONTRIBUTING.md + CHANGELOG.md
- Diagramme drawIO archi infra (XML importable dans diagrams.net)

Decisions structurelles enregistrees :
- Scope CFA + Agence avec entite PERSONNE pivot multi-roles (ADR-001)
- Stack composite Docmost AGPL + Baserow MIT + bridge custom (ADR-001)
- Path B : UX quasi-unified via Tiptap node-views custom (ADR-002)
- Monorepo trunk-based development (ADR-003)
- Postgres separe Docmost/Baserow (ADR-004)
- Bridge stack Node 22 + Hono (ADR-005)
- Repo neuf prefere a fork Docmost
- Prod-like des le jour 1 (pas MVP)
2026-05-07 12:16:19 +02:00

8.6 KiB

UML Class Diagram

Vue orientee objet du modele. Scope B (CFA + Agence + Personne pivot). Apporte les methodes que le MCD ne montre pas. Pont entre modele de donnees et code du bridge service Phase 2.

1. Pourquoi un class diagram en plus du MCD

Le MCD montre les donnees (entites + attributs + relations). Le class diagram montre :

  • Les methodes sur chaque classe
  • La visibilite (public/private/protected)
  • Les types de relations OO (composition, agregation, association)
  • Les patterns applicables

2. Diagrammes par zone

Splitte en 3 sous-vues : CFA, Agence, Personne pivot. Plus un diagramme global simplifie pour la vue d'ensemble.

2.1 Vue globale (relations seules)

classDiagram
    Personne -- Attribution : "role formateur"
    Personne -- Intervention : "role developpeur"
    Formation *-- Bloc
    Bloc *-- Module
    Module -- Attribution
    Client -- Projet
    Projet *-- Tache
    Tache -- Intervention
    Projet -- Formation : "projet pedagogique"

2.2 Zone CFA — classes detaillees

classDiagram
    class Formation {
        +int id
        +string nom
        +Filiere filiere
        +decimal heuresTotales
        -decimal heuresAttribuees$
        +Statut statut
        +activer() void
        +archiver() void
        +ajouterBloc(Bloc) void
        +heuresRestantes() decimal
        +rapportPDF() Buffer
    }
    class Bloc {
        +int id
        +string nom
        +decimal heuresPrevues
        -decimal heuresAttribuees$
        +ajouterModule(Module) void
        +heuresRestantes() decimal
    }
    class Module {
        +int id
        +string nom
        +decimal heuresPrevues
        -decimal heuresAttribuees$
        -decimal heuresRealisees$
        +Statut statut
        +creerAttribution(Personne, decimal, Date, Date) Attribution
        +annuler() void
        +cloturer() void
    }
    class Attribution {
        +int id
        +decimal heuresAttribuees
        +decimal heuresRealisees
        +Statut statut
        +demarrer() void
        +saisirHeuresRealisees(decimal) void
        +cloturer() void
        +annuler(string) void
    }

    Formation "1" *-- "1..*" Bloc : composition
    Bloc "1" *-- "1..*" Module : composition
    Module "1" -- "0..*" Attribution : association

2.3 Zone Agence — classes detaillees

classDiagram
    class Client {
        +int id
        +string nom
        +string contactPrincipal
        +Email contactEmail
        +Statut statut
        +creerProjet(string) Projet
        +archiver() void
    }
    class Projet {
        +int id
        +string nom
        +Type type
        +decimal chargeHeures
        -decimal heuresRealisees$
        +Statut statut
        +ajouterTache(string, decimal) Tache
        +lierFormationPedagogique(Formation) void
        +livrer() void
        +cloturer() void
        +rapportPDF() Buffer
    }
    class Tache {
        +int id
        +string titre
        +decimal chargeHeures
        -decimal heuresRealisees$
        +Priorite priorite
        +Statut statut
        +creerIntervention(Personne, decimal, Date) Intervention
        +marquerInProgress() void
        +marquerReview() void
        +marquerDone() void
    }
    class Intervention {
        +int id
        +decimal heures
        +Date date
        +Statut statut
        +annuler(string) void
    }

    Client "1" -- "1..*" Projet : association
    Projet "1" *-- "0..*" Tache : composition
    Tache "1" -- "0..*" Intervention : association

2.4 Zone Personne pivot — classe + roles

classDiagram
    class Personne {
        +int id
        +string nom
        +string prenom
        +Email email
        +decimal capaciteAnnuelle
        +decimal splitFormationPct
        +decimal splitAgencePct
        +Set~Role~ roles
        +Statut statut
        -decimal heuresAttribueesFormation$
        -decimal heuresAttribueesAgence$
        +heuresRestantesFormation() decimal
        +heuresRestantesAgence() decimal
        +heuresRestantesTotal() decimal
        +ajouterRole(Role) void
        +retirerRole(Role) void
        +activer() void
        +inactiver() void
        +rapportPDF() Buffer
    }
    class Attribution {
        +int id
        +decimal heuresAttribuees
        +Statut statut
    }
    class Intervention {
        +int id
        +decimal heures
        +Statut statut
    }

    Personne "1" -- "0..*" Attribution : "role formateur"
    Personne "1" -- "0..*" Intervention : "role developpeur"

2.5 Lien pedagogique cross-zone

classDiagram
    class Projet {
        +int id
        +string nom
        +lierFormationPedagogique(Formation) void
    }
    class Formation {
        +int id
        +string nom
    }
    Projet "0..*" -- "0..1" Formation : "projet pedagogique"

Notation :

  • + public, - private, # protected
  • $ champ derive/calcule (rollup ou formula, pas stocke directement)
  • *-- composition (cycle de vie partage)
  • -- association simple

3. Methodes detaillees — Personne

Methode Signature Description
heuresRestantesFormation() decimal (capacite * split_formation_pct/100) - heures_attribuees_formation
heuresRestantesAgence() decimal (capacite * split_agence_pct/100) - heures_attribuees_agence
heuresRestantesTotal() decimal capacite - heures_attribuees_formation - heures_attribuees_agence
ajouterRole(role) Role → void Ajoute role aux roles existants. Idempotent.
retirerRole(role) Role → void Retire role. Verifie qu'aucune attribution/intervention active n'utilise ce role.
activer() void Statut → actif
inactiver() void Statut → inactif. Bloque nouvelles assignations.

4. Methodes detaillees — Module / Tache

Module.creerAttribution(personne, heures, dateDebut, dateFin)

Verifications :

  • personne.roles.contains(Role.formateur) — sinon throw
  • RG-01 : SUM(this.attributions.heures) + heures <= this.heuresPrevues
  • Warning si heures > personne.heuresRestantesFormation()

Effet : INSERT attribution + recalcul rollups en cascade.

Tache.creerIntervention(personne, heures, date)

Verifications :

  • personne.roles.contains(Role.developpeur) — sinon throw
  • heures > 0
  • personne.statut == actif

Effet : INSERT intervention + recalcul rollups (tache, projet, personne).

5. Patterns OO appliques

Pattern Ou Pourquoi
Value Object Email, Decimal heures, Filiere, Type, Priorite Immutables, validation a la construction
State Pattern Statut sur toutes les entites avec cycle de vie Encapsule transitions valides
Repository PersonneRepo, ProjetRepo, etc. Abstrait l'acces Baserow API
Factory Module.creerAttribution(), Tache.creerIntervention() Encapsule logique creation + validations
Observer Webhooks Baserow → bridge listeners Evenements rollup → recalculs
Strategy Calcul capacite Personne (split formation/agence) Permet de varier la regle (ex: split par periode)

6. Mapping vers le code du bridge service (Phase 2)

// bridge/src/domain/personne.ts
import { Decimal } from 'decimal.js';

export type Role = 'formateur' | 'developpeur' | 'admin' | 'direction' | 'support';

export class Personne {
  constructor(
    public readonly id: number,
    public nom: string,
    public prenom: string,
    public email: string,
    public capaciteAnnuelle: Decimal,
    public splitFormationPct: Decimal,
    public splitAgencePct: Decimal,
    public roles: Set<Role>,
    public statut: 'actif' | 'inactif',
    private _heuresAttribueesFormation: Decimal,
    private _heuresAttribueesAgence: Decimal,
  ) {
    if (!this.splitFormationPct.plus(this.splitAgencePct).equals(100)) {
      throw new Error('Splits doivent sommer a 100');
    }
  }

  heuresRestantesFormation(): Decimal {
    const alloue = this.capaciteAnnuelle.times(this.splitFormationPct).div(100);
    return alloue.minus(this._heuresAttribueesFormation);
  }
  heuresRestantesAgence(): Decimal {
    const alloue = this.capaciteAnnuelle.times(this.splitAgencePct).div(100);
    return alloue.minus(this._heuresAttribueesAgence);
  }
  heuresRestantesTotal(): Decimal {
    return this.capaciteAnnuelle.minus(this._heuresAttribueesFormation).minus(this._heuresAttribueesAgence);
  }
  // ...
}

7. Limites du class diagram

  • Ne montre pas la persistence (deja fait par MLD)
  • Ne montre pas les sequences (deja fait par UML use cases sequence diagrams)
  • Redondant avec MCD sur les attributs simples

C'est intentionnel : chaque vue eclaire un angle different. Le class diagram = angle comportemental statique cote code.