$dto->user->getId(), 'set_name' => $dto->setName, 'element_kind' => $dto->elementKind, 'start_date' => $dto->startDate->format('Y-m-d'), 'target_date' => $dto->targetDate->format('Y-m-d'), ]); foreach ($dto->assignments as $assignmentDto) { ScheduleAssignmentModel::create([ 'schedule_id' => $model->id, 'element_name' => $assignmentDto->name, 'element_kind' => $assignmentDto->kind, 'element_path' => $assignmentDto->path, 'scheduled_date' => $assignmentDto->scheduledDate ->format('Y-m-d'), 'position' => $assignmentDto->position, 'completed_at' => null, ]); } return $this->toDomain($model, $dto->user); }); } public function findForUser(int $id, User $user): ?Schedule { $model = ScheduleModel::query() ->where('id', $id) ->where('user_id', $user->getId()) ->first(); return $model === null ? null : $this->toDomain($model, $user); } public function findAllForUser(User $user): array { $models = ScheduleModel::query() ->where('user_id', $user->getId()) ->orderByDesc('id') ->get(); $schedules = []; foreach ($models as $model) { $schedules[] = $this->toDomain($model, $user); } return $schedules; } public function findIncompleteAssignmentsForUserDueOnOrBefore( User $user, DateTimeImmutable $date, ): array { $assignmentModels = ScheduleAssignmentModel::query() ->select('schedule_assignments.*') ->join( 'schedules', 'schedules.id', '=', 'schedule_assignments.schedule_id', ) ->where('schedules.user_id', $user->getId()) ->where( 'schedule_assignments.scheduled_date', '<=', $date->format('Y-m-d'), ) ->whereNull('schedule_assignments.completed_at') ->with('schedule') ->orderBy('schedule_assignments.scheduled_date') ->orderByDesc('schedules.id') ->orderBy('schedule_assignments.position') ->orderBy('schedule_assignments.id') ->get(); $assignments = []; foreach ($assignmentModels as $assignmentModel) { $assignments[] = new AssignmentForDate( scheduleId: $assignmentModel->schedule->id, setName: $assignmentModel->schedule->set_name, assignment: $this->assignmentToDomain($assignmentModel), ); } return $assignments; } public function findAssignmentForUser( int $id, User $user, ): ?ScheduleAssignment { $model = ScheduleAssignmentModel::query() ->select('schedule_assignments.*') ->join( 'schedules', 'schedules.id', '=', 'schedule_assignments.schedule_id', ) ->where('schedule_assignments.id', $id) ->where('schedules.user_id', $user->getId()) ->first(); return $model === null ? null : $this->assignmentToDomain($model); } public function updateAssignment( ScheduleAssignment $assignment, ): ScheduleAssignment { $model = ScheduleAssignmentModel::find($assignment->getId()); if ($model === null) { throw new DomainException( "Assignment with id {$assignment->getId()} not found", ); } $model->completed_at = $assignment->getCompletedAt(); $model->save(); return $this->assignmentToDomain($model); } private function toDomain(ScheduleModel $model, User $user): Schedule { $assignmentModels = ScheduleAssignmentModel::query() ->where('schedule_id', $model->id) ->orderBy('position') ->orderBy('id') ->get(); $assignments = []; foreach ($assignmentModels as $assignmentModel) { $assignments[] = $this->assignmentToDomain($assignmentModel); } return new Schedule( id: $model->id, user: $user, setName: $model->set_name, elementKind: $model->element_kind, startDate: $this->date($model->start_date), targetDate: $this->date($model->target_date), assignments: $assignments, ); } private function assignmentToDomain( ScheduleAssignmentModel $model, ): ScheduleAssignment { return new ScheduleAssignment( id: $model->id, name: $model->element_name, kind: $model->element_kind, path: $model->element_path, scheduledDate: $this->date($model->scheduled_date), position: $model->position, completedAt: $model->completed_at === null ? null : $this->dateTime($model->completed_at), ); } private function date(string $value): DateTimeImmutable { return new DateTimeImmutable($value, new DateTimeZone('UTC')); } private function dateTime(DateTimeImmutable $value): DateTimeImmutable { return DateTimeImmutable::createFromInterface($value) ->setTimezone(new DateTimeZone('UTC')); } }