Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a17d6a67cf | ||
| 29db3b5025 |
@@ -43,6 +43,7 @@
|
|||||||
## Overtime Rules
|
## Overtime Rules
|
||||||
- Contracts <= 35h: +25% from 35h to 43h, +50% beyond
|
- Contracts <= 35h: +25% from 35h to 43h, +50% beyond
|
||||||
- Contracts >= 39h: +25% from 39h to 43h, +50% beyond
|
- Contracts >= 39h: +25% from 39h to 43h, +50% beyond
|
||||||
|
- CUSTOM contracts (weeklyHours ≠ 35 and ≠ 39, not INTERIM/FORFAIT): reference = actual contractual hours, no 25%/50% bonuses (1h overtime = 1h recovery), deficit doesn't impact balance
|
||||||
- INTERIM: no overtime bonuses, no recovery time
|
- INTERIM: no overtime bonuses, no recovery time
|
||||||
- Driver contracts: no overtime calculation
|
- Driver contracts: no overtime calculation
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.58'
|
app.version: '0.1.59'
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ Documents complementaires:
|
|||||||
- Semaine en déficit (heures travaillées < heures contrat):
|
- Semaine en déficit (heures travaillées < heures contrat):
|
||||||
- le déficit est déduit du cumul RTT : d'abord des heures à 50%, puis des heures à 25%
|
- le déficit est déduit du cumul RTT : d'abord des heures à 50%, puis des heures à 25%
|
||||||
- si aucun solde 50% ni 25%, les heures à 25% deviennent négatives
|
- si aucun solde 50% ni 25%, les heures à 25% deviennent négatives
|
||||||
|
- Contrats CUSTOM (heures hebdo ≠ 35h et ≠ 39h, hors INTERIM/FORFAIT):
|
||||||
|
- référence heures sup = heures contractuelles réelles (ex: 4h → référence 4h)
|
||||||
|
- pas de bonus 25% ni 50% : 1 heure sup = 1 heure de récupération
|
||||||
|
- le déficit (travail < contrat) ne génère pas de récup mais n'impacte pas le solde
|
||||||
- Nature `INTERIM`:
|
- Nature `INTERIM`:
|
||||||
- pas de bonus 25%
|
- pas de bonus 25%
|
||||||
- pas de bonus 50%
|
- pas de bonus 50%
|
||||||
|
|||||||
@@ -206,20 +206,36 @@ final readonly class RttRecoveryComputationService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$weekAnchorNature = $naturesByDate[$employeeId][$weekDays[0]] ?? ContractNature::CDI;
|
$weekAnchorNature = $naturesByDate[$employeeId][$weekDays[0]] ?? ContractNature::CDI;
|
||||||
$weekAnchorContract = $employeeContractsByDate[$weekDays[0]] ?? null;
|
$weekAnchorContract = $employeeContractsByDate[$weekDays[0]] ?? null;
|
||||||
$isWeekPresenceTracking = TrackingMode::PRESENCE->value === $weekAnchorContract?->getTrackingMode();
|
$isWeekPresenceTracking = TrackingMode::PRESENCE->value === $weekAnchorContract?->getTrackingMode();
|
||||||
$disableOvertimeBonuses = $this->hasDisabledOvertimeBonuses($weekAnchorContract, $weekAnchorNature);
|
$disableOvertimeBonuses = $this->hasDisabledOvertimeBonuses($weekAnchorContract, $weekAnchorNature);
|
||||||
$overtimeReferenceMinutes = $this->computeWeeklyOvertimeReferenceMinutes($weekDays, $employeeContractsByDate);
|
$weekContractType = ContractType::resolve(
|
||||||
|
$weekAnchorContract?->getName(),
|
||||||
|
$weekAnchorContract?->getTrackingMode(),
|
||||||
|
$weekAnchorContract?->getWeeklyHours()
|
||||||
|
);
|
||||||
|
$isCustomContract = ContractType::CUSTOM === $weekContractType;
|
||||||
|
$overtimeReferenceMinutes = $isCustomContract
|
||||||
|
? $this->computeWeeklyCustomReferenceMinutes($weekDays, $employeeContractsByDate)
|
||||||
|
: $this->computeWeeklyOvertimeReferenceMinutes($weekDays, $employeeContractsByDate);
|
||||||
$overtime25StartMinutes = $this->computeWeeklyOvertime25StartMinutes($weekDays, $employeeContractsByDate);
|
$overtime25StartMinutes = $this->computeWeeklyOvertime25StartMinutes($weekDays, $employeeContractsByDate);
|
||||||
$weeklyOvertimeTotalMinutes = $isWeekPresenceTracking
|
$weeklyOvertimeTotalMinutes = $isWeekPresenceTracking
|
||||||
? 0
|
? 0
|
||||||
: $weeklyTotalMinutes - $overtimeReferenceMinutes;
|
: $weeklyTotalMinutes - $overtimeReferenceMinutes;
|
||||||
|
|
||||||
$base25 = ($isWeekPresenceTracking || $disableOvertimeBonuses) ? 0 : max(0, min($weeklyTotalMinutes, 43 * 60) - $overtime25StartMinutes);
|
$base25 = ($isWeekPresenceTracking || $disableOvertimeBonuses || $isCustomContract) ? 0 : max(0, min($weeklyTotalMinutes, 43 * 60) - $overtime25StartMinutes);
|
||||||
$bonus25 = ($isWeekPresenceTracking || $disableOvertimeBonuses) ? 0 : (int) round($base25 * 0.25);
|
$bonus25 = ($isWeekPresenceTracking || $disableOvertimeBonuses || $isCustomContract) ? 0 : (int) round($base25 * 0.25);
|
||||||
$base50 = ($isWeekPresenceTracking || $disableOvertimeBonuses) ? 0 : max(0, $weeklyTotalMinutes - 43 * 60);
|
$base50 = ($isWeekPresenceTracking || $disableOvertimeBonuses || $isCustomContract) ? 0 : max(0, $weeklyTotalMinutes - 43 * 60);
|
||||||
$bonus50 = ($isWeekPresenceTracking || $disableOvertimeBonuses) ? 0 : (int) round($base50 * 0.5);
|
$bonus50 = ($isWeekPresenceTracking || $disableOvertimeBonuses || $isCustomContract) ? 0 : (int) round($base50 * 0.5);
|
||||||
|
|
||||||
|
if ($isWeekPresenceTracking || $disableOvertimeBonuses) {
|
||||||
|
$totalMinutes = 0;
|
||||||
|
} elseif ($isCustomContract) {
|
||||||
|
$totalMinutes = max(0, $weeklyOvertimeTotalMinutes);
|
||||||
|
} else {
|
||||||
|
$totalMinutes = $weeklyOvertimeTotalMinutes + $bonus25 + $bonus50;
|
||||||
|
}
|
||||||
|
|
||||||
$results[$weekKey] = new WeekRecoveryDetail(
|
$results[$weekKey] = new WeekRecoveryDetail(
|
||||||
overtimeMinutes: $weeklyOvertimeTotalMinutes,
|
overtimeMinutes: $weeklyOvertimeTotalMinutes,
|
||||||
@@ -227,9 +243,7 @@ final readonly class RttRecoveryComputationService
|
|||||||
bonus25Minutes: $bonus25,
|
bonus25Minutes: $bonus25,
|
||||||
base50Minutes: $base50,
|
base50Minutes: $base50,
|
||||||
bonus50Minutes: $bonus50,
|
bonus50Minutes: $bonus50,
|
||||||
totalMinutes: ($isWeekPresenceTracking || $disableOvertimeBonuses)
|
totalMinutes: $totalMinutes,
|
||||||
? 0
|
|
||||||
: $weeklyOvertimeTotalMinutes + $bonus25 + $bonus50,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,6 +340,23 @@ final readonly class RttRecoveryComputationService
|
|||||||
return max(0, $end - $start);
|
return max(0, $end - $start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $days
|
||||||
|
* @param array<string, ?Contract> $contractsByDate
|
||||||
|
*/
|
||||||
|
private function computeWeeklyCustomReferenceMinutes(array $days, array $contractsByDate): int
|
||||||
|
{
|
||||||
|
$total = 0;
|
||||||
|
foreach ($days as $date) {
|
||||||
|
$isoDay = (int) new DateTimeImmutable($date)->format('N');
|
||||||
|
$contract = $contractsByDate[$date] ?? null;
|
||||||
|
$hours = $contract?->getWeeklyHours();
|
||||||
|
$total += $this->resolveDailyReferenceMinutes($hours, $isoDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<string> $days
|
* @param list<string> $days
|
||||||
* @param array<string, ?Contract> $contractsByDate
|
* @param array<string, ?Contract> $contractsByDate
|
||||||
|
|||||||
Reference in New Issue
Block a user