vendor/store.shopware.com/premsindividualoffer6/src/Core/Offer/Storefront/OfferService.php line 718

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /**
  3.  * PremSoft
  4.  * Copyright © 2020 Premsoft - Sven Mittreiter
  5.  *
  6.  * @copyright  Copyright (c) 2020, premsoft - Sven Mittreiter (http://www.premsoft.de)
  7.  * @author     Sven Mittreiter <info@premsoft.de>
  8.  */
  9. namespace Prems\Plugin\PremsIndividualOffer6\Core\Offer\Storefront;
  10. use Prems\Plugin\PremsIndividualOffer6\Core\Entity\Offer\Aggregate\OfferItem\OfferItemEntity;
  11. use Prems\Plugin\PremsIndividualOffer6\Core\Entity\Offer\OfferDefinition;
  12. use Prems\Plugin\PremsIndividualOffer6\Core\Entity\Offer\OfferEntity;
  13. use Prems\Plugin\PremsIndividualOffer6\Core\Media\MediaService;
  14. use Prems\Plugin\PremsIndividualOffer6\Event\OfferCreatedEvent;
  15. use Prems\Plugin\PremsIndividualOffer6\PremsIndividualOffer6;
  16. use Shopware\Core\Checkout\Cart\Cart;
  17. use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
  18. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  19. use Shopware\Core\Checkout\Cart\LineItemFactoryHandler\CustomLineItemFactory;
  20. use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
  21. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  22. use Shopware\Core\Checkout\Customer\CustomerEntity;
  23. use Shopware\Core\Content\Product\Cart\ProductLineItemFactory;
  24. use Shopware\Core\Content\Product\SalesChannel\Detail\AbstractProductDetailRoute;
  25. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  26. use Shopware\Core\Content\Product\ProductEntity;
  27. use Shopware\Core\Framework\Context;
  28. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  29. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
  30. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  31. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  32. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  33. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
  34. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  35. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  36. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  37. use Shopware\Core\Framework\Struct\ArrayEntity;
  38. use Shopware\Core\Framework\Uuid\Uuid;
  39. use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface;
  40. use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
  41. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  42. use Shopware\Core\Checkout\Customer\Aggregate\CustomerGroup\CustomerGroupEntity;
  43. use Symfony\Component\HttpFoundation\Session\Session;
  44. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  45. use Symfony\Component\HttpFoundation\Request;
  46. use Shopware\Core\Framework\Struct\ArrayStruct;
  47. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  48. class OfferService
  49. {
  50.     /**
  51.      * @var EntityRepositoryInterface
  52.      */
  53.     private $offerRepository;
  54.     /**
  55.      * @var EntityRepositoryInterface
  56.      */
  57.     private $offerItemRepository;
  58.     /**
  59.      * @var EntityRepositoryInterface
  60.      */
  61.     private $offerMessageRepository;
  62.     /**
  63.      * @var SalesChannelRepositoryInterface
  64.      */
  65.     private $productRepository;
  66.     /**
  67.      * @var CartService
  68.      */
  69.     private $cartService;
  70.     /**
  71.      * @var NumberRangeValueGeneratorInterface
  72.      */
  73.     private $numberRangeValueGenerator;
  74.     /**
  75.      * @var ProductLineItemFactory
  76.      */
  77.     private $productLineItemFactory;
  78.     /**
  79.      * @var ConfigService
  80.      */
  81.     private $configService;
  82.     /**
  83.      * @var AbstractProductDetailRoute
  84.      */
  85.     private $productLoader;
  86.     /**
  87.      * @var Session $session
  88.      */
  89.     private $session;
  90.     private OfferStateService $offerStateService;
  91.     /** @var MediaService $mediaService */
  92.     private $mediaService;
  93.     private EventDispatcherInterface $eventDispatcher;
  94.     public function __construct(
  95.         EntityRepositoryInterface $offerRepository,
  96.         EntityRepositoryInterface $offerItemRepository,
  97.         EntityRepositoryInterface $offerMessageRepository,
  98.         SalesChannelRepositoryInterface $productRepository,
  99.         CartService $cartService,
  100.         NumberRangeValueGeneratorInterface $numberRangeValueGenerator,
  101.         ProductLineItemFactory $productLineItemFactory,
  102.         ConfigService $configService,
  103.         AbstractProductDetailRoute $productLoader,
  104.         OfferStateService $offerStateService,
  105.         MediaService $mediaService,
  106.         Session $session,
  107.         EventDispatcherInterface $eventDispatcher
  108.     ) {
  109.         $this->offerRepository $offerRepository;
  110.         $this->offerItemRepository $offerItemRepository;
  111.         $this->offerMessageRepository $offerMessageRepository;
  112.         $this->productRepository $productRepository;
  113.         $this->cartService $cartService;
  114.         $this->numberRangeValueGenerator $numberRangeValueGenerator;
  115.         $this->productLineItemFactory $productLineItemFactory;
  116.         $this->configService $configService;
  117.         $this->productLoader $productLoader;
  118.         $this->session $session;
  119.         $this->offerStateService $offerStateService;
  120.         $this->mediaService $mediaService;
  121.         $this->eventDispatcher $eventDispatcher;
  122.     }
  123.     /**
  124.      * Return all offers for an user
  125.      *
  126.      * @param SalesChannelContext $salesChannelContext
  127.      * @param Request $request
  128.      *
  129.      * @return EntitySearchResult
  130.      *
  131.      * @throws InconsistentCriteriaIdsException
  132.      */
  133.     public function getOffersForUser(SalesChannelContext $salesChannelContextRequest $request)
  134.     {
  135.         $criteria = (new Criteria())
  136.             ->addFilter(new AndFilter([
  137.                 new EqualsFilter('prems_individual_offer.customerId'$salesChannelContext->getCustomer()->getId()),
  138.                 new EqualsFilter('prems_individual_offer.salesChannelId'$salesChannelContext->getSalesChannelId())
  139.             ]))
  140.             ->addAssociation('items')
  141.             ->addAssociation('items.product')
  142.             ->addAssociation('items.product.cover')
  143.             ->addAssociation('items.product.prices')
  144.             ->addSorting(new FieldSorting('createdAt'FieldSorting::DESCENDING));
  145.         $filters = [];
  146.         if ($request->query->get('openRequest')) {
  147.             $filters[] = new EqualsFilter('prems_individual_offer.offered'0);
  148.         }
  149.         if ($request->query->get('received')) {
  150.             $filters[] = new MultiFilter(
  151.                 MultiFilter::CONNECTION_AND,
  152.                 [
  153.                     new EqualsFilter('prems_individual_offer.offered'1),
  154.                     new EqualsFilter('prems_individual_offer.accepted'0),
  155.                     new EqualsFilter('prems_individual_offer.declined'0)
  156.                 ]
  157.             );
  158.         }
  159.         if ($request->query->get('ordered')) {
  160.             $filters[] = new MultiFilter(
  161.                 MultiFilter::CONNECTION_AND,
  162.                 [
  163.                     new EqualsFilter('prems_individual_offer.offered'1),
  164.                     new EqualsFilter('prems_individual_offer.accepted'1),
  165.                     new EqualsFilter('prems_individual_offer.declined'0)
  166.                 ]
  167.             );
  168.         }
  169.         if ($request->query->get('declined')) {
  170.             $filters[] = new EqualsFilter('prems_individual_offer.declined'1);
  171.         }
  172.         if (!empty($filters)) {
  173.             $criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_OR$filters));
  174.         }
  175.         $offers $this->offerRepository->search($criteria$salesChannelContext->getContext());
  176.         if (!empty($offers)) {
  177.             foreach ($offers as $offer) {
  178.                 $currentDate date('Y-m-d H:i:s');
  179.                 $offerDays = (int) $offer->getOfferDaysValid();
  180.                 $offerDate date('Y-m-d H:i:s'strtotime($offer->getUpdatedAtDateTime() . " +{$offerDays} day"));
  181.                 if (($offer->getOfferDaysValid() !== null && $offer->getOfferDaysValid() !== '') &&
  182.                     $offer->getUpdatedAtDateTime() !== null &&
  183.                     $offerDate <= $currentDate &&
  184.                     ($offer->isOffered() && !$offer->isAccepted() && !$offer->isDeclined())
  185.                 ) {
  186.                     $offer->setDeclined(true);
  187.                     $this->offerRepository->update([[
  188.                         'id' => $offer->getId(),
  189.                         'declined' => $offer->isDeclined(),
  190.                         'updated_at' => $offer->getUpdatedAtDateTime()
  191.                     ]], $salesChannelContext->getContext());
  192.                 }
  193.             }
  194.         }
  195.         $offers $this->offerRepository->search($criteria$salesChannelContext->getContext());
  196.         if (!empty($offers)) {
  197.             foreach ($offers as $offer) {
  198.                 $criteriaMessages = (new Criteria())
  199.                     ->addFilter(new EqualsFilter('prems_individual_offer_messages.offerId'$offer->getId()))
  200.                     ->addFilter(new EqualsFilter('prems_individual_offer_messages.customerId'$salesChannelContext->getCustomer()->getId()))
  201.                     ->addAssociation('user')
  202.                     ->addAssociation('customer')
  203.                     ->addSorting(new FieldSorting('prems_individual_offer_messages.createdAt'FieldSorting::DESCENDING));
  204.                 $messages $this->offerMessageRepository->search($criteriaMessages$salesChannelContext->getContext())->getEntities();
  205.                 $offer->setMessages($messages);
  206.             }
  207.         }
  208.         return $offers;
  209.     }
  210.     /**
  211.      * @param Context $context
  212.      */
  213.     public function updateOffersStatus(Context $context)
  214.     {
  215.         $criteria = new Criteria();
  216.         $offers $this->offerRepository->search($criteria$context);
  217.         if (!empty($offers)) {
  218.             foreach ($offers as $offer) {
  219.                 $currentDate date('Y-m-d H:i:s');
  220.                 $offerDays = (int) $offer->getOfferDaysValid();
  221.                 $offerDate date('Y-m-d H:i:s'strtotime($offer->getUpdatedAtDateTime() . " +{$offerDays} day"));
  222.                 if(($offer->getOfferDaysValid() !== null && $offer->getOfferDaysValid() !== '') &&
  223.                     $offer->getUpdatedAtDateTime() !== null &&
  224.                     $offerDate <= $currentDate &&
  225.                     ($offer->isOffered() && !$offer->isAccepted() && !$offer->isDeclined())
  226.                 ) {
  227.                     $offer->setDeclined(true);
  228.                     $this->offerRepository->update([[
  229.                         'id' => $offer->getId(),
  230.                         'declined' => $offer->isDeclined(),
  231.                         'updated_at' => $offer->getUpdatedAtDateTime()
  232.                     ]], $context);
  233.                 }
  234.             }
  235.         }
  236.         return $offers;
  237.     }
  238.     /**
  239.      * @param CustomerEntity $customer
  240.      * @param Context $context
  241.      * @return int
  242.      */
  243.     public function getOpenRequestCount(CustomerEntity $customerContext $context)
  244.     {
  245.         $criteria = (new Criteria())
  246.             ->addFilter(new EqualsFilter('prems_individual_offer.customerId'$customer->getId()))
  247.             ->addAssociation('items')
  248.             ->addAssociation('items.product')
  249.             ->addAssociation('items.product.cover')
  250.             ->addAssociation('items.product.prices')
  251.             ->addFilter(new EqualsFilter('prems_individual_offer.offered'0));
  252.         return $this->offerRepository->search($criteria$context)->getTotal();
  253.     }
  254.     /**
  255.      * @param CustomerEntity $customer
  256.      * @param Context $context
  257.      * @return int
  258.      */
  259.     public function getReceivedCount(CustomerEntity $customerContext $context)
  260.     {
  261.         $criteria = (new Criteria())
  262.             ->addFilter(new EqualsFilter('prems_individual_offer.customerId'$customer->getId()))
  263.             ->addAssociation('items')
  264.             ->addAssociation('items.product')
  265.             ->addAssociation('items.product.cover')
  266.             ->addAssociation('items.product.prices')
  267.             ->addFilter(new MultiFilter(
  268.         MultiFilter::CONNECTION_AND,
  269.                 [
  270.                     new EqualsFilter('prems_individual_offer.offered'1),
  271.                     new EqualsFilter('prems_individual_offer.accepted'0),
  272.                     new EqualsFilter('prems_individual_offer.declined'0)
  273.                 ]
  274.             ));
  275.         return $this->offerRepository->search($criteria$context)->getTotal();
  276.     }
  277.     /**
  278.      * @param CustomerEntity $customer
  279.      * @param Context $context
  280.      * @return int
  281.      */
  282.     public function getOrderedCount(CustomerEntity $customerContext $context)
  283.     {
  284.         $criteria = (new Criteria())
  285.             ->addFilter(new EqualsFilter('prems_individual_offer.customerId'$customer->getId()))
  286.             ->addAssociation('items')
  287.             ->addAssociation('items.product')
  288.             ->addAssociation('items.product.cover')
  289.             ->addAssociation('items.product.prices')
  290.             ->addFilter(new MultiFilter(
  291.                 MultiFilter::CONNECTION_AND,
  292.                 [
  293.                     new EqualsFilter('prems_individual_offer.offered'1),
  294.                     new EqualsFilter('prems_individual_offer.accepted'1),
  295.                     new EqualsFilter('prems_individual_offer.declined'0)
  296.                 ]
  297.             ));
  298.         return $this->offerRepository->search($criteria$context)->getTotal();
  299.     }
  300.     /**
  301.      * @param CustomerEntity $customer
  302.      * @param Context $context
  303.      * @return int
  304.      */
  305.     public function getDeclinedCount(CustomerEntity $customerContext $context)
  306.     {
  307.         $criteria = (new Criteria())
  308.             ->addFilter(new EqualsFilter('prems_individual_offer.customerId'$customer->getId()))
  309.             ->addAssociation('items')
  310.             ->addAssociation('items.product')
  311.             ->addAssociation('items.product.cover')
  312.             ->addAssociation('items.product.prices')
  313.             ->addFilter(new EqualsFilter('prems_individual_offer.declined'1));
  314.         return $this->offerRepository->search($criteria$context)->getTotal();
  315.     }
  316.     /**
  317.      * Get on offer by offer ID
  318.      * @param string $offerId
  319.      * @param Context $context
  320.      * @param CustomerEntity|null $customer
  321.      * @return OfferEntity
  322.      */
  323.     public function getOfferById(string $offerIdContext $contextCustomerEntity $customer null)
  324.     {
  325.         $criteria = new Criteria([ $offerId ]);
  326.         if ($customer) {
  327.             $criteria->addFilter(new EqualsFilter('prems_individual_offer.customerId'$customer->getId()));
  328.         }
  329.         $criteria
  330.             ->addAssociation('items')
  331.             ->addAssociation('items.product')
  332.             ->addAssociation('items.product.prices')
  333.             ->addAssociation('items.product.cover')
  334.             ->addAssociation('customer')
  335.             ->addAssociation('customer.salutation')
  336.             ->addAssociation('customer.defaultBillingAddress')
  337.             ->addAssociation('customer.activeBillingAddress')
  338.             ->addAssociation('customer.defaultBillingAddress.country')
  339.             ->addAssociation('customer.activeBillingAddress.country')
  340.             ->addAssociation('shippingMethod.tax')
  341.             ->addAssociation('currency')
  342.             ->addAssociation('salesChannel.domains')
  343.             ->addAssociation('salesChannel.mailHeaderFooter')
  344.             ->addAssociation('language.locale');
  345.         $offer $this->offerRepository->search($criteria$context)->first();
  346.         if ($offer && $customer != null) {
  347.             $criteriaMessages = (new Criteria())
  348.                 ->addFilter(new EqualsFilter('prems_individual_offer_messages.offerId'$offer->getId()))
  349.                 ->addFilter(new EqualsFilter('prems_individual_offer_messages.customerId'$customer->getId()))
  350.                 ->addAssociation('user')
  351.                 ->addAssociation('customer')
  352.                 ->addSorting(new FieldSorting('prems_individual_offer_messages.createdAt'FieldSorting::DESCENDING));
  353.             $messages $this->offerMessageRepository->search($criteriaMessages$context)->getEntities();
  354.             $offer->setMessages($messages);
  355.         }
  356.         return $offer;
  357.     }
  358.     /**
  359.      * Get on product by product ID
  360.      * @param string $productId
  361.      * @param Context $context
  362.      * @return ProductEntity
  363.      */
  364.     public function getProductById(string $productIdSalesChannelContext $context)
  365.     {
  366.         $criteria = new Criteria([$productId]);
  367.         $criteria->addAssociation('cover');
  368.         $criteria->addAssociation('prices');
  369.         $product $this->productRepository->search($criteria$context)->first();
  370.         return $product;
  371.     }
  372.     /**
  373.      * @param OfferEntity $offer
  374.      * @param ProductEntity $product
  375.      * @param Context $context
  376.      */
  377.     public function addOfferItem(OfferEntity $offerProductEntity $productContext $context)
  378.     {
  379.         $criteria = new Criteria();
  380.         $criteria->addFilter(new EqualsFilter('offerId'$offer->getId()));
  381.         $offerItemsCount $this->offerItemRepository->search($criteria$context)->getTotal();
  382.         $oldPrice $offer->getTaxStatus() == OfferEntity::TAX_STATUS_NET $product->getPrice()->first()->getNet() : $product->getPrice()->first()->getGross();
  383.         $price $offer->getTaxStatus() == OfferEntity::TAX_STATUS_NET $product->getPrice()->first()->getNet() : $product->getPrice()->first()->getGross();
  384.         $quantity 1;
  385.         $data = [
  386.             'oldPrice' =>  $oldPrice,
  387.             'price' => $price,
  388.             'totalPrice' => $price $quantity,
  389.             'quantity' => $quantity,
  390.             'position' => $offerItemsCount 1,
  391.             //'lineItem' => \base64_encode(\serialize($product)),
  392.             'itemOption' => null,
  393.             'productId' => $product->getId(),
  394.             'offerId' => $offer->getId(),
  395.             //'itemType' => 'swag-customized-products'
  396.         ];
  397.         return $this->offerItemRepository->create([
  398.             $data
  399.         ], $context);
  400.     }
  401.     /**
  402.      * Get and return items of an offer
  403.      * @param $offerId
  404.      * @param $customerId
  405.      * @param SalesChannelContext $context
  406.      * @return null|\Prems\Plugin\PremsIndividualOffer6\Core\Entity\Offer\Aggregate\OfferItem\OfferItemCollection
  407.      * @throws InconsistentCriteriaIdsException
  408.      */
  409.     public function getItemsFromOffer($offerId$customerIdSalesChannelContext $context)
  410.     {
  411.         $criteria = new Criteria();
  412.         $criteria->addFilter(new EqualsFilter('id'$offerId))
  413.             ->addFilter(new EqualsFilter('prems_individual_offer.customerId'$customerId))
  414.             ->addAssociation('items')
  415.             ->addAssociation('customer')
  416.             //->addAssociation('items.product.prices')
  417.             /**->addAssociation('items.product')
  418.             ->addAssociation('items.product.cover')
  419.             ->addAssociation('items.product.prices')*/;
  420.         /** @var OfferEntity|null $offer */
  421.         $offer $this->offerRepository->search($criteria$context->getContext())->first();
  422.         if (!$offer instanceof OfferEntity) {
  423.             throw new NotFoundHttpException();
  424.         }
  425.         $nestedItems $offer->getNestedItems();
  426.         $elements $nestedItems->getElements();
  427.         $productCriteria = new Criteria();
  428.         $productCriteria->addAssociation('options.group');
  429.         /** @var OfferItemEntity $element */
  430.         foreach($elements as $element) {
  431.             $clonedCriteria = clone $productCriteria;
  432.             /** @var SalesChannelProductEntity|null $product */
  433.             if (!$element->getProductId()) {
  434.                 continue;
  435.             }
  436.             $result $this->productLoader->load($element->getProductId(), new Request(), $context$clonedCriteria);
  437.             /** @var SalesChannelProductEntity|null $product */
  438.             $product $result->getProduct();
  439.             if ($product) {
  440.                 $element->setProduct($product);
  441.                 $element->setPriceNet();
  442.             }
  443.         }
  444.         return $nestedItems;
  445.     }
  446.     /**
  447.      * Determine if a offer request is generally allowed (Plugin activated for saleschannel, cart amount high enough)
  448.      * @param SalesChannelContext $context
  449.      *
  450.      * @return bool
  451.      */
  452.     public function isOfferRequestAllowed(SalesChannelContext $context)
  453.     {
  454.         $offerSettings $this->configService->getConfig($context);
  455.         $settings $offerSettings->getVars();
  456.         if ($settings['disallowedCustomerGroups'] && is_array($settings['disallowedCustomerGroups'])) {
  457.             $customerGroup $context->getCurrentCustomerGroup();
  458.             if (in_array($customerGroup->getId(), $settings['disallowedCustomerGroups'])) {
  459.                 return false;
  460.             }
  461.         }
  462.         if ($settings['offerMinValueReached'] == true) {
  463.             return true;
  464.         }
  465.         return false;
  466.     }
  467.     /**
  468.      * Creates an offer request for a customer
  469.      * @param CustomerEntity $customer
  470.      * @param CustomerGroupEntity
  471.      * @param SalesChannelContext $context
  472.      *
  473.      * @throws \Exception
  474.      * @return EntityWrittenContainerEvent
  475.      */
  476.     public function createOfferRequest(CustomerEntity $customerCustomerGroupEntity $customerGroupSalesChannelContext $context)
  477.     {
  478.         $items = [];
  479.         $cart $this->cartService->getCart($context->getToken(), $context);
  480.         $lineItems $cart->getLineItems()->filter(
  481.             function (LineItem $lineItem) {
  482.                 return in_array($lineItem->getType(), [LineItem::PRODUCT_LINE_ITEM_TYPE'customized-products'LineItem::PROMOTION_LINE_ITEM_TYPE]);
  483.             }
  484.         );
  485.         $currencySymbol $context->getCurrency()->getSymbol();
  486.         if ($lineItems) {
  487.             //echo "<pre>"; print_R($lineItems); echo "</pre>";die();
  488.             $i 0;
  489.             foreach($lineItems as $lineItem) {
  490.                 $i++;
  491.                 //$products[] = ['id' => $lineItem->getId()];
  492.                 $data = [
  493.                     'productId' => $lineItem->getId(),
  494.                     'oldPrice' => $lineItem->getPrice()->getUnitPrice(),
  495.                     'price' => $lineItem->getPrice()->getUnitPrice(),
  496.                     'totalPrice' => $lineItem->getPrice()->getTotalPrice(),
  497.                     'quantity' => $lineItem->getQuantity(),
  498.                     'position' => $i,
  499.                     'lineItem' => \base64_encode(\serialize($lineItem)),
  500.                 ];
  501.                 // Possibility to use referencedID instead of productId for third party vendors
  502.                 if ($lineItem->hasPayloadValue('prems_individual_offer_use_reference_id_instead_of_product_id')) {
  503.                     $data['productId'] = $lineItem->getReferencedId();
  504.                 }
  505.                 if($lineItem->getType() == LineItem::PROMOTION_LINE_ITEM_TYPE) {
  506.                     $data['productId'] = null;
  507.                     $data['itemType'] = LineItem::PROMOTION_LINE_ITEM_TYPE;
  508.                 }
  509.                 if ($lineItem->getType() == 'customized-products') {
  510.                     $productId 0;
  511.                     $children $lineItem->getChildren();
  512.                     $itemOption = [];
  513.                     foreach($children as $child) {
  514.                         if ($child->getType() == LineItem::PRODUCT_LINE_ITEM_TYPE) {
  515.                             $productId $child->getReferencedId();
  516.                         }
  517.                         if ($child->getType() == 'customized-products-option') {
  518.                             $quantity '';
  519.                             $price '';
  520.                             $payloadValue '';
  521.                             if ($child->getQuantity() > 1) {
  522.                                 $quantity $child->getQuantity() . ' x ';
  523.                             }
  524.                             if ($child->hasChildren()) {
  525.                                 $subChildren $child->getChildren();
  526.                                 $optionValues = [];
  527.                                 foreach($subChildren as $subChild) {
  528.                                     if ($subChild->getPrice()->getTotalPrice() > 0) {
  529.                                         $price ' '.$subChild->getPrice()->getTotalPrice() . ' ' $currencySymbol;
  530.                                     }
  531.                                     $optionValues[] = $subChild->getLabel();
  532.                                 }
  533.                                 $itemOption[] = $quantity.$child->getLabel() . ' (' implode('; '$optionValues) . ')'.$payloadValue.$price;
  534.                             } else {
  535.                                 if ($child->getPrice()->getTotalPrice() > 0) {
  536.                                     $price ' '.$child->getPrice()->getTotalPrice() . ' ' $currencySymbol;
  537.                                 }
  538.                                 $payload $child->getPayload();
  539.                                 if ($payload && array_key_exists('value'$payload) && !is_array($payload['value'])) {
  540.                                     $payloadValue ': '.$payload['value'];
  541.                                 }
  542.                                 if ($payload && array_key_exists('media'$payload) && is_array($payload['media'])) {
  543.                                     $media $this->mediaService->getMediaById($payload['media']['0']['mediaId'], $context->getContext());
  544.                                     $payloadValue ': '.$media->getUrl();
  545.                                 }
  546.                                 $itemOption[] = $quantity.$child->getLabel().$payloadValue.$price ;
  547.                             }
  548.                         }
  549.                     }
  550.                     //$data['itemOption'] = implode('<br>',$itemOption);
  551.                     $data['itemOption'] = json_encode($itemOption);
  552.                     $data['productId'] = $productId;
  553.                     $data['itemType'] = 'swag-customized-products';
  554.                 }
  555.                 $items[] = $data;
  556.             }
  557.         }
  558.         //$this->numberRangeValueGenerator->previewPattern(OfferDefinition::ENTITY_NAME, '{n}', 1);
  559.         if ($customerGroup->getDisplayGross()) {
  560.             $taxStatus 'gross';
  561.         } else {
  562.             $taxStatus 'net';
  563.         }
  564.         $cart->setErrors(new ErrorCollection());
  565.         $cart->setData(null);
  566.         $offerId Uuid::randomHex();
  567.         $offerData =             [
  568.             'id' => $offerId,
  569.             'customerId' => $customer->getId(),
  570.             'taxStatus' => $taxStatus,
  571.             'offerNumber' => $this->numberRangeValueGenerator->getValue(
  572.                 OfferDefinition::ENTITY_NAME,
  573.                 $context->getContext(),
  574.                 $context->getSalesChannel()->getId()
  575.             ),
  576.             'pdfComment' => $this->configService->getConfig($context)->getDefaultPdfComment(),
  577.             'currencyFactor' => $context->getContext()->getCurrencyFactor(),
  578.             'currencyId' => $context->getContext()->getCurrencyId(),
  579.             'languageId' => $context->getContext()->getLanguageId(),
  580.             'salesChannelId' => $context->getSalesChannel()->getId(),
  581.             'cart' => \base64_encode(\serialize($cart)),
  582.             'items' => $items,
  583.         ];
  584.         if ($this->configService->getConfig($context)->getDefaultOfferDaysValid() > 0) {
  585.             $offerData['offer_days_valid'] = (int) $this->configService->getConfig($context)->getDefaultOfferDaysValid();
  586.             $offerData['offerExpiration'] = $this->calculateOfferExpirationDate($offerData['offer_days_valid']);
  587.         }
  588.         $offerCreation $this->offerRepository->create([
  589.             $offerData
  590.         ], $context->getContext());
  591.         $this->eventDispatcher->dispatch(new OfferCreatedEvent($offerId$context));
  592.         return $offerCreation;
  593.     }
  594.     /**
  595.      * Determine if an offer is orderable
  596.      * @param OfferEntity $offer
  597.      * @return bool
  598.      */
  599.     public function isOfferOrderable(OfferEntity $offer): bool
  600.     {
  601.         if ($offer->isOffered() && !$offer->isAccepted() && !$offer->isDeclined()) {
  602.             return true;
  603.         }
  604.         return false;
  605.     }
  606.     /**
  607.      * Determine if an offer is declineable
  608.      * @param OfferEntity $offer
  609.      * @return bool
  610.      */
  611.     public function isOfferDeclineable(OfferEntity $offer): bool
  612.     {
  613.         if ($offer->isOffered() && !$offer->isAccepted() && !$offer->isDeclined()) {
  614.             return true;
  615.         }
  616.         return false;
  617.     }
  618.     /**
  619.      * Determine if customer is allowed to access an offer
  620.      * @param OfferEntity $offer
  621.      * @param CustomerEntity $customer
  622.      * @return bool
  623.      */
  624.     public function hasAccessToOffer(OfferEntity $offerCustomerEntity $customer): bool
  625.     {
  626.         $customerIsOwner $customer->getId() === $offer->getCustomer()->getId();
  627.         return $customerIsOwner;
  628.     }
  629.     /**
  630.      * Determine if basket is in offer mode and then return offer mode
  631.      * @param SalesChannelContext $context
  632.      * @return string
  633.      */
  634.     public function getOfferMode($context): string
  635.     {
  636.         // compatibility mode for older version with session value. If there is an entry then cancel offer mode.
  637.         // Code will be removed in newer versions
  638.         if ($this->session->has('prems_individual_offer')) {
  639.             $this->session->remove('prems_individual_offer');
  640.             $this->cancelOfferMode($context);
  641.         }
  642.         $cart $this->cartService->getCart($context->getToken(), $context);
  643.         //echo "<pre>"; print_R($cart->getExtensions()); die();
  644.         if (!$cart->hasExtension('offer_mode_active')) {
  645.             return '';
  646.         }
  647.         $offerMode $cart->getExtension('offer_mode_active');
  648.         if (!$offerMode || !$offerMode->getId()) {
  649.             return '';
  650.         }
  651.         return $offerMode->getId();
  652.     }
  653.     /**
  654.      * Set flag that basket is in offer mode
  655.      *
  656.      * @param SalesChannelContext $context
  657.      * @param string $offerId
  658.      */
  659.     public function activateOfferMode($context$offerId):void
  660.     {
  661.         $cart $this->cartService->getCart($context->getToken(), $context);
  662.         $cart->addExtension(PremsIndividualOffer6::OFFER_MODE_EXTENSION_NAME, new ArrayEntity(['id' => $offerId]));
  663.     }
  664.     /**
  665.      * Set flag that basket is no longer in offer mode
  666.      */
  667.     public function disableOfferMode($context):void
  668.     {
  669.         $cart $this->cartService->getCart($context->getToken(), $context);
  670.         // compatibility mode for older version with session value.
  671.         if ($cart->hasExtension(PremsIndividualOffer6::OFFER_MODE_EXTENSION_NAME)) {
  672.             $cart->removeExtension(PremsIndividualOffer6::OFFER_MODE_EXTENSION_NAME);
  673.         }
  674.     }
  675.     /**
  676.      * @param SalesChannelContext $context
  677.      */
  678.     public function cancelOfferMode($context): void
  679.     {
  680.         $this->disableOfferMode($context);
  681.         $context->removeState(PremsIndividualOffer6::OFFER_MODE_STATE);
  682.         $this->cartService->deleteCart($context);
  683.     }
  684.     /**
  685.      * Add an offer to basket and set basket to offer mode
  686.      * @param $offer
  687.      * @param SalesChannelContext $context
  688.      */
  689.     public function addOfferToBasket($offer$context)
  690.     {
  691.         // Empty cart
  692.         $cart $this->cartService->getCart($context->getToken(), $context);
  693.         $items $cart->getLineItems();
  694.         foreach($items as $item) {
  695.             $this->cartService->remove($cart$item->getId(), $context);
  696.         }
  697.         $products = [];
  698.         /** @var OfferItemEntity $item */
  699.         foreach ($offer->getItems() as $item) {
  700.             // Swag Custom Product found?
  701.             if ($item->getItemType() == 'swag-customized-products') {
  702.                 $product \unserialize(base64_decode((string)$item->getLineItem()));
  703.                 if ($product && $product instanceof LineItem) {
  704.                     $product->setStackable(true);
  705.                     $product->setQuantity($item->getQuantity());
  706.                     $product->setRemovable(false);
  707.                     $product->setStackable(false);
  708.                     $product->addExtension('prems', new ArrayStruct(['originalId' => $item->getId(), 'price' => $product->getPrice()]));
  709.                 } else {
  710.                     continue;
  711.                 }
  712.             } elseif($item->getItemType() == LineItem::PROMOTION_LINE_ITEM_TYPE) {
  713.                 $row \unserialize(base64_decode((string) $item->getLineItem()));
  714.                 $product = new LineItem(Uuid::randomHex(), LineItem::CUSTOM_LINE_ITEM_TYPE);
  715.                 $product->assign($row->getVars());
  716.                 $product->setType(LineItem::CUSTOM_LINE_ITEM_TYPE);
  717.                 $product->setPriceDefinition(new QuantityPriceDefinition($product->getPrice()->getTotalPrice(), $product->getPrice()->getTaxRules()));
  718.                 $product->setGood(true);
  719.                 $product->setRemovable(false);
  720.                 $product->addExtension('prems', new ArrayStruct(['originalId' => $item->getId(), 'price' => $product->getPrice()]));
  721.             } else {
  722.                 $product \unserialize(base64_decode((string) $item->getLineItem()));
  723.                 if (!$product || !is_object($product)) {
  724.                     $product $this->productLineItemFactory->create($item->getProductId());
  725.                 }
  726.                 $product->setStackable(true);
  727.                 $product->setRemovable(false);
  728.                 $product->setQuantity($item->getQuantity());
  729.                 $product->setStackable(false);
  730.                 $product->addExtension('prems', new ArrayStruct(['originalId' => $item->getId(), 'price' => $product->getPrice()]));
  731.             }
  732.             $products[] = $product;
  733.         }
  734.         $this->cartService->add($cart$products$context);
  735.     }
  736.     /**
  737.      * Declines an offer
  738.      * @param OfferEntity $offer
  739.      * @param Context $context
  740.      * @param int|null $offer_days_valid
  741.      *
  742.      * @return EntityWrittenContainerEvent
  743.      * @throws \Exception
  744.      */
  745.     public function declineOffer(OfferEntity $offerContext $context$offer_days_valid null)
  746.     {
  747.         $offer->setDeclined(true);
  748.         return $this->offerRepository->update([[
  749.           'id' => $offer->getId(),
  750.           'declined' => $offer->isDeclined(),
  751.           'offer_days_valid' => $offer_days_valid,
  752.           'offerExpiration' => $this->calculateOfferExpirationDate($offer_days_valid$offer->getUpdatedAt())
  753.         ]], $context);
  754.     }
  755.     /**
  756.      * Accept an offer
  757.      * @param OfferEntity $offer
  758.      * @param Context $context
  759.      *
  760.      * @return EntityWrittenContainerEvent
  761.      */
  762.     public function acceptOffer(OfferEntity $offerContext $context)
  763.     {
  764.         $offer->setAccepted(true);
  765.         return $this->offerRepository->update([[
  766.             'id' => $offer->getId(),
  767.             'accepted' => $offer->isAccepted()
  768.         ]], $context);
  769.     }
  770.     /**
  771.      * Creates an offer
  772.      * @param OfferEntity $offer
  773.      * @param Context $context
  774.      * @param int|null $offer_days_valid
  775.      *
  776.      * @return EntityWrittenContainerEvent
  777.      * @throws \Exception
  778.      */
  779.     public function createOffer(OfferEntity $offerContext $context$offer_days_valid null)
  780.     {
  781.         $offer->setOffered(true);
  782.         $offer->setAccepted(false);
  783.         $offer->setDeclined(false);
  784.         return $this->offerRepository->update([[
  785.             'id' => $offer->getId(),
  786.             'offered' => $offer->isOffered(),
  787.             'accepted' => $offer->isAccepted(),
  788.             'declined' => $offer->isDeclined(),
  789.             'offer_days_valid' => $offer_days_valid,
  790.             'offerExpiration' => $this->calculateOfferExpirationDate($offer_days_valid$offer->getUpdatedAt()),
  791.             'admin_updated_at' => date("Y-m-d H:i:s")
  792.         ]], $context);
  793.     }
  794.     /**
  795.      * Change days an offer
  796.      * @param OfferEntity $offer
  797.      * @param Context $context
  798.      * @param int|null $offer_days_valid
  799.      *
  800.      * @return EntityWrittenContainerEvent
  801.      * @throws \Exception
  802.      */
  803.     public function changeOfferDays(OfferEntity $offerContext $context$offer_days_valid null)
  804.     {
  805.         $offer->setOffered(true);
  806.         return $this->offerRepository->update([[
  807.             'id' => $offer->getId(),
  808.             'offer_days_valid' => $offer_days_valid ?? null,
  809.             'offerExpiration' => $this->calculateOfferExpirationDate($offer_days_valid$offer->getUpdatedAt())
  810.         ]], $context);
  811.     }
  812.     public function setOffered(string $offerIdContext $context): bool
  813.     {
  814.         $this->offerRepository->update([['id' => $offerId'offered' => true]], $context);
  815.         return true;
  816.     }
  817.     /**
  818.      * Adds Line items to cart
  819.      * @param Cart $cart
  820.      * @param $items
  821.      * @param SalesChannelContext $context
  822.      * @return Cart
  823.      */
  824.     public function addCartItems(Cart $cart$itemsSalesChannelContext $context): Cart
  825.     {
  826.         $cart $this->cartService->add($cart$items$context);
  827.         return $cart;
  828.     }
  829.     /**
  830.      * @param Cart $cart
  831.      * @param string $id
  832.      * @param SalesChannelContext $context
  833.      * @return Cart
  834.      */
  835.     public function removeCartItem(Cart $cartstring $idSalesChannelContext $context): Cart
  836.     {
  837.         $cart $this->cartService->remove($cart$id$context);
  838.         return $cart;
  839.     }
  840.     /**
  841.      * @param array $items
  842.      * @param Context $context
  843.      * @return EntityWrittenContainerEvent
  844.      */
  845.     public function updateOfferItems(array $itemsContext $context): EntityWrittenContainerEvent
  846.     {
  847.         return $this->offerItemRepository->update($items$context);
  848.     }
  849.     /**
  850.      * @param $days
  851.      * @param $date
  852.      * @throws \Exception
  853.      * @return \DateTimeImmutable
  854.      */
  855.     public function calculateOfferExpirationDate($days$date null): \DateTimeImmutable
  856.     {
  857.         if (!$date) {
  858.             $date = new \DateTimeImmutable(date("Y-m-d H:i:s"time()));
  859.         }
  860.         if (is_string($date)) {
  861.             $date = new \DateTimeImmutable($date);
  862.         }
  863.         $interval \DateInterval::createFromDateString("{$days} day");
  864.         return $date->add($interval);
  865.     }
  866. }