0a9b26d31e
Auto Tag Develop / tag (push) Successful in 6s
Les heures contractuelles au-delà de 35h (ex. 39h → 17,33h décimales = 17h20/mois) sont payées chaque mois sans transiter par les paiements RTT (référence 39h). Elles manquaient au contingent. Ajout via StructuralOvertimeContingentCalculator : (weeklyHours-35)×260 min/mois, généralisé aux contrats non-forfait/non-intérim >35h, proratisé aux jours sous contrat. Branché sur l'encart fiche et l'export PDF. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
3.0 KiB
PHP
82 lines
3.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Service\WorkHours;
|
||
|
||
use App\Entity\Employee;
|
||
use App\Enum\ContractType;
|
||
use DateTimeImmutable;
|
||
|
||
/**
|
||
* Heures supplémentaires « structurelles » payées chaque mois pour les contrats
|
||
* au-dessus de 35h (hors forfait/intérim) : les (weeklyHours − 35) h/semaine
|
||
* au-delà de la durée légale sont payées chaque mois, lissées sur l'année :
|
||
* (weeklyHours − 35) × 52/12 h/mois = (weeklyHours − 35) × 260 min/mois.
|
||
*
|
||
* Ces heures ne transitent pas par les paiements RTT (la référence d'un 39h est
|
||
* 39h, pas 35h) mais comptent dans le contingent légal d'heures supplémentaires.
|
||
* Elles sont proratisées aux jours réellement sous contrat dans chaque mois.
|
||
*/
|
||
final readonly class StructuralOvertimeContingentCalculator
|
||
{
|
||
/** 60 min × 52 semaines / 12 mois = minutes mensuelles par heure hebdo au-delà de 35h. */
|
||
private const int MINUTES_PER_WEEKLY_HOUR_PER_MONTH = 260;
|
||
|
||
/**
|
||
* @return array<int, int> clé 1..12 -> minutes structurelles payées (proratisées)
|
||
*/
|
||
public function monthlyStructuralMinutes(Employee $employee, int $civilYear): array
|
||
{
|
||
$accumulated = array_fill(1, 12, 0.0);
|
||
|
||
foreach ($employee->getContractPeriods() as $period) {
|
||
$contract = $period->getContract();
|
||
if (null === $contract) {
|
||
continue;
|
||
}
|
||
|
||
$type = $contract->getType();
|
||
if (ContractType::FORFAIT === $type || ContractType::INTERIM === $type) {
|
||
continue;
|
||
}
|
||
|
||
$weeklyHours = $contract->getWeeklyHours();
|
||
if (null === $weeklyHours || $weeklyHours <= 35) {
|
||
continue;
|
||
}
|
||
|
||
$fullMonthlyMinutes = ($weeklyHours - 35) * self::MINUTES_PER_WEEKLY_HOUR_PER_MONTH;
|
||
$periodStart = $period->getStartDate();
|
||
$periodEnd = $period->getEndDate();
|
||
|
||
for ($month = 1; $month <= 12; ++$month) {
|
||
$monthStart = new DateTimeImmutable(sprintf('%04d-%02d-01', $civilYear, $month));
|
||
$monthEnd = $monthStart->modify('last day of this month');
|
||
$daysInMonth = (int) $monthEnd->format('d');
|
||
|
||
$overlapStart = $periodStart > $monthStart ? $periodStart : $monthStart;
|
||
$overlapEnd = (null !== $periodEnd && $periodEnd < $monthEnd) ? $periodEnd : $monthEnd;
|
||
if ($overlapStart > $overlapEnd) {
|
||
continue;
|
||
}
|
||
|
||
$overlapDays = $overlapStart->diff($overlapEnd)->days + 1;
|
||
$accumulated[$month] += $fullMonthlyMinutes * $overlapDays / $daysInMonth;
|
||
}
|
||
}
|
||
|
||
$months = [];
|
||
for ($month = 1; $month <= 12; ++$month) {
|
||
$months[$month] = (int) round($accumulated[$month]);
|
||
}
|
||
|
||
return $months;
|
||
}
|
||
|
||
public function totalStructuralMinutes(Employee $employee, int $civilYear): int
|
||
{
|
||
return array_sum($this->monthlyStructuralMinutes($employee, $civilYear));
|
||
}
|
||
}
|