fix : ajout de la feature Heures
This commit is contained in:
83
migrations/Version20260219180000.php
Normal file
83
migrations/Version20260219180000.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DatePeriod;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260219180000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Decoupe les absences multi-jours en lignes journalieres.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$rows = $this->connection->fetchAllAssociative(
|
||||||
|
'SELECT id, employee_id, type_id, start_date, end_date, start_half, end_half, comment
|
||||||
|
FROM absences
|
||||||
|
WHERE start_date < end_date
|
||||||
|
ORDER BY id ASC'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$start = DateTimeImmutable::createFromFormat('Y-m-d', (string) $row['start_date']);
|
||||||
|
$end = DateTimeImmutable::createFromFormat('Y-m-d', (string) $row['end_date']);
|
||||||
|
if (!$start instanceof DateTimeImmutable || !$end instanceof DateTimeImmutable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$startHalf = (string) $row['start_half'];
|
||||||
|
$endHalf = (string) $row['end_half'];
|
||||||
|
|
||||||
|
$days = new DatePeriod($start, new DateInterval('P1D'), $end->modify('+1 day'));
|
||||||
|
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');
|
||||||
|
|
||||||
|
if ($isFirst && 'PM' === $startHalf) {
|
||||||
|
$segmentStartHalf = 'PM';
|
||||||
|
$segmentEndHalf = 'PM';
|
||||||
|
} elseif ($isLast && 'AM' === $endHalf) {
|
||||||
|
$segmentStartHalf = 'AM';
|
||||||
|
$segmentEndHalf = 'AM';
|
||||||
|
} else {
|
||||||
|
$segmentStartHalf = 'AM';
|
||||||
|
$segmentEndHalf = 'PM';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection->insert('absences', [
|
||||||
|
'employee_id' => (int) $row['employee_id'],
|
||||||
|
'type_id' => (int) $row['type_id'],
|
||||||
|
'start_date' => $day,
|
||||||
|
'end_date' => $day,
|
||||||
|
'start_half' => $segmentStartHalf,
|
||||||
|
'end_half' => $segmentEndHalf,
|
||||||
|
'comment' => $row['comment'],
|
||||||
|
], [
|
||||||
|
'employee_id' => Types::INTEGER,
|
||||||
|
'type_id' => Types::INTEGER,
|
||||||
|
'start_date' => Types::DATE_IMMUTABLE,
|
||||||
|
'end_date' => Types::DATE_IMMUTABLE,
|
||||||
|
'start_half' => Types::STRING,
|
||||||
|
'end_half' => Types::STRING,
|
||||||
|
'comment' => Types::TEXT,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection->delete('absences', ['id' => (int) $row['id']], ['id' => Types::INTEGER]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->throwIrreversibleMigrationException('Cette migration de decoupage est irreversible.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace App\Repository;
|
|||||||
use App\Entity\Absence;
|
use App\Entity\Absence;
|
||||||
use App\Entity\Employee;
|
use App\Entity\Employee;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
|
use DateTimeInterface;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
@@ -72,4 +73,29 @@ final class AbsenceRepository extends ServiceEntityRepository
|
|||||||
// @var list<Absence> $absences
|
// @var list<Absence> $absences
|
||||||
return $qb->getQuery()->getResult();
|
return $qb->getQuery()->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<Absence>
|
||||||
|
*/
|
||||||
|
public function findByEmployeeAndDateRange(Employee $employee, DateTimeInterface $from, DateTimeInterface $to): array
|
||||||
|
{
|
||||||
|
$fromDate = DateTimeImmutable::createFromInterface($from);
|
||||||
|
$toDate = DateTimeImmutable::createFromInterface($to);
|
||||||
|
|
||||||
|
$qb = $this->createQueryBuilder('a')
|
||||||
|
->leftJoin('a.employee', 'e')
|
||||||
|
->leftJoin('a.type', 't')
|
||||||
|
->addSelect('e', 't')
|
||||||
|
->andWhere('a.employee = :employee')
|
||||||
|
->andWhere('a.startDate >= :from')
|
||||||
|
->andWhere('a.startDate <= :to')
|
||||||
|
->setParameter('employee', $employee)
|
||||||
|
->setParameter('from', $fromDate)
|
||||||
|
->setParameter('to', $toDate)
|
||||||
|
->orderBy('a.startDate', 'ASC')
|
||||||
|
;
|
||||||
|
|
||||||
|
// @var list<Absence> $absences
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,22 @@ use ApiPlatform\Metadata\DeleteOperationInterface;
|
|||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProcessorInterface;
|
use ApiPlatform\State\ProcessorInterface;
|
||||||
use App\Entity\Absence;
|
use App\Entity\Absence;
|
||||||
|
use App\Enum\HalfDay;
|
||||||
|
use App\Repository\AbsenceRepository;
|
||||||
use App\Repository\WorkHourRepository;
|
use App\Repository\WorkHourRepository;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
use DateInterval;
|
||||||
|
use DatePeriod;
|
||||||
|
use DateTime;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||||
|
|
||||||
final readonly class AbsenceWriteProcessor implements ProcessorInterface
|
final readonly class AbsenceWriteProcessor implements ProcessorInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
private EntityManagerInterface $entityManager,
|
||||||
private ProcessorInterface $persistProcessor,
|
private AbsenceRepository $absenceRepository,
|
||||||
#[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')]
|
|
||||||
private ProcessorInterface $removeProcessor,
|
|
||||||
private WorkHourRepository $workHourRepository,
|
private WorkHourRepository $workHourRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -33,14 +38,140 @@ final readonly class AbsenceWriteProcessor implements ProcessorInterface
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->workHourRepository->hasValidatedInRange($employee, $data->getStartDate(), $data->getEndDate())) {
|
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.');
|
throw new ConflictHttpException('Impossible de modifier une absence sur une période validée.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($operation instanceof DeleteOperationInterface) {
|
$existing = $this->absenceRepository->findByEmployeeAndDateRange($employee, $from, $to);
|
||||||
return $this->removeProcessor->process($data, $operation, $uriVariables, $context);
|
foreach ($existing as $existingAbsence) {
|
||||||
|
if ($existingAbsence->getId() === $data->getId()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ConflictHttpException('Cette période chevauche déjà une absence existante.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
$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->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->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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user