refactor(night) : mutualiser le calcul de nuit via NightHoursCalculator

This commit is contained in:
2026-06-11 11:42:45 +02:00
parent bd1a9d120e
commit 320e00d63e
4 changed files with 12 additions and 68 deletions
@@ -29,6 +29,7 @@ class YearlyHoursExportBuilder
private WorkedHoursCreditPolicy $workedHoursCreditPolicy, private WorkedHoursCreditPolicy $workedHoursCreditPolicy,
private PublicHolidayServiceInterface $publicHolidayService, private PublicHolidayServiceInterface $publicHolidayService,
private HolidayVirtualHoursResolver $holidayVirtualHoursResolver, private HolidayVirtualHoursResolver $holidayVirtualHoursResolver,
private NightHoursCalculator $nightHoursCalculator,
) {} ) {}
/** /**
@@ -541,14 +542,12 @@ class YearlyHoursExportBuilder
]; ];
$totalMinutes = 0; $totalMinutes = 0;
$nightMinutes = 0;
foreach ($ranges as [$from, $to]) { foreach ($ranges as [$from, $to]) {
$totalMinutes += $this->intervalMinutes($from, $to); $totalMinutes += $this->intervalMinutes($from, $to);
$nightMinutes += $this->nightIntervalMinutes($from, $to);
} }
$dayMinutes = max(0, $totalMinutes - $nightMinutes); $nightMinutes = $this->nightHoursCalculator->nightMinutesFromRanges($workHour);
$dayMinutes = max(0, $totalMinutes - $nightMinutes);
return new WorkMetrics( return new WorkMetrics(
dayMinutes: $dayMinutes, dayMinutes: $dayMinutes,
@@ -596,35 +595,6 @@ class YearlyHoursExportBuilder
return max(0, $end - $start); return max(0, $end - $start);
} }
private function nightIntervalMinutes(?string $from, ?string $to): int
{
$interval = $this->resolveInterval($from, $to);
if (null === $interval) {
return 0;
}
[$start, $end] = $interval;
$windows = [[0, 360], [1260, 1440]];
$total = 0;
for ($dayOffset = 0; $dayOffset <= 1; ++$dayOffset) {
$shift = $dayOffset * 1440;
foreach ($windows as [$windowStart, $windowEnd]) {
$total += $this->overlap($start, $end, $windowStart + $shift, $windowEnd + $shift);
}
}
return $total;
}
private function overlap(int $startA, int $endA, int $startB, int $endB): int
{
$start = max($startA, $startB);
$end = min($endA, $endB);
return max(0, $end - $start);
}
private function formatMinutes(int $minutes): string private function formatMinutes(int $minutes): string
{ {
if (0 === $minutes) { if (0 === $minutes) {
+4 -35
View File
@@ -28,6 +28,7 @@ use App\Service\PublicHolidayServiceInterface;
use App\Service\WorkHours\AbsenceSegmentsResolver; use App\Service\WorkHours\AbsenceSegmentsResolver;
use App\Service\WorkHours\DailyReferenceMinutesResolver; use App\Service\WorkHours\DailyReferenceMinutesResolver;
use App\Service\WorkHours\HolidayVirtualHoursResolver; use App\Service\WorkHours\HolidayVirtualHoursResolver;
use App\Service\WorkHours\NightHoursCalculator;
use App\Service\WorkHours\WorkedHoursCreditPolicy; use App\Service\WorkHours\WorkedHoursCreditPolicy;
use DateTimeImmutable; use DateTimeImmutable;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
@@ -51,6 +52,7 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
private HolidayVirtualHoursResolver $holidayVirtualHoursResolver, private HolidayVirtualHoursResolver $holidayVirtualHoursResolver,
private PublicHolidayServiceInterface $publicHolidayService, private PublicHolidayServiceInterface $publicHolidayService,
private EmployeeWeekCommentRepository $weekCommentRepository, private EmployeeWeekCommentRepository $weekCommentRepository,
private NightHoursCalculator $nightHoursCalculator,
) {} ) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): WorkHourWeeklySummary public function provide(Operation $operation, array $uriVariables = [], array $context = []): WorkHourWeeklySummary
@@ -433,14 +435,12 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
]; ];
$totalMinutes = 0; $totalMinutes = 0;
$nightMinutes = 0;
foreach ($ranges as [$from, $to]) { foreach ($ranges as [$from, $to]) {
$totalMinutes += $this->intervalMinutes($from, $to); $totalMinutes += $this->intervalMinutes($from, $to);
$nightMinutes += $this->nightIntervalMinutes($from, $to);
} }
$dayMinutes = max(0, $totalMinutes - $nightMinutes); $nightMinutes = $this->nightHoursCalculator->nightMinutesFromRanges($workHour);
$dayMinutes = max(0, $totalMinutes - $nightMinutes);
return new WorkMetrics( return new WorkMetrics(
dayMinutes: $dayMinutes, dayMinutes: $dayMinutes,
@@ -489,37 +489,6 @@ final readonly class WorkHourWeeklySummaryProvider implements ProviderInterface
return max(0, $end - $start); return max(0, $end - $start);
} }
private function nightIntervalMinutes(?string $from, ?string $to): int
{
$interval = $this->resolveInterval($from, $to);
if (null === $interval) {
return 0;
}
[$start, $end] = $interval;
// Fenêtres de nuit: 00:00-06:00 et 21:00-24:00.
$windows = [[0, 360], [1260, 1440]];
$total = 0;
// On projette aussi sur J+1 pour couvrir les shifts qui traversent minuit.
for ($dayOffset = 0; $dayOffset <= 1; ++$dayOffset) {
$shift = $dayOffset * 1440;
foreach ($windows as [$windowStart, $windowEnd]) {
$total += $this->overlap($start, $end, $windowStart + $shift, $windowEnd + $shift);
}
}
return $total;
}
private function overlap(int $startA, int $endA, int $startB, int $endB): int
{
$start = max($startA, $startB);
$end = min($endA, $endB);
return max(0, $end - $start);
}
/** /**
* @param array<string, ?Contract> $contractsByDate * @param array<string, ?Contract> $contractsByDate
*/ */
@@ -14,6 +14,7 @@ use App\Service\PublicHolidayServiceInterface;
use App\Service\WorkHours\AbsenceSegmentsResolver; use App\Service\WorkHours\AbsenceSegmentsResolver;
use App\Service\WorkHours\DailyReferenceMinutesResolver; use App\Service\WorkHours\DailyReferenceMinutesResolver;
use App\Service\WorkHours\HolidayVirtualHoursResolver; use App\Service\WorkHours\HolidayVirtualHoursResolver;
use App\Service\WorkHours\NightHoursCalculator;
use App\Service\WorkHours\WorkedHoursCreditPolicy; use App\Service\WorkHours\WorkedHoursCreditPolicy;
use App\Service\WorkHours\YearlyHoursExportBuilder; use App\Service\WorkHours\YearlyHoursExportBuilder;
use DateTimeImmutable; use DateTimeImmutable;
@@ -87,6 +88,7 @@ final class YearlyHoursDayRowsTest extends TestCase
new WorkedHoursCreditPolicy($contractResolver, new DailyReferenceMinutesResolver()), new WorkedHoursCreditPolicy($contractResolver, new DailyReferenceMinutesResolver()),
$holidayService, $holidayService,
$virtualResolver, $virtualResolver,
new NightHoursCalculator(),
); );
$rows = $builder->buildDayRowsForEmployees([$withContract, $noContract], $date); $rows = $builder->buildDayRowsForEmployees([$withContract, $noContract], $date);
@@ -21,6 +21,7 @@ use App\Service\PublicHolidayServiceInterface;
use App\Service\WorkHours\AbsenceSegmentsResolver; use App\Service\WorkHours\AbsenceSegmentsResolver;
use App\Service\WorkHours\DailyReferenceMinutesResolver; use App\Service\WorkHours\DailyReferenceMinutesResolver;
use App\Service\WorkHours\HolidayVirtualHoursResolver; use App\Service\WorkHours\HolidayVirtualHoursResolver;
use App\Service\WorkHours\NightHoursCalculator;
use App\Service\WorkHours\WorkedHoursCreditPolicy; use App\Service\WorkHours\WorkedHoursCreditPolicy;
use App\State\WorkHourWeeklySummaryProvider; use App\State\WorkHourWeeklySummaryProvider;
use DateTime; use DateTime;
@@ -69,6 +70,7 @@ final class WorkHourWeeklySummaryProviderTest extends TestCase
$this->buildHolidayResolver(), $this->buildHolidayResolver(),
$this->buildHolidayService(), $this->buildHolidayService(),
$this->buildWeekCommentRepoStub(), $this->buildWeekCommentRepoStub(),
new NightHoursCalculator(),
); );
$this->expectException(AccessDeniedHttpException::class); $this->expectException(AccessDeniedHttpException::class);
@@ -133,6 +135,7 @@ final class WorkHourWeeklySummaryProviderTest extends TestCase
$this->buildHolidayResolver(), $this->buildHolidayResolver(),
$this->buildHolidayService(), $this->buildHolidayService(),
$this->buildWeekCommentRepoStub(), $this->buildWeekCommentRepoStub(),
new NightHoursCalculator(),
); );
$result = $provider->provide(new Get()); $result = $provider->provide(new Get());