144 lines
5.9 KiB
PHP
144 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Service\Contracts;
|
|
|
|
use App\Entity\Contract;
|
|
use App\Entity\Employee;
|
|
use App\Entity\EmployeeContractPeriod;
|
|
use App\Enum\ContractNature;
|
|
use App\Enum\ContractType;
|
|
use App\Enum\TrackingMode;
|
|
use App\Service\Contracts\EmployeeContractPhaseResolver;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\TestCase;
|
|
use ReflectionProperty;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
final class EmployeeContractPhaseResolverTest extends TestCase
|
|
{
|
|
public function testSinglePeriodYieldsSinglePhaseMarkedCurrent(): void
|
|
{
|
|
$employee = $this->buildEmployee([
|
|
['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => null],
|
|
]);
|
|
|
|
$phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);
|
|
|
|
self::assertCount(1, $phases);
|
|
self::assertSame(ContractType::H39, $phases[0]->contractType);
|
|
self::assertTrue($phases[0]->isCurrent);
|
|
self::assertNull($phases[0]->endDate);
|
|
}
|
|
|
|
public function testThreeConsecutivePeriodsSameSignatureCollapseIntoSinglePhase(): void
|
|
{
|
|
$employee = $this->buildEmployee([
|
|
['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => '2021-05-31'],
|
|
['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2021-06-01', 'end' => '2022-05-31'],
|
|
['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2022-06-01', 'end' => null],
|
|
]);
|
|
|
|
$phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);
|
|
|
|
self::assertCount(1, $phases);
|
|
self::assertCount(3, $phases[0]->periodIds);
|
|
self::assertSame('2020-06-01', $phases[0]->startDate->format('Y-m-d'));
|
|
self::assertNull($phases[0]->endDate);
|
|
}
|
|
|
|
public function testSwitchFromH39ToForfaitProducesTwoPhasesMostRecentFirst(): void
|
|
{
|
|
$employee = $this->buildEmployee([
|
|
['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => '2026-04-30'],
|
|
['type' => ContractType::FORFAIT, 'hours' => 39, 'driver' => false, 'start' => '2026-05-01', 'end' => null],
|
|
]);
|
|
|
|
$phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);
|
|
|
|
self::assertCount(2, $phases);
|
|
self::assertSame(ContractType::FORFAIT, $phases[0]->contractType);
|
|
self::assertTrue($phases[0]->isCurrent);
|
|
self::assertSame(ContractType::H39, $phases[1]->contractType);
|
|
self::assertFalse($phases[1]->isCurrent);
|
|
self::assertSame('2026-04-30', $phases[1]->endDate?->format('Y-m-d'));
|
|
}
|
|
|
|
public function testInterimBetweenTwoH39PeriodsBreaksThePhases(): void
|
|
{
|
|
$employee = $this->buildEmployee([
|
|
['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2020-06-01', 'end' => '2023-12-31'],
|
|
['type' => ContractType::INTERIM, 'hours' => null, 'driver' => false, 'start' => '2024-01-01', 'end' => '2024-04-30'],
|
|
['type' => ContractType::H39, 'hours' => 39, 'driver' => false, 'start' => '2024-05-01', 'end' => null],
|
|
]);
|
|
|
|
$phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);
|
|
|
|
self::assertCount(3, $phases);
|
|
self::assertSame(ContractType::H39, $phases[0]->contractType);
|
|
self::assertSame(ContractType::INTERIM, $phases[1]->contractType);
|
|
self::assertSame(ContractType::H39, $phases[2]->contractType);
|
|
}
|
|
|
|
public function testCustomPhasesSplitOnWeeklyHoursChange(): void
|
|
{
|
|
$employee = $this->buildEmployee([
|
|
['type' => ContractType::CUSTOM, 'hours' => 28, 'driver' => false, 'start' => '2024-01-01', 'end' => '2024-12-31'],
|
|
['type' => ContractType::CUSTOM, 'hours' => 30, 'driver' => false, 'start' => '2025-01-01', 'end' => null],
|
|
]);
|
|
|
|
$phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);
|
|
|
|
self::assertCount(2, $phases);
|
|
self::assertSame(30, $phases[0]->weeklyHours);
|
|
self::assertSame(28, $phases[1]->weeklyHours);
|
|
}
|
|
|
|
public function testPhasesSplitOnIsDriverChange(): void
|
|
{
|
|
$employee = $this->buildEmployee([
|
|
['type' => ContractType::H35, 'hours' => 35, 'driver' => false, 'start' => '2023-01-01', 'end' => '2024-12-31'],
|
|
['type' => ContractType::H35, 'hours' => 35, 'driver' => true, 'start' => '2025-01-01', 'end' => null],
|
|
]);
|
|
|
|
$phases = new EmployeeContractPhaseResolver()->resolvePhases($employee);
|
|
|
|
self::assertCount(2, $phases);
|
|
self::assertTrue($phases[0]->isDriver);
|
|
self::assertFalse($phases[1]->isDriver);
|
|
}
|
|
|
|
/**
|
|
* @param list<array{type: ContractType, hours: ?int, driver: bool, start: string, end: ?string}> $periodsSpec
|
|
*/
|
|
private function buildEmployee(array $periodsSpec): Employee
|
|
{
|
|
$employee = new Employee();
|
|
$id = 0;
|
|
foreach ($periodsSpec as $spec) {
|
|
$contract = new Contract();
|
|
$contract->setName($spec['type']->value);
|
|
$contract->setTrackingMode(
|
|
ContractType::FORFAIT === $spec['type'] ? TrackingMode::PRESENCE->value : TrackingMode::TIME->value
|
|
);
|
|
$contract->setWeeklyHours($spec['hours']);
|
|
|
|
$period = new EmployeeContractPeriod();
|
|
$reflection = new ReflectionProperty(EmployeeContractPeriod::class, 'id');
|
|
$reflection->setValue($period, ++$id);
|
|
$period->setEmployee($employee);
|
|
$period->setContract($contract);
|
|
$period->setStartDate(new DateTimeImmutable($spec['start']));
|
|
$period->setEndDate(null !== $spec['end'] ? new DateTimeImmutable($spec['end']) : null);
|
|
$period->setContractNature(ContractNature::CDI);
|
|
$period->setIsDriver($spec['driver']);
|
|
$employee->getContractPeriods()->add($period);
|
|
}
|
|
|
|
return $employee;
|
|
}
|
|
}
|