Completed
Push — default-locale-code ( 1d8918 )
by Kamil
05:05
created

OrderFixture::generateInvalidSkipMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sylius\Bundle\CoreBundle\Fixture;
15
16
use Doctrine\Common\Persistence\ObjectManager;
17
use SM\Factory\FactoryInterface as StateMachineFactoryInterface;
18
use Sylius\Bundle\FixturesBundle\Fixture\AbstractFixture;
19
use Sylius\Component\Core\Checker\OrderPaymentMethodSelectionRequirementCheckerInterface;
20
use Sylius\Component\Core\Checker\OrderShippingMethodSelectionRequirementCheckerInterface;
21
use Sylius\Component\Core\Model\AddressInterface;
22
use Sylius\Component\Core\Model\ChannelInterface;
23
use Sylius\Component\Core\Model\OrderInterface;
24
use Sylius\Component\Core\Model\OrderItemInterface;
25
use Sylius\Component\Core\Model\ProductInterface;
26
use Sylius\Component\Core\OrderCheckoutStates;
27
use Sylius\Component\Core\OrderCheckoutTransitions;
28
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
29
use Sylius\Component\Core\Repository\ShippingMethodRepositoryInterface;
30
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
31
use Sylius\Component\Resource\Factory\FactoryInterface;
32
use Sylius\Component\Resource\Repository\RepositoryInterface;
33
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
34
use Webmozart\Assert\Assert;
35
36
class OrderFixture extends AbstractFixture
37
{
38
    /** @var FactoryInterface */
39
    private $orderFactory;
40
41
    /** @var FactoryInterface */
42
    private $orderItemFactory;
43
44
    /** @var OrderItemQuantityModifierInterface */
45
    private $orderItemQuantityModifier;
46
47
    /** @var ObjectManager */
48
    private $orderManager;
49
50
    /** @var RepositoryInterface */
51
    private $channelRepository;
52
53
    /** @var RepositoryInterface */
54
    private $customerRepository;
55
56
    /** @var RepositoryInterface */
57
    private $productRepository;
58
59
    /** @var RepositoryInterface */
60
    private $countryRepository;
61
62
    /** @var PaymentMethodRepositoryInterface */
63
    private $paymentMethodRepository;
64
65
    /** @var ShippingMethodRepositoryInterface */
66
    private $shippingMethodRepository;
67
68
    /** @var FactoryInterface */
69
    private $addressFactory;
70
71
    /** @var StateMachineFactoryInterface */
72
    private $stateMachineFactory;
73
74
    /** @var OrderShippingMethodSelectionRequirementCheckerInterface */
75
    private $orderShippingMethodSelectionRequirementChecker;
76
77
    /** @var OrderPaymentMethodSelectionRequirementCheckerInterface */
78
    private $orderPaymentMethodSelectionRequirementChecker;
79
80
    /** @var \Faker\Generator */
81
    private $faker;
82
83
    public function __construct(
84
        FactoryInterface $orderFactory,
85
        FactoryInterface $orderItemFactory,
86
        OrderItemQuantityModifierInterface $orderItemQuantityModifier,
87
        ObjectManager $orderManager,
88
        RepositoryInterface $channelRepository,
89
        RepositoryInterface $customerRepository,
90
        RepositoryInterface $productRepository,
91
        RepositoryInterface $countryRepository,
92
        PaymentMethodRepositoryInterface $paymentMethodRepository,
93
        ShippingMethodRepositoryInterface $shippingMethodRepository,
94
        FactoryInterface $addressFactory,
95
        StateMachineFactoryInterface $stateMachineFactory,
96
        OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker,
97
        OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker
98
    ) {
99
        $this->orderFactory = $orderFactory;
100
        $this->orderItemFactory = $orderItemFactory;
101
        $this->orderItemQuantityModifier = $orderItemQuantityModifier;
102
        $this->orderManager = $orderManager;
103
        $this->channelRepository = $channelRepository;
104
        $this->customerRepository = $customerRepository;
105
        $this->productRepository = $productRepository;
106
        $this->countryRepository = $countryRepository;
107
        $this->paymentMethodRepository = $paymentMethodRepository;
108
        $this->shippingMethodRepository = $shippingMethodRepository;
109
        $this->addressFactory = $addressFactory;
110
        $this->stateMachineFactory = $stateMachineFactory;
111
        $this->orderShippingMethodSelectionRequirementChecker = $orderShippingMethodSelectionRequirementChecker;
112
        $this->orderPaymentMethodSelectionRequirementChecker = $orderPaymentMethodSelectionRequirementChecker;
113
114
        $this->faker = \Faker\Factory::create();
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function load(array $options): void
121
    {
122
        $channels = $this->channelRepository->findAll();
123
        $customers = $this->customerRepository->findAll();
124
        $countries = $this->countryRepository->findAll();
125
126
        $randomDates = $this->generateDates($options['amount']);
127
128
        for ($i = 0; $i < $options['amount']; ++$i) {
129
            $channel = $this->faker->randomElement($channels);
130
            $customer = $this->faker->randomElement($customers);
131
            $countryCode = $this->faker->randomElement($countries)->getCode();
132
133
            $currencyCode = $channel->getBaseCurrency()->getCode();
134
            $localeCode = $this->faker->randomElement($channel->getLocales()->toArray())->getCode();
135
136
            /** @var OrderInterface $order */
137
            $order = $this->orderFactory->createNew();
138
            $order->setChannel($channel);
139
            $order->setCustomer($customer);
140
            $order->setCurrencyCode($currencyCode);
141
            $order->setLocaleCode($localeCode);
142
143
            $this->generateItems($order);
144
145
            $this->address($order, $countryCode);
146
            $this->selectShipping($order);
147
            $this->selectPayment($order);
148
            $this->completeCheckout($order);
149
            $this->setOrderCompletedDate($order, $randomDates[$i]);
150
151
            $this->orderManager->persist($order);
152
153
            if (0 === ($i % 50)) {
154
                $this->orderManager->flush();
155
            }
156
        }
157
158
        $this->orderManager->flush();
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function getName(): string
165
    {
166
        return 'order';
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    protected function configureOptionsNode(ArrayNodeDefinition $optionsNode): void
173
    {
174
        $optionsNode
175
            ->children()
176
                ->integerNode('amount')->isRequired()->min(0)->end()
177
        ;
178
    }
179
180
    private function generateItems(OrderInterface $order): void
181
    {
182
        $numberOfItems = random_int(1, 5);
183
        $products = $this->productRepository->findAll();
184
        $generatedItems = [];
185
186
        for ($i = 0; $i < $numberOfItems; ++$i) {
187
            /** @var ProductInterface $product */
188
            $product = $this->faker->randomElement($products);
189
190
            if (!$product->hasChannel($order->getChannel())) {
191
                $i--;
192
                continue;
193
            }
194
195
            $variant = $this->faker->randomElement($product->getVariants()->toArray());
196
197
            if (array_key_exists($variant->getCode(), $generatedItems)) {
198
                /** @var OrderItemInterface $item */
199
                $item = $generatedItems[$variant->getCode()];
200
                $this->orderItemQuantityModifier->modify($item, $item->getQuantity() + random_int(1, 5));
201
202
                continue;
203
            }
204
205
            /** @var OrderItemInterface $item */
206
            $item = $this->orderItemFactory->createNew();
207
208
            $item->setVariant($variant);
209
            $this->orderItemQuantityModifier->modify($item, random_int(1, 5));
210
211
            $generatedItems[$variant->getCode()] = $item;
212
            $order->addItem($item);
213
        }
214
    }
215
216
    private function address(OrderInterface $order, string $countryCode): void
217
    {
218
        /** @var AddressInterface $address */
219
        $address = $this->addressFactory->createNew();
220
        $address->setFirstName($this->faker->firstName);
221
        $address->setLastName($this->faker->lastName);
222
        $address->setStreet($this->faker->streetAddress);
223
        $address->setCountryCode($countryCode);
224
        $address->setCity($this->faker->city);
225
        $address->setPostcode($this->faker->postcode);
226
227
        $order->setShippingAddress($address);
228
        $order->setBillingAddress(clone $address);
229
230
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_ADDRESS);
231
    }
232
233
    private function selectShipping(OrderInterface $order): void
234
    {
235
        if (!$this->orderShippingMethodSelectionRequirementChecker->isShippingMethodSelectionRequired($order)) {
236
            $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING);
237
238
            return;
239
        }
240
241
        $channel = $order->getChannel();
242
        $shippingMethods = $this->shippingMethodRepository->findEnabledForChannel($channel);
243
244
        if (count($shippingMethods) === 0) {
245
            throw new \InvalidArgumentException(sprintf(
246
                'You have no shipping method available for the channel with code "%s", but they are required to proceed an order',
247
                $channel->getCode()
248
            ));
249
        }
250
251
        $shippingMethod = $this->faker->randomElement($shippingMethods);
252
253
        /** @var ChannelInterface $channel */
254
        $channel = $order->getChannel();
255
        Assert::notNull($shippingMethod, $this->generateInvalidSkipMessage('shipping', $channel->getCode()));
256
257
        foreach ($order->getShipments() as $shipment) {
258
            $shipment->setMethod($shippingMethod);
259
        }
260
261
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
262
    }
263
264
    private function selectPayment(OrderInterface $order): void
265
    {
266
        if (!$this->orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)) {
267
            $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT);
268
269
            return;
270
        }
271
272
        $paymentMethod = $this
273
            ->faker
274
            ->randomElement($this->paymentMethodRepository->findEnabledForChannel($order->getChannel()))
275
        ;
276
277
        /** @var ChannelInterface $channel */
278
        $channel = $order->getChannel();
279
        Assert::notNull($paymentMethod, $this->generateInvalidSkipMessage('payment', $channel->getCode()));
280
281
        foreach ($order->getPayments() as $payment) {
282
            $payment->setMethod($paymentMethod);
283
        }
284
285
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
286
    }
287
288
    private function completeCheckout(OrderInterface $order): void
289
    {
290
        if ($this->faker->boolean(25)) {
291
            $order->setNotes($this->faker->sentence);
292
        }
293
294
        $this->applyCheckoutStateTransition($order, OrderCheckoutTransitions::TRANSITION_COMPLETE);
295
    }
296
297
    private function applyCheckoutStateTransition(OrderInterface $order, string $transition): void
298
    {
299
        $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition);
300
    }
301
302
    private function setOrderCompletedDate(OrderInterface $order, \DateTimeInterface $date): void
303
    {
304
        if ($order->getCheckoutState() === OrderCheckoutStates::STATE_COMPLETED) {
305
            $order->setCheckoutCompletedAt($date);
306
        }
307
    }
308
309
    private function generateDates(int $amount): array
310
    {
311
        $dates = [];
312
313
        for ($i = 0; $i < $amount; ++$i) {
314
            /** @var \DateTimeInterface|array $dates */
315
            $dates[] = $this->faker->dateTimeBetween('-1 years', 'now');
316
        }
317
        sort($dates);
318
319
        return $dates;
320
    }
321
322
    private function generateInvalidSkipMessage(string $type, string $channelCode): string
323
    {
324
        return sprintf(
325
            "No enabled %s method was found for the channel '%s'. " .
326
            "Set 'skipping_%s_step_allowed' option to true for this channel if you want to skip %s method selection.",
327
            $type, $channelCode, $type, $type
328
        );
329
    }
330
}
331