diff --git a/app/Filament/Resources/OrdenProduccionResource.php b/app/Filament/Resources/OrdenProduccionResource.php index 8e57f66..704993a 100644 --- a/app/Filament/Resources/OrdenProduccionResource.php +++ b/app/Filament/Resources/OrdenProduccionResource.php @@ -131,6 +131,14 @@ class OrdenProduccionResource extends Resource TextColumn::make('cantidad_total') ->label('Cantidad'), + TextColumn::make('realizado') + ->label('Realizado') + ->sortable(), + + TextColumn::make('faltantes') + ->label('Faltan') + ->sortable(), + TextColumn::make('tela.codigo') ->label('Tela') ->toggleable(), diff --git a/app/Models/OrdenProduccion.php b/app/Models/OrdenProduccion.php index 4519822..21751e0 100644 --- a/app/Models/OrdenProduccion.php +++ b/app/Models/OrdenProduccion.php @@ -58,6 +58,18 @@ class OrdenProduccion extends Model return $this->belongsTo(\App\Models\Producto::class, 'producto_id'); } + // Total realizado: suma de cantidad_terminada en inventarios asociados + public function getRealizadoAttribute() + { + return (int) $this->inventarioPrenda()->sum('cantidad_terminada'); + } + + // Faltantes = cantidad_total - realizado + public function getFaltantesAttribute() + { + return max(0, (int)$this->cantidad_total - $this->realizado); + } + public function avanzarEstado(): void { $flujo = [ diff --git a/app/Models/ProcesoAcabado.php b/app/Models/ProcesoAcabado.php index bb21533..75a2c77 100644 --- a/app/Models/ProcesoAcabado.php +++ b/app/Models/ProcesoAcabado.php @@ -80,9 +80,15 @@ class ProcesoAcabado extends Model ->exists(); if (!$pendientes) { - $acabado->ordenProduccion?->update([ - 'estado' => 'finalizada', - ]); + $op = $acabado->ordenProduccion; + if ($op) { + $realizado = (int) $op->inventarioPrenda()->sum('cantidad_terminada'); + if ($realizado >= (int) ($op->cantidad_total ?? 0)) { + $op->update([ + 'estado' => 'finalizada', + ]); + } + } } } }; diff --git a/database/migrations/2026_01_20_071000_backfill_inventario_producto_from_op.php b/database/migrations/2026_01_20_071000_backfill_inventario_producto_from_op.php index 12e61e8..eedda20 100644 --- a/database/migrations/2026_01_20_071000_backfill_inventario_producto_from_op.php +++ b/database/migrations/2026_01_20_071000_backfill_inventario_producto_from_op.php @@ -10,7 +10,19 @@ return new class extends Migration public function up(): void { // Backfill producto_id in inventario_prendas from la OP relacionada cuando sea NULL - DB::statement("UPDATE inventario_prendas ip SET producto_id = op.producto_id FROM orden_produccions op WHERE ip.orden_produccion_id = op.id AND ip.producto_id IS NULL AND op.producto_id IS NOT NULL"); + // Use a compatible update for both MySQL/Postgres and SQLite + $driver = Schema::getConnection()->getDriverName(); + if ($driver === 'sqlite') { + DB::statement( + "UPDATE inventario_prendas SET producto_id = ( + SELECT op.producto_id FROM orden_produccions op WHERE op.id = inventario_prendas.orden_produccion_id + ) WHERE producto_id IS NULL AND orden_produccion_id IS NOT NULL AND ( + SELECT op.producto_id FROM orden_produccions op WHERE op.id = inventario_prendas.orden_produccion_id + ) IS NOT NULL" + ); + } else { + DB::statement("UPDATE inventario_prendas ip SET producto_id = op.producto_id FROM orden_produccions op WHERE ip.orden_produccion_id = op.id AND ip.producto_id IS NULL AND op.producto_id IS NOT NULL"); + } } public function down(): void diff --git a/tests/Feature/OrdenProduccionFaltantesTest.php b/tests/Feature/OrdenProduccionFaltantesTest.php new file mode 100644 index 0000000..35eee34 --- /dev/null +++ b/tests/Feature/OrdenProduccionFaltantesTest.php @@ -0,0 +1,63 @@ + 'OP-900', 'prenda_modelo' => 'ModeloX', 'cantidad_total' => 50, 'metros_requeridos' => 0, 'fecha_inicio' => now(), 'fecha_entrega_estimada' => now()->addDays(7)]); + + // Initially nothing done + $this->assertEquals(0, $op->realizado); + $this->assertEquals(50, $op->faltantes); + + // Create an inventario with 20 finished + InventarioPrenda::create(['orden_produccion_id' => $op->id, 'cantidad_terminada' => 20, 'cantidad_disponible' => 20, 'fecha_ingreso' => now(), 'estado' => 'en_bodega']); + + $op->refresh(); + + $this->assertEquals(20, $op->realizado); + $this->assertEquals(30, $op->faltantes); + } + + public function test_do_not_set_finalizada_until_completed() + { + $op = OrdenProduccion::create(['numero_orden' => 'OP-901', 'prenda_modelo' => 'ModeloY', 'cantidad_total' => 10, 'metros_requeridos' => 0, 'fecha_inicio' => now(), 'fecha_entrega_estimada' => now()->addDays(7)]); + + // Create process finished but no inventario yet + ProcesoAcabado::create(['orden_produccion_id' => $op->id, 'tipo_proceso' => 'PA', 'proveedor_id' => \App\Models\Proveedor::factory()->create(['categoria' => 'proveedor'])->id, 'cantidad_enviada' => 0, 'cantidad_recibida' => 8, 'fecha_envio' => now(), 'fecha_recepcion' => now()]); + + $op->refresh(); + + // Since only 8 are received into processus, but no inventory recorded, still not finalizada + $this->assertNotEquals('finalizada', $op->estado); + + // Create inventory for the 8 (not enough to finalize) + InventarioPrenda::create(['orden_produccion_id' => $op->id, 'cantidad_terminada' => 8, 'cantidad_disponible' => 8, 'fecha_ingreso' => now(), 'estado' => 'en_bodega']); + + $op->refresh(); + $this->assertEquals(2, $op->faltantes); + + // Now add remaining inventory + InventarioPrenda::create(['orden_produccion_id' => $op->id, 'cantidad_terminada' => 2, 'cantidad_disponible' => 2, 'fecha_ingreso' => now(), 'estado' => 'en_bodega']); + + $op->refresh(); + $this->assertEquals(0, $op->faltantes); + + // Simulate no pending procesosAcabado and trigger the check + ProcesoAcabado::where('orden_produccion_id', $op->id)->update(['fecha_recepcion' => now()]); + $op->refresh(); + + // Now the orden should be finalizada when all completed + $this->assertEquals('finalizada', $op->estado); + } +}