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>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import { Decimal } from 'decimal.js';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { Attribution } from '../../src/domain/attribution.js';
|
|
|
|
const make = (overrides: Partial<ConstructorParameters<typeof Attribution>[0]> = {}) =>
|
|
new Attribution({
|
|
id: 1,
|
|
moduleId: 10,
|
|
personneId: 5,
|
|
heuresAttribuees: new Decimal(20),
|
|
...overrides,
|
|
});
|
|
|
|
describe('Attribution — constructeur', () => {
|
|
it('cree une attribution valide', () => {
|
|
const a = make();
|
|
expect(a.statut).toBe('planifie');
|
|
expect(a.heuresRealisees.toNumber()).toBe(0);
|
|
});
|
|
|
|
it('throw si heuresAttribuees <= 0', () => {
|
|
expect(() => make({ heuresAttribuees: new Decimal(0) })).toThrow();
|
|
expect(() => make({ heuresAttribuees: new Decimal(-5) })).toThrow();
|
|
});
|
|
|
|
it('throw si heuresRealisees < 0', () => {
|
|
expect(() => make({ heuresRealisees: new Decimal(-1) })).toThrow();
|
|
});
|
|
|
|
it('throw si dateFin < dateDebut', () => {
|
|
expect(() =>
|
|
make({ dateDebut: new Date('2026-02-01'), dateFin: new Date('2026-01-01') }),
|
|
).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Attribution — transitions de statut', () => {
|
|
it('demarrer: planifie -> en_cours', () => {
|
|
const a = make();
|
|
a.demarrer();
|
|
expect(a.statut).toBe('en_cours');
|
|
});
|
|
|
|
it('demarrer invalide depuis realise', () => {
|
|
const a = make({ statut: 'realise' });
|
|
expect(() => a.demarrer()).toThrow();
|
|
});
|
|
|
|
it('cloturer: en_cours -> realise', () => {
|
|
const a = make({ statut: 'en_cours' });
|
|
a.cloturer();
|
|
expect(a.statut).toBe('realise');
|
|
});
|
|
|
|
it('cloturer: planifie -> realise (skip en_cours autorise)', () => {
|
|
const a = make();
|
|
a.cloturer();
|
|
expect(a.statut).toBe('realise');
|
|
});
|
|
|
|
it('cloturer invalide depuis annule', () => {
|
|
const a = make({ statut: 'annule' });
|
|
expect(() => a.cloturer()).toThrow();
|
|
});
|
|
|
|
it('annuler depuis planifie OK', () => {
|
|
const a = make();
|
|
a.annuler('client desistement');
|
|
expect(a.statut).toBe('annule');
|
|
});
|
|
|
|
it('annuler invalide depuis realise', () => {
|
|
const a = make({ statut: 'realise' });
|
|
expect(() => a.annuler('motif')).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Attribution — saisirHeuresRealisees', () => {
|
|
it('cas nominal', () => {
|
|
const a = make();
|
|
a.saisirHeuresRealisees(new Decimal(15));
|
|
expect(a.heuresRealisees.toNumber()).toBe(15);
|
|
});
|
|
|
|
it('throw si heures < 0', () => {
|
|
const a = make();
|
|
expect(() => a.saisirHeuresRealisees(new Decimal(-1))).toThrow();
|
|
});
|
|
|
|
it('throw si attribution annulee', () => {
|
|
const a = make({ statut: 'annule' });
|
|
expect(() => a.saisirHeuresRealisees(new Decimal(5))).toThrow();
|
|
});
|
|
|
|
it('throw si attribution realisee', () => {
|
|
const a = make({ statut: 'realise' });
|
|
expect(() => a.saisirHeuresRealisees(new Decimal(5))).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Attribution — isActive', () => {
|
|
it('planifie et en_cours sont actifs', () => {
|
|
expect(make().isActive()).toBe(true);
|
|
expect(make({ statut: 'en_cours' }).isActive()).toBe(true);
|
|
});
|
|
it('realise et annule ne sont pas actifs', () => {
|
|
expect(make({ statut: 'realise' }).isActive()).toBe(false);
|
|
expect(make({ statut: 'annule' }).isActive()).toBe(false);
|
|
});
|
|
});
|