Passed
Push — trunk ( 9b2ebb...065c18 )
by Christian
10:35 queued 13s
created

OrderConverter::convertToCart()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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

368
                    /** @scrutinizer ignore-type */ $lineItems->get($identifier),
Loading history...
369
                    $position->getPrice()->getQuantity(),
370
                    $position->getPrice(),
371
                    $deliveryDate
372
                );
373
                $deliveryPosition->addExtension(self::ORIGINAL_ID, new IdStruct($position->getId()));
374
375
                $deliveryPositions->add($deliveryPosition);
376
            }
377
378
            if ($orderDelivery->getShippingMethod() === null
379
                || $orderDelivery->getShippingOrderAddress() === null
380
                || $orderDelivery->getShippingOrderAddress()->getCountry() === null
381
            ) {
382
                continue;
383
            }
384
385
            $cartDelivery = new Delivery(
386
                $deliveryPositions,
387
                $deliveryDate,
388
                $orderDelivery->getShippingMethod(),
389
                new ShippingLocation(
390
                    $orderDelivery->getShippingOrderAddress()->getCountry(),
391
                    $orderDelivery->getShippingOrderAddress()->getCountryState(),
392
                    null
393
                ),
394
                $orderDelivery->getShippingCosts()
395
            );
396
            $cartDelivery->addExtension(self::ORIGINAL_ID, new IdStruct($orderDelivery->getId()));
397
398
            $cartDeliveries->add($cartDelivery);
399
        }
400
401
        return $cartDeliveries;
402
    }
403
}
404