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>
96 lines
3.7 KiB
PHP
96 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controller\Absence;
|
|
|
|
use App\Entity\User;
|
|
use App\Enum\AbsenceType;
|
|
use App\Enum\HalfDay;
|
|
use App\Repository\AbsenceBalanceRepository;
|
|
use App\Repository\AbsencePolicyRepository;
|
|
use App\Service\AbsenceBalanceService;
|
|
use App\Service\AbsenceDayCalculator;
|
|
use DateTimeImmutable;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
|
|
/**
|
|
* Dry-run endpoint for the "new request" form: returns the number of deducted
|
|
* days and the projected balance without creating anything. Required because
|
|
* public holidays are computed server-side.
|
|
*/
|
|
class AbsencePreviewController extends AbstractController
|
|
{
|
|
public function __construct(
|
|
private readonly Security $security,
|
|
private readonly AbsenceDayCalculator $calculator,
|
|
private readonly AbsencePolicyRepository $policyRepository,
|
|
private readonly AbsenceBalanceRepository $balanceRepository,
|
|
private readonly AbsenceBalanceService $balanceService,
|
|
) {}
|
|
|
|
#[Route('/api/absence_requests/preview', name: 'absence_request_preview', methods: ['POST'], priority: 1)]
|
|
#[IsGranted('ROLE_USER')]
|
|
public function __invoke(Request $request): JsonResponse
|
|
{
|
|
/** @var array<string, mixed> $payload */
|
|
$payload = json_decode($request->getContent(), true) ?? [];
|
|
|
|
$type = AbsenceType::tryFrom((string) ($payload['type'] ?? ''));
|
|
if (null === $type) {
|
|
throw new UnprocessableEntityHttpException('Unknown absence type.');
|
|
}
|
|
|
|
$startRaw = (string) ($payload['startDate'] ?? '');
|
|
$endRaw = (string) ($payload['endDate'] ?? '');
|
|
if ('' === $startRaw || '' === $endRaw) {
|
|
throw new UnprocessableEntityHttpException('Start date and end date are required.');
|
|
}
|
|
|
|
$start = new DateTimeImmutable($startRaw);
|
|
$end = new DateTimeImmutable($endRaw);
|
|
if ($end < $start) {
|
|
throw new UnprocessableEntityHttpException('End date must be on or after start date.');
|
|
}
|
|
|
|
$policy = $this->policyRepository->findOneByType($type);
|
|
$workingDaysOnly = $policy?->isCountWorkingDaysOnly() ?? true;
|
|
|
|
$countedDays = $this->calculator->countWorkingDays(
|
|
$start,
|
|
$end,
|
|
isset($payload['startHalfDay']) ? HalfDay::tryFrom((string) $payload['startHalfDay']) : null,
|
|
isset($payload['endHalfDay']) ? HalfDay::tryFrom((string) $payload['endHalfDay']) : null,
|
|
$workingDaysOnly,
|
|
);
|
|
|
|
$user = $this->security->getUser();
|
|
assert($user instanceof User);
|
|
|
|
$available = null;
|
|
$projectedAvailable = null;
|
|
$period = null;
|
|
|
|
if ($type->decrementsBalance()) {
|
|
$period = $this->balanceService->periodFor($user, $type, $start);
|
|
$balance = $this->balanceRepository->findOneForPeriod($user, $type, $period);
|
|
$available = $balance?->getAvailable() ?? 0.0;
|
|
$projectedAvailable = $available - $countedDays;
|
|
}
|
|
|
|
return $this->json([
|
|
'countedDays' => $countedDays,
|
|
'period' => $period,
|
|
'available' => $available,
|
|
'projectedAvailable' => $projectedAvailable,
|
|
'justificationRequired' => $policy?->isJustificationRequired() ?? false,
|
|
]);
|
|
}
|
|
}
|