fix : écran du récap. congés ordre d'affichage + Calcule des jours ouvrés pour les FORFAIT

This commit is contained in:
2026-04-14 15:54:57 +02:00
parent 0897154460
commit c2eaa06aff
7 changed files with 109 additions and 7 deletions

View File

@@ -27,6 +27,7 @@ final class EmployeeLeaveRecap
public ?string $siteName = null;
public ?string $siteColor = null;
public ?string $contractName = null;
public int $contractSortKey = 99;
public float $cpN1Remaining = 0.0;
public string $cpN = '-';
public string $acquiredSaturdays = '-';

View File

@@ -71,7 +71,10 @@ final readonly class LeaveBalanceComputationService
$fractionedDays = $this->resolveFractionedDays($employee, $ruleCode, $year);
if (LeaveRuleCode::FORFAIT_218 === $ruleCode) {
$totalBusinessDays = $this->countBusinessDays($from, $to);
// Business days for forfait must use the RAW holiday list (excluded holidays
// like "Lundi de Pentecôte" / journée de solidarité still count as non-working
// days for the 218-day legal target).
$totalBusinessDays = $this->countBusinessDaysInRange($from, $to, $this->buildRawPublicHolidayMap($from, $to));
$baseAcquiredDays = (float) max(0, $totalBusinessDays - self::FORFAIT_TARGET_WORKED_DAYS);
$acquiredDays = $carryDays + $baseAcquiredDays + $fractionedDays;
$absences = $this->absenceRepository->findByEmployeeAndOverlappingDateRange($employee, $effectiveFrom, $to);
@@ -406,6 +409,29 @@ final readonly class LeaveBalanceComputationService
return $map;
}
/**
* @return array<string, string>
*/
private function buildRawPublicHolidayMap(DateTimeImmutable $from, DateTimeImmutable $to): array
{
$map = [];
$startYear = (int) $from->format('Y');
$endYear = (int) $to->format('Y');
try {
for ($year = $startYear; $year <= $endYear; ++$year) {
$holidays = $this->publicHolidayService->getRawHolidaysDayByYears('metropole', (string) $year);
foreach ($holidays as $date => $label) {
$map[(string) $date] = (string) $label;
}
}
} catch (Throwable) {
return [];
}
return $map;
}
/**
* @param list<Absence> $absences
*

View File

@@ -78,12 +78,25 @@ final readonly class PublicHolidayService implements PublicHolidayServiceInterfa
* @throws ClientExceptionInterface
*/
public function getHolidaysDayByYears(string $zone, string $years): array
{
return $this->applyExclusions($this->fetchHolidaysByYears($zone, $years));
}
public function getRawHolidaysDayByYears(string $zone, string $years): array
{
return $this->fetchHolidaysByYears($zone, $years);
}
/**
* @return array<string, string>
*/
private function fetchHolidaysByYears(string $zone, string $years): array
{
$zone = strtolower(trim($zone));
$years = trim($years);
$key = "public_holidays_{$zone}_{$years}";
$holidays = $this->cache->get($key, function (ItemInterface $item) use ($zone, $years): array {
return $this->cache->get($key, function (ItemInterface $item) use ($zone, $years): array {
$item->expiresAfter(30 * 86400);
$url = $this->holidayUrl."{$zone}/{$years}.json";
@@ -101,8 +114,6 @@ final readonly class PublicHolidayService implements PublicHolidayServiceInterfa
return json_decode($response->getContent(), true);
});
return $this->applyExclusions($holidays);
}
/**

View File

@@ -9,4 +9,11 @@ interface PublicHolidayServiceInterface
public function getHolidaysDay(string $zone): array;
public function getHolidaysDayByYears(string $zone, string $years): array;
/**
* Same as getHolidaysDayByYears but WITHOUT the configured exclusions applied.
* Used for legal/contractual computations (e.g. forfait 218 days) where excluded
* holidays (journée de solidarité) must still count as non-working days.
*/
public function getRawHolidaysDayByYears(string $zone, string $years): array;
}

View File

@@ -7,8 +7,10 @@ namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\EmployeeLeaveRecap;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\User;
use App\Enum\ContractType;
use App\Repository\EmployeeRepository;
use App\Security\EmployeeScopeService;
use App\Service\Leave\LeaveRecapRowBuilder;
@@ -63,6 +65,7 @@ final readonly class EmployeeLeaveRecapProvider implements ProviderInterface
$resource->siteName = $site?->getName();
$resource->siteColor = $site?->getColor();
$resource->contractName = $row['contractName'] ?? null;
$resource->contractSortKey = $this->resolveContractSortKey($employee->getContract());
$resource->cpN1Remaining = is_numeric($row['cpN1Remaining']) ? (float) $row['cpN1Remaining'] : 0.0;
$resource->cpN = (string) $row['cpN'];
$resource->acquiredSaturdays = (string) $row['acquiredSaturdays'];
@@ -78,6 +81,10 @@ final readonly class EmployeeLeaveRecapProvider implements ProviderInterface
if (0 !== $siteCmp) {
return $siteCmp;
}
$contractCmp = $a->contractSortKey <=> $b->contractSortKey;
if (0 !== $contractCmp) {
return $contractCmp;
}
$lastCmp = strcmp($a->lastName, $b->lastName);
if (0 !== $lastCmp) {
return $lastCmp;
@@ -89,6 +96,30 @@ final readonly class EmployeeLeaveRecapProvider implements ProviderInterface
return $rows;
}
/**
* Sort order: FORFAIT → 39h → 35h → 25h → 4h → autres.
*/
private function resolveContractSortKey(?Contract $contract): int
{
if (null === $contract) {
return 99;
}
if (ContractType::FORFAIT === $contract->getType()) {
return 0;
}
$weeklyHours = $contract->getWeeklyHours();
return match ($weeklyHours) {
39 => 1,
35 => 2,
25 => 3,
4 => 4,
default => 99,
};
}
/**
* @return list<Employee>
*/

View File

@@ -561,7 +561,10 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
{
$type = $employee->getContract()?->getType();
if (ContractType::FORFAIT === $type) {
$businessDaysInPeriod = $this->countBusinessDays($from, $to);
// Business days for forfait must use the RAW holiday list (excluded holidays like
// "Lundi de Pentecôte" / journée de solidarité still count as non-working days for
// the 218-day legal target).
$businessDaysInPeriod = $this->countBusinessDays($from, $to, $this->buildRawPublicHolidayMap($from, $to));
$publicHolidays = $this->buildPublicHolidayMap($from, $to);
$weekdayHolidays = array_filter(
array_keys($publicHolidays),
@@ -655,6 +658,29 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
return $map;
}
/**
* @return array<string, string>
*/
private function buildRawPublicHolidayMap(DateTimeImmutable $from, DateTimeImmutable $to): array
{
$map = [];
$startYear = (int) $from->format('Y');
$endYear = (int) $to->format('Y');
try {
for ($year = $startYear; $year <= $endYear; ++$year) {
$holidays = $this->publicHolidayService->getRawHolidaysDayByYears('metropole', (string) $year);
foreach ($holidays as $date => $label) {
$map[(string) $date] = (string) $label;
}
}
} catch (Throwable) {
return [];
}
return $map;
}
/**
* Presence days = business days (Mon-Fri) - public holidays + weekend worked days - absence days.
*