feat : ajout d'un onglet formation
Auto Tag Develop / tag (push) Has been cancelled

This commit is contained in:
2026-04-13 09:41:36 +02:00
parent b185accdbb
commit 4cd30de3e3
29 changed files with 1244 additions and 36 deletions
+48 -11
View File
@@ -6,9 +6,11 @@ namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\Formation;
use App\Enum\ContractNature;
use App\Enum\HalfDay;
use App\Repository\AbsenceRepository;
use App\Repository\Contract\FormationReadRepositoryInterface;
use App\Repository\EmployeeRepository;
use App\Service\PublicHolidayServiceInterface;
use DateInterval;
@@ -30,6 +32,7 @@ class AbsencePrintProvider implements ProviderInterface
private readonly RequestStack $requestStack,
private EmployeeRepository $employeeRepository,
private AbsenceRepository $absenceRepository,
private FormationReadRepositoryInterface $formationRepository,
private PublicHolidayServiceInterface $publicHolidayService,
) {}
@@ -58,24 +61,27 @@ class AbsencePrintProvider implements ProviderInterface
$workContractIds = $this->parseIds($request->query->get('workContracts'));
$contractNatures = $this->parseContractNatures($request->query->get('contractNatures'));
$employees = $this->loadEmployees($siteIds, $contractNatures, $workContractIds);
$absences = $this->loadAbsences($fromDate, $toDate, $employees);
$employees = $this->loadEmployees($siteIds, $contractNatures, $workContractIds);
$absences = $this->loadAbsences($fromDate, $toDate, $employees);
$formations = $this->formationRepository->findByDateRangeAndEmployees($fromDate, $toDate, $employees);
$days = $this->buildDays($fromDate, $toDate);
$absenceMap = $this->buildAbsenceMap($absences, $fromDate, $toDate);
$holidayMap = $this->buildHolidayMap($fromDate, $toDate);
$days = $this->buildDays($fromDate, $toDate);
$absenceMap = $this->buildAbsenceMap($absences, $fromDate, $toDate);
$formationMap = $this->buildFormationMap($formations, $fromDate, $toDate);
$holidayMap = $this->buildHolidayMap($fromDate, $toDate);
$options = new Options();
$options->set('isRemoteEnabled', true);
$dompdf = new Dompdf($options);
$html = $this->twig->render('absence/print.html.twig', [
'from' => $fromDate,
'to' => $toDate,
'days' => $days,
'employees' => $employees,
'absenceMap' => $absenceMap,
'holidayMap' => $holidayMap,
'from' => $fromDate,
'to' => $toDate,
'days' => $days,
'employees' => $employees,
'absenceMap' => $absenceMap,
'formationMap' => $formationMap,
'holidayMap' => $holidayMap,
]);
$dompdf->loadHtml($html);
@@ -203,6 +209,37 @@ class AbsencePrintProvider implements ProviderInterface
return $map;
}
/**
* @param list<Formation> $formations
*
* @return array<int, array<string, bool>>
*/
private function buildFormationMap(array $formations, DateTimeImmutable $from, DateTimeImmutable $to): array
{
$map = [];
foreach ($formations as $formation) {
$employeeId = $formation->getEmployee()?->getId();
if (!$employeeId) {
continue;
}
$formationStart = DateTimeImmutable::createFromInterface($formation->getStartDate());
$formationEnd = DateTimeImmutable::createFromInterface($formation->getEndDate());
$start = max($formationStart, $from);
$end = min($formationEnd, $to);
$current = $start;
while ($current <= $end) {
$map[$employeeId][$current->format('Y-m-d')] = true;
$current = $current->add(new DateInterval('P1D'));
}
}
return $map;
}
private function buildHolidayMap(DateTimeImmutable $from, DateTimeImmutable $to): array
{
$map = [];
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Formation;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final readonly class FormationDeleteProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
#[Autowire('%kernel.project_dir%/var/uploads')]
private string $uploadDir,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): null
{
if (!$data instanceof Formation) {
return null;
}
$justificatifPath = $data->getJustificatifPath();
if (null !== $justificatifPath) {
$absolutePath = sprintf('%s/%s', $this->uploadDir, $justificatifPath);
if (file_exists($absolutePath)) {
unlink($absolutePath);
}
}
$this->entityManager->remove($data);
$this->entityManager->flush();
return null;
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\Formation;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
final readonly class FormationJustificatifDownloadProvider implements ProviderInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
#[Autowire('%kernel.project_dir%/var/uploads')]
private string $uploadDir,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): BinaryFileResponse
{
$formation = $this->entityManager->find(Formation::class, $uriVariables['id']);
if (null === $formation) {
throw new NotFoundHttpException('Formation not found.');
}
$justificatifPath = $formation->getJustificatifPath();
if (null === $justificatifPath) {
throw new NotFoundHttpException('No justificatif found for this formation.');
}
$absolutePath = sprintf('%s/%s', $this->uploadDir, $justificatifPath);
if (!file_exists($absolutePath)) {
throw new NotFoundHttpException('Justificatif file not found.');
}
$response = new BinaryFileResponse($absolutePath);
$disposition = HeaderUtils::makeDisposition(
HeaderUtils::DISPOSITION_ATTACHMENT,
$formation->getJustificatifName() ?? 'justificatif.pdf'
);
$response->headers->set('Content-Disposition', $disposition);
return $response;
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Formation;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Uid\Uuid;
final readonly class FormationJustificatifUploadProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
private RequestStack $requestStack,
#[Autowire('%kernel.project_dir%/var/uploads')]
private string $uploadDir,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): JsonResponse
{
if (!$data instanceof Formation) {
throw new BadRequestHttpException('Invalid entity.');
}
$request = $this->requestStack->getCurrentRequest();
$file = $request?->files->get('file');
if (null === $file) {
throw new BadRequestHttpException('No file uploaded.');
}
if ('application/pdf' !== $file->getMimeType()) {
throw new BadRequestHttpException('Only PDF files are accepted.');
}
$startDate = $data->getStartDate();
$year = $startDate?->format('Y') ?? date('Y');
$monthNumber = $startDate?->format('m') ?? date('m');
$relativePath = sprintf('formations/%s/%s', $year, $monthNumber);
$absoluteDir = sprintf('%s/%s', $this->uploadDir, $relativePath);
if (!is_dir($absoluteDir)) {
mkdir($absoluteDir, 0o755, true);
}
$filename = Uuid::v4()->toRfc4122().'.pdf';
$fullRelative = sprintf('%s/%s', $relativePath, $filename);
$originalName = $file->getClientOriginalName();
$previousPath = $data->getJustificatifPath();
$file->move($absoluteDir, $filename);
$data->setJustificatifPath($fullRelative);
$data->setJustificatifName($originalName);
$this->entityManager->flush();
if (null !== $previousPath) {
$previousAbsolute = sprintf('%s/%s', $this->uploadDir, $previousPath);
if (file_exists($previousAbsolute)) {
unlink($previousAbsolute);
}
}
return new JsonResponse(['path' => $fullRelative, 'name' => $originalName], Response::HTTP_OK);
}
}
+14 -3
View File
@@ -11,6 +11,7 @@ use App\Dto\WorkHours\DayContextRow;
use App\Entity\User;
use App\Repository\Contract\AbsenceReadRepositoryInterface;
use App\Repository\Contract\EmployeeScopedRepositoryInterface;
use App\Repository\Contract\FormationReadRepositoryInterface;
use App\Service\Contracts\EmployeeContractResolver;
use App\Service\WorkHours\AbsenceSegmentsResolver;
use App\Service\WorkHours\WorkedHoursCreditPolicy;
@@ -27,6 +28,7 @@ final readonly class WorkHourDayContextProvider implements ProviderInterface
private RequestStack $requestStack,
private EmployeeScopedRepositoryInterface $employeeRepository,
private AbsenceReadRepositoryInterface $absenceRepository,
private FormationReadRepositoryInterface $formationRepository,
private EmployeeContractResolver $contractResolver,
private AbsenceSegmentsResolver $absenceSegmentsResolver,
private WorkedHoursCreditPolicy $workedHoursCreditPolicy,
@@ -40,9 +42,10 @@ final readonly class WorkHourDayContextProvider implements ProviderInterface
throw new AccessDeniedHttpException('Authentication required.');
}
$workDate = $this->resolveWorkDate();
$employees = $this->employeeRepository->findScoped($user);
$absences = $this->absenceRepository->findByDateAndEmployees($workDate, $employees);
$workDate = $this->resolveWorkDate();
$employees = $this->employeeRepository->findScoped($user);
$absences = $this->absenceRepository->findByDateAndEmployees($workDate, $employees);
$formations = $this->formationRepository->findByDateAndEmployees($workDate, $employees);
$rowsByEmployeeId = [];
foreach ($employees as $employee) {
@@ -87,6 +90,14 @@ final readonly class WorkHourDayContextProvider implements ProviderInterface
);
}
foreach ($formations as $formation) {
$employeeId = $formation->getEmployee()?->getId();
if (!$employeeId || !isset($rowsByEmployeeId[$employeeId])) {
continue;
}
$rowsByEmployeeId[$employeeId]->setFormation('Formation');
}
$response = new WorkHourDayContext();
$response->workDate = $dateKey;
$response->rows = array_map(