feat: cambiar tipo de datos de starting_page y ending_page a string en la tabla packages y actualizar validaciones en las solicitudes
This commit is contained in:
parent
73b0e6f1b1
commit
706625575c
@ -144,18 +144,18 @@ public function store(PackageStoreRequest $request)
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Verificar folios en el mismo lote antes de crear
|
||||
$packageIds = Package::where('lot', $request->lot)->pluck('id');
|
||||
|
||||
$conflicting = Tag::whereIn('package_id', $packageIds)
|
||||
->whereRaw('CAST(folio AS UNSIGNED) BETWEEN ? AND ?', [$request->starting_page, $request->ending_page])
|
||||
// Verificar folios duplicados globalmente (la restricción tags_folio_unique es global)
|
||||
$conflicting = Tag::whereRaw('CAST(folio AS UNSIGNED) BETWEEN ? AND ?', [$request->starting_page, $request->ending_page])
|
||||
->pluck('folio');
|
||||
|
||||
if ($conflicting->isNotEmpty()) {
|
||||
DB::rollBack();
|
||||
return ApiResponse::UNPROCESSABLE_CONTENT->response([
|
||||
'starting_page' => [
|
||||
'Los folios ' . $conflicting->join(', ') . ' ya están registrados en el lote "' . $request->lot . '".',
|
||||
'message' => 'Los folios ingresados ya están registrados.',
|
||||
'errors' => [
|
||||
'starting_page' => [
|
||||
'Los folios ' . $conflicting->join(', ') . ' ya existen en el sistema.',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
@ -167,9 +167,13 @@ public function store(PackageStoreRequest $request)
|
||||
|
||||
$statusAvailable = CatalogTagStatus::where('code', 'available')->firstOrFail();
|
||||
|
||||
for ($page = $request->starting_page; $page <= $request->ending_page; $page++) {
|
||||
$padLength = strlen($request->starting_page);
|
||||
$numericStart = (int) $request->starting_page;
|
||||
$numericEnd = (int) $request->ending_page;
|
||||
|
||||
for ($page = $numericStart; $page <= $numericEnd; $page++) {
|
||||
Tag::create([
|
||||
'folio' => $page,
|
||||
'folio' => str_pad($page, $padLength, '0', STR_PAD_LEFT),
|
||||
'tag_number' => null,
|
||||
'package_id' => $package->id,
|
||||
'status_id' => $statusAvailable->id,
|
||||
@ -183,6 +187,20 @@ public function store(PackageStoreRequest $request)
|
||||
'package' => $package->load('tags'),
|
||||
'tags_created' => $package->tags()->count(),
|
||||
]);
|
||||
} catch (QueryException $e) {
|
||||
DB::rollBack();
|
||||
if ($e->getCode() == 23000 && str_contains($e->getMessage(), 'tags_folio_unique')) {
|
||||
return ApiResponse::UNPROCESSABLE_CONTENT->response([
|
||||
'message' => 'Uno o más folios del rango ingresado ya existen en el sistema.',
|
||||
'errors' => [
|
||||
'starting_page' => ['El rango de folios contiene duplicados. Verifica los valores e intenta de nuevo.'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
return ApiResponse::INTERNAL_ERROR->response([
|
||||
'message' => 'Error al crear el paquete',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return ApiResponse::INTERNAL_ERROR->response([
|
||||
|
||||
@ -137,65 +137,71 @@ public function store(TagStoreRequest $request)
|
||||
// Obtener el paquete
|
||||
$package = Package::findOrFail($validated['package_id']);
|
||||
$folioNumerico = (int) $validated['folio'];
|
||||
$padLength = strlen($validated['folio']);
|
||||
|
||||
// Verificar si el folio está fuera del rango actual del paquete
|
||||
$packageUpdated = false;
|
||||
$rangeChanges = [];
|
||||
$missingTags = [];
|
||||
|
||||
$packageStartNumeric = (int) $package->starting_page;
|
||||
$packageEndNumeric = (int) $package->ending_page;
|
||||
|
||||
// Caso 1: El folio es MENOR que el starting_page (crear tags intermedios)
|
||||
if ($folioNumerico < $package->starting_page) {
|
||||
if ($folioNumerico < $packageStartNumeric) {
|
||||
$rangeChanges['starting_page'] = [
|
||||
'old' => $package->starting_page,
|
||||
'new' => $folioNumerico,
|
||||
'new' => $validated['folio'],
|
||||
];
|
||||
|
||||
// Crear tags intermedios (desde el nuevo folio hasta el starting_page - 1)
|
||||
for ($i = $folioNumerico + 1; $i < $package->starting_page; $i++) {
|
||||
for ($i = $folioNumerico + 1; $i < $packageStartNumeric; $i++) {
|
||||
$folioIntermedio = str_pad($i, $padLength, '0', STR_PAD_LEFT);
|
||||
// Verificar que el tag no exista
|
||||
$existingTag = Tag::where('folio', $i)->where('package_id', $package->id)->first();
|
||||
$existingTag = Tag::where('folio', $folioIntermedio)->where('package_id', $package->id)->first();
|
||||
if (!$existingTag) {
|
||||
Tag::create([
|
||||
'folio' => $i,
|
||||
'folio' => $folioIntermedio,
|
||||
'tag_number' => null,
|
||||
'package_id' => $package->id,
|
||||
'module_id' => null,
|
||||
'status_id' => $statusAvailable->id,
|
||||
'vehicle_id' => null,
|
||||
]);
|
||||
$missingTags[] = $i;
|
||||
$missingTags[] = $folioIntermedio;
|
||||
}
|
||||
}
|
||||
|
||||
$package->starting_page = $folioNumerico;
|
||||
$package->starting_page = $validated['folio'];
|
||||
$packageUpdated = true;
|
||||
}
|
||||
|
||||
// Caso 2: El folio es MAYOR que el ending_page (crear tags intermedios)
|
||||
if ($folioNumerico > $package->ending_page) {
|
||||
if ($folioNumerico > $packageEndNumeric) {
|
||||
$rangeChanges['ending_page'] = [
|
||||
'old' => $package->ending_page,
|
||||
'new' => $folioNumerico,
|
||||
'new' => $validated['folio'],
|
||||
];
|
||||
|
||||
// Crear tags intermedios (desde ending_page + 1 hasta el nuevo folio - 1)
|
||||
for ($i = $package->ending_page + 1; $i < $folioNumerico; $i++) {
|
||||
for ($i = $packageEndNumeric + 1; $i < $folioNumerico; $i++) {
|
||||
$folioIntermedio = str_pad($i, $padLength, '0', STR_PAD_LEFT);
|
||||
// Verificar que el tag no exista
|
||||
$existingTag = Tag::where('folio', $i)->where('package_id', $package->id)->first();
|
||||
$existingTag = Tag::where('folio', $folioIntermedio)->where('package_id', $package->id)->first();
|
||||
if (!$existingTag) {
|
||||
Tag::create([
|
||||
'folio' => $i,
|
||||
'folio' => $folioIntermedio,
|
||||
'tag_number' => null,
|
||||
'package_id' => $package->id,
|
||||
'module_id' => null,
|
||||
'status_id' => $statusAvailable->id,
|
||||
'vehicle_id' => null,
|
||||
]);
|
||||
$missingTags[] = $i;
|
||||
$missingTags[] = $folioIntermedio;
|
||||
}
|
||||
}
|
||||
|
||||
$package->ending_page = $folioNumerico;
|
||||
$package->ending_page = $validated['folio'];
|
||||
$packageUpdated = true;
|
||||
}
|
||||
|
||||
|
||||
@ -284,6 +284,7 @@ public function vehicleUpdate(Request $request) //ACTUALIZAR INFORMACIÓN VEHÍC
|
||||
$request->validate([
|
||||
'placa' => 'required|string|max:7',
|
||||
'telefono' => 'required|string|max:10',
|
||||
'vin' => 'nullable|string|max:32'
|
||||
], [
|
||||
'placa.required' => 'La placa es requerida',
|
||||
'placa.max' => 'La placa no puede exceder 7 caracteres',
|
||||
@ -292,9 +293,16 @@ public function vehicleUpdate(Request $request) //ACTUALIZAR INFORMACIÓN VEHÍC
|
||||
]);
|
||||
|
||||
$placa = $request->input('placa');
|
||||
$vin = $request->input('vin');
|
||||
|
||||
// Buscar vehículo por placa o por VIN
|
||||
$vehicle = Vehicle::with(['owner', 'tag.status'])
|
||||
->where('placa', $placa)
|
||||
->where(function ($query) use ($placa, $vin) {
|
||||
$query->where('placa', $placa);
|
||||
if ($vin) {
|
||||
$query->orWhere('niv', $vin);
|
||||
}
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$vehicle) {
|
||||
@ -383,11 +391,11 @@ public function vehicleUpdate(Request $request) //ACTUALIZAR INFORMACIÓN VEHÍC
|
||||
$ownerDataEstatal
|
||||
);
|
||||
|
||||
// Crear Vehicle
|
||||
$vehicle = Vehicle::create(array_merge(
|
||||
$vehicleDataEstatal,
|
||||
['owner_id' => $owner->id]
|
||||
));
|
||||
// Crear o actualizar Vehicle (updateOrCreate por NIV para manejar cambios de placa)
|
||||
$vehicle = Vehicle::updateOrCreate(
|
||||
['niv' => $vehicleDataEstatal['niv']],
|
||||
array_merge($vehicleDataEstatal, ['owner_id' => $owner->id])
|
||||
);
|
||||
|
||||
// Verificar si el Tag ya existe por folio
|
||||
$tag = Tag::where('folio', $folio)->first();
|
||||
@ -437,6 +445,14 @@ public function vehicleUpdate(Request $request) //ACTUALIZAR INFORMACIÓN VEHÍC
|
||||
'module_id' => Auth::user()->module_id ?? null,
|
||||
]);
|
||||
|
||||
// Registrar log de actualización
|
||||
VehicleTagLog::create([
|
||||
'vehicle_id' => $vehicle->id,
|
||||
'tag_id' => $tag->id,
|
||||
'action_type' => 'actualizacion',
|
||||
'performed_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
// REPUVE Nacional
|
||||
ProcessRepuveResponse::dispatch($record->id, $datosCompletosRaw);
|
||||
|
||||
@ -670,7 +686,7 @@ private function prepararDatosParaInscripcion(string $niv): array
|
||||
return [
|
||||
'ent_fed' => $datos['ent_fed'] ?? '',
|
||||
'ofcexp' => $datos['ofcexp'] ?? '',
|
||||
'fechaexp' => $datos['fechaexp'] ?? '',
|
||||
'fechaexp' => $this->formatFechaForRepuve($datos['fechaexp'] ?? ''),
|
||||
'placa' => $datos['placa'] ?? '',
|
||||
'tarjetacir' => $datos['tarjetacir'] ?? '',
|
||||
'marca' => $datos['marca'] ?? '',
|
||||
@ -803,6 +819,8 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
$hasOwnerChanges = false;
|
||||
$hasFolioChange = false;
|
||||
$oldFolio = $record->folio;
|
||||
$newlyStoredPaths = [];
|
||||
$pathsToDeleteAfterCommit = [];
|
||||
|
||||
if ($request->has('vehicle')) {
|
||||
$vehicleData = $request->input('vehicle', []);
|
||||
@ -1005,8 +1023,8 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
'path' => $fileToDelete->path,
|
||||
];
|
||||
|
||||
// Eliminar archivo físico y registro de BD
|
||||
Storage::disk('public')->delete($fileToDelete->path);
|
||||
// Diferir eliminación del archivo físico hasta después del commit
|
||||
$pathsToDeleteAfterCommit[] = $fileToDelete->path;
|
||||
$fileToDelete->delete();
|
||||
|
||||
// Si es Evidencia Adicional, renumerar las restantes
|
||||
@ -1055,6 +1073,9 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
$nameId = $nameIds[$indx] ?? null;
|
||||
|
||||
if ($nameId === null || $nameId === '') {
|
||||
foreach ($newlyStoredPaths as $orphanPath) {
|
||||
Storage::disk('public')->delete($orphanPath);
|
||||
}
|
||||
DB::rollBack();
|
||||
return ApiResponse::BAD_REQUEST->response([
|
||||
'message' => "Falta el nombre para el archivo en el índice {$indx}",
|
||||
@ -1065,6 +1086,9 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
$catalogName = CatalogNameImg::find($nameId);
|
||||
|
||||
if (!$catalogName) {
|
||||
foreach ($newlyStoredPaths as $orphanPath) {
|
||||
Storage::disk('public')->delete($orphanPath);
|
||||
}
|
||||
DB::rollBack();
|
||||
return ApiResponse::BAD_REQUEST->response([
|
||||
'message' => "No se encontró el catálogo de nombre con id {$nameId}",
|
||||
@ -1121,8 +1145,8 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
$oldPath = $existingFile->path;
|
||||
$oldMd5 = $existingFile->md5;
|
||||
|
||||
// Eliminar archivo físico viejo
|
||||
Storage::disk('public')->delete($oldPath);
|
||||
// Diferir eliminación del archivo viejo hasta después del commit
|
||||
$pathsToDeleteAfterCommit[] = $oldPath;
|
||||
|
||||
// Mantener el mismo nombre de archivo pero actualizar extensión si cambió
|
||||
$pathInfo = pathinfo($oldPath);
|
||||
@ -1131,6 +1155,7 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
|
||||
// Guardar nuevo archivo con el mismo nombre
|
||||
$path = $file->storeAs($directory, $fileName, 'public');
|
||||
$newlyStoredPaths[] = $path;
|
||||
|
||||
// Actualizar registro existente
|
||||
$existingFile->update([
|
||||
@ -1175,6 +1200,7 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
}
|
||||
|
||||
$path = $file->storeAs("records/{$record->folio}", $fileName, 'public');
|
||||
$newlyStoredPaths[] = $path;
|
||||
|
||||
$fileRecord = File::create([
|
||||
'name_id' => $nameId,
|
||||
@ -1225,7 +1251,7 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
$datosCompletos = [
|
||||
'ent_fed' => $owner->ent_fed ?? '',
|
||||
'ofcexp' => $vehicle->ofcexpedicion ?? '',
|
||||
'fechaexp' => $vehicle->fechaexpedicion ?? '',
|
||||
'fechaexp' => $this->formatFechaForRepuve($vehicle->fechaexpedicion ?? ''),
|
||||
'placa' => $vehicle->placa ?? '',
|
||||
'tarjetacir' => $vehicle->rfv ?? '',
|
||||
'marca' => $vehicle->marca ?? '',
|
||||
@ -1266,6 +1292,11 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
|
||||
DB::commit();
|
||||
|
||||
// Eliminar archivos viejos del disco solo después de commit exitoso
|
||||
foreach ($pathsToDeleteAfterCommit as $pathToDelete) {
|
||||
Storage::disk('public')->delete($pathToDelete);
|
||||
}
|
||||
|
||||
// Recargar relaciones para la respuesta
|
||||
$record->load(['vehicle.owner', 'vehicle.tag', 'files', 'error']);
|
||||
|
||||
@ -1333,6 +1364,13 @@ public function updateData(VehicleUpdateRequest $request, $id) // ACTUALIZAR INF
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
// Limpiar archivos nuevos del disco que quedaron huérfanos
|
||||
if (isset($newlyStoredPaths)) {
|
||||
foreach ($newlyStoredPaths as $orphanPath) {
|
||||
Storage::disk('public')->delete($orphanPath);
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse::INTERNAL_ERROR->response([
|
||||
'message' => 'Error al actualizar el expediente',
|
||||
'error' => $e->getMessage(),
|
||||
@ -1354,7 +1392,7 @@ public function resendToRepuve($id)
|
||||
$datosCompletos = [
|
||||
'ent_fed' => $owner->ent_fed ?? '',
|
||||
'ofcexp' => $vehicle->ofcexpedicion ?? '',
|
||||
'fechaexp' => $vehicle->fechaexpedicion ?? '',
|
||||
'fechaexp' => $this->formatFechaForRepuve($vehicle->fechaexpedicion ?? ''),
|
||||
'placa' => $vehicle->placa ?? '',
|
||||
'tarjetacir' => $vehicle->rfv ?? '',
|
||||
'marca' => $vehicle->marca ?? '',
|
||||
@ -1484,4 +1522,28 @@ private function moveRecordFiles(int $recordId, string $oldFolio, string $newFol
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normaliza la fecha de expedición para envío a REPUVE.
|
||||
* Elimina cualquier componente de hora que pueda haber sido agregado
|
||||
* por el ORM o la base de datos (ej: '2026-04-02 00:00:00' → '2026-04-02').
|
||||
*/
|
||||
private function formatFechaForRepuve(?string $fecha): string
|
||||
{
|
||||
if (empty($fecha)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Si tiene hora después de la fecha en formato Y-m-d (ej: '2026-04-02 00:00:00')
|
||||
if (preg_match('/^(\d{4}-\d{2}-\d{2})[\sT]/', $fecha, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
// Si tiene hora después de la fecha en formato d/m/Y (ej: '02/04/2026 00:00:00')
|
||||
if (preg_match('/^(\d{1,2}\/\d{1,2}\/\d{4})[\sT]/', $fecha, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
// Ya está limpia, retornar tal cual
|
||||
return $fecha;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ public function rules(): array
|
||||
return [
|
||||
'lot' => ['required', 'string', Rule::unique('packages', 'lot')->where(fn($q) => $q->where('box_number', $this->input('box_number')))],
|
||||
'box_number' => ['required', 'integer'],
|
||||
'starting_page' => ['required', 'integer', 'min:1'],
|
||||
'ending_page' => ['required', 'integer', 'min:1', 'gte:starting_page'],
|
||||
'starting_page' => ['required', 'string', 'regex:/^\d+$/'],
|
||||
'ending_page' => ['required', 'string', 'regex:/^\d+$/', 'gte:starting_page'],
|
||||
];
|
||||
}
|
||||
|
||||
@ -29,12 +29,10 @@ public function messages(): array
|
||||
'box_number.required' => 'El número de caja es requerido',
|
||||
|
||||
'starting_page.required' => 'La página inicial es requerida',
|
||||
'starting_page.integer' => 'La página inicial debe ser un número',
|
||||
'starting_page.min' => 'La página inicial debe ser al menos 1',
|
||||
'starting_page.regex' => 'La página inicial debe ser un número',
|
||||
|
||||
'ending_page.required' => 'La página final es requerida',
|
||||
'ending_page.integer' => 'La página final debe ser un número',
|
||||
'ending_page.min' => 'La página final debe ser al menos 1',
|
||||
'ending_page.regex' => 'La página final debe ser un número',
|
||||
'ending_page.gte' => 'La página final debe ser mayor o igual a la página inicial',
|
||||
];
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@ public function rules(): array
|
||||
return [
|
||||
'lot' => ['sometimes', 'string'],
|
||||
'box_number' => ['sometimes', 'integer'],
|
||||
'starting_page' => ['sometimes', 'integer', 'min:1'],
|
||||
'ending_page' => ['sometimes', 'integer', 'min:1', 'gte:starting_page'],
|
||||
'starting_page' => ['sometimes', 'string', 'regex:/^\d+$/'],
|
||||
'ending_page' => ['sometimes', 'string', 'regex:/^\d+$/', 'gte:starting_page'],
|
||||
];
|
||||
}
|
||||
|
||||
@ -29,12 +29,10 @@ public function messages(): array
|
||||
'box_number.required' => 'El número de caja es requerido',
|
||||
|
||||
'starting_page.required' => 'La página inicial es requerida',
|
||||
'starting_page.integer' => 'La página inicial debe ser un número',
|
||||
'starting_page.min' => 'La página inicial debe ser al menos 1',
|
||||
'starting_page.regex' => 'La página inicial debe ser un número',
|
||||
|
||||
'ending_page.required' => 'La página final es requerida',
|
||||
'ending_page.integer' => 'La página final debe ser un número',
|
||||
'ending_page.min' => 'La página final debe ser al menos 1',
|
||||
'ending_page.regex' => 'La página final debe ser un número',
|
||||
'ending_page.gte' => 'La página final debe ser mayor o igual a la página inicial',
|
||||
];
|
||||
}
|
||||
|
||||
@ -17,10 +17,7 @@ class Package extends Model
|
||||
'user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'starting_page' => 'integer',
|
||||
'ending_page' => 'integer',
|
||||
];
|
||||
protected $casts = [];
|
||||
|
||||
public function tags()
|
||||
{
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('packages', function (Blueprint $table) {
|
||||
$table->string('starting_page')->change();
|
||||
$table->string('ending_page')->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('packages', function (Blueprint $table) {
|
||||
$table->integer('starting_page')->change();
|
||||
$table->integer('ending_page')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user