All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | #322 | Page horaire | ## Description de la PR [#322] Page horaire ## Modification du .env ## Check list - [ ] Pas de régression - [ ] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: #4 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
224 lines
7.5 KiB
PHP
224 lines
7.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\State;
|
|
|
|
use ApiPlatform\Metadata\DeleteOperationInterface;
|
|
use ApiPlatform\Metadata\Operation;
|
|
use ApiPlatform\State\ProcessorInterface;
|
|
use App\Entity\Absence;
|
|
use App\Entity\Employee;
|
|
use App\Enum\HalfDay;
|
|
use App\Repository\Contract\AbsenceReadRepositoryInterface;
|
|
use App\Repository\Contract\WorkHourReadRepositoryInterface;
|
|
use DateInterval;
|
|
use DatePeriod;
|
|
use DateTime;
|
|
use DateTimeImmutable;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
|
|
final readonly class AbsenceWriteProcessor implements ProcessorInterface
|
|
{
|
|
public function __construct(
|
|
private EntityManagerInterface $entityManager,
|
|
private AbsenceReadRepositoryInterface $absenceRepository,
|
|
private WorkHourReadRepositoryInterface $workHourRepository,
|
|
) {}
|
|
|
|
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
|
{
|
|
if (!$data instanceof Absence) {
|
|
return $data;
|
|
}
|
|
|
|
$employee = $data->getEmployee();
|
|
if (null === $employee) {
|
|
return $data;
|
|
}
|
|
|
|
if ($operation instanceof DeleteOperationInterface) {
|
|
if ($this->workHourRepository->hasValidatedInRange($employee, $data->getStartDate(), $data->getEndDate())) {
|
|
throw new ConflictHttpException('Impossible de modifier une absence sur une période validée.');
|
|
}
|
|
|
|
$this->entityManager->remove($data);
|
|
$this->entityManager->flush();
|
|
|
|
return null;
|
|
}
|
|
|
|
$segments = $this->expandAbsenceRange($data);
|
|
if ([] === $segments) {
|
|
throw new UnprocessableEntityHttpException('La période de l\'absence est invalide.');
|
|
}
|
|
|
|
$from = DateTimeImmutable::createFromInterface($segments[0]['date']);
|
|
$to = DateTimeImmutable::createFromInterface($segments[count($segments) - 1]['date']);
|
|
|
|
if ($this->workHourRepository->hasValidatedInRange($employee, $from, $to)) {
|
|
throw new ConflictHttpException('Impossible de modifier une absence sur une période validée.');
|
|
}
|
|
|
|
$existing = $this->absenceRepository->findByEmployeeAndDateRange($employee, $from, $to);
|
|
foreach ($existing as $existingAbsence) {
|
|
if ($existingAbsence->getId() === $data->getId()) {
|
|
continue;
|
|
}
|
|
|
|
throw new ConflictHttpException('Cette période chevauche déjà une absence existante.');
|
|
}
|
|
|
|
$first = array_shift($segments);
|
|
if (null === $first) {
|
|
throw new UnprocessableEntityHttpException('La période de l\'absence est invalide.');
|
|
}
|
|
|
|
$data
|
|
->setStartDate($this->toMutableDate($first['date']))
|
|
->setEndDate($this->toMutableDate($first['date']))
|
|
->setStartHalf($first['startHalf'])
|
|
->setEndHalf($first['endHalf'])
|
|
;
|
|
$this->clearWorkHoursForSegment($employee, $first);
|
|
$this->entityManager->persist($data);
|
|
|
|
foreach ($segments as $segment) {
|
|
$absence = new Absence()
|
|
->setEmployee($employee)
|
|
->setType($data->getType())
|
|
->setComment($data->getComment())
|
|
->setStartDate($this->toMutableDate($segment['date']))
|
|
->setEndDate($this->toMutableDate($segment['date']))
|
|
->setStartHalf($segment['startHalf'])
|
|
->setEndHalf($segment['endHalf'])
|
|
;
|
|
|
|
$this->clearWorkHoursForSegment($employee, $segment);
|
|
$this->entityManager->persist($absence);
|
|
}
|
|
|
|
$this->entityManager->flush();
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @return list<array{date: DateTimeImmutable, startHalf: HalfDay, endHalf: HalfDay}>
|
|
*/
|
|
private function expandAbsenceRange(Absence $absence): array
|
|
{
|
|
$start = DateTimeImmutable::createFromInterface($absence->getStartDate());
|
|
$end = DateTimeImmutable::createFromInterface($absence->getEndDate());
|
|
|
|
if ($start > $end) {
|
|
throw new UnprocessableEntityHttpException('La date de fin ne peut pas être avant la date de début.');
|
|
}
|
|
|
|
if (
|
|
$start->format('Y-m-d') === $end->format('Y-m-d')
|
|
&& HalfDay::PM === $absence->getStartHalf()
|
|
&& HalfDay::AM === $absence->getEndHalf()
|
|
) {
|
|
throw new UnprocessableEntityHttpException('La demi-journée de fin ne peut pas être avant la demi-journée de début.');
|
|
}
|
|
|
|
$days = new DatePeriod($start, new DateInterval('P1D'), $end->modify('+1 day'));
|
|
|
|
$segments = [];
|
|
foreach ($days as $day) {
|
|
$isFirst = $day->format('Y-m-d') === $start->format('Y-m-d');
|
|
$isLast = $day->format('Y-m-d') === $end->format('Y-m-d');
|
|
$isSame = $isFirst && $isLast;
|
|
|
|
if ($isSame) {
|
|
$segments[] = [
|
|
'date' => $day,
|
|
'startHalf' => $absence->getStartHalf(),
|
|
'endHalf' => $absence->getEndHalf(),
|
|
];
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($isFirst && HalfDay::PM === $absence->getStartHalf()) {
|
|
$segments[] = [
|
|
'date' => $day,
|
|
'startHalf' => HalfDay::PM,
|
|
'endHalf' => HalfDay::PM,
|
|
];
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($isLast && HalfDay::AM === $absence->getEndHalf()) {
|
|
$segments[] = [
|
|
'date' => $day,
|
|
'startHalf' => HalfDay::AM,
|
|
'endHalf' => HalfDay::AM,
|
|
];
|
|
|
|
continue;
|
|
}
|
|
|
|
$segments[] = [
|
|
'date' => $day,
|
|
'startHalf' => HalfDay::AM,
|
|
'endHalf' => HalfDay::PM,
|
|
];
|
|
}
|
|
|
|
return $segments;
|
|
}
|
|
|
|
private function toMutableDate(DateTimeImmutable $date): DateTime
|
|
{
|
|
return DateTime::createFromImmutable($date);
|
|
}
|
|
|
|
/**
|
|
* @param array{date: DateTimeImmutable, startHalf: HalfDay, endHalf: HalfDay} $segment
|
|
*/
|
|
private function clearWorkHoursForSegment(Employee $employee, array $segment): void
|
|
{
|
|
$workHour = $this->workHourRepository->findOneByEmployeeAndDate($employee, $segment['date']);
|
|
if (null === $workHour) {
|
|
return;
|
|
}
|
|
|
|
// Demi-journée matin: on efface uniquement la plage du matin.
|
|
if (HalfDay::AM === $segment['startHalf'] && HalfDay::AM === $segment['endHalf']) {
|
|
$workHour
|
|
->setMorningFrom(null)
|
|
->setMorningTo(null)
|
|
;
|
|
|
|
return;
|
|
}
|
|
|
|
// Demi-journée après-midi: on efface après-midi + soirée.
|
|
if (HalfDay::PM === $segment['startHalf'] && HalfDay::PM === $segment['endHalf']) {
|
|
$workHour
|
|
->setAfternoonFrom(null)
|
|
->setAfternoonTo(null)
|
|
->setEveningFrom(null)
|
|
->setEveningTo(null)
|
|
;
|
|
|
|
return;
|
|
}
|
|
|
|
// Journée complète: on efface toutes les plages horaires.
|
|
$workHour
|
|
->setMorningFrom(null)
|
|
->setMorningTo(null)
|
|
->setAfternoonFrom(null)
|
|
->setAfternoonTo(null)
|
|
->setEveningFrom(null)
|
|
->setEveningTo(null)
|
|
;
|
|
}
|
|
}
|