306cfd34cd
LST-66 (2.3) backend. Behaviour-preserving move of the absences domain into
src/Module/Absence/. API operations, securities, routes and the 10 MCP tool
names are unchanged.
- 3 entities + 3 enums moved to Domain/{Entity,Enum}; user relations stay on
UserInterface. 3 repositories split into Domain/Repository interfaces +
Doctrine impls (bound in services.yaml); find() kept off interfaces
(findById instead).
- Pure services (AbsenceDayCalculator, PublicHolidayProvider) -> Domain/Service;
AbsenceBalanceService -> Application/Service; State (5), controllers (5),
10 MCP tools and AccrueLeaveCommand -> Infrastructure/.
- New LeaveProfileInterface contract (Shared) exposes the HR getters used by
AbsenceBalanceService/AccrueLeaveCommand; User implements it -> Absence no
longer imports the concrete Core User. MCP tools/command inject
UserRepositoryInterface (findById) instead of the concrete repository.
- Timestampable/Blamable added to AbsenceBalance and AbsencePolicy (additive
migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
NULL + COMMENT). AbsenceRequest untouched (already has createdAt/reviewedAt).
- AbsenceModule registered (id absence, 4 RBAC perms, not re-wired); doctrine
mapping added; team-absences sidebar item gated by the module.
161 tests green, mapping valid, no API route regression, cs-fixer clean.
192 lines
6.3 KiB
PHP
192 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Service;
|
|
|
|
use App\Module\Absence\Domain\Enum\HalfDay;
|
|
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
|
|
use App\Module\Absence\Domain\Service\PublicHolidayProvider;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
class AbsenceDayCalculatorTest extends TestCase
|
|
{
|
|
private AbsenceDayCalculator $calculator;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->calculator = new AbsenceDayCalculator(new PublicHolidayProvider());
|
|
}
|
|
|
|
public function testFullWeekIsFiveWorkingDays(): void
|
|
{
|
|
// Mon 2026-06-01 to Fri 2026-06-05, no holidays that week
|
|
self::assertSame(5.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-01'),
|
|
new DateTimeImmutable('2026-06-05'),
|
|
));
|
|
}
|
|
|
|
public function testWeekendIsSkipped(): void
|
|
{
|
|
// Fri 2026-06-05 to Mon 2026-06-08 => Fri + Mon = 2
|
|
self::assertSame(2.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-05'),
|
|
new DateTimeImmutable('2026-06-08'),
|
|
));
|
|
}
|
|
|
|
public function testHolidayIsSkipped(): void
|
|
{
|
|
// Thu 2026-05-07 to Fri 2026-05-08 (Victoire 1945) => Thu only = 1
|
|
self::assertSame(1.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-05-07'),
|
|
new DateTimeImmutable('2026-05-08'),
|
|
));
|
|
}
|
|
|
|
public function testHalfDayStartSubtractsHalf(): void
|
|
{
|
|
self::assertSame(4.5, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-01'),
|
|
new DateTimeImmutable('2026-06-05'),
|
|
HalfDay::Afternoon,
|
|
));
|
|
}
|
|
|
|
public function testBothHalfDaysSubtractOne(): void
|
|
{
|
|
self::assertSame(4.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-01'),
|
|
new DateTimeImmutable('2026-06-05'),
|
|
HalfDay::Afternoon,
|
|
HalfDay::Morning,
|
|
));
|
|
}
|
|
|
|
public function testSingleFullDayCountsOne(): void
|
|
{
|
|
// Most common request: a single full working day = 1.0 (no half-day).
|
|
self::assertSame(1.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-01'),
|
|
new DateTimeImmutable('2026-06-01'),
|
|
));
|
|
}
|
|
|
|
public function testSingleHalfDay(): void
|
|
{
|
|
self::assertSame(0.5, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-01'),
|
|
new DateTimeImmutable('2026-06-01'),
|
|
HalfDay::Morning,
|
|
));
|
|
}
|
|
|
|
public function testHalfDayEndOnlySubtractsHalf(): void
|
|
{
|
|
// Mirror of testHalfDayStartSubtractsHalf: end half-day only, on a
|
|
// counted multi-day range => 5 - 0.5 = 4.5.
|
|
self::assertSame(4.5, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-01'),
|
|
new DateTimeImmutable('2026-06-05'),
|
|
null,
|
|
HalfDay::Morning,
|
|
));
|
|
}
|
|
|
|
public function testEndBeforeStartCountsZero(): void
|
|
{
|
|
// Inverted range guard (end < start) => 0, never negative.
|
|
self::assertSame(0.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-05'),
|
|
new DateTimeImmutable('2026-06-01'),
|
|
));
|
|
}
|
|
|
|
public function testSingleDayOnWeekendCountsZero(): void
|
|
{
|
|
// Saturday, jours ouvrés: not counted, even with a half-day => 0.
|
|
self::assertSame(0.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-06'),
|
|
new DateTimeImmutable('2026-06-06'),
|
|
HalfDay::Morning,
|
|
HalfDay::Morning,
|
|
));
|
|
}
|
|
|
|
public function testSingleDayOnHolidayCountsZero(): void
|
|
{
|
|
// Fri 2026-05-08 (Victoire 1945): public holiday => 0.
|
|
self::assertSame(0.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-05-08'),
|
|
new DateTimeImmutable('2026-05-08'),
|
|
));
|
|
}
|
|
|
|
public function testHalfDayOnSaturdayCountedInOpenDays(): void
|
|
{
|
|
// Fri + Sat in "ouvrables" mode = 2; the end half-day sits on Saturday,
|
|
// which IS counted here => 2 - 0.5 = 1.5.
|
|
self::assertSame(1.5, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-05'),
|
|
new DateTimeImmutable('2026-06-06'),
|
|
null,
|
|
HalfDay::Morning,
|
|
false,
|
|
));
|
|
}
|
|
|
|
public function testSingleDayWithBothHalfDaysIsStillHalf(): void
|
|
{
|
|
// Real-world single-day request: the form mirrors the start half-day
|
|
// onto the end (same day), so both boundaries carry a half-day. It must
|
|
// still count as 0.5, not 0 (the two boundaries collapse onto one day).
|
|
self::assertSame(0.5, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-01'),
|
|
new DateTimeImmutable('2026-06-01'),
|
|
HalfDay::Morning,
|
|
HalfDay::Morning,
|
|
));
|
|
}
|
|
|
|
public function testHalfDayOnNonCountedStartIsIgnored(): void
|
|
{
|
|
// Sat 2026-06-06 → Mon 2026-06-08, jours ouvrés : Sat & Sun skipped, Mon = 1.
|
|
// startHalfDay sits on Saturday (not counted) => must NOT subtract 0.5.
|
|
self::assertSame(1.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-06'),
|
|
new DateTimeImmutable('2026-06-08'),
|
|
HalfDay::Afternoon,
|
|
));
|
|
}
|
|
|
|
public function testHalfDayOnNonCountedEndIsIgnored(): void
|
|
{
|
|
// Fri 2026-06-05 → Sun 2026-06-07, jours ouvrés : Fri = 1, weekend skipped.
|
|
// endHalfDay sits on Sunday (not counted) => must NOT subtract 0.5.
|
|
self::assertSame(1.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-05'),
|
|
new DateTimeImmutable('2026-06-07'),
|
|
null,
|
|
HalfDay::Morning,
|
|
));
|
|
}
|
|
|
|
public function testWorkingDaysVsOpenDays(): void
|
|
{
|
|
// Fri 2026-06-05 to Mon 2026-06-08, "ouvrables" includes Saturday
|
|
// => Fri + Sat + Mon = 3 (Sunday always skipped)
|
|
self::assertSame(3.0, $this->calculator->countWorkingDays(
|
|
new DateTimeImmutable('2026-06-05'),
|
|
new DateTimeImmutable('2026-06-08'),
|
|
null,
|
|
null,
|
|
false,
|
|
));
|
|
}
|
|
}
|