From ce7e1f998e3a548da10e1e29e5f0e7430dad4a46 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Thu, 15 Jan 2026 12:55:58 -0600 Subject: [PATCH] add: generar excel del tag history --- .../Controllers/Repuve/ExcelController.php | 276 ++++++++++++++++++ .../Repuve/InscriptionController.php | 6 +- routes/api.php | 1 + 3 files changed, 281 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Repuve/ExcelController.php b/app/Http/Controllers/Repuve/ExcelController.php index af4c2b0..cf1da64 100644 --- a/app/Http/Controllers/Repuve/ExcelController.php +++ b/app/Http/Controllers/Repuve/ExcelController.php @@ -11,6 +11,7 @@ use App\Models\VehicleTagLog; use App\Models\Tag; use App\Models\Module; +use App\Models\Record; use App\Models\TagCancellationLog; use Carbon\Carbon; use Notsoweb\ApiResponse\Enums\ApiResponse; @@ -869,4 +870,279 @@ public function excelGeneral(Request $request) return response()->download($filePath, $fileName)->deleteFileAfterSend(true); } + + /** + * Exportar resultados de búsqueda a Excel + */ + public function exportSearchRecords(Request $request) + { + // Reutilizar la misma validación y lógica de búsqueda + $request->validate([ + 'folio' => 'nullable|string', + 'placa' => 'nullable|string', + 'vin' => 'nullable|string', + 'tag_number' => 'nullable|string', + 'module_id' => 'nullable|integer|exists:modules,id', + 'action_type' => 'nullable|string|in:inscripcion,actualizacion,sustitucion,cancelacion', + 'status' => 'nullable|string', + 'start_date' => 'nullable|date', + 'end_date' => 'nullable|date|after_or_equal:start_date', + ]); + + // Reutilizar la misma query de búsqueda (sin paginación) + $records = Record::with([ + 'vehicle', + 'vehicle.owner', + 'vehicle.tag:id,vehicle_id,folio,tag_number,status_id,package_id,module_id', + 'vehicle.tag.status:id,code,name', + 'vehicle.tag.package:id,lot,box_number', + 'vehicle.tag.module:id,name', + 'files:id,record_id,name_id,path,md5', + 'files.catalogName:id,name', + 'user:id,name,email,module_id', + 'module:id,name', + 'error:id,code,description', + 'vehicle.vehicleTagLogs' => function ($q) { + $q->with([ + 'tag:id,folio,tag_number,status_id,module_id,package_id', + 'tag.status:id,code,name', + 'tag.module:id,name', + 'tag.package:id,lot,box_number' + ])->orderBy('created_at', 'DESC'); + }, + ])->orderBy('id', 'ASC'); + + // Aplicar los mismos filtros + if ($request->filled('folio')) { + $records->whereHas('vehicle.tag', function ($q) use ($request) { + $q->where('folio', 'LIKE', '%' . $request->input('folio') . '%'); + }); + } + if ($request->filled('placa')) { + $records->whereHas('vehicle', function ($q) use ($request) { + $q->where('placa', 'LIKE', '%' . $request->input('placa') . '%'); + }); + } + if ($request->filled('vin')) { + $records->whereHas('vehicle', function ($q) use ($request) { + $q->where('niv', 'LIKE', '%' . $request->input('vin') . '%'); + }); + } + if ($request->filled('tag_number')) { + $records->whereHas('vehicle.tag', function ($q) use ($request) { + $q->where('tag_number', 'LIKE', '%' . $request->input('tag_number') . '%'); + }); + } + if ($request->filled('module_id')) { + $records->where('module_id', $request->input('module_id')); + } + if ($request->filled('action_type')) { + $records->whereHas('vehicle.vehicleTagLogs', function ($q) use ($request) { + $q->where('action_type', $request->input('action_type')) + ->whereRaw('id = ( + SELECT MAX(id) + FROM vehicle_tags_logs + WHERE vehicle_id = vehicle.id + )'); + }); + } + if ($request->filled('status')) { + $records->whereHas('vehicle.tag.status', function ($q) use ($request) { + $q->where('code', $request->input('status')); + }); + } + if ($request->filled('start_date')) { + $records->whereDate('created_at', '>=', $request->input('start_date')); + } + if ($request->filled('end_date')) { + $records->whereDate('created_at', '<=', $request->input('end_date')); + } + + $allRecords = $records->get(); + + if ($allRecords->isEmpty()) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'No se encontraron registros con los criterios de búsqueda proporcionados.', + ]); + } + + // Preparar datos para Excel: una fila por cada entrada del historial + $excelRows = []; + + foreach ($allRecords as $record) { + $niv = $record->vehicle->niv ?? ''; + $currentTagId = $record->vehicle->tag?->id; + $recordRows = []; // Filas para este registro principal + + // Obtener todos los logs ordenados por fecha ascendente + $vehicleLogs = $record->vehicle->vehicleTagLogs->sortBy('created_at'); + $processedTags = []; + + // Procesar tags del historial (excluyendo el tag actual) + foreach ($vehicleLogs as $log) { + $tagId = $log->tag_id; + + // Excluir el tag actual y solo procesar cada tag una vez + if ($tagId && $tagId !== $currentTagId && !in_array($tagId, $processedTags)) { + $processedTags[] = $tagId; + $tag = $log->tag; + + // Buscar todos los logs relacionados con este tag + $tagLogs = $vehicleLogs->where('tag_id', $tagId); + + // Buscar fecha de cancelación si existe + $cancelLog = $tagLogs + ->whereIn('action_type', ['cancelacion', 'sustitucion']) + ->whereNotNull('cancellation_at') + ->first(); + + // Obtener fecha de asignación (inscripción o sustitución) + $assignedLog = $tagLogs + ->whereIn('action_type', ['inscripcion', 'sustitucion']) + ->first(); + + // Determinar fecha: si fue cancelado, usar fecha de cancelación, sino fecha de asignación + $fecha = null; + $status = 'unknown'; + + if ($cancelLog && $cancelLog->cancellation_at) { + $fecha = Carbon::parse($cancelLog->cancellation_at); + $status = 'Cancelado'; + } elseif ($assignedLog && $assignedLog->created_at) { + $fecha = Carbon::parse($assignedLog->created_at); + $status = ucfirst($assignedLog->action_type); + } + + // Si no hay fecha, usar el status del tag + if (!$fecha) { + $status = $tag?->status?->name ?? 'unknown'; + } + + $recordRows[] = [ + 'status' => $status, + 'folio' => $tag?->folio ?? '', + 'tag' => $tag?->tag_number ?? '', + 'niv' => $niv, + 'fecha' => $fecha ? $fecha->format('d/m/Y H:i') : '', + 'sort_date' => $fecha ? $fecha->timestamp : 0, + 'record_id' => $record->id, // Para mantener agrupado + ]; + } + } + + // Ordenar las filas del historial por fecha ascendente + usort($recordRows, function ($a, $b) { + return $a['sort_date'] <=> $b['sort_date']; + }); + + // Agregar el registro principal (tag actual) al final del grupo + if ($record->vehicle->tag) { + $currentTag = $record->vehicle->tag; + $latestLog = $record->vehicle->vehicleTagLogs->first(); + + // Determinar fecha y status del tag actual + $fechaActual = null; + $statusActual = $currentTag->status?->name ?? 'unknown'; + + if ($latestLog) { + if ($latestLog->cancellation_at) { + $fechaActual = Carbon::parse($latestLog->cancellation_at); + $statusActual = 'Cancelado'; + } else { + $fechaActual = Carbon::parse($latestLog->created_at); + $statusActual = ucfirst($latestLog->action_type); + } + } else { + $fechaActual = $record->created_at ? Carbon::parse($record->created_at) : now(); + } + + $recordRows[] = [ + 'status' => $statusActual, + 'folio' => $currentTag->folio ?? '', + 'tag' => $currentTag->tag_number ?? '', + 'niv' => $niv, + 'fecha' => $fechaActual->format('d/m/Y H:i'), + 'sort_date' => $fechaActual->timestamp, + 'record_id' => $record->id, + ]; + } + + // Agregar todas las filas de este registro al array principal + $excelRows = array_merge($excelRows, $recordRows); + } + + // Crear Excel + $fileName = 'Busqueda_Registros_' . now()->format('Ymd_His') . '.xlsx'; + $filePath = storage_path('app/temp/' . $fileName); + if (!file_exists(dirname($filePath))) { + mkdir(dirname($filePath), 0755, true); + } + + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Registros'); + + // Estilos + $headerStyle = [ + 'font' => ['bold' => true, 'size' => 11, 'color' => ['rgb' => 'FFFFFF']], + 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], + 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]] + ]; + + $cellStyle = [ + 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]], + 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], + ]; + + // Encabezados (solo las 5 columnas solicitadas) + $headers = [ + 'A' => 'Status', + 'B' => 'Folio', + 'C' => 'Tag', + 'D' => 'NIV', + 'E' => 'Fecha de Registro o Cancelación', + ]; + + $row = 1; + foreach ($headers as $col => $text) { + $sheet->setCellValue("{$col}{$row}", $text); + $sheet->getStyle("{$col}{$row}")->applyFromArray($headerStyle); + } + $sheet->getRowDimension($row)->setRowHeight(30); + + // Datos + $row = 2; + foreach ($excelRows as $item) { + $sheet->setCellValue('A' . $row, $item['status']); + $sheet->setCellValue('B' . $row, $item['folio']); + $sheet->setCellValue('C' . $row, $item['tag']); + $sheet->setCellValue('D' . $row, $item['niv']); + $sheet->setCellValue('E' . $row, $item['fecha']); + + // Aplicar estilos + foreach (range('A', 'E') as $col) { + $sheet->getStyle("{$col}{$row}")->applyFromArray($cellStyle); + } + + // Centrar algunas columnas + $sheet->getStyle("A{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + $sheet->getStyle("E{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + + $row++; + } + + // Ajustar anchos de columna + $sheet->getColumnDimension('A')->setWidth(20); + $sheet->getColumnDimension('B')->setWidth(18); + $sheet->getColumnDimension('C')->setWidth(20); + $sheet->getColumnDimension('D')->setWidth(22); + $sheet->getColumnDimension('E')->setWidth(25); + + // Guardar archivo + $writer = new Xlsx($spreadsheet); + $writer->save($filePath); + + return response()->download($filePath, $fileName)->deleteFileAfterSend(true); + } } diff --git a/app/Http/Controllers/Repuve/InscriptionController.php b/app/Http/Controllers/Repuve/InscriptionController.php index a015ca7..0d21e30 100644 --- a/app/Http/Controllers/Repuve/InscriptionController.php +++ b/app/Http/Controllers/Repuve/InscriptionController.php @@ -331,8 +331,10 @@ public function searchRecord(Request $request) // Log de acciones 'vehicle.vehicleTagLogs' => function ($q) { $q->with([ - 'tag:id,folio,tag_number,status_id', - 'tag.status:id,code,name' + 'tag:id,folio,tag_number,status_id,module_id,package_id', + 'tag.status:id,code,name', + 'tag.module:id,name', + 'tag.package:id,lot,box_number' ])->orderBy('created_at', 'DESC'); }, ])->orderBy('id', 'ASC'); diff --git a/routes/api.php b/routes/api.php index c9021b8..e1d0ffc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -35,6 +35,7 @@ // Rutas de inscripción de vehículos Route::post('inscripcion', [InscriptionController::class, 'vehicleInscription']); Route::get('consultaV', [InscriptionController::class, 'searchRecord']); + Route::get('consultaV/export', [InscriptionController::class, 'exportSearchRecords']); Route::post('reporte-robado', [InscriptionController::class, 'stolen']); // Rutas de expedientes y documentos