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)
289 lines
8.6 KiB
Markdown
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**.
|