vendor/boldr/shop-bundle/src/Presenter/Order/OrderPresenter.php line 78

Open in your IDE?
  1. <?php
  2. namespace Boldr\Shop\ShopBundle\Presenter\Order;
  3. use Boldr\Shop\ShopBundle\Presenter\Order\ChangeState\{ ChangeStateView, ChangeStateStepView };
  4. use Boldr\Shop\ShopBundle\Entity\{ Order, OrderItem, Option, Discount, Invoice };
  5. use Boldr\Shop\ShopBundle\OrderFlow\OrderFlowManagerInterface;
  6. use Boldr\Shop\ShopBundle\OrderFlow\ChangeStateSteps;
  7. use Boldr\Shop\ShopBundle\OrderFlow\ChangeStateStep\ChangeStateStepInterface;
  8. use Boldr\Shop\ShopBundle\Presenter\Product\ProductPresenter;
  9. use Boldr\Shop\ShopBundle\Presenter\Customer\CustomerPresenter;
  10. use Boldr\Shop\ShopBundle\Presenter\Currency\CurrencyPresenter;
  11. use Boldr\Shop\ShopBundle\Price\Order\{ OrderPriceCalculator, OrderPriceQuery, OrderItemPrice };
  12. use Boldr\Shop\ShopBundle\Price\PriceModifierManager;
  13. use Boldr\Shop\ShopBundle\Presenter\Option\OptionPresenter;
  14. use Boldr\Shop\ShopBundle\Presenter\PriceModifierView;
  15. use Boldr\Cms\CmsBundle\Content\Assets;
  16. use Doctrine\ORM\EntityManagerInterface;
  17. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  18. use Symfony\Contracts\Service\ServiceSubscriberInterface;
  19. use Symfony\Contracts\Translation\TranslatorInterface;
  20. use Psr\Container\ContainerInterface;
  21. use Boldr\Shop\ShopBundle\Presenter\Price\PricePresenter;
  22. use Boldr\Shop\ShopBundle\DiscountProvider;
  23. use Boldr\Shop\ShopBundle\Presenter\Payment\PaymentOptionPresenter;
  24. class OrderPresenter implements ServiceSubscriberInterface
  25. {
  26. public static function getSubscribedServices(): array
  27. {
  28. return [
  29. OrderFlowManagerInterface::class,
  30. ProductPresenter::class,
  31. CurrencyPresenter::class,
  32. CustomerPresenter::class,
  33. OrderPriceCalculator::class,
  34. PriceModifierManager::class,
  35. UrlGeneratorInterface::class,
  36. TranslatorInterface::class,
  37. OptionPresenter::class,
  38. EntityManagerInterface::class,
  39. PricePresenter::class,
  40. DiscountProvider::class,
  41. PaymentOptionPresenter::class
  42. ];
  43. }
  44. private ContainerInterface $container;
  45. /** @var iterable<OrderViewProcessorInterface> */
  46. private iterable $orderViewProcessors;
  47. /** @var iterable<OrderItemViewProcessorInterface> */
  48. private iterable $orderItemViewProcessors;
  49. private array $priceModifierCategories;
  50. private iterable $changeStateStepPresenters;
  51. public function __construct(
  52. ContainerInterface $container,
  53. iterable $orderViewProcessors,
  54. iterable $orderItemViewProcessors,
  55. iterable $changeStateStepPresenters,
  56. )
  57. {
  58. $this->container = $container;
  59. $this->orderViewProcessors = $orderViewProcessors;
  60. $this->orderItemViewProcessors = $orderItemViewProcessors;
  61. $this->changeStateStepPresenters = $changeStateStepPresenters;
  62. }
  63. /**
  64. * Creates a view object for the whole order
  65. */
  66. public function createOrderView(Order $order, string $locale): OrderView
  67. {
  68. // Create sub-views
  69. $orderItemViews = array_map(
  70. fn($orderItem) => $this->createOrderItemView($orderItem, $locale),
  71. $order->getItems()->toArray()
  72. );
  73. $customerView = $this->container->get(CustomerPresenter::class)->createCustomerView($order->getCustomer(), $locale);
  74. $currencyView = $this->container->get(CurrencyPresenter::class)->createCurrencyView($order->getCurrency(), $locale);
  75. $orderFlowManager = $this->container->get(OrderFlowManagerInterface::class);
  76. // Calculate dynamic options
  77. $nextStateViews = [];
  78. foreach ($orderFlowManager->getNextStates($order) as $nextState)
  79. $nextStateViews[] = $this->createNextStateView($order, $nextState, $locale);
  80. $orderPrice = $this->container->get(OrderPriceCalculator::class)->calculateOrderPrice(new OrderPriceQuery($order));
  81. $stateName = $this->container->get(TranslatorInterface::class)->trans('order_states.'. $order->getState(), [], 'BoldrShopBundle');
  82. $canItemsBeModified = $this->container->get(OrderFlowManagerInterface::class)->canItemsBeModified($order);
  83. $priceModifierManager = $this->container->get(PriceModifierManager::class);
  84. $translator = $this->container->get(TranslatorInterface::class);
  85. $priceModifierCategories = $priceModifierManager->getCategories();
  86. $discountViews = [];
  87. $automaticDiscountViews = [];
  88. $orderDiscounts = $order->getDiscounts()
  89. ->filter(fn($orderDiscount) => $orderDiscount->getDiscount() !== null)
  90. ->map(fn($orderDiscount) => $orderDiscount->getDiscount())
  91. ;
  92. foreach ($this->container->get(DiscountProvider::class)->getOrderDiscounts($order) as $discount)
  93. {
  94. if ($orderDiscounts->contains($discount))
  95. {
  96. $discountViews[] = $this->createDiscountView($discount, $locale);
  97. }
  98. else
  99. {
  100. $automaticDiscountViews[] = $this->createDiscountView($discount, $locale);
  101. }
  102. }
  103. $invoiceAddressView = null;
  104. if ($order->getInvoiceAddress() !== null)
  105. {
  106. $invoiceAddressView = $this->container->get(CustomerPresenter::class)->createAddressView($order->getInvoiceAddress()->getAddress(), null);
  107. }
  108. $invoiceUrls = array_map(
  109. fn($invoice) => $this->container->get(UrlGeneratorInterface::class)->generate('shop_customer_invoice_download', ['invoiceId' => $invoice->getId()]),
  110. array_filter(
  111. $order->getInvoices(),
  112. fn($invoice) => $invoice->getStatus() !== Invoice::STATUS_DRAFT && !$invoice->isProForma(),
  113. )
  114. );
  115. $selectedPaymentOptionView = $order->getSelectedPaymentOption() === null
  116. ? null
  117. : $this->container->get(PaymentOptionPresenter::class)->createPaymentOptionView($order->getSelectedPaymentOption(), $locale);
  118. $orderView = new OrderView(
  119. $order->getId(),
  120. $order->getNumber(),
  121. $customerView,
  122. $currencyView,
  123. $this->container->get(PricePresenter::class)->createOrderPriceView($orderPrice, $locale),
  124. $orderItemViews,
  125. $stateName,
  126. $nextStateViews,
  127. $order->getTimeCustomerConfirmed(),
  128. $order->getCustomerNote(),
  129. $canItemsBeModified,
  130. $discountViews,
  131. $automaticDiscountViews,
  132. $invoiceAddressView,
  133. $invoiceUrls,
  134. $order->getCustomer()->getLocale(),
  135. $selectedPaymentOptionView
  136. );
  137. // Process views
  138. return array_reduce(
  139. iterator_to_array($this->orderViewProcessors),
  140. fn($orderView, $orderViewProcessor) => $orderViewProcessor->processOrderView($orderView, $order, $locale),
  141. $orderView
  142. );
  143. }
  144. public function createOrderItemView(OrderItem $orderItem, string $locale)
  145. {
  146. // Create sub-views
  147. $productPresenter = $this->container->get(ProductPresenter::class);
  148. $productView = $productPresenter->createProductView($orderItem->getProductVariant()->getProduct(), $locale);
  149. $productVariantView = $productPresenter->createProductVariantView($orderItem->getProductVariant(), $locale);
  150. // Calculate item price if not determined yet
  151. $invoiceItem = $orderItem->getInvoiceItem();
  152. if ($invoiceItem)
  153. {
  154. $price = new OrderItemPrice($invoiceItem->getUnitPrice(), $orderItem->getQuantity(), $invoiceItem->getModifiers());
  155. }
  156. else
  157. {
  158. $priceCalculator = $this->container->get(OrderPriceCalculator::class);
  159. $orderPrice = $priceCalculator->calculateOrderPrice(new OrderPriceQuery($orderItem->getOrder()));
  160. $price = $orderPrice->getOrderItemPrices()[$orderItem->getId()];
  161. }
  162. $em = $this->container->get(EntityManagerInterface::class);
  163. $optionRepository = $em->getRepository(Option::class);
  164. $optionPresenter = $this->container->get(OptionPresenter::class);
  165. $optionValueViews = [];
  166. $customer = $orderItem->getOrder()->getCustomer();
  167. $productVariant = $orderItem->getProductVariant();
  168. foreach ($orderItem->getOptionValues() as $optionId => $optionValue)
  169. {
  170. $option = $optionRepository->find($optionId);
  171. if ($option !== null)
  172. {
  173. $optionValueView = $optionPresenter->createOptionValueView($option, $productVariant, $customer, $optionValue, $locale);
  174. $optionValueViews[] = $optionValueView;
  175. }
  176. }
  177. $priceModifierManager = $this->container->get(PriceModifierManager::class);
  178. $currency = $orderItem->getOrder()->getCurrency();
  179. $customer = $orderItem->getOrder()->getCustomer();
  180. $priceView = $this->container->get(PricePresenter::class)->createOrderItemPriceView($price, $currency, $customer, $locale);
  181. $orderItemView = new OrderItemView(
  182. $orderItem->getId(),
  183. $productView,
  184. $productVariantView,
  185. $orderItem->getQuantity(),
  186. $priceView,
  187. $optionValueViews
  188. );
  189. // Process view
  190. return array_reduce(
  191. iterator_to_array($this->orderItemViewProcessors),
  192. fn($orderItemView, $orderItemViewProcessor) => $orderItemViewProcessor->processOrderItemView($orderItemView, $orderItem, $locale),
  193. $orderItemView
  194. );
  195. }
  196. public function createChangeStateStepView(ChangeStateStepInterface $changeStateStep, Order $order, string $locale): ?ChangeStateStepView
  197. {
  198. foreach ($this->changeStateStepPresenters as $changeStateStepPresenter)
  199. {
  200. if ($changeStateStepPresenter->supports($changeStateStep))
  201. {
  202. return $changeStateStepPresenter->createChangeStateStepView($changeStateStep, $order, $locale);
  203. }
  204. }
  205. throw new \Exception('No presenter supports '.get_class($changeStateStep));
  206. }
  207. public function createChangeStateView(string $nextState, ChangeStateSteps $changeStateSteps, Order $order, bool $cantConfirm, string $locale): ChangeStateView
  208. {
  209. $orderView = $this->createOrderView($order, $locale);
  210. $changeStateStepViews = [];
  211. $assets = new Assets;
  212. foreach ($changeStateSteps->getSteps() as $changeStateStep)
  213. {
  214. $changeStateStepView = $this->createChangeStateStepView($changeStateStep, $order, $locale);
  215. if ($changeStateStepView !== null)
  216. {
  217. $changeStateStepViews[] = $changeStateStepView;
  218. $assets->addAll($changeStateStepView->getAssets());
  219. }
  220. }
  221. $title = $this->container->get(TranslatorInterface::class)->trans('next_state_buttons.'. $nextState, [], 'BoldrShopBundle', $locale);
  222. return new ChangeStateView(
  223. $nextState,
  224. $title,
  225. $orderView,
  226. $changeStateStepViews,
  227. $assets,
  228. $cantConfirm
  229. );
  230. }
  231. public function createNextStateView(Order $order, string $nextState, string $locale): NextStateView
  232. {
  233. $currentOrder = $this->container->get(OrderFlowManagerInterface::class)->getCurrentOrder($order->getCustomer());
  234. $translator = $this->container->get(TranslatorInterface::class);
  235. $name = $translator->trans('order_states.'. $nextState, [], 'BoldrShopBundle', $locale);
  236. $key = 'next_state_buttons_from.'. $order->getState() .'.'.$nextState;
  237. if ($translator->getCatalogue($locale)->defines($key, 'BoldrShopBundle'))
  238. {
  239. $buttonText = $translator->trans($key, [], 'BoldrShopBundle', $locale);
  240. }
  241. else
  242. {
  243. $buttonText = $translator->trans('next_state_buttons.'. $nextState, [], 'BoldrShopBundle', $locale);
  244. }
  245. $urlGenerator = $this->container->get(UrlGeneratorInterface::class);
  246. if ($currentOrder === $order)
  247. {
  248. $url = $urlGenerator->generate('shop_change_cart_state', [
  249. '_locale' => $locale,
  250. 'nextState' => $nextState
  251. ], UrlGeneratorInterface::ABSOLUTE_URL);
  252. }
  253. else
  254. {
  255. $url = $urlGenerator->generate('shop_change_order_state', [
  256. '_locale' => $locale,
  257. 'orderId' => $order->getId(),
  258. 'nextState' => $nextState
  259. ], UrlGeneratorInterface::ABSOLUTE_URL);
  260. }
  261. return new NextStateView($nextState, $name, $buttonText, $url);
  262. }
  263. public function createDiscountView(Discount $discount, string $locale): DiscountView
  264. {
  265. $translation = $discount->getTranslations()->get($locale);
  266. return new DiscountView(
  267. $discount->getId(),
  268. $translation->getName(),
  269. $discount->getCode(),
  270. [
  271. Discount::TYPE_AMOUNT => 'amount',
  272. Discount::TYPE_PERCENTAGE => 'percentage',
  273. Discount::TYPE_N_PLUS_N => 'n_plus_n'
  274. ][$discount->getType()]
  275. );
  276. }
  277. }