Skip to content

Shipping category for a variant should be set to default if no longer available #4022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: 5.x
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/elements/Variant.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
16 changes: 9 additions & 7 deletions src/services/ShippingCategories.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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' => '*',
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/data/shipping-category.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
'name' => 'Another Shipping Category',
'handle' => 'anotherShippingCategory',
'description' => 'this is another shipping category',
'default' => 0,
'default' => false,
'storeId' => 1, // Primary
],
];
14 changes: 7 additions & 7 deletions tests/unit/elements/variant/VariantQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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],
],
];
}
Expand All @@ -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],
];

Expand Down
Loading