feat(rtt) : paiement RTT rétroactif sur l'exercice précédent (#23)
Auto Tag Develop / tag (push) Successful in 7s

## Besoin RH
Pouvoir saisir un paiement RTT sur l'exercice précédent (ex. RTT de mai réglés après la bascule du 1er juin).

## Implémentation (Option B)
- Paiement autorisé sur l'exercice courant + l'exercice immédiatement précédent (N-1).
- Après saisie sur N-1, le report d'ouverture de l'exercice courant est recalculé automatiquement (computeClosingBalance) dans une transaction → aucun double comptage.
- Refus si ce report est verrouillé (is_locked) : la RH le déverrouille d'abord.
- Fallback EmployeeRttSummaryProvider::resolveCarry aligné sur computeClosingBalance : disponible correct même sans ligne stockée.
- Front : bouton « + Payer les RTT » actif sur l'exercice précédent.
- Docs : CLAUDE.md, doc/rtt-tab.md, documentation-content.ts.

## Vérification
-  172 tests OK, cs-fixer OK, conteneur compile.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #23
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #23.
This commit is contained in:
2026-06-08 13:27:34 +00:00
committed by Autin
parent c01e1f89a7
commit 1edb8d956f
7 changed files with 139 additions and 11 deletions
@@ -7,6 +7,7 @@ namespace App\Tests\State;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Entity\EmployeeRttBalance;
use App\Enum\ContractNature;
use App\Enum\TrackingMode;
use App\Service\Contracts\EmployeeContractPhaseResolver;
@@ -74,6 +75,54 @@ final class EmployeeRttPaymentProcessorTest extends TestCase
$this->invokePrivate($processor, 'assertYearAllowedForPayment', $employee, 2030);
}
public function testPaymentAllowedOnPreviousExercise(): void
{
// Today = 2026-05-19 → current exercise = 2026. Retroactive payment on the
// immediately previous exercise (2025) is now allowed (Option B).
$employee = $this->buildEmployeeWithTransition('2020-06-01', '2026-04-30', '2026-05-01');
$processor = $this->buildProcessorWithClock(new DateTimeImmutable('2026-05-19'));
$this->invokePrivate($processor, 'assertYearAllowedForPayment', $employee, 2025);
// No exception → previous exercise accepted.
self::assertTrue(true);
}
public function testPaymentStillRejectedTwoExercisesBack(): void
{
// 2024 is two exercises before current (2026) and not a closed-phase end → still rejected.
$employee = $this->buildEmployeeWithTransition('2020-06-01', '2026-04-30', '2026-05-01');
$processor = $this->buildProcessorWithClock(new DateTimeImmutable('2026-05-19'));
$this->expectException(UnprocessableEntityHttpException::class);
$this->invokePrivate($processor, 'assertYearAllowedForPayment', $employee, 2024);
}
public function testRetroactivePaymentRefusedWhenDownstreamReportLocked(): void
{
$processor = $this->buildProcessorWithClock(new DateTimeImmutable('2026-05-19'));
$locked = new EmployeeRttBalance();
$locked->setIsLocked(true);
$this->expectException(UnprocessableEntityHttpException::class);
$this->invokePrivate($processor, 'assertReportNotLocked', $locked);
}
public function testRetroactivePaymentAllowedWhenDownstreamReportMissingOrUnlocked(): void
{
$processor = $this->buildProcessorWithClock(new DateTimeImmutable('2026-05-19'));
$unlocked = new EmployeeRttBalance();
$unlocked->setIsLocked(false);
// Neither a missing (null) nor an unlocked downstream report must block payment.
$this->invokePrivate($processor, 'assertReportNotLocked', null);
$this->invokePrivate($processor, 'assertReportNotLocked', $unlocked);
self::assertTrue(true);
}
// -----------------------------------------------------------------------
// Test harness helpers.
// -----------------------------------------------------------------------