PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | api/Resource/ProductResource.php +30 -6 1.6.2 → 1.6.5 View file →
@@ -210,12 +210,15 @@
210 210 }
211 211
212 212 $variantData = $variant;
213 213
214 - // Remove empty sku and shipping_class to prevent unique constraint violation
215 - if (array_key_exists('sku', $variantData) && empty($variantData['sku'])) {
216 - unset($variantData['sku']);
214 + // An explicitly cleared sku ('' or null) must persist as NULL so the
215 + // stored value is actually cleared, while still avoiding the sku_unique
216 + // constraint (MySQL treats multiple NULLs as distinct, unlike '').
217 + if (array_key_exists('sku', $variantData) && ($variantData['sku'] === '' || $variantData['sku'] === null)) {
218 + $variantData['sku'] = null;
217 219 }
220 + // Remove empty shipping_class to prevent unique constraint violation
218 221 if (array_key_exists('shipping_class', $variantData) && empty($variantData['shipping_class'])) {
219 222 unset($variantData['shipping_class']);
220 223 }
221 224
@@ -257,10 +260,28 @@
257 260 $variant[$column] = Helper::roundCent(Arr::get($variant, $column));
258 261 }
259 262 }
260 263 unset($variant['rowId']);
261 - $variant['serial_index'] = $index + 1;
262 264
265 + // serial_index is display ordering the caller owns, not a column derived
266 + // from this loop. An advanced-variation save carries only the rows the
267 + // merchant actually touched, so deriving it from the payload index
268 + // renumbered an edited row to the front and left two variations sharing a
269 + // position. The reorder path sends an explicit serial_index for every row
270 + // and bulk edit round-trips the stored one, so an absent value means
271 + // "unchanged": drop the column and let batchUpdate's `ELSE serial_index`
272 + // keep what is stored.
273 + if (!isset($variant['serial_index']) || $variant['serial_index'] === '') {
274 + unset($variant['serial_index']);
275 + }
276 +
277 + // An explicitly cleared sku ('' or null) must persist as NULL so the
278 + // stored value is actually cleared, while still avoiding the sku_unique
279 + // constraint (MySQL treats multiple NULLs as distinct, unlike '').
280 + if (array_key_exists('sku', $variant) && ($variant['sku'] === '' || $variant['sku'] === null)) {
281 + $variant['sku'] = null;
282 + }
283 +
263 284 // Recalculate stock_status from available and manage_stock
264 285 if (isset($variant['manage_stock'])) {
265 286 if ($variant['manage_stock']) {
266 287 $avail = intval(Arr::get($variant, 'available', 0));
@@ -307,10 +328,13 @@
307 328
308 329
309 330 }
310 331
311 - $defaultVariationId = Arr::get($detail, 'default_variation_id');
312 - $detail['default_variation_id'] = $defaultVariationId;
332 + // Deliberately NOT defaulted here. $detail is a partial row — the editor
333 + // stages only what the merchant touched — so materialising this key as null
334 + // told ProductDetailResource::update() to clear the stored Default Variant
335 + // on every unrelated save (an inline price edit was enough). Absent now
336 + // means "unchanged"; an explicit empty value still clears it.
313 337
314 338 // Recalculate min_price / max_price from current variant prices
315 339 $variantPriceRange = ProductVariation::query()
316 340 ->where('post_id', $postId)