feat(rtt) : skip 25/50 deficit cascade for flat (custom) recovery weeks

This commit is contained in:
2026-06-09 10:07:55 +02:00
parent fa3861b122
commit 8a0eb40616
2 changed files with 121 additions and 41 deletions
+57 -30
View File
@@ -142,36 +142,13 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
$summary->rttStartDate = $this->rttStartDate;
$summary->weeks = $this->buildWeekSummaries($weekRanges, $currentByWeekStart, $periodFrom, $periodTo);
// Post-process: distribute deficit weeks across cumulative balance (50% first, then 25%)
$cumulative50 = $carry->base50Minutes + $carry->bonus50Minutes;
$cumulative25 = $carry->base25Minutes + $carry->bonus25Minutes;
foreach ($summary->weeks as $i => $week) {
if ($week->totalMinutes >= 0) {
$cumulative50 += $week->base50Minutes + $week->bonus50Minutes;
$cumulative25 += $week->base25Minutes + $week->bonus25Minutes;
} else {
$deficit = -$week->totalMinutes;
$from50 = min($deficit, max(0, $cumulative50));
$from25 = $deficit - $from50;
$cumulative50 -= $from50;
$cumulative25 -= $from25;
$summary->weeks[$i] = new EmployeeRttWeekSummary(
month: $week->month,
weekNumber: $week->weekNumber,
weekStart: $week->weekStart,
weekEnd: $week->weekEnd,
overtimeMinutes: $week->overtimeMinutes,
base25Minutes: $from25 > 0 ? -$from25 : 0,
bonus25Minutes: 0,
base50Minutes: $from50 > 0 ? -$from50 : 0,
bonus50Minutes: 0,
totalMinutes: $week->totalMinutes,
);
}
}
// Post-process: distribute deficit weeks across cumulative balance (50% first, then 25%).
// Flat-recovery (CUSTOM) weeks are skipped — their deficit only reduces the running cumul.
$summary->weeks = $this->applyDeficitCascade(
$summary->weeks,
$carry->base25Minutes + $carry->bonus25Minutes,
$carry->base50Minutes + $carry->bonus50Minutes,
);
$payments = $this->rttPaymentRepository->findByEmployeeAndYear($employee, $year);
$monthBuckets = [];
@@ -356,6 +333,54 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
return $weekEnd;
}
/**
* Distribue les semaines déficitaires sur les tranches 25/50 accumulées (50 % d'abord,
* puis 25 %), en réécrivant les buckets affichés de chaque semaine déficitaire avec les
* montants négatifs drainés.
*
* Les semaines à récupération plate (CUSTOM 1h = 1h) sont ignorées : elles n'ont pas de
* tranches 25/50, donc leur déficit ne réduit que le cumul courant (calculé ensuite à
* partir de totalMinutes) et les colonnes 25/50 restent à 0.
*
* @param list<EmployeeRttWeekSummary> $weeks
*
* @return list<EmployeeRttWeekSummary>
*/
private function applyDeficitCascade(array $weeks, int $cumulative25, int $cumulative50): array
{
foreach ($weeks as $i => $week) {
if ($week->totalMinutes >= 0 || $week->isFlatRecovery) {
$cumulative50 += $week->base50Minutes + $week->bonus50Minutes;
$cumulative25 += $week->base25Minutes + $week->bonus25Minutes;
continue;
}
$deficit = -$week->totalMinutes;
$from50 = min($deficit, max(0, $cumulative50));
$from25 = $deficit - $from50;
$cumulative50 -= $from50;
$cumulative25 -= $from25;
$weeks[$i] = new EmployeeRttWeekSummary(
month: $week->month,
weekNumber: $week->weekNumber,
weekStart: $week->weekStart,
weekEnd: $week->weekEnd,
overtimeMinutes: $week->overtimeMinutes,
base25Minutes: $from25 > 0 ? -$from25 : 0,
bonus25Minutes: 0,
base50Minutes: $from50 > 0 ? -$from50 : 0,
bonus50Minutes: 0,
totalMinutes: $week->totalMinutes,
isFlatRecovery: $week->isFlatRecovery,
);
}
return $weeks;
}
/**
* Build week summaries, splitting weeks that span two months into two entries
* with values distributed proportionally based on daily worked minutes.
@@ -393,6 +418,7 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
base50Minutes: $detail->base50Minutes,
bonus50Minutes: $detail->bonus50Minutes,
totalMinutes: $detail->totalMinutes,
isFlatRecovery: $detail->isFlatRecovery,
);
continue;
@@ -433,6 +459,7 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
base50Minutes: (int) round($detail->base50Minutes * $ratio),
bonus50Minutes: (int) round($detail->bonus50Minutes * $ratio),
totalMinutes: (int) round($detail->totalMinutes * $ratio),
isFlatRecovery: $detail->isFlatRecovery,
);
}
}