From c05206cc51b9af5289372dbc439a9f67a3d84e45 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Tue, 14 Jul 2026 16:43:00 -0300 Subject: [PATCH] feat: refactor cart item handling to use mapped buyable types and improve method signatures --- .../Cart/Controllers/CartController.php | 12 +++++----- app/Domains/Cart/Models/Cart.php | 22 +++++++------------ .../Cart/Requests/AddCartItemRequest.php | 9 ++++++++ .../Cart/Resources/CartItemResource.php | 13 +++++++++-- app/Domains/Cart/Services/CartService.php | 8 +++---- app/Domains/Cart/routes/api.php | 4 ++-- .../Resources/PurchaseItemResource.php | 13 +++++++++-- 7 files changed, 50 insertions(+), 31 deletions(-) diff --git a/app/Domains/Cart/Controllers/CartController.php b/app/Domains/Cart/Controllers/CartController.php index e9faae7..ee914d1 100644 --- a/app/Domains/Cart/Controllers/CartController.php +++ b/app/Domains/Cart/Controllers/CartController.php @@ -29,7 +29,7 @@ class CartController extends Controller $result = $this->cartService->addItem( $tenant, $request, - $request->validated('buyable_type'), + $request->mappedBuyableType(), (int) $request->validated('buyable_id'), (int) $request->validated('cantidad'), ); @@ -48,24 +48,22 @@ class CartController extends Controller public function updateItemQuantity( UpdateCartItemQuantityRequest $request, Tenant $tenant, - string $buyableType, - int $buyableId, + \App\Domains\Cart\Models\CartItem $cartItem, ): CartResource { return CartResource::make( $this->cartService->updateItemQuantity( $tenant, $request, - $buyableType, - $buyableId, + $cartItem->getKey(), (int) $request->validated('cantidad'), ) )->additional(['message' => 'Cantidad de producto actualizada.']); } - public function removeItem(Request $request, Tenant $tenant, string $buyableType, int $buyableId): CartResource + public function removeItem(Request $request, Tenant $tenant, \App\Domains\Cart\Models\CartItem $cartItem): CartResource { return CartResource::make( - $this->cartService->removeItem($tenant, $request, $buyableType, $buyableId) + $this->cartService->removeItem($tenant, $request, $cartItem->getKey()) )->additional(['message' => 'Producto eliminado del carrito.']); } } diff --git a/app/Domains/Cart/Models/Cart.php b/app/Domains/Cart/Models/Cart.php index 4455bc8..25459a4 100644 --- a/app/Domains/Cart/Models/Cart.php +++ b/app/Domains/Cart/Models/Cart.php @@ -116,7 +116,7 @@ class Cart extends Model }); } - public function updateItem(string $buyableType, int $buyableId, int $quantity): CartItem + public function updateItem(int $cartItemId, int $quantity): CartItem { if ($quantity <= 0) { throw ValidationException::withMessages([ @@ -124,17 +124,14 @@ class Cart extends Model ]); } - return DB::transaction(function () use ($buyableType, $buyableId, $quantity): CartItem { - $canonicalType = $this->resolveBuyableClass($buyableType); - + return DB::transaction(function () use ($cartItemId, $quantity): CartItem { /** @var CartItem $item */ $item = $this->items() - ->where('buyable_type', $canonicalType) - ->where('buyable_id', $buyableId) + ->where('id', $cartItemId) ->lockForUpdate() ->firstOrFail(); - $buyable = $this->resolveScopedBuyable($buyableType, $buyableId, true); + $buyable = $this->resolveScopedBuyable($item->buyable_type, $item->buyable_id, true); $delta = $quantity - $item->cantidad; $availableQuantity = $buyable->availableQuantity(); @@ -160,19 +157,16 @@ class Cart extends Model }); } - public function removeItem(string $buyableType, int $buyableId): void + public function removeItem(int $cartItemId): void { - DB::transaction(function () use ($buyableType, $buyableId): void { - $canonicalType = $this->resolveBuyableClass($buyableType); - + DB::transaction(function () use ($cartItemId): void { /** @var CartItem $item */ $item = $this->items() - ->where('buyable_type', $canonicalType) - ->where('buyable_id', $buyableId) + ->where('id', $cartItemId) ->lockForUpdate() ->firstOrFail(); - $buyable = $this->resolveScopedBuyable($buyableType, $buyableId, true); + $buyable = $this->resolveScopedBuyable($item->buyable_type, $item->buyable_id, true); $buyable->decrementReservedStock($item->cantidad); $item->delete(); }); diff --git a/app/Domains/Cart/Requests/AddCartItemRequest.php b/app/Domains/Cart/Requests/AddCartItemRequest.php index 0c7a154..1e3ec0b 100644 --- a/app/Domains/Cart/Requests/AddCartItemRequest.php +++ b/app/Domains/Cart/Requests/AddCartItemRequest.php @@ -22,4 +22,13 @@ class AddCartItemRequest extends FormRequest 'cantidad' => ['required', 'integer', 'min:1'], ]; } + + public function mappedBuyableType(): string + { + return match ($this->input('buyable_type')) { + 'variant' => \App\Domains\Catalog\Models\ProductVariant::class, + 'bundle' => \App\Domains\Bundle\Models\Bundle::class, + default => throw new \InvalidArgumentException('Invalid buyable type'), + }; + } } diff --git a/app/Domains/Cart/Resources/CartItemResource.php b/app/Domains/Cart/Resources/CartItemResource.php index 9f8214b..fc13f25 100644 --- a/app/Domains/Cart/Resources/CartItemResource.php +++ b/app/Domains/Cart/Resources/CartItemResource.php @@ -22,7 +22,7 @@ class CartItemResource extends JsonResource $precio = $buyable?->getPrice(); $imageUrl = null; - if ($this->buyable_type === 'variant' && $buyable && $buyable->relationLoaded('attachments')) { + if ($this->buyable_type === \App\Domains\Catalog\Models\ProductVariant::class && $buyable && $buyable->relationLoaded('attachments')) { $firstAttachment = $buyable->attachments->first(); if ($firstAttachment) { $imageUrl = $firstAttachment->getTemporaryUrl(1440); @@ -33,7 +33,7 @@ class CartItemResource extends JsonResource 'id' => $this->id, 'cantidad' => $this->cantidad, 'precio_unitario' => $this->formatMoney($precio), - 'buyable_type' => $this->buyable_type, + 'buyable_type' => $this->mapBuyableTypeToAlias($this->buyable_type), 'buyable_id' => $this->buyable_id, 'product' => $buyable === null ? null : [ 'nombre' => $productName, @@ -42,6 +42,15 @@ class CartItemResource extends JsonResource ]; } + protected function mapBuyableTypeToAlias(?string $type): string + { + return match ($type) { + \App\Domains\Catalog\Models\ProductVariant::class => 'variant', + \App\Domains\Bundle\Models\Bundle::class => 'bundle', + default => 'unknown', + }; + } + protected function formatMoney(float|int|string|null $amount): string { return number_format((float) ($amount ?? 0), 2, '.', ''); diff --git a/app/Domains/Cart/Services/CartService.php b/app/Domains/Cart/Services/CartService.php index 417579a..c790f6e 100644 --- a/app/Domains/Cart/Services/CartService.php +++ b/app/Domains/Cart/Services/CartService.php @@ -45,20 +45,20 @@ class CartService ]; } - public function updateItemQuantity(Tenant $tenant, Request $request, string $buyableType, int $buyableId, int $quantity): Cart + public function updateItemQuantity(Tenant $tenant, Request $request, int $cartItemId, int $quantity): Cart { $identity = $this->requireIdentity($request); $cart = $this->findCartOrFail($tenant, $identity); - $cart->updateItem($buyableType, $buyableId, $quantity); + $cart->updateItem($cartItemId, $quantity); return $this->loadCart($cart); } - public function removeItem(Tenant $tenant, Request $request, string $buyableType, int $buyableId): Cart + public function removeItem(Tenant $tenant, Request $request, int $cartItemId): Cart { $identity = $this->requireIdentity($request); $cart = $this->findCartOrFail($tenant, $identity); - $cart->removeItem($buyableType, $buyableId); + $cart->removeItem($cartItemId); return $this->loadCart($cart); } diff --git a/app/Domains/Cart/routes/api.php b/app/Domains/Cart/routes/api.php index b127a80..4f8c347 100644 --- a/app/Domains/Cart/routes/api.php +++ b/app/Domains/Cart/routes/api.php @@ -8,6 +8,6 @@ Route::prefix('tenants/{tenant:codigo}') ->group(function (): void { Route::get('cart', [CartController::class, 'show']); Route::post('cart/items', [CartController::class, 'addItem']); - Route::patch('cart/items/{buyableType}/{buyableId}', [CartController::class, 'updateItemQuantity']); - Route::delete('cart/items/{buyableType}/{buyableId}', [CartController::class, 'removeItem']); + Route::patch('cart/items/{cartItem}', [CartController::class, 'updateItemQuantity']); + Route::delete('cart/items/{cartItem}', [CartController::class, 'removeItem']); }); diff --git a/app/Domains/Purchase/Resources/PurchaseItemResource.php b/app/Domains/Purchase/Resources/PurchaseItemResource.php index 54a9e6a..875d770 100644 --- a/app/Domains/Purchase/Resources/PurchaseItemResource.php +++ b/app/Domains/Purchase/Resources/PurchaseItemResource.php @@ -25,7 +25,7 @@ class PurchaseItemResource extends JsonResource $imageUrl = null; $attributes = []; - if ($this->buyable_type === 'variant' && $buyable) { + if ($this->buyable_type === \App\Domains\Catalog\Models\ProductVariant::class && $buyable) { $imageUrl = $this->resolveImageUrl($buyable); $attributes = $this->resolveAttributes($buyable); } @@ -35,7 +35,7 @@ class PurchaseItemResource extends JsonResource 'quantity' => $quantity, 'unit_price' => $this->formatMoney($unitPrice), 'line_total' => $this->formatMoney($lineTotal), - 'buyable_type' => $this->buyable_type, + 'buyable_type' => $this->mapBuyableTypeToAlias($this->buyable_type), 'buyable_id' => $this->buyable_id, 'item_details' => $buyable === null ? null : [ 'nombre' => $buyable->getName(), @@ -45,6 +45,15 @@ class PurchaseItemResource extends JsonResource ]; } + protected function mapBuyableTypeToAlias(?string $type): string + { + return match ($type) { + \App\Domains\Catalog\Models\ProductVariant::class => 'variant', + \App\Domains\Bundle\Models\Bundle::class => 'bundle', + default => 'unknown', + }; + } + protected function resolveUnitPrice(): float { if ($this->resource instanceof PurchaseItem) {