Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions app/Http/Controllers/BLInventarioController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,44 @@ public function index()
{
$productos = BlProducto::with([
'color',
'empaques.inventarioDetalle.posicion.nivel.estanteria'
'empaques.inventarioDetalle.zona.nivel.estanteria'
])
->get()
->map(function ($producto) {
// Obtener todas las ubicaciones donde está este producto
$ubicaciones = [];
$zonasUnicas = [];
$estanteriasUnicas = []; // NUEVO: para almacenar códigos de estanterías

foreach ($producto->empaques as $empaque) {
foreach ($empaque->inventarioDetalle as $inventario) {
if ($inventario->posicion && $inventario->posicion->nivel && $inventario->posicion->nivel->estanteria) {
$ubicaciones[] = $inventario->posicion->nivel->estanteria->nombre;
if ($inventario->zona && $inventario->zona->nivel && $inventario->zona->nivel->estanteria) {
$estanteria = $inventario->zona->nivel->estanteria;
$nivel = $inventario->zona->nivel;
$zona = $inventario->zona;

// Crear ubicación legible
$ubicacionLegible = $estanteria->nombre . ' - ' .
$nivel->nivel . ' - ' .
'Zona ' . $zona->zona;

$ubicaciones[] = $ubicacionLegible;

// Guardar información de zona única
$zonasUnicas[] = [
'estanteria_nombre' => $estanteria->nombre,
'estanteria_codigo' => $estanteria->codigo, // EST-04, RACK-04, etc.
'nivel_nombre' => $nivel->nivel,
'zona_nombre' => $zona->zona,
'codigo_completo' => $zona->codigo_completo
];

// NUEVO: Guardar código de estantería para el plano
$estanteriasUnicas[] = $estanteria->codigo;
}
}
}

// Si no tiene ubicación, mostrar "Sin ubicación"
$estanteria = !empty($ubicaciones) ? implode(', ', array_unique($ubicaciones)) : 'Sin ubicación';

return [
Expand All @@ -49,9 +72,14 @@ public function index()
'descripcion' => $producto->descripcion,
'stock_total' => $producto->empaques
->where('estado', 'disponible')
->sum('cantidad_unidades'),
->sum('cantidad_por_empaque'),
'estanteria' => $estanteria,
'tiene_ubicacion' => !empty($ubicaciones),
'ubicaciones_detalladas' => $zonasUnicas,
'estanterias' => array_unique(array_column($zonasUnicas, 'estanteria_nombre')),
'zonas_completas' => array_unique(array_column($zonasUnicas, 'codigo_completo')),
// NUEVO: Array de códigos de estantería para el plano
'estanterias_codigos' => array_unique($estanteriasUnicas),
];
});

Expand Down
39 changes: 32 additions & 7 deletions app/Models/BlInventarioDetalle.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ class BlInventarioDetalle extends Model

protected $table = 'bl_inventario_detalle';
protected $fillable = [
'empaque_id', 'posicion_id', 'cantidad_actual',
'fecha_ubicacion', 'fecha_vencimiento', 'estado', 'notas'
'empaque_id',
'zona_id', // Cambiado: posicion_id → zona_id
'cantidad_actual',
'fecha_ubicacion',
'fecha_vencimiento',
'estado',
'notas'
];

protected $dates = ['fecha_ubicacion', 'fecha_vencimiento'];
Expand All @@ -23,10 +28,10 @@ public function empaque()
return $this->belongsTo(BlEmpaque::class, 'empaque_id');
}

// Relación con posición
public function posicion()
// NUEVA RELACIÓN con zona (reemplaza posicion)
public function zona()
{
return $this->belongsTo(BlPosicion::class, 'posicion_id');
return $this->belongsTo(BlZonaNivel::class, 'zona_id');
}

// Relación con producto (a través de empaque)
Expand All @@ -42,23 +47,43 @@ public function producto()
);
}

// Scopes útiles
// Scopes útiles ACTUALIZADOS
public function scopeDisponibles($query)
{
return $query->where('estado', 'disponible');
}

public function scopeEnEstanteria($query, $estanteriaId)
{
return $query->whereHas('posicion.nivel.estanteria', function($q) use ($estanteriaId) {
return $query->whereHas('zona.nivel.estanteria', function($q) use ($estanteriaId) {
$q->where('id', $estanteriaId);
});
}

public function scopeEnZona($query, $zonaId)
{
return $query->where('zona_id', $zonaId);
}

public function scopeConProducto($query, $productoId)
{
return $query->whereHas('empaque', function($q) use ($productoId) {
$q->where('producto_id', $productoId);
});
}

// Nuevo scope para buscar por código de zona
public function scopeEnZonaCodigo($query, $codigoZona)
{
return $query->whereHas('zona', function($q) use ($codigoZona) {
$q->where('codigo_completo', $codigoZona);
});
}

// Scope para productos próximos a vencer
public function scopeProximosAVencer($query, $dias = 30)
{
return $query->whereDate('fecha_vencimiento', '<=', now()->addDays($dias))
->where('estado', 'disponible');
}
}
65 changes: 65 additions & 0 deletions app/Models/BlZonaNivel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class BlZonaNivel extends Model
{
use HasFactory;

protected $table = 'bl_zonas_nivel';
protected $fillable = [
'nivel_id',
'zona',
'codigo_completo',
'capacidad_maxima',
'productos_actuales',
'descripcion',
'activa'
];

// Relación con nivel
public function nivel()
{
return $this->belongsTo(BlNivelEstanteria::class, 'nivel_id');
}

// Relación con inventario detalle
public function inventarioDetalle()
{
return $this->hasMany(BlInventarioDetalle::class, 'zona_id');
}

// Relación con estantería (a través de nivel)
public function estanteria()
{
return $this->hasOneThrough(
BlEstanteria::class,
BlNivelEstanteria::class,
'id', // Foreign key on bl_niveles_estanteria
'id', // Foreign key on bl_estanterias
'nivel_id', // Local key on bl_zonas_nivel
'estanteria_id' // Local key on bl_niveles_estanteria
);
}

// Scope para zonas activas
public function scopeActivas($query)
{
return $query->where('activa', true);
}

// Scope para zonas con capacidad disponible
public function scopeConCapacidad($query, $cantidad = 1)
{
return $query->whereRaw('(capacidad_maxima - productos_actuales) >= ?', [$cantidad]);
}

// Método para calcular espacio disponible
public function getEspacioDisponibleAttribute()
{
return $this->capacidad_maxima - $this->productos_actuales;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ public function up(): void
*/
public function down(): void
{
//
Schema::dropIfExists('bl_estanterias');
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ public function up(): void
*/
public function down(): void
{
//
Schema::dropIfExists('bl_niveles_estanteria');
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ public function up(): void
*/
public function down(): void
{
//
Schema::dropIfExists('bl_posiciones');
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ public function up(): void
*/
public function down(): void
{
//
Schema::dropIfExists('bl_inventario_detalle');
}
};
32 changes: 32 additions & 0 deletions database/migrations/2025_09_28_152407_drop_bl_posiciones_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
// Eliminar la tabla de manera segura
Schema::dropIfExists('bl_posiciones');
}

public function down(): void
{
// En caso de rollback, recrea la tabla si es necesario
Schema::create('bl_posiciones', function (Blueprint $table) {
$table->id();
$table->foreignId('nivel_id')->constrained('bl_niveles_estanteria');
$table->string('posicion');
$table->string('codigo_completo')->unique();
$table->decimal('ancho', 8, 2)->nullable();
$table->decimal('alto', 8, 2)->nullable();
$table->decimal('profundidad', 8, 2)->nullable();
$table->integer('capacidad_maxima')->default(1);
$table->boolean('ocupada')->default(false);
$table->boolean('activa')->default(true);
$table->timestamps();
});
}
};
36 changes: 36 additions & 0 deletions database/migrations/2025_09_28_152633_bl_zonas_nivel_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('bl_zonas_nivel', function (Blueprint $table) {
$table->id();
$table->foreignId('nivel_id')->constrained('bl_niveles_estanteria');
$table->string('zona'); // 'A', 'B'
$table->string('codigo_completo')->unique(); // Ej: 'RACK-01-B-A'
$table->integer('capacidad_maxima')->default(5000); // mitad de la capacidad del nivel
$table->integer('productos_actuales')->default(0);
$table->text('descripcion')->nullable(); // "Frente", "Fondo", etc.
$table->boolean('activa')->default(true);
$table->timestamps();

$table->unique(['nivel_id', 'zona']);
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bl_zonas_nivel');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
public function up(): void
{
// 1️⃣ Crear la nueva tabla temporal con la estructura correcta
Schema::create('bl_inventario_detalle_new', function (Blueprint $table) {
$table->id();
$table->foreignId('empaque_id')->constrained('bl_empaques');
$table->foreignId('zona_id')->constrained('bl_zonas_nivel'); // NUEVO CAMPO
$table->integer('cantidad_actual')->default(0);
$table->date('fecha_ubicacion');
$table->date('fecha_vencimiento')->nullable();
$table->enum('estado', ['disponible', 'reservado', 'danado', 'caducado'])->default('disponible');
$table->text('notas')->nullable();
$table->timestamps();

$table->unique(['empaque_id', 'zona_id']);
$table->index(['zona_id', 'estado']);
$table->index('fecha_vencimiento');
});

// 2️⃣ Copiar los datos desde la tabla antigua
// Aquí asumimos que existe un mapeo directo posicón_id → zona_id
// Si necesitas un mapeo distinto, ajusta el SELECT
DB::statement('
INSERT INTO bl_inventario_detalle_new (id, empaque_id, zona_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at)
SELECT id, empaque_id, posicion_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at
FROM bl_inventario_detalle
');

// 3️⃣ Eliminar la tabla antigua
Schema::dropIfExists('bl_inventario_detalle');

// 4️⃣ Renombrar la nueva tabla con el nombre original
Schema::rename('bl_inventario_detalle_new', 'bl_inventario_detalle');
}

public function down(): void
{
// Revertir: volver a la tabla con posicion_id
Schema::create('bl_inventario_detalle_old', function (Blueprint $table) {
$table->id();
$table->foreignId('empaque_id')->constrained('bl_empaques');
$table->foreignId('posicion_id')->constrained('bl_posiciones');
$table->integer('cantidad_actual')->default(0);
$table->date('fecha_ubicacion');
$table->date('fecha_vencimiento')->nullable();
$table->enum('estado', ['disponible', 'reservado', 'danado', 'caducado'])->default('disponible');
$table->text('notas')->nullable();
$table->timestamps();

$table->unique(['empaque_id', 'posicion_id']);
$table->index(['posicion_id', 'estado']);
});

DB::statement('
INSERT INTO bl_inventario_detalle_old (id, empaque_id, posicion_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at)
SELECT id, empaque_id, zona_id, cantidad_actual, fecha_ubicacion, fecha_vencimiento, estado, notas, created_at, updated_at
FROM bl_inventario_detalle
');

Schema::dropIfExists('bl_inventario_detalle');
Schema::rename('bl_inventario_detalle_old', 'bl_inventario_detalle');
}
};
Loading
Loading