feat: Implementación catalogo de razones de cancelación, actualizar manual, pdf contancia dañada
This commit is contained in:
parent
a305c82956
commit
684315eb18
@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Repuve\CancelConstanciaRequest;
|
use App\Http\Requests\Repuve\CancelConstanciaRequest;
|
||||||
|
use App\Models\CatalogCancellationReason;
|
||||||
use App\Models\Record;
|
use App\Models\Record;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
|
use App\Models\TagCancellationLog;
|
||||||
use App\Models\VehicleTagLog;
|
use App\Models\VehicleTagLog;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@ -59,7 +62,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
|
|||||||
'vehicle_id' => $vehicle->id,
|
'vehicle_id' => $vehicle->id,
|
||||||
'tag_id' => $tag->id,
|
'tag_id' => $tag->id,
|
||||||
'action_type' => 'cancelacion',
|
'action_type' => 'cancelacion',
|
||||||
'cancellation_reason' => $request->cancellation_reason,
|
'cancellation_reason_id' => $request->cancellation_reason_id,
|
||||||
'cancellation_observations' => $request->cancellation_observations,
|
'cancellation_observations' => $request->cancellation_observations,
|
||||||
'cancellation_at' => now(),
|
'cancellation_at' => now(),
|
||||||
'cancelled_by' => Auth::id(),
|
'cancelled_by' => Auth::id(),
|
||||||
@ -100,7 +103,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
|
|||||||
'vehicle_id' => $vehicle->id,
|
'vehicle_id' => $vehicle->id,
|
||||||
'tag_id' => $newTag->id,
|
'tag_id' => $newTag->id,
|
||||||
'action_type' => 'sustitucion',
|
'action_type' => 'sustitucion',
|
||||||
'cancellation_reason' => $request->cancellation_reason,
|
'cancellation_reason_id' => $request->cancellation_reason_id,
|
||||||
'cancellation_observations' => 'Tag sustituido. Tag anterior: ' . $oldTagNumber . ' (Folio: ' . $oldFolio . '). Motivo: ' . ($request->cancellation_observations ?? ''),
|
'cancellation_observations' => 'Tag sustituido. Tag anterior: ' . $oldTagNumber . ' (Folio: ' . $oldFolio . '). Motivo: ' . ($request->cancellation_observations ?? ''),
|
||||||
'performed_by' => Auth::id(),
|
'performed_by' => Auth::id(),
|
||||||
]);
|
]);
|
||||||
@ -136,7 +139,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' => $request->cancellation_reason,
|
'cancellation_reason_id' => $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,
|
||||||
@ -157,4 +160,74 @@ public function cancelarConstancia(CancelConstanciaRequest $request)
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function cancelarTagNoAsignado(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'tag_number' => 'required|string|exists:tags,tag_number',
|
||||||
|
'cancellation_reason_id' => 'required|exists:catalog_cancellation_reasons,id',
|
||||||
|
'cancellation_observations' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$tag = Tag::where('tag_number', $request->tag_number)->first();
|
||||||
|
|
||||||
|
// Validar que el tag NO esté asignado
|
||||||
|
if ($tag->isAssigned()) {
|
||||||
|
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()) {
|
||||||
|
return ApiResponse::BAD_REQUEST->response([
|
||||||
|
'message' => 'El tag ya está cancelado.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear log de cancelación ANTES de cancelar el tag
|
||||||
|
$cancellationLog = TagCancellationLog::create([
|
||||||
|
'tag_id' => $tag->id,
|
||||||
|
'cancellation_reason_id' => $request->cancellation_reason_id,
|
||||||
|
'cancellation_observations' => $request->cancellation_observations,
|
||||||
|
'cancellation_at' => now(),
|
||||||
|
'cancelled_by' => Auth::id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Cancelar el tag
|
||||||
|
$tag->markAsCancelled();
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'message' => 'Tag cancelado exitosamente',
|
||||||
|
'tag' => [
|
||||||
|
'id' => $tag->id,
|
||||||
|
'tag_number' => $tag->tag_number,
|
||||||
|
'folio' => $tag->folio,
|
||||||
|
'previous_status' => 'Disponible',
|
||||||
|
'new_status' => 'Cancelado',
|
||||||
|
],
|
||||||
|
'cancellation' => [
|
||||||
|
'id' => $cancellationLog->id,
|
||||||
|
'reason' => $cancellationLog->cancellationReason->name,
|
||||||
|
'observations' => $cancellationLog->cancellation_observations,
|
||||||
|
'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(),
|
||||||
|
'cancelled_by' => Auth::user()->name,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
return ApiResponse::BAD_REQUEST->response([
|
||||||
|
'message' => 'Error al cancelar el tag',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
app/Http/Controllers/Repuve/CatalogController.php
Normal file
115
app/Http/Controllers/Repuve/CatalogController.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Repuve;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Repuve\CancelConstanciaRequest;
|
||||||
|
use App\Models\CatalogCancellationReason;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descripción
|
||||||
|
*/
|
||||||
|
class CatalogController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$type = $request->query('type');
|
||||||
|
|
||||||
|
$query = CatalogCancellationReason::query();
|
||||||
|
|
||||||
|
if ($type === 'cancelacion') {
|
||||||
|
$query->forCancellation();
|
||||||
|
} elseif ($type === 'sustitucion') {
|
||||||
|
$query->forSubstitution();
|
||||||
|
} else {
|
||||||
|
$query->orderBy('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
$reasons = $query->get(['id', 'code', 'name', 'description', 'applies_to']);
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'message' => 'Razones de cancelación obtenidas exitosamente',
|
||||||
|
'data' => $reasons,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$reason = CatalogCancellationReason::find($id);
|
||||||
|
|
||||||
|
if (!$reason) {
|
||||||
|
return ApiResponse::NOT_FOUND->response([
|
||||||
|
'message' => 'Razón de cancelación no encontrada',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'message' => 'Razón de cancelación obtenida exitosamente',
|
||||||
|
'data' => $reason,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'code' => 'required|string|unique:catalog_cancellation_reasons,code',
|
||||||
|
'name' => 'required|string',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'applies_to' => 'required|in:cancelacion,sustitucion,ambos',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$reason = CatalogCancellationReason::create($validated);
|
||||||
|
|
||||||
|
return ApiResponse::CREATED->response([
|
||||||
|
'message' => 'Razón de cancelación creada exitosamente',
|
||||||
|
'data' => $reason,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$reason = CatalogCancellationReason::find($id);
|
||||||
|
|
||||||
|
if (!$reason) {
|
||||||
|
return ApiResponse::NOT_FOUND->response([
|
||||||
|
'message' => 'Razón de cancelación no encontrada',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'applies_to' => 'required|in:cancelacion,sustitucion,ambos',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$reason->update($validated);
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'message' => 'Razón de cancelación actualizada exitosamente',
|
||||||
|
'data' => $reason,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$reason = CatalogCancellationReason::find($id);
|
||||||
|
|
||||||
|
if (!$reason) {
|
||||||
|
return ApiResponse::NOT_FOUND->response([
|
||||||
|
'message' => 'Razón de cancelación no encontrada',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$reason->delete();
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'message' => 'Razón de cancelación eliminada exitosamente',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,6 +45,7 @@ public function constanciasSustituidas(Request $request)
|
|||||||
$logs = VehicleTagLog::with([
|
$logs = VehicleTagLog::with([
|
||||||
'vehicle',
|
'vehicle',
|
||||||
'tag',
|
'tag',
|
||||||
|
'cancellationReason'
|
||||||
])
|
])
|
||||||
->where('action_type', 'sustitucion')
|
->where('action_type', 'sustitucion')
|
||||||
->whereHas('vehicle.records', function ($query) use ($moduleId) {
|
->whereHas('vehicle.records', function ($query) use ($moduleId) {
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Barryvdh\DomPDF\Facade\Pdf;
|
use Barryvdh\DomPDF\Facade\Pdf;
|
||||||
use App\Models\Record;
|
use App\Models\Record;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||||
use Codedge\Fpdf\Fpdf\Fpdf;
|
use Codedge\Fpdf\Fpdf\Fpdf;
|
||||||
@ -182,9 +184,6 @@ public function generatePdfImages($id)
|
|||||||
public function generatePdfForm(Request $request)
|
public function generatePdfForm(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'fecha' => 'nullable|string',
|
|
||||||
'mes' => 'nullable|string',
|
|
||||||
'anio' => 'nullable|string',
|
|
||||||
'marca' => 'required|string',
|
'marca' => 'required|string',
|
||||||
'linea' => 'required|string',
|
'linea' => 'required|string',
|
||||||
'modelo' => 'required|string',
|
'modelo' => 'required|string',
|
||||||
@ -195,21 +194,21 @@ public function generatePdfForm(Request $request)
|
|||||||
'telefono' => 'nullable|string',
|
'telefono' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$now = Carbon::now()->locale('es_MX');
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'fecha' => $request->input('fecha', ''),
|
|
||||||
'mes' => $request->input('mes', ''),
|
|
||||||
'anio' => $request->input('anio', ''),
|
|
||||||
'marca' => $request->input('marca'),
|
|
||||||
'linea' => $request->input('linea'),
|
|
||||||
'modelo' => $request->input('modelo'),
|
'modelo' => $request->input('modelo'),
|
||||||
'niv' => $request->input('niv'),
|
'niv' => $request->input('niv'),
|
||||||
'numero_motor' => $request->input('numero_motor'),
|
'numero_motor' => $request->input('numero_motor'),
|
||||||
'placa' => $request->input('placa'),
|
'placa' => $request->input('placa'),
|
||||||
'folio' => $request->input('folio'),
|
'folio' => $request->input('folio'),
|
||||||
'telefono' => $request->input('telefono', ''),
|
'telefono' => $request->input('telefono', ''),
|
||||||
|
'fecha' => $now->format('d'),
|
||||||
|
'mes' => ucfirst($now->translatedFormat('F')),
|
||||||
|
'anio' => $now->format('Y'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$pdf = Pdf::loadView('pdfs.form', $data)
|
$pdf = Pdf::loadView('pdfs.tag', $data)
|
||||||
->setPaper('a4', 'portrait')
|
->setPaper('a4', 'portrait')
|
||||||
->setOptions([
|
->setOptions([
|
||||||
'defaultFont' => 'sans-serif',
|
'defaultFont' => 'sans-serif',
|
||||||
@ -220,6 +219,128 @@ public function generatePdfForm(Request $request)
|
|||||||
return $pdf->stream('solicitud-sustitucion-' . time() . '.pdf');
|
return $pdf->stream('solicitud-sustitucion-' . time() . '.pdf');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function pdfCancelledTag(Tag $tag)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Validar que el tag esté cancelado
|
||||||
|
if (!$tag->isCancelled()) {
|
||||||
|
return ApiResponse::BAD_REQUEST->response([
|
||||||
|
'message' => 'Solo se puede generar PDF para tags cancelados.',
|
||||||
|
'current_status' => $tag->status->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener datos de cancelación
|
||||||
|
$cancellationData = $this->cancellationData($tag);
|
||||||
|
|
||||||
|
$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 . '.pdf');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return ApiResponse::INTERNAL_ERROR->response([
|
||||||
|
'message' => 'Error al generar el PDF.',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cancellationData(Tag $tag)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'fecha' => now()->format('d/m/Y'),
|
||||||
|
'folio' => $tag->folio ?? '',
|
||||||
|
'id_chip' => '',
|
||||||
|
'placa' => '',
|
||||||
|
'niv' => '',
|
||||||
|
'motivo' => 'N/A',
|
||||||
|
'operador' => 'N/A',
|
||||||
|
'modulo' => '',
|
||||||
|
'ubicacion' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Intentar obtener datos del vehículo si existe
|
||||||
|
if ($tag->vehicle_id && $tag->vehicle) {
|
||||||
|
$data['id_chip'] = $tag->vehicle->id_chip ?? '';
|
||||||
|
$data['placa'] = $tag->vehicle->placa ?? '';
|
||||||
|
$data['niv'] = $tag->vehicle->niv ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buscar log de cancelación directa
|
||||||
|
$tagCancellationLog = $tag->cancellationLogs()
|
||||||
|
->with(['cancellationReason', 'cancelledBy'])
|
||||||
|
->latest()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($tagCancellationLog) {
|
||||||
|
$data['motivo'] = $tagCancellationLog->cancellationReason->name ?? 'No especificado';
|
||||||
|
$data['operador'] = $tagCancellationLog->cancelledBy->name ?? 'Sistema';
|
||||||
|
|
||||||
|
// Cargar módulo del cual el usuario es responsable
|
||||||
|
if ($tagCancellationLog->cancelledBy) {
|
||||||
|
$user = $tagCancellationLog->cancelledBy;
|
||||||
|
$this->loadUserModule($user, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buscar log de vehículo (tag asignado y luego cancelado)
|
||||||
|
$vehicleTagLog = $tag->vehicleTagLogs()
|
||||||
|
->where('action_type', 'cancelacion')
|
||||||
|
->with(['cancellationReason', 'cancelledBy', 'vehicle'])
|
||||||
|
->latest()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($vehicleTagLog) {
|
||||||
|
$data['motivo'] = $vehicleTagLog->cancellationReason->name ?? 'No especificado';
|
||||||
|
$data['operador'] = $vehicleTagLog->cancelledBy->name ?? 'Sistema';
|
||||||
|
|
||||||
|
// Cargar módulo del cual el usuario es responsable
|
||||||
|
if ($vehicleTagLog->cancelledBy) {
|
||||||
|
$user = $vehicleTagLog->cancelledBy;
|
||||||
|
$this->loadUserModule($user, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($vehicleTagLog->vehicle) {
|
||||||
|
$data['id_chip'] = $vehicleTagLog->vehicle->id_chip ?? '';
|
||||||
|
$data['placa'] = $vehicleTagLog->vehicle->placa ?? '';
|
||||||
|
$data['niv'] = $vehicleTagLog->vehicle->niv ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cargar módulo del usuario
|
||||||
|
*/
|
||||||
|
private function loadUserModule($user, &$data)
|
||||||
|
{
|
||||||
|
// Intentar cargar module
|
||||||
|
$user->load('module');
|
||||||
|
|
||||||
|
// Si no tiene module, usar responsibleModule
|
||||||
|
if (!$user->module) {
|
||||||
|
$user->load('responsibleModule');
|
||||||
|
|
||||||
|
if ($user->responsibleModule) {
|
||||||
|
$data['modulo'] = $user->responsibleModule->name;
|
||||||
|
$data['ubicacion'] = $user->responsibleModule->address;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$data['modulo'] = $user->module->name;
|
||||||
|
$data['ubicacion'] = $user->module->address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function errors(Request $request)
|
public function errors(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
|
|||||||
@ -9,10 +9,16 @@
|
|||||||
|
|
||||||
class TagsController extends Controller
|
class TagsController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$tags = Tag::with('vehicle:id,placa,niv', 'package:id,lot,box_number')->orderBy('id', 'ASC');
|
$tags = Tag::with('vehicle:id,placa,niv', 'package:id,lot,box_number', 'status:id,name')->orderBy('id', 'ASC');
|
||||||
|
|
||||||
|
if ($request->has('status')) {
|
||||||
|
$tags->whereHas('status', function ($q) use ($request) {
|
||||||
|
$q->where('name', $request->status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
return ApiResponse::OK->response([
|
||||||
'tag' => $tags->paginate(config('app.pagination')),
|
'tag' => $tags->paginate(config('app.pagination')),
|
||||||
|
|||||||
@ -161,7 +161,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
|
|||||||
|
|
||||||
if (!$tag) {
|
if (!$tag) {
|
||||||
return ApiResponse::NOT_FOUND->response([
|
return ApiResponse::NOT_FOUND->response([
|
||||||
'message' => 'No se encontró el tag con el tag_number proporcionado',
|
'message' => 'El TAG no coincide con el registrado en el expediente',
|
||||||
'tag_number' => $tagNumber,
|
'tag_number' => $tagNumber,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -174,74 +174,126 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
|
|||||||
|
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
// Intentar obtener datos del caché primero
|
$isManualUpdate = $request->has('vehicle') || $request->has('owner');
|
||||||
$vehicleData = Cache::get("update_vehicle_{$niv}");
|
$hasVehicleChanges = false;
|
||||||
$ownerData = Cache::get("update_owner_{$niv}");
|
$hasOwnerChanges = false;
|
||||||
|
|
||||||
// Si no hay
|
|
||||||
if (!$vehicleData || !$ownerData) {
|
|
||||||
$vehicleData = $this->getVehicle($niv);
|
|
||||||
$ownerData = $this->getOwner($niv);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpiar caché
|
|
||||||
Cache::forget("update_vehicle_{$niv}");
|
|
||||||
Cache::forget("update_owner_{$niv}");
|
|
||||||
|
|
||||||
// Detectar si hay cambios
|
|
||||||
$hasVehicleChanges = $this->detectVehicleChanges($vehicle, $vehicleData);
|
|
||||||
$hasOwnerChanges = $this->detectOwnerChanges($vehicle->owner, $ownerData);
|
|
||||||
|
|
||||||
// Actualizar propietario solo si hay cambios
|
|
||||||
$owner = $vehicle->owner;
|
$owner = $vehicle->owner;
|
||||||
if ($hasOwnerChanges) {
|
|
||||||
$owner = Owner::updateOrCreate(
|
if ($isManualUpdate) {
|
||||||
['rfc' => $ownerData['rfc']],
|
if ($request->has('vehicle')) {
|
||||||
[
|
$vehicleData = $request->input('vehicle', []);
|
||||||
'name' => $ownerData['name'],
|
|
||||||
'paternal' => $ownerData['paternal'],
|
$allowedVehicleFields = [
|
||||||
'maternal' => $ownerData['maternal'],
|
'placa',
|
||||||
'curp' => $ownerData['curp'],
|
'marca',
|
||||||
'address' => $ownerData['address'],
|
'linea',
|
||||||
'tipopers' => $ownerData['tipopers'],
|
'sublinea',
|
||||||
'pasaporte' => $ownerData['pasaporte'],
|
'modelo',
|
||||||
'licencia' => $ownerData['licencia'],
|
'color',
|
||||||
'ent_fed' => $ownerData['ent_fed'],
|
'numero_motor',
|
||||||
'munic' => $ownerData['munic'],
|
'clase_veh',
|
||||||
'callep' => $ownerData['callep'],
|
'tipo_servicio',
|
||||||
'num_ext' => $ownerData['num_ext'],
|
'rfv',
|
||||||
'num_int' => $ownerData['num_int'],
|
'ofcexpedicion',
|
||||||
'colonia' => $ownerData['colonia'],
|
'fechaexpedicion',
|
||||||
'cp' => $ownerData['cp'],
|
'tipo_veh',
|
||||||
]
|
'numptas',
|
||||||
);
|
'observac',
|
||||||
|
'cve_vehi',
|
||||||
|
'nrpv',
|
||||||
|
'tipo_mov'
|
||||||
|
];
|
||||||
|
|
||||||
|
$vehicleData = array_filter(
|
||||||
|
array_intersect_key($vehicleData, array_flip($allowedVehicleFields)),
|
||||||
|
fn($value) => $value !== null && $value !== ''
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($vehicleData)) {
|
||||||
|
$vehicle->update($vehicleData);
|
||||||
|
$hasVehicleChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('owner')) {
|
||||||
|
$ownerInput = $request->input('owner', []);
|
||||||
|
|
||||||
|
$allowedOwnerFields = [
|
||||||
|
'name',
|
||||||
|
'paternal',
|
||||||
|
'maternal',
|
||||||
|
'rfc',
|
||||||
|
'curp',
|
||||||
|
'address',
|
||||||
|
'tipopers',
|
||||||
|
'pasaporte',
|
||||||
|
'licencia',
|
||||||
|
'ent_fed',
|
||||||
|
'munic',
|
||||||
|
'callep',
|
||||||
|
'num_ext',
|
||||||
|
'num_int',
|
||||||
|
'colonia',
|
||||||
|
'cp',
|
||||||
|
'telefono'
|
||||||
|
];
|
||||||
|
|
||||||
|
$ownerData = array_filter(
|
||||||
|
array_intersect_key($ownerInput, array_flip($allowedOwnerFields)),
|
||||||
|
fn($value) => $value !== null && $value !== ''
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($ownerData)) {
|
||||||
|
// Si se cambió el RFC, buscar/crear otro propietario
|
||||||
|
if (isset($ownerData['rfc']) && $ownerData['rfc'] !== $owner->rfc) {
|
||||||
|
$owner = Owner::updateOrCreate(
|
||||||
|
['rfc' => $ownerData['rfc']],
|
||||||
|
$ownerData
|
||||||
|
);
|
||||||
|
$vehicle->update(['owner_id' => $owner->id]);
|
||||||
|
} else {
|
||||||
|
// Actualizar propietario existente
|
||||||
|
$owner->update($ownerData);
|
||||||
|
}
|
||||||
|
$hasOwnerChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Intentar obtener datos del caché primero
|
||||||
|
$vehicleDataEstatal = Cache::get("update_vehicle_{$niv}");
|
||||||
|
$ownerDataEstatal = Cache::get("update_owner_{$niv}");
|
||||||
|
|
||||||
|
// Si no hay
|
||||||
|
if (!$vehicleDataEstatal || !$ownerDataEstatal) {
|
||||||
|
$vehicleDataEstatal = $this->getVehicle($niv);
|
||||||
|
$ownerDataEstatal = $this->getOwner($niv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpiar caché
|
||||||
|
Cache::forget("update_vehicle_{$niv}");
|
||||||
|
Cache::forget("update_owner_{$niv}");
|
||||||
|
|
||||||
|
// Detectar si hay cambios
|
||||||
|
$hasVehicleChanges = $this->detectVehicleChanges($vehicle, $vehicleDataEstatal);
|
||||||
|
$hasOwnerChanges = $this->detectOwnerChanges($vehicle->owner, $ownerDataEstatal);
|
||||||
|
|
||||||
|
// Actualizar vehículo solo si hay cambios
|
||||||
|
if ($hasVehicleChanges) {
|
||||||
|
$vehicle->update($vehicleDataEstatal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar propietario solo si hay cambios
|
||||||
|
$owner = $vehicle->owner;
|
||||||
|
if ($hasOwnerChanges) {
|
||||||
|
$owner = Owner::updateOrCreate(
|
||||||
|
['rfc' => $ownerDataEstatal['rfc']],
|
||||||
|
$ownerDataEstatal
|
||||||
|
);
|
||||||
|
$vehicle->update(['owner_id' => $owner->id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar vehículo solo si hay cambios
|
|
||||||
if ($hasVehicleChanges) {
|
|
||||||
$vehicle->update([
|
|
||||||
'placa' => $vehicleData['placa'],
|
|
||||||
'marca' => $vehicleData['marca'],
|
|
||||||
'linea' => $vehicleData['linea'],
|
|
||||||
'sublinea' => $vehicleData['sublinea'],
|
|
||||||
'modelo' => $vehicleData['modelo'],
|
|
||||||
'color' => $vehicleData['color'],
|
|
||||||
'numero_motor' => $vehicleData['numero_motor'],
|
|
||||||
'clase_veh' => $vehicleData['clase_veh'],
|
|
||||||
'tipo_servicio' => $vehicleData['tipo_servicio'],
|
|
||||||
'rfv' => $vehicleData['rfv'],
|
|
||||||
'rfc' => $vehicleData['rfc'],
|
|
||||||
'ofcexpedicion' => $vehicleData['ofcexpedicion'],
|
|
||||||
'fechaexpedicion' => $vehicleData['fechaexpedicion'],
|
|
||||||
'tipo_veh' => $vehicleData['tipo_veh'],
|
|
||||||
'numptas' => $vehicleData['numptas'],
|
|
||||||
'observac' => $vehicleData['observac'],
|
|
||||||
'cve_vehi' => $vehicleData['cve_vehi'],
|
|
||||||
'nrpv' => $vehicleData['nrpv'],
|
|
||||||
'tipo_mov' => $vehicleData['tipo_mov'],
|
|
||||||
'owner_id' => $owner->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$uploadedFiles = [];
|
$uploadedFiles = [];
|
||||||
$replacedFiles = [];
|
$replacedFiles = [];
|
||||||
@ -268,7 +320,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
|
|||||||
if ($nameId === null) {
|
if ($nameId === null) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
return ApiResponse::BAD_REQUEST->response([
|
||||||
'message' => "Falta el name_id para el archivo",
|
'message' => "Falta el nombre para el archivo, busca en el catálogo de nombres",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +369,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
|
|||||||
'vehicle_id' => $vehicle->id,
|
'vehicle_id' => $vehicle->id,
|
||||||
'tag_id' => $tag->id,
|
'tag_id' => $tag->id,
|
||||||
'action_type' => 'actualizacion',
|
'action_type' => 'actualizacion',
|
||||||
'performed_by' => Auth::id(),
|
'performed_by' => Auth::id()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,7 +562,6 @@ private function detectVehicleChanges($vehicle, array $vehicleDataEstatal): bool
|
|||||||
'clase_veh',
|
'clase_veh',
|
||||||
'tipo_servicio',
|
'tipo_servicio',
|
||||||
'rfv',
|
'rfv',
|
||||||
'rfc',
|
|
||||||
'ofcexpedicion',
|
'ofcexpedicion',
|
||||||
'tipo_veh',
|
'tipo_veh',
|
||||||
'numptas',
|
'numptas',
|
||||||
|
|||||||
@ -20,9 +20,9 @@ public function authorize(): bool
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'record_id' => 'required|exists:records,id',
|
'record_id' => 'nullable|exists:records,id',
|
||||||
'folio' => 'required|string',
|
'folio' => 'required|string',
|
||||||
'cancellation_reason' => 'required|in:fallo_lectura_handheld,cambio_parabrisas,roto_al_pegarlo,extravio,otro',
|
'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',
|
||||||
];
|
];
|
||||||
@ -38,11 +38,11 @@ public function messages(): array
|
|||||||
'record_id.integer' => 'El id del expediente debe ser un número entero.',
|
'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.required' => 'El motivo de cancelación es obligatorio.',
|
'cancellation_reason_id.required' => 'El motivo de cancelación es obligatorio.',
|
||||||
'cancellation_reason.in' => 'El motivo de cancelación no es válido. Opciones: fallo_lectura_handheld, cambio_parabrisas, roto_al_pegarlo, extravio, otro.',
|
'cancellation_reason_id.exists' => 'El motivo de cancelación no es válido.',
|
||||||
|
|
||||||
'cancellation_observations.string' => 'Las observaciones deben ser texto.',
|
'new_tag_number.exists' => 'El nuevo tag no existe',
|
||||||
'cancellation_observations.max' => 'Las observaciones no pueden exceder 1000 caracteres.',
|
'folio.required_with' => 'El folio es requerido cuando se proporciona un nuevo tag',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
<?php namespace App\Http\Requests\Repuve;
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Repuve;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
@ -13,18 +15,63 @@ public function authorize(): bool
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'files' => ['nullable', 'array', 'min:1'],
|
// --- DATOS DEL VEHÍCULO ---
|
||||||
'files.*' => ['file', 'mimes:jpeg,png,jpg,pdf', 'max:10240'],
|
'vehicle.placa' => 'nullable|string|max:20',
|
||||||
'names' => ['nullable', 'array'],
|
'vehicle.marca' => 'nullable|string|max:100',
|
||||||
'names.*' => ['string', 'max:255'],
|
'vehicle.linea' => 'nullable|string|max:100',
|
||||||
|
'vehicle.sublinea' => 'nullable|string|max:100',
|
||||||
|
'vehicle.modelo' => 'nullable|string',
|
||||||
|
'vehicle.color' => 'nullable|string|max:50',
|
||||||
|
'vehicle.numero_motor' => 'nullable|string|max:50',
|
||||||
|
'vehicle.clase_veh' => 'nullable|string|max:50',
|
||||||
|
'vehicle.tipo_servicio' => 'nullable|string|max:50',
|
||||||
|
'vehicle.rfv' => 'nullable|string|max:50',
|
||||||
|
'vehicle.rfc' => 'nullable|string|max:13',
|
||||||
|
'vehicle.ofcexpedicion' => 'nullable|string|max:100',
|
||||||
|
'vehicle.fechaexpedicion' => 'nullable|date',
|
||||||
|
'vehicle.tipo_veh' => 'nullable|string|max:50',
|
||||||
|
'vehicle.numptas' => 'nullable|string',
|
||||||
|
'vehicle.observac' => 'nullable|string|max:500',
|
||||||
|
'vehicle.cve_vehi' => 'nullable|string|max:50',
|
||||||
|
'vehicle.nrpv' => 'nullable|string|max:50',
|
||||||
|
'vehicle.tipo_mov' => 'nullable|string|max:50',
|
||||||
|
|
||||||
|
// --- DATOS DEL PROPIETARIO ---
|
||||||
|
'owner.name' => 'nullable|string|max:100',
|
||||||
|
'owner.paternal' => 'nullable|string|max:100',
|
||||||
|
'owner.maternal' => 'nullable|string|max:100',
|
||||||
|
'owner.rfc' => 'nullable|string|max:13',
|
||||||
|
'owner.curp' => 'nullable|string|max:18',
|
||||||
|
'owner.address' => 'nullable|string|max:255',
|
||||||
|
'owner.tipopers' => 'nullable|boolean',
|
||||||
|
'owner.pasaporte' => 'nullable|string|max:20',
|
||||||
|
'owner.licencia' => 'nullable|string|max:20',
|
||||||
|
'owner.ent_fed' => 'nullable|string|max:50',
|
||||||
|
'owner.munic' => 'nullable|string|max:100',
|
||||||
|
'owner.callep' => 'nullable|string|max:100',
|
||||||
|
'owner.num_ext' => 'nullable|string|max:10',
|
||||||
|
'owner.num_int' => 'nullable|string|max:10',
|
||||||
|
'owner.colonia' => 'nullable|string|max:100',
|
||||||
|
'owner.cp' => 'nullable|string|max:5',
|
||||||
|
'owner.telefono' => 'nullable|string|max:15',
|
||||||
|
|
||||||
|
// --- ARCHIVOS ---
|
||||||
|
'files' => 'nullable|array|min:1',
|
||||||
|
'files.*' => 'file|mimes:jpeg,png,jpg|max:2048',
|
||||||
|
'name_id' => 'nullable|array',
|
||||||
|
'name_id.*' => 'integer|exists:catalog_name_img,id',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function messages(): array
|
public function messages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'vehicle.modelo.string' => 'El modelo debe ser texto',
|
||||||
|
'vehicle.numptas.string' => 'El número de puertas debe ser texto',
|
||||||
|
'owner.tipopers.boolean' => 'El tipo de persona debe ser física o Moral',
|
||||||
|
'owner.cp.max' => 'El código postal debe tener máximo 5 caracteres',
|
||||||
'files.*.mimes' => 'Solo se permiten archivos JPG, PNG o JPEG',
|
'files.*.mimes' => 'Solo se permiten archivos JPG, PNG o JPEG',
|
||||||
'files.*.max' => 'El archivo no debe superar 3MB',
|
'files.*.max' => 'El archivo no debe superar 2MB',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
app/Models/CatalogCancellationReason.php
Normal file
42
app/Models/CatalogCancellationReason.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CatalogCancellationReason extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'code',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'applies_to'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener razones para cancelación
|
||||||
|
*/
|
||||||
|
public function scopeForCancellation($query)
|
||||||
|
{
|
||||||
|
return $query->whereIn('applies_to', ['cancelacion', 'ambos']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener razones para sustitución
|
||||||
|
*/
|
||||||
|
public function scopeForSubstitution($query)
|
||||||
|
{
|
||||||
|
return $query->whereIn('applies_to', ['sustitucion', 'ambos']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs que usan esta razón
|
||||||
|
*/
|
||||||
|
public function vehicleTagLogs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(VehicleTagLog::class, 'cancellation_reason_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,6 +48,11 @@ public function scanHistories()
|
|||||||
return $this->hasMany(ScanHistory::class);
|
return $this->hasMany(ScanHistory::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function cancellationLogs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(TagCancellationLog::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marcar tag como asignado a un vehículo
|
* Marcar tag como asignado a un vehículo
|
||||||
*/
|
*/
|
||||||
|
|||||||
51
app/Models/TagCancellationLog.php
Normal file
51
app/Models/TagCancellationLog.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log de cancelación de tags no asignados
|
||||||
|
*
|
||||||
|
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
||||||
|
*
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
class TagCancellationLog extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'tag_cancellation_logs';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'tag_id',
|
||||||
|
'cancellation_reason_id',
|
||||||
|
'cancellation_observations',
|
||||||
|
'cancellation_at',
|
||||||
|
'cancelled_by',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cancellation_at' => 'datetime',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
|
public function tag()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Tag::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancellationReason()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CatalogCancellationReason::class, 'cancellation_reason_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancelledBy()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'cancelled_by');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -133,4 +133,12 @@ public function module()
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Module::class);
|
return $this->belongsTo(Module::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Módulo del cual el usuario es responsable
|
||||||
|
*/
|
||||||
|
public function responsibleModule()
|
||||||
|
{
|
||||||
|
return $this->hasOne(Module::class, 'responsible_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ class VehicleTagLog extends Model
|
|||||||
'vehicle_id',
|
'vehicle_id',
|
||||||
'tag_id',
|
'tag_id',
|
||||||
'action_type',
|
'action_type',
|
||||||
'cancellation_reason',
|
'cancellation_reason_id',
|
||||||
'cancellation_observations',
|
'cancellation_observations',
|
||||||
'cancellation_at',
|
'cancellation_at',
|
||||||
'cancelled_by',
|
'cancelled_by',
|
||||||
@ -41,6 +41,11 @@ public function cancelledBy() {
|
|||||||
return $this->belongsTo(User::class, 'cancelled_by');
|
return $this->belongsTo(User::class, 'cancelled_by');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function cancellationReason()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CatalogCancellationReason::class, 'cancellation_reason_id');
|
||||||
|
}
|
||||||
|
|
||||||
public function isInscription()
|
public function isInscription()
|
||||||
{
|
{
|
||||||
return $this->action_type === 'inscripcion';
|
return $this->action_type === 'inscripcion';
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('catalog_cancellation_reasons', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('code')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->text('description')->nullable();
|
||||||
|
$table->enum('applies_to', ['cancelacion', 'sustitucion', 'ambos']);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('catalog_cancellation_reasons');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('vehicle_tags_logs', function (Blueprint $table) {
|
||||||
|
// Agregar nueva columna con foreign key
|
||||||
|
$table->foreignId('cancellation_reason_id')
|
||||||
|
->nullable()
|
||||||
|
->after('tag_id')
|
||||||
|
->constrained('catalog_cancellation_reasons')
|
||||||
|
->nullOnDelete();
|
||||||
|
|
||||||
|
// Eliminar el enum viejo
|
||||||
|
$table->dropColumn('cancellation_reason');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('vehicle_tags_logs', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['cancellation_reason_id']);
|
||||||
|
$table->dropColumn('cancellation_reason_id');
|
||||||
|
|
||||||
|
// Restaurar enum (solo si es necesario hacer rollback)
|
||||||
|
$table->enum('cancellation_reason', [
|
||||||
|
'fallo_lectura_handheld',
|
||||||
|
'cambio_parabrisas',
|
||||||
|
'roto_al_pegarlo',
|
||||||
|
'extravio',
|
||||||
|
'otro'
|
||||||
|
])->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tag_cancellation_logs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('tag_id')->constrained('tags')->cascadeOnDelete();
|
||||||
|
$table->foreignId('cancellation_reason_id')->nullable()->constrained('catalog_cancellation_reasons')->nullOnDelete();
|
||||||
|
$table->text('cancellation_observations')->nullable();
|
||||||
|
$table->timestamp('cancellation_at');
|
||||||
|
$table->foreignId('cancelled_by')->nullable()->constrained('users')->nullOnDelete();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tag_cancellation_logs');
|
||||||
|
}
|
||||||
|
};
|
||||||
70
database/seeders/CatalogCancellationReasonSeeder.php
Normal file
70
database/seeders/CatalogCancellationReasonSeeder.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php namespace Database\Seeders;
|
||||||
|
/**
|
||||||
|
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Models\CatalogCancellationReason;
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descripción
|
||||||
|
*
|
||||||
|
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
||||||
|
*
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
class CatalogCancellationReasonSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Ejecutar sembrado de base de datos
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$reasons = [
|
||||||
|
[
|
||||||
|
'code' => '01',
|
||||||
|
'name' => 'Fallo de lectura en handheld',
|
||||||
|
'description' => 'El dispositivo handheld no puede leer el TAG correctamente',
|
||||||
|
'applies_to' => 'ambos',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '02',
|
||||||
|
'name' => 'Cambio de parabrisas',
|
||||||
|
'description' => 'El vehículo requirió cambio de parabrisas',
|
||||||
|
'applies_to' => 'sustitucion',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '03',
|
||||||
|
'name' => 'TAG roto al pegarlo',
|
||||||
|
'description' => 'El TAG se dañó durante la instalación',
|
||||||
|
'applies_to' => 'sustitucion',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '04',
|
||||||
|
'name' => 'Extravío del TAG',
|
||||||
|
'description' => 'El TAG fue extraviado o perdido',
|
||||||
|
'applies_to' => 'ambos',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '05',
|
||||||
|
'name' => 'Daño físico del TAG',
|
||||||
|
'description' => 'El TAG presenta daño físico que impide su funcionamiento',
|
||||||
|
'applies_to' => 'ambos',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => '06',
|
||||||
|
'name' => 'Otro motivo',
|
||||||
|
'description' => 'Motivo no especificado en las opciones anteriores',
|
||||||
|
'applies_to' => 'ambos',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($reasons as $reason) {
|
||||||
|
CatalogCancellationReason::updateOrCreate(
|
||||||
|
['code' => $reason['code']],
|
||||||
|
$reason
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,5 +27,6 @@ public function run(): void
|
|||||||
$this->call(MunicipalitySeeder::class);
|
$this->call(MunicipalitySeeder::class);
|
||||||
$this->call(ModuleSeeder::class);
|
$this->call(ModuleSeeder::class);
|
||||||
$this->call(CatalogTagStatusSeeder::class);
|
$this->call(CatalogTagStatusSeeder::class);
|
||||||
|
$this->call(CatalogCancellationReasonSeeder::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -200,8 +200,7 @@
|
|||||||
|
|
||||||
<!-- Fecha -->
|
<!-- Fecha -->
|
||||||
<div class="date-line">
|
<div class="date-line">
|
||||||
<span class="date-blank">{{ $fecha ?? '' }}</span> de
|
{{ $fecha }} de {{ $mes }} del {{ $anio }}
|
||||||
<span class="date-blank" style="min-width: 90px;">{{ $mes ?? '' }}</span> del 20<span class="date-blank" style="min-width: 30px;">{{ $anio ?? '' }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Título -->
|
<!-- Título -->
|
||||||
|
|||||||
152
resources/views/pdfs/tag.blade.php
Normal file
152
resources/views/pdfs/tag.blade.php
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Constancia Cancelada - REPUVE Tabasco</title>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
margin: 2cm 1.5cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
color: #000;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border: 2px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #000;
|
||||||
|
padding: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14pt;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheader {
|
||||||
|
text-align: center;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 11pt;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-box {
|
||||||
|
height: 200px;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16pt;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-box-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-label {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 8px 10px;
|
||||||
|
width: 25%;
|
||||||
|
border-right: 1px solid #000;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
font-size: 9pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-value {
|
||||||
|
padding: 8px 10px;
|
||||||
|
width: 75%;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-row:last-child .data-label,
|
||||||
|
.data-row:last-child .data-value {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
REPUVE TABASCO
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subheader -->
|
||||||
|
<div class="subheader">
|
||||||
|
CONSTANCIA DAÑADA
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Espacio para pegar constancia -->
|
||||||
|
<div class="space-box">
|
||||||
|
<div class="space-box-text">
|
||||||
|
PEGAR CONSTANCIA
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabla de datos -->
|
||||||
|
<table class="data-table">
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">FECHA:</td>
|
||||||
|
<td class="data-value">{{ $cancellation['fecha'] }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">FOLIO:</td>
|
||||||
|
<td class="data-value">{{ $cancellation['folio'] ?? '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">ID CHIP:</td>
|
||||||
|
<td class="data-value">{{ $cancellation['id_chip'] ?? '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">PLACAS:</td>
|
||||||
|
<td class="data-value">{{ $cancellation['placa'] ?? '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">VIN:</td>
|
||||||
|
<td class="data-value">{{ $cancellation['niv'] ?? '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">MOTIVO:</td>
|
||||||
|
<td class="data-value">{{ strtoupper($cancellation['motivo'] ?? '') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">OPERADOR:</td>
|
||||||
|
<td class="data-value">{{ strtoupper($cancellation['operador'] ?? '') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">MÓDULO:</td>
|
||||||
|
<td class="data-value">{{ $cancellation['modulo']}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="data-row">
|
||||||
|
<td class="data-label">UBICACIÓN:</td>
|
||||||
|
<td class="data-value">{{ $cancellation['ubicacion'] }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Repuve\CatalogController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use App\Http\Controllers\Repuve\MunicipalityController;
|
use App\Http\Controllers\Repuve\MunicipalityController;
|
||||||
use App\Http\Controllers\Repuve\RecordController;
|
use App\Http\Controllers\Repuve\RecordController;
|
||||||
@ -39,6 +40,7 @@
|
|||||||
Route::get('expediente/{id}/pdfVerificacion', [RecordController::class, 'generatePdfVerification']);
|
Route::get('expediente/{id}/pdfVerificacion', [RecordController::class, 'generatePdfVerification']);
|
||||||
Route::get('expediente/{id}/pdfConstancia', [RecordController::class, 'generatePdfConstancia']);
|
Route::get('expediente/{id}/pdfConstancia', [RecordController::class, 'generatePdfConstancia']);
|
||||||
Route::get('expediente/{id}/pdfImagenes', [RecordController::class, 'generatePdfImages']);
|
Route::get('expediente/{id}/pdfImagenes', [RecordController::class, 'generatePdfImages']);
|
||||||
|
Route::get('tags/{tag}/pdfTag-cancelado', [RecordController::class, 'pdfCancelledTag']);
|
||||||
Route::post('expediente/pdfFormulario', [RecordController::class, 'generatePdfForm']);
|
Route::post('expediente/pdfFormulario', [RecordController::class, 'generatePdfForm']);
|
||||||
Route::get('RecordErrors', [RecordController::class, 'errors']);
|
Route::get('RecordErrors', [RecordController::class, 'errors']);
|
||||||
|
|
||||||
@ -47,7 +49,9 @@
|
|||||||
Route::post('actualizar', [UpdateController::class, 'vehicleUpdate']);
|
Route::post('actualizar', [UpdateController::class, 'vehicleUpdate']);
|
||||||
|
|
||||||
// Rutas de cancelación de constancias
|
// Rutas de cancelación de constancias
|
||||||
|
Route::resource('/razones-cancelacion', CatalogController::class);
|
||||||
Route::delete('cancelacion', [CancellationController::class, 'cancelarConstancia']);
|
Route::delete('cancelacion', [CancellationController::class, 'cancelarConstancia']);
|
||||||
|
Route::post('tags/cancelar', [CancellationController::class, 'cancelarTagNoAsignado']);
|
||||||
Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']);
|
Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']);
|
||||||
|
|
||||||
//Rutas de Modulos
|
//Rutas de Modulos
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user