feat: implement cart service and resource to manage user and guest shopping carts

This commit is contained in:
2026-07-01 14:56:12 -03:00
parent a7dba4dc10
commit a79f58d520
3 changed files with 57 additions and 32 deletions

View File

@@ -2,7 +2,6 @@
namespace App\Domains\Cart\Resources;
use App\Domains\Catalog\Resources\ProductVariantDefinitionResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -19,19 +18,39 @@ class CartItemResource extends JsonResource
$variant = $this->variant;
$product = $variant?->product;
$attributesText = '';
if ($variant && $variant->relationLoaded('definitions')) {
$attributesText = $variant->definitions
->map(function ($definition) {
$attrName = $definition->productAttribute?->attribute?->nombre;
$value = $definition->value;
return $attrName ? "{$attrName}: {$value}" : $value;
})
->filter()
->implode(', ');
}
$productName = $product?->nombre;
if ($productName && $attributesText !== '') {
$productName .= " ({$attributesText})";
}
$imageUrl = null;
if ($variant && $variant->relationLoaded('attachments')) {
$firstAttachment = $variant->attachments->first();
if ($firstAttachment) {
$imageUrl = $firstAttachment->getTemporaryUrl(1440);
}
}
return [
'id' => $this->id,
'cantidad' => $this->cantidad,
'precio_unitario' => $this->formatMoney($product?->precio),
'subtotal' => $this->formatMoney(($product?->precio ?? 0) * $this->cantidad),
'product_id' => $product?->id,
'product' => $product === null ? null : [
'id' => $product->id,
'nombre' => $product->nombre,
'slug' => $product->slug,
],
'variant' => $variant === null ? null : [
'id' => $variant->id,
'definitions' => ProductVariantDefinitionResource::collection($variant->definitions),
'nombre' => $productName,
'imagen' => $imageUrl,
],
];
}

View File

@@ -95,6 +95,7 @@ class CartService
return $cart->fresh()->load([
'items.variant.product',
'items.variant.definitions.productAttribute.attribute',
'items.variant.attachments',
]);
}

View File

@@ -5,6 +5,7 @@ namespace Tests\Feature\Cart;
use App\Domains\Catalog\Models\Product;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\ProductVariant;
use App\Domains\Catalog\Models\ProductAttribute;
use App\Domains\Catalog\Models\ProductVariantDefinition;
use App\Domains\Tenant\Models\Tenant;
use App\Models\User;
@@ -22,11 +23,13 @@ class CartControllerTest extends TestCase
$this->getJson('/api/tenants/acme/cart')
->assertOk()
->assertJson([
'data' => [
'id' => null,
'tenant_codigo' => 'acme',
'status' => 'active',
'items' => [],
'subtotal' => '0.00',
]
]);
}
@@ -40,9 +43,14 @@ class CartControllerTest extends TestCase
'type' => 'string',
]);
$productAttribute = ProductAttribute::query()->create([
'product_id' => $variant->producto_id,
'attribute_id' => $attribute->id,
]);
ProductVariantDefinition::query()->create([
'producto_variante_id' => $variant->id,
'attribute_id' => $attribute->id,
'products_attribute_id' => $productAttribute->id,
'value' => 'Red',
]);
@@ -54,14 +62,13 @@ class CartControllerTest extends TestCase
$response
->assertOk()
->assertCookie('guest_token')
->assertJsonPath('tenant_codigo', 'acme')
->assertJsonPath('items.0.cantidad', 2)
->assertJsonPath('items.0.precio_unitario', '49.90')
->assertJsonPath('items.0.subtotal', '99.80')
->assertJsonPath('items.0.product.id', $variant->product->id)
->assertJsonPath('items.0.variant.id', $variant->id)
->assertJsonPath('items.0.variant.definitions.0.attribute.codigo', 'color')
->assertJsonPath('subtotal', '99.80');
->assertJsonPath('data.tenant_codigo', 'acme')
->assertJsonPath('data.items.0.cantidad', 2)
->assertJsonPath('data.items.0.precio_unitario', '49.90')
->assertJsonPath('data.items.0.product_id', $variant->product->id)
->assertJsonPath('data.items.0.product.nombre', 'Shirt acme (Color: Red)')
->assertJsonPath('data.items.0.product.imagen', null)
->assertJsonPath('data.subtotal', '99.80');
$this->assertDatabaseHas('carritos', [
'tenant_codigo' => 'acme',
@@ -97,9 +104,8 @@ class CartControllerTest extends TestCase
'cantidad' => 3,
])
->assertOk()
->assertJsonPath('items.0.cantidad', 5)
->assertJsonPath('items.0.subtotal', '125.00')
->assertJsonPath('subtotal', '125.00');
->assertJsonPath('data.items.0.cantidad', 5)
->assertJsonPath('data.subtotal', '125.00');
$this->assertDatabaseCount('carritos', 1);
$this->assertDatabaseCount('carrito_items', 1);
@@ -129,9 +135,8 @@ class CartControllerTest extends TestCase
'cantidad' => 5,
])
->assertOk()
->assertJsonPath('items.0.cantidad', 5)
->assertJsonPath('items.0.subtotal', '75.00')
->assertJsonPath('subtotal', '75.00');
->assertJsonPath('data.items.0.cantidad', 5)
->assertJsonPath('data.subtotal', '75.00');
$this->assertDatabaseHas('carrito_items', [
'producto_variante_id' => $variant->id,
@@ -157,8 +162,8 @@ class CartControllerTest extends TestCase
$this->withCookie('guest_token', $guestToken)
->deleteJson("/api/tenants/acme/cart/items/{$variant->id}")
->assertOk()
->assertJsonPath('items', [])
->assertJsonPath('subtotal', '0.00');
->assertJsonPath('data.items', [])
->assertJsonPath('data.subtotal', '0.00');
$this->assertDatabaseCount('carrito_items', 0);
$this->assertDatabaseHas('productos_variantes', [
@@ -187,7 +192,7 @@ class CartControllerTest extends TestCase
'cantidad' => 2,
])
->assertOk()
->assertJsonPath('subtotal', '50.00');
->assertJsonPath('data.subtotal', '50.00');
$this->actingAs($user)
->postJson('/api/tenants/globex/cart/items', [