* * @version 1.0.0 */ class Inventory extends Model { protected $fillable = [ 'category_id', 'name', 'sku', 'barcode', 'track_serials', 'is_active', ]; protected $casts = [ 'is_active' => 'boolean', 'track_serials' => 'boolean', ]; protected $appends = ['has_serials', 'inventory_value', 'stock']; public function warehouses() { return $this->belongsToMany(Warehouse::class, 'inventory_warehouse') ->withPivot('stock', 'min_stock', 'max_stock') ->withTimestamps(); } /** * Stock total en todos los almacenes */ public function getStockAttribute(): int { return $this->warehouses()->sum('inventory_warehouse.stock'); } /** * Alias para compatibilidad */ public function getTotalStockAttribute(): int { return $this->stock; } /** * Stock en un almacén específico */ public function stockInWarehouse(int $warehouseId): int { return $this->warehouses() ->where('warehouse_id', $warehouseId) ->value('inventory_warehouse.stock') ?? 0; } public function category() { return $this->belongsTo(Category::class); } public function price() { return $this->hasOne(Price::class); } public function serials() { return $this->hasMany(InventorySerial::class); } /** * Obtener seriales disponibles */ public function availableSerials() { return $this->hasMany(InventorySerial::class) ->where('status', 'disponible'); } /** * Stock basado en seriales disponibles (para productos con track_serials) */ public function getAvailableStockAttribute(): int { return $this->availableSerials()->count(); } public function getHasSerialsAttribute(): bool { return isset($this->attributes['serials_count']) && $this->attributes['serials_count'] > 0; } /** * Valor total del inventario (stock * costo) */ public function getInventoryValueAttribute(): float { return $this->stock * ($this->price?->cost ?? 0); } /** * Sincronizar stock basado en seriales disponibles * Delega al servicio para mantener la lógica centralizada */ public function syncStock(): void { app(InventoryMovementService::class)->syncStockFromSerials($this); } }