feat : ajout des suspensions et des jours de présence
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
This commit is contained in:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Tests\Service\Leave;
|
||||
|
||||
use App\Service\Leave\LeaveBalanceComputationService;
|
||||
use App\Service\Leave\SuspensionDaysCalculator;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
@@ -16,7 +17,7 @@ final class LeaveBalanceComputationServiceTest extends TestCase
|
||||
{
|
||||
public function testComputeAccruedDaysProratesPartialFirstMonth(): void
|
||||
{
|
||||
$service = new ReflectionClass(LeaveBalanceComputationService::class)->newInstanceWithoutConstructor();
|
||||
$service = $this->createServiceWithoutConstructor();
|
||||
$method = new ReflectionClass(LeaveBalanceComputationService::class)->getMethod('computeAccruedDays');
|
||||
|
||||
$result = $method->invoke(
|
||||
@@ -32,7 +33,7 @@ final class LeaveBalanceComputationServiceTest extends TestCase
|
||||
|
||||
public function testComputeAccruedDaysTotalMatchesAlainCase(): void
|
||||
{
|
||||
$service = new ReflectionClass(LeaveBalanceComputationService::class)->newInstanceWithoutConstructor();
|
||||
$service = $this->createServiceWithoutConstructor();
|
||||
$method = new ReflectionClass(LeaveBalanceComputationService::class)->getMethod('computeAccruedDays');
|
||||
|
||||
$days = $method->invoke(
|
||||
@@ -55,7 +56,7 @@ final class LeaveBalanceComputationServiceTest extends TestCase
|
||||
|
||||
public function testComputeAccruedDaysIncludesLastDayOfMonthDespiteTimeComponents(): void
|
||||
{
|
||||
$service = new ReflectionClass(LeaveBalanceComputationService::class)->newInstanceWithoutConstructor();
|
||||
$service = $this->createServiceWithoutConstructor();
|
||||
$method = new ReflectionClass(LeaveBalanceComputationService::class)->getMethod('computeAccruedDays');
|
||||
|
||||
$result = $method->invoke(
|
||||
@@ -68,4 +69,15 @@ final class LeaveBalanceComputationServiceTest extends TestCase
|
||||
|
||||
self::assertEqualsWithDelta(25.0 / 12.0, $result, 0.0001);
|
||||
}
|
||||
|
||||
private function createServiceWithoutConstructor(): LeaveBalanceComputationService
|
||||
{
|
||||
$rc = new ReflectionClass(LeaveBalanceComputationService::class);
|
||||
$service = $rc->newInstanceWithoutConstructor();
|
||||
|
||||
$prop = $rc->getProperty('suspensionDaysCalculator');
|
||||
$prop->setValue($service, new SuspensionDaysCalculator());
|
||||
|
||||
return $service;
|
||||
}
|
||||
}
|
||||
|
||||
141
tests/Service/Leave/SuspensionDaysCalculatorTest.php
Normal file
141
tests/Service/Leave/SuspensionDaysCalculatorTest.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service\Leave;
|
||||
|
||||
use App\Entity\ContractSuspension;
|
||||
use App\Service\Leave\SuspensionDaysCalculator;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SuspensionDaysCalculatorTest extends TestCase
|
||||
{
|
||||
public function testNoSuspensionsReturnsZero(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
$result = $calc->countSuspendedDaysInMonth(
|
||||
new DateTimeImmutable('2026-03-01'),
|
||||
new DateTimeImmutable('2026-03-31'),
|
||||
[]
|
||||
);
|
||||
|
||||
self::assertSame(0, $result);
|
||||
}
|
||||
|
||||
public function testFullMonthSuspension(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
$suspension = $this->buildSuspension('2026-03-01', '2026-03-31');
|
||||
|
||||
$result = $calc->countSuspendedDaysInMonth(
|
||||
new DateTimeImmutable('2026-03-01'),
|
||||
new DateTimeImmutable('2026-03-31'),
|
||||
[$suspension]
|
||||
);
|
||||
|
||||
self::assertSame(31, $result);
|
||||
}
|
||||
|
||||
public function testPartialMonthSuspension(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
$suspension = $this->buildSuspension('2026-03-10', '2026-03-20');
|
||||
|
||||
$result = $calc->countSuspendedDaysInMonth(
|
||||
new DateTimeImmutable('2026-03-01'),
|
||||
new DateTimeImmutable('2026-03-31'),
|
||||
[$suspension]
|
||||
);
|
||||
|
||||
self::assertSame(11, $result);
|
||||
}
|
||||
|
||||
public function testSuspensionSpanningMultipleMonths(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
$suspension = $this->buildSuspension('2026-02-15', '2026-04-10');
|
||||
|
||||
// March fully covered
|
||||
$result = $calc->countSuspendedDaysInMonth(
|
||||
new DateTimeImmutable('2026-03-01'),
|
||||
new DateTimeImmutable('2026-03-31'),
|
||||
[$suspension]
|
||||
);
|
||||
|
||||
self::assertSame(31, $result);
|
||||
}
|
||||
|
||||
public function testSuspensionWithoutEndDate(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
$suspension = $this->buildSuspension('2026-03-15', null);
|
||||
|
||||
$result = $calc->countSuspendedDaysInMonth(
|
||||
new DateTimeImmutable('2026-03-01'),
|
||||
new DateTimeImmutable('2026-03-31'),
|
||||
[$suspension]
|
||||
);
|
||||
|
||||
self::assertSame(17, $result);
|
||||
}
|
||||
|
||||
public function testMultipleSuspensionsInSameMonth(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
$s1 = $this->buildSuspension('2026-03-01', '2026-03-10');
|
||||
$s2 = $this->buildSuspension('2026-03-20', '2026-03-25');
|
||||
|
||||
$result = $calc->countSuspendedDaysInMonth(
|
||||
new DateTimeImmutable('2026-03-01'),
|
||||
new DateTimeImmutable('2026-03-31'),
|
||||
[$s1, $s2]
|
||||
);
|
||||
|
||||
self::assertSame(16, $result);
|
||||
}
|
||||
|
||||
public function testSuspensionOutsideMonthReturnsZero(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
$suspension = $this->buildSuspension('2026-01-01', '2026-01-31');
|
||||
|
||||
$result = $calc->countSuspendedDaysInMonth(
|
||||
new DateTimeImmutable('2026-03-01'),
|
||||
new DateTimeImmutable('2026-03-31'),
|
||||
[$suspension]
|
||||
);
|
||||
|
||||
self::assertSame(0, $result);
|
||||
}
|
||||
|
||||
public function testCountSuspendedBusinessDays(): void
|
||||
{
|
||||
$calc = new SuspensionDaysCalculator();
|
||||
// March 2-6, 2026 = Mon-Fri = 5 business days
|
||||
$suspension = $this->buildSuspension('2026-03-02', '2026-03-06');
|
||||
|
||||
$result = $calc->countSuspendedBusinessDays(
|
||||
new DateTimeImmutable('2026-01-01'),
|
||||
new DateTimeImmutable('2026-12-31'),
|
||||
[$suspension],
|
||||
[]
|
||||
);
|
||||
|
||||
self::assertSame(5, $result);
|
||||
}
|
||||
|
||||
private function buildSuspension(string $start, ?string $end): ContractSuspension
|
||||
{
|
||||
$s = new ContractSuspension();
|
||||
$s->setStartDate(new DateTimeImmutable($start));
|
||||
if (null !== $end) {
|
||||
$s->setEndDate(new DateTimeImmutable($end));
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
132
tests/State/ContractSuspensionWriteProcessorTest.php
Normal file
132
tests/State/ContractSuspensionWriteProcessorTest.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\State;
|
||||
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\ContractSuspension;
|
||||
use App\Entity\EmployeeContractPeriod;
|
||||
use App\Enum\ContractNature;
|
||||
use App\State\ContractSuspensionWriteProcessor;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionProperty;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ContractSuspensionWriteProcessorTest extends TestCase
|
||||
{
|
||||
public function testPersistsValidSuspension(): void
|
||||
{
|
||||
$period = $this->buildPeriodWithId(1, '2026-01-01', null);
|
||||
$suspension = new ContractSuspension();
|
||||
$suspension->setContractPeriod($period);
|
||||
$suspension->setStartDate(new DateTimeImmutable('2026-03-01'));
|
||||
$suspension->setEndDate(new DateTimeImmutable('2026-04-30'));
|
||||
$suspension->setComment('Congé sans solde');
|
||||
|
||||
$persistProcessor = $this->createMock(ProcessorInterface::class);
|
||||
$persistProcessor->expects(self::once())->method('process')->willReturn($suspension);
|
||||
|
||||
$entityManager = $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$processor = new ContractSuspensionWriteProcessor($persistProcessor, $entityManager);
|
||||
$result = $processor->process($suspension, new Post());
|
||||
|
||||
self::assertSame($suspension, $result);
|
||||
}
|
||||
|
||||
public function testRejectsEndDateBeforeStartDate(): void
|
||||
{
|
||||
$period = $this->buildPeriodWithId(1, '2026-01-01', null);
|
||||
$suspension = new ContractSuspension();
|
||||
$suspension->setContractPeriod($period);
|
||||
$suspension->setStartDate(new DateTimeImmutable('2026-05-01'));
|
||||
$suspension->setEndDate(new DateTimeImmutable('2026-03-01'));
|
||||
|
||||
$persistProcessor = $this->createStub(ProcessorInterface::class);
|
||||
$entityManager = $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$processor = new ContractSuspensionWriteProcessor($persistProcessor, $entityManager);
|
||||
|
||||
$this->expectException(UnprocessableEntityHttpException::class);
|
||||
$processor->process($suspension, new Post());
|
||||
}
|
||||
|
||||
public function testRejectsStartDateBeforePeriodStart(): void
|
||||
{
|
||||
$period = $this->buildPeriodWithId(1, '2026-06-01', null);
|
||||
$suspension = new ContractSuspension();
|
||||
$suspension->setContractPeriod($period);
|
||||
$suspension->setStartDate(new DateTimeImmutable('2026-01-01'));
|
||||
|
||||
$persistProcessor = $this->createStub(ProcessorInterface::class);
|
||||
$entityManager = $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$processor = new ContractSuspensionWriteProcessor($persistProcessor, $entityManager);
|
||||
|
||||
$this->expectException(UnprocessableEntityHttpException::class);
|
||||
$processor->process($suspension, new Post());
|
||||
}
|
||||
|
||||
public function testRejectsOverlappingSuspension(): void
|
||||
{
|
||||
$period = $this->buildPeriodWithId(1, '2026-01-01', null);
|
||||
|
||||
$existing = new ContractSuspension();
|
||||
$existing->setContractPeriod($period);
|
||||
$existing->setStartDate(new DateTimeImmutable('2026-03-01'));
|
||||
$existing->setEndDate(new DateTimeImmutable('2026-04-30'));
|
||||
$period->getSuspensions()->add($existing);
|
||||
|
||||
$suspension = new ContractSuspension();
|
||||
$suspension->setContractPeriod($period);
|
||||
$suspension->setStartDate(new DateTimeImmutable('2026-04-01'));
|
||||
$suspension->setEndDate(new DateTimeImmutable('2026-05-31'));
|
||||
|
||||
$persistProcessor = $this->createStub(ProcessorInterface::class);
|
||||
$entityManager = $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$processor = new ContractSuspensionWriteProcessor($persistProcessor, $entityManager);
|
||||
|
||||
$this->expectException(UnprocessableEntityHttpException::class);
|
||||
$processor->process($suspension, new Post());
|
||||
}
|
||||
|
||||
public function testRejectsClosedPeriod(): void
|
||||
{
|
||||
$period = $this->buildPeriodWithId(1, '2025-01-01', '2025-12-31');
|
||||
$suspension = new ContractSuspension();
|
||||
$suspension->setContractPeriod($period);
|
||||
$suspension->setStartDate(new DateTimeImmutable('2025-06-01'));
|
||||
$suspension->setEndDate(new DateTimeImmutable('2025-07-31'));
|
||||
|
||||
$persistProcessor = $this->createStub(ProcessorInterface::class);
|
||||
$entityManager = $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$processor = new ContractSuspensionWriteProcessor($persistProcessor, $entityManager);
|
||||
|
||||
$this->expectException(UnprocessableEntityHttpException::class);
|
||||
$processor->process($suspension, new Post());
|
||||
}
|
||||
|
||||
private function buildPeriodWithId(int $id, string $start, ?string $end): EmployeeContractPeriod
|
||||
{
|
||||
$period = new EmployeeContractPeriod();
|
||||
$period->setStartDate(new DateTimeImmutable($start));
|
||||
if (null !== $end) {
|
||||
$period->setEndDate(new DateTimeImmutable($end));
|
||||
}
|
||||
$period->setContractNature(ContractNature::CDI);
|
||||
|
||||
$ref = new ReflectionProperty($period, 'id');
|
||||
$ref->setValue($period, $id);
|
||||
|
||||
return $period;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user