Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Fixed a PHP error that could occur when using partial payments in the example templates. ([#3989](https://github.com/craftcms/commerce/issues/3989))
- Fixed a bug where `craft\commerce\elements\Variant::getUrl()` did not trigger the standard element url events.
- Fixed a bug where matching custom shipping methods that don’t use shipping rules weren’t having shipping adjustments applied to carts.
- 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))

## 5.4.3 - 2025-07-23

Expand Down
11 changes: 11 additions & 0 deletions src/elements/Variant.php
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,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 @@ -203,16 +204,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 @@ -232,12 +233,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