diff --git a/app/Http/Controllers/Repuve/ExcelController.php b/app/Http/Controllers/Repuve/ExcelController.php index 0201bcc..88011c8 100644 --- a/app/Http/Controllers/Repuve/ExcelController.php +++ b/app/Http/Controllers/Repuve/ExcelController.php @@ -252,7 +252,7 @@ private function prepareExcelData($logs) $newTag->folio ?? 'N/A', $newTag->tag_number ?? 'N/A', $log->created_at->format('d/m/Y'), - $log->cancellation_observations ?? 'CONSTANCIA DAÑADA', + $log->cancellation_observations ?? 'CONSTANCIA SUSTITUIDA', ]; $no++; diff --git a/app/Http/Controllers/Repuve/RecordController.php b/app/Http/Controllers/Repuve/RecordController.php index 5a4f1d6..00e63be 100644 --- a/app/Http/Controllers/Repuve/RecordController.php +++ b/app/Http/Controllers/Repuve/RecordController.php @@ -187,9 +187,9 @@ public function generatePdfForm(Request $request) 'marca' => 'required|string', 'linea' => 'required|string', 'modelo' => 'required|string', - 'niv' => 'required|string', + 'niv' => 'required|string|max:17', 'numero_motor' => 'required|string', - 'placa' => 'required|string', + 'placa' => 'required|string|max:7', 'folio' => 'required|string', 'telefono' => 'nullable|string', ]); diff --git a/app/Http/Controllers/Repuve/TagsController.php b/app/Http/Controllers/Repuve/TagsController.php index 79f7185..91cfa40 100644 --- a/app/Http/Controllers/Repuve/TagsController.php +++ b/app/Http/Controllers/Repuve/TagsController.php @@ -3,8 +3,11 @@ namespace App\Http\Controllers\Repuve; use App\Http\Controllers\Controller; +use App\Models\CatalogTagStatus; use Illuminate\Http\Request; use App\Models\Tag; +use Exception; +use Illuminate\Support\Facades\DB; use Notsoweb\ApiResponse\Enums\ApiResponse; class TagsController extends Controller @@ -73,4 +76,139 @@ public function destroy(Tag $tag) ]); } } + + /* -------------------------------------------------------------------------- */ + + public function tagStore(Request $request) + { + try { + $request->validate([ + 'package_id' => 'required|integer|exists:packages,id', + 'tags' => 'required|array', + 'tags.*.folio' => 'required|string|max:8', + 'tags.*.tag_number' => 'required|string|max:32', + ]); + + DB::beginTransaction(); + + $statusAvailable = CatalogTagStatus::where('name', Tag::STATUS_AVAILABLE)->first(); + + if (!$statusAvailable) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'El tag no tiene estado disponible.', + ]); + } + + $createdTags = []; + $errors = []; + + foreach ($request->tags as $index => $tagData) { + try { + $tag = Tag::create([ + 'folio' => $tagData['folio'], + 'tag_number' => $tagData['tag_number'], + 'package_id' => $request->package_id, + 'status_id' => $statusAvailable->id, + 'vehicle_id' => null, + 'module_id' => null, + ]); + $createdTags[] = $tag; + } catch (Exception $e) { + $errors[] = [ + 'index' => $index, + 'folio' => $tagData['folio'], + 'tag_number' => $tagData['tag_number'], + 'error' => $e->getMessage(), + ]; + } + } + + if (!empty($errors)) { + DB::rollback(); + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'Error al importar tags.', + 'errors' => $errors, + 'exitosos' => $createdTags, + 'fallidos' => count($errors), + ]); + } + + DB::commit(); + return ApiResponse::CREATED->response([ + 'message' => 'Tags importados correctamente.', + 'tags' => $createdTags, + 'total' => count($createdTags), + 'package_id' => $request->package_id, + ]); + } catch (Exception $e) { + DB::rollback(); + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al importar tags.', + 'error' => $e->getMessage(), + ]); + } + } + + public function assignToModule(Request $request) + { + try { + $request->validate([ + 'module_id' => 'required|exists:modules,id', + 'tag_ids' => 'required_without_all:package_id,from_tag_number,to_tag_number|array|min:1', + 'tag_ids.*' => 'exists:tags,id', + 'package_id' => 'required_with:from_tag_number,to_tag_number|exists:packages,id', + 'from_tag_number' => 'required_with:package_id|string', + 'to_tag_number' => 'required_with:package_id|string', + ]); + + DB::beginTransaction(); + + // Determinar qué tags asignar + if ($request->has('tag_ids')) { + // Asignación por IDs específicos + $tags = Tag::with('status')->whereIn('id', $request->tag_ids)->get(); + } else { + // Asignación por rango + $tags = Tag::where('package_id', $request->package_id) + ->where('tag_number', '>=', $request->from_tag_number) + ->where('tag_number', '<=', $request->to_tag_number) + ->get(); + } + + if ($tags->isEmpty()) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'No se encontraron tags con los criterios especificados.', + ]); + } + + // Validar que no estén asignados a vehículos + $unavailableTags = $tags->filter(fn($tag) => $tag->vehicle_id !== null); + + if ($unavailableTags->isNotEmpty()) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'Algunos tags ya están asignados a vehículos.', + 'unavailable_tags' => $unavailableTags->pluck('tag_number')->toArray(), + ]); + } + + // Asignar módulo + $tagIds = $tags->pluck('id')->toArray(); + Tag::whereIn('id', $tagIds)->update(['module_id' => $request->module_id]); + + DB::commit(); + + return ApiResponse::OK->response([ + 'message' => 'Tags asignados al módulo correctamente.', + 'count' => count($tagIds), + 'module_id' => $request->module_id, + 'tags_assigned' => $tags->pluck('tag_number')->toArray(), + ]); + } catch (Exception $e) { + DB::rollback(); + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al asignar tags al módulo.', + 'error' => $e->getMessage(), + ]); + } + } } diff --git a/app/Models/Module.php b/app/Models/Module.php index 2619b12..d9ac07a 100644 --- a/app/Models/Module.php +++ b/app/Models/Module.php @@ -35,6 +35,10 @@ public function municipality() return $this->belongsTo(Municipality::class); } + public function tags(){ + return $this->hasMany(Tag::class, 'module_id'); + } + public function devices() { return $this->belongsTo(Device::class, 'device_module') diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 08e502f..2d67915 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -38,6 +38,10 @@ public function status() return $this->belongsTo(CatalogTagStatus::class, 'status_id'); } + public function module(){ + return $this->belongsTo(Module::class, 'module_id'); + } + public function vehicleTagLogs() { return $this->hasMany(VehicleTagLog::class); diff --git a/database/migrations/2025_11_28_160342_add_module_id_to_tags_table.php b/database/migrations/2025_11_28_160342_add_module_id_to_tags_table.php new file mode 100644 index 0000000..e56b8bf --- /dev/null +++ b/database/migrations/2025_11_28_160342_add_module_id_to_tags_table.php @@ -0,0 +1,30 @@ +unsignedBigInteger('module_id')->nullable()->after('package_id'); + $table->foreign('module_id')->references('id')->on('modules')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('tags', function (Blueprint $table) { + $table->dropForeign(['module_id']); + $table->dropColumn('module_id'); + }); + } +}; diff --git a/resources/views/pdfs/damage.blade.php b/resources/views/pdfs/damage.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/routes/api.php b/routes/api.php index 174f714..3add0a1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -70,6 +70,8 @@ //Ruta Tags Route::resource('tags', TagsController::class); + Route::post('tags/import', [TagsController::class, 'tagStore']); + Route::post('tags/assign-to-module', [TagsController::class, 'assignToModule']); //Rutas de nombres de archivos en catálogo Route::resource('catalog-name-imgs', CatalogNameImgController::class);