nextPickup(); return $this->getDefaultDeadline($nextPickup); } public function nextPickup(): CarbonInterface { $now = $this->getNow(); $startOfThisDay = $now->copy()->startOfDay(); if ($startOfThisDay->isDayOfWeek(self::PICKUP_DAY->value) && $now->hour < self::CUTOFF_HOUR) { return $startOfThisDay->addHours(self::CUTOFF_HOUR); } return $startOfThisDay ->next(self::PICKUP_DAY->value) ->addHours(self::CUTOFF_HOUR); } public function getPeriodFromPickUpDay(): CarbonPeriod { $nextPickup = $this->nextPickup(); return $this->getPeriodForPickupDate($nextPickup); } public function getPeriodForPickupDate(CarbonInterface $pickupDate): CarbonPeriod { $pickupDate = CarbonImmutable::createFromInterface($pickupDate); $defaultDeadLine = $this->getDefaultDeadline($pickupDate); return CarbonPeriod::create($defaultDeadLine, $pickupDate->endOfDay()); } /** @return \Domain\Models\ValueObjects\EditablePickupExceptionForShop[] */ public function getEditablePickupExceptionsFor(Shop $shop): array { $allExceptions = $this->pickupExceptionRepository->getAllExceptions(); $shopExceptions = $this->pickupExceptionRepository->getExceptionsAssociatedWithShop($shop->id()); return array_map(function (PickupExceptionOnDate $pickupException) use ($shopExceptions) { $associated = null; foreach ($shopExceptions as $shopEx) { if ($shopEx->exception->id->equals($pickupException->id)) { $associated = $shopEx; break; } } $isChecked = $associated !== null; $isPickupDay = $pickupException->isPickupDay(self::PICKUP_DAY); $defaultDeadline = $this->getDefaultDeadline($pickupException->date); $isBlocked = $associated !== null && ! $associated->hasReplacement(); $isPastDeadline = $associated !== null && $associated->isDeadlinePassed($this->getNow(), $defaultDeadline); return new EditablePickupExceptionForShop( $pickupException, $isPickupDay, $isChecked && $isPickupDay && $isBlocked, $isChecked && $isPickupDay && $isPastDeadline ); }, $allExceptions); } /** @return \Domain\Models\ValueObjects\PickupTimeSlot[] */ public function getTimeSlotsFor(Shop $shop, \DateTimeInterface $until): array { $todayDate = $this->getNow()->startOfDay(); $untilDayDate = CarbonImmutable::createFromInterface($until)->startOfDay(); $coveredPeriod = CarbonPeriod::create($todayDate, $untilDayDate); if ($untilDayDate->lessThanOrEqualTo($todayDate)) { throw new \InvalidArgumentException('Cannot get time slots for past days'); } $pickupExceptionList = $this->pickupExceptionRepository->getExceptionsForShop($shop->id(), $coveredPeriod); $filteredDates = array_filter( $coveredPeriod->toArray(), fn (CarbonInterface $date) => $date->isDayOfWeek(self::PICKUP_DAY->value) ); $mappedSlots = array_map(function (CarbonInterface $date) use ($pickupExceptionList) { $pickupDayDate = CarbonImmutable::createFromInterface($date); $shopPickupExceptionForTheDate = null; foreach ($pickupExceptionList as $ex) { if ($pickupDayDate->isSameDay($ex->exception->date)) { $shopPickupExceptionForTheDate = $ex; break; } } return $shopPickupExceptionForTheDate !== null ? $this->getTimeSlotForException($shopPickupExceptionForTheDate) : $this->getTimeSlotForPickupDay($pickupDayDate); }, $filteredDates); $validSlots = array_filter($mappedSlots); usort($validSlots, fn (PickupTimeSlot $a, PickupTimeSlot $b) => $a->getAt() <=> $b->getAt()); return $validSlots; } protected function getTimeSlotForPickupDay(CarbonInterface $pickupDayDate): ?PickupTimeSlot { $defaultDeadline = $this->getDefaultDeadline($pickupDayDate); // ? Have we reached the default standard deadline ? return $this->isBefore($defaultDeadline) // * Nominal pipeline: timeslot matches standard pickup day ? $this->createTimeSlot($pickupDayDate) : null; } protected function getTimeSlotForException(ShopPickupOverride $shopPickupException): ?PickupTimeSlot { if (! $shopPickupException->hasReplacement() || $shopPickupException->replacementDate === null) { return null; } $defaultDeadline = $this->getDefaultDeadline(CarbonImmutable::createFromInterface($shopPickupException->exception->date)); $deadline = $shopPickupException->effectiveDeadline($defaultDeadline); return $this->isBefore($deadline) ? $this->createTimeSlot($shopPickupException->replacementDate->toImmutable()) : null; } protected function createTimeSlot(CarbonInterface $forDate): PickupTimeSlot { return new PickupTimeSlot( $forDate->startOfDay(), \DateInterval::createFromDateString('23 hours 59 minutes 59 seconds') ); } protected function getDefaultDeadline(CarbonInterface $pickupDate): CarbonImmutable { $deadline = CarbonImmutable::createFromInterface($pickupDate) ->startOfWeek(CarbonInterface::MONDAY) ->addDays(self::CUTOFF_DAY->value - 1) ->addHours(self::CUTOFF_HOUR); return $this->getCutoffWeekOffset() !== 0 ? $deadline->addWeeks($this->getCutoffWeekOffset()) : $deadline; } private function getCutoffWeekOffset(): int { return self::CUTOFF_WEEK_OFFSET; } protected function isBefore(CarbonInterface $date): bool { return $this->getNow()->lessThanOrEqualTo($date); } protected function getNow(): CarbonImmutable { return CarbonImmutable::createFromInterface(new \DateTimeImmutable()); } }