Gestion du changement de type de contrat + correction du calcule des RTT sur un contrat qui commence en milieu de semaine #19

Merged
tristan merged 55 commits from feat/contract-phase-view-selector into develop 2026-05-22 06:42:33 +00:00
2 changed files with 112 additions and 4 deletions
Showing only changes of commit ddd1b8116e - Show all commits

View File

@@ -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);

View 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;
}
}