items() ->whereNotNull('variant_id') ->orderBy('id') ->lockForUpdate() ->get(); foreach ($items as $item) { $variant = Variant::query()->lockForUpdate()->find($item->variant_id); if ($variant === null) { throw $this->unavailableVariant(); } $replacement = $this->latestReplacement($variant); if ($replacement->is($variant)) { if (! $variant->isSellable()) { throw $this->unavailableVariant(); } continue; } if (! $replacement->isSellable()) { throw $this->unavailableVariant(); } /** @var CartItem|null $targetItem */ $targetItem = $cart->items() ->whereKeyNot($item->getKey()) ->where('catalog_item_id', $item->catalog_item_id) ->where('variant_id', $replacement->getKey()) ->lockForUpdate() ->first(); $replacementQuantity = $item->cantidad + ($targetItem?->cantidad ?? 0); if ($replacement->inventory_id !== $variant->inventory_id) { $available = $this->inventory->availableQuantity($replacement); if ($available !== null && $available < $replacementQuantity) { throw $this->unavailableVariant(); } } if ($targetItem !== null) { $targetItem->cantidad += $item->cantidad; $targetItem->save(); $item->delete(); continue; } $item->update(['variant_id' => $replacement->getKey()]); } } private function latestReplacement(Variant $variant): Variant { $current = $variant; $visited = []; while ($current->replaced_by_variant_id !== null) { if (isset($visited[$current->getKey()])) { throw $this->unavailableVariant(); } $visited[$current->getKey()] = true; $current = Variant::query() ->lockForUpdate() ->find($current->replaced_by_variant_id) ?? throw $this->unavailableVariant(); } return $current; } private function unavailableVariant(): ValidationException { return ValidationException::withMessages([ 'cart_id' => [__('api.cart.cart_variant_unavailable')], ]); } }