diff --git a/vendor/magento/module-reward/Cron/ScheduledPointsExpiration.php b/vendor/magento/module-reward/Cron/ScheduledPointsExpiration.php
index b11c5c83573..a2333c21ae8 100644
--- a/vendor/magento/module-reward/Cron/ScheduledPointsExpiration.php
+++ b/vendor/magento/module-reward/Cron/ScheduledPointsExpiration.php
@@ -29,19 +29,27 @@ class ScheduledPointsExpiration
*/
protected $_rewardData;
+ /**
+ * @var int
+ */
+ private $expirationBatchLimit;
+
/**
* @param \Magento\Reward\Helper\Data $rewardData
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Reward\Model\ResourceModel\Reward\HistoryFactory $_historyItemFactory
+ * @param int $expirationBatchLimit
*/
public function __construct(
\Magento\Reward\Helper\Data $rewardData,
\Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Reward\Model\ResourceModel\Reward\HistoryFactory $_historyItemFactory
+ \Magento\Reward\Model\ResourceModel\Reward\HistoryFactory $_historyItemFactory,
+ int $expirationBatchLimit = 1000
) {
$this->_rewardData = $rewardData;
$this->_storeManager = $storeManager;
$this->_historyItemFactory = $_historyItemFactory;
+ $this->expirationBatchLimit = $expirationBatchLimit;
}
/**
@@ -59,7 +67,11 @@ class ScheduledPointsExpiration
continue;
}
$expiryType = $this->_rewardData->getGeneralConfig('expiry_calculation', $website->getId());
- $this->_historyItemFactory->create()->expirePoints($website->getId(), $expiryType, 100);
+ $this->_historyItemFactory->create()->expirePoints(
+ $website->getId(),
+ $expiryType,
+ $this->expirationBatchLimit
+ );
}
return $this;
diff --git a/vendor/magento/module-reward/Model/ResourceModel/Reward/History.php b/vendor/magento/module-reward/Model/ResourceModel/Reward/History.php
index 60ea12ef44b..d978d7b730c 100644
--- a/vendor/magento/module-reward/Model/ResourceModel/Reward/History.php
+++ b/vendor/magento/module-reward/Model/ResourceModel/Reward/History.php
@@ -221,19 +221,20 @@ class History extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
$connection = $this->getConnection();
$now = $this->dateTime->formatDate(time());
$field = $expiryType == 'static' ? 'expired_at_static' : 'expired_at_dynamic';
+ $mainTable = $this->getMainTable();
+ $rewardTable = $this->getTable('magento_reward');
$select = $connection->select()->from(
- $this->getMainTable()
+ $mainTable,
+ ['history_id', 'reward_id', 'points_delta', 'points_used']
)->joinInner(
- ['reward' => $this->getTable('magento_reward')],
- 'reward.reward_id = ' . $this->getMainTable() . '.reward_id',
+ ['reward' => $rewardTable],
+ 'reward.reward_id = ' . $mainTable . '.reward_id',
[]
)->where(
- $this->getMainTable() . '.website_id = :website_id'
+ $mainTable . '.website_id = :website_id'
)->where(
"{$field} < :time_now"
- )->where(
- "{$field} IS NOT NULL"
)->where(
'is_expired=?',
0
@@ -243,58 +244,24 @@ class History extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
)->limit(
(int)$limit
);
- $select->columns(
- [$this->getMainTable() . '.*',
- 'current_balance' => 'reward.points_balance']
- );
$bind = [':website_id' => $websiteId, ':time_now' => $now];
- $duplicates = [];
$expiredAmounts = [];
$expiredHistoryIds = [];
$stmt = $connection->query($select, $bind);
while ($row = $stmt->fetch()) {
- $row['created_at'] = $now;
- $row['expired_at_static'] = null;
- $row['expired_at_dynamic'] = null;
- $row['is_expired'] = '1';
- $row['is_duplicate_of'] = $row['history_id'];
- $expiredHistoryIds[] = $row['history_id'];
- unset($row['history_id']);
- if (!isset($expiredAmounts[$row['reward_id']])) {
- $expiredAmounts[$row['reward_id']] = 0;
+ $expiredHistoryIds[] = (int)$row['history_id'];
+ $rewardId = (int)$row['reward_id'];
+ if (!isset($expiredAmounts[$rewardId])) {
+ $expiredAmounts[$rewardId] = 0;
}
- $expiredAmount = $row['points_delta'] - $row['points_used'];
- $row['points_delta'] = -$expiredAmount;
- $row['points_balance'] = $row['current_balance'] - abs($expiredAmount);
- $row['points_used'] = 0;
- $expiredAmounts[$row['reward_id']] += $expiredAmount;
- unset($row['current_balance']);
- $duplicates[] = $row;
+ $expiredAmounts[$rewardId] += $row['points_delta'] - $row['points_used'];
}
if (count($expiredHistoryIds) > 0) {
- // decrease points balance of rewards
- foreach ($expiredAmounts as $rewardId => $expired) {
- if ($expired == 0) {
- continue;
- }
- $bind = [
- 'points_balance' => $connection->getCheckSql(
- "points_balance > " . abs($expired),
- "points_balance-" . abs($expired),
- 0
- ),
- ];
- $where = ['reward_id=?' => $rewardId];
- $connection->update($this->getTable('magento_reward'), $bind, $where);
- }
-
- // duplicate expired records
- $connection->insertMultiple($this->getMainTable(), $duplicates);
-
- // update is_expired field (using history ids instead where clause for better performance)
+ $this->insertExpiredDuplicates($expiredHistoryIds, $now);
+ $this->updateRewardBalances($expiredAmounts);
$connection->update(
- $this->getMainTable(),
+ $mainTable,
['is_expired' => '1'],
['history_id IN (?)' => $expiredHistoryIds]
);
@@ -303,6 +270,95 @@ class History extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
return $this;
}
+ /**
+ * Insert duplicate history records for expired points via INSERT...SELECT
+ *
+ * @param int[] $historyIds
+ * @param string $now
+ * @return void
+ */
+ private function insertExpiredDuplicates(array $historyIds, string $now): void
+ {
+ $connection = $this->getConnection();
+ $mainTable = $this->getMainTable();
+ $rewardTable = $this->getTable('magento_reward');
+ $columns = [
+ 'reward_id', 'website_id', 'store_id', 'action', 'entity',
+ 'points_balance', 'points_delta', 'points_used', 'points_voided',
+ 'currency_amount', 'currency_delta', 'base_currency_code',
+ 'additional_data', 'comment', 'created_at',
+ 'expired_at_static', 'expired_at_dynamic',
+ 'is_expired', 'is_duplicate_of', 'notification_sent',
+ ];
+ $balanceExpr = $connection->getCheckSql(
+ 'r.points_balance > ABS(h.points_delta - h.points_used)',
+ 'r.points_balance - ABS(h.points_delta - h.points_used)',
+ '0'
+ );
+ $select = $connection->select()->from(
+ ['h' => $mainTable],
+ []
+ )->joinInner(
+ ['r' => $rewardTable],
+ 'r.reward_id = h.reward_id',
+ []
+ )->columns([
+ 'h.reward_id', 'h.website_id', 'h.store_id', 'h.action', 'h.entity',
+ 'points_balance' => new \Zend_Db_Expr($balanceExpr),
+ 'points_delta' => new \Zend_Db_Expr('-(h.points_delta - h.points_used)'),
+ 'points_used' => new \Zend_Db_Expr('0'),
+ 'h.points_voided',
+ 'h.currency_amount', 'h.currency_delta', 'h.base_currency_code',
+ 'h.additional_data', 'h.comment',
+ 'created_at' => new \Zend_Db_Expr($connection->quote($now)),
+ 'expired_at_static' => new \Zend_Db_Expr('NULL'),
+ 'expired_at_dynamic' => new \Zend_Db_Expr('NULL'),
+ 'is_expired' => new \Zend_Db_Expr('1'),
+ 'is_duplicate_of' => 'h.history_id',
+ 'h.notification_sent',
+ ])->where('h.history_id IN (?)', $historyIds);
+
+ $connection->query($connection->insertFromSelect($select, $mainTable, $columns));
+ }
+
+ /**
+ * Decrease reward points balance for expired amounts in a single batched query
+ *
+ * @param array $expiredAmounts [reward_id => expired_amount]
+ * @return void
+ */
+ private function updateRewardBalances(array $expiredAmounts): void
+ {
+ $connection = $this->getConnection();
+ $rewardTable = $this->getTable('magento_reward');
+ $rewardIds = [];
+ $caseExpr = '';
+
+ foreach ($expiredAmounts as $rewardId => $expired) {
+ if ($expired == 0) {
+ continue;
+ }
+ $absExpired = abs($expired);
+ $rewardIds[] = (int)$rewardId;
+ $caseExpr .= $connection->quoteInto(
+ ' WHEN reward_id = ? THEN ',
+ (int)$rewardId
+ ) . $connection->getCheckSql(
+ "points_balance > {$absExpired}",
+ "points_balance - {$absExpired}",
+ '0'
+ );
+ }
+
+ if (!empty($rewardIds)) {
+ $connection->update(
+ $rewardTable,
+ ['points_balance' => new \Zend_Db_Expr("CASE{$caseExpr} ELSE points_balance END")],
+ ['reward_id IN (?)' => $rewardIds]
+ );
+ }
+ }
+
/**
* Mark history records as notification was sent to customer (about points expiration)
*
diff --git a/vendor/magento/module-reward/etc/db_schema.xml b/vendor/magento/module-reward/etc/db_schema.xml
index 8bc3017212e..cce5c013f0b 100644
--- a/vendor/magento/module-reward/etc/db_schema.xml
+++ b/vendor/magento/module-reward/etc/db_schema.xml
@@ -96,6 +96,16 @@