Skip to content

Commit bd37890

Browse files
authored
Merge pull request #3992 from craftcms/feature/pt-2755-4x-saving-variants-programmatically-doesnt-update-the
[4.x] Fix product default variant data not being updated when saving variantAdd failing test
2 parents f9bd349 + cc550e0 commit bd37890

3 files changed

Lines changed: 158 additions & 0 deletions

File tree

src/elements/Product.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,15 @@ public function afterSave(bool $isNew): void
889889
$record->defaultWidth = $defaultVariant->width ?? 0;
890890
$record->defaultWeight = $defaultVariant->weight ?? 0;
891891

892+
// Make sure to update the object
893+
$this->defaultVariantId = $defaultVariant->id ?? null;
894+
$this->defaultSku = $defaultVariant->skuAsText ?? '';
895+
$this->defaultPrice = $defaultVariant->price ?? 0.0;
896+
$this->defaultHeight = $defaultVariant->height ?? 0;
897+
$this->defaultLength = $defaultVariant->length ?? 0;
898+
$this->defaultWidth = $defaultVariant->width ?? 0;
899+
$this->defaultWeight = $defaultVariant->weight ?? 0;
900+
892901
// We want to always have the same date as the element table, based on the logic for updating these in the element service i.e resaving
893902
$record->dateUpdated = $this->dateUpdated;
894903
$record->dateCreated = $this->dateCreated;

src/elements/Variant.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use craft\elements\conditions\ElementConditionInterface;
3131
use craft\gql\types\DateTime;
3232
use craft\helpers\ArrayHelper;
33+
use craft\helpers\Db;
3334
use craft\helpers\Html;
3435
use craft\models\FieldLayout;
3536
use Throwable;
@@ -1006,6 +1007,19 @@ public function afterSave(bool $isNew): void
10061007
}
10071008

10081009
$record->save(false);
1010+
1011+
// Fallback updating of default variant data if the variant is being saved in isolation
1012+
if ($this->duplicateOf === null && $this->isDefault) {
1013+
Db::update(Table::PRODUCTS, [
1014+
'defaultVariantId' => $this->id,
1015+
'defaultSku' => $this->getSkuAsText(),
1016+
'defaultPrice' => $this->price ?? 0.0,
1017+
'defaultHeight' => $this->height ?? 0,
1018+
'defaultLength' => $this->length ?? 0,
1019+
'defaultWidth' => $this->width ?? 0,
1020+
'defaultWeight' => $this->weight ?? 0,
1021+
], ['id' => $this->productId]);
1022+
}
10091023
}
10101024

10111025
parent::afterSave($isNew);

tests/unit/elements/product/ProductTest.php

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
namespace craftcommercetests\unit\elements\product;
99

1010
use Codeception\Test\Unit;
11+
use craft\commerce\db\Table;
1112
use craft\commerce\elements\Product;
1213
use craft\commerce\elements\Variant;
14+
use craft\db\Query;
1315
use craftcommercetests\fixtures\ProductFixture;
16+
use DateTime;
1417

1518
/**
1619
* ProductTest
@@ -136,4 +139,136 @@ public function productVariantMethodsDataProvider(): array
136139
],
137140
];
138141
}
142+
143+
/**
144+
* @return void
145+
*/
146+
public function testSaveProductAndVariants(): void
147+
{
148+
$product = new Product();
149+
$product->title = 'Test Product';
150+
$product->typeId = 2000;
151+
$product->slug = 'test-product';
152+
$product->enabled = true;
153+
$product->enabledForSite = true;
154+
$product->availableForPurchase = true;
155+
$product->promotable = true;
156+
$product->postDate = (new DateTime('now'));
157+
$product->shippingCategoryId = 101;
158+
$product->taxCategoryId = 101;
159+
160+
$variants = [];
161+
$variant = new Variant();
162+
$variant->title = 'Test Variant';
163+
$variant->slug = 'test-variant';
164+
$variant->sku = 'test-variant-sku';
165+
$variant->price = 99.99;
166+
$variant->sortOrder = 0;
167+
$variant->width = null;
168+
$variant->height = null;
169+
$variant->length = null;
170+
$variant->weight = null;
171+
$variant->stock = null;
172+
$variant->hasUnlimitedStock = true;
173+
$variant->minQty = null;
174+
$variant->maxQty = null;
175+
$variant->isDefault = true;
176+
177+
$variants[] = $variant;
178+
179+
$variant = new Variant();
180+
$variant->title = 'Test Variant 2';
181+
$variant->slug = 'test-variant 2';
182+
$variant->sku = 'test-variant-sku2';
183+
$variant->price = 100.99;
184+
$variant->sortOrder = 1;
185+
$variant->width = null;
186+
$variant->height = null;
187+
$variant->length = null;
188+
$variant->weight = null;
189+
$variant->stock = null;
190+
$variant->hasUnlimitedStock = true;
191+
$variant->minQty = null;
192+
$variant->maxQty = null;
193+
$variant->isDefault = false;
194+
$variants[] = $variant;
195+
196+
$product->setVariants($variants);
197+
198+
\Craft::$app->getElements()->saveElement($product, false);
199+
200+
// Check default data when the variant is saved as part of the product save
201+
$productData = (new Query())
202+
->select([
203+
'defaultVariantId',
204+
'defaultSku',
205+
'defaultPrice',
206+
'defaultWidth',
207+
'defaultHeight',
208+
'defaultLength',
209+
'defaultWeight',
210+
])
211+
->from(Table::PRODUCTS)
212+
->where(['id' => $product->id])
213+
->one();
214+
215+
$defaultVariantData = (new Query())
216+
->select([
217+
'id',
218+
])
219+
->from(Table::VARIANTS)
220+
->where(['productId' => $product->id])
221+
->andWhere(['sku' => 'test-variant-sku'])
222+
->one();
223+
224+
// Check the product object
225+
self::assertEquals($defaultVariantData['id'], $product->defaultVariantId);
226+
self::assertEquals('test-variant-sku', $product->defaultSku);
227+
self::assertEquals(99.99, $product->defaultPrice);
228+
self::assertEquals(0, $product->defaultWidth);
229+
self::assertEquals(0, $product->defaultHeight);
230+
self::assertEquals(0, $product->defaultLength);
231+
self::assertEquals(0, $product->defaultWeight);
232+
233+
// Check the product data in the database
234+
self::assertEquals($defaultVariantData['id'], $productData['defaultVariantId']);
235+
self::assertEquals('test-variant-sku', $productData['defaultSku']);
236+
self::assertEquals(99.99, $productData['defaultPrice']);
237+
self::assertEquals(0, $productData['defaultWidth']);
238+
self::assertEquals(0, $productData['defaultHeight']);
239+
self::assertEquals(0, $productData['defaultLength']);
240+
self::assertEquals(0, $productData['defaultWeight']);
241+
242+
// Make changes and independently save the default variant to check the product data is updated
243+
$variant = $product->getDefaultVariant();
244+
$variant->setSku('test-variant-sku-updated');
245+
$variant->price = 199.99;
246+
247+
\Craft::$app->getElements()->saveElement($variant, false);
248+
249+
$newProductData = (new Query())
250+
->select([
251+
'defaultVariantId',
252+
'defaultSku',
253+
'defaultPrice',
254+
'defaultWidth',
255+
'defaultHeight',
256+
'defaultLength',
257+
'defaultWeight',
258+
])
259+
->from(Table::PRODUCTS)
260+
->where(['id' => $product->id])
261+
->one();
262+
263+
self::assertEquals($defaultVariantData['id'], $newProductData['defaultVariantId']);
264+
self::assertEquals('test-variant-sku-updated', $newProductData['defaultSku']);
265+
self::assertEquals(199.99, $newProductData['defaultPrice']);
266+
self::assertEquals(0, $newProductData['defaultWidth']);
267+
self::assertEquals(0, $newProductData['defaultHeight']);
268+
self::assertEquals(0, $newProductData['defaultLength']);
269+
self::assertEquals(0, $newProductData['defaultWeight']);
270+
271+
// Remove the product
272+
\Craft::$app->getElements()->deleteElementById($product->id, Product::class, null, true);
273+
}
139274
}

0 commit comments

Comments
 (0)