Files
SIRH/doc/contract-phase-view.md
tristan f48f1d2f3a fix(contracts) : hide contract phases entirely before RTT_START_DATE
EmployeeContractPhaseResolver now accepts the data-start date and filters
out phases whose endDate is strictly before it. No work-hour or absence
data exists before the application launch date, so legacy contract
periods that ended before that date would surface meaningless RTT/CP
figures in the phase picker.

For employees who joined long before the software (typical legacy 35h
contracts, in production since 2014), only the current phase remains
visible — which also collapses the picker (threshold ≥ 2 phases).

The Employee entity reads RTT_START_DATE from $_SERVER/$_ENV directly
since it has no DI. The resolver service is wired via services.yaml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:17:54 +02:00

4.2 KiB

Vue contrat — sélecteur de phase

Objectif

Permettre à la RH de consulter les onglets Congés et RTT d'un employé selon une phase de contrat passée (ex. un 39h CDI avant un switch FORFAIT, ou un CDD clôturé avec solde de tout compte) sans changer le comportement par défaut sur la phase courante.

Concept de "phase"

Une phase = groupe d'EmployeeContractPeriod consécutifs (triés par startDate) partageant la signature (contract.type, weeklyHours, isDriver). Le service EmployeeContractPhaseResolver (src/Service/Contracts/EmployeeContractPhaseResolver.php) calcule ces phases à la volée depuis Employee::getContractPeriods().

Une transition de signature (35h → 39h, 39h → FORFAIT, driver false→true, weeklyHours 28→30, etc.) ouvre une nouvelle phase. Un type différent entre deux périodes de même signature empêche leur fusion (39h → INTERIM → 39h = 3 phases).

Filtrage par RTT_START_DATE

Les phases dont la date de fin est strictement antérieure à RTT_START_DATE (env, date de mise en service du logiciel) sont masquées du picker. Aucune donnée logiciel (heures, absences) n'existe avant cette date, donc consulter une phase entièrement antérieure n'a pas de sens fonctionnel.

Exemple : un employé sous 35h CDI de 2014 à 2025-10-31 puis 39h CDI depuis 2025-11-01, avec RTT_START_DATE=2026-02-23 :

  • Phase 35h (2014 → 2025-10-31) : entièrement avant → masquée
  • Phase 39h (depuis 2025-11-01) : chevauche → visible
  • Résultat : 1 seule phase visible → picker caché (seuil ≥ 2 phases)

Une phase dont la date de fin est égale à RTT_START_DATE est conservée (inégalité >=, non stricte).

EmployeeContractPhaseResolver reçoit RTT_START_DATE via DI (services.yaml). L'entité Employee::getContractPhases() lit la valeur depuis $_SERVER/$_ENV directement (l'entité n'a pas d'injection) et passe la chaîne au constructeur du resolver.

Picker UI

  • Position : en haut de la fiche employé, sous le nom et au-dessus des onglets.
  • Libellé : Vue contrat.
  • Caché si l'employé n'a qu'une seule phase.
  • Sélection non persistée — chaque ouverture de fiche démarre sur la phase courante.

Bandeau d'information

Affiché quand le picker est sur une phase passée. Indique que le mode lecture est partiel — les paiements de solde restent possibles, l'édition d'absences et des stocks de report est désactivée.

Effet sur les onglets

Onglet Effet
Congés Recharge avec les règles de la phase. Période Juin→Mai pour non-forfait, Jan→Déc pour FORFAIT. Exercices bornés à la phase.
RTT Visible ssi phase.contractType !== FORFAIT. Exercices bornés à la phase.
Heures, Frais, Formation, Contrat, Calendrier Non impactés.

Paiements de solde sur phase passée

  • RTT : + Payer les RTT activé sur le dernier exercice de la phase uniquement (l'exercice contenant phase.endDate). Les exercices antérieurs restent en lecture seule.
  • CP : utiliser le mécanisme existant EmployeeContractPeriod.paidLeaveSettledClosureDate pour solder. Pas de nouveau channel.

Transition d'exercice

Quand un exercice chevauche deux phases (ex. switch 39h→FORFAIT au 01/05/2026 fait que l'exercice Juin 2025 → Mai 2026 est à cheval) :

  • Vu depuis la phase 39h, l'exercice est borné à phase.endDate (30/04/2026).
  • Vu depuis la phase FORFAIT, la période civile 2026 est bornée à phase.startDate (01/05/2026).

API

Les endpoints suivants acceptent ?phaseId=N :

  • GET /employees/{id}/leave-summary
  • GET /employees/{id}/rtt-summary

Quand absent, ils utilisent la phase courante (comportement inchangé).

Employee.contractPhases (lecture, groupe employee:read) liste les phases au format {id, contractType, weeklyHours, isDriver, contractNature, startDate, endDate, periodIds, isCurrent}.

Tests

  • tests/Service/Contracts/EmployeeContractPhaseResolverTest.php (unit)
  • tests/State/EmployeeLeaveSummaryProviderTest.php (functional, phaseId)
  • tests/State/EmployeeRttSummaryProviderTest.php (functional, phaseId)
  • tests/State/EmployeeRttPaymentProcessorTest.php (functional, dernier exo phase clôturée)
  • tests/Service/Exercise/ExerciseYearResolverTest.php (helper extrait pendant Task 5)