Attainly/backend/app/Schedule/UseCases/RescheduleSchedule/RescheduleSchedule.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;
}
}