- Add methods to convert OrderLine and DeliveryAddress to DTOs. - Introduce toPlacedEvent method in Order to generate domain event. - Enhance OrderUnit with Regate code mapping. - Implement tests for new functionality and ensure correctness.
203 lines
7.1 KiB
PHP
203 lines
7.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Domain\Services;
|
|
|
|
use Carbon\CarbonImmutable;
|
|
use Carbon\CarbonInterface;
|
|
use Carbon\CarbonPeriod;
|
|
use Carbon\WeekDay;
|
|
use Domain\Models\Entities\PickupExceptionOnDate;
|
|
use Domain\Models\Entities\Shop;
|
|
use Domain\Models\ValueObjects\EditablePickupExceptionForShop;
|
|
use Domain\Models\ValueObjects\PickupTimeSlot;
|
|
use Domain\Models\ValueObjects\ShopPickupOverride;
|
|
use Domain\Ports\Repositories\PickupExceptionRepository;
|
|
|
|
class PickupSchedulingService
|
|
{
|
|
public function __construct(
|
|
private readonly PickupExceptionRepository $pickupExceptionRepository
|
|
) {
|
|
}
|
|
|
|
private const PICKUP_DAY = WeekDay::Friday;
|
|
|
|
private const CUTOFF_WEEK_OFFSET = 0;
|
|
|
|
private const CUTOFF_DAY = WeekDay::Monday;
|
|
|
|
private const CUTOFF_HOUR = 14;
|
|
|
|
public function getPickupDay(): WeekDay
|
|
{
|
|
return self::PICKUP_DAY;
|
|
}
|
|
|
|
public function currentCutoff(): CarbonInterface
|
|
{
|
|
$nextPickup = $this->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());
|
|
}
|
|
}
|