fix(absences) : demi-journée mono-jour décomptée 0,5 (et non 0)
Sur une demande d'un seul jour, le formulaire recopie la demi-journée de début sur la fin (même date), si bien que les deux bornes portaient une demi-journée et 0,5 était soustrait deux fois (1 - 0,5 - 0,5 = 0). Quand start et end tombent le même jour, les deux bornes se confondent : on ne soustrait désormais 0,5 qu'une seule fois. Comparaison par getTimestamp() pour rester compatible strict_comparison (=== sur deux DateTimeImmutable distincts teste l'identité d'instance, pas la valeur). Couverture complétée : mono-jour plein, week-end, férié, inversion de dates, demi-journée de fin seule, demi sur samedi en mode ouvrables. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -54,11 +54,19 @@ final readonly class AbsenceDayCalculator
|
||||
// A half-day only subtracts 0.5 when its boundary day is actually
|
||||
// counted (otherwise a half-day posted on a weekend/holiday would
|
||||
// wrongly under-count the absence).
|
||||
if (null !== $startHalfDay && $this->isCountedDay($start, $workingDaysOnly)) {
|
||||
$days -= 0.5;
|
||||
}
|
||||
if (null !== $endHalfDay && $this->isCountedDay($end, $workingDaysOnly)) {
|
||||
$days -= 0.5;
|
||||
if ($start->getTimestamp() === $end->getTimestamp()) {
|
||||
// Single-day request: both boundaries collapse onto the same day,
|
||||
// so a half-day must subtract 0.5 once, never twice.
|
||||
if ((null !== $startHalfDay || null !== $endHalfDay) && $this->isCountedDay($start, $workingDaysOnly)) {
|
||||
$days -= 0.5;
|
||||
}
|
||||
} else {
|
||||
if (null !== $startHalfDay && $this->isCountedDay($start, $workingDaysOnly)) {
|
||||
$days -= 0.5;
|
||||
}
|
||||
if (null !== $endHalfDay && $this->isCountedDay($end, $workingDaysOnly)) {
|
||||
$days -= 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
return max(0.0, $days);
|
||||
|
||||
@@ -68,6 +68,15 @@ class AbsenceDayCalculatorTest extends TestCase
|
||||
));
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -77,6 +86,73 @@ class AbsenceDayCalculatorTest extends TestCase
|
||||
));
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user