Files
Lesstime/src/Module/Absence/Infrastructure/ApiPlatform/State/AbsenceRequestProvider.php
T
Matthieu 306cfd34cd feat(absence) : migrate Absence domain into module (back)
LST-66 (2.3) backend. Behaviour-preserving move of the absences domain into
src/Module/Absence/. API operations, securities, routes and the 10 MCP tool
names are unchanged.

- 3 entities + 3 enums moved to Domain/{Entity,Enum}; user relations stay on
  UserInterface. 3 repositories split into Domain/Repository interfaces +
  Doctrine impls (bound in services.yaml); find() kept off interfaces
  (findById instead).
- Pure services (AbsenceDayCalculator, PublicHolidayProvider) -> Domain/Service;
  AbsenceBalanceService -> Application/Service; State (5), controllers (5),
  10 MCP tools and AccrueLeaveCommand -> Infrastructure/.
- New LeaveProfileInterface contract (Shared) exposes the HR getters used by
  AbsenceBalanceService/AccrueLeaveCommand; User implements it -> Absence no
  longer imports the concrete Core User. MCP tools/command inject
  UserRepositoryInterface (findById) instead of the concrete repository.
- Timestampable/Blamable added to AbsenceBalance and AbsencePolicy (additive
  migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
  NULL + COMMENT). AbsenceRequest untouched (already has createdAt/reviewedAt).
- AbsenceModule registered (id absence, 4 RBAC perms, not re-wired); doctrine
  mapping added; team-absences sidebar item gated by the module.

161 tests green, mapping valid, no API route regression, cs-fixer clean.
2026-06-20 18:32:02 +02:00

85 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Module\Absence\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Module\Absence\Domain\Entity\AbsenceRequest;
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
use App\Shared\Domain\Contract\UserInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
/**
* @implements ProviderInterface<AbsenceRequest>
*/
final readonly class AbsenceRequestProvider implements ProviderInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
private AbsenceRequestRepositoryInterface $requestRepository,
private Security $security,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): AbsenceRequest|array|null
{
$user = $this->security->getUser();
assert($user instanceof UserInterface);
$isAdmin = $this->security->isGranted('ROLE_ADMIN');
// Single item: owner or admin only
if (isset($uriVariables['id'])) {
$request = $this->requestRepository->findById((int) $uriVariables['id']);
if (null === $request) {
return null;
}
if (!$isAdmin && $request->getUser() !== $user) {
return null;
}
return $request;
}
$qb = $this->entityManager->getRepository(AbsenceRequest::class)
->createQueryBuilder('a')
->orderBy('a.createdAt', 'DESC')
;
if (!$isAdmin) {
$qb->andWhere('a.user = :user')->setParameter('user', $user);
}
$filters = $context['filters'] ?? [];
if (isset($filters['status'])) {
$qb->andWhere('a.status = :status')->setParameter('status', $filters['status']);
}
if (isset($filters['type'])) {
$qb->andWhere('a.type = :type')->setParameter('type', $filters['type']);
}
if (isset($filters['year']) && is_numeric($filters['year'])) {
$year = (int) $filters['year'];
$qb->andWhere('a.startDate <= :yearEnd')
->andWhere('a.endDate >= :yearStart')
->setParameter('yearStart', sprintf('%d-01-01', $year))
->setParameter('yearEnd', sprintf('%d-12-31', $year))
;
}
if ($isAdmin && isset($filters['user'])) {
$qb->andWhere('a.user = :filterUser')
->setParameter('filterUser', self::extractId($filters['user']))
;
}
return $qb->getQuery()->getResult();
}
private static function extractId(string $value): int
{
return is_numeric($value) ? (int) $value : (int) basename($value);
}
}