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

289 lines
8.6 KiB
Markdown

# 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)
```mermaid
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
```mermaid
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
```mermaid
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
```mermaid
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
```mermaid
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)
```typescript
// 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**.