111 lines
4.6 KiB
PHP
111 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Mcp\Tool\Absence;
|
|
|
|
use App\Entity\AbsenceRequest;
|
|
use App\Enum\AbsenceStatus;
|
|
use App\Enum\AbsenceType;
|
|
use App\Enum\HalfDay;
|
|
use App\Mcp\Tool\Serializer;
|
|
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
|
|
use App\Repository\AbsencePolicyRepository;
|
|
use App\Repository\AbsenceRequestRepository;
|
|
use App\Service\AbsenceBalanceService;
|
|
use App\Service\AbsenceDayCalculator;
|
|
use DateTimeImmutable;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use InvalidArgumentException;
|
|
use Mcp\Capability\Attribute\McpTool;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
|
|
|
use function sprintf;
|
|
|
|
#[McpTool(name: 'create-absence-request', description: 'Create an absence request on behalf of an employee (userId). Validates active policy + no overlap, computes deducted working days, sets status=pending and reserves the days in the pending balance. type: cp|mariage_pacs|conge_parental|deces|maladie. Dates YYYY-MM-DD. halfDay (matin|apres_midi) on a boundary subtracts 0.5.')]
|
|
class CreateAbsenceRequestTool
|
|
{
|
|
public function __construct(
|
|
private readonly EntityManagerInterface $entityManager,
|
|
private readonly DoctrineUserRepository $userRepository,
|
|
private readonly AbsencePolicyRepository $policyRepository,
|
|
private readonly AbsenceRequestRepository $requestRepository,
|
|
private readonly AbsenceDayCalculator $calculator,
|
|
private readonly AbsenceBalanceService $balanceService,
|
|
private readonly Security $security,
|
|
) {}
|
|
|
|
public function __invoke(
|
|
int $userId,
|
|
string $type,
|
|
string $startDate,
|
|
string $endDate,
|
|
?string $startHalfDay = null,
|
|
?string $endHalfDay = null,
|
|
?string $reason = null,
|
|
): string {
|
|
if (!$this->security->isGranted('ROLE_USER')) {
|
|
throw new AccessDeniedException('Access denied: ROLE_USER required.');
|
|
}
|
|
|
|
$user = $this->userRepository->find($userId)
|
|
?? throw new InvalidArgumentException(sprintf('User with ID %d not found.', $userId));
|
|
|
|
$typeEnum = AbsenceType::tryFrom($type)
|
|
?? throw new InvalidArgumentException(sprintf('Unknown absence type "%s".', $type));
|
|
|
|
$start = new DateTimeImmutable($startDate);
|
|
$end = new DateTimeImmutable($endDate);
|
|
if ($end < $start) {
|
|
throw new InvalidArgumentException('End date must be on or after start date.');
|
|
}
|
|
|
|
$startHd = null !== $startHalfDay
|
|
? (HalfDay::tryFrom($startHalfDay) ?? throw new InvalidArgumentException(sprintf('Unknown half day "%s".', $startHalfDay)))
|
|
: null;
|
|
$endHd = null !== $endHalfDay
|
|
? (HalfDay::tryFrom($endHalfDay) ?? throw new InvalidArgumentException(sprintf('Unknown half day "%s".', $endHalfDay)))
|
|
: null;
|
|
|
|
$policy = $this->policyRepository->findOneByType($typeEnum);
|
|
if (null === $policy || !$policy->isActive()) {
|
|
throw new InvalidArgumentException('This absence type is not available.');
|
|
}
|
|
|
|
// Bereavement has no fixed entitlement: the relationship to the deceased
|
|
// drives the legal number of days, so the reason is mandatory.
|
|
if (AbsenceType::Bereavement === $typeEnum && '' === trim((string) $reason)) {
|
|
throw new InvalidArgumentException('A reason (relationship to the deceased) is required for bereavement leave.');
|
|
}
|
|
|
|
if ($this->requestRepository->hasOverlap($user, $start, $end)) {
|
|
throw new InvalidArgumentException('This request overlaps an existing absence.');
|
|
}
|
|
|
|
$countedDays = $this->calculator->countWorkingDays($start, $end, $startHd, $endHd, $policy->isCountWorkingDaysOnly());
|
|
if ($countedDays <= 0.0) {
|
|
throw new InvalidArgumentException('The selected range contains no working day.');
|
|
}
|
|
|
|
$request = new AbsenceRequest();
|
|
$request->setUser($user);
|
|
$request->setType($typeEnum);
|
|
$request->setStartDate($start);
|
|
$request->setEndDate($end);
|
|
$request->setStartHalfDay($startHd);
|
|
$request->setEndHalfDay($endHd);
|
|
$request->setReason($reason);
|
|
$request->setCountedDays($countedDays);
|
|
$request->setStatus(AbsenceStatus::Pending);
|
|
$request->setRejectionReason(null);
|
|
$request->setCreatedAt(new DateTimeImmutable());
|
|
|
|
$this->entityManager->persist($request);
|
|
$this->balanceService->reservePending($request);
|
|
$this->entityManager->flush();
|
|
|
|
return json_encode(Serializer::absenceRequest($request));
|
|
}
|
|
}
|