104 lines
3.5 KiB
PHP
104 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service\WorkHours;
|
|
|
|
use App\ApiResource\WorkHourBulkValidationResult;
|
|
use App\Entity\User;
|
|
use App\Entity\WorkHour;
|
|
use App\Repository\EmployeeRepository;
|
|
use App\Repository\WorkHourRepository;
|
|
use DateTimeImmutable;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
|
|
final readonly class WorkHourBulkValidationExecutor
|
|
{
|
|
public function __construct(
|
|
private EntityManagerInterface $entityManager,
|
|
private EmployeeRepository $employeeRepository,
|
|
private WorkHourRepository $workHourRepository,
|
|
) {}
|
|
|
|
/**
|
|
* @param list<mixed> $employeeIds
|
|
* @param callable(?WorkHour, int): bool $shouldSkip
|
|
* @param callable(WorkHour, int): void $applyUpdate
|
|
*/
|
|
public function execute(
|
|
User $user,
|
|
string $workDateValue,
|
|
array $employeeIds,
|
|
callable $shouldSkip,
|
|
callable $applyUpdate
|
|
): WorkHourBulkValidationResult {
|
|
$workDate = DateTimeImmutable::createFromFormat('Y-m-d', $workDateValue);
|
|
if (!$workDate || $workDate->format('Y-m-d') !== $workDateValue) {
|
|
throw new UnprocessableEntityHttpException('workDate must use Y-m-d format.');
|
|
}
|
|
|
|
$normalizedEmployeeIds = $this->normalizeEmployeeIds($employeeIds);
|
|
if ([] === $normalizedEmployeeIds) {
|
|
throw new UnprocessableEntityHttpException('employeeIds must contain at least one employee.');
|
|
}
|
|
|
|
$employeesById = $this->employeeRepository->findAccessibleByIds($normalizedEmployeeIds, $user);
|
|
if (count($employeesById) !== count($normalizedEmployeeIds)) {
|
|
throw new AccessDeniedHttpException('At least one employee is unknown or outside your scope.');
|
|
}
|
|
|
|
$existingByEmployeeId = $this->workHourRepository
|
|
->findByDateAndEmployeesIndexedByEmployeeId($workDate, array_values($employeesById))
|
|
;
|
|
|
|
$result = new WorkHourBulkValidationResult();
|
|
$result->requested = count($normalizedEmployeeIds);
|
|
|
|
foreach ($normalizedEmployeeIds as $employeeId) {
|
|
$workHour = $existingByEmployeeId[$employeeId] ?? null;
|
|
if (null === $workHour || $shouldSkip($workHour, $employeeId)) {
|
|
++$result->skipped;
|
|
$result->skippedEmployeeIds[] = $employeeId;
|
|
|
|
continue;
|
|
}
|
|
|
|
$applyUpdate($workHour, $employeeId);
|
|
++$result->updated;
|
|
$result->updatedEmployeeIds[] = $employeeId;
|
|
}
|
|
|
|
if ($result->updated > 0) {
|
|
$this->entityManager->flush();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param list<mixed> $employeeIds
|
|
*
|
|
* @return list<int>
|
|
*/
|
|
private function normalizeEmployeeIds(array $employeeIds): array
|
|
{
|
|
$normalized = [];
|
|
foreach ($employeeIds as $index => $rawId) {
|
|
$employeeId = (int) $rawId;
|
|
if ($employeeId <= 0) {
|
|
throw new UnprocessableEntityHttpException(sprintf('employeeIds[%d] must be a positive integer.', $index));
|
|
}
|
|
|
|
if (isset($normalized[$employeeId])) {
|
|
throw new UnprocessableEntityHttpException(sprintf('Employee %d appears multiple times in payload.', $employeeId));
|
|
}
|
|
|
|
$normalized[$employeeId] = $employeeId;
|
|
}
|
|
|
|
return array_values($normalized);
|
|
}
|
|
}
|