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