Skip to content

Commit 5d6a1ba

Browse files
committed
Merge remote-tracking branch 'l3/ACP2E-148' into PR_L3_22_04_2022
2 parents 507ec45 + 3c00856 commit 5d6a1ba

16 files changed

+642
-15
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\SalesRule\Model\Queue\Consumer;
9+
10+
use Magento\AsynchronousOperations\Api\Data\OperationInterface;
11+
use Magento\Framework\DB\Adapter\ConnectionException;
12+
use Magento\Framework\DB\Adapter\DeadlockException;
13+
use Magento\Framework\DB\Adapter\LockWaitException;
14+
use Magento\Framework\EntityManager\EntityManager;
15+
use Magento\Framework\Serialize\SerializerInterface;
16+
use Magento\SalesRule\Model\Spi\RuleQuoteRecollectTotalsInterface;
17+
use Psr\Log\LoggerInterface;
18+
use Throwable;
19+
20+
/**
21+
* Queue consumer for triggering recollect totals by rule ID
22+
*/
23+
class RuleQuoteRecollectTotals
24+
{
25+
/**
26+
* @var RuleQuoteRecollectTotalsInterface
27+
*/
28+
private $ruleQuoteRecollectTotals;
29+
30+
/**
31+
* @var SerializerInterface
32+
*/
33+
private $serializer;
34+
35+
/**
36+
* @var LoggerInterface
37+
*/
38+
private $logger;
39+
40+
/**
41+
* @var EntityManager
42+
*/
43+
private $entityManager;
44+
45+
/**
46+
* @param RuleQuoteRecollectTotalsInterface $ruleQuoteRecollectTotals
47+
* @param LoggerInterface $logger
48+
* @param SerializerInterface $serializer
49+
* @param EntityManager $entityManager
50+
*/
51+
public function __construct(
52+
RuleQuoteRecollectTotalsInterface $ruleQuoteRecollectTotals,
53+
LoggerInterface $logger,
54+
SerializerInterface $serializer,
55+
EntityManager $entityManager
56+
) {
57+
$this->ruleQuoteRecollectTotals = $ruleQuoteRecollectTotals;
58+
$this->logger = $logger;
59+
$this->serializer = $serializer;
60+
$this->entityManager = $entityManager;
61+
}
62+
63+
/**
64+
* Process coupon usage update
65+
*
66+
* @param OperationInterface $operation
67+
* @return void
68+
* @throws \Exception
69+
*/
70+
public function process(OperationInterface $operation): void
71+
{
72+
try {
73+
$serializedData = $operation->getSerializedData();
74+
$data = $this->serializer->unserialize($serializedData);
75+
$this->ruleQuoteRecollectTotals->execute($data['rule_id']);
76+
} catch (LockWaitException $e) {
77+
$this->logger->critical($e->getMessage());
78+
$status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
79+
$errorCode = $e->getCode();
80+
$message = __($e->getMessage());
81+
} catch (DeadlockException $e) {
82+
$this->logger->critical($e->getMessage());
83+
$status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
84+
$errorCode = $e->getCode();
85+
$message = __($e->getMessage());
86+
} catch (ConnectionException $e) {
87+
$this->logger->critical($e->getMessage());
88+
$status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
89+
$errorCode = $e->getCode();
90+
$message = __($e->getMessage());
91+
} catch (Throwable $e) {
92+
$this->logger->critical($e->getMessage());
93+
$status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
94+
$errorCode = $e->getCode();
95+
$message = __(
96+
'Sorry, something went wrong while triggering recollect totals for affected quotes.' .
97+
' Please see log for details.'
98+
);
99+
}
100+
101+
$operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
102+
->setErrorCode($errorCode ?? null)
103+
->setResultMessage($message ?? null);
104+
105+
$this->entityManager->save($operation);
106+
}
107+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\SalesRule\Model\Rule;
10+
11+
use Magento\Authorization\Model\UserContextInterface;
12+
use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory;
13+
use Magento\Framework\Bulk\BulkManagementInterface;
14+
use Magento\Framework\Bulk\OperationInterface;
15+
use Magento\Framework\DataObject\IdentityGeneratorInterface;
16+
use Magento\Framework\Serialize\SerializerInterface;
17+
use Magento\SalesRule\Model\Spi\RuleQuoteRecollectTotalsInterface;
18+
19+
/**
20+
* Trigger recollect totals for quotes asynchronously.
21+
*/
22+
class RuleQuoteRecollectTotalsAsync implements RuleQuoteRecollectTotalsInterface
23+
{
24+
private const TOPIC_NAME = 'sales.rule.quote.trigger.recollect';
25+
26+
/**
27+
* @var BulkManagementInterface
28+
*/
29+
private $bulkManagement;
30+
31+
/**
32+
* @var OperationInterfaceFactory
33+
*/
34+
private $operationFactory;
35+
36+
/**
37+
* @var IdentityGeneratorInterface
38+
*/
39+
private $identityService;
40+
41+
/**
42+
* @var SerializerInterface
43+
*/
44+
private $serializer;
45+
46+
/**
47+
* @var UserContextInterface
48+
*/
49+
private $userContext;
50+
51+
/**
52+
* @param BulkManagementInterface $bulkManagement
53+
* @param OperationInterfaceFactory $operationFactory
54+
* @param IdentityGeneratorInterface $identityService
55+
* @param SerializerInterface $serializer
56+
* @param UserContextInterface $userContext
57+
*/
58+
public function __construct(
59+
BulkManagementInterface $bulkManagement,
60+
OperationInterfaceFactory $operationFactory,
61+
IdentityGeneratorInterface $identityService,
62+
SerializerInterface $serializer,
63+
UserContextInterface $userContext
64+
) {
65+
$this->bulkManagement = $bulkManagement;
66+
$this->operationFactory = $operationFactory;
67+
$this->identityService = $identityService;
68+
$this->serializer = $serializer;
69+
$this->userContext = $userContext;
70+
}
71+
72+
/**
73+
* Publish a message in the queue for triggering recollect totals for quotes affected by rule ID
74+
*
75+
* @param int $ruleId
76+
* @return void
77+
*/
78+
public function execute(int $ruleId): void
79+
{
80+
$bulkUuid = $this->identityService->generateId();
81+
$bulkDescription = __('Trigger recollect totals for quotes by rule ID %1', $ruleId);
82+
83+
$data = [
84+
'data' => [
85+
'bulk_uuid' => $bulkUuid,
86+
'topic_name' => self::TOPIC_NAME,
87+
'serialized_data' => $this->serializer->serialize(['rule_id' => $ruleId]),
88+
'status' => OperationInterface::STATUS_TYPE_OPEN,
89+
]
90+
];
91+
$operation = $this->operationFactory->create($data);
92+
93+
$this->bulkManagement->scheduleBulk(
94+
$bulkUuid,
95+
[$operation],
96+
$bulkDescription,
97+
$this->userContext->getUserId()
98+
);
99+
}
100+
}

app/code/Magento/SalesRule/Model/Rule/RuleQuoteRecollectTotalsOnDemand.php

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Magento\SalesRule\Model\Rule;
1010

11+
use Magento\Framework\DB\Select;
1112
use Magento\Quote\Model\ResourceModel\Quote;
1213
use Magento\SalesRule\Model\Spi\RuleQuoteRecollectTotalsInterface;
1314

@@ -16,6 +17,16 @@
1617
*/
1718
class RuleQuoteRecollectTotalsOnDemand implements RuleQuoteRecollectTotalsInterface
1819
{
20+
/**
21+
* Select queries batch size
22+
*/
23+
private const SELECT_BATCH_SIZE = 10000;
24+
25+
/**
26+
* Update queries batch size
27+
*/
28+
private const UPDATE_BATCH_SIZE = 1000;
29+
1930
/**
2031
* @var Quote
2132
*/
@@ -39,14 +50,32 @@ public function __construct(Quote $quoteResourceModel)
3950
*/
4051
public function execute(int $ruleId): void
4152
{
42-
$this->quoteResourceModel->getConnection()
43-
->update(
44-
$this->quoteResourceModel->getMainTable(),
45-
['trigger_recollect' => 1],
46-
[
47-
'is_active = ?' => 1,
48-
'FIND_IN_SET(?, applied_rule_ids)' => $ruleId
49-
]
50-
);
53+
$connection = $this->quoteResourceModel->getConnection();
54+
55+
$lastEntityId = 0;
56+
do {
57+
$select = $connection->select()
58+
->from($this->quoteResourceModel->getMainTable(), ['entity_id'])
59+
->where('is_active = ?', 1)
60+
->where('FIND_IN_SET(?, applied_rule_ids)', $ruleId)
61+
->where('entity_id > ?', (int)$lastEntityId)
62+
->order('entity_id ' . Select::SQL_ASC)
63+
->limit(self::SELECT_BATCH_SIZE);
64+
$entityIds = $connection->fetchCol($select);
65+
$lastEntityId = null;
66+
if ($entityIds) {
67+
$lastEntityId = $entityIds[self::SELECT_BATCH_SIZE - 1] ?? null;
68+
foreach (array_chunk($entityIds, self::UPDATE_BATCH_SIZE) as $batchEntityIds) {
69+
$connection->update(
70+
$this->quoteResourceModel->getMainTable(),
71+
['trigger_recollect' => 1],
72+
[
73+
'entity_id IN (?)' => array_map('intval', $batchEntityIds),
74+
]
75+
);
76+
}
77+
}
78+
79+
} while ($lastEntityId !== null);
5180
}
5281
}

app/code/Magento/SalesRule/Observer/RuleQuoteRecollectTotalsObserver.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public function execute(Observer $observer): void
4343
{
4444
/** @var Rule $rule */
4545
$rule = $observer->getRule();
46-
if (!$rule->isObjectNew() && (!$rule->getIsActive() || $rule->isDeleted())) {
46+
if (!$rule->isObjectNew()
47+
&& ($rule->isDeleted() || ($rule->dataHasChangedFor('is_active') && !$rule->getIsActive()))
48+
) {
4749
$this->recollectTotals->execute((int) $rule->getId());
4850
}
4951
}

0 commit comments

Comments
 (0)