feat : ajout de la gestion des heures chauffeurs
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s
This commit is contained in:
@@ -65,7 +65,8 @@ final readonly class EmployeeWriteProcessor implements ProcessorInterface
|
||||
contract: $currentContract,
|
||||
startDate: $startDate,
|
||||
endDate: $changeRequest->contractEndDate,
|
||||
nature: $nature
|
||||
nature: $nature,
|
||||
isDriver: $changeRequest->isDriver ?? false,
|
||||
);
|
||||
|
||||
$data->setEntryDate($startDate);
|
||||
@@ -108,7 +109,8 @@ final readonly class EmployeeWriteProcessor implements ProcessorInterface
|
||||
startDate: $startDate,
|
||||
endDate: $changeRequest->contractEndDate,
|
||||
nature: $nature,
|
||||
todayPeriod: $todayPeriod
|
||||
todayPeriod: $todayPeriod,
|
||||
isDriver: $changeRequest->isDriver ?? false,
|
||||
);
|
||||
|
||||
return $result;
|
||||
|
||||
@@ -95,7 +95,8 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
));
|
||||
}
|
||||
$isPresenceTracking = TrackingMode::PRESENCE->value === $contract?->getTrackingMode();
|
||||
$normalized = $this->normalizeEntry($entry, $employeeId, $isPresenceTracking);
|
||||
$isDriver = $this->contractResolver->resolveIsDriverForEmployeeAndDate($employee, $workDate);
|
||||
$normalized = $this->normalizeEntry($entry, $employeeId, $isPresenceTracking, $isDriver);
|
||||
$existing = $existingByEmployeeId[$employeeId] ?? null;
|
||||
$isAdmin = in_array('ROLE_ADMIN', $user->getRoles(), true);
|
||||
$isSelf = in_array('ROLE_SELF', $user->getRoles(), true);
|
||||
@@ -225,11 +226,34 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
* eveningFrom:?string,
|
||||
* eveningTo:?string,
|
||||
* isPresentMorning:bool,
|
||||
* isPresentAfternoon:bool
|
||||
* isPresentAfternoon:bool,
|
||||
* dayHoursMinutes:?int,
|
||||
* nightHoursMinutes:?int,
|
||||
* hasBreakfast:bool,
|
||||
* hasLunch:bool,
|
||||
* hasOvernight:bool
|
||||
* }
|
||||
*/
|
||||
private function normalizeEntry(array $entry, int $employeeId, bool $isPresenceTracking): array
|
||||
private function normalizeEntry(array $entry, int $employeeId, bool $isPresenceTracking, bool $isDriver): array
|
||||
{
|
||||
if ($isDriver) {
|
||||
return [
|
||||
'morningFrom' => null,
|
||||
'morningTo' => null,
|
||||
'afternoonFrom' => null,
|
||||
'afternoonTo' => null,
|
||||
'eveningFrom' => null,
|
||||
'eveningTo' => null,
|
||||
'isPresentMorning' => false,
|
||||
'isPresentAfternoon' => false,
|
||||
'dayHoursMinutes' => $this->normalizeMinutes($entry['dayHoursMinutes'] ?? null, $employeeId, 'dayHoursMinutes'),
|
||||
'nightHoursMinutes' => $this->normalizeMinutes($entry['nightHoursMinutes'] ?? null, $employeeId, 'nightHoursMinutes'),
|
||||
'hasBreakfast' => $this->normalizePresence($entry['hasBreakfast'] ?? false, $employeeId, 'hasBreakfast'),
|
||||
'hasLunch' => $this->normalizePresence($entry['hasLunch'] ?? false, $employeeId, 'hasLunch'),
|
||||
'hasOvernight' => $this->normalizePresence($entry['hasOvernight'] ?? false, $employeeId, 'hasOvernight'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($isPresenceTracking) {
|
||||
return [
|
||||
'morningFrom' => null,
|
||||
@@ -240,6 +264,11 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
'eveningTo' => null,
|
||||
'isPresentMorning' => $this->normalizePresence($entry['isPresentMorning'] ?? false, $employeeId, 'isPresentMorning'),
|
||||
'isPresentAfternoon' => $this->normalizePresence($entry['isPresentAfternoon'] ?? false, $employeeId, 'isPresentAfternoon'),
|
||||
'dayHoursMinutes' => null,
|
||||
'nightHoursMinutes' => null,
|
||||
'hasBreakfast' => false,
|
||||
'hasLunch' => false,
|
||||
'hasOvernight' => false,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -254,6 +283,11 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
// même si le contrat résolu ce jour est en suivi horaire.
|
||||
'isPresentMorning' => $this->normalizePresence($entry['isPresentMorning'] ?? false, $employeeId, 'isPresentMorning'),
|
||||
'isPresentAfternoon' => $this->normalizePresence($entry['isPresentAfternoon'] ?? false, $employeeId, 'isPresentAfternoon'),
|
||||
'dayHoursMinutes' => null,
|
||||
'nightHoursMinutes' => null,
|
||||
'hasBreakfast' => false,
|
||||
'hasLunch' => false,
|
||||
'hasOvernight' => false,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -283,6 +317,32 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
return $time;
|
||||
}
|
||||
|
||||
private function normalizeMinutes(mixed $value, int $employeeId, string $field): ?int
|
||||
{
|
||||
if (null === $value || '' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_int($value) && !is_float($value)) {
|
||||
throw new UnprocessableEntityHttpException(sprintf(
|
||||
'Employee %d: %s must be an integer (minutes).',
|
||||
$employeeId,
|
||||
$field
|
||||
));
|
||||
}
|
||||
|
||||
$minutes = (int) $value;
|
||||
if ($minutes < 0) {
|
||||
throw new UnprocessableEntityHttpException(sprintf(
|
||||
'Employee %d: %s must be >= 0.',
|
||||
$employeeId,
|
||||
$field
|
||||
));
|
||||
}
|
||||
|
||||
return $minutes;
|
||||
}
|
||||
|
||||
private function normalizePresence(mixed $value, int $employeeId, string $field): bool
|
||||
{
|
||||
if (!is_bool($value)) {
|
||||
@@ -305,7 +365,12 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
* eveningFrom:?string,
|
||||
* eveningTo:?string,
|
||||
* isPresentMorning:bool,
|
||||
* isPresentAfternoon:bool
|
||||
* isPresentAfternoon:bool,
|
||||
* dayHoursMinutes:?int,
|
||||
* nightHoursMinutes:?int,
|
||||
* hasBreakfast:bool,
|
||||
* hasLunch:bool,
|
||||
* hasOvernight:bool
|
||||
* } $entry
|
||||
*/
|
||||
private function isEntryEmpty(array $entry): bool
|
||||
@@ -317,7 +382,12 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
&& null === $entry['eveningFrom']
|
||||
&& null === $entry['eveningTo']
|
||||
&& false === $entry['isPresentMorning']
|
||||
&& false === $entry['isPresentAfternoon'];
|
||||
&& false === $entry['isPresentAfternoon']
|
||||
&& (null === $entry['dayHoursMinutes'] || 0 === $entry['dayHoursMinutes'])
|
||||
&& (null === $entry['nightHoursMinutes'] || 0 === $entry['nightHoursMinutes'])
|
||||
&& false === $entry['hasBreakfast']
|
||||
&& false === $entry['hasLunch']
|
||||
&& false === $entry['hasOvernight'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,7 +399,12 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
* eveningFrom:?string,
|
||||
* eveningTo:?string,
|
||||
* isPresentMorning:bool,
|
||||
* isPresentAfternoon:bool
|
||||
* isPresentAfternoon:bool,
|
||||
* dayHoursMinutes:?int,
|
||||
* nightHoursMinutes:?int,
|
||||
* hasBreakfast:bool,
|
||||
* hasLunch:bool,
|
||||
* hasOvernight:bool
|
||||
* } $entry
|
||||
*/
|
||||
private function hydrateWorkHour(WorkHour $workHour, array $entry): void
|
||||
@@ -343,6 +418,11 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
->setEveningTo($entry['eveningTo'])
|
||||
->setIsPresentMorning($entry['isPresentMorning'])
|
||||
->setIsPresentAfternoon($entry['isPresentAfternoon'])
|
||||
->setDayHoursMinutes($entry['dayHoursMinutes'])
|
||||
->setNightHoursMinutes($entry['nightHoursMinutes'])
|
||||
->setHasBreakfast($entry['hasBreakfast'])
|
||||
->setHasLunch($entry['hasLunch'])
|
||||
->setHasOvernight($entry['hasOvernight'])
|
||||
// Toute modification invalide la validation chef de site.
|
||||
->setIsSiteValid(false)
|
||||
// Toute modification utilisateur repasse la ligne en attente de validation RH.
|
||||
@@ -359,7 +439,12 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
* eveningFrom:?string,
|
||||
* eveningTo:?string,
|
||||
* isPresentMorning:bool,
|
||||
* isPresentAfternoon:bool
|
||||
* isPresentAfternoon:bool,
|
||||
* dayHoursMinutes:?int,
|
||||
* nightHoursMinutes:?int,
|
||||
* hasBreakfast:bool,
|
||||
* hasLunch:bool,
|
||||
* hasOvernight:bool
|
||||
* } $entry
|
||||
*/
|
||||
private function isSameAsExisting(WorkHour $workHour, array $entry): bool
|
||||
@@ -371,6 +456,11 @@ final readonly class WorkHourBulkUpsertProcessor implements ProcessorInterface
|
||||
&& $workHour->getEveningFrom() === $entry['eveningFrom']
|
||||
&& $workHour->getEveningTo() === $entry['eveningTo']
|
||||
&& $workHour->getIsPresentMorning() === $entry['isPresentMorning']
|
||||
&& $workHour->getIsPresentAfternoon() === $entry['isPresentAfternoon'];
|
||||
&& $workHour->getIsPresentAfternoon() === $entry['isPresentAfternoon']
|
||||
&& $workHour->getDayHoursMinutes() === $entry['dayHoursMinutes']
|
||||
&& $workHour->getNightHoursMinutes() === $entry['nightHoursMinutes']
|
||||
&& $workHour->getHasBreakfast() === $entry['hasBreakfast']
|
||||
&& $workHour->getHasLunch() === $entry['hasLunch']
|
||||
&& $workHour->getHasOvernight() === $entry['hasOvernight'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,11 @@ final readonly class WorkHourDayContextProvider implements ProviderInterface
|
||||
}
|
||||
|
||||
// On initialise toutes les lignes, même sans absence ce jour-là.
|
||||
$contract = $this->contractResolver->resolveForEmployeeAndDate($employee, $workDate);
|
||||
$rowsByEmployeeId[$employeeId] = new DayContextRow(
|
||||
employeeId: $employeeId,
|
||||
hasContractAtDate: null !== $this->contractResolver->resolveForEmployeeAndDate($employee, $workDate)
|
||||
hasContractAtDate: null !== $contract,
|
||||
isDriverContract: $this->contractResolver->resolveIsDriverForEmployeeAndDate($employee, $workDate),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
||||
{
|
||||
$contractsByEmployeeDate = $this->contractResolver->resolveForEmployeesAndDays($employees, $days);
|
||||
$contractNaturesByEmployeeDate = $this->contractResolver->resolveNaturesForEmployeesAndDays($employees, $days);
|
||||
$isDriverByEmployeeDate = $this->contractResolver->resolveIsDriverForEmployeesAndDays($employees, $days);
|
||||
$metricsByEmployeeDate = [];
|
||||
foreach ($workHours as $workHour) {
|
||||
$employeeId = $workHour->getEmployee()?->getId();
|
||||
@@ -129,6 +130,11 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
||||
'metrics' => $this->computeMetrics($workHour),
|
||||
'isPresentMorning' => $workHour->getIsPresentMorning(),
|
||||
'isPresentAfternoon' => $workHour->getIsPresentAfternoon(),
|
||||
'dayHoursMinutes' => $workHour->getDayHoursMinutes(),
|
||||
'nightHoursMinutes' => $workHour->getNightHoursMinutes(),
|
||||
'hasBreakfast' => $workHour->getHasBreakfast(),
|
||||
'hasLunch' => $workHour->getHasLunch(),
|
||||
'hasOvernight' => $workHour->getHasOvernight(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -156,7 +162,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
||||
[$absentMorning, $absentAfternoon] = $this->absenceSegmentsResolver->resolveForDate($absence, $date);
|
||||
if ($absentMorning || $absentAfternoon) {
|
||||
$absenceByEmployeeDate[$employeeId][$date] = true;
|
||||
$absentMorningByEmployeeDate[$employeeId][$date] = ($absentMorningByEmployeeDate[$employeeId][$date] ?? false) || $absentMorning;
|
||||
$absentMorningByEmployeeDate[$employeeId][$date] = ($absentMorningByEmployeeDate[$employeeId][$date] ?? false) || $absentMorning;
|
||||
$absentAfternoonByEmployeeDate[$employeeId][$date] = ($absentAfternoonByEmployeeDate[$employeeId][$date] ?? false) || $absentAfternoon;
|
||||
if (!isset($absenceLabelByEmployeeDate[$employeeId][$date])) {
|
||||
$absenceLabelByEmployeeDate[$employeeId][$date] = $absence->getType()?->getLabel();
|
||||
@@ -179,15 +185,21 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$weeklyDayMinutes = 0;
|
||||
$weeklyNightMinutes = 0;
|
||||
$weeklyTotalMinutes = 0;
|
||||
$weeklyPresenceCount = 0.0;
|
||||
$daily = [];
|
||||
$weeklyDayMinutes = 0;
|
||||
$weeklyNightMinutes = 0;
|
||||
$weeklyTotalMinutes = 0;
|
||||
$weeklyPresenceCount = 0.0;
|
||||
$weeklyBreakfastCount = 0;
|
||||
$weeklyLunchCount = 0;
|
||||
$weeklyOvernightCount = 0;
|
||||
$daily = [];
|
||||
// Les contrats au suivi "présence" ne manipulent pas les heures, mais des demi-journées.
|
||||
$weekAnchorContract = $contractsByEmployeeDate[$employeeId][$anchorDateYmd]
|
||||
?? $contractsByEmployeeDate[$employeeId][$days[0]]
|
||||
?? null;
|
||||
$isDriver = $isDriverByEmployeeDate[$employeeId][$anchorDateYmd]
|
||||
?? $isDriverByEmployeeDate[$employeeId][$days[0]]
|
||||
?? false;
|
||||
$weekAnchorContractNature = $contractNaturesByEmployeeDate[$employeeId][$anchorDateYmd]
|
||||
?? $contractNaturesByEmployeeDate[$employeeId][$days[0]]
|
||||
?? ContractNature::CDI;
|
||||
@@ -198,14 +210,42 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
||||
|
||||
foreach ($days as $date) {
|
||||
$entry = $metricsByEmployeeDate[$employeeId][$date] ?? null;
|
||||
$metrics = $entry['metrics'] ?? new WorkMetrics();
|
||||
$creditedMinutes = $creditedByEmployeeDate[$employeeId][$date] ?? 0;
|
||||
$contractAtDate = $employeeContractsByDate[$date] ?? null;
|
||||
$isPresenceTracking = TrackingMode::PRESENCE->value === $contractAtDate?->getTrackingMode();
|
||||
// Les absences "comptées comme travaillées" alimentent le total du jour.
|
||||
$metrics->addCreditedMinutes($creditedMinutes);
|
||||
$isDateDriver = $isDriverByEmployeeDate[$employeeId][$date] ?? false;
|
||||
|
||||
$hasBreakfast = false;
|
||||
$hasLunch = false;
|
||||
$hasOvernight = false;
|
||||
|
||||
if ($isDateDriver) {
|
||||
$dayMinutes = ($entry['dayHoursMinutes'] ?? 0);
|
||||
$nightMinutes = ($entry['nightHoursMinutes'] ?? 0);
|
||||
$totalMinutes = $dayMinutes + $nightMinutes;
|
||||
$hasBreakfast = $entry['hasBreakfast'] ?? false;
|
||||
$hasLunch = $entry['hasLunch'] ?? false;
|
||||
$hasOvernight = $entry['hasOvernight'] ?? false;
|
||||
if ($hasBreakfast) {
|
||||
++$weeklyBreakfastCount;
|
||||
}
|
||||
if ($hasLunch) {
|
||||
++$weeklyLunchCount;
|
||||
}
|
||||
if ($hasOvernight) {
|
||||
++$weeklyOvernightCount;
|
||||
}
|
||||
} else {
|
||||
$metrics = $entry['metrics'] ?? new WorkMetrics();
|
||||
// Les absences "comptées comme travaillées" alimentent le total du jour.
|
||||
$metrics->addCreditedMinutes($creditedMinutes);
|
||||
$dayMinutes = $metrics->dayMinutes;
|
||||
$nightMinutes = $metrics->nightMinutes;
|
||||
$totalMinutes = $metrics->totalMinutes;
|
||||
}
|
||||
|
||||
$present = null;
|
||||
if ($isPresenceTracking) {
|
||||
if ($isPresenceTracking && !$isDateDriver) {
|
||||
$absentMorning = $absentMorningByEmployeeDate[$employeeId][$date] ?? false;
|
||||
$absentAfternoon = $absentAfternoonByEmployeeDate[$employeeId][$date] ?? false;
|
||||
$morning = (($entry['isPresentMorning'] ?? false) && !$absentMorning) ? 0.5 : 0.0;
|
||||
@@ -214,30 +254,33 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
||||
$present = min(1.0, $morning + $afternoon + $creditedPresence);
|
||||
}
|
||||
|
||||
$weeklyDayMinutes += $metrics->dayMinutes;
|
||||
$weeklyNightMinutes += $metrics->nightMinutes;
|
||||
$weeklyTotalMinutes += $metrics->totalMinutes;
|
||||
$weeklyDayMinutes += $dayMinutes;
|
||||
$weeklyNightMinutes += $nightMinutes;
|
||||
$weeklyTotalMinutes += $totalMinutes;
|
||||
if (null !== $present) {
|
||||
$weeklyPresenceCount += $present;
|
||||
}
|
||||
|
||||
$daily[] = new WeeklyDaySummary(
|
||||
date: $date,
|
||||
dayMinutes: $metrics->dayMinutes,
|
||||
nightMinutes: $metrics->nightMinutes,
|
||||
totalMinutes: $metrics->totalMinutes,
|
||||
dayMinutes: $dayMinutes,
|
||||
nightMinutes: $nightMinutes,
|
||||
totalMinutes: $totalMinutes,
|
||||
present: $present,
|
||||
hasAbsence: $absenceByEmployeeDate[$employeeId][$date] ?? false,
|
||||
absenceLabel: $absenceLabelByEmployeeDate[$employeeId][$date] ?? null,
|
||||
absenceColor: $absenceColorByEmployeeDate[$employeeId][$date] ?? null,
|
||||
hasBreakfast: $hasBreakfast,
|
||||
hasLunch: $hasLunch,
|
||||
hasOvernight: $hasOvernight,
|
||||
);
|
||||
}
|
||||
|
||||
$isWeekPresenceTracking = TrackingMode::PRESENCE->value === $weekAnchorContract?->getTrackingMode();
|
||||
$disableOvertimeBonuses = $this->hasDisabledOvertimeBonuses($weekAnchorContract, $weekAnchorContractNature);
|
||||
$disableOvertimeBonuses = $isDriver || $this->hasDisabledOvertimeBonuses($weekAnchorContract, $weekAnchorContractNature);
|
||||
$overtimeReferenceMinutes = $this->computeWeeklyOvertimeReferenceMinutes($days, $employeeContractsByDate);
|
||||
$overtime25StartMinutes = $this->computeWeeklyOvertime25StartMinutes($days, $employeeContractsByDate);
|
||||
$weeklyOvertimeTotalMinutes = $isWeekPresenceTracking
|
||||
$weeklyOvertimeTotalMinutes = ($isWeekPresenceTracking || $isDriver)
|
||||
? 0
|
||||
: max(0, $weeklyTotalMinutes - $overtimeReferenceMinutes);
|
||||
$weeklyOvertime25Minutes = ($isWeekPresenceTracking || $disableOvertimeBonuses)
|
||||
@@ -266,7 +309,11 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
|
||||
weeklyOvertimeTotalMinutes: $weeklyOvertimeTotalMinutes,
|
||||
weeklyOvertime25Minutes: $weeklyOvertime25Minutes,
|
||||
weeklyOvertime50Minutes: $weeklyOvertime50Minutes,
|
||||
weeklyRecoveryMinutes: $weeklyRecoveryMinutes
|
||||
weeklyRecoveryMinutes: $weeklyRecoveryMinutes,
|
||||
isDriver: $isDriver,
|
||||
weeklyBreakfastCount: $weeklyBreakfastCount,
|
||||
weeklyLunchCount: $weeklyLunchCount,
|
||||
weeklyOvernightCount: $weeklyOvernightCount,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user