fix(absences) : garde-fou solde négatif à l'approbation + cohérence fixture

- AbsenceBalanceService::availableForRequest() : jours disponibles (acquis N-1
  + en cours N − pris) pour la période de la demande, null si type non suivi.
- Blocage de l'approbation si countedDays > disponible, dans les deux chemins
  (REST AbsenceReviewProcessor + MCP ReviewAbsenceRequestTool), comme le motif
  décès. Les CP en cours d'acquisition restent posables, mais pas au-delà du
  droit total (plus de solde négatif silencieux à l'approbation).
- Fixture : demande pending CP d'alice replacée dans sa période de référence
  2025-2026 (26→29/05/2026, 4 j ouvrés) et solde pending aligné (5 → 4) ;
  plus de "en attente" orphelin non lié à une demande.
- Test fonctionnel testApproveBeyondAvailableBalanceIsBlocked + employé de test
  doté d'un droit pour que les approbations existantes passent le garde-fou.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-05-22 16:48:49 +02:00
parent f9773b3a5e
commit 65df36dd1a
5 changed files with 89 additions and 4 deletions
@@ -52,6 +52,16 @@ class ReviewAbsenceRequestTool
assert($admin instanceof User);
if ('approve' === $decision) {
// Never let an approval push the balance below zero (CP only).
$available = $this->balanceService->availableForRequest($request);
if (null !== $available && $request->getCountedDays() > $available + 1e-9) {
throw new InvalidArgumentException(sprintf(
'Approving this request would put the balance below zero: %g day(s) requested but only %g available.',
$request->getCountedDays(),
$available,
));
}
$request->setStatus(AbsenceStatus::Approved);
$request->setRejectionReason(null);
$this->balanceService->applyApproval($request);