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 * Cancelar la constancia/tag
*/ */
public function cancelarConstancia(CancelConstanciaRequest $request) public function cancelarConstancia(CancelConstanciaRequest $request, $recordId)
{ {
try { try {
DB::beginTransaction(); DB::beginTransaction();
// Buscar el expediente con sus relaciones // 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; $vehicle = $record->vehicle;
$tag = $vehicle->tag; $tag = $vehicle->tag;
@ -39,18 +47,41 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
]); ]);
} }
// Validar que el tag está en estado activo // Validar que el tag está en estado activo O cancelado (para permitir sustitución posterior)
if (!$tag->isAssigned()) { if (!$tag->isAssigned() && !$tag->isCancelled()) {
return ApiResponse::BAD_REQUEST->response([ 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 // Guardar información del tag anterior ANTES de cancelarlo
$oldTagNumber = $tag->tag_number; $oldTagNumber = $tag->tag_number;
$oldFolio = $tag->folio; $oldFolio = $tag->folio;
// Crear registro en el log de vehiculos // Crear registro en el log de vehículos
if ($tag->isAssigned()) {
$cancellationLog = VehicleTagLog::create([ $cancellationLog = VehicleTagLog::create([
'vehicle_id' => $vehicle->id, 'vehicle_id' => $vehicle->id,
'tag_id' => $tag->id, 'tag_id' => $tag->id,
@ -62,21 +93,29 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
'performed_by' => Auth::id(), 'performed_by' => Auth::id(),
]); ]);
// Actualizar estado del tag a 'cancelled' y desasignar vehículo // Actualizar estado del tag a 'cancelled'
$tag->markAsCancelled(); $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; $newTag = null;
$substitutionLog = null; $substitutionLog = null;
$isSubstitution = $request->filled('new_folio') && $request->filled('new_tag_number');
if ($isSubstitution) { if ($isSubstitution) {
// Buscar el nuevo tag por folio
$newTag = Tag::where('folio', $request->new_folio)->first(); $newTag = Tag::where('folio', $request->new_folio)->first();
if (!$newTag) { if (!$newTag) {
DB::rollBack(); DB::rollBack();
return ApiResponse::NOT_FOUND->response([ return ApiResponse::NOT_FOUND->response([
'message' => 'El nuevo tag proporcionado no existe.', 'message' => 'El tag con el folio proporcionado no existe.',
'new_tag_number' => $request->new_tag_number, 'new_folio' => $request->new_folio,
]); ]);
} }
@ -84,17 +123,22 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
DB::rollBack(); DB::rollBack();
return ApiResponse::BAD_REQUEST->response([ return ApiResponse::BAD_REQUEST->response([
'message' => 'El nuevo tag no está disponible para asignación', '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, 'current_status' => $newTag->status->name,
]); ]);
} }
// Asignar tag_number al nuevo tag si no lo tiene
if (!$newTag->tag_number) { if (!$newTag->tag_number) {
$existingTag = Tag::where('tag_number', $request->new_tag_number)->first(); // Validar que el tag_number no esté usado por otro tag
if ($existingTag && $existingTag->id !== $newTag->id) { $existingTag = Tag::where('tag_number', $request->new_tag_number)
->where('id', '!=', $newTag->id)
->first();
if ($existingTag) {
DB::rollBack(); DB::rollBack();
return ApiResponse::BAD_REQUEST->response([ 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, 'new_tag_number' => $request->new_tag_number,
'folio_existente' => $existingTag->folio, 'folio_existente' => $existingTag->folio,
]); ]);
@ -105,15 +149,19 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
} elseif ($newTag->tag_number !== $request->new_tag_number) { } elseif ($newTag->tag_number !== $request->new_tag_number) {
DB::rollBack(); DB::rollBack();
return ApiResponse::BAD_REQUEST->response([ 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, 'assigned_tag_number' => $newTag->tag_number,
'provided_tag_number' => $request->new_tag_number, 'provided_tag_number' => $request->new_tag_number,
]); ]);
} }
// Usar el folio del request // Desasignar el tag viejo para evitar conflicto de unique constraint
$newTag->markAsAssigned($vehicle->id, $request->folio); $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([ $substitutionLog = VehicleTagLog::create([
'vehicle_id' => $vehicle->id, 'vehicle_id' => $vehicle->id,
'tag_id' => $newTag->id, 'tag_id' => $newTag->id,
@ -123,7 +171,8 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
'performed_by' => Auth::id(), '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(); DB::commit();
@ -132,9 +181,22 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
? 'Tag cancelado y sustituido exitosamente' ? 'Tag cancelado y sustituido exitosamente'
: 'Constancia cancelada 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([ return ApiResponse::OK->response([
'message' => $message, 'message' => $message,
'is_substitution' => $isSubstitution, 'is_substitution' => $isSubstitution,
'alert' => $alert,
'cancellation' => [ 'cancellation' => [
'id' => $cancellationLog->id, 'id' => $cancellationLog->id,
'vehicle' => [ 'vehicle' => [
@ -144,8 +206,8 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
], ],
'old_tag' => [ 'old_tag' => [
'id' => $tag->id, 'id' => $tag->id,
'folio' => $tag->folio, 'folio' => $oldFolio,
'tag_number' => $tag->tag_number, 'tag_number' => $oldTagNumber,
'new_status' => 'Cancelado', 'new_status' => 'Cancelado',
], ],
'new_tag' => $newTag ? [ 'new_tag' => $newTag ? [
@ -154,7 +216,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
'tag_number' => $newTag->tag_number, 'tag_number' => $newTag->tag_number,
'status' => $newTag->status->name, 'status' => $newTag->status->name,
] : null, ] : null,
'cancellation_reason_id' => $cancellationLog->cancellationReason->name, 'cancellation_reason' => $cancellationLog->cancellationReason->name,
'cancellation_observations' => $request->cancellation_observations, 'cancellation_observations' => $request->cancellation_observations,
'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(), 'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(),
'cancelled_by' => Auth::user()->name, 'cancelled_by' => Auth::user()->name,
@ -164,7 +226,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
DB::rollBack(); DB::rollBack();
Log::error('Error en cancelarConstancia: ' . $e->getMessage(), [ Log::error('Error en cancelarConstancia: ' . $e->getMessage(), [
'record_id' => $request->record_id ?? null, 'record_id' => $recordId ?? null,
'cancellation_reason' => $request->cancellation_reason ?? null, 'cancellation_reason' => $request->cancellation_reason ?? null,
'trace' => $e->getTraceAsString() 'trace' => $e->getTraceAsString()
]); ]);

View File

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

View File

@ -55,7 +55,7 @@
// Rutas de cancelación de constancias // Rutas de cancelación de constancias
Route::resource('/razones-cancelacion', CatalogController::class); 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::post('tags/cancelar', [CancellationController::class, 'cancelarTagNoAsignado']);
Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']); Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']);
Route::get('excel/constancias-canceladas', [ExcelController::class, 'constanciasCanceladas']); Route::get('excel/constancias-canceladas', [ExcelController::class, 'constanciasCanceladas']);