[#SIRH-36] corriger calcule rtt contrat custom (#27)
Auto Tag Develop / tag (push) Successful in 7s

| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #27
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #27.
This commit is contained in:
2026-06-11 08:36:57 +00:00
committed by Autin
parent 081d92b9f4
commit f0387233e4
20 changed files with 1997 additions and 57 deletions
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Tests\Service\Rtt;
use App\Entity\Contract;
use App\Enum\TrackingMode;
use App\Service\Rtt\RttRecoveryComputationService;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
@@ -113,6 +114,159 @@ final class RttRecoveryComputationServiceTest extends TestCase
self::assertSame(3 * 60, $base50);
}
public function testBuildWeekDetailCustomDeficitKeepsSignedTotalAndFlatFlag(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
// CUSTOM, semaine sous les heures : overtime -120 (worked 2h sur réf 4h).
$detail = $this->invokePrivate(
$service,
'buildWeekRecoveryDetail',
false, // isPresence
false, // disableBonuses
true, // isCustom
-120, // overtimeTotalMinutes
0, // rawBase25
0, // rawBase50
[], // dailyMinutes
);
self::assertSame(-120, $detail->totalMinutes);
self::assertTrue($detail->isFlatRecovery);
self::assertSame(0, $detail->base25Minutes);
self::assertSame(0, $detail->base50Minutes);
}
public function testBuildWeekDetailCustomPositiveIsFlatOneToOne(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$detail = $this->invokePrivate($service, 'buildWeekRecoveryDetail', false, false, true, 180, 0, 0, []);
self::assertSame(180, $detail->totalMinutes); // 1h = 1h
self::assertTrue($detail->isFlatRecovery);
}
public function testBuildWeekDetailStandardKeepsBucketsAndBonuses(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
// 39h : overtime 300, base25 240, base50 60.
$detail = $this->invokePrivate($service, 'buildWeekRecoveryDetail', false, false, false, 300, 240, 60, []);
self::assertFalse($detail->isFlatRecovery);
self::assertSame(240, $detail->base25Minutes);
self::assertSame(60, $detail->bonus25Minutes); // round(240 * 0.25)
self::assertSame(60, $detail->base50Minutes);
self::assertSame(30, $detail->bonus50Minutes); // round(60 * 0.5)
self::assertSame(300 + 60 + 30, $detail->totalMinutes);
}
/**
* CUSTOM 4h, jour de solidarité non travaillé (RTT posé ou vide) : delta = (attendu 0) prorata.
* attendu lundi = workDaysHours = 120 ; prorata = round(4×12) = 48 ; delta = 120 48 = 72.
* (Combiné au naturel 120 de la semaine, donne 48 min.).
*/
public function testSolidarityAdjustmentCustomNotWorkedNeutralisesToProrata(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$delta = $this->invokePrivate(
$service,
'computeSolidarityDeficitAdjustment',
self::customContract(4),
120, // expectedMinutes (workDaysHours du lundi)
0, // workedMinutes (RTT posé / vide)
);
self::assertSame(72, $delta);
}
/**
* CUSTOM 4h, jour de solidarité travaillé normalement (120) : delta = (120 120) 48 = 48.
*/
public function testSolidarityAdjustmentCustomWorkedNormallyChargesProrata(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$delta = $this->invokePrivate($service, 'computeSolidarityDeficitAdjustment', self::customContract(4), 120, 120);
self::assertSame(-48, $delta);
}
/**
* CUSTOM 4h, jour de solidarité travaillé en plus (240) : delta = (120 240) 48 = 168.
* Le surplus du jour de solidarité n'est PAS crédité (jour neutralisé, net forcé à prorata).
*/
public function testSolidarityAdjustmentCustomWorkedExtraStillNetsProrata(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$delta = $this->invokePrivate($service, 'computeSolidarityDeficitAdjustment', self::customContract(4), 120, 240);
self::assertSame(-168, $delta);
}
/**
* CUSTOM 28h : prorata = round(28×12) = 336 (5h36). worked 0, expected 336 → delta 0.
* Le delta est nul ici par coïncidence du fallback uniforme (expected = prorata) ; avec un vrai
* workDaysHours où la valeur du lundi diffère, expected ≠ prorata et le delta serait non nul.
*/
public function testSolidarityAdjustmentCustom28hUsesProrata(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$delta = $this->invokePrivate($service, 'computeSolidarityDeficitAdjustment', self::customContract(28), 336, 0);
self::assertSame(0, $delta);
}
/**
* CUSTOM ≥ 35h (36h) : hors périmètre → delta 0.
*/
public function testSolidarityAdjustmentCustom36hOutOfScope(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$delta = $this->invokePrivate($service, 'computeSolidarityDeficitAdjustment', self::customContract(36), 999, 0);
self::assertSame(0, $delta);
}
/**
* 35h : type H35 (pas CUSTOM) → delta 0 (comportement inchangé, RTT posé fait foi).
*/
public function testSolidarityAdjustment35hOutOfScope(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$contract = new Contract()->setName('35h')->setTrackingMode(TrackingMode::TIME)->setWeeklyHours(35);
$delta = $this->invokePrivate($service, 'computeSolidarityDeficitAdjustment', $contract, 420, 0);
self::assertSame(0, $delta);
}
/**
* Aucun contrat ce jour-là (salarié parti / pas encore embauché) → delta 0.
*/
public function testSolidarityAdjustmentNoContractIsZero(): void
{
$service = new ReflectionClass(RttRecoveryComputationService::class)->newInstanceWithoutConstructor();
$delta = $this->invokePrivate($service, 'computeSolidarityDeficitAdjustment', null, 0, 0);
self::assertSame(0, $delta);
}
private static function customContract(int $weeklyHours): Contract
{
return new Contract()
->setName('Temps partiel')
->setTrackingMode(TrackingMode::TIME)
->setWeeklyHours($weeklyHours)
;
}
private function invokePrivate(object $obj, string $method, mixed ...$args): mixed
{
return new ReflectionClass($obj::class)->getMethod($method)->invoke($obj, ...$args);