import { Decimal } from 'decimal.js'; import { describe, expect, it } from 'vitest'; import { Personne } from '../../src/domain/personne.js'; import type { Role } from '../../src/domain/types.js'; const baseProps = (overrides: Partial[0]> = {}) => ({ id: 1, nom: 'Dupont', prenom: 'Pierre', email: 'pierre@acadenice.fr', capaciteAnnuelle: new Decimal(1000), splitFormationPct: new Decimal(60), splitAgencePct: new Decimal(40), roles: new Set(['formateur']), statut: 'actif' as const, ...overrides, }); describe('Personne — constructeur', () => { it('cree une personne valide', () => { const p = new Personne(baseProps()); expect(p.id).toBe(1); expect(p.statut).toBe('actif'); }); it('throw si splits != 100', () => { expect( () => new Personne( baseProps({ splitFormationPct: new Decimal(70), splitAgencePct: new Decimal(40) }), ), ).toThrow(/100/); }); it('throw si capaciteAnnuelle < 0', () => { expect(() => new Personne(baseProps({ capaciteAnnuelle: new Decimal(-1) }))).toThrow(); }); it('throw si splitFormationPct hors [0,100]', () => { expect( () => new Personne( baseProps({ splitFormationPct: new Decimal(-10), splitAgencePct: new Decimal(110) }), ), ).toThrow(); }); it('throw si splitAgencePct hors [0,100]', () => { expect( () => new Personne( baseProps({ splitFormationPct: new Decimal(150), splitAgencePct: new Decimal(-50) }), ), ).toThrow(); }); it('admin pur capacite=0 splits=0/100 — accepte uniquement si somme=100', () => { const p = new Personne( baseProps({ capaciteAnnuelle: new Decimal(0), splitFormationPct: new Decimal(0), splitAgencePct: new Decimal(100), roles: new Set(['admin']), }), ); expect(p.capaciteAnnuelle.toNumber()).toBe(0); }); }); describe('Personne — calculs heures restantes', () => { it('cas nominal split 60/40, capacite 1000', () => { const p = new Personne(baseProps()); expect(p.capaciteFormation().toNumber()).toBe(600); expect(p.capaciteAgence().toNumber()).toBe(400); expect(p.heuresRestantesFormation().toNumber()).toBe(600); expect(p.heuresRestantesAgence().toNumber()).toBe(400); expect(p.heuresRestantesTotal().toNumber()).toBe(1000); }); it('avec heures attribuees', () => { const p = new Personne( baseProps({ heuresAttribueesFormation: new Decimal(150), heuresAttribueesAgence: new Decimal(100), }), ); expect(p.heuresRestantesFormation().toNumber()).toBe(450); expect(p.heuresRestantesAgence().toNumber()).toBe(300); expect(p.heuresRestantesTotal().toNumber()).toBe(750); }); it('cas zero capacite', () => { const p = new Personne( baseProps({ capaciteAnnuelle: new Decimal(0), splitFormationPct: new Decimal(50), splitAgencePct: new Decimal(50), }), ); expect(p.heuresRestantesTotal().toNumber()).toBe(0); expect(p.heuresRestantesFormation().toNumber()).toBe(0); }); }); describe('Personne — gestion roles', () => { it('ajouterRole idempotent', () => { const p = new Personne(baseProps()); p.ajouterRole('formateur'); p.ajouterRole('formateur'); expect(p.roles.size).toBe(1); }); it('ajouterRole nouveau', () => { const p = new Personne(baseProps()); p.ajouterRole('developpeur'); expect(p.hasRole('developpeur')).toBe(true); expect(p.roles.size).toBe(2); }); it('retirerRole sans attributions actives', () => { const p = new Personne(baseProps()); p.retirerRole('formateur'); expect(p.hasRole('formateur')).toBe(false); }); it('retirerRole formateur bloque si attributions actives', () => { const p = new Personne(baseProps()); expect(() => p.retirerRole('formateur', { activeAttributions: 2 })).toThrow(/attributions/); }); it('retirerRole developpeur bloque si interventions actives', () => { const p = new Personne(baseProps({ roles: new Set(['formateur', 'developpeur']) })); expect(() => p.retirerRole('developpeur', { activeInterventions: 1 })).toThrow(/interventions/); }); }); describe('Personne — transitions de statut', () => { it('activer / inactiver', () => { const p = new Personne(baseProps({ statut: 'inactif' })); p.activer(); expect(p.statut).toBe('actif'); p.inactiver(); expect(p.statut).toBe('inactif'); }); }); describe('Personne — mutations rollups', () => { it('appliquer heures formation accumule', () => { const p = new Personne(baseProps()); p._appliquerHeuresFormation(new Decimal(100)); p._appliquerHeuresFormation(new Decimal(50)); expect(p.heuresAttribueesFormation.toNumber()).toBe(150); }); it('appliquer heures agence accumule', () => { const p = new Personne(baseProps()); p._appliquerHeuresAgence(new Decimal(80)); expect(p.heuresAttribueesAgence.toNumber()).toBe(80); }); it('throw si rollup formation devient negatif', () => { const p = new Personne(baseProps()); expect(() => p._appliquerHeuresFormation(new Decimal(-10))).toThrow(); }); it('throw si rollup agence devient negatif', () => { const p = new Personne(baseProps()); expect(() => p._appliquerHeuresAgence(new Decimal(-5))).toThrow(); }); it('warn quand surcharge formation (depasse capacite)', () => { const p = new Personne(baseProps({ capaciteAnnuelle: new Decimal(100) })); // capacite formation = 60. Apply 80 → warn mais accepte. p._appliquerHeuresFormation(new Decimal(80)); expect(p.heuresAttribueesFormation.toNumber()).toBe(80); }); });