OrderStateMachine::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
c 0
b 0
f 0
nc 1
nop 10
dl 0
loc 22
rs 9.9332

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Oms\Business\OrderStateMachine;
9
10
use DateTime;
11
use Exception;
12
use Generated\Shared\Transfer\MessageTransfer;
13
use Generated\Shared\Transfer\OmsCheckConditionsQueryCriteriaTransfer;
14
use Generated\Shared\Transfer\OmsEventTriggerResponseTransfer;
15
use Generated\Shared\Transfer\ReservationRequestTransfer;
16
use LogicException;
17
use Orm\Zed\Oms\Persistence\SpyOmsOrderItemState;
18
use Orm\Zed\Oms\Persistence\SpyOmsOrderItemStateQuery;
19
use Orm\Zed\Sales\Persistence\SpySalesOrderItem;
20
use Spryker\Shared\ErrorHandler\ErrorLogger;
21
use Spryker\Zed\Oms\Business\Notifier\EventTriggeredNotifierInterface;
22
use Spryker\Zed\Oms\Business\Process\ProcessInterface;
23
use Spryker\Zed\Oms\Business\Process\StateInterface;
24
use Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject;
25
use Spryker\Zed\Oms\Business\Util\ReservationInterface;
26
use Spryker\Zed\Oms\Business\Util\TransitionLogInterface;
27
use Spryker\Zed\Oms\Communication\Plugin\Oms\Command\CommandCollection;
28
use Spryker\Zed\Oms\Communication\Plugin\Oms\Command\CommandCollectionInterface;
29
use Spryker\Zed\Oms\Communication\Plugin\Oms\Condition\ConditionCollection;
30
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandByItemInterface;
31
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandByOrderInterface;
32
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandInterface;
33
use Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface;
34
use Spryker\Zed\Oms\OmsConfig;
35
use Spryker\Zed\Oms\Persistence\OmsQueryContainerInterface;
36
use Spryker\Zed\Propel\Persistence\BatchProcessor\ActiveRecordBatchProcessorTrait;
37
use Spryker\Zed\PropelOrm\Business\Transaction\DatabaseTransactionHandlerTrait;
38
39
class OrderStateMachine implements OrderStateMachineInterface, CheckConditionForProcessAwareInterface
40
{
41
    use DatabaseTransactionHandlerTrait;
0 ignored issues
show
Deprecated Code introduced by
The trait Spryker\Zed\PropelOrm\Bu...TransactionHandlerTrait has been deprecated: Use {@link \Spryker\Zed\Kernel\Persistence\EntityManager\TransactionTrait} instead. ( Ignorable by Annotation )

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

41
    use /** @scrutinizer ignore-deprecated */ DatabaseTransactionHandlerTrait;

This trait has been deprecated. The supplier of the trait has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the trait will be removed and what other trait to use instead.

Loading history...
42
    use ActiveRecordBatchProcessorTrait;
43
    use OrderStateOrderItemsFetchTrait;
44
45
    /**
46
     * @var string
47
     */
48
    public const BY_ITEM = 'byItem';
49
50
    /**
51
     * @var string
52
     */
53
    public const BY_ORDER = 'byOrder';
54
55
    /**
56
     * @var int
57
     */
58
    public const MAX_EVENT_REPEATS = 10;
59
60
    /**
61
     * @deprecated Not in use anymore, will be removed in the next major.
62
     *
63
     * @var int
64
     */
65
    public const MAX_ON_ENTER = 50;
66
67
    /**
68
     * @var string
69
     */
70
    protected const RETURN_DATA_UPDATED_ORDER_ITEMS = 'updatedOrderItems';
71
72
    /**
73
     * @var array
74
     */
75
    protected $eventCounter = [];
76
77
    /**
78
     * @var array
79
     */
80
    protected $returnData = [];
81
82
    /**
83
     * @var array
84
     */
85
    protected $processBuffer = [];
86
87
    /**
88
     * @var array
89
     */
90
    protected $states = [];
91
92
    /**
93
     * @var \Spryker\Zed\Oms\Persistence\OmsQueryContainerInterface
94
     */
95
    protected $queryContainer;
96
97
    /**
98
     * @var \Spryker\Zed\Oms\Business\OrderStateMachine\TimeoutInterface
99
     */
100
    protected $timeout;
101
102
    /**
103
     * @var \Spryker\Zed\Oms\Business\OrderStateMachine\BuilderInterface
104
     */
105
    protected $builder;
106
107
    /**
108
     * @var \Spryker\Zed\Oms\Business\Util\TransitionLogInterface
109
     */
110
    protected $transitionLog;
111
112
    /**
113
     * @var \Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject
114
     */
115
    protected $activeProcesses;
116
117
    /**
118
     * @var \Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface
119
     */
120
    protected $conditions;
121
122
    /**
123
     * @var \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface
124
     */
125
    protected $commands;
126
127
    /**
128
     * @var \Spryker\Zed\Oms\Business\Util\ReservationInterface
129
     */
130
    protected $reservation;
131
132
    /**
133
     * @var \Spryker\Zed\Oms\OmsConfig
134
     */
135
    protected $omsConfig;
136
137
    /**
138
     * @var \Spryker\Zed\Oms\Business\Notifier\EventTriggeredNotifierInterface
139
     */
140
    protected EventTriggeredNotifierInterface $eventTriggeredNotifier;
141
142
    /**
143
     * @param \Spryker\Zed\Oms\Persistence\OmsQueryContainerInterface $queryContainer
144
     * @param \Spryker\Zed\Oms\Business\OrderStateMachine\BuilderInterface $builder
145
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $transitionLog
146
     * @param \Spryker\Zed\Oms\Business\OrderStateMachine\TimeoutInterface $timeout
147
     * @param \Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject $activeProcesses
148
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface|array $conditions
149
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface|array $commands
150
     * @param \Spryker\Zed\Oms\Business\Util\ReservationInterface $reservation
151
     * @param \Spryker\Zed\Oms\OmsConfig $omsConfig
152
     * @param \Spryker\Zed\Oms\Business\Notifier\EventTriggeredNotifierInterface $eventTriggeredNotifier
153
     */
154
    public function __construct(
155
        OmsQueryContainerInterface $queryContainer,
156
        BuilderInterface $builder,
157
        TransitionLogInterface $transitionLog,
158
        TimeoutInterface $timeout,
159
        ReadOnlyArrayObject $activeProcesses,
160
        $conditions,
161
        $commands,
162
        ReservationInterface $reservation,
163
        OmsConfig $omsConfig,
164
        EventTriggeredNotifierInterface $eventTriggeredNotifier
165
    ) {
166
        $this->queryContainer = $queryContainer;
167
        $this->builder = $builder;
168
        $this->transitionLog = $transitionLog;
169
        $this->timeout = $timeout;
170
        $this->activeProcesses = $activeProcesses;
171
        $this->setConditions($conditions);
172
        $this->setCommands($commands);
173
        $this->reservation = $reservation;
174
        $this->omsConfig = $omsConfig;
175
        $this->eventTriggeredNotifier = $eventTriggeredNotifier;
176
    }
177
178
    /**
179
     * Converts array to collection for BC
180
     *
181
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface|array $conditions
182
     *
183
     * @return void
184
     */
185
    protected function setConditions($conditions)
186
    {
187
        if ($conditions instanceof ConditionCollectionInterface) {
188
            $this->conditions = $conditions;
189
190
            return;
191
        }
192
193
        $conditionCollection = new ConditionCollection();
194
        foreach ($conditions as $name => $condition) {
195
            $conditionCollection->add($condition, $name);
196
        }
197
198
        $this->conditions = $conditionCollection;
199
    }
200
201
    /**
202
     * Converts array to collection for BC
203
     *
204
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface|array $commands
205
     *
206
     * @return void
207
     */
208
    protected function setCommands($commands)
209
    {
210
        if ($commands instanceof CommandCollectionInterface) {
211
            $this->commands = $commands;
212
213
            return;
214
        }
215
216
        $commandCollection = new CommandCollection();
217
        foreach ($commands as $name => $command) {
218
            $commandCollection->add($command, $name);
219
        }
220
221
        $this->commands = $commandCollection;
222
    }
223
224
    /**
225
     * @param string $eventId
226
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
227
     * @param \Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject|array $data
228
     *
229
     * @return array
230
     */
231
    public function triggerEvent($eventId, array $orderItems, $data)
232
    {
233
        $data = $this->makeDataReadOnly($data);
234
235
        $processes = $this->getProcesses($orderItems);
236
237
        $orderItems = $this->filterAffectedOrderItems($eventId, $orderItems, $processes);
238
239
        $log = $this->initTransitionLog($orderItems);
240
241
        $orderGroup = $this->groupByOrderAndState($eventId, $orderItems, $processes);
242
        $sourceStateBuffer = [];
243
244
        $allProcessedOrderItems = [];
245
        foreach ($orderGroup as $orderGroupKey => $groupedOrderItems) {
246
            if (!$this->checkOrderGroupForEventRepetitions($eventId, $orderGroupKey)) {
247
                continue;
248
            }
249
250
            $this->logSourceState($groupedOrderItems, $log);
251
252
            $processedOrderItems = $this->runCommand($eventId, $groupedOrderItems, $processes, $data, $log);
253
            if ($processedOrderItems === null) {
254
                continue;
255
            }
256
            $sourceStateBuffer = $this->updateStateByEvent($eventId, $processedOrderItems, $sourceStateBuffer, $log);
257
            $this->saveOrderItems($processedOrderItems, $log, $processes, $sourceStateBuffer);
258
259
            $currentOrderItemEntity = current($processedOrderItems);
260
261
            if ($currentOrderItemEntity) {
262
                $orderEntity = $currentOrderItemEntity->getOrder();
263
264
                $this->eventTriggeredNotifier->notifyOmsEventTriggeredListeners($eventId, $processedOrderItems, $orderEntity, $data);
265
            }
266
267
            $allProcessedOrderItems = array_merge($allProcessedOrderItems, $processedOrderItems);
268
        }
269
270
        $orderItemsWithOnEnterEvent = $this->filterItemsWithOnEnterEvent($allProcessedOrderItems, $processes, $sourceStateBuffer);
271
272
        $log->saveAll();
273
274
        $this->triggerOnEnterEvents($orderItemsWithOnEnterEvent, $data);
275
276
        return $this->returnData;
277
    }
278
279
    /**
280
     * @param string $eventId
281
     * @param array $orderItemIds
282
     * @param array<string, mixed> $data
283
     *
284
     * @return array
285
     */
286
    public function triggerEventForOrderItems($eventId, array $orderItemIds, $data)
287
    {
288
        $orderItems = $this->queryContainer
289
            ->querySalesOrderItems($orderItemIds)
290
            ->find()
291
            ->getData();
292
293
        return $this->triggerEvent($eventId, $orderItems, $data);
294
    }
295
296
    /**
297
     * @param string $eventId
298
     * @param int $orderItemId
299
     * @param array<string, mixed> $data
300
     *
301
     * @return array|null
302
     */
303
    public function triggerEventForOneOrderItem($eventId, $orderItemId, $data)
304
    {
305
        $orderItems = $this->queryContainer
306
            ->querySalesOrderItems([$orderItemId])
307
            ->find()
308
            ->getData();
309
310
        return $this->triggerEvent($eventId, $orderItems, $data);
311
    }
312
313
    /**
314
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
315
     * @param array<string, mixed> $data
316
     *
317
     * @return array
318
     */
319
    public function triggerEventForNewItem(array $orderItems, $data)
320
    {
321
        $data = $this->makeDataReadOnly($data);
322
        $sourceStateBuffer = [];
323
        $processes = $this->getProcesses($orderItems);
324
325
        $orderItemsWithOnEnterEvent = $this->filterItemsWithOnEnterEvent($orderItems, $processes, $sourceStateBuffer);
326
        $this->triggerOnEnterEvents($orderItemsWithOnEnterEvent, $data);
327
328
        $orderItemsWithTimeoutEvent = $this->filterItemsWithTimeoutEvent($orderItems, $processes);
329
        $this->saveTimeoutEvents($orderItemsWithTimeoutEvent);
330
331
        return $this->returnData;
332
    }
333
334
    /**
335
     * @param array<int> $orderItemIds
336
     * @param array<string, mixed> $data
337
     *
338
     * @return array
339
     */
340
    public function triggerEventForNewOrderItems(array $orderItemIds, $data)
341
    {
342
        $orderItems = $this->queryContainer
343
            ->querySalesOrderItems($orderItemIds)
344
            ->find()
345
            ->getData();
346
347
        return $this->triggerEventForNewItem($orderItems, $data);
348
    }
349
350
    /**
351
     * @param array $logContext
352
     * @param \Generated\Shared\Transfer\OmsCheckConditionsQueryCriteriaTransfer|null $omsCheckConditionsQueryCriteriaTransfer
353
     *
354
     * @return int
355
     */
356
    public function checkConditions(array $logContext = [], ?OmsCheckConditionsQueryCriteriaTransfer $omsCheckConditionsQueryCriteriaTransfer = null)
357
    {
358
        $affectedOrderItems = 0;
359
        foreach ($this->activeProcesses as $processName) {
360
            $process = $this->builder->createProcess($processName);
361
            $orderStateMachine = clone $this;
362
            $affectedOrderItems += $orderStateMachine->checkConditionsForProcess($process, $omsCheckConditionsQueryCriteriaTransfer);
363
        }
364
365
        return $affectedOrderItems;
366
    }
367
368
    /**
369
     * @param \Spryker\Zed\Oms\Business\Process\ProcessInterface $process
370
     * @param \Generated\Shared\Transfer\OmsCheckConditionsQueryCriteriaTransfer|null $omsCheckConditionsQueryCriteriaTransfer
371
     * @param array|null $stateToTransitionsMap
372
     * @param array|null $orderItems
373
     *
374
     * @return int
375
     */
376
    public function checkConditionsForProcess(
377
        ProcessInterface $process,
378
        ?OmsCheckConditionsQueryCriteriaTransfer $omsCheckConditionsQueryCriteriaTransfer,
379
        ?array $stateToTransitionsMap = null,
380
        ?array $orderItems = null
381
    ): int {
382
        if ($stateToTransitionsMap === null) {
383
            $transitions = $process->getAllTransitionsWithoutEvent();
384
385
            $stateToTransitionsMap = $this->createStateToTransitionMap($transitions);
386
        }
387
388
        if ($orderItems === null) {
389
            $orderItems = $this->getOrderItemsByState(array_keys($stateToTransitionsMap), $process, $omsCheckConditionsQueryCriteriaTransfer);
390
        }
391
392
        $countAffectedItems = count($orderItems);
393
394
        if (count($orderItems) === 0) {
395
            return 0;
396
        }
397
398
        $log = $this->initTransitionLog($orderItems);
399
400
        $sourceStateBuffer = $this->updateStateByTransition($stateToTransitionsMap, $orderItems, [], $log);
401
402
        $processes = [$process->getName() => $process];
403
404
        $this->saveOrderItems($orderItems, $log, $processes, $sourceStateBuffer);
405
406
        $orderItemsWithOnEnterEvent = $this->filterItemsWithOnEnterEvent($orderItems, $processes, $sourceStateBuffer);
407
408
        $data = $this->makeDataReadOnly([]);
409
410
        $this->triggerOnEnterEvents($orderItemsWithOnEnterEvent, $data);
411
412
        return $countAffectedItems;
413
    }
414
415
    /**
416
     * @param array<\Spryker\Zed\Oms\Business\Process\TransitionInterface> $transitions
417
     * @param \Orm\Zed\Sales\Persistence\SpySalesOrderItem $orderItem
418
     * @param \Spryker\Zed\Oms\Business\Process\StateInterface $sourceState
419
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
420
     *
421
     * @throws \Exception
422
     *
423
     * @return \Spryker\Zed\Oms\Business\Process\StateInterface
424
     */
425
    protected function checkCondition(array $transitions, $orderItem, StateInterface $sourceState, TransitionLogInterface $log)
426
    {
427
        $possibleTransitions = [];
428
429
        foreach ($transitions as $transition) {
430
            if ($transition->hasCondition()) {
431
                $conditionString = $transition->getCondition();
432
                $conditionModel = $this->getCondition($conditionString);
433
434
                try {
435
                    $conditionCheck = $conditionModel->check($orderItem);
436
                } catch (Exception $e) {
437
                    $log->setIsError(true);
438
                    $log->setErrorMessage(get_class($e) . ' - ' . $e->getMessage());
439
                    $log->saveAll();
440
441
                    throw $e;
442
                }
443
444
                if ($conditionCheck === true) {
445
                    array_unshift($possibleTransitions, $transition);
446
                }
447
448
                $log->addCondition($orderItem, $conditionModel);
449
            } else {
450
                array_push($possibleTransitions, $transition);
451
            }
452
        }
453
454
        if (count($possibleTransitions) > 0) {
455
            /** @var \Spryker\Zed\Oms\Business\Process\TransitionInterface $selectedTransition */
456
            $selectedTransition = array_shift($possibleTransitions);
457
            $targetState = $selectedTransition->getTarget();
458
        } else {
459
            $targetState = $sourceState;
460
        }
461
462
        return $targetState;
463
    }
464
465
    /**
466
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
467
     *
468
     * @return array<\Spryker\Zed\Oms\Business\Process\ProcessInterface>
469
     */
470
    protected function getProcesses(array $orderItems)
471
    {
472
        $processes = [];
473
        foreach ($orderItems as $orderItem) {
474
            $processName = $orderItem->getProcess()->getName();
475
            if (array_key_exists($processName, $processes) === false) {
476
                $processes[$processName] = $this->builder->createProcess($processName);
477
            }
478
        }
479
480
        return $processes;
481
    }
482
483
    /**
484
     * Filters out all items that are not affected by the current event
485
     *
486
     * @param string $eventId
487
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
488
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
489
     *
490
     * @return array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem>
491
     */
492
    protected function filterAffectedOrderItems($eventId, array $orderItems, $processes)
493
    {
494
        $orderItemsFiltered = [];
495
        foreach ($orderItems as $orderItem) {
496
            $stateName = $orderItem->getState()->getName();
497
            $processName = $orderItem->getProcess()->getName();
498
            $process = $processes[$processName];
499
500
            $state = $process->getStateFromAllProcesses($stateName);
501
502
            if ($state->hasEvent($eventId)) {
503
                $orderItemsFiltered[] = $orderItem;
504
            }
505
        }
506
507
        return $orderItemsFiltered;
508
    }
509
510
    /**
511
     * @param string $eventId
512
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
513
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
514
     *
515
     * @return array
516
     */
517
    protected function groupByOrderAndState($eventId, array $orderItems, $processes)
518
    {
519
        $orderEventGroup = [];
520
        foreach ($orderItems as $orderItem) {
521
            $stateId = $orderItem->getState()->getName();
522
            $processId = $orderItem->getProcess()->getName();
523
            $process = $processes[$processId];
524
            $orderId = $orderItem->getOrder()->getIdSalesOrder();
525
526
            $state = $process->getStateFromAllProcesses($stateId);
527
528
            if ($state->hasEvent($eventId)) {
529
                $key = $orderId . '-' . $stateId;
530
                if (!isset($orderEventGroup[$key])) {
531
                    $orderEventGroup[$key] = [];
532
                }
533
                $orderEventGroup[$key][] = $orderItem;
534
            }
535
        }
536
537
        return $orderEventGroup;
538
    }
539
540
    /**
541
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandInterface $command
542
     *
543
     * @throws \LogicException
544
     *
545
     * @return string
546
     */
547
    protected function getCommandType(CommandInterface $command)
548
    {
549
        if ($command instanceof CommandByOrderInterface) {
550
            return static::BY_ORDER;
551
        }
552
        if ($command instanceof CommandByItemInterface) {
553
            return static::BY_ITEM;
554
        }
555
556
        throw new LogicException('Unknown type of command: ' . get_class($command));
557
    }
558
559
    /**
560
     * Specification:
561
     * - Performs commands on items
562
     * - All passing items should have the same event available
563
     * - For CommandByOrderInterface the command will be taken from the first order item
564
     *
565
     * @param string $eventId
566
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
567
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
568
     * @param \Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject $data
569
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
570
     *
571
     * @throws \LogicException
572
     *
573
     * @return array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem>|null
574
     */
575
    protected function runCommand($eventId, array $orderItems, array $processes, ReadOnlyArrayObject $data, TransitionLogInterface $log)
576
    {
577
        $processedOrderItems = [];
578
579
        /** @var \Orm\Zed\Sales\Persistence\SpySalesOrderItem $currentOrderItemEntity */
580
        $currentOrderItemEntity = current($orderItems);
581
        $orderEntity = $currentOrderItemEntity->getOrder();
582
583
        foreach ($orderItems as $orderItemEntity) {
584
            $stateId = $orderItemEntity->getState()->getName();
585
            $processId = $orderItemEntity->getProcess()->getName();
586
            $process = $processes[$processId];
587
            $state = $process->getStateFromAllProcesses($stateId);
588
            $event = $state->getEvent($eventId);
589
590
            $log->setEvent($event);
591
592
            if (!$event->hasCommand()) {
593
                $processedOrderItems[] = $orderItemEntity;
594
595
                continue;
596
            }
597
598
            /** @var \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandByOrderInterface|\Spryker\Zed\Oms\Dependency\Plugin\Command\CommandByItemInterface|\Spryker\Zed\Oms\Dependency\Plugin\Command\CommandInterface $command */
599
            $command = $this->getCommand($event->getCommand());
600
            $type = $this->getCommandType($command);
601
602
            $log->addCommand($orderItemEntity, $command);
603
604
            $omsEventTriggerResponseTransfer = (new OmsEventTriggerResponseTransfer())->setIsSuccessful(true);
605
            $this->returnData[OmsConfig::OMS_EVENT_TRIGGER_RESPONSE] = $omsEventTriggerResponseTransfer;
606
607
            try {
608
                if ($command instanceof CommandByOrderInterface) {
609
                    $returnData = $command->run($orderItems, $orderEntity, $data);
610
                    if (is_array($returnData)) {
611
                        $this->returnData = array_merge($this->returnData, $returnData);
612
                        $orderItems = $this->handleUpdatedOrderItems($orderItems, $returnData, $log);
613
                    }
614
615
                    return $orderItems;
616
                }
617
618
                if ($command instanceof CommandByItemInterface) {
619
                    $returnData = $command->run($orderItemEntity, $data);
620
                    $this->returnData = array_merge($this->returnData, $returnData);
621
                    $processedOrderItems[] = $orderItemEntity;
622
                } else {
623
                    throw new LogicException('Unknown type of command: ' . get_class($command));
624
                }
625
            } catch (Exception $e) {
626
                $log->setIsError(true);
627
                $log->setErrorMessage(get_class($e) . ' - ' . $e->getMessage());
628
                $log->saveAll();
629
630
                ErrorLogger::getInstance()->log($e);
631
632
                $errorMessage = $e->getMessage() ?: 'Currently not executable.';
633
                $omsEventTriggerResponseTransfer
634
                    ->setIsSuccessful(false)
635
                    ->addMessage(
636
                        (new MessageTransfer())->setValue($errorMessage),
637
                    );
638
                $this->returnData[OmsConfig::OMS_EVENT_TRIGGER_RESPONSE] = $omsEventTriggerResponseTransfer;
639
640
                if ($type === static::BY_ORDER) {
641
                    return null; // intercept the processing of a grouped order items for the current order state
642
                }
643
            }
644
        }
645
646
        return $processedOrderItems;
647
    }
648
649
    /**
650
     * @param string $eventId
651
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
652
     * @param array $sourceStateBuffer
653
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
654
     *
655
     * @return array
656
     */
657
    protected function updateStateByEvent($eventId, array $orderItems, array $sourceStateBuffer, TransitionLogInterface $log)
658
    {
659
        $targetStateMap = [];
660
        foreach ($orderItems as $i => $orderItem) {
661
            $stateId = $orderItem->getState()->getName();
662
            $sourceStateBuffer[$orderItem->getIdSalesOrderItem()] = $stateId;
663
664
            $process = $this->builder->createProcess($orderItem->getProcess()->getName());
665
            $sourceState = $process->getStateFromAllProcesses($stateId);
666
667
            $log->addSourceState($orderItem, $sourceState->getName());
668
669
            $targetState = $sourceState;
670
            if ($eventId && $sourceState->hasEvent($eventId)) {
671
                $transitions = $sourceState->getEvent($eventId)->getTransitionsBySource($sourceState);
672
                $targetState = $this->checkCondition($transitions, $orderItem, $sourceState, $log);
673
                $log->addTargetState($orderItem, $targetState->getName());
674
            }
675
676
            $targetStateMap[$i] = $targetState->getName();
677
        }
678
679
        foreach ($orderItems as $i => $orderItem) {
680
            $this->setState($orderItems[$i], $targetStateMap[$i]);
681
        }
682
683
        return $sourceStateBuffer;
684
    }
685
686
    /**
687
     * @param array $stateToTransitionsMap
688
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
689
     * @param array $sourceStateBuffer
690
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
691
     *
692
     * @return array
693
     */
694
    protected function updateStateByTransition($stateToTransitionsMap, array $orderItems, array $sourceStateBuffer, TransitionLogInterface $log)
695
    {
696
        $targetStateMap = [];
697
        foreach ($orderItems as $i => $orderItem) {
698
            $stateId = $orderItem->getState()->getName();
699
            $sourceStateBuffer[$orderItem->getIdSalesOrderItem()] = $stateId;
700
            $process = $this->builder->createProcess($orderItem->getProcess()->getName());
701
            $sourceState = $process->getStateFromAllProcesses($stateId);
702
703
            $log->addSourceState($orderItem, $sourceState->getName());
704
705
            $transitions = $stateToTransitionsMap[$orderItem->getState()->getName()];
706
707
            $targetState = $sourceState;
708
            if (count($transitions) > 0) {
709
                $targetState = $this->checkCondition($transitions, $orderItem, $sourceState, $log);
710
            }
711
712
            $log->addTargetState($orderItem, $targetState->getName());
713
714
            $targetStateMap[$i] = $targetState->getName();
715
        }
716
717
        foreach ($orderItems as $i => $orderItem) {
718
            $this->setState($orderItems[$i], $targetStateMap[$i]);
719
        }
720
721
        return $sourceStateBuffer;
722
    }
723
724
    /**
725
     * @param \Orm\Zed\Sales\Persistence\SpySalesOrderItem $orderItem
726
     * @param string $stateName
727
     *
728
     * @return void
729
     */
730
    protected function setState($orderItem, $stateName)
731
    {
732
        if (isset($this->states[$stateName])) {
733
            $state = $this->states[$stateName];
734
        } else {
735
            $state = SpyOmsOrderItemStateQuery::create()->findOneByName($stateName);
736
            if ($state === null) {
737
                $state = new SpyOmsOrderItemState();
738
                $state->setName($stateName);
739
                $state->save();
740
            }
741
            $this->states[$stateName] = $state;
742
        }
743
        $orderItem->setState($state);
744
        $orderItem->setLastStateChange(new DateTime());
745
    }
746
747
    /**
748
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
749
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
750
     * @param array $sourceStateBuffer
751
     *
752
     * @throws \LogicException
753
     *
754
     * @return array<array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem>>
755
     */
756
    protected function filterItemsWithOnEnterEvent(array $orderItems, array $processes, array $sourceStateBuffer)
757
    {
758
        $orderItemsWithOnEnterEvent = [];
759
        foreach ($orderItems as $orderItem) {
760
            $stateId = $orderItem->getState()->getName();
761
            $processId = $orderItem->getProcess()->getName();
762
763
            if (!isset($processes[$processId])) {
764
                throw new LogicException("Unknown process $processId");
765
            }
766
767
            $process = $processes[$processId];
768
            $targetState = $process->getStateFromAllProcesses($stateId);
769
770
            if (isset($sourceStateBuffer[$orderItem->getIdSalesOrderItem()])) {
771
                $sourceState = $sourceStateBuffer[$orderItem->getIdSalesOrderItem()];
772
            } else {
773
                $sourceState = $process->getStateFromAllProcesses($orderItem->getState()->getName());
774
            }
775
776
            if ($sourceState === $targetState && $targetState->isReserved()) {
777
                $reservationRequestTransfer = (new ReservationRequestTransfer())
778
                    ->fromArray($orderItem->toArray(), true);
779
                $this->reservation->updateReservation($reservationRequestTransfer);
780
            }
781
782
            if (
783
                $sourceState !== $targetState->getName()
784
                && $targetState->hasOnEnterEvent()
785
            ) {
786
                $event = $targetState->getOnEnterEvent();
787
                if (array_key_exists($event->getName(), $orderItemsWithOnEnterEvent) === false) {
788
                    $orderItemsWithOnEnterEvent[$event->getName()] = [];
789
                }
790
                $orderItemsWithOnEnterEvent[$event->getName()][] = $orderItem;
791
            }
792
        }
793
794
        return $orderItemsWithOnEnterEvent;
795
    }
796
797
    /**
798
     * @param \Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject|array $data
799
     *
800
     * @return \Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject
801
     */
802
    protected function makeDataReadOnly($data)
803
    {
804
        if (is_array($data)) {
805
            $data = new ReadOnlyArrayObject($data);
806
        }
807
808
        return $data;
809
    }
810
811
    /**
812
     * To protect against loops, every event can only be used several times per order group.
813
     *
814
     * @param string $eventId
815
     * @param string $orderGroupKey
816
     *
817
     * @return bool
818
     */
819
    protected function checkOrderGroupForEventRepetitions(string $eventId, string $orderGroupKey): bool
820
    {
821
        if (!isset($this->eventCounter[$eventId][$orderGroupKey])) {
822
            $this->eventCounter[$eventId][$orderGroupKey] = 0;
823
        }
824
825
        $this->eventCounter[$eventId][$orderGroupKey]++;
826
827
        return $this->eventCounter[$eventId][$orderGroupKey] < static::MAX_EVENT_REPEATS;
828
    }
829
830
    /**
831
     * @param array<array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem>> $orderItemsWithOnEnterEvent
832
     * @param \Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject $data
833
     *
834
     * @return void
835
     */
836
    protected function triggerOnEnterEvents(array $orderItemsWithOnEnterEvent, ReadOnlyArrayObject $data)
837
    {
838
        if (count($orderItemsWithOnEnterEvent) > 0) {
839
            foreach ($orderItemsWithOnEnterEvent as $eventId => $orderItems) {
840
                $this->triggerEvent($eventId, $orderItems, $data);
841
            }
842
        }
843
    }
844
845
    /**
846
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
847
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
848
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
849
     * @param array $sourceStateBuffer
850
     *
851
     * @return void
852
     */
853
    protected function saveOrderItems(array $orderItems, TransitionLogInterface $log, array $processes, array $sourceStateBuffer)
854
    {
855
        $currentTime = new DateTime('now');
856
        $timeoutModel = clone $this->timeout;
857
858
        $this->handleDatabaseTransaction(function () use ($orderItems, $processes, $sourceStateBuffer, $timeoutModel, $log, $currentTime) {
859
            $this->executeBulkSaveOrderItemTransaction(
860
                $orderItems,
861
                $processes,
862
                $sourceStateBuffer,
863
                $timeoutModel,
864
                $log,
865
                $currentTime,
866
            );
867
        });
868
    }
869
870
    /**
871
     * @deprecated Use {@link executeBulkSaveOrderItemTransaction()} instead.
872
     *
873
     * @param \Orm\Zed\Sales\Persistence\SpySalesOrderItem $orderItem
874
     * @param array $processes
875
     * @param array $sourceStateBuffer
876
     * @param \Spryker\Zed\Oms\Business\OrderStateMachine\TimeoutInterface $timeoutModel
877
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
878
     * @param \DateTime $currentTime
879
     *
880
     * @return void
881
     */
882
    protected function executeSaveOrderItemTransaction(
883
        SpySalesOrderItem $orderItem,
884
        array $processes,
885
        array $sourceStateBuffer,
886
        TimeoutInterface $timeoutModel,
887
        TransitionLogInterface $log,
888
        DateTime $currentTime
889
    ) {
890
        $process = $processes[$orderItem->getProcess()->getName()];
891
892
        $sourceState = $sourceStateBuffer[$orderItem->getIdSalesOrderItem()];
893
        $targetState = $orderItem->getState()->getName();
894
895
        if ($sourceState !== $targetState) {
896
            $timeoutModel->dropOldTimeout($process, $sourceState, $orderItem);
897
            $timeoutModel->setNewTimeout($process, $orderItem, $currentTime);
898
        }
899
900
        $orderItem->save();
901
        $this->updateOmsReservation($process, $sourceState, $targetState, $orderItem);
902
        $log->save($orderItem);
903
    }
904
905
    /**
906
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItemEntities
907
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
908
     * @param array<int, string> $sourceStateBuffer
909
     * @param \Spryker\Zed\Oms\Business\OrderStateMachine\TimeoutInterface $timeoutModel
910
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
911
     * @param \DateTime $currentTime
912
     *
913
     * @return void
914
     */
915
    protected function executeBulkSaveOrderItemTransaction(
916
        array $orderItemEntities,
917
        array $processes,
918
        array $sourceStateBuffer,
919
        TimeoutInterface $timeoutModel,
920
        TransitionLogInterface $log,
921
        DateTime $currentTime
922
    ) {
923
        $indexedOrderItemEntities = [];
924
        foreach ($orderItemEntities as $orderItemEntity) {
925
            $process = $processes[$orderItemEntity->getProcess()->getName()];
926
            $sourceState = $sourceStateBuffer[$orderItemEntity->getIdSalesOrderItem()];
927
            $targetState = $orderItemEntity->getState()->getName();
928
929
            if ($sourceState !== $targetState) {
930
                $timeoutModel->dropOldTimeout($process, $sourceState, $orderItemEntity);
931
                $timeoutModel->setNewTimeout($process, $orderItemEntity, $currentTime);
932
            }
933
            $indexedOrderItemEntities[$orderItemEntity->getIdSalesOrderItem()] = $orderItemEntity;
934
            $this->persist($orderItemEntity);
935
        }
936
937
        $this->commit();
938
939
        foreach ($indexedOrderItemEntities as $orderItemEntity) {
940
            $this->updateOmsReservation($process, $sourceState, $targetState, $orderItemEntity);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $process seems to be defined by a foreach iteration on line 924. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $sourceState seems to be defined by a foreach iteration on line 924. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $targetState seems to be defined by a foreach iteration on line 924. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
941
            $log->save($orderItemEntity);
942
        }
943
    }
944
945
    /**
946
     * @param string $command
947
     *
948
     * @return \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandInterface
949
     */
950
    protected function getCommand($command)
951
    {
952
        return $this->commands->get($command);
953
    }
954
955
    /**
956
     * @param string $condition
957
     *
958
     * @return \Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionInterface
959
     */
960
    protected function getCondition($condition)
961
    {
962
        return $this->conditions->get($condition);
963
    }
964
965
    /**
966
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
967
     *
968
     * @return \Spryker\Zed\Oms\Business\Util\TransitionLogInterface
969
     */
970
    protected function initTransitionLog(array $orderItems)
971
    {
972
        $log = clone $this->transitionLog;
973
974
        $log->init($orderItems);
975
976
        return $log;
977
    }
978
979
    /**
980
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
981
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
982
     *
983
     * @return void
984
     */
985
    protected function logSourceState($orderItems, TransitionLogInterface $log)
986
    {
987
        foreach ($orderItems as $orderItem) {
988
            $stateName = $orderItem->getState()->getName();
989
            $log->addSourceState($orderItem, $stateName);
990
        }
991
    }
992
993
    /**
994
     * @deprecated Use {@link updateOmsReservation()} instead.
995
     *
996
     * @param \Spryker\Zed\Oms\Business\Process\ProcessInterface $process
997
     * @param string $sourceStateId
998
     * @param string $targetStateId
999
     * @param string $sku
1000
     *
1001
     * @return void
1002
     */
1003
    protected function updateReservation(ProcessInterface $process, $sourceStateId, $targetStateId, $sku)
1004
    {
1005
        $sourceStateIsReserved = $process->getStateFromAllProcesses($sourceStateId)->isReserved();
1006
        $targetStateIsReserved = $process->getStateFromAllProcesses($targetStateId)->isReserved();
1007
1008
        if ($sourceStateIsReserved !== $targetStateIsReserved) {
1009
            $this->reservation->updateReservationQuantity($sku);
1010
        }
1011
    }
1012
1013
    /**
1014
     * @param \Spryker\Zed\Oms\Business\Process\ProcessInterface $process
1015
     * @param string $sourceState
1016
     * @param string $targetState
1017
     * @param \Orm\Zed\Sales\Persistence\SpySalesOrderItem $salesOrderItem
1018
     *
1019
     * @return void
1020
     */
1021
    protected function updateOmsReservation(
1022
        ProcessInterface $process,
1023
        string $sourceState,
1024
        string $targetState,
1025
        SpySalesOrderItem $salesOrderItem
1026
    ): void {
1027
        $sourceStateIsReserved = $process->getStateFromAllProcesses($sourceState)->isReserved();
1028
        $targetStateIsReserved = $process->getStateFromAllProcesses($targetState)->isReserved();
1029
1030
        if ($sourceStateIsReserved !== $targetStateIsReserved) {
1031
            $reservationRequestTransfer = (new ReservationRequestTransfer())
1032
                ->fromArray($salesOrderItem->toArray(), true);
1033
            $this->reservation->updateReservation($reservationRequestTransfer);
1034
        }
1035
    }
1036
1037
    /**
1038
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
1039
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
1040
     *
1041
     * @throws \LogicException
1042
     *
1043
     * @return array
1044
     */
1045
    protected function filterItemsWithTimeoutEvent(array $orderItems, array $processes)
1046
    {
1047
        $orderItemsWithTimeoutEvent = [];
1048
        foreach ($orderItems as $orderItem) {
1049
            $stateId = $orderItem->getState()->getName();
1050
            $processId = $orderItem->getProcess()->getName();
1051
1052
            if (!isset($processes[$processId])) {
1053
                throw new LogicException("Unknown process $processId");
1054
            }
1055
1056
            $process = $processes[$processId];
1057
            $targetState = $process->getStateFromAllProcesses($stateId);
1058
1059
            if ($targetState->hasTimeoutEvent()) {
1060
                $events = $targetState->getTimeoutEvents();
1061
                foreach ($events as $event) {
1062
                    $orderItemKey = sprintf('%s_%s', $orderItem->getIdSalesOrderItem(), $orderItem->getFkOmsOrderItemState());
1063
                    if (!isset($orderItemsWithTimeoutEvent[$event->getName()][$orderItemKey])) {
1064
                        $orderItemsWithTimeoutEvent[$event->getName()][$orderItemKey] = $orderItem;
1065
                    }
1066
                }
1067
            }
1068
        }
1069
1070
        return $orderItemsWithTimeoutEvent;
1071
    }
1072
1073
    /**
1074
     * @param array $orderItemsWithTimeoutEvent
1075
     *
1076
     * @return void
1077
     */
1078
    protected function saveTimeoutEvents(array $orderItemsWithTimeoutEvent)
1079
    {
1080
        foreach ($orderItemsWithTimeoutEvent as $eventId => $orderItems) {
1081
            $this->saveTimeoutEvent($eventId, $orderItems);
1082
        }
1083
    }
1084
1085
    /**
1086
     * @param string $eventId
1087
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
1088
     *
1089
     * @return void
1090
     */
1091
    protected function saveTimeoutEvent($eventId, array $orderItems)
1092
    {
1093
        $processes = $this->getProcesses($orderItems);
1094
        $orderItems = $this->filterAffectedOrderItems($eventId, $orderItems, $processes);
1095
        $sourceStateBuffer = $this->getStateByEvent($orderItems);
1096
        $orderGroup = $this->groupByOrderAndState($eventId, $orderItems, $processes);
1097
1098
        foreach ($orderGroup as $orderGroupKey => $groupedOrderItems) {
1099
            if (!$this->checkOrderGroupForEventRepetitions($eventId, $orderGroupKey)) {
1100
                return;
1101
            }
1102
1103
            $this->saveOrderItemsTimeout($groupedOrderItems, $processes, $sourceStateBuffer);
1104
        }
1105
    }
1106
1107
    /**
1108
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
1109
     *
1110
     * @return array
1111
     */
1112
    protected function getStateByEvent(array $orderItems)
1113
    {
1114
        $sourceStateBuffer = [];
1115
        foreach ($orderItems as $orderItem) {
1116
            $stateId = $orderItem->getState()->getName();
1117
            $sourceStateBuffer[$orderItem->getIdSalesOrderItem()] = $stateId;
1118
        }
1119
1120
        return $sourceStateBuffer;
1121
    }
1122
1123
    /**
1124
     * @param array<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
1125
     * @param array<\Spryker\Zed\Oms\Business\Process\ProcessInterface> $processes
1126
     * @param array $sourceStateBuffer
1127
     *
1128
     * @return void
1129
     */
1130
    protected function saveOrderItemsTimeout(array $orderItems, array $processes, array $sourceStateBuffer)
1131
    {
1132
        $currentTime = new DateTime('now');
1133
1134
        $this->timeout->dropOldTimeouts($orderItems, $processes, $sourceStateBuffer);
1135
        $this->timeout->setNewTimeouts($orderItems, $currentTime, $processes);
1136
    }
1137
1138
    /**
1139
     * @param list<\Orm\Zed\Sales\Persistence\SpySalesOrderItem> $orderItems
1140
     * @param array<mixed> $returnData
1141
     * @param \Spryker\Zed\Oms\Business\Util\TransitionLogInterface $log
1142
     *
1143
     * @return list<\Orm\Zed\Sales\Persistence\SpySalesOrderItem>
1144
     */
1145
    protected function handleUpdatedOrderItems(array $orderItems, array $returnData, TransitionLogInterface $log): array
1146
    {
1147
        if (!isset($returnData[static::RETURN_DATA_UPDATED_ORDER_ITEMS])) {
1148
            return $orderItems;
1149
        }
1150
1151
        $originalSalesOrderItemIds = array_map(
1152
            static fn (SpySalesOrderItem $orderItem): int => $orderItem->getIdSalesOrderItem(),
1153
            $orderItems,
1154
        );
1155
        $updatedOrderItemIds = array_map(
1156
            static fn (SpySalesOrderItem $orderItem): int => $orderItem->getIdSalesOrderItem(),
1157
            $returnData[static::RETURN_DATA_UPDATED_ORDER_ITEMS],
1158
        );
1159
        $originalSalesOrderItemIdsToDelete = array_diff($originalSalesOrderItemIds, $updatedOrderItemIds);
1160
1161
        if (count($originalSalesOrderItemIdsToDelete) > 0) {
1162
            foreach ($originalSalesOrderItemIdsToDelete as $orderItemId) {
1163
                $log->deleteLog($orderItemId);
1164
            }
1165
        }
1166
1167
        return $returnData[static::RETURN_DATA_UPDATED_ORDER_ITEMS];
1168
    }
1169
}
1170