setId === null || $request->setId < 1) { throw new BadRequestException('setId is required'); } $set = $this->setRepository->find($request->setId); if ($set === null) { throw new NotFoundException('set not found'); } if ($request->elementKind === null || $request->elementKind === '') { throw new BadRequestException('elementKind is required'); } $startDate = $this->parseDate($request->startDate, 'startDate'); $targetDate = $this->parseDate($request->targetDate, 'targetDate'); if ($targetDate < $startDate) { throw new BadRequestException( 'targetDate must not be before startDate', ); } $elements = array_values(array_filter( $this->orderedElements($set), function (Element $element) use ($request): bool { return $element->getKind() === $request->elementKind; }, )); if ($elements === []) { throw new BadRequestException( 'elementKind is not available for set', ); } $assignments = $this->assignments( elements: $elements, startDate: $startDate, targetDate: $targetDate, ); return $this->scheduleRepository->create(new CreateScheduleDto( user: $request->user, setName: $set->getName(), elementKind: $request->elementKind, startDate: $startDate, targetDate: $targetDate, assignments: $assignments, )); } /** * @return list */ private function orderedElements(Set $set): array { $childrenByParentId = []; foreach ($this->elementRepository->findBySet($set) as $element) { $parentId = $element->getParentElement()?->getId() ?? 0; $childrenByParentId[$parentId][] = $element; } foreach ($childrenByParentId as &$children) { usort($children, function (Element $first, Element $second): int { $positionComparison = $first->getPosition() <=> $second->getPosition(); if ($positionComparison !== 0) { return $positionComparison; } return $first->getId() <=> $second->getId(); }); } unset($children); $orderedElements = []; $this->appendChildren( parentId: 0, childrenByParentId: $childrenByParentId, orderedElements: $orderedElements, ); return $orderedElements; } /** * @param array> $childrenByParentId * @param list $orderedElements */ private function appendChildren( int $parentId, array $childrenByParentId, array &$orderedElements, ): void { foreach ($childrenByParentId[$parentId] ?? [] as $element) { $orderedElements[] = $element; $this->appendChildren( parentId: $element->getId(), childrenByParentId: $childrenByParentId, orderedElements: $orderedElements, ); } } /** * @param list $elements * @return list */ private function assignments( array $elements, DateTimeImmutable $startDate, DateTimeImmutable $targetDate, ): array { $differenceInDays = $startDate->diff($targetDate)->days; $dayCount = $differenceInDays + 1; $elementCount = count($elements); $assignments = []; foreach ($elements as $index => $element) { $dayIndex = $this->dayIndex( elementIndex: $index, elementCount: $elementCount, dayCount: $dayCount, ); $assignments[] = new CreateScheduleAssignmentDto( name: $element->getName(), kind: $element->getKind(), path: $this->elementPath($element), scheduledDate: $startDate->modify("+{$dayIndex} days"), position: $index + 1, ); } return $assignments; } /** * @return list */ private function elementPath(Element $element): array { $path = []; $currentElement = $element; while ($currentElement !== null) { array_unshift($path, $currentElement->getName()); $currentElement = $currentElement->getParentElement(); } return $path; } private function dayIndex( int $elementIndex, int $elementCount, int $dayCount, ): int { if ($elementCount === 1 || $dayCount === 1) { return 0; } if ($elementCount < $dayCount) { $scaledIndex = $elementIndex * ($dayCount - 1) / ($elementCount - 1); return (int) floor($scaledIndex + 0.5); } return intdiv($elementIndex * $dayCount, $elementCount); } /** * @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; } }