feat: actualizar método de cancelación de constancias para incluir validaciones y soporte de sustitución

This commit is contained in:
Juan Felipe Zapata Moreno 2025-12-22 14:57:46 -06:00
parent 908ba8aaf1
commit 44ef2f9306
3 changed files with 98 additions and 39 deletions

View File

@ -22,13 +22,21 @@ class CancellationController extends Controller
/**
* Cancelar la constancia/tag
*/
public function cancelarConstancia(CancelConstanciaRequest $request)
public function cancelarConstancia(CancelConstanciaRequest $request, $recordId)
{
try {
DB::beginTransaction();
// Buscar el expediente con sus relaciones
$record = Record::with('vehicle.tag.status')->findOrFail($request->record_id);
$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;
@ -39,44 +47,75 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
]);
}
// Validar que el tag está en estado activo
if (!$tag->isAssigned()) {
// 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 ya no está activo. Status actual: ' . $tag->status->name
'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 vehiculos
$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(),
]);
// 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' y desasignar vehículo
$tag->markAsCancelled();
// 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;
$isSubstitution = $request->filled('new_folio') && $request->filled('new_tag_number');
$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 nuevo tag proporcionado no existe.',
'new_tag_number' => $request->new_tag_number,
'message' => 'El tag con el folio proporcionado no existe.',
'new_folio' => $request->new_folio,
]);
}
@ -84,17 +123,22 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'El nuevo tag no está disponible para asignación',
'new_tag_number' => $request->new_tag_number,
'new_folio' => $request->new_folio,
'current_status' => $newTag->status->name,
]);
}
// Asignar tag_number al nuevo tag si no lo tiene
if (!$newTag->tag_number) {
$existingTag = Tag::where('tag_number', $request->new_tag_number)->first();
if ($existingTag && $existingTag->id !== $newTag->id) {
// 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 nuevo tag_number ya está asignado a otro folio.',
'message' => 'El tag_number ya está asignado a otro tag.',
'new_tag_number' => $request->new_tag_number,
'folio_existente' => $existingTag->folio,
]);
@ -105,15 +149,19 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
} elseif ($newTag->tag_number !== $request->new_tag_number) {
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'El nuevo tag ya tiene un tag_number diferente asignado.',
'message' => 'El tag ya tiene un tag_number diferente asignado.',
'assigned_tag_number' => $newTag->tag_number,
'provided_tag_number' => $request->new_tag_number,
]);
}
// Usar el folio del request
$newTag->markAsAssigned($vehicle->id, $request->folio);
// 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,
@ -123,7 +171,8 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
'performed_by' => Auth::id(),
]);
$record->update(['folio' => $request->folio]);
// Actualizar el folio del expediente con el folio del nuevo tag
$record->update(['folio' => $newTag->folio]);
}
DB::commit();
@ -132,9 +181,22 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
? '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' => [
@ -144,8 +206,8 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
],
'old_tag' => [
'id' => $tag->id,
'folio' => $tag->folio,
'tag_number' => $tag->tag_number,
'folio' => $oldFolio,
'tag_number' => $oldTagNumber,
'new_status' => 'Cancelado',
],
'new_tag' => $newTag ? [
@ -154,7 +216,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
'tag_number' => $newTag->tag_number,
'status' => $newTag->status->name,
] : null,
'cancellation_reason_id' => $cancellationLog->cancellationReason->name,
'cancellation_reason' => $cancellationLog->cancellationReason->name,
'cancellation_observations' => $request->cancellation_observations,
'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(),
'cancelled_by' => Auth::user()->name,
@ -164,7 +226,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
DB::rollBack();
Log::error('Error en cancelarConstancia: ' . $e->getMessage(), [
'record_id' => $request->record_id ?? null,
'record_id' => $recordId ?? null,
'cancellation_reason' => $request->cancellation_reason ?? null,
'trace' => $e->getTraceAsString()
]);

View File

@ -20,8 +20,7 @@ public function authorize(): bool
public function rules(): array
{
return [
'record_id' => 'nullable|exists:records,id',
'folio' => 'required|string',
'new_folio' => 'required|string',
'cancellation_reason_id' => 'required|exists:catalog_cancellation_reasons,id',
'cancellation_observations' => 'nullable|string',
'new_tag_number' => 'nullable|exists:tags,tag_number',
@ -34,8 +33,6 @@ public function rules(): array
public function messages(): array
{
return [
'record_id.required' => 'El id del expediente es obligatorio.',
'record_id.integer' => 'El id del expediente debe ser un número entero.',
'record_id.exists' => 'El expediente especificado no existe.',
'cancellation_reason_id.required' => 'El motivo de cancelación es obligatorio.',

View File

@ -55,7 +55,7 @@
// Rutas de cancelación de constancias
Route::resource('/razones-cancelacion', CatalogController::class);
Route::delete('cancelacion', [CancellationController::class, 'cancelarConstancia']);
Route::post('{recordId}/cancelacion', [CancellationController::class, 'cancelarConstancia']);
Route::post('tags/cancelar', [CancellationController::class, 'cancelarTagNoAsignado']);
Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']);
Route::get('excel/constancias-canceladas', [ExcelController::class, 'constanciasCanceladas']);