ADD: Se implementó el controlador de cajas y se añadieron las rutas correspondientes, junto con las solicitudes de validación para el almacenamiento y actualización de cajas.

This commit is contained in:
Juan Felipe Zapata Moreno 2025-12-04 11:51:56 -06:00
parent 6af33e4503
commit 70f3679ba4
15 changed files with 424 additions and 74 deletions

View File

@ -0,0 +1,160 @@
<?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

@ -16,17 +16,16 @@ class PackageController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
try { try {
$packages = Package::with([ $packages = Package::with(['boxes'])->withCount('boxes')->orderBy('id', 'ASC');
'module:id,name,responsible_id',
'module.responsible:id,name,email'
])->orderBy('id', 'ASC');
if ($request->filled('lote')) { if ($request->filled('lote')) {
$packages->where('lot', 'LIKE', '%' . $request->lote . '%'); $packages->where('lot', 'LIKE', '%' . $request->lote . '%');
} }
if ($request->filled('caja')) { 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([ return ApiResponse::OK->response([
'packages' => $packages->paginate(config('app.pagination')) 'packages' => $packages->paginate(config('app.pagination'))
@ -46,10 +45,8 @@ public function store(PackageStoreRequest $request)
$package = Package::create([ $package = Package::create([
'lot' => $request->input('lot'), 'lot' => $request->input('lot'),
'box_number' => $request->input('box_number'), 'total_boxes' => $request->input('total_boxes'),
'starting_page' => $request->input('starting_page'), 'description' => $request->input('description'),
'ending_page' => $request->input('ending_page'),
'module_id' => $request->input('module_id'),
]); ]);
@ -60,10 +57,8 @@ public function store(PackageStoreRequest $request)
'package' => [ 'package' => [
'id' => $package->id, 'id' => $package->id,
'lot' => $package->lot, 'lot' => $package->lot,
'box_number' => $package->box_number, 'total_boxes' => $package->total_boxes,
'starting_page' => $package->starting_page, 'description' => $package->description,
'ending_page' => $package->ending_page,
'module_id' => $package->module_id,
'created_at' => $package->created_at->format('Y-m-d H:i:s'), 'created_at' => $package->created_at->format('Y-m-d H:i:s'),
], ],
]); ]);
@ -80,8 +75,7 @@ public function show($id)
{ {
try { try {
$package = Package::with([ $package = Package::with([
'module:id,name,responsible_id', 'boxes.tags'
'module.responsible:id,name,email'
])->findOrFail($id); ])->findOrFail($id);
return ApiResponse::OK->response([ return ApiResponse::OK->response([
@ -111,10 +105,8 @@ public function update(PackageUpdateRequest $request, $id)
'package' => [ 'package' => [
'id' => $package->id, 'id' => $package->id,
'lot' => $package->lot, 'lot' => $package->lot,
'box_number' => $package->box_number, 'total_boxes' => $package->total_boxes,
'starting_page' => $package->starting_page, 'description' => $package->description,
'ending_page' => $package->ending_page,
'module_id' => $package->module_id,
'updated_at' => $package->updated_at->format('Y-m-d H:i:s'), 'updated_at' => $package->updated_at->format('Y-m-d H:i:s'),
], ],
]); ]);
@ -133,6 +125,13 @@ public function destroy($id)
DB::beginTransaction(); DB::beginTransaction();
$package = Package::findOrFail($id); $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(); $package->delete();
DB::commit(); DB::commit();

View File

@ -20,7 +20,8 @@ public function index(Request $request)
try { try {
$tags = Tag::with([ $tags = Tag::with([
'vehicle:id,placa,niv', 'vehicle:id,placa,niv',
'package:id,lot,box_number', 'box:id,box_number,package_id',
'box.package:id,lot',
'status:id,code,name', 'status:id,code,name',
'module:id,name' 'module:id,name'
])->orderBy('id', 'ASC'); ])->orderBy('id', 'ASC');
@ -32,11 +33,15 @@ public function index(Request $request)
} }
if ($request->has('lot')) { if ($request->has('lot')) {
$tags->whereHas('package', function ($q) use ($request) { $tags->whereHas('box.package', function ($q) use ($request) {
$q->where('lot', $request->lot); $q->where('lot', $request->lot);
}); });
} }
if ($request->has('box_id')) {
$tags->where('box_id', $request->box_id);
}
if ($request->has('module_id')) { if ($request->has('module_id')) {
$tags->where('module_id', $request->module_id); $tags->where('module_id', $request->module_id);
} }
@ -56,8 +61,9 @@ public function store(Request $request)
{ {
$validated = $request->validate([ $validated = $request->validate([
'folio' => 'required|string|max:255', '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', 'tag_number' => 'required|string|max:255',
'module_id' => 'nullable|integer|exists:modules,id',
]); ]);
$tag = Tag::create($validated); $tag = Tag::create($validated);
@ -69,6 +75,8 @@ public function store(Request $request)
public function show(Tag $tag) public function show(Tag $tag)
{ {
$tag->load(['box.package', 'module', 'vehicle', 'status']);
return ApiResponse::OK->response([ return ApiResponse::OK->response([
'tag' => $tag, 'tag' => $tag,
]); ]);
@ -95,7 +103,7 @@ public function tagStore(Request $request)
{ {
try { try {
$request->validate([ $request->validate([
'package_id' => 'required|integer|exists:packages,id', 'box_id' => 'required|integer|exists:boxes,id',
'tags' => 'required|array', 'tags' => 'required|array',
'tags.*.folio' => 'required|string|max:8', 'tags.*.folio' => 'required|string|max:8',
'tags.*.tag_number' => 'required|string|max:32', 'tags.*.tag_number' => 'required|string|max:32',
@ -119,7 +127,7 @@ public function tagStore(Request $request)
$tag = Tag::create([ $tag = Tag::create([
'folio' => $tagData['folio'], 'folio' => $tagData['folio'],
'tag_number' => $tagData['tag_number'], 'tag_number' => $tagData['tag_number'],
'package_id' => $request->package_id, 'box_id' => $request->box_id,
'status_id' => $statusAvailable->id, 'status_id' => $statusAvailable->id,
'vehicle_id' => null, 'vehicle_id' => null,
'module_id' => null, 'module_id' => null,
@ -156,7 +164,7 @@ public function tagStore(Request $request)
'message' => 'Tags importados correctamente.', 'message' => 'Tags importados correctamente.',
'tags' => $createdTags, 'tags' => $createdTags,
'total' => count($createdTags), 'total' => count($createdTags),
'package_id' => $request->package_id, 'box' => $request->box_id,
]); ]);
} catch (Exception $e) { } catch (Exception $e) {
DB::rollback(); DB::rollback();
@ -173,6 +181,7 @@ public function assignToModule(Request $request)
$request->validate([ $request->validate([
'module_id' => 'required|exists:modules,id', 'module_id' => 'required|exists:modules,id',
'cantidad' => 'required|integer|min:1', 'cantidad' => 'required|integer|min:1',
'box_id' => 'nullable|integer|exists:boxes,id',
]); ]);
DB::beginTransaction(); DB::beginTransaction();

View File

@ -0,0 +1,37 @@
<?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

@ -0,0 +1,32 @@
<?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

@ -9,14 +9,12 @@ public function authorize(): bool
return true; return true;
} }
public function rules(): array public function rules(): array
{ {
return [ return [
'lot' => ['required', 'string', 'max:50', 'unique:packages,lot'], 'lot' => ['required', 'string', 'max:50', 'unique:packages,lot'],
'box_number' => ['required', 'string', 'max:100'], 'total_boxes' => ['nullable', 'integer', 'min:0'],
'starting_page' => ['required', 'max:255'], 'description' => ['nullable', 'string', 'max:255'],
'ending_page' => ['required', 'max:255'],
'module_id' => ['required', 'exists:modules,id'],
]; ];
} }
@ -25,11 +23,9 @@ public function messages(): array
return [ return [
'lot.required' => 'El lote es requerido', 'lot.required' => 'El lote es requerido',
'lot.unique' => 'El lote ya existe', 'lot.unique' => 'El lote ya existe',
'box_number.required' => 'El número de caja es requerido', 'total_boxes.integer' => 'El total de cajas debe ser un número',
'starting_page.required' => 'Tag de inicio es requerido', 'total_boxes.min' => 'El total de cajas no puede ser negativo',
'ending_page.required' => 'Tag de fin es requerido', 'description.max' => 'La descripción no puede exceder 255 caracteres',
'module_id.required' => 'El módulo es requerido',
'module_id.exists' => 'El módulo no existe',
]; ];
} }
} }

View File

@ -14,22 +14,19 @@ public function authorize(): bool
public function rules(): array public function rules(): array
{ {
return [ return [
'lot' => ['required', 'string'], 'lot' => ['required', 'string', 'max:50'],
'box_number' => ['required', 'string'], 'total_boxes' => ['nullable', 'integer', 'min:0'],
'starting_page' => ['required'], 'description' => ['nullable', 'string', 'max:255'],
'ending_page' => ['required'],
'module_id' => ['required', 'exists:modules,id'],
]; ];
} }
public function messages(): array public function messages(): array
{ {
return [ return [
'box_number.required' => 'El número de caja es requerido', 'lot.required' => 'El lote es requerido',
'starting_page.required' => 'Tag de inicio es requerido', 'total_boxes.integer' => 'El total de cajas debe ser un número',
'ending_page.required' => 'Tag de fin es requerido', 'total_boxes.min' => 'El total de cajas no puede ser negativo',
'module_id.required' => 'El módulo es requerido', 'description.max' => 'La descripción no puede exceder 255 caracteres',
'module_id.exists' => 'El módulo no existe',
]; ];
} }
} }

35
app/Models/Box.php Normal file
View File

@ -0,0 +1,35 @@
<?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

@ -59,11 +59,6 @@ public function activeDevices()
->withTimestamps(); ->withTimestamps();
} }
public function packages()
{
return $this->hasMany(Package::class);
}
public function responsible() public function responsible()
{ {
return $this->belongsTo(User::class, 'responsible_id'); return $this->belongsTo(User::class, 'responsible_id');

View File

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

View File

@ -19,7 +19,8 @@ class Tag extends Model
'folio', 'folio',
'tag_number', 'tag_number',
'vehicle_id', 'vehicle_id',
'package_id', 'box_id',
'module_id',
'status_id', 'status_id',
]; ];
@ -28,9 +29,9 @@ public function vehicle()
return $this->belongsTo(Vehicle::class); return $this->belongsTo(Vehicle::class);
} }
public function package() public function box()
{ {
return $this->belongsTo(Package::class); return $this->belongsTo(Box::class);
} }
public function status() public function status()
@ -81,7 +82,6 @@ public function markAsCancelled(): void
$this->update([ $this->update([
'status_id' => $statusCancelled->id, 'status_id' => $statusCancelled->id,
'vehicle_id' => null, '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; 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;
}
} }

View File

@ -0,0 +1,37 @@
<?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->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();
});
}
};

View File

@ -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('boxes', function (Blueprint $table) {
$table->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');
}
};

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('tags', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -1,5 +1,6 @@
<?php <?php
use App\Http\Controllers\BoxController;
use App\Http\Controllers\Repuve\CatalogController; 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;
@ -79,6 +80,9 @@
//Rutas de nombres de archivos en catálogo //Rutas de nombres de archivos en catálogo
Route::resource('catalog-name-imgs', CatalogNameImgController::class); Route::resource('catalog-name-imgs', CatalogNameImgController::class);
//Ruta cajas
Route::resource('boxes', BoxController::class);
}); });
/** Rutas públicas */ /** Rutas públicas */