Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
}

/**
Expand Down
5 changes: 1 addition & 4 deletions src/base/Purchasable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/elements/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
51 changes: 50 additions & 1 deletion src/services/CatalogPricing.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -484,7 +533,7 @@ public function afterSavePurchasableHandler(ModelEvent $event): void
return;
}

$this->createCatalogPricingJob(['purchasableIds' => [$event->sender->id]]);
$this->requestPurchasablePriceGeneration($event->sender);
}

/**
Expand Down
Loading