Gestion du changement de type de contrat + correction du calcule des RTT sur un contrat qui commence en milieu de semaine #19
@@ -6,6 +6,7 @@ namespace App\State;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProviderInterface;
|
use ApiPlatform\State\ProviderInterface;
|
||||||
|
use App\Entity\Employee;
|
||||||
use App\Entity\Formation;
|
use App\Entity\Formation;
|
||||||
use App\Enum\ContractNature;
|
use App\Enum\ContractNature;
|
||||||
use App\Enum\HalfDay;
|
use App\Enum\HalfDay;
|
||||||
@@ -56,12 +57,15 @@ class AbsencePrintProvider implements ProviderInterface
|
|||||||
|
|
||||||
$fromDate = DateTimeImmutable::createFromFormat('Y-m-d', $from);
|
$fromDate = DateTimeImmutable::createFromFormat('Y-m-d', $from);
|
||||||
$toDate = DateTimeImmutable::createFromFormat('Y-m-d', $to);
|
$toDate = DateTimeImmutable::createFromFormat('Y-m-d', $to);
|
||||||
|
if (!$fromDate instanceof DateTimeImmutable || !$toDate instanceof DateTimeImmutable) {
|
||||||
|
return new Response('Invalid from/to date format.', Response::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
$siteIds = $this->parseIds($request->query->get('sites'));
|
$siteIds = $this->parseIds($request->query->get('sites'));
|
||||||
$workContractIds = $this->parseIds($request->query->get('workContracts'));
|
$workContractIds = $this->parseIds($request->query->get('workContracts'));
|
||||||
$contractNatures = $this->parseContractNatures($request->query->get('contractNatures'));
|
$contractNatures = $this->parseContractNatures($request->query->get('contractNatures'));
|
||||||
|
|
||||||
$employees = $this->loadEmployees($siteIds, $contractNatures, $workContractIds);
|
$employees = $this->loadEmployees($siteIds, $contractNatures, $workContractIds, $fromDate, $toDate);
|
||||||
$absences = $this->loadAbsences($fromDate, $toDate, $employees);
|
$absences = $this->loadAbsences($fromDate, $toDate, $employees);
|
||||||
$formations = $this->formationRepository->findByDateRangeAndEmployees($fromDate, $toDate, $employees);
|
$formations = $this->formationRepository->findByDateRangeAndEmployees($fromDate, $toDate, $employees);
|
||||||
|
|
||||||
@@ -117,21 +121,41 @@ class AbsencePrintProvider implements ProviderInterface
|
|||||||
return array_values(array_unique($ids));
|
return array_values(array_unique($ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadEmployees(array $siteIds, array $contractNatures, array $workContractIds): array
|
private function loadEmployees(array $siteIds, array $contractNatures, array $workContractIds, DateTimeImmutable $from, DateTimeImmutable $to): array
|
||||||
{
|
{
|
||||||
$employees = $this->employeeRepository->findForPrintBySiteIds($siteIds);
|
$employees = $this->employeeRepository->findForPrintBySiteIds($siteIds);
|
||||||
|
|
||||||
return array_values(array_filter($employees, static function ($employee) use ($contractNatures, $workContractIds): bool {
|
return array_values(array_filter($employees, function ($employee) use ($contractNatures, $workContractIds, $from, $to): bool {
|
||||||
$employeeNature = (string) $employee->getCurrentContractNature();
|
$employeeNature = (string) $employee->getCurrentContractNature();
|
||||||
$employeeContractId = $employee->getContract()?->getId();
|
$employeeContractId = $employee->getContract()?->getId();
|
||||||
|
|
||||||
$natureMatches = [] === $contractNatures || in_array($employeeNature, $contractNatures, true);
|
$natureMatches = [] === $contractNatures || in_array($employeeNature, $contractNatures, true);
|
||||||
$contractMatches = [] === $workContractIds || (null !== $employeeContractId && in_array($employeeContractId, $workContractIds, true));
|
$contractMatches = [] === $workContractIds || (null !== $employeeContractId && in_array($employeeContractId, $workContractIds, true));
|
||||||
|
|
||||||
return $natureMatches && $contractMatches;
|
// Exclut les employés dont aucune période de contrat n'intersecte la période imprimée
|
||||||
|
// (ex. un salarié parti en avril ne doit pas apparaître sur une impression de mai).
|
||||||
|
return $natureMatches && $contractMatches && $this->hasContractInRange($employee, $from, $to);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vrai si au moins une période de contrat de l'employé intersecte [$from, $to].
|
||||||
|
* Une période sans date de fin (contrat en cours) est considérée ouverte jusqu'à l'infini.
|
||||||
|
* Aligné avec le filtre `hasContractInSelectedMonth` de la vue Calendrier.
|
||||||
|
*/
|
||||||
|
private function hasContractInRange(Employee $employee, DateTimeImmutable $from, DateTimeImmutable $to): bool
|
||||||
|
{
|
||||||
|
foreach ($employee->getContractPeriods() as $period) {
|
||||||
|
$start = $period->getStartDate();
|
||||||
|
$end = $period->getEndDate();
|
||||||
|
if ($start <= $to && (null === $end || $end >= $from)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private function loadAbsences(DateTimeImmutable $from, DateTimeImmutable $to, array $employees): array
|
private function loadAbsences(DateTimeImmutable $from, DateTimeImmutable $to, array $employees): array
|
||||||
{
|
{
|
||||||
return $this->absenceRepository->findForPrint($from, $to, $employees);
|
return $this->absenceRepository->findForPrint($from, $to, $employees);
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\State;
|
||||||
|
|
||||||
|
use App\Entity\Employee;
|
||||||
|
use App\Entity\EmployeeContractPeriod;
|
||||||
|
use App\State\AbsencePrintProvider;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider constructor takes final-class collaborators (Twig, repositories) that
|
||||||
|
* PHPUnit cannot double. The pure contract-range helper is exercised via
|
||||||
|
* newInstanceWithoutConstructor + reflection.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class AbsencePrintProviderTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testHasContractInRangeFalseWhenContractEndedBeforeRange(): void
|
||||||
|
{
|
||||||
|
$provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor();
|
||||||
|
$employee = $this->buildEmployeeWithPeriod('2025-01-01', '2026-04-30');
|
||||||
|
|
||||||
|
// Imprime mai : l'employé parti le 30/04 ne doit pas être inclus.
|
||||||
|
self::assertFalse($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasContractInRangeTrueWhenContractOverlapsRange(): void
|
||||||
|
{
|
||||||
|
$provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor();
|
||||||
|
$employee = $this->buildEmployeeWithPeriod('2025-01-01', '2026-05-15');
|
||||||
|
|
||||||
|
// Contrat finissant le 15/05 → chevauche le mois de mai → inclus.
|
||||||
|
self::assertTrue($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasContractInRangeTrueForOpenEndedContract(): void
|
||||||
|
{
|
||||||
|
$provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor();
|
||||||
|
$employee = $this->buildEmployeeWithPeriod('2020-01-01', null);
|
||||||
|
|
||||||
|
self::assertTrue($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasContractInRangeFalseWhenContractStartsAfterRange(): void
|
||||||
|
{
|
||||||
|
$provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor();
|
||||||
|
$employee = $this->buildEmployeeWithPeriod('2026-06-01', null);
|
||||||
|
|
||||||
|
self::assertFalse($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasContractInRangeFalseWhenNoPeriods(): void
|
||||||
|
{
|
||||||
|
$provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor();
|
||||||
|
$employee = new Employee();
|
||||||
|
|
||||||
|
self::assertFalse($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasInRange(object $provider, Employee $employee, string $from, string $to): bool
|
||||||
|
{
|
||||||
|
return new ReflectionClass($provider::class)
|
||||||
|
->getMethod('hasContractInRange')
|
||||||
|
->invoke($provider, $employee, new DateTimeImmutable($from), new DateTimeImmutable($to))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildEmployeeWithPeriod(string $start, ?string $end): Employee
|
||||||
|
{
|
||||||
|
$employee = new Employee();
|
||||||
|
$period = new EmployeeContractPeriod();
|
||||||
|
$period->setEmployee($employee);
|
||||||
|
$period->setStartDate(new DateTimeImmutable($start));
|
||||||
|
$period->setEndDate(null !== $end ? new DateTimeImmutable($end) : null);
|
||||||
|
$employee->getContractPeriods()->add($period);
|
||||||
|
|
||||||
|
return $employee;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user