Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
8 changes: 7 additions & 1 deletion CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# WIP Release Notes for Craft Commerce 5.7

## System
### Extensibility
- Added `craft\commerce\db\Table::CATALOG_PRICING_QUEUE`.
- Added `craft\commerce\records\CatalogPricingQueue`.
- Added `craft\commerce\services\CatalogPricing::reserveCatalogPricingQueueRow()`.
- Added `craft\commerce\services\CatalogPricing::releaseCatalogPricingQueueRowById()`.
- Added `craft\commerce\services\CatalogPricing::deleteCatalogPricingQueueRowById()`.


2 changes: 1 addition & 1 deletion src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ public static function editions(): array
/**
* @inheritDoc
*/
public string $schemaVersion = '5.6.1.1';
public string $schemaVersion = '5.7.0.0';

/**
* @inheritdoc
Expand Down
3 changes: 3 additions & 0 deletions src/db/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,7 @@ abstract class Table
public const INVENTORYLOCATIONS = '{{%commerce_inventorylocations}}';
public const INVENTORYLOCATIONS_STORES = '{{%commerce_inventorylocations_stores}}';
public const INVENTORYTRANSACTIONS = '{{%commerce_inventorytransactions}}';

/** @since 5.7.0 */
public const CATALOG_PRICING_QUEUE = '{{%commerce_catalogpricing_queue}}';
}
16 changes: 16 additions & 0 deletions src/migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use craft\commerce\models\SiteStore;
use craft\commerce\models\Store;
use craft\commerce\Plugin;
use craft\commerce\records\CatalogPricingQueue;
use craft\commerce\records\CatalogPricingRule;
use craft\commerce\records\InventoryLocation;
use craft\commerce\records\TaxCategory;
Expand Down Expand Up @@ -132,6 +133,18 @@ public function createTables(): void
'uid' => $this->uid(),
]);

$this->archiveTableIfExists(Table::CATALOG_PRICING_QUEUE);
$this->createTable(Table::CATALOG_PRICING_QUEUE, [
'id' => $this->primaryKey(),
'storeId' => $this->integer(),
'type' => $this->enum('type', [CatalogPricingQueue::TYPE_PURCHASABLE, CatalogPricingQueue::TYPE_RULE])->notNull(),
'ids' => $this->mediumText(),
'reserved' => $this->boolean()->notNull()->defaultValue(false),
'dateCreated' => $this->dateTime()->notNull(),
'dateUpdated' => $this->dateTime()->notNull(),
'uid' => $this->uid(),
]);

$this->archiveTableIfExists(Table::CUSTOMERS);
$this->createTable(Table::CUSTOMERS, [
'id' => $this->primaryKey(), // Not used in v4 but is the old customerId
Expand Down Expand Up @@ -1090,6 +1103,8 @@ public function createIndexes(): void
$this->createIndex(null, Table::CATALOG_PRICING, ['purchasableId', 'storeId', 'isPromotionalPrice', 'price', 'catalogPricingRuleId', 'dateFrom', 'dateTo'], false);
$this->createIndex(null, Table::CATALOG_PRICING, ['purchasableId', 'storeId', 'isPromotionalPrice', 'price'], false);
$this->createIndex(null, Table::CATALOG_PRICING, ['purchasableId', 'storeId'], false);
$this->createIndex(null, Table::CATALOG_PRICING_QUEUE, 'reserved', false);
$this->createIndex(null, Table::CATALOG_PRICING_QUEUE, ['storeId', 'type', 'reserved'], false);
$this->createIndex(null, Table::CATALOG_PRICING_RULES, 'storeId', false);
$this->createIndex(null, Table::CATALOG_PRICING_RULES_USERS, 'catalogPricingRuleId', false);
$this->createIndex(null, Table::CATALOG_PRICING_RULES_USERS, 'userId', false);
Expand Down Expand Up @@ -1214,6 +1229,7 @@ public function addForeignKeys(): void
$this->addForeignKey(null, Table::CATALOG_PRICING, ['purchasableId'], Table::PURCHASABLES, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::CATALOG_PRICING, ['storeId'], Table::STORES, ['id'], 'CASCADE');
$this->addForeignKey(null, Table::CATALOG_PRICING, ['userId'], CraftTable::USERS, ['id'], 'CASCADE');
$this->addForeignKey(null, Table::CATALOG_PRICING_QUEUE, ['storeId'], Table::STORES, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::CATALOG_PRICING_RULES, ['storeId'], Table::STORES, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::CATALOG_PRICING_RULES_USERS, ['catalogPricingRuleId'], Table::CATALOG_PRICING_RULES, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::CATALOG_PRICING_RULES_USERS, ['userId'], CraftTable::USERS, ['id'], 'CASCADE', 'CASCADE');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace craft\commerce\migrations;

use craft\commerce\db\Table;
use craft\commerce\records\CatalogPricingQueue;
use craft\db\Migration;

/**
* m260407_000000_add_catalog_pricing_queue_table migration.
*/
class m260407_000000_add_catalog_pricing_queue_table extends Migration
{
/**
* @inheritdoc
*/
public function safeUp(): bool
{
if (!$this->db->tableExists(Table::CATALOG_PRICING_QUEUE)) {
$this->createTable(Table::CATALOG_PRICING_QUEUE, [
'id' => $this->primaryKey(),
'storeId' => $this->integer(),
'type' => $this->enum('type', [CatalogPricingQueue::TYPE_PURCHASABLE, CatalogPricingQueue::TYPE_RULE])->notNull(),
'ids' => $this->mediumText(),
'reserved' => $this->boolean()->notNull()->defaultValue(false),
'dateCreated' => $this->dateTime()->notNull(),
'dateUpdated' => $this->dateTime()->notNull(),
'uid' => $this->uid(),
]);
}

$this->createIndexIfMissing(Table::CATALOG_PRICING_QUEUE, 'reserved', false);
$this->createIndexIfMissing(Table::CATALOG_PRICING_QUEUE, ['storeId', 'type', 'reserved'], false);
$this->addForeignKey(null, Table::CATALOG_PRICING_QUEUE, ['storeId'], Table::STORES, ['id'], 'CASCADE', 'CASCADE');

return true;
}

/**
* @inheritdoc
*/
public function safeDown(): bool
{
echo "m260407_000000_add_catalog_pricing_queue_table cannot be reverted.\n";
return false;
}
}
52 changes: 48 additions & 4 deletions src/queue/jobs/CatalogPricing.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace craft\commerce\queue\jobs;

use craft\commerce\Plugin;
use craft\commerce\records\CatalogPricingQueue as CatalogPricingQueueRecord;
use craft\queue\BaseJob;

class CatalogPricing extends BaseJob
Expand All @@ -29,14 +30,57 @@ class CatalogPricing extends BaseJob

public function execute($queue): void
{
$catalogPricingService = Plugin::getInstance()->getCatalogPricing();
$isConsolidatedJob = $this->storeId === null && $this->purchasableIds === null && $this->catalogPricingRuleIds === null;
$catalogPricingRules = null;
if (!empty($this->catalogPricingRuleIds) && $this->storeId) {
$catalogPricingRules = Plugin::getInstance()->getCatalogPricingRules()->getAllCatalogPricingRules($this->storeId)->whereIn('id', $this->catalogPricingRuleIds)->all();
$reservedRowId = null;

// @TODO: remove these properties and behaviour at next breaking change
$storeId = $this->storeId;
$purchasableIds = $this->purchasableIds;
$catalogPricingRuleIds = $this->catalogPricingRuleIds;

if ($isConsolidatedJob) {
// New method of processing catalog pricing via queue table: reserve a row and process based on its type and IDs
$reservedRecord = $catalogPricingService->reserveCatalogPricingQueueRow();

if (!$reservedRecord) {
return;
}

$reservedRowId = $reservedRecord->id;
$storeId = $reservedRecord->storeId;

if ($reservedRecord->type === CatalogPricingQueueRecord::TYPE_PURCHASABLE) {
// Specific purchasable IDs: regenerate against all applicable rules
$purchasableIds = $reservedRecord->getIds();
} elseif ($reservedRecord->type === CatalogPricingQueueRecord::TYPE_RULE) {
$catalogPricingRuleIds = $reservedRecord->getIds();
} else {
throw new \Exception("CatalogPricing queue rule ids not recognized");
}
}

Plugin::getInstance()->getCatalogPricing()->generateCatalogPrices($this->purchasableIds, $catalogPricingRules, queue: $queue);
if (!empty($catalogPricingRuleIds)) {
$catalogPricingRules = Plugin::getInstance()->getCatalogPricingRules()
->getAllCatalogPricingRules($storeId)
->whereIn('id', $catalogPricingRuleIds)
->all();
}
Comment thread
nfourtythree marked this conversation as resolved.

try {
$catalogPricingService->generateCatalogPrices($purchasableIds, $catalogPricingRules, queue: $queue);

Plugin::getInstance()->getCatalogPricing()->clearCatalogPricingJob($this);
if ($reservedRowId) {
$catalogPricingService->deleteCatalogPricingQueueById($reservedRowId);
}
} catch (\Throwable $e) {
if ($reservedRowId) {
$catalogPricingService->releaseCatalogPricingQueueById($reservedRowId);
}

throw $e;
}
}

protected function defaultDescription(): ?string
Expand Down
84 changes: 84 additions & 0 deletions src/records/CatalogPricingQueue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\commerce\records;

use craft\commerce\db\Table;
use craft\db\ActiveRecord;
use craft\helpers\Json;
use yii\db\ActiveQueryInterface;

/**
* Catalog Pricing Queue record.
*
* @property int $id
* @property int|null $storeId
* @property string $type
* @property array|null $ids
* @property bool $reserved
* @property \DateTime $dateCreated
* @property \DateTime $dateUpdated
* @property string $uid
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 5.7.0
*/
class CatalogPricingQueue extends ActiveRecord
{
/**
* Row type for purchasable-ID-based catalog pricing work.
*/
public const TYPE_PURCHASABLE = 'purchasable';

/**
* Row type for rule-ID-based (or full-regeneration) catalog pricing work.
*/
public const TYPE_RULE = 'rule';

/**
* @inheritdoc
*/
public static function tableName(): string
{
return Table::CATALOG_PRICING_QUEUE;
}

/**
* Returns the decoded IDs array from the JSON column value.
*
* @return array|null
*/
public function getIds(): ?array
{
$raw = $this->getAttribute('ids');

if ($raw === null || $raw === '') {
return null;
}

$decoded = Json::decodeIfJson($raw);

return is_array($decoded) ? $decoded : null;
}

/**
* Encodes the IDs array to JSON and stores it in the column.
*
* @param array|null $ids
*/
public function setIds(?array $ids): void
{
$this->setAttribute('ids', $ids !== null ? Json::encode($ids) : null);
}

/**
* @return ActiveQueryInterface
*/
public function getStore(): ActiveQueryInterface
{
return $this->hasOne(Store::class, ['id' => 'storeId']);
}
}
Loading
Loading