feat : modification de la gestion des jours fériés
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
This commit is contained in:
47
src/Service/WorkHours/DailyReferenceMinutesResolver.php
Normal file
47
src/Service/WorkHours/DailyReferenceMinutesResolver.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\WorkHours;
|
||||
|
||||
final readonly class DailyReferenceMinutesResolver
|
||||
{
|
||||
/**
|
||||
* Returns the contractual expected minutes for a given weekday.
|
||||
*
|
||||
* - Saturday/Sunday: always 0
|
||||
* - If $workDaysMinutes is provided (per-employee schedule on `EmployeeContractPeriod`),
|
||||
* it takes precedence: returns the minutes for that iso day if scheduled, 0 otherwise.
|
||||
* - Else 35h: 7h every weekday
|
||||
* - Else 39h: 8h Mon-Thu, 7h Fri
|
||||
* - Else other positive values: weeklyHours/5 per weekday
|
||||
* - Else null/<=0 weeklyHours: 0
|
||||
*
|
||||
* @param int $isoWeekDay 1 = Monday ... 7 = Sunday
|
||||
* @param null|array<int, int> $workDaysMinutes iso-day → minutes (1=Mon, ..., 5=Fri)
|
||||
*/
|
||||
public function resolve(?int $weeklyHours, int $isoWeekDay, ?array $workDaysMinutes = null): int
|
||||
{
|
||||
if ($isoWeekDay >= 6) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (null !== $workDaysMinutes) {
|
||||
return (int) ($workDaysMinutes[$isoWeekDay] ?? 0);
|
||||
}
|
||||
|
||||
if (null === $weeklyHours || $weeklyHours <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (39 === $weeklyHours) {
|
||||
return 5 === $isoWeekDay ? 7 * 60 : 8 * 60;
|
||||
}
|
||||
|
||||
if (35 === $weeklyHours) {
|
||||
return 7 * 60;
|
||||
}
|
||||
|
||||
return (int) round(($weeklyHours * 60) / 5);
|
||||
}
|
||||
}
|
||||
116
src/Service/WorkHours/HolidayVirtualHoursResolver.php
Normal file
116
src/Service/WorkHours/HolidayVirtualHoursResolver.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\WorkHours;
|
||||
|
||||
use App\Entity\Contract;
|
||||
use App\Entity\Employee;
|
||||
use App\Enum\TrackingMode;
|
||||
use App\Service\Contracts\EmployeeContractResolver;
|
||||
use App\Service\PublicHolidayServiceInterface;
|
||||
use DateTimeImmutable;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Applies the business rule: a public holiday from Monday to Friday, for any
|
||||
* non-Forfait contract, credits the contractually expected daily hours.
|
||||
* If the employee has also entered hours that day, the effective total is the
|
||||
* max between entered minutes and the contractual reference.
|
||||
*/
|
||||
final readonly class HolidayVirtualHoursResolver
|
||||
{
|
||||
public function __construct(
|
||||
private DailyReferenceMinutesResolver $dailyReferenceResolver,
|
||||
private PublicHolidayServiceInterface $publicHolidayService,
|
||||
private EmployeeContractResolver $contractResolver,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns the effective daily minutes to count for RTT and weekly total
|
||||
* aggregation, applying the holiday credit when applicable.
|
||||
*
|
||||
* If an absence is declared on the day, the absence dictates the credit
|
||||
* (via WorkedHoursCreditPolicy) and the holiday virtual rule is bypassed —
|
||||
* $actualMinutes already includes the absence credit.
|
||||
*
|
||||
* @param null|array<int, int> $workDaysMinutes per-employee schedule (iso day → minutes)
|
||||
*/
|
||||
public function resolveEffectiveDailyMinutes(
|
||||
?Contract $contract,
|
||||
DateTimeImmutable $date,
|
||||
int $actualMinutes,
|
||||
bool $hasAbsenceOnDate = false,
|
||||
?array $workDaysMinutes = null,
|
||||
): int {
|
||||
$reference = $this->resolveVirtualCredit($contract, $date, $hasAbsenceOnDate, $workDaysMinutes);
|
||||
if (0 === $reference) {
|
||||
return $actualMinutes;
|
||||
}
|
||||
|
||||
return max($actualMinutes, $reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the virtual credit (reference minutes) alone — 0 if the rule
|
||||
* does not apply (weekend, non-holiday, Forfait contract, absence declared,
|
||||
* or employee schedule indicates a non-working day). Used by the frontend.
|
||||
*
|
||||
* @param null|array<int, int> $workDaysMinutes per-employee schedule (iso day → minutes)
|
||||
*/
|
||||
public function resolveVirtualCredit(
|
||||
?Contract $contract,
|
||||
DateTimeImmutable $date,
|
||||
bool $hasAbsenceOnDate = false,
|
||||
?array $workDaysMinutes = null,
|
||||
): int {
|
||||
if ($hasAbsenceOnDate) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$isoDay = (int) $date->format('N');
|
||||
if ($isoDay >= 6) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (null === $contract) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (TrackingMode::PRESENCE->value === $contract->getTrackingMode()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$this->isPublicHoliday($date)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->dailyReferenceResolver->resolve($contract->getWeeklyHours(), $isoDay, $workDaysMinutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience helper: resolves the schedule internally for a single employee/date.
|
||||
* Used by callers that have an Employee in hand (e.g. DayContext, LeaveRecap).
|
||||
*/
|
||||
public function resolveVirtualCreditForEmployee(
|
||||
Employee $employee,
|
||||
DateTimeImmutable $date,
|
||||
bool $hasAbsenceOnDate = false,
|
||||
): int {
|
||||
$contract = $this->contractResolver->resolveForEmployeeAndDate($employee, $date);
|
||||
$workDaysMinutes = $this->contractResolver->resolveWorkDaysMinutesForEmployeeAndDate($employee, $date);
|
||||
|
||||
return $this->resolveVirtualCredit($contract, $date, $hasAbsenceOnDate, $workDaysMinutes);
|
||||
}
|
||||
|
||||
private function isPublicHoliday(DateTimeImmutable $date): bool
|
||||
{
|
||||
try {
|
||||
$holidays = $this->publicHolidayService->getHolidaysDayByYears('metropole', $date->format('Y'));
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($holidays[$date->format('Y-m-d')]);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ final readonly class WorkedHoursCreditPolicy
|
||||
{
|
||||
public function __construct(
|
||||
private EmployeeContractResolver $contractResolver,
|
||||
private DailyReferenceMinutesResolver $dailyReferenceResolver,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -38,9 +39,11 @@ final readonly class WorkedHoursCreditPolicy
|
||||
return 0;
|
||||
}
|
||||
|
||||
$weekday = (int) $workDate->format('N');
|
||||
// On applique la règle de crédit dépendante du contrat (35h / 39h / fallback).
|
||||
$dayMinutes = $this->resolveContractDayMinutes($contract?->getWeeklyHours(), $weekday);
|
||||
$weekday = (int) $workDate->format('N');
|
||||
$workDaysMinutes = $this->contractResolver->resolveWorkDaysMinutesForEmployeeAndDate($employee, $workDate);
|
||||
// Quand un planning est configuré sur la période (contrats non-standards),
|
||||
// il prime : jour non programmé = 0 crédit, sinon on utilise les minutes prévues.
|
||||
$dayMinutes = $this->resolveContractDayMinutes($contract?->getWeeklyHours(), $weekday, $workDaysMinutes);
|
||||
if ($dayMinutes <= 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -74,34 +77,14 @@ final readonly class WorkedHoursCreditPolicy
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public function resolveContractDayMinutes(?int $weeklyHours, int $isoWeekDay): int
|
||||
/**
|
||||
* Single source of truth = {@see DailyReferenceMinutesResolver}. Weekend=0,
|
||||
* schedule precedence, 35h/39h fixed rules, fallback = weeklyHours/5.
|
||||
*
|
||||
* @param null|array<int, int> $workDaysMinutes planning iso-day → minutes (priorité absolue si fourni)
|
||||
*/
|
||||
public function resolveContractDayMinutes(?int $weeklyHours, int $isoWeekDay, ?array $workDaysMinutes = null): int
|
||||
{
|
||||
// Week-end non travaillé dans cette politique.
|
||||
if ($isoWeekDay >= 6) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Règle fixe: 35h => 7h/jour.
|
||||
if (35 === $weeklyHours) {
|
||||
return 7 * 60;
|
||||
}
|
||||
|
||||
// Règle fixe: 39h => 8h lundi-jeudi, 7h le vendredi.
|
||||
if (39 === $weeklyHours) {
|
||||
return 5 === $isoWeekDay ? 7 * 60 : 8 * 60;
|
||||
}
|
||||
|
||||
// Cas spécifique métier: contrat 4h/semaine réparti sur 2 jours => 2h/jour.
|
||||
if (4 === $weeklyHours) {
|
||||
return 2 * 60;
|
||||
}
|
||||
|
||||
// Contrat non renseigné/invalide: aucun crédit.
|
||||
if (null === $weeklyHours || $weeklyHours <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fallback générique: répartition homogène sur 5 jours ouvrés.
|
||||
return (int) round(($weeklyHours * 60) / 5);
|
||||
return $this->dailyReferenceResolver->resolve($weeklyHours, $isoWeekDay, $workDaysMinutes);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user