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\State\ProviderInterface;
|
||||
use App\Entity\Employee;
|
||||
use App\Entity\Formation;
|
||||
use App\Enum\ContractNature;
|
||||
use App\Enum\HalfDay;
|
||||
@@ -56,12 +57,15 @@ class AbsencePrintProvider implements ProviderInterface
|
||||
|
||||
$fromDate = DateTimeImmutable::createFromFormat('Y-m-d', $from);
|
||||
$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'));
|
||||
$workContractIds = $this->parseIds($request->query->get('workContracts'));
|
||||
$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);
|
||||
$formations = $this->formationRepository->findByDateRangeAndEmployees($fromDate, $toDate, $employees);
|
||||
|
||||
@@ -117,21 +121,41 @@ class AbsencePrintProvider implements ProviderInterface
|
||||
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);
|
||||
|
||||
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();
|
||||
$employeeContractId = $employee->getContract()?->getId();
|
||||
|
||||
$natureMatches = [] === $contractNatures || in_array($employeeNature, $contractNatures, 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
|
||||
{
|
||||
return $this->absenceRepository->findForPrint($from, $to, $employees);
|
||||
|
||||
84
tests/State/AbsencePrintProviderTest.php
Normal file
84
tests/State/AbsencePrintProviderTest.php
Normal file
@@ -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