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>
112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
import { Decimal } from 'decimal.js';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { Formation } from '../../src/domain/formation.js';
|
|
import { Projet } from '../../src/domain/projet.js';
|
|
|
|
describe('Projet', () => {
|
|
it('cree un projet valide', () => {
|
|
const p = new Projet({ id: 1, clientId: 5, nom: 'Site corpo', chargeHeures: new Decimal(80) });
|
|
expect(p.statut).toBe('devis');
|
|
expect(p.heuresRestantes().toNumber()).toBe(80);
|
|
});
|
|
|
|
it('throw si chargeHeures < 0', () => {
|
|
expect(
|
|
() => new Projet({ id: 1, clientId: 5, nom: 'X', chargeHeures: new Decimal(-1) }),
|
|
).toThrow();
|
|
});
|
|
|
|
it('ajouterTache cas nominal', () => {
|
|
const p = new Projet({ id: 1, clientId: 5, nom: 'X', chargeHeures: new Decimal(80) });
|
|
const t = p.ajouterTache('Setup repo', new Decimal(2), 100);
|
|
expect(t.titre).toBe('Setup repo');
|
|
expect(p.taches.length).toBe(1);
|
|
expect(p.heuresAttribuees().toNumber()).toBe(2);
|
|
});
|
|
|
|
it('ajouterTache bloque si projet cloture', () => {
|
|
const p = new Projet({
|
|
id: 1,
|
|
clientId: 5,
|
|
nom: 'X',
|
|
chargeHeures: new Decimal(80),
|
|
statut: 'cloture',
|
|
});
|
|
expect(() => p.ajouterTache('T', new Decimal(2), 1)).toThrow();
|
|
});
|
|
|
|
it('ajouterTache bloque si projet abandonne', () => {
|
|
const p = new Projet({
|
|
id: 1,
|
|
clientId: 5,
|
|
nom: 'X',
|
|
chargeHeures: new Decimal(80),
|
|
statut: 'abandonne',
|
|
});
|
|
expect(() => p.ajouterTache('T', new Decimal(2), 1)).toThrow();
|
|
});
|
|
|
|
it('ajouterTache throw si charge < 0', () => {
|
|
const p = new Projet({ id: 1, clientId: 5, nom: 'X', chargeHeures: new Decimal(80) });
|
|
expect(() => p.ajouterTache('T', new Decimal(-2), 1)).toThrow();
|
|
});
|
|
|
|
it('lierFormationPedagogique', () => {
|
|
const p = new Projet({ id: 1, clientId: 5, nom: 'X', chargeHeures: new Decimal(80) });
|
|
const f = new Formation({ id: 42, nom: 'Bootcamp', heuresTotales: new Decimal(700) });
|
|
p.lierFormationPedagogique(f);
|
|
expect(p.formationId).toBe(42);
|
|
});
|
|
|
|
it('livrer transitions', () => {
|
|
const p = new Projet({ id: 1, clientId: 5, nom: 'X', chargeHeures: new Decimal(80) });
|
|
p.livrer();
|
|
expect(p.statut).toBe('livre');
|
|
});
|
|
|
|
it('livrer depuis en_cours OK', () => {
|
|
const p = new Projet({
|
|
id: 1,
|
|
clientId: 5,
|
|
nom: 'X',
|
|
chargeHeures: new Decimal(80),
|
|
statut: 'en_cours',
|
|
});
|
|
p.livrer();
|
|
expect(p.statut).toBe('livre');
|
|
});
|
|
|
|
it('livrer bloque depuis cloture', () => {
|
|
const p = new Projet({
|
|
id: 1,
|
|
clientId: 5,
|
|
nom: 'X',
|
|
chargeHeures: new Decimal(80),
|
|
statut: 'cloture',
|
|
});
|
|
expect(() => p.livrer()).toThrow();
|
|
});
|
|
|
|
it('cloturer transition', () => {
|
|
const p = new Projet({
|
|
id: 1,
|
|
clientId: 5,
|
|
nom: 'X',
|
|
chargeHeures: new Decimal(80),
|
|
statut: 'livre',
|
|
});
|
|
p.cloturer();
|
|
expect(p.statut).toBe('cloture');
|
|
});
|
|
|
|
it('cloturer bloque depuis abandonne', () => {
|
|
const p = new Projet({
|
|
id: 1,
|
|
clientId: 5,
|
|
nom: 'X',
|
|
chargeHeures: new Decimal(80),
|
|
statut: 'abandonne',
|
|
});
|
|
expect(() => p.cloturer()).toThrow();
|
|
});
|
|
});
|