diff --git a/app/Domains/Cart/Models/Cart.php b/app/Domains/Cart/Models/Cart.php new file mode 100644 index 0000000..3c84560 --- /dev/null +++ b/app/Domains/Cart/Models/Cart.php @@ -0,0 +1,152 @@ + 'integer', + ]; + } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * @return HasMany + */ + public function items(): HasMany + { + return $this->hasMany(CartItem::class, 'cart_id'); + } + + public function addItem(int $productVariantId, int $quantity): CartItem + { + if ($quantity <= 0) { + throw ValidationException::withMessages([ + 'cantidad' => 'La cantidad debe ser mayor a cero.', + ]); + } + + return DB::transaction(function () use ($productVariantId, $quantity) { + /** @var ProductVariant $variant */ + $variant = ProductVariant::query() + ->lockForUpdate() + ->findOrFail($productVariantId); + + if ($variant->stock < $quantity) { + throw ValidationException::withMessages([ + 'cantidad' => 'Stock insuficiente para la variante solicitada.', + ]); + } + + /** @var CartItem|null $item */ + $item = $this->items() + ->where('producto_variante_id', $productVariantId) + ->lockForUpdate() + ->first(); + + if ($item) { + $item->cantidad += $quantity; + $item->save(); + } else { + $item = $this->items()->create([ + 'producto_variante_id' => $productVariantId, + 'cantidad' => $quantity, + ]); + } + + $variant->decrement('stock', $quantity); + + return $item->fresh(); + }); + } + + public function updateItem(int $productVariantId, int $quantity): CartItem + { + if ($quantity <= 0) { + throw ValidationException::withMessages([ + 'cantidad' => 'La cantidad debe ser mayor a cero.', + ]); + } + + return DB::transaction(function () use ($productVariantId, $quantity) { + /** @var CartItem $item */ + $item = $this->items() + ->where('producto_variante_id', $productVariantId) + ->lockForUpdate() + ->firstOrFail(); + + /** @var ProductVariant $variant */ + $variant = ProductVariant::query() + ->lockForUpdate() + ->findOrFail($productVariantId); + + $delta = $quantity - $item->cantidad; + + if ($delta > 0 && $variant->stock < $delta) { + throw ValidationException::withMessages([ + 'cantidad' => 'Stock insuficiente para actualizar la cantidad solicitada.', + ]); + } + + $item->cantidad = $quantity; + $item->save(); + + if ($delta > 0) { + $variant->decrement('stock', $delta); + } + + if ($delta < 0) { + $variant->increment('stock', abs($delta)); + } + + return $item->fresh(); + }); + } + + public function removeItem(int $productVariantId): void + { + DB::transaction(function () use ($productVariantId) { + /** @var CartItem $item */ + $item = $this->items() + ->where('producto_variante_id', $productVariantId) + ->lockForUpdate() + ->firstOrFail(); + + /** @var ProductVariant $variant */ + $variant = ProductVariant::query() + ->lockForUpdate() + ->findOrFail($productVariantId); + + $variant->increment('stock', $item->cantidad); + $item->delete(); + }); + } +} diff --git a/app/Domains/Cart/Models/CartItem.php b/app/Domains/Cart/Models/CartItem.php new file mode 100644 index 0000000..1062182 --- /dev/null +++ b/app/Domains/Cart/Models/CartItem.php @@ -0,0 +1,46 @@ + 'integer', + 'producto_variante_id' => 'integer', + 'cantidad' => 'integer', + ]; + } + + /** + * @return BelongsTo + */ + public function cart(): BelongsTo + { + return $this->belongsTo(Cart::class, 'cart_id'); + } + + /** + * @return BelongsTo + */ + public function variant(): BelongsTo + { + return $this->belongsTo(ProductVariant::class, 'producto_variante_id'); + } +}