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)
{
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();

View File

@ -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();

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;
}
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',
];
}
}

View File

@ -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',
];
}
}

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();
}
public function packages()
{
return $this->hasMany(Package::class);
}
public function responsible()
{
return $this->belongsTo(User::class, 'responsible_id');

View File

@ -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);
}
}

View File

@ -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;
}
}

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
use App\Http\Controllers\BoxController;
use App\Http\Controllers\Repuve\CatalogController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Repuve\MunicipalityController;
@ -79,6 +80,9 @@
//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 */