From 50c9ca84d5618fdfacca0f340c549b6a56fd6fa6 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 9 Jul 2025 11:42:26 +0100 Subject: [PATCH] Fix #4054 catalog price generation when saving a product --- CHANGELOG.md | 3 ++ src/Plugin.php | 2 ++ src/base/Purchasable.php | 5 +--- src/elements/Product.php | 3 ++ src/services/CatalogPricing.php | 51 ++++++++++++++++++++++++++++++++- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe4981de4..e63c9484ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ - Fixed a PHP error that could occur when viewing variant indexes and using Craft CMS 5.6.17 or earlier. ([#4060](https://github.com/craftcms/commerce/issues/4060)) - Fixed a bug where currency based order condition rules incorrectly showed up as custom fields. - Fixed a bug where Subscription and Transfer elements’ field layouts were not deleted when Commerce was uninstalled. +- Fixed a bug where catalog prices weren’t getting generated when saving a product. ([#4054](https://github.com/craftcms/commerce/issues/4054)) +- Added `craft\commerce\services\CatalogPricing::afterRequestHandler()`. +- Added `craft\commerce\services\CatalogPricing::requestPurchasablePriceGeneration()`. ## 5.4.0 - 2025-06-23 diff --git a/src/Plugin.php b/src/Plugin.php index f64c6d4888..3426bc8694 100755 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -854,6 +854,8 @@ function(DefineBehaviorsEvent $event) { Event::on(Elements::class, Elements::EVENT_AUTHORIZE_VIEW, [$this->getInventoryLocations(), 'authorizeInventoryLocationAddressView']); Event::on(Elements::class, Elements::EVENT_AUTHORIZE_SAVE, [$this->getInventoryLocations(), 'authorizeInventoryLocationAddressEdit']); Event::on(Elements::class, Elements::EVENT_AUTHORIZE_CREATE_DRAFTS, [$this->getInventoryLocations(), 'authorizeInventoryLocationAddressEdit']); + + Event::on(Craft::$app::class, Craft::$app::EVENT_AFTER_REQUEST, [$this->getCatalogPricing(), 'afterRequestHandler']); } /** diff --git a/src/base/Purchasable.php b/src/base/Purchasable.php index 54c9f16b99..8949367771 100644 --- a/src/base/Purchasable.php +++ b/src/base/Purchasable.php @@ -1213,10 +1213,7 @@ public function afterPropagate(bool $isNew): void { parent::afterPropagate($isNew); - Plugin::getInstance()->getCatalogPricing()->createCatalogPricingJob([ - 'purchasableIds' => [$this->getCanonicalId()], - 'storeId' => $this->getStoreId(), - ]); + Plugin::getInstance()->getCatalogPricing()->requestPurchasablePriceGeneration($this); } /** diff --git a/src/elements/Product.php b/src/elements/Product.php index 557517da38..cd6c29fbee 100644 --- a/src/elements/Product.php +++ b/src/elements/Product.php @@ -2018,6 +2018,9 @@ public function afterPropagate(bool $isNew): void $this->getVariantManager()->maintainNestedElements($this, $isNew); parent::afterPropagate($isNew); + // Add variants to the queue for price generation + $this->getVariants()->each(fn(Variant $v) => Plugin::getInstance()->getCatalogPricing()->requestPurchasablePriceGeneration($v)); + // Save a new revision? if ($this->_shouldSaveRevision()) { Craft::$app->getRevisions()->createRevision($this, notes: $this->revisionNotes); diff --git a/src/services/CatalogPricing.php b/src/services/CatalogPricing.php index 1c66ebd974..6f18ec5774 100755 --- a/src/services/CatalogPricing.php +++ b/src/services/CatalogPricing.php @@ -45,6 +45,55 @@ class CatalogPricing extends Component */ private ?array $_allCatalogPrices = null; + /** + * @var array Purchasable IDs collected during the request phase, indexed by Store ID. + */ + private array $_requestedPurchasableIds = []; + + /** + * @param Purchasable $purchasable + * @return void + * @throws InvalidConfigException + * @since 5.4.1 + */ + public function requestPurchasablePriceGeneration(Purchasable $purchasable): void + { + $storeId = $purchasable->getStoreId(); + $id = $purchasable->getCanonicalId(); + + if (!isset($this->_requestedPurchasableIds[$storeId])) { + $this->_requestedPurchasableIds[$storeId] = []; + } + + $this->_requestedPurchasableIds[$storeId][] = $id; + } + + /** + * @return void + * @throws InvalidConfigException + * @since 5.4.1 + */ + public function afterRequestHandler(): void + { + if (empty($this->_requestedPurchasableIds)) { + return; + } + + foreach ($this->_requestedPurchasableIds as $storeId => $purchasableIds) { + $purchasableIds = array_unique($purchasableIds); + if (empty($purchasableIds)) { + continue; + } + + $this->createCatalogPricingJob([ + 'purchasableIds' => $purchasableIds, + 'storeId' => $storeId, + ]); + } + + $this->_requestedPurchasableIds = []; + } + /** * @param Queue|QueueInterface|null $queue * @param float $progress @@ -484,7 +533,7 @@ public function afterSavePurchasableHandler(ModelEvent $event): void return; } - $this->createCatalogPricingJob(['purchasableIds' => [$event->sender->id]]); + $this->requestPurchasablePriceGeneration($event->sender); } /**