From a6f9e4f3f7d53f923be1ff847120abf1972359ec Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 17 Oct 2024 11:25:13 +0100 Subject: [PATCH 01/15] WIP fixing product revisions --- src/base/Purchasable.php | 39 ++++++++++++++++++---------- src/elements/Product.php | 8 +++++- src/elements/db/PurchasableQuery.php | 4 +-- src/services/CatalogPricing.php | 3 +++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/base/Purchasable.php b/src/base/Purchasable.php index 03e9d992e8..5ca8100630 100644 --- a/src/base/Purchasable.php +++ b/src/base/Purchasable.php @@ -947,7 +947,9 @@ public function getInventoryLevels(): Collection */ public function afterSave(bool $isNew): void { - $purchasableId = $this->getCanonicalId(); + $canonicalPurchasableId = $this->getCanonicalId(); + $purchasableId = $this->id; + if (!$this->propagating) { $isOwnerDraftApplying = false; @@ -986,20 +988,29 @@ public function afterSave(bool $isNew): void // Always create the inventory item even if it's a temporary draft (in the slide) since we want to allow stock to be // added to inventory before it is saved as a permanent variant. - if ($purchasableId) { - // Set the inventory item data - /** @var InventoryItem|null $inventoryItem */ - $inventoryItem = InventoryItemRecord::find()->where(['purchasableId' => $purchasableId])->one(); - if (!$inventoryItem) { - $inventoryItem = new InventoryItemRecord(); - $inventoryItem->purchasableId = $purchasableId; - $inventoryItem->countryCodeOfOrigin = ''; - $inventoryItem->administrativeAreaCodeOfOrigin = ''; - $inventoryItem->harmonizedSystemCode = ''; - $inventoryItem->save(); + if ($canonicalPurchasableId) { + if ($isOwnerDraftApplying && $this->duplicateOf !== null) { + /** @var InventoryItem|null $inventoryItem */ + $inventoryItem = InventoryItemRecord::find()->where(['purchasableId' => $this->duplicateOf->id])->one(); + if ($inventoryItem) { + $inventoryItem->purchasableId = $canonicalPurchasableId; + $inventoryItem->save(); + $this->inventoryItemId = $inventoryItem->id; + } + } else { + // Set the inventory item data + /** @var InventoryItem|null $inventoryItem */ + $inventoryItem = InventoryItemRecord::find()->where(['purchasableId' => $canonicalPurchasableId])->one(); + if (!$inventoryItem) { + $inventoryItem = new InventoryItemRecord(); + $inventoryItem->purchasableId = $canonicalPurchasableId; + $inventoryItem->countryCodeOfOrigin = ''; + $inventoryItem->administrativeAreaCodeOfOrigin = ''; + $inventoryItem->harmonizedSystemCode = ''; + $inventoryItem->save(); + $this->inventoryItemId = $inventoryItem->id; + } } - - $this->inventoryItemId = $inventoryItem->id; } } diff --git a/src/elements/Product.php b/src/elements/Product.php index 32d692a46c..deb74ec546 100644 --- a/src/elements/Product.php +++ b/src/elements/Product.php @@ -1755,10 +1755,16 @@ protected static function defineSearchableAttributes(): array */ private static function createVariantQuery(Product $product): VariantQuery { - return Variant::find() + $query = Variant::find() ->productId($product->id) ->siteId($product->siteId) ->orderBy(['sortOrder' => SORT_ASC]); + + if ($product->getIsRevision()) { + $query->revisions(null)->trashed(null); + } + + return $query; } /** diff --git a/src/elements/db/PurchasableQuery.php b/src/elements/db/PurchasableQuery.php index bbda2206c6..9f054c08da 100755 --- a/src/elements/db/PurchasableQuery.php +++ b/src/elements/db/PurchasableQuery.php @@ -670,7 +670,7 @@ protected function afterPrepare(): bool $this->subQuery->leftJoin(['purchasables_stores' => Table::PURCHASABLES_STORES], '[[purchasables_stores.storeId]] = [[sitestores.storeId]] AND [[purchasables_stores.purchasableId]] = [[commerce_purchasables.id]]'); $this->subQuery->leftJoin(['catalogprices' => $catalogPricesQuery], '[[catalogprices.purchasableId]] = [[commerce_purchasables.id]] AND [[catalogprices.storeId]] = [[sitestores.storeId]]'); - $this->subQuery->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]]'); + $this->subQuery->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]] OR [[inventoryitems.purchasableId]] = [[elements.canonicalId]]'); return parent::afterPrepare(); } @@ -705,7 +705,7 @@ protected function beforePrepare(): bool $this->query->leftJoin(Table::SITESTORES . ' sitestores', '[[elements_sites.siteId]] = [[sitestores.siteId]]'); $this->query->leftJoin(Table::PURCHASABLES_STORES . ' purchasables_stores', '[[purchasables_stores.storeId]] = [[sitestores.storeId]] AND [[purchasables_stores.purchasableId]] = [[commerce_purchasables.id]]'); - $this->query->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]]'); + $this->query->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]] OR [[inventoryitems.purchasableId]] = [[elements.canonicalId]]'); $this->subQuery->addSelect([ 'catalogprices.price', diff --git a/src/services/CatalogPricing.php b/src/services/CatalogPricing.php index 5b9a34660a..edeb81ff3f 100755 --- a/src/services/CatalogPricing.php +++ b/src/services/CatalogPricing.php @@ -84,6 +84,9 @@ public function generateCatalogPrices(?array $purchasableIds = null, ?array $cat return; } + // Remove all purchasable IDs that are revisions + + // Rules with user ID records $cprWithUserIds = (new Query()) ->select(['catalogPricingRuleId']) From 36ca5e7025d010761e4633e662d476d14f1ab3e3 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 17 Oct 2024 16:18:55 +0100 Subject: [PATCH 02/15] Prevent catalog pricing from generating prices from revision or draft purchasables --- src/services/CatalogPricing.php | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/services/CatalogPricing.php b/src/services/CatalogPricing.php index edeb81ff3f..4ba3039695 100755 --- a/src/services/CatalogPricing.php +++ b/src/services/CatalogPricing.php @@ -75,18 +75,35 @@ public function generateCatalogPrices(?array $purchasableIds = null, ?array $cat $isAllPurchasables = $purchasableIds === null; if ($isAllPurchasables) { $purchasableIds = (new Query()) - ->select(['id']) - ->from([Table::PURCHASABLES]) + ->select(['purchasables.id']) + ->from(Table::PURCHASABLES . ' purchasables') + ->innerJoin(\craft\db\Table::ELEMENTS . ' e', '[[e.id]] = [[purchasables.id]]') + // Make sure we aren't putting and draft or revision purchasables in the catalog pricing table + ->where(['e.revisionId' => null]) + ->andWhere(['e.draftId' => null]) ->column(); + } else { + // If purchasable IDs have been passed in remove all IDs that are revisions or drafts + $allowedPurchasableIds = []; + // Chunk through the IDs to avoid hitting the int limit in the where clause + foreach (array_chunk($purchasableIds, 2000) as $purchasableIdsChunk) { + $allowedPurchasableIds = array_merge($allowedPurchasableIds, (new Query()) + ->select(['purchasables.id']) + ->from(Table::PURCHASABLES . ' purchasables') + ->innerJoin(\craft\db\Table::ELEMENTS . ' e', '[[e.id]] = [[purchasables.id]]') + ->where(['e.revisionId' => null]) + ->andWhere(['e.draftId' => null]) + ->andWhere(['purchasables.id' => $purchasableIdsChunk]) + ->column()); + } + + $purchasableIds = $allowedPurchasableIds; } if (empty($purchasableIds)) { return; } - // Remove all purchasable IDs that are revisions - - // Rules with user ID records $cprWithUserIds = (new Query()) ->select(['catalogPricingRuleId']) From a1ed9690c37e8811ef7a410f5d27a7119cc670b5 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Fri, 18 Oct 2024 08:09:56 +0100 Subject: [PATCH 03/15] =?UTF-8?q?Make=20sure=20variants=20aren=E2=80=99t?= =?UTF-8?q?=20available=20if=20they=20are=20a=20revision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/elements/Variant.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/elements/Variant.php b/src/elements/Variant.php index 9758491a38..c6701ff8fa 100755 --- a/src/elements/Variant.php +++ b/src/elements/Variant.php @@ -361,6 +361,14 @@ public function canDuplicate(User $user): bool */ public function getIsAvailable(): bool { + if ($this->getIsRevision()) { + return false; + } + + if ($this->getIsDraft()) { + return false; + } + if ($this->getPrimaryOwner()->getIsDraft()) { return false; } From fee8a8a8cb218e351129c7038df6352752c7be45 Mon Sep 17 00:00:00 2001 From: Luke Holder Date: Fri, 18 Oct 2024 19:01:33 +0800 Subject: [PATCH 04/15] luke changes --- src/base/Purchasable.php | 30 ++++++++++++++++++++++++++++++ src/models/Store.php | 3 +-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/base/Purchasable.php b/src/base/Purchasable.php index 5ca8100630..301a6fdcb2 100644 --- a/src/base/Purchasable.php +++ b/src/base/Purchasable.php @@ -40,6 +40,7 @@ use Money\Money; use Money\Teller; use yii\base\InvalidConfigException; +use yii\db\ActiveQueryInterface; use yii\validators\Validator; /** @@ -818,6 +819,10 @@ protected function defineRules(): array UniqueValidator::class, 'targetClass' => PurchasableRecord::class, 'caseInsensitive' => true, + 'filter' => function(ActiveQueryInterface $query) { + $query->leftJoin(\craft\db\Table::ELEMENTS . ' elements', '[[elements.id]] = [[commerce_purchasables.id]]'); + $query->andWhere(['elements.revisionId' => null, 'elements.draftId' => null]); + }, 'on' => self::SCENARIO_LIVE, ], [['basePrice'], 'number'], @@ -953,6 +958,27 @@ public function afterSave(bool $isNew): void if (!$this->propagating) { $isOwnerDraftApplying = false; + $owner = $this->getOwner(); + $state = [ + 'id' => $this->id, + 'canonicalId' => $this->canonicalId, + 'isDraft' => $this->getIsDraft(), + 'isRevision' => $this->getIsRevision(), + 'isCanonical' => $this->getIsCanonical(), + 'isDuplicateOf' => $this->duplicateOf !== null, + 'duplicateOfId' => $this->duplicateOf?->id, + 'ownerId' => $owner?->id, + 'ownerCanonicalId' => $owner?->canonicalId, + 'ownerIsDraft' => $owner?->getIsDraft(), + 'ownerIsRevision' => $owner?->getIsRevision(), + 'ownerIsCanonical' =>$owner?->getIsCanonical(), + 'ownerIsDuplicateOf' => $owner->duplicateOf !== null, + 'ownerDuplicateOfId' => $owner->duplicateOf?->id, + 'ownerDuplicateOfIsCanonical' => $owner->duplicateOf?->getIsCanonical(), + 'ownerDuplicateOfIsDraft' => $owner->duplicateOf?->getIsDraft(), + 'ownerDuplicateOfIsRevision' => $owner->duplicateOf?->getIsRevision(), + ]; + // If this is a nested element, check if the owner is a draft and is being applied if ($this instanceof NestedElementInterface) { $owner = $this->getOwner(); @@ -960,6 +986,10 @@ public function afterSave(bool $isNew): void } if ($this->duplicateOf !== null && !$this->getIsRevision() && !$isOwnerDraftApplying) { + + // TODO: remove this comment + // This code is being run when a revision is being restored and the variant is being put back as the main variant + $this->sku = PurchasableHelper::tempSku(); // Nullify inventory item so a new one is created $this->inventoryItemId = null; diff --git a/src/models/Store.php b/src/models/Store.php index 96d53512f0..beb9d310ed 100644 --- a/src/models/Store.php +++ b/src/models/Store.php @@ -101,6 +101,7 @@ public function extraFields(): array { $fields = parent::extraFields(); $fields[] = 'settings.locationAddress'; + $fields[] = 'settings.countries'; return $fields; } @@ -112,8 +113,6 @@ public function attributes(): array { $names = parent::attributes(); $names[] = 'name'; - $names[] = 'countries'; - $names[] = 'marketAddressCondition'; $names[] = 'settings'; return $names; } From ecd331a6c491a4318d07ccd3c555b9e4c2ddda46 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Mon, 21 Oct 2024 08:04:51 +0100 Subject: [PATCH 05/15] Tidy store location address serialization --- src/models/Store.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/models/Store.php b/src/models/Store.php index beb9d310ed..1f05f17a25 100644 --- a/src/models/Store.php +++ b/src/models/Store.php @@ -97,15 +97,6 @@ class Store extends Model */ private bool|string $_allowEmptyCartOnCheckout = false; - public function extraFields(): array - { - $fields = parent::extraFields(); - $fields[] = 'settings.locationAddress'; - $fields[] = 'settings.countries'; - - return $fields; - } - /** * @inheritdoc */ From 7dd4952cdd1fdab8d08521031a57b7a5bf1a6993 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Mon, 21 Oct 2024 08:54:47 +0100 Subject: [PATCH 06/15] Update purchasable fields to handle static display --- .../PurchasableAllowedQtyField.php | 2 + .../PurchasableAvailableForPurchaseField.php | 4 +- .../PurchasableDimensionsField.php | 3 + .../PurchasableFreeShippingField.php | 1 + .../PurchasablePriceField.php | 4 + .../PurchasablePromotableField.php | 1 + .../PurchasableSkuField.php | 4 +- .../PurchasableStockField.php | 115 ++++++++++-------- .../PurchasableWeightField.php | 1 + 9 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/fieldlayoutelements/PurchasableAllowedQtyField.php b/src/fieldlayoutelements/PurchasableAllowedQtyField.php index 9aa4b09670..74502acd5f 100644 --- a/src/fieldlayoutelements/PurchasableAllowedQtyField.php +++ b/src/fieldlayoutelements/PurchasableAllowedQtyField.php @@ -60,6 +60,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'value' => $element->minQty, 'placeholder' => Craft::t('commerce', 'Any'), 'title' => Craft::t('commerce', 'Minimum allowed quantity'), + 'disabled' => $static, ]) . Html::endTag('div') . Html::tag('div', Craft::t('commerce', 'to'), ['class' => 'label light']) . @@ -70,6 +71,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'value' => $element->maxQty, 'placeholder' => Craft::t('commerce', 'Any'), 'title' => Craft::t('commerce', 'Maximum allowed quantity'), + 'disabled' => $static, ]) . Html::endTag('div') . Html::endTag('div'); diff --git a/src/fieldlayoutelements/PurchasableAvailableForPurchaseField.php b/src/fieldlayoutelements/PurchasableAvailableForPurchaseField.php index b67808f0e6..0b1461c3bd 100644 --- a/src/fieldlayoutelements/PurchasableAvailableForPurchaseField.php +++ b/src/fieldlayoutelements/PurchasableAvailableForPurchaseField.php @@ -51,7 +51,9 @@ public function inputHtml(ElementInterface $element = null, bool $static = false throw new InvalidArgumentException(static::class . ' can only be used in purchasable field layouts.'); } - return PurchasableHelper::availableForPurchaseInputHtml($element->availableForPurchase); + return PurchasableHelper::availableForPurchaseInputHtml($element->availableForPurchase, [ + 'disabled' => $static, + ]); } /** diff --git a/src/fieldlayoutelements/PurchasableDimensionsField.php b/src/fieldlayoutelements/PurchasableDimensionsField.php index af645c9b2a..16c6e3a867 100644 --- a/src/fieldlayoutelements/PurchasableDimensionsField.php +++ b/src/fieldlayoutelements/PurchasableDimensionsField.php @@ -77,6 +77,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'class' => 'text', 'size' => 10, 'unit' => Plugin::getInstance()->getSettings()->dimensionUnits, + 'disabled' => $static, ]), ['id' => 'length', 'label' => Craft::t('commerce', 'Length')]) . Cp::fieldHtml(Cp::textHtml([ 'id' => 'width', @@ -85,6 +86,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'class' => 'text', 'size' => 10, 'unit' => Plugin::getInstance()->getSettings()->dimensionUnits, + 'disabled' => $static, ]), ['id' => 'width', 'label' => Craft::t('commerce', 'Width')]) . Cp::fieldHtml(Cp::textHtml([ 'id' => 'height', @@ -93,6 +95,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'class' => 'text', 'size' => 10, 'unit' => Plugin::getInstance()->getSettings()->dimensionUnits, + 'disabled' => $static, ]), ['id' => 'height', 'label' => Craft::t('commerce', 'Height')]) . Html::endTag('div'); } diff --git a/src/fieldlayoutelements/PurchasableFreeShippingField.php b/src/fieldlayoutelements/PurchasableFreeShippingField.php index 0956fdf7ad..60bc4584d1 100644 --- a/src/fieldlayoutelements/PurchasableFreeShippingField.php +++ b/src/fieldlayoutelements/PurchasableFreeShippingField.php @@ -56,6 +56,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'name' => 'freeShipping', 'small' => true, 'on' => $element->freeShipping, + 'disabled' => $static, ]); } diff --git a/src/fieldlayoutelements/PurchasablePriceField.php b/src/fieldlayoutelements/PurchasablePriceField.php index b7a80972fc..68addde243 100755 --- a/src/fieldlayoutelements/PurchasablePriceField.php +++ b/src/fieldlayoutelements/PurchasablePriceField.php @@ -147,6 +147,8 @@ public function inputHtml(ElementInterface $element = null, bool $static = false } } + $toggleContent = $static ? null : $toggleContent; + $currency = $element->getStore()->getCurrency(); return Html::beginTag('div', [ @@ -161,6 +163,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'currencyLabel' => $currency->getCode(), 'required' => true, 'errors' => $element->getErrors('basePrice'), + 'disabled' => $static, ]), [ 'id' => 'base-price', 'label' => Craft::t('commerce', 'Price'), @@ -174,6 +177,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'currency' => $currency->getCode(), 'currencyLabel' => $currency->getCode(), 'errors' => $element->getErrors('basePromotionalPrice'), + 'disabled' => $static, ]), [ 'id' => 'promotional-price', 'label' => Craft::t('commerce', 'Promotional Price'), diff --git a/src/fieldlayoutelements/PurchasablePromotableField.php b/src/fieldlayoutelements/PurchasablePromotableField.php index e07f8aa057..7e3da18c59 100644 --- a/src/fieldlayoutelements/PurchasablePromotableField.php +++ b/src/fieldlayoutelements/PurchasablePromotableField.php @@ -56,6 +56,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'name' => 'promotable', 'small' => true, 'on' => $element->promotable, + 'disabled' => $static, ]); } diff --git a/src/fieldlayoutelements/PurchasableSkuField.php b/src/fieldlayoutelements/PurchasableSkuField.php index 31e7d348a5..bf05bc4050 100644 --- a/src/fieldlayoutelements/PurchasableSkuField.php +++ b/src/fieldlayoutelements/PurchasableSkuField.php @@ -58,7 +58,9 @@ public function inputHtml(ElementInterface $element = null, bool $static = false if ($element instanceof Variant && $element->getOwner()->getType()->skuFormat !== null && !$element->id) { $html .= Html::hiddenInput('sku', ''); } else { - $html .= PurchasableHelper::skuInputHtml($element->getSkuAsText()); + $html .= PurchasableHelper::skuInputHtml($element->getSkuAsText(), [ + 'disabled' => $static, + ]); } return $html; diff --git a/src/fieldlayoutelements/PurchasableStockField.php b/src/fieldlayoutelements/PurchasableStockField.php index d9612622b1..f6f4e7159e 100644 --- a/src/fieldlayoutelements/PurchasableStockField.php +++ b/src/fieldlayoutelements/PurchasableStockField.php @@ -114,72 +114,80 @@ public function inputHtml(ElementInterface $element = null, bool $static = false $inventoryLevelTableRows .= Html::beginTag('tr') . Html::beginTag('td') . - $inventoryLevel->getInventoryLocation()->name . + $inventoryLevel->getInventoryLocation()->name . Html::endTag('td') . Html::beginTag('td') . - Html::beginTag('div', ['class' => 'flex']) . - Html::tag('div', (string)$inventoryLevel->availableTotal, [ - 'id' => $updatedValueId, - ]) . - Html::tag('div',Html::button(Craft::t('commerce', ''), - [ - 'class' => 'btn menubtn action-btn', - 'id' => $editUpdateQuantityInventoryItemId, - ])) . - Html::endTag('div') . + Html::beginTag('div', ['class' => 'flex']) . + Html::tag('div', (string)$inventoryLevel->availableTotal, [ + 'id' => $updatedValueId, + ]) . + (!$static ? Html::tag('div',Html::button(Craft::t('commerce', ''), + [ + 'class' => 'btn menubtn action-btn', + 'id' => $editUpdateQuantityInventoryItemId, + ])) : '') . + Html::endTag('div') . Html::endTag('td') . - Html::beginTag('td') . - (Craft::$app->getUser()->checkPermission('commerce-manageInventoryStockLevels') ? - Html::a( - Craft::t('commerce', 'Manage'), - UrlHelper::cpUrl('commerce/inventory/levels/' . $inventoryLevel->getInventoryLocation()->handle, [ - 'inventoryItemId' => $inventoryLevel->getInventoryItem()->id, - ]), - [ - 'target' => '_blank', - 'class' => 'btn small', - 'id' => $editUpdateQuantityInventoryItemId, - 'aria-label' => Craft::t('app', 'Open in a new tab'), - 'data-icon' => 'external', - ] - ) : '') . + (!$static ? Html::beginTag('td') . + (Craft::$app->getUser()->checkPermission('commerce-manageInventoryStockLevels') ? + Html::a( + Craft::t('commerce', 'Manage'), + UrlHelper::cpUrl('commerce/inventory/levels/' . $inventoryLevel->getInventoryLocation()->handle, [ + 'inventoryItemId' => $inventoryLevel->getInventoryItem()->id, + ]), + [ + 'target' => '_blank', + 'class' => 'btn small', + 'id' => $editUpdateQuantityInventoryItemId, + 'aria-label' => Craft::t('app', 'Open in a new tab'), + 'data-icon' => 'external', + ] + ) : '') : '') . Html::endTag('td') . Html::endTag('tr'); } $inventoryLevelsTable = Html::beginTag('table', ['class' => 'data fullwidth', 'style' => 'margin-top:5px;']) . Html::beginTag('thead') . - Html::beginTag('tr') . - Html::beginTag('th') . - Craft::t('commerce', 'Location') . - Html::endTag('th') . + Html::beginTag('tr') . Html::beginTag('th') . - Craft::t('commerce', 'Available') . + Craft::t('commerce', 'Location') . Html::endTag('th') . - Html::beginTag('th') . - Craft::t('commerce', 'Manage') . - Html::endTag('th') . - Html::endTag('tr') . + Html::beginTag('th') . + Craft::t('commerce', 'Available') . + Html::endTag('th') . + + + (!$static ? Html::beginTag('th') . + Craft::t('commerce', 'Manage') . + Html::endTag('th') : '') . + + + + Html::endTag('tr') . Html::endTag('thead') . + Html::beginTag('tbody') . - $inventoryLevelTableRows . - Html::beginTag('tr') . - Html::beginTag('td', ['colspan' => '2']) . - $availableStockLabel . - Html::beginTag('td') . - Html::a( - Craft::t('commerce', 'Edit'), - '#', - [ - 'class' => 'btn small', - 'id' => $editInventoryItemId, - 'aria-label' => Craft::t('app', 'Edit Inventory Item'), - 'data-icon' => 'edit', - ] - ) . - Html::endTag('td') . - Html::endTag('td') . - Html::endTag('tr') . + $inventoryLevelTableRows . + Html::beginTag('tr') . + Html::beginTag('td', ['colspan' => '2']) . + $availableStockLabel . + Html::endTag('td') . + + (!$static ? Html::beginTag('td') . + Html::a( + Craft::t('commerce', 'Edit'), + '#', + [ + 'class' => 'btn small', + 'id' => $editInventoryItemId, + 'aria-label' => Craft::t('app', 'Edit Inventory Item'), + 'data-icon' => 'edit', + ] + ) . + Html::endTag('td') : '') . + + Html::endTag('tr') . Html::endTag('tbody') . Html::endTag('table'); @@ -190,6 +198,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'small' => true, 'on' => $element->inventoryTracked, 'toggle' => $inventoryItemTrackedId, + 'disabled' => $static, ]; return Html::beginTag('div') . diff --git a/src/fieldlayoutelements/PurchasableWeightField.php b/src/fieldlayoutelements/PurchasableWeightField.php index 94eec2eaf0..b3adf9315d 100755 --- a/src/fieldlayoutelements/PurchasableWeightField.php +++ b/src/fieldlayoutelements/PurchasableWeightField.php @@ -76,6 +76,7 @@ public function inputHtml(ElementInterface $element = null, bool $static = false 'size' => 10, 'unit' => Plugin::getInstance()->getSettings()->weightUnits, 'placeholder' => Craft::t('commerce', 'Weight'), + 'disabled' => $static, ]); } From a5880e07bf2f250da1afd73c498ba073bf8abaff Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Mon, 21 Oct 2024 08:56:26 +0100 Subject: [PATCH 07/15] Update purchasable meta fields to displaying statically --- src/base/Purchasable.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/base/Purchasable.php b/src/base/Purchasable.php index 301a6fdcb2..678ffc934d 100644 --- a/src/base/Purchasable.php +++ b/src/base/Purchasable.php @@ -1222,6 +1222,7 @@ protected function shippingCategoryFieldHtml(bool $static): string 'label' => Craft::t('commerce', 'Shipping Category'), 'options' => $options, 'value' => $this->shippingCategoryId, + 'disabled' => $static, ]); } @@ -1251,6 +1252,7 @@ protected function taxCategoryFieldHtml(bool $static): string 'label' => Craft::t('commerce', 'Tax Category'), 'options' => $options, 'value' => $this->taxCategoryId, + 'disabled' => $static, ]); } From 381be77ebeac4e5149d8fc76d874ab039f22c177 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Mon, 21 Oct 2024 13:00:05 +0100 Subject: [PATCH 08/15] WIP fixing variant revisions --- src/base/Purchasable.php | 25 +++++++++++++++-------- src/fieldlayoutelements/VariantsField.php | 4 ++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/base/Purchasable.php b/src/base/Purchasable.php index 678ffc934d..9e12947a67 100644 --- a/src/base/Purchasable.php +++ b/src/base/Purchasable.php @@ -957,6 +957,7 @@ public function afterSave(bool $isNew): void if (!$this->propagating) { $isOwnerDraftApplying = false; + $isOwnerRevisionApplying = false; $owner = $this->getOwner(); $state = [ @@ -982,17 +983,23 @@ public function afterSave(bool $isNew): void // If this is a nested element, check if the owner is a draft and is being applied if ($this instanceof NestedElementInterface) { $owner = $this->getOwner(); - $isOwnerDraftApplying = $owner && $owner->getIsCanonical() && $owner->duplicateOf !== null && $owner->duplicateOf->getIsDraft(); + $isOwnerDraftApplying = $owner + && $owner->getIsCanonical() + && $owner->duplicateOf !== null + && $owner->duplicateOf->getIsDraft(); + + $isOwnerRevisionApplying = $owner + && $owner->duplicateOf !== null + && $owner->duplicateOf->getIsRevision(); } - if ($this->duplicateOf !== null && !$this->getIsRevision() && !$isOwnerDraftApplying) { - - // TODO: remove this comment - // This code is being run when a revision is being restored and the variant is being put back as the main variant - - $this->sku = PurchasableHelper::tempSku(); - // Nullify inventory item so a new one is created - $this->inventoryItemId = null; + if (!$this->getIsRevision()) { + // Reset the purchasable's SKU when it is explicitly being duplicating + if ($this->duplicateOf !== null && !$isOwnerDraftApplying && !$isOwnerRevisionApplying) { + $this->sku = PurchasableHelper::tempSku(); + // Nullify inventory item so a new one is created + $this->inventoryItemId = null; + } } $purchasable = PurchasableRecord::findOne($purchasableId); diff --git a/src/fieldlayoutelements/VariantsField.php b/src/fieldlayoutelements/VariantsField.php index 23990b48cd..20af5277a6 100644 --- a/src/fieldlayoutelements/VariantsField.php +++ b/src/fieldlayoutelements/VariantsField.php @@ -60,9 +60,9 @@ protected function inputHtml(ElementInterface $element = null, bool $static = fa Craft::$app->getView()->registerDeltaName($this->attribute()); return $element->getVariantManager()->getIndexHtml($element, [ - 'canCreate' => true, + 'canCreate' => !$static, 'allowedViewModes' => [ElementIndexViewMode::Cards, ElementIndexViewMode::Table], - 'sortable' => true, + 'sortable' => !$static, 'fieldLayouts' => [$element->getType()->getVariantFieldLayout()], ]); } From 0676c126d421c18df4b96ebdb20d6481acd2d5ff Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Tue, 22 Oct 2024 08:56:00 +0100 Subject: [PATCH 09/15] WIP variant revision migration --- src/Plugin.php | 2 +- ...4_add_missing_variant_revision_records.php | 133 ++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/migrations/m241022_075144_add_missing_variant_revision_records.php diff --git a/src/Plugin.php b/src/Plugin.php index 0b9e573173..eef3e819ea 100755 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -254,7 +254,7 @@ public static function editions(): array /** * @inheritDoc */ - public string $schemaVersion = '5.2.0.4'; + public string $schemaVersion = '5.2.0.5'; /** * @inheritdoc diff --git a/src/migrations/m241022_075144_add_missing_variant_revision_records.php b/src/migrations/m241022_075144_add_missing_variant_revision_records.php new file mode 100644 index 0000000000..613c116392 --- /dev/null +++ b/src/migrations/m241022_075144_add_missing_variant_revision_records.php @@ -0,0 +1,133 @@ +select([ + 'id', + 'canonicalId', + 'revisionId', + ]) + ->from('{{%elements}}' . ' e') + ->where(['type' => Variant::class]) + ->andWhere(['not', ['revisionId' => null]]) + ->collect(); + + /** @var Collection $variantsWithRevisions */ + $canonicalVariantIds = $variantsWithRevisions->map(fn (array $v) => $v['canonicalId'])->unique()->all(); + $revisionVariantElementIds = $variantsWithRevisions->map(fn (array $v) => $v['id'])->unique()->all(); + $nonCanonicalPurchasableRecords = []; + $nonCanonicalPurchasableStoreRecords = []; + + foreach (array_chunk($revisionVariantElementIds, 1000) as $chunk) { + $nonCanonicalPurchasableRecords += Purchasable::find()->where(['element.id' => $chunk])->indexBy('element.id')->all(); + $nonCanonicalPurchasableStoreRecords += PurchasableStore::find()->where(['purchasableId' => $chunk])->indexBy(fn(PurchasableStore $row) => $row['purchasableId'] . '-' . $row['storeId'])->all(); + } + + $canonicalVariantPurchasableRecords = []; + $canonicalVariantPurchasableStoreRecords = []; + + foreach (array_chunk($canonicalVariantIds, 1000) as $chunk) { + $canonicalVariantPurchasableRecords += Purchasable::find()->where(['element.id' => $chunk])->indexBy('element.id')->all(); + $canonicalVariantPurchasableStoreRecords += PurchasableStore::find()->where(['purchasableId' => $chunk])->indexBy(fn(PurchasableStore $row) => $row['purchasableId'] . '-' . $row['storeId'])->all(); + } + + $stores = Store::find()->all(); + $purchasableInserts = []; + $purchasableStoresInserts = []; + $date = Db::prepareDateForDb(new \DateTime()); + + foreach ($variantsWithRevisions as $v) { + $canonicalPurchasableRecord = $canonicalVariantPurchasableRecords[$v['canonicalId']] ?? null; + $nonCanonicalPurchasableRecord = $nonCanonicalPurchasableRecords[$v['id']] ?? null; + + // Skip if we can't find the canonical record or if a record exists for this variant ID + if (!$canonicalPurchasableRecord || $nonCanonicalPurchasableRecord) { + continue; + } + + $purchasableInserts[] = [ + 'id' => $v['id'], + 'description' => $canonicalPurchasableRecord['description'], + 'sku' => $canonicalPurchasableRecord['sku'], + 'width' => $canonicalPurchasableRecord['width'], + 'height' => $canonicalPurchasableRecord['height'], + 'length' => $canonicalPurchasableRecord['length'], + 'weight' => $canonicalPurchasableRecord['weight'], + 'dateCreated' => $date, + 'dateUpdated' => $date, + 'taxCategoryId' => $canonicalPurchasableRecord['taxCategoryId'], + ]; + + foreach ($stores as $store) { + $canonicalPurchasableStoreRecord = $canonicalVariantPurchasableStoreRecords[$v['canonicalId'] . '-' . $store['id']] ?? null; + $nonCanonicalPurchaseStoreRecord = $nonCanonicalPurchasableStoreRecords[$v['id'] . '-' . $store['id']] ?? null; + + // Skip if we can't find the canonical record or if a record exists for this variant ID and Store ID + if (!$canonicalPurchasableStoreRecord || $nonCanonicalPurchaseStoreRecord) { + continue; + } + + $purchasableStoresInserts[] = [ + 'purchasableId' => $v['id'], + 'storeId' => $store['id'], + 'basePrice' => $canonicalPurchasableStoreRecord['basePrice'], + 'basePromotionalPrice' => $canonicalPurchasableStoreRecord['basePromotionalPrice'], + 'promotable' => $canonicalPurchasableStoreRecord['promotable'], + 'availableForPurchase' => $canonicalPurchasableStoreRecord['availableForPurchase'], + 'freeShipping' => $canonicalPurchasableStoreRecord['freeShipping'], + 'stock' => $canonicalPurchasableStoreRecord['stock'], + 'inventoryTracked' => $canonicalPurchasableStoreRecord['inventoryTracked'], + 'minQty' => $canonicalPurchasableStoreRecord['minQty'], + 'maxQty' => $canonicalPurchasableStoreRecord['maxQty'], + 'shippingCategoryId' => $canonicalPurchasableStoreRecord['shippingCategoryId'], + 'dateCreated' => $date, + 'dateUpdated' => $date, + ]; + } + } + + if (!empty($purchasableInserts)) { + Craft::$app->getDb()->createCommand() + ->batchInsert('{{%commerce_purchasables}}', array_keys($purchasableInserts[0]), $purchasableInserts) + ->execute(); + } + + if (!empty($purchasableStoresInserts)) { + Craft::$app->getDb()->createCommand() + ->batchInsert('{{%commerce_purchasables_stores}}', array_keys($purchasableStoresInserts[0]), $purchasableStoresInserts) + ->execute(); + } + + return true; + } + + /** + * @inheritdoc + */ + public function safeDown(): bool + { + echo "m241022_075144_add_missing_variant_revision_records cannot be reverted.\n"; + return false; + } +} From b1b8a8094862afc0f38ae46aa381bb5924847ce8 Mon Sep 17 00:00:00 2001 From: Luke Holder Date: Wed, 23 Oct 2024 21:04:18 +0800 Subject: [PATCH 10/15] Show the menu item if they have permission for anysite not just the current --- src/Plugin.php | 7 ++++--- src/services/ProductTypes.php | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index 0b9e573173..d4cff8224f 100755 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -354,19 +354,20 @@ public function getSettingsResponse(): mixed public function getCpNavItem(): ?array { $ret = parent::getCpNavItem(); + $userService = Craft::$app->getUser(); - if (Craft::$app->getUser()->checkPermission('accessPlugin-commerce')) { + if ($userService->checkPermission('accessPlugin-commerce')) { $ret['label'] = Craft::t('commerce', 'Commerce'); } - if (Craft::$app->getUser()->checkPermission('commerce-manageOrders')) { + if ($userService->checkPermission('commerce-manageOrders')) { $ret['subnav']['orders'] = [ 'label' => Craft::t('commerce', 'Orders'), 'url' => 'commerce/orders', ]; } - $hasEditableProductTypes = !empty($this->getProductTypes()->getEditableProductTypes()); + $hasEditableProductTypes = Plugin::getInstance()->getProductTypes()->getEditableProductTypeIds(true); if ($hasEditableProductTypes) { $ret['subnav']['products'] = [ 'label' => Craft::t('commerce', 'Products'), diff --git a/src/services/ProductTypes.php b/src/services/ProductTypes.php index e9f9cba766..ec5cd855da 100755 --- a/src/services/ProductTypes.php +++ b/src/services/ProductTypes.php @@ -154,7 +154,7 @@ public function getEditableProductTypes(): array * * @return array An array of all the editable product types’ IDs. */ - public function getEditableProductTypeIds(): array + public function getEditableProductTypeIds(bool $anySite = false): array { $editableIds = []; $user = Craft::$app->getUser()->getIdentity(); @@ -167,7 +167,7 @@ public function getEditableProductTypeIds(): array continue; } - if ($cpSite && !isset($productType->getSiteSettings()[$cpSite->id])) { + if (!$anySite && $cpSite && !isset($productType->getSiteSettings()[$cpSite->id])) { continue; } From 068eb3588229ceffd19466e3a9b6e9e9df6e11b5 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 24 Oct 2024 09:34:03 +0100 Subject: [PATCH 11/15] Update variant revision migration --- src/Plugin.php | 2 +- ...4_add_missing_variant_revision_records.php | 90 +++++++++++-------- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/Plugin.php b/src/Plugin.php index eef3e819ea..5baa433330 100755 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -254,7 +254,7 @@ public static function editions(): array /** * @inheritDoc */ - public string $schemaVersion = '5.2.0.5'; + public string $schemaVersion = '5.2.0.6'; /** * @inheritdoc diff --git a/src/migrations/m241022_075144_add_missing_variant_revision_records.php b/src/migrations/m241022_075144_add_missing_variant_revision_records.php index 613c116392..1e003a6dc9 100644 --- a/src/migrations/m241022_075144_add_missing_variant_revision_records.php +++ b/src/migrations/m241022_075144_add_missing_variant_revision_records.php @@ -10,6 +10,7 @@ use craft\db\Migration; use craft\db\Query; use craft\helpers\Db; +use craft\helpers\StringHelper; use Illuminate\Support\Collection; /** @@ -24,18 +25,25 @@ public function safeUp(): bool { $variantsWithRevisions = (new Query()) ->select([ - 'id', - 'canonicalId', - 'revisionId', + 'e.id', + 'e.canonicalId', + 'e.revisionId', + 'es.siteId', ]) ->from('{{%elements}}' . ' e') + ->innerJoin('{{%elements_sites}}' . ' es', '[[e.id]] = [[es.elementId]]') ->where(['type' => Variant::class]) ->andWhere(['not', ['revisionId' => null]]) ->collect(); + $sitesStores = (new Query()) + ->select(['siteId', 'storeId']) + ->from('{{%commerce_site_stores}}') + ->collect(); + /** @var Collection $variantsWithRevisions */ - $canonicalVariantIds = $variantsWithRevisions->map(fn (array $v) => $v['canonicalId'])->unique()->all(); - $revisionVariantElementIds = $variantsWithRevisions->map(fn (array $v) => $v['id'])->unique()->all(); + $canonicalVariantIds = $variantsWithRevisions->pluck('canonicalId')->unique()->all(); + $revisionVariantElementIds = $variantsWithRevisions->pluck('id')->unique()->all(); $nonCanonicalPurchasableRecords = []; $nonCanonicalPurchasableStoreRecords = []; @@ -52,7 +60,6 @@ public function safeUp(): bool $canonicalVariantPurchasableStoreRecords += PurchasableStore::find()->where(['purchasableId' => $chunk])->indexBy(fn(PurchasableStore $row) => $row['purchasableId'] . '-' . $row['storeId'])->all(); } - $stores = Store::find()->all(); $purchasableInserts = []; $purchasableStoresInserts = []; $date = Db::prepareDateForDb(new \DateTime()); @@ -66,31 +73,37 @@ public function safeUp(): bool continue; } - $purchasableInserts[] = [ - 'id' => $v['id'], - 'description' => $canonicalPurchasableRecord['description'], - 'sku' => $canonicalPurchasableRecord['sku'], - 'width' => $canonicalPurchasableRecord['width'], - 'height' => $canonicalPurchasableRecord['height'], - 'length' => $canonicalPurchasableRecord['length'], - 'weight' => $canonicalPurchasableRecord['weight'], - 'dateCreated' => $date, - 'dateUpdated' => $date, - 'taxCategoryId' => $canonicalPurchasableRecord['taxCategoryId'], - ]; - - foreach ($stores as $store) { - $canonicalPurchasableStoreRecord = $canonicalVariantPurchasableStoreRecords[$v['canonicalId'] . '-' . $store['id']] ?? null; - $nonCanonicalPurchaseStoreRecord = $nonCanonicalPurchasableStoreRecords[$v['id'] . '-' . $store['id']] ?? null; - - // Skip if we can't find the canonical record or if a record exists for this variant ID and Store ID - if (!$canonicalPurchasableStoreRecord || $nonCanonicalPurchaseStoreRecord) { - continue; - } - - $purchasableStoresInserts[] = [ + // As we are looping over variants across sites we need to ensure we only insert a purchasable once + if (!($purchasableInserts[$v['id']] ?? null)) { + $purchasableInserts[$v['id']] = [ + 'id' => $v['id'], + 'description' => $canonicalPurchasableRecord['description'], + 'sku' => $canonicalPurchasableRecord['sku'], + 'width' => $canonicalPurchasableRecord['width'], + 'height' => $canonicalPurchasableRecord['height'], + 'length' => $canonicalPurchasableRecord['length'], + 'weight' => $canonicalPurchasableRecord['weight'], + 'dateCreated' => $date, + 'dateUpdated' => $date, + 'taxCategoryId' => $canonicalPurchasableRecord['taxCategoryId'], + 'uid' => StringHelper::UUID(), + ]; + } + + $storeId = $sitesStores->where('siteId', $v['siteId'])->pluck('storeId')->first(); + + $canonicalPurchasableStoreRecord = $canonicalVariantPurchasableStoreRecords[$v['canonicalId'] . '-' . $storeId] ?? null; + $nonCanonicalPurchaseStoreRecord = $nonCanonicalPurchasableStoreRecords[$v['id'] . '-' . $storeId] ?? null; + + // Skip if we can't find the canonical record or if a record exists for this variant ID and Store ID + if (!$canonicalPurchasableStoreRecord || $nonCanonicalPurchaseStoreRecord) { + continue; + } + + if (!($purchasableStoresInserts[$v['id'] . '-' . $storeId] ?? null)) { + $purchasableStoresInserts[$v['id'] . '-' . $storeId] = [ 'purchasableId' => $v['id'], - 'storeId' => $store['id'], + 'storeId' => $storeId, 'basePrice' => $canonicalPurchasableStoreRecord['basePrice'], 'basePromotionalPrice' => $canonicalPurchasableStoreRecord['basePromotionalPrice'], 'promotable' => $canonicalPurchasableStoreRecord['promotable'], @@ -101,6 +114,7 @@ public function safeUp(): bool 'minQty' => $canonicalPurchasableStoreRecord['minQty'], 'maxQty' => $canonicalPurchasableStoreRecord['maxQty'], 'shippingCategoryId' => $canonicalPurchasableStoreRecord['shippingCategoryId'], + 'uid' => StringHelper::UUID(), 'dateCreated' => $date, 'dateUpdated' => $date, ]; @@ -108,15 +122,19 @@ public function safeUp(): bool } if (!empty($purchasableInserts)) { - Craft::$app->getDb()->createCommand() - ->batchInsert('{{%commerce_purchasables}}', array_keys($purchasableInserts[0]), $purchasableInserts) - ->execute(); + foreach (array_chunk($purchasableInserts, 1000) as $purchasableInsertsChunk) { + Craft::$app->getDb()->createCommand() + ->batchInsert('{{%commerce_purchasables}}', array_keys($purchasableInsertsChunk[0]), $purchasableInsertsChunk) + ->execute(); + } } if (!empty($purchasableStoresInserts)) { - Craft::$app->getDb()->createCommand() - ->batchInsert('{{%commerce_purchasables_stores}}', array_keys($purchasableStoresInserts[0]), $purchasableStoresInserts) - ->execute(); + foreach (array_chunk($purchasableStoresInserts, 1000) as $purchasableStoresInsertsChunk) { + Craft::$app->getDb()->createCommand() + ->batchInsert('{{%commerce_purchasables_stores}}', array_keys($purchasableStoresInsertsChunk[0]), $purchasableStoresInsertsChunk) + ->execute(); + } } return true; From d2c02cff0f6edf5593890f685eeda5d89d89a676 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Thu, 24 Oct 2024 09:34:17 +0100 Subject: [PATCH 12/15] Fix order edit purchasable table query --- src/controllers/OrdersController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/OrdersController.php b/src/controllers/OrdersController.php index fb61553061..f0dc5d5a42 100644 --- a/src/controllers/OrdersController.php +++ b/src/controllers/OrdersController.php @@ -688,6 +688,8 @@ public function actionPurchasablesTable(): Response ->innerJoin(Table::PURCHASABLES_STORES . ' pstores', '[[purchasables.id]] = [[pstores.purchasableId]]') ->where(['elements.enabled' => true]) ->andWhere(['pstores.storeId' => $store->id]) + ->andWhere(['elements.revisionId' => null]) + ->andWhere(['elements.draftId' => null]) ->from(['purchasables' => Table::PURCHASABLES]); // Are they searching for a SKU or purchasable description? From 13f6b4e9769087bdf7a5cf8dfbb8c1dcfa735e9e Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 6 Nov 2024 10:21:38 +0000 Subject: [PATCH 13/15] fix cs --- src/base/Purchasable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/Purchasable.php b/src/base/Purchasable.php index 9e12947a67..d43160577f 100644 --- a/src/base/Purchasable.php +++ b/src/base/Purchasable.php @@ -972,7 +972,7 @@ public function afterSave(bool $isNew): void 'ownerCanonicalId' => $owner?->canonicalId, 'ownerIsDraft' => $owner?->getIsDraft(), 'ownerIsRevision' => $owner?->getIsRevision(), - 'ownerIsCanonical' =>$owner?->getIsCanonical(), + 'ownerIsCanonical' => $owner?->getIsCanonical(), 'ownerIsDuplicateOf' => $owner->duplicateOf !== null, 'ownerDuplicateOfId' => $owner->duplicateOf?->id, 'ownerDuplicateOfIsCanonical' => $owner->duplicateOf?->getIsCanonical(), From 36e9f995dd208c4bed3a20ae2cdce53e7ce460e0 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 6 Nov 2024 10:30:10 +0000 Subject: [PATCH 14/15] PHPStan fixes --- src/base/Purchasable.php | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/base/Purchasable.php b/src/base/Purchasable.php index d43160577f..b8657031d3 100644 --- a/src/base/Purchasable.php +++ b/src/base/Purchasable.php @@ -10,7 +10,6 @@ use Craft; use craft\base\Element; use craft\base\NestedElementInterface; -use craft\commerce\db\Table; use craft\commerce\elements\Order; use craft\commerce\errors\StoreNotFoundException; use craft\commerce\helpers\Currency; @@ -28,6 +27,7 @@ use craft\commerce\records\InventoryItem as InventoryItemRecord; use craft\commerce\records\Purchasable as PurchasableRecord; use craft\commerce\records\PurchasableStore; +use craft\db\ActiveQuery; use craft\errors\DeprecationException; use craft\errors\SiteNotFoundException; use craft\helpers\ArrayHelper; @@ -40,7 +40,6 @@ use Money\Money; use Money\Teller; use yii\base\InvalidConfigException; -use yii\db\ActiveQueryInterface; use yii\validators\Validator; /** @@ -819,7 +818,7 @@ protected function defineRules(): array UniqueValidator::class, 'targetClass' => PurchasableRecord::class, 'caseInsensitive' => true, - 'filter' => function(ActiveQueryInterface $query) { + 'filter' => function(ActiveQuery $query) { $query->leftJoin(\craft\db\Table::ELEMENTS . ' elements', '[[elements.id]] = [[commerce_purchasables.id]]'); $query->andWhere(['elements.revisionId' => null, 'elements.draftId' => null]); }, @@ -959,27 +958,6 @@ public function afterSave(bool $isNew): void $isOwnerDraftApplying = false; $isOwnerRevisionApplying = false; - $owner = $this->getOwner(); - $state = [ - 'id' => $this->id, - 'canonicalId' => $this->canonicalId, - 'isDraft' => $this->getIsDraft(), - 'isRevision' => $this->getIsRevision(), - 'isCanonical' => $this->getIsCanonical(), - 'isDuplicateOf' => $this->duplicateOf !== null, - 'duplicateOfId' => $this->duplicateOf?->id, - 'ownerId' => $owner?->id, - 'ownerCanonicalId' => $owner?->canonicalId, - 'ownerIsDraft' => $owner?->getIsDraft(), - 'ownerIsRevision' => $owner?->getIsRevision(), - 'ownerIsCanonical' => $owner?->getIsCanonical(), - 'ownerIsDuplicateOf' => $owner->duplicateOf !== null, - 'ownerDuplicateOfId' => $owner->duplicateOf?->id, - 'ownerDuplicateOfIsCanonical' => $owner->duplicateOf?->getIsCanonical(), - 'ownerDuplicateOfIsDraft' => $owner->duplicateOf?->getIsDraft(), - 'ownerDuplicateOfIsRevision' => $owner->duplicateOf?->getIsRevision(), - ]; - // If this is a nested element, check if the owner is a draft and is being applied if ($this instanceof NestedElementInterface) { $owner = $this->getOwner(); @@ -1027,7 +1005,7 @@ public function afterSave(bool $isNew): void // added to inventory before it is saved as a permanent variant. if ($canonicalPurchasableId) { if ($isOwnerDraftApplying && $this->duplicateOf !== null) { - /** @var InventoryItem|null $inventoryItem */ + /** @var InventoryItemRecord|null $inventoryItem */ $inventoryItem = InventoryItemRecord::find()->where(['purchasableId' => $this->duplicateOf->id])->one(); if ($inventoryItem) { $inventoryItem->purchasableId = $canonicalPurchasableId; @@ -1036,7 +1014,7 @@ public function afterSave(bool $isNew): void } } else { // Set the inventory item data - /** @var InventoryItem|null $inventoryItem */ + /** @var InventoryItemRecord|null $inventoryItem */ $inventoryItem = InventoryItemRecord::find()->where(['purchasableId' => $canonicalPurchasableId])->one(); if (!$inventoryItem) { $inventoryItem = new InventoryItemRecord(); From e2d3b041494898b8544e083b8449e448f3944180 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Wed, 6 Nov 2024 10:58:07 +0000 Subject: [PATCH 15/15] Changelog --- CHANGELOG.md | 3 +++ src/Plugin.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a8dfe735..3e3081b406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Release Notes for Craft Commerce +## Unreleased +- Fixed a bug where product revisions weren’t storing variant relations. + ## 5.2.1 - 2024-10-23 - Fixed a bug where the Commerce subnav could be missing the “Product” nav item. ([#3735](https://github.com/craftcms/commerce/issues/3735)) diff --git a/src/Plugin.php b/src/Plugin.php index 231026ea0f..3e95e4d3c6 100755 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -254,7 +254,7 @@ public static function editions(): array /** * @inheritDoc */ - public string $schemaVersion = '5.2.0.6'; + public string $schemaVersion = '5.2.0.5'; /** * @inheritdoc