Passed
Push — trunk ( 2d6001...da76dd )
by Christian
15:37 queued 13s
created

OrderConverter   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 367
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 201
c 0
b 0
f 0
dl 0
loc 367
rs 8.64
wmc 47

5 Methods

Rating   Name   Duplication   Size   Complexity  
A convertToCart() 0 37 4
B convertDeliveries() 0 68 10
F assembleSalesChannelContext() 0 79 16
A __construct() 0 18 1
F convertToOrder() 0 103 16

How to fix   Complexity   

Complex Class

Complex classes like OrderConverter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OrderConverter, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Checkout\Cart\Order;
4
5
use Shopware\Core\Checkout\Cart\Cart;
6
use Shopware\Core\Checkout\Cart\CartException;
7
use Shopware\Core\Checkout\Cart\Delivery\DeliveryProcessor;
8
use Shopware\Core\Checkout\Cart\Delivery\Struct\Delivery;
9
use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryCollection;
10
use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryDate;
11
use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryPosition;
12
use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryPositionCollection;
13
use Shopware\Core\Checkout\Cart\Delivery\Struct\ShippingLocation;
14
use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection;
15
use Shopware\Core\Checkout\Cart\Order\Transformer\AddressTransformer;
16
use Shopware\Core\Checkout\Cart\Order\Transformer\CartTransformer;
17
use Shopware\Core\Checkout\Cart\Order\Transformer\CustomerTransformer;
18
use Shopware\Core\Checkout\Cart\Order\Transformer\DeliveryTransformer;
19
use Shopware\Core\Checkout\Cart\Order\Transformer\LineItemTransformer;
20
use Shopware\Core\Checkout\Cart\Order\Transformer\TransactionTransformer;
21
use Shopware\Core\Checkout\Customer\CustomerEntity;
0 ignored issues
show
Bug introduced by
The type Shopware\Core\Checkout\Customer\CustomerEntity was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Shopware\Core\Checkout\Customer\Exception\AddressNotFoundException;
23
use Shopware\Core\Checkout\Order\Aggregate\OrderAddress\OrderAddressEntity;
0 ignored issues
show
Bug introduced by
The type Shopware\Core\Checkout\O...ress\OrderAddressEntity was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryCollection;
25
use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryStates;
26
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates;
27
use Shopware\Core\Checkout\Order\Exception\DeliveryWithoutAddressException;
28
use Shopware\Core\Checkout\Order\OrderDefinition;
29
use Shopware\Core\Checkout\Order\OrderEntity;
0 ignored issues
show
Bug introduced by
The type Shopware\Core\Checkout\Order\OrderEntity was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
use Shopware\Core\Checkout\Order\OrderException;
31
use Shopware\Core\Checkout\Order\OrderStates;
32
use Shopware\Core\Checkout\Promotion\Cart\PromotionCollector;
33
use Shopware\Core\Content\Product\Cart\ProductCartProcessor;
34
use Shopware\Core\Framework\Context;
35
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
36
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
37
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
38
use Shopware\Core\Framework\Uuid\Uuid;
39
use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface;
40
use Shopware\Core\System\SalesChannel\Context\AbstractSalesChannelContextFactory;
41
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
42
use Shopware\Core\System\SalesChannel\SalesChannelContext;
43
use Shopware\Core\System\StateMachine\Loader\InitialStateIdLoader;
44
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
45
46
/**
47
 * @package checkout
48
 */
49
class OrderConverter
50
{
51
    public const CART_CONVERTED_TO_ORDER_EVENT = 'cart.convertedToOrder.event';
52
53
    public const CART_TYPE = 'recalculation';
54
55
    public const ORIGINAL_ID = 'originalId';
56
57
    public const ORIGINAL_ORDER_NUMBER = 'originalOrderNumber';
58
59
    public const ORIGINAL_DOWNLOADS = 'originalDownloads';
60
61
    public const ADMIN_EDIT_ORDER_PERMISSIONS = [
62
        ProductCartProcessor::ALLOW_PRODUCT_PRICE_OVERWRITES => true,
63
        ProductCartProcessor::SKIP_PRODUCT_RECALCULATION => true,
64
        DeliveryProcessor::SKIP_DELIVERY_PRICE_RECALCULATION => true,
65
        DeliveryProcessor::SKIP_DELIVERY_TAX_RECALCULATION => true,
66
        PromotionCollector::SKIP_PROMOTION => true,
67
        ProductCartProcessor::SKIP_PRODUCT_STOCK_VALIDATION => true,
68
        ProductCartProcessor::KEEP_INACTIVE_PRODUCT => true,
69
    ];
70
71
    protected EntityRepository $customerRepository;
72
73
    protected AbstractSalesChannelContextFactory $salesChannelContextFactory;
74
75
    protected EventDispatcherInterface $eventDispatcher;
76
77
    private NumberRangeValueGeneratorInterface $numberRangeValueGenerator;
78
79
    private OrderDefinition $orderDefinition;
80
81
    private EntityRepository $orderAddressRepository;
82
83
    private InitialStateIdLoader $initialStateIdLoader;
84
85
    private LineItemDownloadLoader $downloadLoader;
86
87
    /**
88
     * @internal
89
     */
90
    public function __construct(
91
        EntityRepository $customerRepository,
92
        AbstractSalesChannelContextFactory $salesChannelContextFactory,
93
        EventDispatcherInterface $eventDispatcher,
94
        NumberRangeValueGeneratorInterface $numberRangeValueGenerator,
95
        OrderDefinition $orderDefinition,
96
        EntityRepository $orderAddressRepository,
97
        InitialStateIdLoader $initialStateIdLoader,
98
        LineItemDownloadLoader $downloadLoader
99
    ) {
100
        $this->customerRepository = $customerRepository;
101
        $this->salesChannelContextFactory = $salesChannelContextFactory;
102
        $this->eventDispatcher = $eventDispatcher;
103
        $this->numberRangeValueGenerator = $numberRangeValueGenerator;
104
        $this->orderDefinition = $orderDefinition;
105
        $this->orderAddressRepository = $orderAddressRepository;
106
        $this->initialStateIdLoader = $initialStateIdLoader;
107
        $this->downloadLoader = $downloadLoader;
108
    }
109
110
    /**
111
     * @throws DeliveryWithoutAddressException
112
     *
113
     * @return array<string, mixed|float|string|array<int, array<string, string|int|bool|mixed>>|null>
114
     */
115
    public function convertToOrder(Cart $cart, SalesChannelContext $context, OrderConversionContext $conversionContext): array
116
    {
117
        foreach ($cart->getDeliveries() as $delivery) {
118
            if ($delivery->getLocation()->getAddress() !== null || $delivery->hasExtensionOfType(self::ORIGINAL_ID, IdStruct::class)) {
119
                continue;
120
            }
121
122
            throw new DeliveryWithoutAddressException();
123
        }
124
        $data = CartTransformer::transform(
125
            $cart,
126
            $context,
127
            $this->initialStateIdLoader->get(OrderStates::STATE_MACHINE),
128
            $conversionContext->shouldIncludeOrderDate()
129
        );
130
131
        if ($conversionContext->shouldIncludeCustomer()) {
132
            $customer = $context->getCustomer();
133
            if ($customer === null) {
134
                throw CartException::customerNotLoggedIn();
135
            }
136
137
            $data['orderCustomer'] = CustomerTransformer::transform($customer);
138
        }
139
140
        $data['languageId'] = $context->getLanguageId();
141
142
        $convertedLineItems = LineItemTransformer::transformCollection($cart->getLineItems());
143
        $shippingAddresses = [];
144
145
        if ($conversionContext->shouldIncludeDeliveries()) {
146
            $shippingAddresses = AddressTransformer::transformCollection($cart->getDeliveries()->getAddresses(), true);
147
            $data['deliveries'] = DeliveryTransformer::transformCollection(
148
                $cart->getDeliveries(),
149
                $convertedLineItems,
150
                $this->initialStateIdLoader->get(OrderDeliveryStates::STATE_MACHINE),
151
                $context->getContext(),
152
                $shippingAddresses
153
            );
154
        }
155
156
        if ($conversionContext->shouldIncludeBillingAddress()) {
157
            $customer = $context->getCustomer();
158
            if ($customer === null) {
159
                throw CartException::customerNotLoggedIn();
160
            }
161
162
            $activeBillingAddress = $customer->getActiveBillingAddress();
163
            if ($activeBillingAddress === null) {
164
                throw new AddressNotFoundException('');
165
            }
166
            $customerAddressId = $activeBillingAddress->getId();
167
168
            if (\array_key_exists($customerAddressId, $shippingAddresses)) {
169
                $billingAddressId = $shippingAddresses[$customerAddressId]['id'];
170
            } else {
171
                $billingAddress = AddressTransformer::transform($activeBillingAddress);
172
                $data['addresses'] = [$billingAddress];
173
                $billingAddressId = $billingAddress['id'];
174
            }
175
            $data['billingAddressId'] = $billingAddressId;
176
        }
177
178
        if ($conversionContext->shouldIncludeTransactions()) {
179
            $data['transactions'] = TransactionTransformer::transformCollection(
180
                $cart->getTransactions(),
181
                $this->initialStateIdLoader->get(OrderTransactionStates::STATE_MACHINE),
182
                $context->getContext()
183
            );
184
        }
185
186
        $data['lineItems'] = array_values($convertedLineItems);
187
188
        foreach ($this->downloadLoader->load($data['lineItems'], $context->getContext()) as $key => $downloads) {
189
            if (!\array_key_exists($key, $data['lineItems'])) {
190
                continue;
191
            }
192
193
            $data['lineItems'][$key]['downloads'] = $downloads;
194
        }
195
196
        /** @var IdStruct|null $idStruct */
197
        $idStruct = $cart->getExtensionOfType(self::ORIGINAL_ID, IdStruct::class);
198
        $data['id'] = $idStruct ? $idStruct->getId() : Uuid::randomHex();
0 ignored issues
show
introduced by
$idStruct is of type Shopware\Core\Checkout\Cart\Order\IdStruct, thus it always evaluated to true.
Loading history...
199
200
        /** @var IdStruct|null $orderNumberStruct */
201
        $orderNumberStruct = $cart->getExtensionOfType(self::ORIGINAL_ORDER_NUMBER, IdStruct::class);
202
        if ($orderNumberStruct !== null) {
203
            $data['orderNumber'] = $orderNumberStruct->getId();
204
        } else {
205
            $data['orderNumber'] = $this->numberRangeValueGenerator->getValue(
206
                $this->orderDefinition->getEntityName(),
207
                $context->getContext(),
208
                $context->getSalesChannel()->getId()
209
            );
210
        }
211
212
        $data['ruleIds'] = $context->getRuleIds();
213
214
        $event = new CartConvertedEvent($cart, $data, $context, $conversionContext);
215
        $this->eventDispatcher->dispatch($event);
216
217
        return $event->getConvertedCart();
218
    }
219
220
    /**
221
     * @throws CartException
222
     */
223
    public function convertToCart(OrderEntity $order, Context $context): Cart
224
    {
225
        if ($order->getLineItems() === null) {
226
            throw OrderException::missingAssociation('lineItems');
227
        }
228
229
        if ($order->getDeliveries() === null) {
230
            throw OrderException::missingAssociation('deliveries');
231
        }
232
233
        $cart = new Cart(self::CART_TYPE, Uuid::randomHex());
234
        $cart->setPrice($order->getPrice());
235
        $cart->setCustomerComment($order->getCustomerComment());
236
        $cart->setAffiliateCode($order->getAffiliateCode());
237
        $cart->setCampaignCode($order->getCampaignCode());
238
        $cart->addExtension(self::ORIGINAL_ID, new IdStruct($order->getId()));
239
        $orderNumber = $order->getOrderNumber();
240
        if ($orderNumber === null) {
241
            throw OrderException::missingOrderNumber($order->getId());
242
        }
243
244
        $cart->addExtension(self::ORIGINAL_ORDER_NUMBER, new IdStruct($orderNumber));
245
        /* NEXT-708 support:
246
            - transactions
247
        */
248
249
        $lineItems = LineItemTransformer::transformFlatToNested($order->getLineItems());
250
251
        $cart->addLineItems($lineItems);
252
        $cart->setDeliveries(
253
            $this->convertDeliveries($order->getDeliveries(), $lineItems)
254
        );
255
256
        $event = new OrderConvertedEvent($order, $cart, $context);
257
        $this->eventDispatcher->dispatch($event);
258
259
        return $event->getConvertedCart();
260
    }
261
262
    /**
263
     * @param array<string, array<string, bool>|string> $overrideOptions
264
     *
265
     * @throws InconsistentCriteriaIdsException
266
     */
267
    public function assembleSalesChannelContext(OrderEntity $order, Context $context, array $overrideOptions = []): SalesChannelContext
268
    {
269
        if ($order->getTransactions() === null) {
270
            throw OrderException::missingAssociation('transactions');
271
        }
272
        if ($order->getOrderCustomer() === null) {
273
            throw OrderException::missingAssociation('orderCustomer');
274
        }
275
276
        $customerId = $order->getOrderCustomer()->getCustomerId();
277
        $customerGroupId = null;
278
279
        if ($customerId) {
280
            /** @var CustomerEntity|null $customer */
281
            $customer = $this->customerRepository->search(new Criteria([$customerId]), $context)->get($customerId);
282
            if ($customer !== null) {
283
                $customerGroupId = $customer->getGroupId();
284
            }
285
        }
286
287
        $billingAddressId = $order->getBillingAddressId();
288
        /** @var OrderAddressEntity|null $billingAddress */
289
        $billingAddress = $this->orderAddressRepository->search(new Criteria([$billingAddressId]), $context)->get($billingAddressId);
290
        if ($billingAddress === null) {
291
            throw new AddressNotFoundException($billingAddressId);
292
        }
293
294
        $options = [
295
            SalesChannelContextService::CURRENCY_ID => $order->getCurrencyId(),
296
            SalesChannelContextService::LANGUAGE_ID => $order->getLanguageId(),
297
            SalesChannelContextService::CUSTOMER_ID => $customerId,
298
            SalesChannelContextService::COUNTRY_STATE_ID => $billingAddress->getCountryStateId(),
299
            SalesChannelContextService::CUSTOMER_GROUP_ID => $customerGroupId,
300
            SalesChannelContextService::PERMISSIONS => self::ADMIN_EDIT_ORDER_PERMISSIONS,
301
            SalesChannelContextService::VERSION_ID => $context->getVersionId(),
302
        ];
303
304
        $delivery = $order->getDeliveries() !== null ? $order->getDeliveries()->first() : null;
305
        if ($delivery !== null) {
306
            $options[SalesChannelContextService::SHIPPING_METHOD_ID] = $delivery->getShippingMethodId();
307
        }
308
309
        //get the first not paid transaction or, if all paid, the last transaction
310
        if ($order->getTransactions() !== null) {
311
            foreach ($order->getTransactions() as $transaction) {
312
                $options[SalesChannelContextService::PAYMENT_METHOD_ID] = $transaction->getPaymentMethodId();
313
                if (
314
                    $transaction->getStateMachineState() !== null
315
                    && $transaction->getStateMachineState()->getTechnicalName() !== OrderTransactionStates::STATE_PAID
316
                ) {
317
                    break;
318
                }
319
            }
320
        }
321
322
        $options = array_merge($options, $overrideOptions);
323
324
        $salesChannelContext = $this->salesChannelContextFactory->create(Uuid::randomHex(), $order->getSalesChannelId(), $options);
325
        $salesChannelContext->getContext()->addExtensions($context->getExtensions());
326
        $salesChannelContext->addState(...$context->getStates());
327
        $salesChannelContext->setTaxState($order->getTaxStatus());
328
329
        if ($context->hasState(Context::SKIP_TRIGGER_FLOW)) {
330
            $salesChannelContext->getContext()->addState(Context::SKIP_TRIGGER_FLOW);
331
        }
332
333
        if ($order->getItemRounding() !== null) {
334
            $salesChannelContext->setItemRounding($order->getItemRounding());
335
        }
336
337
        if ($order->getTotalRounding() !== null) {
338
            $salesChannelContext->setTotalRounding($order->getTotalRounding());
339
        }
340
341
        if ($order->getRuleIds() !== null) {
342
            $salesChannelContext->setRuleIds($order->getRuleIds());
343
        }
344
345
        return $salesChannelContext;
346
    }
347
348
    private function convertDeliveries(OrderDeliveryCollection $orderDeliveries, LineItemCollection $lineItems): DeliveryCollection
349
    {
350
        $cartDeliveries = new DeliveryCollection();
351
352
        foreach ($orderDeliveries as $orderDelivery) {
353
            $deliveryDate = new DeliveryDate(
354
                $orderDelivery->getShippingDateEarliest(),
355
                $orderDelivery->getShippingDateLatest()
356
            );
357
358
            $deliveryPositions = new DeliveryPositionCollection();
359
360
            if ($orderDelivery->getPositions() === null) {
361
                continue;
362
            }
363
364
            foreach ($orderDelivery->getPositions() as $position) {
365
                if ($position->getOrderLineItem() === null) {
366
                    continue;
367
                }
368
369
                $identifier = $position->getOrderLineItem()->getIdentifier();
370
371
                // line item has been removed and will not be added to delivery
372
                if ($lineItems->get($identifier) === null) {
373
                    continue;
374
                }
375
376
                if ($position->getPrice() === null) {
377
                    continue;
378
                }
379
380
                $deliveryPosition = new DeliveryPosition(
381
                    $identifier,
382
                    $lineItems->get($identifier),
0 ignored issues
show
Bug introduced by
It seems like $lineItems->get($identifier) can also be of type null; however, parameter $lineItem of Shopware\Core\Checkout\C...Position::__construct() does only seem to accept Shopware\Core\Checkout\Cart\LineItem\LineItem, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

382
                    /** @scrutinizer ignore-type */ $lineItems->get($identifier),
Loading history...
383
                    $position->getPrice()->getQuantity(),
384
                    $position->getPrice(),
385
                    $deliveryDate
386
                );
387
                $deliveryPosition->addExtension(self::ORIGINAL_ID, new IdStruct($position->getId()));
388
389
                $deliveryPositions->add($deliveryPosition);
390
            }
391
392
            if ($orderDelivery->getShippingMethod() === null
393
                || $orderDelivery->getShippingOrderAddress() === null
394
                || $orderDelivery->getShippingOrderAddress()->getCountry() === null
395
            ) {
396
                continue;
397
            }
398
399
            $cartDelivery = new Delivery(
400
                $deliveryPositions,
401
                $deliveryDate,
402
                $orderDelivery->getShippingMethod(),
403
                new ShippingLocation(
404
                    $orderDelivery->getShippingOrderAddress()->getCountry(),
405
                    $orderDelivery->getShippingOrderAddress()->getCountryState(),
406
                    null
407
                ),
408
                $orderDelivery->getShippingCosts()
409
            );
410
            $cartDelivery->addExtension(self::ORIGINAL_ID, new IdStruct($orderDelivery->getId()));
411
412
            $cartDeliveries->add($cartDelivery);
413
        }
414
415
        return $cartDeliveries;
416
    }
417
}
418