feat(checkout): route carts to replacement variants
This commit is contained in:
99
app/Domains/Cart/Services/CartVariantReplacementService.php
Normal file
99
app/Domains/Cart/Services/CartVariantReplacementService.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Cart\Services;
|
||||
|
||||
use App\Domains\Cart\Models\Cart;
|
||||
use App\Domains\Cart\Models\CartItem;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Catalog\Services\CatalogInventoryService;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CartVariantReplacementService
|
||||
{
|
||||
public function __construct(private readonly CatalogInventoryService $inventory) {}
|
||||
|
||||
public function replaceHistoricalVariants(Cart $cart): void
|
||||
{
|
||||
$items = $cart->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')],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user