feat(cart): enhance item update functionality to support variant changes and stock management; update related request validation and resource formatting; add tests for variant handling in cart
This commit is contained in:
@@ -54,16 +54,26 @@ class CartController extends Controller
|
||||
Tenant $tenant,
|
||||
CartItem $cartItem,
|
||||
): CartResource {
|
||||
$updatesVariant = $request->exists('variant_id');
|
||||
|
||||
return CartResource::make(
|
||||
$this->cartService->updateItemQuantity(
|
||||
$this->cartService->updateItem(
|
||||
$tenant,
|
||||
$request,
|
||||
$cartItem->getKey(),
|
||||
(int) $request->validated('cantidad'),
|
||||
$updatesVariant
|
||||
? ($request->validated('variant_id') !== null
|
||||
? (int) $request->validated('variant_id')
|
||||
: null)
|
||||
: $cartItem->variant_id,
|
||||
$updatesVariant,
|
||||
)
|
||||
)->additional([
|
||||
'code' => 'cart.quantity_updated',
|
||||
'message' => __('api.cart.quantity_updated'),
|
||||
'code' => $updatesVariant ? 'cart.item_updated' : 'cart.quantity_updated',
|
||||
'message' => $updatesVariant
|
||||
? __('api.cart.item_updated')
|
||||
: __('api.cart.quantity_updated'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -124,27 +124,85 @@ class Cart extends Model
|
||||
});
|
||||
}
|
||||
|
||||
public function updateItem(int $cartItemId, int $quantity): CartItem
|
||||
{
|
||||
public function updateItem(
|
||||
int $cartItemId,
|
||||
int $quantity,
|
||||
?int $variantId = null,
|
||||
bool $updateVariant = false,
|
||||
): CartItem {
|
||||
if ($quantity <= 0) {
|
||||
throw ValidationException::withMessages([
|
||||
'cantidad' => __('api.cart.positive_quantity'),
|
||||
]);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($cartItemId, $quantity): CartItem {
|
||||
return DB::transaction(function () use (
|
||||
$cartItemId,
|
||||
$quantity,
|
||||
$variantId,
|
||||
$updateVariant,
|
||||
): CartItem {
|
||||
/** @var CartItem $item */
|
||||
$item = $this->items()
|
||||
->where('id', $cartItemId)
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
|
||||
$selectedItem = $this->resolveScopedItem(
|
||||
$currentSelection = $this->resolveScopedItem(
|
||||
$item->catalog_item_id,
|
||||
$item->variant_id,
|
||||
true,
|
||||
);
|
||||
$inventoryService = app(CatalogInventoryService::class);
|
||||
|
||||
if ($updateVariant && $variantId !== $item->variant_id) {
|
||||
$nextSelection = $this->resolveScopedItem(
|
||||
$item->catalog_item_id,
|
||||
$variantId,
|
||||
true,
|
||||
);
|
||||
$otherVariantsQuantity = (int) $this->items()
|
||||
->where('catalog_item_id', $item->catalog_item_id)
|
||||
->whereKeyNot($item->getKey())
|
||||
->sum('cantidad');
|
||||
$this->assertUserPurchaseLimit(
|
||||
$nextSelection,
|
||||
$otherVariantsQuantity + $quantity,
|
||||
);
|
||||
|
||||
$inventoryService->release($currentSelection, $item->cantidad);
|
||||
$availableQuantity = $inventoryService->availableQuantity($nextSelection);
|
||||
|
||||
if ($availableQuantity !== null && $availableQuantity < $quantity) {
|
||||
throw ValidationException::withMessages([
|
||||
'variant_id' => __('api.cart.insufficient_stock', ['max' => $availableQuantity]),
|
||||
]);
|
||||
}
|
||||
|
||||
$targetItem = $this->items()
|
||||
->where('catalog_item_id', $item->catalog_item_id)
|
||||
->where('variant_id', $variantId)
|
||||
->whereKeyNot($item->getKey())
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
$inventoryService->reserve($nextSelection, $quantity);
|
||||
|
||||
if ($targetItem !== null) {
|
||||
$targetItem->cantidad += $quantity;
|
||||
$targetItem->save();
|
||||
$item->delete();
|
||||
|
||||
return $targetItem->fresh();
|
||||
}
|
||||
|
||||
$item->variant_id = $variantId;
|
||||
$item->cantidad = $quantity;
|
||||
$item->save();
|
||||
|
||||
return $item->fresh();
|
||||
}
|
||||
|
||||
$delta = $quantity - $item->cantidad;
|
||||
|
||||
if ($delta > 0) {
|
||||
@@ -153,12 +211,12 @@ class Cart extends Model
|
||||
->whereKeyNot($item->getKey())
|
||||
->sum('cantidad');
|
||||
$this->assertUserPurchaseLimit(
|
||||
$selectedItem,
|
||||
$currentSelection,
|
||||
$otherVariantsQuantity + $quantity,
|
||||
);
|
||||
}
|
||||
|
||||
$availableQuantity = $inventoryService->availableQuantity($selectedItem);
|
||||
$availableQuantity = $inventoryService->availableQuantity($currentSelection);
|
||||
|
||||
if ($delta > 0 && $availableQuantity !== null && $availableQuantity < $delta) {
|
||||
$maxAvailable = $availableQuantity + $item->cantidad;
|
||||
@@ -171,11 +229,11 @@ class Cart extends Model
|
||||
$item->save();
|
||||
|
||||
if ($delta > 0) {
|
||||
$inventoryService->reserve($selectedItem, $delta);
|
||||
$inventoryService->reserve($currentSelection, $delta);
|
||||
}
|
||||
|
||||
if ($delta < 0) {
|
||||
$inventoryService->release($selectedItem, abs($delta));
|
||||
$inventoryService->release($currentSelection, abs($delta));
|
||||
}
|
||||
|
||||
return $item->fresh();
|
||||
|
||||
@@ -19,7 +19,7 @@ class UpdateCartItemQuantityRequest extends FormRequest
|
||||
return [
|
||||
'cantidad' => ['required', 'integer', 'min:1'],
|
||||
'catalog_item_id' => ['prohibited'],
|
||||
'variant_id' => ['prohibited'],
|
||||
'variant_id' => ['sometimes', 'nullable', 'integer'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
namespace App\Domains\Cart\Resources;
|
||||
|
||||
use App\Domains\Cart\Models\CartItem;
|
||||
use App\Domains\Catalog\Enums\InventoryPolicy;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* @mixin CartItem
|
||||
@@ -36,10 +39,42 @@ class CartItemResource extends JsonResource
|
||||
'product' => $selectedItem === null ? null : [
|
||||
'nombre' => $selectedItem->getName(),
|
||||
'imagen' => $imageUrl,
|
||||
'variants' => $this->catalogItem->variants
|
||||
->map(fn (Variant $variant): array => [
|
||||
'id' => $variant->id,
|
||||
'precio' => $this->formatMoney($variant->getPrice()),
|
||||
'stock_tecnico' => $this->catalogItem->inventory_policy === InventoryPolicy::Unlimited
|
||||
? null
|
||||
: $variant->inventory->availableStock(),
|
||||
'values' => $this->variantValues($variant),
|
||||
])
|
||||
->values(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return Collection<string, string|array<int, string>> */
|
||||
private function variantValues(Variant $variant): Collection
|
||||
{
|
||||
$values = $variant->selectionValues();
|
||||
$eventDates = $variant->selectedEventDates();
|
||||
|
||||
if ($eventDates->isNotEmpty()) {
|
||||
$labels = $eventDates
|
||||
->map(fn ($eventDate): string => $eventDate->date->format('d/m/Y').' · '
|
||||
.substr($eventDate->time_start, 0, 5).' a '
|
||||
.substr($eventDate->time_end, 0, 5))
|
||||
->values();
|
||||
|
||||
$values->put(
|
||||
'event_date',
|
||||
$labels->count() === 1 ? $labels->first() : $labels->all(),
|
||||
);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
protected function formatMoney(float|int|string|null $amount): string
|
||||
{
|
||||
return number_format((float) ($amount ?? 0), 2, '.', '');
|
||||
|
||||
@@ -51,11 +51,17 @@ class CartService
|
||||
];
|
||||
}
|
||||
|
||||
public function updateItemQuantity(Tenant $tenant, Request $request, int $cartItemId, int $quantity): Cart
|
||||
{
|
||||
public function updateItem(
|
||||
Tenant $tenant,
|
||||
Request $request,
|
||||
int $cartItemId,
|
||||
int $quantity,
|
||||
?int $variantId,
|
||||
bool $updateVariant,
|
||||
): Cart {
|
||||
$identity = $this->requireIdentity($request);
|
||||
$cart = $this->findCartOrFail($tenant, $identity);
|
||||
$cart->updateItem($cartItemId, $quantity);
|
||||
$cart->updateItem($cartItemId, $quantity, $variantId, $updateVariant);
|
||||
|
||||
return $this->loadCart($cart);
|
||||
}
|
||||
@@ -101,6 +107,10 @@ class CartService
|
||||
return $cart->fresh()->load([
|
||||
'items.catalogItem.attachments',
|
||||
'items.catalogItem.inventory',
|
||||
'items.catalogItem.variants.inventory',
|
||||
'items.catalogItem.variants.definitions.itemAttribute.attribute',
|
||||
'items.catalogItem.variants.eventDates',
|
||||
'items.catalogItem.variants.eventDate',
|
||||
'items.variant.attachments',
|
||||
'items.variant.inventory',
|
||||
'items.variant.definitions.itemAttribute.attribute',
|
||||
|
||||
Reference in New Issue
Block a user