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
+23
View File
@@ -70,6 +70,29 @@ final readonly class AbsenceBalanceService
$balance->setPending($balance->getPending() + $request->getCountedDays());
}
/**
* Days still available to take in the request's balance period
* (acquired N-1 + acquiring N already taken), or null when the type is
* not balance-tracked (per-event leaves such as bereavement or marriage).
*
* Days currently reserved in PENDING are intentionally not subtracted: the
* request being reviewed already sits in that pending bucket, and approval
* only moves it to TAKEN.
*/
public function availableForRequest(AbsenceRequest $request): ?float
{
if (!$this->shouldTrack($request)) {
return null;
}
/** @var User $user */
$user = $request->getUser();
$period = $this->periodFor($user, $request->getType(), $request->getStartDate());
$balance = $this->balanceRepository->findOneForPeriod($user, $request->getType(), $period);
return $balance?->getAvailable() ?? 0.0;
}
/** Move reserved days from PENDING to TAKEN on approval. */
public function applyApproval(AbsenceRequest $request): void
{