Issues (3641)

Business/OrderStateMachine/OrderStateMachine.php (1 issue)

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\PropelOrm\Business\Transaction\DatabaseTransactionHandlerTrait;
37
38
class OrderStateMachine implements OrderStateMachineInterface
39
{
40
    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

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