de98924fd3
Module type Payfit (étapes 1+2 de la spec V1) : demande d'absence, validation admin, soldes à jour. - Enums : AbsenceType, AbsenceStatus, HalfDay, ContractType, FamilySituation - Entités : AbsencePolicy, AbsenceBalance, AbsenceRequest + champs RH sur User - Services : PublicHolidayProvider (fériés FR métropole en PHP pur, Computus), AbsenceDayCalculator (décompte jours ouvrés/ouvrables + demi-journées, TDD), AbsenceBalanceService (périodes + pending/taken/recrédit) - API Platform : providers/processors (création, approve/reject/cancel) + RBAC me/admin, contrôleurs preview (dry-run), upload/download justificatif, calendrier - Migrations : une par table + colonnes RH user (DEFAULT puis DROP DEFAULT) - Fixtures : 5 policies par défaut, salariés démo, soldes et demandes - Tests unitaires : PublicHolidayProvider, AbsenceDayCalculator (12 tests) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
2.4 KiB
PHP
65 lines
2.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controller\Absence;
|
|
|
|
use App\Entity\AbsenceRequest;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
|
|
/**
|
|
* Streams the justification file of an absence request. Owner or admin only.
|
|
*/
|
|
class AbsenceJustificationDownloadController extends AbstractController
|
|
{
|
|
public function __construct(
|
|
private readonly EntityManagerInterface $entityManager,
|
|
private readonly Security $security,
|
|
private readonly string $uploadDir,
|
|
) {}
|
|
|
|
#[Route('/api/absence_requests/{id}/justificatif', name: 'absence_justification_download', methods: ['GET'], priority: 1)]
|
|
#[IsGranted('ROLE_USER')]
|
|
public function __invoke(int $id): BinaryFileResponse
|
|
{
|
|
$absence = $this->entityManager->getRepository(AbsenceRequest::class)->find($id);
|
|
if (null === $absence) {
|
|
throw new NotFoundHttpException('Absence request not found.');
|
|
}
|
|
|
|
if (!$this->security->isGranted('ROLE_ADMIN') && $absence->getUser() !== $this->security->getUser()) {
|
|
throw new AccessDeniedHttpException('You do not have access to this file.');
|
|
}
|
|
|
|
$fileName = $absence->getJustificationFileName();
|
|
if (null === $fileName) {
|
|
throw new NotFoundHttpException('No justification file for this request.');
|
|
}
|
|
|
|
$filePath = $this->uploadDir.'/'.$fileName;
|
|
if (!file_exists($filePath)) {
|
|
throw new NotFoundHttpException('File not found on disk.');
|
|
}
|
|
|
|
$response = new BinaryFileResponse($filePath);
|
|
$mimeType = mime_content_type($filePath) ?: 'application/octet-stream';
|
|
|
|
$disposition = (str_starts_with($mimeType, 'image/') || 'application/pdf' === $mimeType)
|
|
? ResponseHeaderBag::DISPOSITION_INLINE
|
|
: ResponseHeaderBag::DISPOSITION_ATTACHMENT;
|
|
|
|
$response->setContentDisposition($disposition, $fileName);
|
|
$response->headers->set('Content-Type', $mimeType);
|
|
|
|
return $response;
|
|
}
|
|
}
|