import { Decimal } from 'decimal.js'; import { describe, expect, it } from 'vitest'; import { Module } from '../../src/domain/module.js'; import { Personne } from '../../src/domain/personne.js'; import type { Role } from '../../src/domain/types.js'; const formateurActif = () => new Personne({ id: 1, nom: 'X', prenom: 'Y', email: 'x@y.fr', capaciteAnnuelle: new Decimal(1000), splitFormationPct: new Decimal(50), splitAgencePct: new Decimal(50), roles: new Set(['formateur']), statut: 'actif', }); describe('Module — constructeur', () => { it('cree un module valide', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); expect(m.statut).toBe('a_attribuer'); }); it('throw si heuresPrevues < 0', () => { expect( () => new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(-1) }), ).toThrow(); }); }); describe('Module — creerAttribution happy path', () => { it('cree attribution + maj rollups + statut module', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); const p = formateurActif(); const a = m.creerAttribution(p, new Decimal(20), null, null, 100); expect(a.id).toBe(100); expect(m.attributions.length).toBe(1); expect(m.heuresAttribuees().toNumber()).toBe(20); expect(p.heuresAttribueesFormation.toNumber()).toBe(20); expect(m.statut).toBe('attribue'); }); }); describe('Module — creerAttribution validations', () => { it('throw si personne sans role formateur', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); const p = formateurActif(); p.retirerRole('formateur'); p.ajouterRole('developpeur'); expect(() => m.creerAttribution(p, new Decimal(10), null, null, 1)).toThrow(/formateur/); }); it('throw si personne inactive', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); const p = formateurActif(); p.inactiver(); expect(() => m.creerAttribution(p, new Decimal(10), null, null, 1)).toThrow(/inactiv/); }); it('throw si heures <= 0', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); const p = formateurActif(); expect(() => m.creerAttribution(p, new Decimal(0), null, null, 1)).toThrow(); }); it('RG-01 violation : SUM(heures) > heuresPrevues', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); const p = formateurActif(); m.creerAttribution(p, new Decimal(30), null, null, 1); expect(() => m.creerAttribution(p, new Decimal(15), null, null, 2)).toThrow(/RG-01/); }); it('warning si heures > capacite formation restante de la personne (ne throw pas)', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(1000) }); const p = new Personne({ id: 1, nom: 'X', prenom: 'Y', email: 'x@y.fr', capaciteAnnuelle: new Decimal(100), splitFormationPct: new Decimal(50), splitAgencePct: new Decimal(50), roles: new Set(['formateur']), statut: 'actif', }); // capacite formation = 50. On attribue 80. const a = m.creerAttribution(p, new Decimal(80), null, null, 1); expect(a.heuresAttribuees.toNumber()).toBe(80); expect(p.heuresAttribueesFormation.toNumber()).toBe(80); }); it('RG-01 ignore les attributions annulees', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); const p = formateurActif(); const a1 = m.creerAttribution(p, new Decimal(40), null, null, 1); a1.annuler('test'); // La nouvelle attribution doit etre acceptee maintenant. const a2 = m.creerAttribution(p, new Decimal(20), null, null, 2); expect(a2.heuresAttribuees.toNumber()).toBe(20); }); }); describe('Module — heuresRealisees / heuresRestantes', () => { it('calcule heures realisees en ignorant annulations', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(50) }); const p = formateurActif(); const a = m.creerAttribution(p, new Decimal(30), null, null, 1); a.saisirHeuresRealisees(new Decimal(10)); expect(m.heuresRealisees().toNumber()).toBe(10); expect(m.heuresRestantes().toNumber()).toBe(20); }); }); describe('Module — transitions de statut', () => { it('cloturer', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); m.cloturer(); expect(m.statut).toBe('realise'); }); it('annuler depuis a_attribuer', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40) }); m.annuler(); expect(m.statut).toBe('annule'); }); it('annuler bloque depuis realise', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40), statut: 'realise', }); expect(() => m.annuler()).toThrow(); }); it('cloturer bloque depuis annule', () => { const m = new Module({ id: 1, blocId: 10, nom: 'M1', heuresPrevues: new Decimal(40), statut: 'annule', }); expect(() => m.cloturer()).toThrow(); }); });