Files
2026-01-21 09:34:34 -05:00

264 lines
6.8 KiB
PHP

<?php
namespace App\Models;
use Filament\Actions\Concerns\BelongsToGroup;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Producto extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'nombre',
'descripcion',
'codigo_barras',
'precio_compra',
'precio_venta',
'stock',
'categoria_id',
'estado',
'imagen',
'stock_minimo',
'stock_maximo',
'unidad_medida'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'estado' => 'boolean',
];
/**
* Valores por defecto para atributos del modelo
*/
protected $attributes = [
'imagen' => '',
];
public function variants(): HasMany
{
return $this->hasMany(ProductVariant::class);
}
public function categoria(): BelongsTo
{
return $this->belongsTo(Categoria::class);
}
/**
* Relación many-to-many con bodegas
*/
public function bodegas(): BelongsToMany
{
return $this->belongsToMany(Bodega::class, 'producto_bodega')
->withPivot('stock')
->withTimestamps();
}
/**
* Obtiene las unidades de medida disponibles
*/
public static function getUnidadesMedida(): array
{
return [
'unidad' => 'Unidad (1)',
'docena' => 'Docena (12)',
];
}
/**
* Obtiene el factor de conversión para una unidad de medida
*/
public static function getFactorConversion(string $unidad): int
{
$factores = [
'unidad' => 1,
'docena' => 12,
];
return $factores[$unidad] ?? 1;
}
/**
* Convierte una cantidad en la unidad especificada a unidades individuales
*/
public static function convertirAUnidades(float $cantidad, string $unidad): int
{
return (int) ($cantidad * self::getFactorConversion($unidad));
}
/**
* Convierte unidades individuales a la unidad de medida especificada
*/
public static function convertirDesdeUnidades(int $unidades, string $unidad): float
{
$factor = self::getFactorConversion($unidad);
return $factor > 1 ? round($unidades / $factor, 2) : $unidades;
}
/**
* Obtiene el stock en la unidad de medida configurada
*/
public function getStockEnUnidadMedida(): float
{
$stockTotal = $this->variants()->exists()
? $this->variants()->sum('stock')
: $this->stock;
return self::convertirDesdeUnidades($stockTotal, $this->unidad_medida ?? 'unidad');
}
/**
* Obtiene el nombre de la unidad de medida
*/
public function getNombreUnidadMedida(): string
{
$unidades = self::getUnidadesMedida();
return $unidades[$this->unidad_medida ?? 'unidad'] ?? 'Unidad (1)';
}
/**
* Obtener el stock total en todas las bodegas
*/
public function getStockTotalBodegas(): int
{
return $this->bodegas()->sum('producto_bodega.stock');
}
/**
* Obtener el stock en una bodega específica
*/
public function getStockEnBodega(int $bodegaId): int
{
$bodega = $this->bodegas()->where('bodega_id', $bodegaId)->first();
return $bodega ? $bodega->pivot->stock : 0;
}
/**
* Obtener el stock total en una bodega específica (considerando variantes)
*/
public function getStockTotalEnBodega(int $bodegaId): int
{
if ($this->variants()->exists()) {
$stockVariantes = 0;
foreach ($this->variants as $variante) {
$bodegaVariante = $variante->bodegas()->where('bodega_id', $bodegaId)->first();
if ($bodegaVariante) {
$stockVariantes += $bodegaVariante->pivot->stock;
}
}
return $stockVariantes;
} else {
return $this->getStockEnBodega($bodegaId);
}
}
/**
* Distribuir stock total entre bodegas
*/
public function distribuirStock(array $distribucion): void
{
foreach ($distribucion as $bodegaId => $stock) {
$this->bodegas()->syncWithoutDetaching([
$bodegaId => ['stock' => $stock]
]);
}
}
/**
* Obtener el stock efectivo (considerando bodegas Y variantes correctamente)
*/
public function getStockEfectivo(): int
{
// Si tiene variantes, el stock se calcula desde las variantes
if ($this->variants()->exists()) {
$stockVariantes = 0;
foreach ($this->variants as $variante) {
$stockVariantes += $variante->getStockEfectivo();
}
return $stockVariantes;
} else {
// Para productos sin variantes
if ($this->bodegas()->exists()) {
return $this->getStockTotalBodegas();
} else {
return $this->stock;
}
}
}
/**
* Accessor para código de barras dinámico
*/
public function getCodigoBarrasAttribute($value)
{
// Si el producto tiene variantes, el código de barras es NULL
if ($this->variants()->exists()) {
return null;
}
// Si NO tiene variantes, usa su código de barras propio
return $value;
}
/**
* Accessor para stock dinámico
* Si el producto tiene variantes, el stock directo debe ser 0
*/
public function getStockAttribute($value)
{
// Si el producto tiene variantes, el stock directo debe ser 0
if ($this->variants()->exists()) {
return 0;
}
// Si NO tiene variantes, devuelve el stock real
return $value;
}
/**
* Mutator para stock - evita que se asigne stock directo si hay variantes
*/
public function setStockAttribute($value)
{
// Si el producto ya tiene variantes, no permitir stock directo
if ($this->exists && $this->variants()->exists()) {
$this->attributes['stock'] = 0;
} else {
$this->attributes['stock'] = $value;
}
}
/**
* Relación con detalles de compra
*/
public function detalleCompras(): HasMany
{
return $this->hasMany(DetalleCompra::class);
}
/**
* Relación con detalles de venta
*/
public function detalleVentas(): HasMany
{
return $this->hasMany(DetalleVenta::class);
}
}