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:
@@ -32,7 +32,12 @@ final class WorkHourBulkUpsert
|
||||
* eveningFrom?:?string,
|
||||
* eveningTo?:?string,
|
||||
* isPresentMorning?:bool,
|
||||
* isPresentAfternoon?:bool
|
||||
* isPresentAfternoon?:bool,
|
||||
* dayHoursMinutes?:?int,
|
||||
* nightHoursMinutes?:?int,
|
||||
* hasBreakfast?:bool,
|
||||
* hasLunch?:bool,
|
||||
* hasOvernight?:bool
|
||||
* }>
|
||||
*/
|
||||
public array $entries = [];
|
||||
|
||||
@@ -27,5 +27,7 @@ final class ContractHistoryItem
|
||||
public ?int $periodId = null,
|
||||
#[Groups(['employee:read'])]
|
||||
public array $suspensions = [],
|
||||
#[Groups(['employee:read'])]
|
||||
public bool $isDriver = false,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ final class DayContextRow
|
||||
public bool $absentAfternoon = false,
|
||||
public int $creditedMinutes = 0,
|
||||
public float $creditedPresenceUnits = 0.0,
|
||||
public bool $isDriverContract = false,
|
||||
) {}
|
||||
|
||||
public function addAbsence(
|
||||
@@ -78,6 +79,7 @@ final class DayContextRow
|
||||
'absentAfternoon' => $this->absentAfternoon,
|
||||
'creditedMinutes' => $this->creditedMinutes,
|
||||
'creditedPresenceUnits' => $this->creditedPresenceUnits,
|
||||
'isDriverContract' => $this->isDriverContract,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -15,5 +15,8 @@ final class WeeklyDaySummary
|
||||
public bool $hasAbsence = false,
|
||||
public ?string $absenceLabel = null,
|
||||
public ?string $absenceColor = null,
|
||||
public bool $hasBreakfast = false,
|
||||
public bool $hasLunch = false,
|
||||
public bool $hasOvernight = false,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,9 @@ final class WeeklySummaryRow
|
||||
public int $weeklyOvertime25Minutes,
|
||||
public int $weeklyOvertime50Minutes,
|
||||
public int $weeklyRecoveryMinutes,
|
||||
public bool $isDriver = false,
|
||||
public int $weeklyBreakfastCount = 0,
|
||||
public int $weeklyLunchCount = 0,
|
||||
public int $weeklyOvernightCount = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,9 @@ class Employee
|
||||
#[Groups(['employee:write'])]
|
||||
private ?string $contractComment = null;
|
||||
|
||||
#[Groups(['employee:write'])]
|
||||
private ?bool $isDriverInput = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new DateTimeImmutable();
|
||||
@@ -245,6 +248,24 @@ class Employee
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsDriverInput(): ?bool
|
||||
{
|
||||
return $this->isDriverInput;
|
||||
}
|
||||
|
||||
public function setIsDriverInput(?bool $isDriverInput): self
|
||||
{
|
||||
$this->isDriverInput = $isDriverInput;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Groups(['employee:read'])]
|
||||
public function getIsDriver(): bool
|
||||
{
|
||||
return $this->resolveCurrentContractPeriod()?->getIsDriver() ?? false;
|
||||
}
|
||||
|
||||
#[Groups(['employee:read'])]
|
||||
public function getCurrentContractNature(): string
|
||||
{
|
||||
@@ -329,6 +350,7 @@ class Employee
|
||||
comment: $period->getComment(),
|
||||
periodId: $period->getId(),
|
||||
suspensions: $suspensionData,
|
||||
isDriver: $period->getIsDriver(),
|
||||
);
|
||||
},
|
||||
$periods
|
||||
|
||||
@@ -39,6 +39,9 @@ class EmployeeContractPeriod
|
||||
#[ORM\Column(type: 'string', length: 20, options: ['default' => ContractNature::CDI->value])]
|
||||
private string $contractNature = ContractNature::CDI->value;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
private bool $isDriver = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
private bool $paidLeaveSettled = false;
|
||||
|
||||
@@ -137,6 +140,18 @@ class EmployeeContractPeriod
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getIsDriver(): bool
|
||||
{
|
||||
return $this->isDriver;
|
||||
}
|
||||
|
||||
public function setIsDriver(bool $isDriver): self
|
||||
{
|
||||
$this->isDriver = $isDriver;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPaidLeaveSettled(): bool
|
||||
{
|
||||
return $this->paidLeaveSettled;
|
||||
|
||||
@@ -99,6 +99,26 @@ class WorkHour
|
||||
#[Groups(['work_hour:read'])]
|
||||
private bool $isPresentAfternoon = false;
|
||||
|
||||
#[ORM\Column(type: 'integer', nullable: true)]
|
||||
#[Groups(['work_hour:read'])]
|
||||
private ?int $dayHoursMinutes = null;
|
||||
|
||||
#[ORM\Column(type: 'integer', nullable: true)]
|
||||
#[Groups(['work_hour:read'])]
|
||||
private ?int $nightHoursMinutes = null;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
#[Groups(['work_hour:read'])]
|
||||
private bool $hasBreakfast = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
#[Groups(['work_hour:read'])]
|
||||
private bool $hasLunch = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
#[Groups(['work_hour:read'])]
|
||||
private bool $hasOvernight = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
||||
#[Groups(['work_hour:read', 'work_hour:validate'])]
|
||||
private bool $isValid = false;
|
||||
@@ -212,6 +232,66 @@ class WorkHour
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDayHoursMinutes(): ?int
|
||||
{
|
||||
return $this->dayHoursMinutes;
|
||||
}
|
||||
|
||||
public function setDayHoursMinutes(?int $dayHoursMinutes): self
|
||||
{
|
||||
$this->dayHoursMinutes = $dayHoursMinutes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNightHoursMinutes(): ?int
|
||||
{
|
||||
return $this->nightHoursMinutes;
|
||||
}
|
||||
|
||||
public function setNightHoursMinutes(?int $nightHoursMinutes): self
|
||||
{
|
||||
$this->nightHoursMinutes = $nightHoursMinutes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHasBreakfast(): bool
|
||||
{
|
||||
return $this->hasBreakfast;
|
||||
}
|
||||
|
||||
public function setHasBreakfast(bool $hasBreakfast): self
|
||||
{
|
||||
$this->hasBreakfast = $hasBreakfast;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHasLunch(): bool
|
||||
{
|
||||
return $this->hasLunch;
|
||||
}
|
||||
|
||||
public function setHasLunch(bool $hasLunch): self
|
||||
{
|
||||
$this->hasLunch = $hasLunch;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHasOvernight(): bool
|
||||
{
|
||||
return $this->hasOvernight;
|
||||
}
|
||||
|
||||
public function setHasOvernight(bool $hasOvernight): self
|
||||
{
|
||||
$this->hasOvernight = $hasOvernight;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPresentMorning(): bool
|
||||
{
|
||||
return $this->isPresentMorning;
|
||||
|
||||
@@ -15,6 +15,7 @@ final readonly class EmployeeContractChangeRequest
|
||||
public ?DateTimeImmutable $contractEndDate,
|
||||
public ?bool $contractPaidLeaveSettled,
|
||||
public ?string $contractComment,
|
||||
public ?bool $isDriver = null,
|
||||
) {}
|
||||
|
||||
public function hasPeriodChangeRequest(): bool
|
||||
|
||||
@@ -19,6 +19,7 @@ final class EmployeeContractChangeRequestFactory
|
||||
contractEndDate: $this->parseOptionalYmd($employee->getContractEndDate(), 'contractEndDate'),
|
||||
contractPaidLeaveSettled: $employee->getContractPaidLeaveSettled(),
|
||||
contractComment: $employee->getContractComment(),
|
||||
isDriver: $employee->getIsDriverInput(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ final class EmployeeContractPeriodBuilder
|
||||
DateTimeImmutable $startDate,
|
||||
?DateTimeImmutable $endDate,
|
||||
ContractNature $nature,
|
||||
bool $isDriver = false,
|
||||
): EmployeeContractPeriod {
|
||||
return new EmployeeContractPeriod()
|
||||
->setEmployee($employee)
|
||||
@@ -25,6 +26,7 @@ final class EmployeeContractPeriodBuilder
|
||||
->setStartDate($startDate)
|
||||
->setEndDate($endDate)
|
||||
->setContractNature($nature)
|
||||
->setIsDriver($isDriver)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
||||
DateTimeImmutable $startDate,
|
||||
?DateTimeImmutable $endDate,
|
||||
ContractNature $nature,
|
||||
bool $isDriver = false,
|
||||
): void {
|
||||
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
|
||||
|
||||
@@ -36,7 +37,7 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
||||
return;
|
||||
}
|
||||
|
||||
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature);
|
||||
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
@@ -69,7 +70,8 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
||||
DateTimeImmutable $startDate,
|
||||
?DateTimeImmutable $endDate,
|
||||
ContractNature $nature,
|
||||
?EmployeeContractPeriod $todayPeriod
|
||||
?EmployeeContractPeriod $todayPeriod,
|
||||
bool $isDriver = false,
|
||||
): void {
|
||||
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
|
||||
|
||||
@@ -81,7 +83,7 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
||||
}
|
||||
}
|
||||
|
||||
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature);
|
||||
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature, $isDriver);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
@@ -91,8 +93,9 @@ final readonly class EmployeeContractPeriodManager implements EmployeeContractPe
|
||||
DateTimeImmutable $startDate,
|
||||
?DateTimeImmutable $endDate,
|
||||
ContractNature $nature,
|
||||
bool $isDriver = false,
|
||||
): void {
|
||||
$period = $this->periodBuilder->build($employee, $contract, $startDate, $endDate, $nature);
|
||||
$period = $this->periodBuilder->build($employee, $contract, $startDate, $endDate, $nature, $isDriver);
|
||||
$this->entityManager->persist($period);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ interface EmployeeContractPeriodManagerInterface
|
||||
DateTimeImmutable $startDate,
|
||||
?DateTimeImmutable $endDate,
|
||||
ContractNature $nature,
|
||||
bool $isDriver = false,
|
||||
): void;
|
||||
|
||||
public function closeCurrentPeriod(
|
||||
@@ -33,6 +34,7 @@ interface EmployeeContractPeriodManagerInterface
|
||||
DateTimeImmutable $startDate,
|
||||
?DateTimeImmutable $endDate,
|
||||
ContractNature $nature,
|
||||
?EmployeeContractPeriod $todayPeriod
|
||||
?EmployeeContractPeriod $todayPeriod,
|
||||
bool $isDriver = false,
|
||||
): void;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,60 @@ readonly class EmployeeContractResolver
|
||||
return $period?->getContract();
|
||||
}
|
||||
|
||||
public function resolveIsDriverForEmployeeAndDate(Employee $employee, DateTimeImmutable $date): bool
|
||||
{
|
||||
$period = $this->periodRepository->findOneCoveringDate($employee, $date);
|
||||
|
||||
return $period?->getIsDriver() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Employee> $employees
|
||||
* @param list<string> $days
|
||||
*
|
||||
* @return array<int, array<string, bool>>
|
||||
*/
|
||||
public function resolveIsDriverForEmployeesAndDays(array $employees, array $days): array
|
||||
{
|
||||
$resolved = [];
|
||||
if ([] === $employees || [] === $days) {
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
foreach ($employees as $employee) {
|
||||
$employeeId = $employee->getId();
|
||||
if (!$employeeId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($days as $day) {
|
||||
$resolved[$employeeId][$day] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$from = new DateTimeImmutable(min($days));
|
||||
$to = new DateTimeImmutable(max($days));
|
||||
$periods = $this->periodRepository->findByEmployeesAndDateRange($employees, $from, $to);
|
||||
foreach ($periods as $period) {
|
||||
$employeeId = $period->getEmployee()?->getId();
|
||||
if (!$employeeId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = $period->getStartDate()->format('Y-m-d');
|
||||
$end = $period->getEndDate()?->format('Y-m-d') ?? '9999-12-31';
|
||||
$isDriver = $period->getIsDriver();
|
||||
foreach ($days as $day) {
|
||||
if ($day < $start || $day > $end) {
|
||||
continue;
|
||||
}
|
||||
$resolved[$employeeId][$day] = $isDriver;
|
||||
}
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
public function resolveNatureForEmployeeAndDate(Employee $employee, DateTimeImmutable $date): ContractNature
|
||||
{
|
||||
$period = $this->periodRepository->findOneCoveringDate($employee, $date);
|
||||
|
||||
@@ -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