diff --git a/CHANGELOG.md b/CHANGELOG.md index 351ca3b460..54b23449ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Fixed a bug where variants were not getting the default shipping category set when the currently set category was no longer available. ([#4018](https://github.com/craftcms/commerce/issues/4018)) - Fixed a PHP error that could occur when sending emails. ([#4017](https://github.com/craftcms/commerce/issues/4017)) ## 5.3.13 - 2025-05-21 diff --git a/src/elements/Variant.php b/src/elements/Variant.php index adb687c699..41fd92eb25 100755 --- a/src/elements/Variant.php +++ b/src/elements/Variant.php @@ -1181,6 +1181,17 @@ public function beforeSave(bool $isNew): bool $productType = $product->getType(); $this->fieldLayoutId = $productType->variantFieldLayoutId; + // Validate shipping category ID is available for this product type + $availableShippingCategories = $this->availableShippingCategories(); + $availableShippingCategoryIds = ArrayHelper::getColumn($availableShippingCategories, 'id'); + + // If the current shipping category ID is not in the available categories, set it to the default one + $currentShippingCategoryId = $this->getShippingCategoryId(); + if (!in_array($currentShippingCategoryId, $availableShippingCategoryIds)) { + $defaultShippingCategory = Plugin::getInstance()->getShippingCategories()->getDefaultShippingCategory($this->getStoreId()); + $this->setShippingCategoryId($defaultShippingCategory->id); + } + return parent::beforeSave($isNew); } diff --git a/src/services/ShippingCategories.php b/src/services/ShippingCategories.php index 5c8a553cb0..fee0394462 100644 --- a/src/services/ShippingCategories.php +++ b/src/services/ShippingCategories.php @@ -10,6 +10,7 @@ use Craft; use craft\commerce\db\Table; use craft\commerce\elements\Product; +use craft\commerce\elements\Variant; use craft\commerce\errors\StoreNotFoundException; use craft\commerce\models\ShippingCategory; use craft\commerce\Plugin; @@ -205,16 +206,16 @@ public function saveShippingCategory(ShippingCategory $shippingCategory, bool $r foreach ($currentProductTypeIds as $oldProductTypeId) { // If we are removing a product type for this shipping category the products of that type should be re-saved if (!in_array($oldProductTypeId, $newProductTypeIds, false)) { - // Re-save all products that no longer have this shipping category available to them - $this->_resaveProductsByProductTypeId($oldProductTypeId); + // Re-save all variants that no longer have this shipping category available to them + $this->_resaveVariantsByProductTypeId($oldProductTypeId); } } foreach ($newProductTypeIds as $newProductTypeId) { // If we are adding a product type for this shipping category the products of that type should be re-saved if (!in_array($newProductTypeId, $currentProductTypeIds, false)) { - // Re-save all products when assigning this shipping category available to them - $this->_resaveProductsByProductTypeId($newProductTypeId); + // Re-save all variants when assigning this shipping category available to them + $this->_resaveVariantsByProductTypeId($newProductTypeId); } } @@ -234,12 +235,13 @@ public function saveShippingCategory(ShippingCategory $shippingCategory, bool $r } /** - * Re-save products by product type id + * Re-save variants by product type id */ - private function _resaveProductsByProductTypeId(int $productTypeId): void + private function _resaveVariantsByProductTypeId(int $productTypeId): void { Craft::$app->getQueue()->push(new ResaveElements([ - 'elementType' => Product::class, + 'elementType' => Variant::class, + 'updateSearchIndex' => false, 'criteria' => [ 'typeId' => $productTypeId, 'siteId' => '*', diff --git a/tests/fixtures/data/shipping-category.php b/tests/fixtures/data/shipping-category.php index fa2ea0faaa..beb7622347 100644 --- a/tests/fixtures/data/shipping-category.php +++ b/tests/fixtures/data/shipping-category.php @@ -10,7 +10,7 @@ 'name' => 'Another Shipping Category', 'handle' => 'anotherShippingCategory', 'description' => 'this is another shipping category', - 'default' => 0, + 'default' => false, 'storeId' => 1, // Primary ], ]; diff --git a/tests/unit/elements/variant/VariantQueryTest.php b/tests/unit/elements/variant/VariantQueryTest.php index b6d1410463..5228f05f14 100644 --- a/tests/unit/elements/variant/VariantQueryTest.php +++ b/tests/unit/elements/variant/VariantQueryTest.php @@ -102,11 +102,9 @@ private function _getShippingCategoryIdData(): array $shippingCategoryId, [ 'no-params' => [null, 3], - 'specific-id' => [$shippingCategoryId, 3], - 'in' => [[$shippingCategoryId, 99999], 3], + 'specific-id' => [$shippingCategoryId, 1], + 'in' => [[$shippingCategoryId, 99999], 1], 'not-in' => [['not', 99998, 99999], 3], - 'greater-than' => ['> ' . ($shippingCategoryId - 1), 3], - 'less-than' => ['< ' . ($shippingCategoryId), 0], ], ]; } @@ -118,15 +116,17 @@ public function testShippingCategory(): void { self::assertTrue(method_exists(Variant::find(), 'shippingCategoryId')); $fixture = $this->tester->grabFixture('shippingCategories'); - $matchingShippingCategory = new ShippingCategory(['id' => $fixture->data['anotherShippingCategory']['id']]); + $shippingCategoryId = $fixture->data['anotherShippingCategory']['id']; + + $matchingShippingCategory = new ShippingCategory(['id' => $shippingCategoryId]); $nonMatchingShippingCategory = new ShippingCategory(['id' => 99999]); $tests = [ 'no-params' => [null, 3], - 'specific-handle' => ['anotherShippingCategory', 3], + 'specific-handle' => ['anotherShippingCategory', 1], 'in' => [['anotherShippingCategory', 'general'], 3], 'not-in' => [['not', 'foo', 'bar'], 3], - 'matching-shipping-category' => [$matchingShippingCategory, 3], + 'matching-shipping-category' => [$matchingShippingCategory, 1], 'non-matching-shipping-category' => [$nonMatchingShippingCategory, 0], ];