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, startDate: $startDate, 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; } }