1486b770b1
L'export récap salaire comptait tous les congés 'C' d'un forfait et ne créditait aucune présence sur les jours de congé. Or un congé imputé sur le stock N-1 ne doit pas s'afficher et doit compter comme jour de présence (règle déjà appliquée dans la fiche employé via EmployeeLeaveSummaryProvider). - Nouvelle méthode publique resolvePreviousYearTakenDays() (mutualise le budget N-1 avec la fiche: phase courante + recalcul jours payés). - SalaryRecapPrintProvider charge les congés depuis le 1er janvier et consomme le budget N-1 chronologiquement (splitForfaitCongesByN1): jours couverts N-1 retirés de l'affichage congés et ajoutés à la présence; au-delà = congés N. - Non-forfait / budget N-1 = 0: comportement inchangé. Vérifié end-to-end sur données prod (SARAZI mai: +1 présence, 4 congés affichés; LIOT/ODUNCU budget 0 après paiement N-1 -> congés affichés). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
3.3 KiB
PHP
96 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\State;
|
|
|
|
use App\Entity\Absence;
|
|
use App\Enum\HalfDay;
|
|
use App\Service\WorkHours\AbsenceSegmentsResolver;
|
|
use App\State\SalaryRecapPrintProvider;
|
|
use DateTime;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\TestCase;
|
|
use ReflectionClass;
|
|
use ReflectionProperty;
|
|
|
|
/**
|
|
* Forfait N-1 split for the salary recap. The provider's collaborators are final classes
|
|
* PHPUnit cannot double, so the pure split helper is exercised via reflection, with a real
|
|
* AbsenceSegmentsResolver (no deps) injected into the uninitialized property.
|
|
*
|
|
* @internal
|
|
*/
|
|
final class SalaryRecapPrintProviderTest extends TestCase
|
|
{
|
|
public function testN1BudgetPartiallyCoversADayAndOverflowsToN(): void
|
|
{
|
|
// Budget N-1 = 2.5 j ; 3 congés pleins (1 j) lun/mar/mer de janvier.
|
|
// 1.0 + 1.0 + 0.5 consommés en N-1 → reste 0.5 j affiché en congé (le mercredi).
|
|
$conges = [
|
|
$this->buildConge('2026-01-05'),
|
|
$this->buildConge('2026-01-06'),
|
|
$this->buildConge('2026-01-07'),
|
|
];
|
|
|
|
$result = $this->split($conges, 2.5, '2026-01-01', '2026-01-31');
|
|
|
|
self::assertSame(2.5, $result['n1PresenceDays']);
|
|
self::assertSame(0.5, $result['count']);
|
|
self::assertSame('07/01', $result['dates']);
|
|
}
|
|
|
|
public function testN1BudgetConsumedInPriorMonthLeavesCurrentMonthFullyDisplayed(): void
|
|
{
|
|
// Budget 1 j, consommé par le congé de janvier. Récap de février → le congé de février
|
|
// est entièrement imputé N (affiché, 0 présence N-1 dans le mois).
|
|
$conges = [
|
|
$this->buildConge('2026-01-12'),
|
|
$this->buildConge('2026-02-09'),
|
|
];
|
|
|
|
$result = $this->split($conges, 1.0, '2026-02-01', '2026-02-28');
|
|
|
|
self::assertSame(0.0, $result['n1PresenceDays']);
|
|
self::assertSame(1.0, $result['count']);
|
|
self::assertSame('09/02', $result['dates']);
|
|
}
|
|
|
|
public function testZeroBudgetDisplaysAllCongesInMonth(): void
|
|
{
|
|
$conges = [$this->buildConge('2026-03-03')];
|
|
|
|
$result = $this->split($conges, 0.0, '2026-03-01', '2026-03-31');
|
|
|
|
self::assertSame(0.0, $result['n1PresenceDays']);
|
|
self::assertSame(1.0, $result['count']);
|
|
self::assertSame('03/03', $result['dates']);
|
|
}
|
|
|
|
/**
|
|
* @param list<Absence> $conges
|
|
*
|
|
* @return array{count: float, dates: string, n1PresenceDays: float}
|
|
*/
|
|
private function split(array $conges, float $budget, string $from, string $to): array
|
|
{
|
|
$provider = new ReflectionClass(SalaryRecapPrintProvider::class)->newInstanceWithoutConstructor();
|
|
new ReflectionProperty(SalaryRecapPrintProvider::class, 'absenceSegmentsResolver')
|
|
->setValue($provider, new AbsenceSegmentsResolver());
|
|
|
|
return new ReflectionClass($provider::class)
|
|
->getMethod('splitForfaitCongesByN1')
|
|
->invoke($provider, $conges, $budget, new DateTimeImmutable($from), new DateTimeImmutable($to));
|
|
}
|
|
|
|
private function buildConge(string $date): Absence
|
|
{
|
|
return new Absence()
|
|
->setStartDate(new DateTime($date))
|
|
->setEndDate(new DateTime($date))
|
|
->setStartHalf(HalfDay::AM)
|
|
->setEndHalf(HalfDay::PM)
|
|
;
|
|
}
|
|
}
|