feat: Implement inventory policy for product variants
- Introduced InventoryPolicy enum to manage tracked and unlimited inventory types. - Updated ProductVariant model to include inventory_policy and cantidad_vendida attributes. - Modified addItem and updateItem methods in Cart to check available quantity based on inventory policy. - Added migration to include inventory_policy and cantidad_vendida in productos_variantes table. - Enhanced tests to validate inventory behavior for both tracked and unlimited policies. - Updated various request and resource classes to handle new inventory fields.
This commit is contained in:
@@ -3,17 +3,18 @@
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Catalog\Enums\InventoryPolicy;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
|
||||
#[Fillable([
|
||||
'producto_id',
|
||||
'inventory_policy',
|
||||
'stock_real',
|
||||
'stock_reservado',
|
||||
'stock',
|
||||
@@ -30,9 +31,20 @@ class ProductVariant extends Model
|
||||
|
||||
protected $appends = ['stock_tecnico'];
|
||||
|
||||
protected $attributes = [
|
||||
'inventory_policy' => 'tracked',
|
||||
'stock_real' => 0,
|
||||
'stock_reservado' => 0,
|
||||
'cantidad_vendida' => 0,
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::saving(function (ProductVariant $variant) {
|
||||
if ($variant->exists && $variant->isDirty('inventory_policy')) {
|
||||
throw new \InvalidArgumentException('La politica de inventario no puede modificarse.');
|
||||
}
|
||||
|
||||
$variant->validateStock();
|
||||
});
|
||||
}
|
||||
@@ -47,34 +59,44 @@ class ProductVariant extends Model
|
||||
throw new \InvalidArgumentException('El stock reservado no puede ser negativo.');
|
||||
}
|
||||
|
||||
if ($this->stock_reservado > $this->stock_real) {
|
||||
if ($this->cantidad_vendida < 0) {
|
||||
throw new \InvalidArgumentException('La cantidad vendida no puede ser negativa.');
|
||||
}
|
||||
|
||||
if ($this->tracksInventory() && $this->stock_reservado > $this->stock_real) {
|
||||
throw new \InvalidArgumentException('El stock reservado no puede ser mayor que el stock real.');
|
||||
}
|
||||
}
|
||||
|
||||
public function incrementRealStock(int $amount): void
|
||||
public function tracksInventory(): bool
|
||||
{
|
||||
return $this->inventory_policy === InventoryPolicy::Tracked;
|
||||
}
|
||||
|
||||
public function availableQuantity(): ?int
|
||||
{
|
||||
if (! $this->tracksInventory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->stock_real - $this->stock_reservado;
|
||||
}
|
||||
|
||||
public function isAvailableForSale(): bool
|
||||
{
|
||||
return ! $this->tracksInventory() || $this->availableQuantity() > 0;
|
||||
}
|
||||
|
||||
public function reserveStock(int $amount): void
|
||||
{
|
||||
if ($amount < 0) {
|
||||
throw new \InvalidArgumentException('El monto a incrementar debe ser positivo.');
|
||||
}
|
||||
$this->stock_real += $amount;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function decrementRealStock(int $amount): void
|
||||
{
|
||||
if ($amount < 0) {
|
||||
throw new \InvalidArgumentException('El monto a decrementar debe ser positivo.');
|
||||
if ($this->tracksInventory() && $this->availableQuantity() < $amount) {
|
||||
throw new \InvalidArgumentException('No hay suficiente stock disponible para reservar.');
|
||||
}
|
||||
$this->stock_real -= $amount;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function incrementReservedStock(int $amount): void
|
||||
{
|
||||
if ($amount < 0) {
|
||||
throw new \InvalidArgumentException('El monto a incrementar debe ser positivo.');
|
||||
}
|
||||
$this->stock_reservado += $amount;
|
||||
$this->save();
|
||||
}
|
||||
@@ -88,13 +110,13 @@ class ProductVariant extends Model
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function confirmReservedStock(int $amount): void
|
||||
public function buy(int $amount): void
|
||||
{
|
||||
if ($amount < 0) {
|
||||
throw new \InvalidArgumentException('El monto a confirmar debe ser positivo.');
|
||||
}
|
||||
|
||||
if ($this->stock_real < $amount) {
|
||||
if ($this->tracksInventory() && $this->stock_real < $amount) {
|
||||
throw new \InvalidArgumentException('No hay suficiente stock real para confirmar la reserva.');
|
||||
}
|
||||
|
||||
@@ -102,14 +124,18 @@ class ProductVariant extends Model
|
||||
throw new \InvalidArgumentException('No hay suficiente stock reservado para confirmar la reserva.');
|
||||
}
|
||||
|
||||
$this->stock_real -= $amount;
|
||||
if ($this->tracksInventory()) {
|
||||
$this->stock_real -= $amount;
|
||||
}
|
||||
|
||||
$this->stock_reservado -= $amount;
|
||||
$this->cantidad_vendida += $amount;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
protected function stockTecnico(): Attribute
|
||||
{
|
||||
return Attribute::get(fn () => $this->stock_real - $this->stock_reservado);
|
||||
return Attribute::get(fn (): ?int => $this->availableQuantity());
|
||||
}
|
||||
|
||||
protected function stock(): Attribute
|
||||
@@ -126,8 +152,10 @@ class ProductVariant extends Model
|
||||
{
|
||||
return [
|
||||
'producto_id' => 'integer',
|
||||
'inventory_policy' => InventoryPolicy::class,
|
||||
'stock_real' => 'integer',
|
||||
'stock_reservado' => 'integer',
|
||||
'cantidad_vendida' => 'integer',
|
||||
'is_placeholder' => 'boolean',
|
||||
'has_tickets' => 'boolean',
|
||||
'minimum_use_date' => 'datetime',
|
||||
|
||||
Reference in New Issue
Block a user