feat(cart): implement cart management with add, update, and remove item functionalities

This commit is contained in:
2026-06-25 10:24:45 -03:00
parent a5b1539a80
commit 492c6c4ac2
12 changed files with 838 additions and 23 deletions

View File

@@ -3,6 +3,7 @@
namespace App\Domains\Cart\Models;
use App\Domains\Catalog\Models\ProductVariant;
use App\Domains\Tenant\Models\Tenant;
use App\Models\User;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -11,8 +12,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[Fillable([
'tenant_codigo',
'user_id',
'guest_token',
'status',
@@ -30,6 +33,14 @@ class Cart extends Model
];
}
/**
* @return BelongsTo<Tenant, $this>
*/
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
}
/**
* @return BelongsTo<User, $this>
*/
@@ -54,11 +65,8 @@ class Cart extends Model
]);
}
return DB::transaction(function () use ($productVariantId, $quantity) {
/** @var ProductVariant $variant */
$variant = ProductVariant::query()
->lockForUpdate()
->findOrFail($productVariantId);
return DB::transaction(function () use ($productVariantId, $quantity): CartItem {
$variant = $this->resolveScopedVariant($productVariantId, true);
if ($variant->stock < $quantity) {
throw ValidationException::withMessages([
@@ -68,18 +76,18 @@ class Cart extends Model
/** @var CartItem|null $item */
$item = $this->items()
->where('producto_variante_id', $productVariantId)
->where('producto_variante_id', $variant->getKey())
->lockForUpdate()
->first();
if ($item) {
$item->cantidad += $quantity;
$item->save();
} else {
if ($item === null) {
$item = $this->items()->create([
'producto_variante_id' => $productVariantId,
'producto_variante_id' => $variant->getKey(),
'cantidad' => $quantity,
]);
} else {
$item->cantidad += $quantity;
$item->save();
}
$variant->decrement('stock', $quantity);
@@ -96,18 +104,14 @@ class Cart extends Model
]);
}
return DB::transaction(function () use ($productVariantId, $quantity) {
return DB::transaction(function () use ($productVariantId, $quantity): CartItem {
/** @var CartItem $item */
$item = $this->items()
->where('producto_variante_id', $productVariantId)
->lockForUpdate()
->firstOrFail();
/** @var ProductVariant $variant */
$variant = ProductVariant::query()
->lockForUpdate()
->findOrFail($productVariantId);
$variant = $this->resolveScopedVariant($productVariantId, true);
$delta = $quantity - $item->cantidad;
if ($delta > 0 && $variant->stock < $delta) {
@@ -133,20 +137,36 @@ class Cart extends Model
public function removeItem(int $productVariantId): void
{
DB::transaction(function () use ($productVariantId) {
DB::transaction(function () use ($productVariantId): void {
/** @var CartItem $item */
$item = $this->items()
->where('producto_variante_id', $productVariantId)
->lockForUpdate()
->firstOrFail();
/** @var ProductVariant $variant */
$variant = ProductVariant::query()
->lockForUpdate()
->findOrFail($productVariantId);
$variant = $this->resolveScopedVariant($productVariantId, true);
$variant->increment('stock', $item->cantidad);
$item->delete();
});
}
protected function resolveScopedVariant(int $productVariantId, bool $lockForUpdate = false): ProductVariant
{
$query = ProductVariant::query()
->whereKey($productVariantId)
->whereHas('product', fn ($query) => $query->where('tenant_codigo', $this->tenant_codigo));
if ($lockForUpdate) {
$query->lockForUpdate();
}
/** @var ProductVariant|null $variant */
$variant = $query->first();
if ($variant === null) {
throw new NotFoundHttpException('Product variant not found for tenant.');
}
return $variant;
}
}