212 lines
7.8 KiB
PHP
212 lines
7.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\State;
|
|
|
|
use ApiPlatform\Metadata\Operation;
|
|
use ApiPlatform\State\ProviderInterface;
|
|
use App\Entity\Employee;
|
|
use App\Enum\ContractNature;
|
|
use App\Enum\ContractType;
|
|
use App\Enum\TrackingMode;
|
|
use App\Repository\EmployeeRepository;
|
|
use App\Repository\EmployeeRttBalanceRepository;
|
|
use App\Repository\EmployeeRttPaymentRepository;
|
|
use App\Repository\WorkHourRepository;
|
|
use App\Service\Rtt\RttRecoveryComputationService;
|
|
use DateTimeImmutable;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Dompdf\Dompdf;
|
|
use Dompdf\Options;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Throwable;
|
|
use Twig\Environment;
|
|
|
|
class LeaveRecapPrintProvider implements ProviderInterface
|
|
{
|
|
public function __construct(
|
|
private Environment $twig,
|
|
private EmployeeRepository $employeeRepository,
|
|
private EmployeeLeaveSummaryProvider $leaveSummaryProvider,
|
|
private RttRecoveryComputationService $rttRecoveryService,
|
|
private EmployeeRttBalanceRepository $rttBalanceRepository,
|
|
private EmployeeRttPaymentRepository $rttPaymentRepository,
|
|
private EntityManagerInterface $entityManager,
|
|
private WorkHourRepository $workHourRepository,
|
|
) {}
|
|
|
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Response
|
|
{
|
|
$today = new DateTimeImmutable('today');
|
|
$employees = $this->employeeRepository->findForPrintBySiteIds([]);
|
|
|
|
$siteGroups = [];
|
|
|
|
foreach ($employees as $employee) {
|
|
if (!$employee->getHasActiveContract()) {
|
|
continue;
|
|
}
|
|
|
|
$site = $employee->getSite();
|
|
$siteId = $site ? $site->getId() : 0;
|
|
|
|
if (!isset($siteGroups[$siteId])) {
|
|
$siteGroups[$siteId] = [
|
|
'name' => $site ? $site->getName() : 'Sans site',
|
|
'color' => $site?->getColor() ?? '#ffd7d7',
|
|
'employees' => [],
|
|
];
|
|
}
|
|
|
|
$siteGroups[$siteId]['employees'][] = $this->buildEmployeeRow($employee, $today);
|
|
$this->entityManager->clear();
|
|
}
|
|
|
|
// Re-load Twig environment after clear
|
|
$options = new Options();
|
|
$options->set('isRemoteEnabled', true);
|
|
|
|
$dompdf = new Dompdf($options);
|
|
$html = $this->twig->render('leave-recap/print.html.twig', [
|
|
'today' => $today,
|
|
'siteGroups' => $siteGroups,
|
|
]);
|
|
|
|
$dompdf->loadHtml($html);
|
|
$dompdf->setPaper('A4', 'portrait');
|
|
$dompdf->render();
|
|
|
|
$filename = sprintf('recap_conges_%s.pdf', $today->format('Y-m-d'));
|
|
|
|
return new Response($dompdf->output(), Response::HTTP_OK, [
|
|
'Content-Type' => 'application/pdf',
|
|
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
|
]);
|
|
}
|
|
|
|
private function buildEmployeeRow(Employee $employee, DateTimeImmutable $today): array
|
|
{
|
|
$contract = $employee->getContract();
|
|
$contractName = $contract?->getName();
|
|
$isForfait = ContractType::FORFAIT === $contract?->getType();
|
|
$nature = ContractNature::tryFrom($employee->getCurrentContractNature());
|
|
$isInterim = ContractNature::INTERIM === $nature;
|
|
|
|
$cpN1Remaining = 0.0;
|
|
$cpN = '-';
|
|
$acquiredSaturdays = '-';
|
|
$rtt = '-';
|
|
|
|
if (!$isInterim) {
|
|
$leaveYear = $this->leaveSummaryProvider->resolveLeaveYearForToday($employee);
|
|
$yearSummary = $this->leaveSummaryProvider->computeYearSummary($employee, $leaveYear);
|
|
|
|
if (null !== $yearSummary) {
|
|
if ($isForfait) {
|
|
$cpN1Remaining = round($yearSummary['previousYearRemainingDays'], 2);
|
|
$cpN = (string) round($yearSummary['acquiredDays'], 2);
|
|
$acquiredSaturdays = '-';
|
|
} else {
|
|
$cpN1Remaining = round($yearSummary['remainingDays'], 2);
|
|
$cpN = (string) round($yearSummary['accruingDays'], 2);
|
|
$acquiredSaturdays = (string) round($yearSummary['remainingSaturdays'], 2);
|
|
}
|
|
}
|
|
|
|
if (!$isForfait && TrackingMode::PRESENCE->value !== $contract?->getTrackingMode()) {
|
|
try {
|
|
$rtt = $this->formatMinutes($this->computeAvailableRttMinutes($employee, $today));
|
|
} catch (Throwable) {
|
|
$rtt = '-';
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'lastName' => $employee->getLastName(),
|
|
'firstName' => $employee->getFirstName(),
|
|
'contractName' => $contractName,
|
|
'cpN1Remaining' => $cpN1Remaining,
|
|
'cpN' => $cpN,
|
|
'acquiredSaturdays' => $acquiredSaturdays,
|
|
'rtt' => $rtt,
|
|
];
|
|
}
|
|
|
|
private function computeAvailableRttMinutes(Employee $employee, DateTimeImmutable $today): int
|
|
{
|
|
$month = (int) $today->format('n');
|
|
$year = (int) $today->format('Y');
|
|
$exerciseYear = $month >= 6 ? $year + 1 : $year;
|
|
|
|
// Exclude incomplete current week: limit to last Sunday
|
|
$isoDay = (int) $today->format('N');
|
|
$limitDate = 7 === $isoDay ? $today : $today->modify('last sunday');
|
|
|
|
// Include the current week if all existing days are admin-validated
|
|
if (7 !== $isoDay) {
|
|
$currentWeekStart = $today->modify('monday this week');
|
|
$currentWeekEnd = $currentWeekStart->modify('+6 days');
|
|
$checkEnd = $this->resolveWeekEndForEmployee($employee, $currentWeekStart, $currentWeekEnd, $today);
|
|
if ($this->workHourRepository->isWeekFullyValidated($employee, $currentWeekStart, $checkEnd)) {
|
|
$limitDate = $currentWeekEnd;
|
|
}
|
|
}
|
|
|
|
// Carry from previous exercise
|
|
$carry = 0;
|
|
$balance = $this->rttBalanceRepository->findOneByEmployeeAndYear($employee, $exerciseYear);
|
|
if (null !== $balance) {
|
|
$carry = $balance->getTotalOpeningMinutes();
|
|
} else {
|
|
$previousTotal = $this->rttRecoveryService->computeTotalRecoveryForExercise($employee, $exerciseYear - 1);
|
|
$carry = $previousTotal->totalMinutes;
|
|
}
|
|
|
|
// Current exercise (limited to completed weeks)
|
|
$current = $this->rttRecoveryService->computeTotalRecoveryForExercise($employee, $exerciseYear, $limitDate);
|
|
|
|
// Paid RTT
|
|
$paid = 0;
|
|
$payments = $this->rttPaymentRepository->findByEmployeeAndYear($employee, $exerciseYear);
|
|
foreach ($payments as $payment) {
|
|
$paid += $payment->getBase25Minutes() + $payment->getBase50Minutes();
|
|
}
|
|
|
|
return $carry + $current->totalMinutes - $paid;
|
|
}
|
|
|
|
private function resolveWeekEndForEmployee(Employee $employee, DateTimeImmutable $weekStart, DateTimeImmutable $weekEnd, DateTimeImmutable $today): DateTimeImmutable
|
|
{
|
|
foreach ($employee->getContractPeriods() as $period) {
|
|
if ($period->getStartDate() > $today) {
|
|
continue;
|
|
}
|
|
$endDate = $period->getEndDate();
|
|
if (null === $endDate) {
|
|
continue;
|
|
}
|
|
if ($endDate >= $weekStart && $endDate <= $weekEnd) {
|
|
return $endDate;
|
|
}
|
|
}
|
|
|
|
return $weekEnd;
|
|
}
|
|
|
|
private function formatMinutes(int $minutes): string
|
|
{
|
|
if (0 === $minutes) {
|
|
return '0 h';
|
|
}
|
|
|
|
$sign = $minutes < 0 ? '- ' : '';
|
|
$abs = abs($minutes);
|
|
$h = intdiv($abs, 60);
|
|
$m = $abs % 60;
|
|
|
|
return 0 === $m ? "{$sign}{$h} h" : "{$sign}{$h} h {$m} m";
|
|
}
|
|
}
|