add: generar excel del tag history

This commit is contained in:
Juan Felipe Zapata Moreno 2026-01-15 12:55:58 -06:00
parent c0225a69e2
commit ce7e1f998e
3 changed files with 281 additions and 2 deletions

View File

@ -11,6 +11,7 @@
use App\Models\VehicleTagLog; use App\Models\VehicleTagLog;
use App\Models\Tag; use App\Models\Tag;
use App\Models\Module; use App\Models\Module;
use App\Models\Record;
use App\Models\TagCancellationLog; use App\Models\TagCancellationLog;
use Carbon\Carbon; use Carbon\Carbon;
use Notsoweb\ApiResponse\Enums\ApiResponse; use Notsoweb\ApiResponse\Enums\ApiResponse;
@ -869,4 +870,279 @@ public function excelGeneral(Request $request)
return response()->download($filePath, $fileName)->deleteFileAfterSend(true); 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);
}
} }

View File

@ -331,8 +331,10 @@ public function searchRecord(Request $request)
// Log de acciones // Log de acciones
'vehicle.vehicleTagLogs' => function ($q) { 'vehicle.vehicleTagLogs' => function ($q) {
$q->with([ $q->with([
'tag:id,folio,tag_number,status_id', 'tag:id,folio,tag_number,status_id,module_id,package_id',
'tag.status:id,code,name' 'tag.status:id,code,name',
'tag.module:id,name',
'tag.package:id,lot,box_number'
])->orderBy('created_at', 'DESC'); ])->orderBy('created_at', 'DESC');
}, },
])->orderBy('id', 'ASC'); ])->orderBy('id', 'ASC');

View File

@ -35,6 +35,7 @@
// Rutas de inscripción de vehículos // Rutas de inscripción de vehículos
Route::post('inscripcion', [InscriptionController::class, 'vehicleInscription']); Route::post('inscripcion', [InscriptionController::class, 'vehicleInscription']);
Route::get('consultaV', [InscriptionController::class, 'searchRecord']); Route::get('consultaV', [InscriptionController::class, 'searchRecord']);
Route::get('consultaV/export', [InscriptionController::class, 'exportSearchRecords']);
Route::post('reporte-robado', [InscriptionController::class, 'stolen']); Route::post('reporte-robado', [InscriptionController::class, 'stolen']);
// Rutas de expedientes y documentos // Rutas de expedientes y documentos