repuve-backend-v1/app/Http/Controllers/Repuve/CancellationController.php
2025-12-26 16:45:06 -06:00

373 lines
15 KiB
PHP

<?php
namespace App\Http\Controllers\Repuve;
use App\Http\Controllers\Controller;
use App\Http\Requests\Repuve\CancelConstanciaRequest;
use Illuminate\Validation\ValidationException;
use App\Models\Record;
use App\Models\Tag;
use App\Models\TagCancellationLog;
use App\Models\VehicleTagLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Barryvdh\DomPDF\Facade\Pdf;
class CancellationController extends Controller
{
/**
* Cancelar la constancia/tag
*/
public function cancelarConstancia(CancelConstanciaRequest $request, $recordId)
{
try {
DB::beginTransaction();
// Buscar el expediente con sus relaciones
$record = Record::with('vehicle.tag.status')->find($recordId);
if (!$record) {
return ApiResponse::NOT_FOUND->response([
'message' => 'El expediente no existe.',
'record_id' => $recordId,
]);
}
$vehicle = $record->vehicle;
$tag = $vehicle->tag;
// Validar que el vehículo tiene un tag asignado
if (!$tag) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'El vehículo no tiene un tag/constancia asignada.'
]);
}
// Validar que el tag está en estado activo O cancelado (para permitir sustitución posterior)
if (!$tag->isAssigned() && !$tag->isCancelled()) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'El tag debe estar asignado o cancelado. Status actual: ' . $tag->status->name
]);
}
// Validar que se proporcionen los datos de sustitución
if (!$request->filled('new_folio') || !$request->filled('new_tag_number')) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'Para cancelar la constancia, debe proporcionar: nuevo folio y numero de constancia.',
'provided' => [
'new_folio' => $request->filled('new_folio'),
'new_tag_number' => $request->filled('new_tag_number'),
],
]);
}
// Validar que el tag_number tenga exactamente 17 caracteres
if (strlen($request->new_tag_number) !== 32) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'El tag_number debe tener exactamente 32 caracteres',
'provided_tag_number' => $request->new_tag_number,
'length' => strlen($request->new_tag_number),
]);
}
$isSubstitution = true;
// Guardar información del tag anterior ANTES de cancelarlo
$oldTagNumber = $tag->tag_number;
$oldFolio = $tag->folio;
// Crear registro en el log de vehículos
if ($tag->isAssigned()) {
$cancellationLog = VehicleTagLog::create([
'vehicle_id' => $vehicle->id,
'tag_id' => $tag->id,
'action_type' => 'cancelacion',
'cancellation_reason_id' => $request->cancellation_reason_id,
'cancellation_observations' => $request->cancellation_observations,
'cancellation_at' => now(),
'cancelled_by' => Auth::id(),
'performed_by' => Auth::id(),
]);
// Actualizar estado del tag a 'cancelled'
$tag->markAsCancelled();
} else {
// Si ya estaba cancelado, buscar el último log de cancelación
$cancellationLog = VehicleTagLog::where('vehicle_id', $vehicle->id)
->where('tag_id', $tag->id)
->where('action_type', 'cancelacion')
->latest()
->first();
}
$newTag = null;
$substitutionLog = null;
if ($isSubstitution) {
// Buscar el nuevo tag por folio
$newTag = Tag::where('folio', $request->new_folio)->first();
if (!$newTag) {
DB::rollBack();
return ApiResponse::NOT_FOUND->response([
'message' => 'El tag con el folio proporcionado no existe.',
'new_folio' => $request->new_folio,
]);
}
if (!$newTag->isAvailable()) {
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'El nuevo tag no está disponible para asignación',
'new_folio' => $request->new_folio,
'current_status' => $newTag->status->name,
]);
}
// Asignar tag_number al nuevo tag si no lo tiene
if (!$newTag->tag_number) {
// Validar que el tag_number no esté usado por otro tag
$existingTag = Tag::where('tag_number', $request->new_tag_number)
->where('id', '!=', $newTag->id)
->first();
if ($existingTag) {
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'El tag_number ya está asignado a otro tag.',
'new_tag_number' => $request->new_tag_number,
'folio_existente' => $existingTag->folio,
]);
}
$newTag->tag_number = $request->new_tag_number;
$newTag->save();
} elseif ($newTag->tag_number !== $request->new_tag_number) {
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'El tag ya tiene un tag_number diferente asignado.',
'assigned_tag_number' => $newTag->tag_number,
'provided_tag_number' => $request->new_tag_number,
]);
}
// Desasignar el tag viejo para evitar conflicto de unique constraint
$tag->update(['vehicle_id' => null]);
// Asignar el nuevo tag al vehículo (usa el folio del tag encontrado)
$newTag->markAsAssigned($vehicle->id, $newTag->folio);
// Crear log de sustitución
$substitutionLog = VehicleTagLog::create([
'vehicle_id' => $vehicle->id,
'tag_id' => $newTag->id,
'action_type' => 'sustitucion',
'cancellation_reason_id' => $request->cancellation_reason_id,
'cancellation_observations' => 'Tag sustituido. Tag anterior: ' . $oldTagNumber . ' (Folio: ' . $oldFolio . '). Motivo: ' . ($request->cancellation_observations ?? ''),
'performed_by' => Auth::id(),
]);
// Actualizar el folio del expediente con el folio del nuevo tag
$record->update(['folio' => $newTag->folio]);
}
DB::commit();
$message = $isSubstitution
? 'Tag cancelado y sustituido exitosamente'
: 'Constancia cancelada exitosamente';
// Agregar alerta si NO hay sustitución
$alert = null;
if (!$isSubstitution) {
$alert = [
'type' => 'warning',
'message' => 'El tag ha sido cancelado y necesita sustitución',
'requires_action' => true,
'cancellation_date' => $cancellationLog->cancellation_at->format('d/m/Y H:i:s'),
'cancellation_reason' => $cancellationLog->cancellationReason->name,
];
}
return ApiResponse::OK->response([
'message' => $message,
'is_substitution' => $isSubstitution,
'alert' => $alert,
'cancellation' => [
'id' => $cancellationLog->id,
'vehicle' => [
'id' => $vehicle->id,
'placa' => $vehicle->placa,
'niv' => $vehicle->niv,
],
'old_tag' => [
'id' => $tag->id,
'folio' => $oldFolio,
'tag_number' => $oldTagNumber,
'new_status' => 'Cancelado',
],
'new_tag' => $newTag ? [
'id' => $newTag->id,
'folio' => $newTag->folio,
'tag_number' => $newTag->tag_number,
'status' => $newTag->status->name,
] : null,
'cancellation_reason' => $cancellationLog->cancellationReason->name,
'cancellation_observations' => $request->cancellation_observations,
'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(),
'cancelled_by' => Auth::user()->name,
]
]);
} catch (\Exception $e) {
DB::rollBack();
Log::error('Error en cancelarConstancia: ' . $e->getMessage(), [
'record_id' => $recordId ?? null,
'cancellation_reason' => $request->cancellation_reason ?? null,
'trace' => $e->getTraceAsString()
]);
return ApiResponse::BAD_REQUEST->response([
'message' => 'Error al cancelar la constancia',
'error' => $e->getMessage()
]);
}
}
public function cancelarTagNoAsignado(Request $request)
{
try {
$request->validate([
'folio' => 'required|string|exists:tags,folio',
'cancellation_reason_id' => 'required|exists:catalog_cancellation_reasons,id',
'cancellation_observations' => 'nullable|string',
'module_id' => 'nullable|exists:modules,id',
]);
DB::beginTransaction();
$tag = Tag::where('folio', $request->folio)->first();
if (!$tag) {
DB::rollBack();
return ApiResponse::NOT_FOUND->response([
'message' => 'No se encontró el tag con el folio proporcionado.',
'folio' => $request->folio,
]);
}
// Validar que el tag NO esté asignado
if ($tag->isAssigned()) {
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'Este tag está asignado a un vehículo. Usa el endpoint de cancelación de constancia.',
'tag_status' => $tag->status->name,
]);
}
// Validar que no esté ya cancelado
if ($tag->isCancelled()) {
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'El tag ya está cancelado.',
]);
}
$observations = $request->cancellation_observations;
$cancellationLog = TagCancellationLog::create([
'tag_id' => $tag->id,
'cancellation_reason_id' => $request->cancellation_reason_id,
'cancellation_observations' => $observations,
'cancellation_at' => now(),
'cancelled_by' => Auth::id(),
]);
// Cargar las relaciones necesarias ANTES de usarlas
$cancellationLog->load(['cancellationReason', 'cancelledBy']);
// Validar que la relación se cargó correctamente
if (!$cancellationLog->cancellationReason) {
DB::rollBack();
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al cargar el motivo de cancelación.',
'error' => 'La relación cancellationReason no se pudo cargar correctamente.',
]);
}
// Actualizar el módulo del tag si se proporciona
if ($request->filled('module_id')) {
$tag->module_id = $request->module_id;
$tag->save();
}
// Cancelar el tag
$tag->markAsDamaged();
DB::commit();
// Recargar el tag con sus relaciones actualizadas
$tag->load(['status', 'cancellationLogs.cancellationReason', 'cancellationLogs.cancelledBy', 'module']);
// Obtener datos de cancelación del último log
$lastCancellation = $tag->cancellationLogs()
->with(['cancellationReason', 'cancelledBy'])
->latest()
->first();
// Preparar datos para el PDF
$cancellationData = [
'fecha' => $lastCancellation ? $lastCancellation->cancellation_at->format('d/m/Y') : now()->format('d/m/Y'),
'folio' => $tag->folio ?? '',
'tag_number' => $tag->tag_number ?? 'N/A',
'motivo' => $lastCancellation && $lastCancellation->cancellationReason
? $lastCancellation->cancellationReason->name
: 'No especificado',
'operador' => $lastCancellation && $lastCancellation->cancelledBy
? $lastCancellation->cancelledBy->name
: 'Sistema',
'modulo' => $tag->module ? $tag->module->name : 'No especificado',
'ubicacion' => $tag->module ? $tag->module->address : 'No especificado',
];
try {
$pdf = Pdf::loadView('pdfs.tag', [
'cancellation' => $cancellationData,
])
->setPaper('a4', 'portrait')
->setOptions([
'defaultFont' => 'sans-serif',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('constancia_cancelada_' . ($tag->tag_number ?? $tag->folio) . '.pdf');
} catch (\Exception $e) {
// Retornar error como JSON
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Tag cancelado pero error al generar PDF',
'error' => $e->getMessage(),
]);
}
} catch (ValidationException $e) {
// Errores de validación
return ApiResponse::BAD_REQUEST->response([
'message' => 'Error de validación',
'errors' => $e->errors(),
]);
} catch (\Exception $e) {
DB::rollBack();
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al cancelar el tag',
'error' => $e->getMessage(),
]);
}
}
}