131 lines
4 KiB
PHP
131 lines
4 KiB
PHP
<?php
|
|
|
|
namespace App\Schedule\UseCases\RescheduleSchedule;
|
|
|
|
use App\Exceptions\BadRequestException;
|
|
use App\Exceptions\NotFoundException;
|
|
use App\Schedule\EvenDistributionScheduler;
|
|
use App\Schedule\RescheduleScheduleAssignmentDto;
|
|
use App\Schedule\RescheduleScheduleDto;
|
|
use App\Schedule\Schedule;
|
|
use App\Schedule\ScheduleAssignment;
|
|
use App\Schedule\ScheduleRepository;
|
|
use App\Schedule\WorkloadPlacement;
|
|
use DateTimeImmutable;
|
|
use DateTimeZone;
|
|
|
|
class RescheduleSchedule
|
|
{
|
|
public function __construct(
|
|
private ScheduleRepository $scheduleRepository,
|
|
private EvenDistributionScheduler $evenDistributionScheduler,
|
|
) {}
|
|
|
|
/**
|
|
* @throws BadRequestException
|
|
* @throws NotFoundException
|
|
*/
|
|
public function execute(RescheduleScheduleRequest $request): Schedule
|
|
{
|
|
$schedule = $this->scheduleRepository->findForUser(
|
|
$request->scheduleId,
|
|
$request->user,
|
|
);
|
|
if ($schedule === null) {
|
|
throw new NotFoundException('schedule not found');
|
|
}
|
|
|
|
$startDate = $this->parseDate($request->startDate, 'startDate');
|
|
$targetDate = $this->parseDate($request->targetDate, 'targetDate');
|
|
if ($targetDate < $startDate) {
|
|
throw new BadRequestException(
|
|
'targetDate must not be before startDate',
|
|
);
|
|
}
|
|
$workloadPlacement = $this->workloadPlacement(
|
|
$request->workloadPlacement,
|
|
);
|
|
$unfinishedAssignments = array_values(array_filter(
|
|
$schedule->getAssignments(),
|
|
function (ScheduleAssignment $assignment): bool {
|
|
return $assignment->getCompletedAt() === null;
|
|
},
|
|
));
|
|
if ($unfinishedAssignments === []) {
|
|
throw new BadRequestException(
|
|
'schedule has no unfinished assignments',
|
|
);
|
|
}
|
|
|
|
$scheduledDates = $this->evenDistributionScheduler->scheduledDates(
|
|
assignmentCount: count($unfinishedAssignments),
|
|
startDate: $startDate,
|
|
targetDate: $targetDate,
|
|
workloadPlacement: $workloadPlacement,
|
|
);
|
|
$assignmentDtos = [];
|
|
foreach ($unfinishedAssignments as $index => $assignment) {
|
|
$assignmentDtos[] = new RescheduleScheduleAssignmentDto(
|
|
id: $assignment->getId(),
|
|
scheduledDate: $scheduledDates[$index],
|
|
);
|
|
}
|
|
|
|
return $this->scheduleRepository->reschedule(
|
|
new RescheduleScheduleDto(
|
|
scheduleId: $schedule->getId(),
|
|
user: $request->user,
|
|
targetDate: $targetDate,
|
|
assignments: $assignmentDtos,
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @throws BadRequestException
|
|
*/
|
|
private function parseDate(?string $value, string $field): DateTimeImmutable
|
|
{
|
|
if ($value === null || $value === '') {
|
|
throw new BadRequestException("{$field} is required");
|
|
}
|
|
|
|
$date = DateTimeImmutable::createFromFormat(
|
|
'!Y-m-d',
|
|
$value,
|
|
new DateTimeZone('UTC'),
|
|
);
|
|
$errors = DateTimeImmutable::getLastErrors();
|
|
if (
|
|
$date === false
|
|
|| $date->format('Y-m-d') !== $value
|
|
|| ($errors !== false
|
|
&& ($errors['warning_count'] > 0 || $errors['error_count'] > 0))
|
|
) {
|
|
throw new BadRequestException(
|
|
"{$field} must be a valid date in YYYY-MM-DD format",
|
|
);
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
/**
|
|
* @throws BadRequestException
|
|
*/
|
|
private function workloadPlacement(?string $value): WorkloadPlacement
|
|
{
|
|
if ($value === null) {
|
|
return WorkloadPlacement::Middle;
|
|
}
|
|
|
|
$workloadPlacement = WorkloadPlacement::tryFrom($value);
|
|
if ($workloadPlacement === null) {
|
|
throw new BadRequestException(
|
|
'workloadPlacement must be start, middle, or end',
|
|
);
|
|
}
|
|
|
|
return $workloadPlacement;
|
|
}
|
|
}
|