ShippingProcessor.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <?php
  2. /**
  3. * @copyright Vertex. All rights reserved. https://www.vertexinc.com/
  4. * @author Mediotype https://www.mediotype.com/
  5. */
  6. namespace Vertex\Tax\Model\Api\Data\InvoiceRequestBuilder;
  7. use Magento\Sales\Api\Data\OrderExtensionInterface;
  8. use Magento\Sales\Api\Data\OrderInterface;
  9. use Magento\Sales\Api\Data\ShippingAssignmentInterface;
  10. use Magento\Sales\Api\Data\ShippingInterface;
  11. use Magento\Sales\Api\Data\TotalInterface;
  12. use Magento\Sales\Api\OrderRepositoryInterface;
  13. use Vertex\Data\LineItemInterface;
  14. use Vertex\Data\LineItemInterfaceFactory;
  15. use Vertex\Tax\Model\Config;
  16. use Vertex\Tax\Model\Repository\TaxClassNameRepository;
  17. /**
  18. * Processes shipping data for Invoices and Creditmemos
  19. */
  20. class ShippingProcessor
  21. {
  22. /** @var TaxClassNameRepository */
  23. private $classNameRepository;
  24. /** @var Config */
  25. private $config;
  26. /** @var LineItemInterfaceFactory */
  27. private $lineItemFactory;
  28. /** @var OrderRepositoryInterface */
  29. private $orderRepository;
  30. /**
  31. * @param OrderRepositoryInterface $orderRepository
  32. * @param Config $config
  33. * @param TaxClassNameRepository $classNameRepository
  34. * @param LineItemInterfaceFactory $lineItemFactory
  35. */
  36. public function __construct(
  37. OrderRepositoryInterface $orderRepository,
  38. Config $config,
  39. TaxClassNameRepository $classNameRepository,
  40. LineItemInterfaceFactory $lineItemFactory
  41. ) {
  42. $this->orderRepository = $orderRepository;
  43. $this->config = $config;
  44. $this->classNameRepository = $classNameRepository;
  45. $this->lineItemFactory = $lineItemFactory;
  46. }
  47. /**
  48. * Retrieve line items for the shipping methods invoiced or credited against an Order
  49. *
  50. * In Magento, an Invoice does not know which shipping methods it is charging
  51. * to the customer, only that it is - in fact - charging the customer some
  52. * amount of shipping. Magento core tax does not care, as there is only one
  53. * tax class for all shipping methods - however, in an environment like
  54. * Vertex, each shipping method can theoretically have it's own tax. This
  55. * matters in Magento, because each Order can theoretically have multiple
  56. * shipping methods.
  57. *
  58. * Thus, we invoice each shipment the percentage it was of the total cost.
  59. *
  60. * This will work in the vast majority of use-cases, but can be incorrect
  61. * if there are large tax differences between shipping methods and an order
  62. * is never fully invoiced.
  63. *
  64. * @param int $orderId
  65. * @param float $totalShipmentCost
  66. * @return LineItemInterface[]
  67. */
  68. public function getShippingLineItems($orderId, $totalShipmentCost)
  69. {
  70. $order = $this->orderRepository->get($orderId);
  71. $extensionAttributes = $order->getExtensionAttributes();
  72. if ($extensionAttributes === null || !$extensionAttributes instanceof OrderExtensionInterface) {
  73. return [];
  74. }
  75. /** @var ShippingAssignmentInterface[]|null $shippingAssignments */
  76. $shippingAssignments = $extensionAttributes->getShippingAssignments();
  77. if ($shippingAssignments === null) {
  78. return [];
  79. }
  80. /** @var float $orderAmount The total cost of shipping on the order */
  81. $orderAmount = 0;
  82. /** @var float[string] $shippingCosts Cost of each shipping method, indexed by identifier */
  83. $shippingCosts = [];
  84. foreach ($shippingAssignments as $shippingAssignment) {
  85. // This just gathers those variables
  86. $shipping = $shippingAssignment->getShipping();
  87. if ($shipping === null || !$shipping instanceof ShippingInterface) {
  88. continue;
  89. }
  90. $total = $shipping->getTotal();
  91. if ($total === null || !$total instanceof TotalInterface) {
  92. continue;
  93. }
  94. $cost = $total->getBaseShippingAmount() - $total->getBaseShippingDiscountAmount();
  95. $orderAmount += $cost;
  96. $shippingCosts[$shipping->getMethod()] = $cost;
  97. }
  98. return $this->buildLineItems($totalShipmentCost, $shippingCosts, $orderAmount, $order);
  99. }
  100. /**
  101. * Create LineItems by calculating the value based on the total costs
  102. *
  103. * @param float $totalShipmentCost
  104. * @param float[] $shippingCosts Cost of each shipping method indexed by identifier
  105. * @param float $orderAmount
  106. * @param OrderInterface $order
  107. * @return array
  108. */
  109. private function buildLineItems($totalShipmentCost, $shippingCosts, $orderAmount, $order)
  110. {
  111. if ($orderAmount == 0) {
  112. return [];
  113. }
  114. // Pre-fetch the shipping tax class since all shipment types have the same one
  115. $taxClassId = $this->config->getShippingTaxClassId($order->getStoreId());
  116. $productClass = $this->classNameRepository->getById($taxClassId);
  117. $lineItems = [];
  118. foreach ($shippingCosts as $method => $cost) {
  119. $percentage = $cost / $orderAmount; // as a decimal
  120. $invoicedCost = round($totalShipmentCost * $percentage, 2);
  121. if ($invoicedCost == 0) {
  122. continue;
  123. }
  124. /** @var LineItemInterface $lineItem */
  125. $lineItem = $this->lineItemFactory->create();
  126. $lineItem->setProductCode($method);
  127. $lineItem->setProductClass($productClass);
  128. $lineItem->setUnitPrice($invoicedCost);
  129. $lineItem->setQuantity(1);
  130. $lineItem->setExtendedPrice($invoicedCost);
  131. $lineItems[] = $lineItem;
  132. }
  133. return $lineItems;
  134. }
  135. }