Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
Modele OO complet (cf docs/12-uml-class-diagram.md) en TypeScript strict :
- Personne (pivot multi-roles, splits formation/agence, heuresRestantes*)
- Formation -> Bloc -> Module composition + heuresRestantes rollup
- Module.creerAttribution avec validation RG-01 (capacite) + role formateur
- Attribution lifecycle : demarrer/saisirHeuresRealisees/cloturer/annuler
- Client -> Projet -> Tache composition + lierFormationPedagogique
- Tache.creerIntervention avec validation role developpeur + heures > 0 + actif
- Schemas zod pour runtime validation (z.infer types exposes)
- Decimal.js partout pour les heures (zero erreur flottante)
Patterns appliques :
- Statuts comme discriminated unions ('actif' | 'inactif' | ...)
- Statuts annules exclus des rollups (annulation libere capacite)
- _appliquerHeures* en pseudo-private (convention underscore, pas de friend en TS)
- Warn surcharge Personne non bloquant (overbooking volontaire possible) — RG-01 Module reste bloquante
Tests : 111 pass / 0 fail. Coverage domain : 97.86% lines, 98.57% funcs.
tsc strict EXIT 0, biome ci EXIT 0.
Hors scope (a venir) :
- Repository pattern (Bloc 3 avec routes Hono)
- rapportPDF (Phase 2.4)
- Tests adapters Bloc 1 (Bloc 6 via bridge-tester)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
55 lines
1.4 KiB
TypeScript
55 lines
1.4 KiB
TypeScript
import { Decimal } from 'decimal.js';
|
|
import type { Module } from './module.js';
|
|
|
|
export interface BlocProps {
|
|
id: number;
|
|
formationId: number;
|
|
nom: string;
|
|
heuresPrevues: Decimal;
|
|
ordre?: number;
|
|
modules?: Module[];
|
|
}
|
|
|
|
const ZERO = new Decimal(0);
|
|
|
|
export class Bloc {
|
|
public readonly id: number;
|
|
public readonly formationId: number;
|
|
public nom: string;
|
|
public heuresPrevues: Decimal;
|
|
public ordre: number;
|
|
public readonly modules: Module[];
|
|
|
|
constructor(props: BlocProps) {
|
|
if (props.heuresPrevues.lt(0)) {
|
|
throw new Error('heuresPrevues doit etre >= 0');
|
|
}
|
|
this.id = props.id;
|
|
this.formationId = props.formationId;
|
|
this.nom = props.nom;
|
|
this.heuresPrevues = props.heuresPrevues;
|
|
this.ordre = props.ordre ?? 0;
|
|
this.modules = props.modules ?? [];
|
|
}
|
|
|
|
heuresAttribuees(): Decimal {
|
|
return this.modules.reduce((acc, m) => acc.plus(m.heuresPrevues), ZERO);
|
|
}
|
|
|
|
heuresRestantes(): Decimal {
|
|
return this.heuresPrevues.minus(this.heuresAttribuees());
|
|
}
|
|
|
|
ajouterModule(module: Module): void {
|
|
if (this.modules.some((m) => m.id === module.id)) {
|
|
throw new Error(`Module ${module.id} deja present dans le bloc`);
|
|
}
|
|
const total = this.heuresAttribuees().plus(module.heuresPrevues);
|
|
if (total.gt(this.heuresPrevues)) {
|
|
throw new Error(
|
|
`Capacite bloc depassee: ${total.toString()} > ${this.heuresPrevues.toString()}`,
|
|
);
|
|
}
|
|
this.modules.push(module);
|
|
}
|
|
}
|