src/Ecommerce/AvailabilitySystem/Event/AvailabilitySystem.php line 60

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by Elements.at New Media Solutions GmbH
  4.  *
  5.  */
  6. namespace App\Ecommerce\AvailabilitySystem\Event;
  7. use App\Model\Shop\Event\EventProduct;
  8. use Carbon\Carbon;
  9. use Doctrine\DBAL\Query\QueryBuilder;
  10. use Elements\Bundle\TicketShopFrameworkBundle\Model\Shop\Cart\TicketShopCartInterface;
  11. use Elements\Bundle\TicketShopFrameworkBundle\Service\CartService;
  12. use Pimcore\Bundle\EcommerceFrameworkBundle\AvailabilitySystem\AvailabilitySystemInterface;
  13. use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
  14. use Pimcore\Db;
  15. use Pimcore\Model\DataObject\AbstractObject;
  16. use Pimcore\Model\DataObject\OnlineShopOrder;
  17. use Pimcore\Model\DataObject\OnlineShopOrderItem;
  18. use Pimcore\Model\DataObject\ShopEvent;
  19. class AvailabilitySystem implements AvailabilitySystemInterface
  20. {
  21.     private TicketShopCartInterface $cart;
  22.     private bool $showCancelledItems false;
  23.     public function __construct(CartService $cartService)
  24.     {
  25.         $environment Factory::getInstance()->getEnvironment();
  26.         $cartName $environment->getCurrentCheckoutTenant() === 'b2b' ?: 'cart';
  27.         $this->cart $cartService->getCart($cartName);
  28.     }
  29.     public function setShowCancelledItems(bool $showCancelledItems): void
  30.     {
  31.         $this->showCancelledItems $showCancelledItems;
  32.     }
  33.     /**
  34.      * @param EventProduct $product
  35.      * @param int $quantityScale
  36.      * @param null $products
  37.      *
  38.      * @return Availability|null
  39.      */
  40.     public function getAvailabilityInfo(
  41.         $product,
  42.         $quantityScale 1,
  43.         $products null
  44.     ): ?Availability {
  45.         $returnAvailability null;
  46.         if ($product->getType() == AbstractObject::OBJECT_TYPE_VARIANT) {
  47.             $returnAvailability $this->getAvailableQuota($product$product->getEventStartDate());
  48.         } else {
  49.             //search every given availability
  50.             $availableList $this->getAvailableList($product);
  51.             foreach ($availableList as $availability) {
  52.                 if ($availability->isAvailable()) {
  53.                     $returnAvailability $availability;
  54.                     break;
  55.                 }
  56.             }
  57.         }
  58.         return $returnAvailability;
  59.     }
  60.     /**
  61.      * ermittelt alle verfügbaren tage und kontingente
  62.      *
  63.      * @param EventProduct $event
  64.      * @param Carbon|null $startDate
  65.      * @param Carbon|null $endDate
  66.      * @param bool $getPastAvailabilities
  67.      *
  68.      * @return Availability[]
  69.      */
  70.     public function getAvailableList(
  71.         EventProduct $event,
  72.         Carbon $startDate null,
  73.         Carbon $endDate null,
  74.         bool $getPastAvailabilities false,
  75.         bool $ignoreCartItems false
  76.     ): array {
  77.         if ($availableDates $this->getAvailableDates($event$startDate$endDate$getPastAvailabilities)) {
  78.             return $this->getAvailableQuotas($event$availableDates$ignoreCartItems);
  79.         }
  80.         return [];
  81.     }
  82.     /**
  83.      * Check if event has any availability on the whole day
  84.      *
  85.      * @param EventProduct $event
  86.      * @param Carbon $date
  87.      *
  88.      * @return bool
  89.      */
  90.     public function isAvailableOnDay(EventProduct $eventCarbon $date): bool
  91.     {
  92.         /** @var EventProduct $event */
  93.         $event $event->getReference();
  94.         $availabilities $this->getAvailableList($event$date->copy()->startOfDay(), $date->copy()->endOfDay());
  95.         return count($availabilities) > 0;
  96.     }
  97.     /**
  98.      * @param EventProduct $event
  99.      *
  100.      * @return Availability|null
  101.      */
  102.     public function getNextAvailability(EventProduct $event): ?Availability
  103.     {
  104.         $availability null;
  105.         if ($validityDates $event->getValidityDates()) {
  106.             if ($nextOccurrence $validityDates->getNextOccurrence()) {
  107.                 $nextDate Carbon::parse($nextOccurrence);
  108.                 $availability $this->getAvailableQuota($event$nextDate);
  109.             }
  110.         }
  111.         return $availability;
  112.     }
  113.     /**
  114.      * all available dates
  115.      *
  116.      * @param EventProduct $event
  117.      * @param Carbon|null $startDate
  118.      * @param Carbon|null $endDate
  119.      *
  120.      * @return Carbon[]
  121.      */
  122.     public function getAvailableDates(EventProduct $eventCarbon $startDate nullCarbon $endDate nullbool $getPastAvailabilities false): array
  123.     {
  124.         $availableDates = [];
  125.         if (!$startDate) {
  126.             if ($event->getBookableOnDayOfEvent()) {
  127.                 $startDate Carbon::now()->startOfDay();
  128.             } else {
  129.                 $startDate Carbon::now();
  130.             }
  131.         }
  132.         if (!$endDate) {
  133.             $endDate Carbon::today()->addYears(2);
  134.         }
  135.         if ($validityDates $event->getValidityDates()) {
  136.             $validDates $validityDates->getOccurrencesBetween($startDate$endDate);
  137.             foreach ($validDates as $validDate) {
  138.                 $date Carbon::parse($validDate);
  139.                 if ($this->isAvailableByDate($event$date$getPastAvailabilities$startDate)) {
  140.                     $availableDates[$date->getTimestamp()] = $date;
  141.                 }
  142.             }
  143.         }
  144.         return $availableDates;
  145.     }
  146.     /**
  147.      * @param EventProduct $event
  148.      * @param Carbon $date
  149.      * @param bool $ignoreCartItems
  150.      *
  151.      * @return Availability
  152.      */
  153.     public function getAvailableQuota(EventProduct $eventCarbon $datebool $ignoreCartItems false): Availability
  154.     {
  155.         $availabilities $this->getAvailableQuotas($event, [$date], $ignoreCartItems);
  156.         if (!empty($availabilities)) {
  157.             return array_shift($availabilities);
  158.         } else {
  159.             return new Availability($eventfalse00$date);
  160.         }
  161.     }
  162.     /**
  163.      * @param EventProduct $event
  164.      * @param array<mixed> $dates
  165.      * @param bool $ignoreCartItems
  166.      *
  167.      * @return Availability[]
  168.      */
  169.     protected function getAvailableQuotas(EventProduct $event, array $datesbool $ignoreCartItems false): array
  170.     {
  171.         $results = [];
  172.         $totalQuota $event->getQuota();
  173.         array_walk($dates, function (&$option) {
  174.             $option $option->getTimestamp();
  175.         });
  176.         $queryBuilder Db::get()->createQueryBuilder();
  177.         $queryBuilder->setParameters(['productId' => $event->getReference()->getId()]);
  178.         $this->buildQuery($queryBuilder);
  179.         $this->addProductCondition($queryBuilder);
  180.         $this->addItemStateCondition($queryBuilder);
  181.         $this->addDateCondition($queryBuilder$dates);
  182.         $this->addParentStateCondition($queryBuilder);
  183.         $list $queryBuilder->execute()->fetchAllAssociative();
  184.         if (!$ignoreCartItems) {
  185.             $cartItems $this->getCartItemsCount();
  186.         }
  187.         $id $event->getId();
  188.         foreach ($list as $entry) {
  189.             $availableQuota $totalQuota - (int)$entry['quota'];
  190.             if (isset($cartItems[$entry['eventStartDate'] . $id])) {
  191.                 $availableQuota -= $cartItems[$entry['eventStartDate'] . $id];
  192.             }
  193.             $eventDate Carbon::createFromTimestamp($entry['eventStartDate']);
  194.             $results[$entry['eventStartDate']] = new Availability(
  195.                 $event,
  196.                 ($availableQuota 0) && $this->isAvailableByDate($event$eventDate),
  197.                 $availableQuota $availableQuota 0,
  198.                 intval($totalQuota),
  199.                 $eventDate);
  200.         }
  201.         if (!$this->showCancelledItems) {
  202.             $tmpResult = [];
  203.             foreach ($results as $timestamp => $result) {
  204.                 if (!$result->isCancelled()) {
  205.                     $tmpResult[$timestamp] = $result;
  206.                 }
  207.             }
  208.             $results $tmpResult;
  209.         }
  210.         //if no booked event found, fill the rest with the total quota
  211.         foreach ($dates as $date) {
  212.             if (!key_exists($date$results)) {
  213.                 $availableQuota $totalQuota;
  214.                 if (isset($cartItems[$date $id])) {
  215.                     $availableQuota -= $cartItems[$date $id];
  216.                 }
  217.                 $eventDate Carbon::createFromTimestamp($date);
  218.                 $isAvailable $this->isAvailableByDate($event$eventDate);
  219.                 $availability = new Availability($event$isAvailable$availableQuotaintval($totalQuota), $eventDate);
  220.                 if ($this->showCancelledItems || !$availability->isCancelled()) {
  221.                     $results[$date] = $availability;
  222.                 }
  223.             }
  224.         }
  225.         ksort($results);
  226.         return $results;
  227.     }
  228.     private function isAvailableByDate(EventProduct $eventCarbon $eventDate, ?bool $getPastAvailabilities false, ?Carbon $startDate null): bool
  229.     {
  230.         if ($getPastAvailabilities) {
  231.             $startDate $startDate ?? (Carbon::now())->subDays(14);
  232.             $currentDateTime $startDate;
  233.         } else {
  234.             $currentDateTime Carbon::now();
  235.             //not available before x hours
  236.             if ($bookingStopHours $event->getStopSale()) {
  237.                 /** @phpstan-ignore-next-line  */
  238.                 $currentDateTime Carbon::now()->addhours($bookingStopHours);
  239.             }
  240.         }
  241.         return $currentDateTime->lte($event->getBookableOnDayOfEvent() ? $eventDate->endOfDay() : $eventDate);
  242.     }
  243.     /**
  244.      * @return int[]
  245.      */
  246.     private function getCartItemsCount(): array
  247.     {
  248.         $cartItems = [];
  249.         $eventCartItems $this->cart->getItemsByProductClass(EventProduct::class);
  250.         foreach ($eventCartItems as $item) {
  251.             /** @var EventProduct $product */
  252.             $product $item->getProduct();
  253.             $parentProduct $product->getReference();
  254.             if (isset($cartItems[$product->getEventStartDate()->getTimestamp() . $parentProduct->getId()])) {
  255.                 $cartItems[$product->getEventStartDate()->getTimestamp() . $parentProduct->getId()] += $item->getCount();
  256.             } else {
  257.                 $cartItems[$product->getEventStartDate()->getTimestamp() . $parentProduct->getId()] = $item->getCount();
  258.             }
  259.         }
  260.         return $cartItems;
  261.     }
  262.     /**
  263.      * @param QueryBuilder $queryBuilder
  264.      *
  265.      * @return void
  266.      */
  267.     protected function buildQuery(QueryBuilder $queryBuilder): void
  268.     {
  269.         $queryBuilder->select('sum(orderItem.amount) as sum''eventStartDate''sum(eventBrick.realQuota) as quota')
  270.             ->from('object_' OnlineShopOrderItem::classId(), 'orderItem')
  271.             ->join('orderItem''object_' ShopEvent::classId(), 'product''product.o_id = orderItem.product__id ')
  272.             ->join('orderItem''object_brick_store_OrderItemCustomizedEvent_' OnlineShopOrderItem::classId(), 'eventBrick''eventBrick.o_id = orderItem.o_id ')
  273.         ;
  274.     }
  275.     /**
  276.      * @param QueryBuilder $queryBuilder
  277.      * @param Carbon[] $startDateList
  278.      *
  279.      * @return void
  280.      */
  281.     protected function addDateCondition(QueryBuilder $queryBuilder, array $startDateList): void
  282.     {
  283.         $queryBuilder->andWhere('product.eventStartDate IN(' implode(','$startDateList) . ')')->addGroupBy('product.eventStartDate');
  284.     }
  285.     /**
  286.      * @param QueryBuilder $queryBuilder
  287.      *
  288.      * @return void
  289.      */
  290.     protected function addProductCondition(QueryBuilder $queryBuilder): void
  291.     {
  292.         $queryBuilder->andWhere(sprintf('(select o_parentId from object_%s p where p.o_id = product.o_id) = :productId'EventProduct::classId()));
  293.     }
  294.     /**
  295.      * @param QueryBuilder $queryBuilder
  296.      *
  297.      * @return void
  298.      */
  299.     protected function addItemStateCondition(QueryBuilder $queryBuilder): void
  300.     {
  301.         $queryBuilder->andWhere(' (orderItem.orderState is NULL OR orderItem.orderState = "committed" OR orderItem.orderState = "") ');
  302.     }
  303.     /**
  304.      * @param QueryBuilder $queryBuilder
  305.      *
  306.      * @return void
  307.      */
  308.     protected function addParentStateCondition(QueryBuilder $queryBuilder): void
  309.     {
  310.         $queryBuilder
  311.             ->andWhere(sprintf(' (SELECT shopOrder.orderState FROM object_%s shopOrder WHERE  shopOrder.o_id = orderItem.o_parentId) IN ("committed", "cancelled")'OnlineShopOrder::classId()));
  312.     }
  313. }