fix : écran du récap. congés ordre d'affichage + Calcule des jours ouvrés pour les FORFAIT
This commit is contained in:
@@ -34,8 +34,8 @@
|
|||||||
<span class="text-left">Prénom</span>
|
<span class="text-left">Prénom</span>
|
||||||
<span class="text-left">Contrat</span>
|
<span class="text-left">Contrat</span>
|
||||||
<span class="text-right">CP N-1 restant</span>
|
<span class="text-right">CP N-1 restant</span>
|
||||||
<span class="text-right">CP N</span>
|
|
||||||
<span class="text-right">Samedis</span>
|
<span class="text-right">Samedis</span>
|
||||||
|
<span class="text-right">CP N</span>
|
||||||
<span class="text-right">RTT</span>
|
<span class="text-right">RTT</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-x border-b border-primary-500 rounded-b-md">
|
<div class="border-x border-b border-primary-500 rounded-b-md">
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
<span class="truncate">{{ row.firstName }}</span>
|
<span class="truncate">{{ row.firstName }}</span>
|
||||||
<span class="truncate">{{ row.contractName ?? '-' }}</span>
|
<span class="truncate">{{ row.contractName ?? '-' }}</span>
|
||||||
<span class="text-right tabular-nums">{{ formatNumber(row.cpN1Remaining) }}</span>
|
<span class="text-right tabular-nums">{{ formatNumber(row.cpN1Remaining) }}</span>
|
||||||
<span class="text-right tabular-nums">{{ row.cpN }}</span>
|
|
||||||
<span class="text-right tabular-nums">{{ row.acquiredSaturdays }}</span>
|
<span class="text-right tabular-nums">{{ row.acquiredSaturdays }}</span>
|
||||||
|
<span class="text-right tabular-nums">{{ row.cpN }}</span>
|
||||||
<span class="text-right tabular-nums">{{ row.rtt }}</span>
|
<span class="text-right tabular-nums">{{ row.rtt }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ final class EmployeeLeaveRecap
|
|||||||
public ?string $siteName = null;
|
public ?string $siteName = null;
|
||||||
public ?string $siteColor = null;
|
public ?string $siteColor = null;
|
||||||
public ?string $contractName = null;
|
public ?string $contractName = null;
|
||||||
|
public int $contractSortKey = 99;
|
||||||
public float $cpN1Remaining = 0.0;
|
public float $cpN1Remaining = 0.0;
|
||||||
public string $cpN = '-';
|
public string $cpN = '-';
|
||||||
public string $acquiredSaturdays = '-';
|
public string $acquiredSaturdays = '-';
|
||||||
|
|||||||
@@ -71,7 +71,10 @@ final readonly class LeaveBalanceComputationService
|
|||||||
$fractionedDays = $this->resolveFractionedDays($employee, $ruleCode, $year);
|
$fractionedDays = $this->resolveFractionedDays($employee, $ruleCode, $year);
|
||||||
|
|
||||||
if (LeaveRuleCode::FORFAIT_218 === $ruleCode) {
|
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);
|
$baseAcquiredDays = (float) max(0, $totalBusinessDays - self::FORFAIT_TARGET_WORKED_DAYS);
|
||||||
$acquiredDays = $carryDays + $baseAcquiredDays + $fractionedDays;
|
$acquiredDays = $carryDays + $baseAcquiredDays + $fractionedDays;
|
||||||
$absences = $this->absenceRepository->findByEmployeeAndOverlappingDateRange($employee, $effectiveFrom, $to);
|
$absences = $this->absenceRepository->findByEmployeeAndOverlappingDateRange($employee, $effectiveFrom, $to);
|
||||||
@@ -406,6 +409,29 @@ final readonly class LeaveBalanceComputationService
|
|||||||
return $map;
|
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
|
* @param list<Absence> $absences
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -78,12 +78,25 @@ final readonly class PublicHolidayService implements PublicHolidayServiceInterfa
|
|||||||
* @throws ClientExceptionInterface
|
* @throws ClientExceptionInterface
|
||||||
*/
|
*/
|
||||||
public function getHolidaysDayByYears(string $zone, string $years): array
|
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));
|
$zone = strtolower(trim($zone));
|
||||||
$years = trim($years);
|
$years = trim($years);
|
||||||
$key = "public_holidays_{$zone}_{$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);
|
$item->expiresAfter(30 * 86400);
|
||||||
$url = $this->holidayUrl."{$zone}/{$years}.json";
|
$url = $this->holidayUrl."{$zone}/{$years}.json";
|
||||||
|
|
||||||
@@ -101,8 +114,6 @@ final readonly class PublicHolidayService implements PublicHolidayServiceInterfa
|
|||||||
|
|
||||||
return json_decode($response->getContent(), true);
|
return json_decode($response->getContent(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $this->applyExclusions($holidays);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,4 +9,11 @@ interface PublicHolidayServiceInterface
|
|||||||
public function getHolidaysDay(string $zone): array;
|
public function getHolidaysDay(string $zone): array;
|
||||||
|
|
||||||
public function getHolidaysDayByYears(string $zone, string $years): 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ namespace App\State;
|
|||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProviderInterface;
|
use ApiPlatform\State\ProviderInterface;
|
||||||
use App\ApiResource\EmployeeLeaveRecap;
|
use App\ApiResource\EmployeeLeaveRecap;
|
||||||
|
use App\Entity\Contract;
|
||||||
use App\Entity\Employee;
|
use App\Entity\Employee;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Enum\ContractType;
|
||||||
use App\Repository\EmployeeRepository;
|
use App\Repository\EmployeeRepository;
|
||||||
use App\Security\EmployeeScopeService;
|
use App\Security\EmployeeScopeService;
|
||||||
use App\Service\Leave\LeaveRecapRowBuilder;
|
use App\Service\Leave\LeaveRecapRowBuilder;
|
||||||
@@ -63,6 +65,7 @@ final readonly class EmployeeLeaveRecapProvider implements ProviderInterface
|
|||||||
$resource->siteName = $site?->getName();
|
$resource->siteName = $site?->getName();
|
||||||
$resource->siteColor = $site?->getColor();
|
$resource->siteColor = $site?->getColor();
|
||||||
$resource->contractName = $row['contractName'] ?? null;
|
$resource->contractName = $row['contractName'] ?? null;
|
||||||
|
$resource->contractSortKey = $this->resolveContractSortKey($employee->getContract());
|
||||||
$resource->cpN1Remaining = is_numeric($row['cpN1Remaining']) ? (float) $row['cpN1Remaining'] : 0.0;
|
$resource->cpN1Remaining = is_numeric($row['cpN1Remaining']) ? (float) $row['cpN1Remaining'] : 0.0;
|
||||||
$resource->cpN = (string) $row['cpN'];
|
$resource->cpN = (string) $row['cpN'];
|
||||||
$resource->acquiredSaturdays = (string) $row['acquiredSaturdays'];
|
$resource->acquiredSaturdays = (string) $row['acquiredSaturdays'];
|
||||||
@@ -78,6 +81,10 @@ final readonly class EmployeeLeaveRecapProvider implements ProviderInterface
|
|||||||
if (0 !== $siteCmp) {
|
if (0 !== $siteCmp) {
|
||||||
return $siteCmp;
|
return $siteCmp;
|
||||||
}
|
}
|
||||||
|
$contractCmp = $a->contractSortKey <=> $b->contractSortKey;
|
||||||
|
if (0 !== $contractCmp) {
|
||||||
|
return $contractCmp;
|
||||||
|
}
|
||||||
$lastCmp = strcmp($a->lastName, $b->lastName);
|
$lastCmp = strcmp($a->lastName, $b->lastName);
|
||||||
if (0 !== $lastCmp) {
|
if (0 !== $lastCmp) {
|
||||||
return $lastCmp;
|
return $lastCmp;
|
||||||
@@ -89,6 +96,30 @@ final readonly class EmployeeLeaveRecapProvider implements ProviderInterface
|
|||||||
return $rows;
|
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>
|
* @return list<Employee>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -561,7 +561,10 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
{
|
{
|
||||||
$type = $employee->getContract()?->getType();
|
$type = $employee->getContract()?->getType();
|
||||||
if (ContractType::FORFAIT === $type) {
|
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);
|
$publicHolidays = $this->buildPublicHolidayMap($from, $to);
|
||||||
$weekdayHolidays = array_filter(
|
$weekdayHolidays = array_filter(
|
||||||
array_keys($publicHolidays),
|
array_keys($publicHolidays),
|
||||||
@@ -655,6 +658,29 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
|
|||||||
return $map;
|
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.
|
* Presence days = business days (Mon-Fri) - public holidays + weekend worked days - absence days.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user