REFACTOR: Eliminar el modelo y controlador de cajas, actualizar relaciones en paquetes y etiquetas

This commit is contained in:
Juan Felipe Zapata Moreno 2025-12-05 23:04:20 -06:00
parent 8c6afe40bc
commit baf3961036
14 changed files with 196 additions and 347 deletions

View File

@ -1,160 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Repuve\BoxStoreRequest;
use App\Http\Requests\Repuve\BoxUpdateRequest;
use App\Models\Box;
use App\Models\Package;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Notsoweb\ApiResponse\Enums\ApiResponse;
class BoxController extends Controller
{
public function index(Request $request)
{
try {
$boxes = Box::with(['package:id,lot,description'])->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(),
]);
}
}
}

View File

@ -303,11 +303,10 @@ public function searchRecord(Request $request)
'vehicle',
'vehicle.owner',
// Tag con Box y Package
'vehicle.tag:id,vehicle_id,folio,tag_number,status_id,box_id',
// Tag con Package
'vehicle.tag:id,vehicle_id,folio,tag_number,status_id,package_id',
'vehicle.tag.status:id,code,name',
'vehicle.tag.box:id,box_number,package_id',
'vehicle.tag.box.package:id,lot',
'vehicle.tag.package:id,lot,box_number',
// Archivos
'files:id,record_id,name_id,path,md5',
@ -400,21 +399,18 @@ public function searchRecord(Request $request)
// Propietario
'owner' => $record->vehicle->owner,
// Tag y Caja
// Tag y Paquete
'tag' => $record->vehicle->tag ? [
'id' => $record->vehicle->tag->id,
'folio' => $record->vehicle->tag->folio,
'tag_number' => $record->vehicle->tag->tag_number,
'status' => $record->vehicle->tag->status?->name,
// Caja
'box' => $record->vehicle->tag->box ? [
'id' => $record->vehicle->tag->box->id,
'box_number' => $record->vehicle->tag->box->box_number,
'package' => $record->vehicle->tag->box->package ? [
'id' => $record->vehicle->tag->box->package->id,
'lot' => $record->vehicle->tag->box->package->lot,
] : null,
// Paquete
'package' => $record->vehicle->tag->package ? [
'id' => $record->vehicle->tag->package->id,
'lot' => $record->vehicle->tag->package->lot,
'box_number' => $record->vehicle->tag->package->box_number,
] : null,
] : null,

View File

@ -8,6 +8,7 @@
use App\Http\Requests\Repuve\PackageUpdateRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use Illuminate\Database\QueryException;
use App\Models\Package;
class PackageController extends Controller
@ -16,17 +17,16 @@ class PackageController extends Controller
public function index(Request $request)
{
try {
$packages = Package::with(['boxes'])->withCount('boxes')->orderBy('id', 'ASC');
$packages = Package::with(['tags'])->withCount('tags')->orderBy('id', 'ASC');
if ($request->filled('lote')) {
$packages->where('lot', 'LIKE', '%' . $request->lote . '%');
}
if ($request->filled('caja')) {
$packages->whereHas('boxes', function ($q) use ($request) {
$q->where('box_number', 'LIKE', '%' . $request->caja . '%');
});
$packages->where('box_number', 'LIKE', '%' . $request->caja . '%');
}
return ApiResponse::OK->response([
'packages' => $packages->paginate(config('app.pagination'))
]);
@ -44,23 +44,32 @@ public function store(PackageStoreRequest $request)
DB::beginTransaction();
$package = Package::create([
'lot' => $request->input('lot'),
'total_boxes' => $request->input('total_boxes'),
'description' => $request->input('description'),
'lot' => $request->lot,
'box_number' => $request->box_number,
'starting_page' => $request->starting_page,
'ending_page' => $request->ending_page,
]);
DB::commit();
return ApiResponse::CREATED->response([
'message' => 'Paquete creado exitosamente',
'package' => [
'id' => $package->id,
'lot' => $package->lot,
'total_boxes' => $package->total_boxes,
'description' => $package->description,
'created_at' => $package->created_at->format('Y-m-d H:i:s'),
],
'message' => 'Paquete registrado exitosamente',
'package' => $package,
'expected_tags' => $package->ending_page - $package->starting_page + 1,
'actual_tags' => $package->tags()->count(),
]);
} catch (QueryException $e) {
DB::rollBack();
if ($e->getCode() == 23000 && str_contains($e->getMessage(), 'packages_lot_box_unique')) {
return ApiResponse::BAD_REQUEST->response([
'message' => "Ya existe un paquete con el lote '{$request->lot}' y caja número '{$request->box_number}'.",
]);
}
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al crear el paquete',
'error' => $e->getMessage(),
]);
} catch (\Exception $e) {
DB::rollBack();
@ -74,9 +83,7 @@ public function store(PackageStoreRequest $request)
public function show($id)
{
try {
$package = Package::with([
'boxes.tags'
])->findOrFail($id);
$package = Package::with(['tags'])->findOrFail($id);
return ApiResponse::OK->response([
'package' => $package,
@ -105,8 +112,9 @@ public function update(PackageUpdateRequest $request, $id)
'package' => [
'id' => $package->id,
'lot' => $package->lot,
'total_boxes' => $package->total_boxes,
'description' => $package->description,
'box_number' => $package->box_number,
'starting_page' => $package->starting_page,
'ending_page' => $package->ending_page,
'updated_at' => $package->updated_at->format('Y-m-d H:i:s'),
],
]);
@ -125,10 +133,11 @@ public function destroy($id)
DB::beginTransaction();
$package = Package::findOrFail($id);
if ($package->boxes()->count() > 0) {
if ($package->tags()->count() > 0) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'No se puede eliminar el paquete porque tiene cajas asociadas.',
'boxes_count' => $package->boxes()->count(),
'message' => 'No se puede eliminar el paquete porque tiene tags asociados.',
'tags_count' => $package->tags()->count(),
]);
}

View File

@ -20,8 +20,7 @@ public function index(Request $request)
try {
$tags = Tag::with([
'vehicle:id,placa,niv',
'box:id,box_number,package_id',
'box.package:id,lot',
'package:id,lot,box_number',
'status:id,code,name',
'module:id,name'
])->orderBy('id', 'ASC');
@ -33,13 +32,13 @@ public function index(Request $request)
}
if ($request->has('lot')) {
$tags->whereHas('box.package', function ($q) use ($request) {
$tags->whereHas('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('package_id')) {
$tags->where('package_id', $request->package_id);
}
if ($request->has('module_id')) {
@ -61,7 +60,7 @@ public function store(Request $request)
{
$validated = $request->validate([
'folio' => 'required|string|max:255',
'box_id' => 'required|integer|exists:boxes,id',
'package_id' => 'required|integer|exists:packages,id',
'tag_number' => 'nullable|string|max:255',
]);
@ -74,7 +73,7 @@ public function store(Request $request)
public function show(Tag $tag)
{
$tag->load(['box.package', 'module', 'vehicle', 'status']);
$tag->load(['package', 'module', 'vehicle', 'status']);
return ApiResponse::OK->response([
'tag' => $tag,
@ -102,7 +101,7 @@ public function tagStore(Request $request)
{
try {
$request->validate([
'box_id' => 'required|integer|exists:boxes,id',
'package_id' => 'required|integer|exists:packages,id',
'tags' => 'required|array',
'tags.*.folio' => 'required|string|max:8',
'tags.*.tag_number' => 'nullable|string|max:32',
@ -126,7 +125,7 @@ public function tagStore(Request $request)
$tag = Tag::create([
'folio' => $tagData['folio'],
'tag_number' => $tagData['tag_number'] ?? null,
'box_id' => $request->box_id,
'package_id' => $request->package_id,
'status_id' => $statusAvailable->id,
'vehicle_id' => null,
'module_id' => null,
@ -163,7 +162,7 @@ public function tagStore(Request $request)
'message' => 'Tags importados correctamente.',
'tags' => $createdTags,
'total' => count($createdTags),
'box' => $request->box_id,
'package' => $request->package_id,
]);
} catch (Exception $e) {
DB::rollback();
@ -180,7 +179,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',
'package_id' => 'nullable|integer|exists:packages,id',
]);
DB::beginTransaction();

View File

@ -1,37 +0,0 @@
<?php
namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class BoxStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'box_number' => ['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',
];
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class BoxUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'box_number' => ['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',
];
}
}

View File

@ -12,9 +12,10 @@ public function authorize(): bool
public function rules(): array
{
return [
'lot' => ['required', 'string', 'max:50', 'unique:packages,lot'],
'total_boxes' => ['nullable', 'integer', 'min:0'],
'description' => ['nullable', 'string', 'max:255'],
'lot' => ['required', 'string'],
'box_number' => ['required', 'integer'],
'starting_page' => ['required', 'integer', 'min:1'],
'ending_page' => ['required', 'integer', 'min:1', 'gte:starting_page'],
];
}
@ -22,10 +23,17 @@ public function messages(): array
{
return [
'lot.required' => 'El lote es requerido',
'lot.unique' => 'El lote ya 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',
'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',
'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.gte' => 'La página final debe ser mayor o igual a la página inicial',
];
}
}

View File

@ -11,12 +11,13 @@ public function authorize(): bool
return true;
}
public function rules(): array
public function rules(): array
{
return [
'lot' => ['required', 'string', 'max:50'],
'total_boxes' => ['nullable', 'integer', 'min:0'],
'description' => ['nullable', 'string', 'max:255'],
'lot' => ['required', 'string'],
'box_number' => ['required', 'string'],
'starting_page' => ['required', 'integer', 'min:1'],
'ending_page' => ['required', 'integer', 'min:1', 'gte:starting_page'],
];
}
@ -24,9 +25,17 @@ public function messages(): array
{
return [
'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',
'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',
'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.gte' => 'La página final debe ser mayor o igual a la página inicial',
];
}
}

View File

@ -1,35 +0,0 @@
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Box extends Model
{
protected $fillable = [
'box_number',
'package_id',
'starting_page',
'ending_page',
];
protected function casts(): array
{
return [
'box_number' => '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);
}
}

View File

@ -11,24 +11,18 @@ class Package extends Model
protected $fillable = [
'lot',
'total_boxes',
'description',
'box_number',
'starting_page',
'ending_page',
];
protected $attributes = [
'total_boxes' => 0,
protected $casts = [
'starting_page' => 'integer',
'ending_page' => 'integer',
];
protected function casts(): array
public function tags()
{
return [
'total_boxes' => 'integer',
'description' => 'string',
];
}
public function boxes()
{
return $this->hasMany(Box::class);
return $this->hasMany(Tag::class);
}
}

View File

@ -19,7 +19,7 @@ class Tag extends Model
'folio',
'tag_number',
'vehicle_id',
'box_id',
'package_id',
'module_id',
'status_id',
];
@ -29,9 +29,9 @@ public function vehicle()
return $this->belongsTo(Vehicle::class);
}
public function box()
public function package()
{
return $this->belongsTo(Box::class);
return $this->belongsTo(Package::class);
}
public function status()

View File

@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
DB::table('boxes')->orderBy('id')->chunk(100, function($boxes) {
foreach ($boxes as $box) {
$parentPackage = DB::table('packages')->find($box->package_id);
$newPackageId = DB::table('packages')->insertGetId([
'lot' => $parentPackage->lot,
'box_number' => $box->box_number,
'starting_page' => $box->starting_page,
'ending_page' => $box->ending_page,
'created_at' => $box->created_at,
'updated_at' => $box->updated_at,
]);
// Actualizar tags: box_id → package_id
DB::table('tags')
->where('box_id', $box->id)
->update(['package_id' => $newPackageId]);
}
});
Schema::table('tags', function (Blueprint $table) {
$table->dropForeign(['box_id']);
});
Schema::table('tags', function (Blueprint $table) {
$table->renameColumn('box_id', 'package_id');
});
Schema::table('tags', function (Blueprint $table) {
$table->foreign('package_id')->references('id')->on('packages')->onDelete('cascade');
});
// eliminar tabla boxes
Schema::dropIfExists('boxes');
// 6. Eliminar columnas de packages que ya no se usan
Schema::table('packages', function (Blueprint $table) {
if (Schema::hasColumn('packages', 'total_boxes')) {
$table->dropColumn('total_boxes');
}
if (Schema::hasColumn('packages', 'description')) {
$table->dropColumn('description');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -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::table('packages', function (Blueprint $table) {
$table->integer('box_number')->after('lot');
$table->integer('starting_page')->after('box_number');
$table->integer('ending_page')->after('starting_page');
$table->unique(['lot', 'box_number'], 'packages_lot_box_unique');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('packages', function (Blueprint $table) {
$table->dropUnique('packages_lot_box_unique');
$table->dropColumn(['box_number', 'starting_page', 'ending_page']);
});
}
};

View File

@ -81,10 +81,6 @@
//Rutas de nombres de archivos en catálogo
Route::resource('catalog-name-imgs', CatalogNameImgController::class);
//Ruta cajas
Route::resource('boxes', BoxController::class);
});
/** Rutas públicas */