OrderShip.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. /**
  3. * Refer to LICENSE.txt distributed with the Temando Shipping module for notice of license
  4. */
  5. namespace Temando\Shipping\ViewModel\Order;
  6. use Magento\Directory\Helper\Data as DirectoryHelper;
  7. use Magento\Framework\App\Config\ScopeConfigInterface;
  8. use Magento\Framework\Exception\LocalizedException;
  9. use Magento\Framework\Serialize\Serializer\Json;
  10. use Magento\Framework\UrlInterface;
  11. use Magento\Framework\View\Element\Block\ArgumentInterface;
  12. use Magento\Sales\Api\Data\OrderInterface;
  13. use Magento\Store\Model\ScopeInterface;
  14. use Psr\Log\LoggerInterface;
  15. use Temando\Shipping\Model\Pickup\PickupLoader;
  16. use Temando\Shipping\Model\ResourceModel\Repository\OrderCollectionPointRepositoryInterface;
  17. use Temando\Shipping\Model\ResourceModel\Repository\OrderRepositoryInterface;
  18. use Temando\Shipping\Model\Shipment\ShipmentProviderInterface;
  19. use Temando\Shipping\ViewModel\CoreApiInterface;
  20. use Temando\Shipping\ViewModel\DataProvider\CoreApiAccess;
  21. use Temando\Shipping\ViewModel\DataProvider\CoreApiAccessInterface;
  22. use Temando\Shipping\ViewModel\DataProvider\DeliveryType;
  23. use Temando\Shipping\ViewModel\DataProvider\ShippingApiAccess;
  24. use Temando\Shipping\ViewModel\DataProvider\ShippingApiAccessInterface;
  25. use Temando\Shipping\ViewModel\OrderShipInterface;
  26. use Temando\Shipping\ViewModel\Pickup\PickupItems;
  27. use Temando\Shipping\ViewModel\ShippingApiInterface;
  28. /**
  29. * View model for order ship JS component.
  30. *
  31. * @package Temando\Shipping\ViewModel
  32. * @author Christoph Aßmann <christoph.assmann@netresearch.de>
  33. * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  34. * @link https://www.temando.com/
  35. */
  36. class OrderShip implements ArgumentInterface, CoreApiInterface, OrderShipInterface, ShippingApiInterface
  37. {
  38. /**
  39. * @var CoreApiAccess
  40. */
  41. private $coreApiAccess;
  42. /**
  43. * @var ShippingApiAccess
  44. */
  45. private $shippingApiAccess;
  46. /**
  47. * @var UrlInterface
  48. */
  49. private $urlBuilder;
  50. /**
  51. * @var ScopeConfigInterface
  52. */
  53. private $scopeConfig;
  54. /**
  55. * @var Json
  56. */
  57. private $serializer;
  58. /**
  59. * @var DeliveryType
  60. */
  61. private $deliveryType;
  62. /**
  63. * @var OrderRepositoryInterface
  64. */
  65. private $orderRepository;
  66. /**
  67. * @var ShipmentProviderInterface
  68. */
  69. private $shipmentProvider;
  70. /**
  71. * @var OrderCollectionPointRepositoryInterface
  72. */
  73. private $collectionPointRepository;
  74. /**
  75. * @var PickupLoader
  76. */
  77. private $pickupLoader;
  78. /**
  79. * @var PickupItems
  80. */
  81. private $pickupItems;
  82. /**
  83. * @var LoggerInterface
  84. */
  85. private $logger;
  86. /**
  87. * OrderShip constructor.
  88. * @param CoreApiAccess $coreApiAccess
  89. * @param ShippingApiAccess $shippingApiAccess
  90. * @param UrlInterface $urlBuilder
  91. * @param ScopeConfigInterface $scopeConfig
  92. * @param Json $serializer
  93. * @param DeliveryType $deliveryType
  94. * @param OrderRepositoryInterface $orderRepository
  95. * @param ShipmentProviderInterface $shipmentProvider
  96. * @param OrderCollectionPointRepositoryInterface $collectionPointRepository
  97. * @param PickupLoader $pickupLoader
  98. * @param PickupItems $pickupItems
  99. * @param LoggerInterface $logger
  100. */
  101. public function __construct(
  102. CoreApiAccess $coreApiAccess,
  103. ShippingApiAccess $shippingApiAccess,
  104. UrlInterface $urlBuilder,
  105. ScopeConfigInterface $scopeConfig,
  106. Json $serializer,
  107. DeliveryType $deliveryType,
  108. OrderRepositoryInterface $orderRepository,
  109. ShipmentProviderInterface $shipmentProvider,
  110. OrderCollectionPointRepositoryInterface $collectionPointRepository,
  111. PickupLoader $pickupLoader,
  112. PickupItems $pickupItems,
  113. LoggerInterface $logger
  114. ) {
  115. $this->coreApiAccess = $coreApiAccess;
  116. $this->shippingApiAccess = $shippingApiAccess;
  117. $this->urlBuilder = $urlBuilder;
  118. $this->scopeConfig = $scopeConfig;
  119. $this->serializer = $serializer;
  120. $this->deliveryType = $deliveryType;
  121. $this->orderRepository = $orderRepository;
  122. $this->shipmentProvider = $shipmentProvider;
  123. $this->collectionPointRepository = $collectionPointRepository;
  124. $this->pickupLoader = $pickupLoader;
  125. $this->pickupItems = $pickupItems;
  126. $this->logger = $logger;
  127. }
  128. /**
  129. * Obtain order properties for JSON serialization.
  130. *
  131. * @return string[]
  132. */
  133. private function getOrderProperties(): array
  134. {
  135. return [
  136. 'entity_id',
  137. 'increment_id',
  138. 'is_virtual',
  139. 'store_id',
  140. 'customer_id',
  141. 'base_shipping_amount',
  142. 'customer_is_guest',
  143. 'billing_address_id',
  144. 'shipping_address_id',
  145. 'weight',
  146. 'total_qty_ordered',
  147. 'base_currency_code'
  148. ];
  149. }
  150. /**
  151. * Obtain order item properties for JSON serialization.
  152. *
  153. * @return string[]
  154. */
  155. private function getOrderItemProperties(): array
  156. {
  157. return [
  158. 'item_id',
  159. 'order_id',
  160. 'store_id',
  161. 'product_id',
  162. 'weight',
  163. 'is_virtual',
  164. 'sku',
  165. 'name',
  166. 'qty_ordered',
  167. 'qty_shipped',
  168. 'base_price',
  169. 'base_row_total'
  170. ];
  171. }
  172. /**
  173. * Obtain order address properties for JSON serialization.
  174. *
  175. * @return string[]
  176. */
  177. private function getOrderAddressProperties(): array
  178. {
  179. return [
  180. 'entity_id',
  181. 'postcode',
  182. 'lastname',
  183. 'street',
  184. 'city',
  185. 'email',
  186. 'telephone',
  187. 'country_id',
  188. 'firstname',
  189. 'address_type',
  190. 'prefix',
  191. 'middlename',
  192. 'suffix',
  193. 'company'
  194. ];
  195. }
  196. /**
  197. * @return OrderInterface|\Magento\Sales\Model\Order
  198. */
  199. private function getOrder(): OrderInterface
  200. {
  201. /** @var \Magento\Sales\Model\Order\Shipment $shipment */
  202. $shipment = $this->shipmentProvider->getSalesShipment();
  203. $order = $shipment->getOrder();
  204. return $order;
  205. }
  206. /**
  207. * Determine the quantity to be excluded from shipments, e.g. due to them
  208. * being already prepared for store pickup.
  209. *
  210. * @param string $itemSku
  211. * @return int
  212. */
  213. private function getReservedQty(string $itemSku): int
  214. {
  215. $order = $this->getOrder();
  216. if (!$this->deliveryType->isPickupOrder($order)) {
  217. return 0;
  218. }
  219. $orderId = (int)$order->getEntityId();
  220. $pickups = $this->pickupLoader->load($orderId);
  221. $this->pickupLoader->register($pickups, $orderId);
  222. return $this->pickupItems->getQtyPrepared($itemSku);
  223. }
  224. /**
  225. * @return CoreApiAccessInterface
  226. */
  227. public function getCoreApiAccess(): CoreApiAccessInterface
  228. {
  229. return $this->coreApiAccess;
  230. }
  231. /**
  232. * @return ShippingApiAccessInterface
  233. */
  234. public function getShippingApiAccess(): ShippingApiAccessInterface
  235. {
  236. return $this->shippingApiAccess;
  237. }
  238. /**
  239. * Obtain Magento REST API endpoint for shipment creation.
  240. *
  241. * @return string
  242. */
  243. public function getShipEndpoint(): string
  244. {
  245. $orderId = $this->getOrder()->getId();
  246. $endpoint = $this->urlBuilder->getDirectUrl("rest/V1/order/$orderId/ship", ['_secure' => true]);
  247. // core bug workaround, route parameter "_direct" does not get reset
  248. $this->urlBuilder->getUrl("rest/V1/order/$orderId/ship", ['_direct' => null]);
  249. return $endpoint;
  250. }
  251. /**
  252. * Obtain a JSON representation of relevant order data for usage in the
  253. * OrderShip UI component.
  254. *
  255. * @return string
  256. */
  257. public function getOrderData(): string
  258. {
  259. $order = $this->getOrder();
  260. $orderItems = [];
  261. foreach ($order->getAllItems() as $orderItem) {
  262. if (!$orderItem->getIsVirtual() && !$orderItem->getParentItem()) {
  263. // skip virtual and child items
  264. $qtyShipped = $orderItem->getQtyShipped() + $this->getReservedQty($orderItem->getSku());
  265. $orderItems[$orderItem->getId()] = $orderItem->toArray($this->getOrderItemProperties());
  266. $orderItems[$orderItem->getId()]['qty_shipped'] = number_format($qtyShipped, 3);
  267. }
  268. }
  269. $orderAddresses = [];
  270. /** @var \Magento\Sales\Model\Order\Address $orderAddress */
  271. foreach ($order->getAddresses() as $orderAddress) {
  272. $orderAddresses[$orderAddress->getId()] = $orderAddress->toArray($this->getOrderAddressProperties());
  273. $orderAddresses[$orderAddress->getId()]['region'] = $orderAddress->getRegionCode();
  274. }
  275. $orderData = $order->toArray($this->getOrderProperties());
  276. $orderData['items'] = $orderItems;
  277. $orderData['addresses'] = $orderAddresses;
  278. try {
  279. $collectionPoint = $this->collectionPointRepository->get($orderData['shipping_address_id']);
  280. $orderData['final_recipient_address_id'] = $orderData['shipping_address_id'];
  281. $orderData['shipping_address_id'] = $collectionPoint->getCollectionPointId();
  282. $orderData['addresses'][$collectionPoint->getCollectionPointId()] = [
  283. 'entity_id' => $collectionPoint->getCollectionPointId(),
  284. 'postcode' => $collectionPoint->getPostcode(),
  285. 'street' => implode("\n", $collectionPoint->getStreet()),
  286. 'city' => $collectionPoint->getCity(),
  287. 'country_id' => $collectionPoint->getCountry(),
  288. 'address_type' => 'collection_point',
  289. 'company' => $collectionPoint->getName(),
  290. 'region' => $collectionPoint->getRegion(),
  291. ];
  292. } catch (LocalizedException $exception) {
  293. $orderData['final_recipient_address_id'] = $orderData['shipping_address_id'];
  294. }
  295. return $this->serializer->serialize($orderData);
  296. }
  297. /**
  298. * Obtain a JSON representation of relevant order metadata for usage in the
  299. * OrderShip UI component.
  300. *
  301. * @return string
  302. */
  303. public function getOrderMeta(): string
  304. {
  305. $order = $this->getOrder();
  306. $shippingAddressId = $order->getShippingAddress()->getId();
  307. try {
  308. $collectionPoint = $this->collectionPointRepository->get($shippingAddressId);
  309. $isCollectionPoint = (bool) $collectionPoint->getRecipientAddressId();
  310. } catch (LocalizedException $e) {
  311. $isCollectionPoint = false;
  312. }
  313. $orderMeta = ['isCollectionPoint' => $isCollectionPoint];
  314. return $this->serializer->serialize($orderMeta);
  315. }
  316. /**
  317. * @return string
  318. */
  319. public function getSelectedExperience(): string
  320. {
  321. $order = $this->getOrder();
  322. $shippingMethod = $order->getShippingMethod(true);
  323. $experienceCode = $shippingMethod->getData('method');
  324. return $experienceCode;
  325. }
  326. /**
  327. * @return string
  328. */
  329. public function getExtOrderId(): string
  330. {
  331. $order = $this->getOrder();
  332. try {
  333. $orderReference = $this->orderRepository->getReferenceByOrderId($order->getId());
  334. $extOrderId = $orderReference->getExtOrderId();
  335. } catch (LocalizedException $e) {
  336. $this->logger->critical($e->getMessage(), ['exception' => $e]);
  337. $extOrderId = '';
  338. }
  339. return $extOrderId;
  340. }
  341. /**
  342. * @return string
  343. */
  344. public function getShipmentViewPageUrl(): string
  345. {
  346. return $this->urlBuilder->getUrl('sales/shipment/view', ['shipment_id' => '--id--']);
  347. }
  348. /**
  349. * @return string
  350. */
  351. public function getDefaultCurrency(): string
  352. {
  353. $order = $this->getOrder();
  354. return $order->getBaseCurrencyCode();
  355. }
  356. /**
  357. * @return string
  358. */
  359. public function getDefaultDimensionsUnit(): string
  360. {
  361. $weightUnit = $this->getDefaultWeightUnit();
  362. if ($weightUnit === 'lbs') {
  363. return 'in';
  364. }
  365. return 'cm';
  366. }
  367. /**
  368. * @return string
  369. */
  370. public function getDefaultWeightUnit(): string
  371. {
  372. $order = $this->getOrder();
  373. $weightUnit = $this->scopeConfig->getValue(
  374. DirectoryHelper::XML_PATH_WEIGHT_UNIT,
  375. ScopeInterface::SCOPE_STORE,
  376. $order->getStore()->getCode()
  377. );
  378. return $weightUnit;
  379. }
  380. /**
  381. * @return string
  382. */
  383. public function getConfigUrl(): string
  384. {
  385. return $this->urlBuilder->getUrl('adminhtml/system_config/edit', [
  386. 'section' => 'carriers',
  387. '_fragment' => 'carriers_temando-link',
  388. ]);
  389. }
  390. /**
  391. * Check if a shipment was registered for component rendering.
  392. * @see \Temando\Shipping\Plugin\Shipping\Order\ShipmentLoaderPlugin::afterLoad
  393. *
  394. * @return bool
  395. */
  396. public function hasSalesShipment(): bool
  397. {
  398. /** @var \Magento\Sales\Model\Order\Shipment $shipment */
  399. return (bool) $this->shipmentProvider->getSalesShipment();
  400. }
  401. }