createAvataxTransactionBuilder()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 15
c 1
b 1
f 0
nc 2
nop 1
dl 0
loc 27
rs 9.7666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Odiseo\SyliusAvataxPlugin\Taxation\Applicator;
6
7
use Avalara\DocumentType;
8
use Avalara\TransactionAddressType;
9
use Avalara\TransactionBuilder;
10
use Odiseo\SyliusAvataxPlugin\Api\AvataxClient;
11
use Odiseo\SyliusAvataxPlugin\Entity\AvataxConfigurationSenderDataInterface;
12
use Odiseo\SyliusAvataxPlugin\Provider\EnabledAvataxConfigurationProviderInterface;
13
use Odiseo\SyliusAvataxPlugin\Resolver\OrderItemAvataxCodeResolverInterface;
14
use Odiseo\SyliusAvataxPlugin\Resolver\ShippingAvataxCodeResolverInterface;
15
use Sylius\Component\Addressing\Matcher\ZoneMatcherInterface;
16
use Sylius\Component\Addressing\Model\ZoneInterface;
17
use Sylius\Component\Core\Model\AddressInterface;
18
use Sylius\Component\Core\Model\AdjustmentInterface;
19
use Sylius\Component\Core\Model\OrderInterface;
20
use Sylius\Component\Core\Model\OrderItemInterface;
21
use Sylius\Component\Core\Model\ProductVariantInterface;
22
use Sylius\Component\Core\Model\Scope;
23
use Sylius\Component\Core\Provider\ZoneProviderInterface;
24
use Sylius\Component\Core\Taxation\Applicator\OrderTaxesApplicatorInterface;
25
use Sylius\Component\Order\Factory\AdjustmentFactoryInterface;
26
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
27
28
final class OrderAvataxTaxesApplicator implements OrderTaxesApplicatorInterface
29
{
30
    /** @var AvataxClient */
31
    private $avataxClient;
32
33
    /** @var AdjustmentFactoryInterface */
34
    private $adjustmentFactory;
35
36
    /** @var ZoneProviderInterface */
37
    private $defaultTaxZoneProvider;
38
39
    /** @var ZoneMatcherInterface */
40
    private $zoneMatcher;
41
42
    /** @var EnabledAvataxConfigurationProviderInterface */
43
    private $enabledAvataxConfigurationProvider;
44
45
    /** @var OrderItemAvataxCodeResolverInterface */
46
    private $orderItemAvataxCodeResolver;
47
48
    /** @var ShippingAvataxCodeResolverInterface */
49
    private $shippingAvataxCodeResolver;
50
51
    public function __construct(
52
        AvataxClient $avaTaxClient,
53
        AdjustmentFactoryInterface $adjustmentFactory,
54
        ZoneProviderInterface $defaultTaxZoneProvider,
55
        ZoneMatcherInterface $zoneMatcher,
56
        EnabledAvataxConfigurationProviderInterface $enabledAvataxConfigurationProvider,
57
        OrderItemAvataxCodeResolverInterface $orderItemAvataxCodeResolver,
58
        ShippingAvataxCodeResolverInterface $shippingAvataxCodeResolver
59
    ) {
60
        $this->avataxClient = $avaTaxClient;
61
        $this->adjustmentFactory = $adjustmentFactory;
62
        $this->defaultTaxZoneProvider = $defaultTaxZoneProvider;
63
        $this->zoneMatcher = $zoneMatcher;
64
        $this->enabledAvataxConfigurationProvider = $enabledAvataxConfigurationProvider;
65
        $this->orderItemAvataxCodeResolver = $orderItemAvataxCodeResolver;
66
        $this->shippingAvataxCodeResolver = $shippingAvataxCodeResolver;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function apply(OrderInterface $order, ZoneInterface $zone): void
73
    {
74
        if (!$this->matchTaxZone($order)) {
75
            return;
76
        }
77
78
        $taxes = $this->createAvataxTransactionBuilder($order)->create();
79
        if (!isset($taxes->lines)) {
80
            /** @var string $message */
81
            $message = $taxes;
82
83
            throw new BadRequestHttpException($message);
84
        }
85
86
        foreach ($taxes->lines as $line) {
87
            if ('shipping' === $line->itemCode) {
88
                /** @var AdjustmentInterface $shippingTaxAdjustment */
89
                $shippingTaxAdjustment = $this->adjustmentFactory
90
                    ->createWithData(AdjustmentInterface::TAX_ADJUSTMENT, 'shipping_tax', intval($line->taxCalculated*100), false)
91
                ;
92
                $order->addAdjustment($shippingTaxAdjustment);
93
            } else {
94
                $matchItems = $order->getItems()->filter(function (OrderItemInterface $item) use ($line): bool {
95
                    /** @var ProductVariantInterface $variant */
96
                    $variant = $item->getVariant();
97
98
                    return $line->itemCode === $variant->getCode();
99
                });
100
101
                if (count($matchItems) > 0) {
102
                    /** @var OrderItemInterface $matchItem */
103
                    $matchItem = $matchItems->first();
104
105
                    foreach ($matchItem->getUnits() as $unit) {
106
                        $unitTaxAdjustment = $this->adjustmentFactory
107
                            ->createWithData(AdjustmentInterface::TAX_ADJUSTMENT, 'item_tax', intval($line->taxCalculated*100), false);
108
109
                        $unit->addAdjustment($unitTaxAdjustment);
110
                    }
111
                }
112
            }
113
        }
114
    }
115
116
    /**
117
     * @param OrderInterface $order
118
     * @return TransactionBuilder
119
     */
120
    private function createAvataxTransactionBuilder(OrderInterface $order): TransactionBuilder
121
    {
122
        $transactionBuilder = $this->createAvataxBaseTransactionBuilder($order);
123
124
        foreach ($order->getItems() as $item) {
125
            $quantity = $item->getQuantity();
126
            /** @var ProductVariantInterface $variant */
127
            $variant = $item->getVariant();
128
129
            $transactionBuilder->withLine(
130
                $item->getTotal() / 100,
131
                $quantity,
132
                (string) $variant->getCode(),
133
                $this->orderItemAvataxCodeResolver->getTaxCode($item)
134
            );
135
        }
136
137
        $transactionBuilder->withLine(
138
            $order->getShippingTotal() / 100,
139
            1,
140
            'shipping',
141
            $this->shippingAvataxCodeResolver->getTaxCode(
142
                $this->enabledAvataxConfigurationProvider->getConfiguration()
143
            )
144
        );
145
146
        return $transactionBuilder;
147
    }
148
149
    /**
150
     * @param OrderInterface $order
151
     * @return TransactionBuilder
152
     */
153
    private function createAvataxBaseTransactionBuilder(OrderInterface $order): TransactionBuilder
154
    {
155
        $customer = $order->getCustomer();
156
157
        $customerCode = $customer !== null ? $customer->getEmail() : 'DEFAULT_CUSTOMER_CODE';
158
159
        $transactionBuilder = new TransactionBuilder(
160
            $this->avataxClient,
161
            'DEFAULT',
162
            (string) DocumentType::C_SALESINVOICE,
163
            (string) $customerCode
164
        );
165
166
        $transactionBuilder->withCurrencyCode((string) $order->getCurrencyCode());
167
168
        $avataxConfiguration = $this->enabledAvataxConfigurationProvider->getConfiguration();
169
        $senderData = $avataxConfiguration->getSenderData();
170
171
        if ($senderData !== null && $this->isValidAddress($senderData)) {
172
            $transactionBuilder->withAddress(
173
                TransactionAddressType::C_SHIPFROM,
174
                $senderData->getStreet(),
175
                null,
176
                null,
177
                $senderData->getCity(),
178
                $senderData->getProvinceCode(),
179
                $senderData->getPostcode(),
180
                $senderData->getCountryCode()
181
            );
182
        }
183
184
        /** @var AddressInterface $shippingAddress */
185
        $shippingAddress = $order->getShippingAddress();
186
187
        $provinceCode = $shippingAddress->getProvinceName();
188
        if ($provinceCode === null) {
189
            $provinceCode = substr((string) $shippingAddress->getProvinceCode(), 3, 2);
190
        }
191
192
        $transactionBuilder->withAddress(
193
            TransactionAddressType::C_SHIPTO,
194
            $shippingAddress->getStreet(),
195
            null,
196
            null,
197
            $shippingAddress->getCity(),
198
            $provinceCode,
199
            $shippingAddress->getPostcode(),
200
            $shippingAddress->getCountryCode()
201
        );
202
203
        return $transactionBuilder;
204
    }
205
206
    /**
207
     * @param OrderInterface $order
208
     * @return bool
209
     */
210
    private function matchTaxZone(OrderInterface $order): bool
211
    {
212
        $billingAddress = $order->getBillingAddress();
213
        $zones = [];
214
215
        if (null !== $billingAddress) {
216
            $zones = $this->zoneMatcher->matchAll($billingAddress, Scope::TAX);
217
        } else {
218
            $zones[] = $this->defaultTaxZoneProvider->getZone($order);
219
        }
220
221
        if (count($zones) === 0) {
222
            return false;
223
        }
224
225
        $avataxConfiguration = $this->enabledAvataxConfigurationProvider->getConfiguration();
226
        if (!in_array($avataxConfiguration->getZone(), $zones, true)) {
227
            return false;
228
        }
229
230
        return true;
231
    }
232
233
    /**
234
     * @param AvataxConfigurationSenderDataInterface $senderData
235
     * @return bool
236
     */
237
    private function isValidAddress(AvataxConfigurationSenderDataInterface $senderData): bool
238
    {
239
        return null !== $senderData->getStreet() && null !== $senderData->getCity() && null !== $senderData->getProvinceCode() &&
240
            null !== $senderData->getPostcode() && null !== $senderData->getCountryCode();
241
    }
242
}
243