src/Model/Shop/Ticket/TicketProduct.php line 43

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by Elements.at New Media Solutions GmbH
  4.  *
  5.  */
  6. namespace App\Model\Shop\Ticket;
  7. use App\Model\Shop\CancelableInterface;
  8. use App\Model\Shop\OrderItem;
  9. use App\Model\Traits\SkidataTrait;
  10. use App\Model\Type\ForwardingStateType;
  11. use App\Model\Type\PoolType;
  12. use App\Model\Type\TenantType;
  13. use Carbon\Carbon;
  14. use Elements\Bundle\SkidataTicketingSwebBundle\Model\Constant\ValidityUnit;
  15. use Elements\Bundle\SkidataTicketingSwebBundle\Skidata\Sweb\Client\SalesChannel\Enum\PermissionStatusEnum;
  16. use Elements\Bundle\SkidataTicketingSwebBundle\Skidata\Sweb\Client\SalesChannel\Enum\SalesStatusEnum;
  17. use Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\PriceGroupInterface;
  18. use Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketProduct as TSF_TicketProduct;
  19. use Elements\Bundle\TicketShopFrameworkBundle\Model\Shop\Product\PriceGroupAwareProduct;
  20. use Pimcore;
  21. use Pimcore\Logger;
  22. use Pimcore\Model\DataObject\Objectbrick\Data\TpB2bArData;
  23. use Pimcore\Model\DataObject\Objectbrick\Data\TpMarketplaceArData;
  24. use Pimcore\Model\DataObject\Objectbrick\Data\TpTradeArData;
  25. use Pimcore\Model\DataObject\TicketMetaProduct;
  26. use Pimcore\Model\DataObject\TicketshopTicketAdditional;
  27. use Throwable;
  28. /**
  29.  * Class TicketProduct
  30.  *
  31.  * @package App\Model\Shop\Ticketing
  32.  *
  33.  * @method TicketConsumerCategory|null getTicketConsumerCategory()
  34.  * @method ShopTicketCatalog|null getCatalogObject()
  35.  * @method TicketProduct getReference()
  36.  * @method TicketConsumerCategory[]|null getConsumerCategories()
  37.  *
  38.  */
  39. class TicketProduct extends TSF_TicketProduct implements CancelableInterface
  40. {
  41.     use SkidataTrait;
  42.     const PRODUCT_TYPE 'ticket';
  43.     const SHOP_SYSTEM_NAME 'ticket';
  44.     protected function getShopSystemName(): string
  45.     {
  46.         return TicketProduct::SHOP_SYSTEM_NAME;
  47.     }
  48.     /**
  49.      * defines the name of the price system for this product.
  50.      * there should either be a attribute in pro product object or
  51.      * it should be overwritten in mapped sub classes of product classes
  52.      *
  53.      * @return string
  54.      */
  55.     public function getPriceSystemName(): ?string
  56.     {
  57.         return $this->getShopSystemName();
  58.     }
  59.     /**
  60.      * defines the name of the availability system for this product.
  61.      * there should either be a attribute in pro product object or
  62.      * it should be overwritten in mapped sub classes of product classes
  63.      *
  64.      * @return string
  65.      */
  66.     public function getAvailabilitySystemName(): ?string
  67.     {
  68.         return 'default';
  69.     }
  70.     public function getOrderCatalog(): ?ShopTicketCatalog
  71.     {
  72.         // load unpublished objects too to display catalog infos in backend attask #419238 - storno-thema
  73.         Pimcore\Model\DataObject\AbstractObject::setHideUnpublished(false);
  74.         $orderCatalog $this->getCatalogObject();
  75.         Pimcore\Model\DataObject\AbstractObject::setHideUnpublished(true);
  76.         return $orderCatalog;
  77.     }
  78.     /**
  79.      * Checks if TicketProduct is bookable
  80.      */
  81.     public function isBookable(int $quantity 1): bool
  82.     {
  83.         if (!\Pimcore::inDebugMode() && $this->deactivateSkidataRequests()) {
  84.             return false;
  85.         }
  86.         $skidataProduct $this->getSkidataProduct();
  87.         if (null === $skidataProduct) {
  88.             return false;
  89.         }
  90.         if ($this->getOnlyToday()) {
  91.             if (Carbon::now()->lt(Carbon::now()->setTime(121000))) {
  92.                 return false;
  93.             }
  94.         }
  95.         return $skidataProduct->getActive() && $this->getReference()->isPublished();
  96.     }
  97.     public function isSingleRide(): bool
  98.     {
  99.         return (bool)$this->getSingleRideTicket();
  100.     }
  101.     public function isCancelable(OrderItem $orderItem null): bool
  102.     {
  103.         if ($orderItem) {
  104.             $currentAdminUser \Pimcore\Tool\Admin::getCurrentUser();
  105.             $skidataBrick $orderItem->getSkidataBrick();
  106.             // can not cancel if cancelled in Skidata - except for special users
  107.             if ($orderItem->isCanceledInSkidata() && !$currentAdminUser?->isAllowed('shop_backend_cancel_cancelled_in_skidata')) {
  108.                 return false;
  109.             }
  110.             // can cancel always if not submitted !
  111.             if (!$skidataBrick || $skidataBrick->getTransmisssionState() == ForwardingStateType::ERROR || $skidataBrick->getTransmisssionState() == null) {
  112.                 return true;
  113.             }
  114.             if (!$this->isCancelableInSkidata($orderItem)) {
  115.                 return false;
  116.             }
  117.             if (!$this->isSeasonTicket()) {
  118.                 if ($validityBrick $orderItem->getCustomized()->getOrderItemCustomizedValidityRange()) {
  119.                     // do not allow cancellation in between validity -> enhance to allow until 7 o clock if needed
  120.                     // this logic shouldn't be applied for seasonal tickets (attask #414766)
  121.                     // Root Cause for this: the Consumed State is only synced once a day ( at around 23:50)  so we can not rely on that -> hence  during validity cancellation is locked -> if not consumed it can be cancelled after validity again.
  122.                     if (Carbon::now()->between($validityBrick->getStartDate(), $validityBrick->getEndDate())) {
  123.                         return false;
  124.                     }
  125.                 }
  126.             }
  127.         }
  128.         return true;
  129.     }
  130.     public function isCancelableInSkidata(OrderItem $orderItem): bool
  131.     {
  132.         $skidataBrick $orderItem->getSkidataBrick();
  133.         if ($skidataBrick) {
  134.             /**
  135.              * Ein "TicketOrderItem" ist stornierbar falls sämtliche TicketItems:
  136.              * 1.) die Permission noch nicht "consumed" ist
  137.              * 2.) das TicketItem selbst stornierbar ist, entspricht folgendener Abfrage
  138.              * Booked ( BOOKED oder BOOKED_AND_REJECTED oder  BOOKED_AND_TRANSFERRED )
  139.              * ! reserved => wieder stornierbar ( #642504 / #624984)
  140.              *
  141.              * wenn bereits in Skidata storniert => cancel im Pimcore erlaubt (Pimcore-Storno, RĂĽckerstattung datatrans)
  142.              */
  143.             $salesStatus $skidataBrick->getSkiDataSalesStatus();
  144.             $cancelableSalesStates array_merge(SalesStatusEnum::getBookedSalesStates(), SalesStatusEnum::getCanceledSalesStates());
  145.             $cancelableSalesStates[] = SalesStatusEnum::Reserved;
  146.             if (!in_array($salesStatus$cancelableSalesStates)) {
  147.                 return false;
  148.             }
  149.             $permissionStatus $skidataBrick->getSkiDataPermissionStatus();
  150.             // not cancelable anymore if they are on this  states !
  151.             if (in_array($permissionStatus, [PermissionStatusEnum::ConsumedPermissionStatusEnum::Blocked])) {
  152.                 return false;
  153.             }
  154.             if ($skidataBrick->getSkiDataPermissionConsumed()) {
  155.                 return false;
  156.             }
  157.         }
  158.         return true;
  159.     }
  160.     public function getMetaProduct(): TicketMetaProduct|false
  161.     {
  162.         $listing = new TicketMetaProduct\Listing();
  163.         $listing->addConditionParam("find_in_set({$this->getId()}, relatedTo)");
  164.         return $listing->current();
  165.     }
  166.     public function getInsuranceMetaProduct(): TicketshopTicketAdditional|false
  167.     {
  168.         $listing = new TicketshopTicketAdditional\Listing();
  169.         $listing->addConditionParam("find_in_set({$this->getId()}, products)");
  170.         return $listing->current();
  171.     }
  172.     /**
  173.      * @param Carbon|null $startDate
  174.      *
  175.      * @return Carbon|null
  176.      */
  177.     public function getTicketEndDate(Carbon $startDate null): ?Carbon
  178.     {
  179.         $duration null;
  180.         $catalog $this->getCatalogObject();
  181.         if ($startDate == null) {
  182.             $startDate $this->getTicketStartDate();
  183.         }
  184.         if ($catalog && $catalog->getIsAnnualCatalog()) {
  185.             $yearlyDate = clone $startDate;
  186.             return $yearlyDate->addYear();
  187.         } elseif ($this->isSeasonTicket() && $catalog) {
  188.             return $catalog->getCurrentDateRangeEndDate();
  189.         }
  190.         if ($this->getValidityUnit() == ValidityUnit::DAY) {
  191.             $duration $this->getValidityValue() - 1// substract 1 as we use StartOfDay and EndOfDay hence 1 Day is always on the same days
  192.         } else {
  193.             //todo - Saisonkarten, Punktekarten? Stundenkarten?
  194.         }
  195.         //  sanity checks that it is at least 0 !! no negative numbers
  196.         if ($duration 0) {
  197.             $duration 0;
  198.         }
  199.         return $startDate $startDate->copy()->addDays($duration)->endOfDay() : null;
  200.     }
  201.     public function isSkidataBookableFor(TicketConsumerCategory $consumerCategory): bool
  202.     {
  203.         if ($this->getSkidataProduct() && !empty($this->getSkidataProduct()->getConsumerCategories())) {
  204.             $skidataConsumer $consumerCategory->getSkidataConsumerForSkidataProduct($this->getSkidataProduct());
  205.             if ($skidataConsumer) {
  206.                 return true;
  207.             }
  208.         }
  209.         return false;
  210.     }
  211.     public function getTicketPool(): string
  212.     {
  213.         $skiDataProduct $this->getReference()->getSkidataProduct();
  214.         if ($skiDataProduct == null) {
  215.             return PoolType::UNASSIGNED;
  216.         }
  217.         $pool $skiDataProduct->getValidPoolName() ?? PoolType::UNASSIGNED;
  218.         if (!PoolType::isValid($pool)) {
  219.             $pool PoolType::UNKNOWN;
  220.         }
  221.         return $pool;
  222.     }
  223.     public function getOrCreateCloneWithPriceGroup(PriceGroupInterface $priceGroup): PriceGroupAwareProduct
  224.     {
  225.         if (!$priceGroup instanceof TicketConsumerCategory) {
  226.             throw new \InvalidArgumentException('PriceGroup must be instance of TicketConsumerCategory');
  227.         }
  228.         /** @phpstan-ignore-next-line */
  229.         return $this->getReference()->getOrCreateOrderableObject($priceGroup,
  230.             $this->getTicketStartDate(),
  231.             $this->getAcquisitionType(),
  232.             $this->getCatalogObject());
  233.     }
  234.     public function getAllowedAcquisitionTypes()
  235.     {
  236.         return $this->getSpecialDataForTenant('getAllowedAcquisitionTypes');
  237.     }
  238.     public function getRequiresAuthorization()
  239.     {
  240.         return $this->getSpecialDataForTenant('getRequiresAuthorization');
  241.     }
  242.     public function getDefaultAcquisitionType()
  243.     {
  244.         return $this->getSpecialDataForTenant('getDefaultAcquisitionType');
  245.     }
  246.     public function getIsDepotTicket()
  247.     {
  248.         return $this->getSpecialDataForTenant('getIsDepotTicket');
  249.     }
  250.     public function getBirthdayRequired()
  251.     {
  252.         return $this->getSpecialDataForTenant('getBirthdayRequired');
  253.     }
  254.     public function getRequirementsPerConsumerCategory()
  255.     {
  256.         return $this->getSpecialDataForTenant('getRequirementsPerConsumerCategory');
  257.     }
  258.     public function getAcquisitionType(): ?string
  259.     {
  260.         return $this->getSpecialDataForTenant('getAcquisitionType');
  261.     }
  262.     public function getRequirements(): ?array
  263.     {
  264.         return $this->getSpecialDataForTenant('getRequirements');
  265.     }
  266.     public function getOrCreateTpTradeArDataBrick(): TpTradeArData
  267.     {
  268.         $brick $this->getAcquisitionRequirementsOverride();
  269.         if (!$data $brick->getTpTradeArData()) {
  270.             $data = new TpTradeArData($this);
  271.             $data->setFieldname('acquisitionRequirementsOverride');
  272.             $brick->setTpTradeArData($data);
  273.         }
  274.         return $data;
  275.     }
  276.     public function getOrCreateTpMarketplaceArDataBrick(): TpMarketplaceArData
  277.     {
  278.         $brick $this->getAcquisitionRequirementsOverride();
  279.         if (!$data $brick->getTpMarketplaceArData()) {
  280.             $data = new TpMarketplaceArData($this);
  281.             $data->setFieldname('acquisitionRequirementsOverride');
  282.             $brick->setTpMarketplaceArData($data);
  283.         }
  284.         return $data;
  285.     }
  286.     public function getOrCreateTpB2bArDataBrick(): TpB2bArData
  287.     {
  288.         $brick $this->getAcquisitionRequirementsOverride();
  289.         if (!$data $brick->getTpB2bArData()) {
  290.             $data = new TpB2bArData($this);
  291.             $data->setFieldname('acquisitionRequirementsOverride');
  292.             $brick->setTpB2bArData($data);
  293.         }
  294.         return $data;
  295.     }
  296.     private function getSpecialDataForTenant(string $name): mixed
  297.     {
  298.         try {
  299.             if (!\Pimcore::inAdmin()) {
  300.                 $factory Pimcore\Bundle\EcommerceFrameworkBundle\Factory::getInstance();
  301.                 if ($factory->getEnvironment()->getCurrentCheckoutTenant() === TenantType::TOUR_OPERATOR) {
  302.                     if ($data $this->getOrCreateTpTradeArDataBrick()->$name()) {
  303.                         return $data;
  304.                     }
  305.                 }
  306.                 if ($factory->getEnvironment()->getCurrentCheckoutTenant() === TenantType::B2B) {
  307.                     if ($data $this->getOrCreateTpB2bArDataBrick()->$name()) {
  308.                         return $data;
  309.                     }
  310.                 }
  311.                 if ($factory->getEnvironment()->getCurrentCheckoutTenant() === TenantType::MARKETPLACE) {
  312.                     if ($data $this->getOrCreateTpMarketplaceArDataBrick()->$name()) {
  313.                         return $data;
  314.                     }
  315.                 }
  316.             }
  317.         } catch (Throwable $e) {
  318.             Logger::error($e);
  319.         }
  320.         return parent::$name();
  321.     }
  322. }