From 70f3679ba4dbf912979fee35d036c400cae5ba76 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Thu, 4 Dec 2025 11:51:56 -0600 Subject: [PATCH] =?UTF-8?q?ADD:=20Se=20implement=C3=B3=20el=20controlador?= =?UTF-8?q?=20de=20cajas=20y=20se=20a=C3=B1adieron=20las=20rutas=20corresp?= =?UTF-8?q?ondientes,=20junto=20con=20las=20solicitudes=20de=20validaci?= =?UTF-8?q?=C3=B3n=20para=20el=20almacenamiento=20y=20actualizaci=C3=B3n?= =?UTF-8?q?=20de=20cajas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/BoxController.php | 160 ++++++++++++++++++ .../Controllers/Repuve/PackageController.php | 37 ++-- .../Controllers/Repuve/TagsController.php | 21 ++- app/Http/Requests/Repuve/BoxStoreRequest.php | 37 ++++ app/Http/Requests/Repuve/BoxUpdateRequest.php | 32 ++++ .../Requests/Repuve/PackageStoreRequest.php | 16 +- .../Requests/Repuve/PackageUpdateRequest.php | 17 +- app/Models/Box.php | 35 ++++ app/Models/Module.php | 5 - app/Models/Package.php | 19 +-- app/Models/Tag.php | 15 +- ...2025_12_04_094140_modify_package_table.php | 37 ++++ .../2025_12_04_094213_create_boxes_table.php | 31 ++++ .../2025_12_04_095636_modify_tags_table.php | 32 ++++ routes/api.php | 4 + 15 files changed, 424 insertions(+), 74 deletions(-) create mode 100644 app/Http/Controllers/BoxController.php create mode 100644 app/Http/Requests/Repuve/BoxStoreRequest.php create mode 100644 app/Http/Requests/Repuve/BoxUpdateRequest.php create mode 100644 app/Models/Box.php create mode 100644 database/migrations/2025_12_04_094140_modify_package_table.php create mode 100644 database/migrations/2025_12_04_094213_create_boxes_table.php create mode 100644 database/migrations/2025_12_04_095636_modify_tags_table.php diff --git a/app/Http/Controllers/BoxController.php b/app/Http/Controllers/BoxController.php new file mode 100644 index 0000000..aaed47a --- /dev/null +++ b/app/Http/Controllers/BoxController.php @@ -0,0 +1,160 @@ +withCount('tags')->orderBy('id', 'ASC'); + + if ($request->filled('package')) { + $boxes->where('package_id', $request->input('package')); + } + + if ($request->filled('box_number')) { + $boxes->where('box_number', 'LIKE', '%' . $request->box_number . '%'); + } + + if ($request->filled('lote')) { + $boxes->whereHas('package', function ($query) use ($request) { + $query->where('lot', 'LIKE', '%' . $request->lot . '%'); + }); + } + + return ApiResponse::OK->response([ + 'boxes' => $boxes->paginate(config('app.pagination')) + ]); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al obtener las cajas', + 'error' => $e->getMessage(), + ]); + } + } + + public function store(BoxStoreRequest $request) + { + try { + DB::beginTransaction(); + + $box = Box::create([ + 'box_number' => $request->input('box_number'), + 'package_id' => $request->input('package_id'), + 'starting_page' => $request->input('starting_page'), + 'ending_page' => $request->input('ending_page'), + ]); + + $package = Package::find($request->input('package_id')); + if ($package) { + $package->increment('total_boxes'); + } + + DB::commit(); + + return ApiResponse::CREATED->response([ + 'message' => 'Caja creada exitosamente', + 'box' => $box, + ]); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al crear la caja', + 'error' => $e->getMessage(), + ]); + } + } + + public function show($id) + { + try { + $box = Box::with(['package:id,lot,description,total_boxes', 'tags' => function ($query) { + $query->with(['status:id,name', 'module:id,name']) + ->orderBy('folio', 'ASC'); + }])->withCount('tags')->findOrFail($id); + + return ApiResponse::OK->response([ + 'box' => $box + ]); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al obtener la caja', + 'error' => $e->getMessage(), + ]); + } + } + + public function update(BoxUpdateRequest $request, $id) + { + try { + $box = Box::findOrFail($id); + + DB::beginTransaction(); + + $oldPackageId = $box->package_id; + $newPackageId = $request->input('package_id'); + + $box->update($request->validated()); + + if ($oldPackageId != $newPackageId) { + Package::find($oldPackageId)?->decrement('total_boxes'); + Package::find($newPackageId)?->increment('total_boxes'); + } + + DB::commit(); + + return ApiResponse::OK->response([ + 'message' => 'Caja actualizada exitosamente', + 'box' => $box, + ]); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al actualizar la caja', + 'error' => $e->getMessage(), + ]); + } + } + + public function destroy($id) + { + try { + DB::beginTransaction(); + + $box = Box::findOrFail($id); + + // Verificar si tiene tags antes de eliminar + if ($box->tags()->count() > 0) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'No se puede eliminar la caja porque tiene tags asociados.', + 'tags_count' => $box->tags()->count(), + ]); + } + + $packageId = $box->package_id; + $box->delete(); + + // Decrementar el contador de cajas del paquete + Package::find($packageId)?->decrement('total_boxes'); + + DB::commit(); + + return ApiResponse::OK->response([ + 'message' => 'Caja eliminada exitosamente.', + ]); + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al eliminar la caja.', + 'error' => $e->getMessage(), + ]); + } + } +} diff --git a/app/Http/Controllers/Repuve/PackageController.php b/app/Http/Controllers/Repuve/PackageController.php index 49704c8..a49d121 100644 --- a/app/Http/Controllers/Repuve/PackageController.php +++ b/app/Http/Controllers/Repuve/PackageController.php @@ -16,17 +16,16 @@ class PackageController extends Controller public function index(Request $request) { try { - $packages = Package::with([ - 'module:id,name,responsible_id', - 'module.responsible:id,name,email' - ])->orderBy('id', 'ASC'); + $packages = Package::with(['boxes'])->withCount('boxes')->orderBy('id', 'ASC'); if ($request->filled('lote')) { $packages->where('lot', 'LIKE', '%' . $request->lote . '%'); } if ($request->filled('caja')) { - $packages->where('box_number', 'LIKE', '%' . $request->caja . '%'); + $packages->whereHas('boxes', function ($q) use ($request) { + $q->where('box_number', 'LIKE', '%' . $request->caja . '%'); + }); } return ApiResponse::OK->response([ 'packages' => $packages->paginate(config('app.pagination')) @@ -46,10 +45,8 @@ public function store(PackageStoreRequest $request) $package = Package::create([ 'lot' => $request->input('lot'), - 'box_number' => $request->input('box_number'), - 'starting_page' => $request->input('starting_page'), - 'ending_page' => $request->input('ending_page'), - 'module_id' => $request->input('module_id'), + 'total_boxes' => $request->input('total_boxes'), + 'description' => $request->input('description'), ]); @@ -60,10 +57,8 @@ public function store(PackageStoreRequest $request) 'package' => [ 'id' => $package->id, 'lot' => $package->lot, - 'box_number' => $package->box_number, - 'starting_page' => $package->starting_page, - 'ending_page' => $package->ending_page, - 'module_id' => $package->module_id, + 'total_boxes' => $package->total_boxes, + 'description' => $package->description, 'created_at' => $package->created_at->format('Y-m-d H:i:s'), ], ]); @@ -80,8 +75,7 @@ public function show($id) { try { $package = Package::with([ - 'module:id,name,responsible_id', - 'module.responsible:id,name,email' + 'boxes.tags' ])->findOrFail($id); return ApiResponse::OK->response([ @@ -111,10 +105,8 @@ public function update(PackageUpdateRequest $request, $id) 'package' => [ 'id' => $package->id, 'lot' => $package->lot, - 'box_number' => $package->box_number, - 'starting_page' => $package->starting_page, - 'ending_page' => $package->ending_page, - 'module_id' => $package->module_id, + 'total_boxes' => $package->total_boxes, + 'description' => $package->description, 'updated_at' => $package->updated_at->format('Y-m-d H:i:s'), ], ]); @@ -133,6 +125,13 @@ public function destroy($id) DB::beginTransaction(); $package = Package::findOrFail($id); + if ($package->boxes()->count() > 0) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'No se puede eliminar el paquete porque tiene cajas asociadas.', + 'boxes_count' => $package->boxes()->count(), + ]); + } + $package->delete(); DB::commit(); diff --git a/app/Http/Controllers/Repuve/TagsController.php b/app/Http/Controllers/Repuve/TagsController.php index dde8ad9..caa46b9 100644 --- a/app/Http/Controllers/Repuve/TagsController.php +++ b/app/Http/Controllers/Repuve/TagsController.php @@ -20,7 +20,8 @@ public function index(Request $request) try { $tags = Tag::with([ 'vehicle:id,placa,niv', - 'package:id,lot,box_number', + 'box:id,box_number,package_id', + 'box.package:id,lot', 'status:id,code,name', 'module:id,name' ])->orderBy('id', 'ASC'); @@ -32,11 +33,15 @@ public function index(Request $request) } if ($request->has('lot')) { - $tags->whereHas('package', function ($q) use ($request) { + $tags->whereHas('box.package', function ($q) use ($request) { $q->where('lot', $request->lot); }); } + if ($request->has('box_id')) { + $tags->where('box_id', $request->box_id); + } + if ($request->has('module_id')) { $tags->where('module_id', $request->module_id); } @@ -56,8 +61,9 @@ public function store(Request $request) { $validated = $request->validate([ 'folio' => 'required|string|max:255', - 'package_id' => 'required|integer|exists:packages,id', + 'box_id' => 'required|integer|exists:boxes,id', 'tag_number' => 'required|string|max:255', + 'module_id' => 'nullable|integer|exists:modules,id', ]); $tag = Tag::create($validated); @@ -69,6 +75,8 @@ public function store(Request $request) public function show(Tag $tag) { + $tag->load(['box.package', 'module', 'vehicle', 'status']); + return ApiResponse::OK->response([ 'tag' => $tag, ]); @@ -95,7 +103,7 @@ public function tagStore(Request $request) { try { $request->validate([ - 'package_id' => 'required|integer|exists:packages,id', + 'box_id' => 'required|integer|exists:boxes,id', 'tags' => 'required|array', 'tags.*.folio' => 'required|string|max:8', 'tags.*.tag_number' => 'required|string|max:32', @@ -119,7 +127,7 @@ public function tagStore(Request $request) $tag = Tag::create([ 'folio' => $tagData['folio'], 'tag_number' => $tagData['tag_number'], - 'package_id' => $request->package_id, + 'box_id' => $request->box_id, 'status_id' => $statusAvailable->id, 'vehicle_id' => null, 'module_id' => null, @@ -156,7 +164,7 @@ public function tagStore(Request $request) 'message' => 'Tags importados correctamente.', 'tags' => $createdTags, 'total' => count($createdTags), - 'package_id' => $request->package_id, + 'box' => $request->box_id, ]); } catch (Exception $e) { DB::rollback(); @@ -173,6 +181,7 @@ public function assignToModule(Request $request) $request->validate([ 'module_id' => 'required|exists:modules,id', 'cantidad' => 'required|integer|min:1', + 'box_id' => 'nullable|integer|exists:boxes,id', ]); DB::beginTransaction(); diff --git a/app/Http/Requests/Repuve/BoxStoreRequest.php b/app/Http/Requests/Repuve/BoxStoreRequest.php new file mode 100644 index 0000000..d2ca19f --- /dev/null +++ b/app/Http/Requests/Repuve/BoxStoreRequest.php @@ -0,0 +1,37 @@ + ['required', 'string', 'max:100', 'unique:boxes,box_number'], + 'package_id' => ['required', 'integer', 'exists:packages,id'], + 'starting_page' => ['required', 'integer', 'min:1'], + 'ending_page' => ['required', 'integer', 'gt:starting_page'], + ]; + } + + public function messages(): array + { + return [ + 'box_number.required' => 'El número de caja es requerido', + 'box_number.unique' => 'El número de caja ya existe', + 'package_id.required' => 'El paquete es requerido', + 'package_id.exists' => 'El paquete no existe', + 'starting_page.required' => 'La página inicial es requerida', + 'starting_page.min' => 'La página inicial debe ser al menos 1', + 'ending_page.required' => 'La página final es requerida', + 'ending_page.gt' => 'La página final debe ser mayor a la página inicial', + ]; + } +} diff --git a/app/Http/Requests/Repuve/BoxUpdateRequest.php b/app/Http/Requests/Repuve/BoxUpdateRequest.php new file mode 100644 index 0000000..0692e19 --- /dev/null +++ b/app/Http/Requests/Repuve/BoxUpdateRequest.php @@ -0,0 +1,32 @@ + ['nullable', 'string', 'max:100'], + 'package_id' => ['nullable', 'integer', 'exists:packages,id'], + 'starting_page' => ['nullable', 'integer', 'min:1'], + 'ending_page' => ['nullable', 'integer', 'gt:starting_page'], + ]; + } + + public function messages(): array + { + return [ + 'package_id.exists' => 'El paquete no existe', + 'starting_page.min' => 'La página inicial debe ser al menos 1', + 'ending_page.gt' => 'La página final debe ser mayor a la página inicial', + ]; + } +} diff --git a/app/Http/Requests/Repuve/PackageStoreRequest.php b/app/Http/Requests/Repuve/PackageStoreRequest.php index 3807391..f1198b3 100644 --- a/app/Http/Requests/Repuve/PackageStoreRequest.php +++ b/app/Http/Requests/Repuve/PackageStoreRequest.php @@ -9,14 +9,12 @@ public function authorize(): bool return true; } - public function rules(): array + public function rules(): array { return [ 'lot' => ['required', 'string', 'max:50', 'unique:packages,lot'], - 'box_number' => ['required', 'string', 'max:100'], - 'starting_page' => ['required', 'max:255'], - 'ending_page' => ['required', 'max:255'], - 'module_id' => ['required', 'exists:modules,id'], + 'total_boxes' => ['nullable', 'integer', 'min:0'], + 'description' => ['nullable', 'string', 'max:255'], ]; } @@ -25,11 +23,9 @@ public function messages(): array return [ 'lot.required' => 'El lote es requerido', 'lot.unique' => 'El lote ya existe', - 'box_number.required' => 'El número de caja es requerido', - 'starting_page.required' => 'Tag de inicio es requerido', - 'ending_page.required' => 'Tag de fin es requerido', - 'module_id.required' => 'El módulo es requerido', - 'module_id.exists' => 'El módulo no existe', + 'total_boxes.integer' => 'El total de cajas debe ser un número', + 'total_boxes.min' => 'El total de cajas no puede ser negativo', + 'description.max' => 'La descripción no puede exceder 255 caracteres', ]; } } diff --git a/app/Http/Requests/Repuve/PackageUpdateRequest.php b/app/Http/Requests/Repuve/PackageUpdateRequest.php index fbac8fa..614b8a2 100644 --- a/app/Http/Requests/Repuve/PackageUpdateRequest.php +++ b/app/Http/Requests/Repuve/PackageUpdateRequest.php @@ -14,22 +14,19 @@ public function authorize(): bool public function rules(): array { return [ - 'lot' => ['required', 'string'], - 'box_number' => ['required', 'string'], - 'starting_page' => ['required'], - 'ending_page' => ['required'], - 'module_id' => ['required', 'exists:modules,id'], + 'lot' => ['required', 'string', 'max:50'], + 'total_boxes' => ['nullable', 'integer', 'min:0'], + 'description' => ['nullable', 'string', 'max:255'], ]; } public function messages(): array { return [ - 'box_number.required' => 'El número de caja es requerido', - 'starting_page.required' => 'Tag de inicio es requerido', - 'ending_page.required' => 'Tag de fin es requerido', - 'module_id.required' => 'El módulo es requerido', - 'module_id.exists' => 'El módulo no existe', + 'lot.required' => 'El lote es requerido', + 'total_boxes.integer' => 'El total de cajas debe ser un número', + 'total_boxes.min' => 'El total de cajas no puede ser negativo', + 'description.max' => 'La descripción no puede exceder 255 caracteres', ]; } } diff --git a/app/Models/Box.php b/app/Models/Box.php new file mode 100644 index 0000000..7cbb417 --- /dev/null +++ b/app/Models/Box.php @@ -0,0 +1,35 @@ + 'string', + 'package_id' => 'integer', + 'starting_page' => 'integer', + 'ending_page' => 'integer', + ]; + } + + public function package() + { + return $this->belongsTo(Package::class); + } + + public function tags() + { + return $this->hasMany(Tag::class); + } + +} diff --git a/app/Models/Module.php b/app/Models/Module.php index 97120bf..274aaf4 100644 --- a/app/Models/Module.php +++ b/app/Models/Module.php @@ -59,11 +59,6 @@ public function activeDevices() ->withTimestamps(); } - public function packages() - { - return $this->hasMany(Package::class); - } - public function responsible() { return $this->belongsTo(User::class, 'responsible_id'); diff --git a/app/Models/Package.php b/app/Models/Package.php index 55a6ba2..ca62276 100644 --- a/app/Models/Package.php +++ b/app/Models/Package.php @@ -11,27 +11,20 @@ class Package extends Model protected $fillable = [ 'lot', - 'box_number', - 'starting_page', - 'ending_page', - 'module_id', + 'total_boxes', + 'description', ]; protected function casts(): array { return [ - 'starting_page' => 'integer', - 'ending_page' => 'integer', + 'total_boxes' => 'integer', + 'description' => 'string', ]; } - public function module() + public function boxes() { - return $this->belongsTo(Module::class); - } - - public function tags() - { - return $this->hasMany(Tag::class); + return $this->hasMany(Box::class); } } diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 2d67915..63d1f2a 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -19,7 +19,8 @@ class Tag extends Model 'folio', 'tag_number', 'vehicle_id', - 'package_id', + 'box_id', + 'module_id', 'status_id', ]; @@ -28,9 +29,9 @@ public function vehicle() return $this->belongsTo(Vehicle::class); } - public function package() + public function box() { - return $this->belongsTo(Package::class); + return $this->belongsTo(Box::class); } public function status() @@ -81,7 +82,6 @@ public function markAsCancelled(): void $this->update([ 'status_id' => $statusCancelled->id, 'vehicle_id' => null, - // Mantener el folio porque la columna no acepta null ]); } @@ -109,11 +109,4 @@ public function isCancelled(): bool return $this->status->code === self::STATUS_CANCELLED; } - /** - * Verificar si el tag está perdido (ahora es parte de cancelado) - */ - public function isLost(): bool - { - return $this->status->code === self::STATUS_CANCELLED; - } } diff --git a/database/migrations/2025_12_04_094140_modify_package_table.php b/database/migrations/2025_12_04_094140_modify_package_table.php new file mode 100644 index 0000000..b6cabb8 --- /dev/null +++ b/database/migrations/2025_12_04_094140_modify_package_table.php @@ -0,0 +1,37 @@ +dropForeign(['module_id']); + $table->dropColumn(['box_number', 'starting_page', 'ending_page', 'module_id']); + + $table->integer('total_boxes')->after('lot')->default(0); + $table->string('description')->after('total_boxes')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('packages', function (Blueprint $table) { + $table->string('box_number')->after('lot'); + $table->integer('starting_page')->after('box_number'); + $table->integer('ending_page')->after('starting_page'); + $table->foreignId('module_id')->after('ending_page') + ->constrained('modules') + ->cascadeOnDelete(); + }); + } +}; diff --git a/database/migrations/2025_12_04_094213_create_boxes_table.php b/database/migrations/2025_12_04_094213_create_boxes_table.php new file mode 100644 index 0000000..96759e9 --- /dev/null +++ b/database/migrations/2025_12_04_094213_create_boxes_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('box_number'); + $table->foreignId('package_id')->constrained('packages')->cascadeOnDelete(); + $table->integer('starting_page'); + $table->integer('ending_page'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('boxes'); + } +}; diff --git a/database/migrations/2025_12_04_095636_modify_tags_table.php b/database/migrations/2025_12_04_095636_modify_tags_table.php new file mode 100644 index 0000000..a419207 --- /dev/null +++ b/database/migrations/2025_12_04_095636_modify_tags_table.php @@ -0,0 +1,32 @@ +dropForeign(['package_id']); + $table->renameColumn('package_id', 'box_id'); + $table->foreign('box_id')->references('id')->on('boxes')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('tags', function (Blueprint $table) { + $table->dropForeign(['box_id']); + $table->renameColumn('box_id', 'package_id'); + $table->foreign('package_id')->references('id')->on('packages')->onDelete('cascade'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 29d37d1..edbbb26 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,6 @@