- Se agregó autorización basada en permisos en múltiples Requests. - Nuevos Requests para motivos de cancelación y tags con validación y autorización. - Se añadieron métodos de roles al modelo User (isDeveloper, isAdmin, isPrimary). - Se actualizó el acceso a Telescope usando validación por roles. - Mejora en el manejo de excepciones de autorización. - Actualización de RoleSeeder con nuevas convenciones de permisos. - Actualización de dependencias (composer.lock).
471 lines
19 KiB
PHP
471 lines
19 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Repuve;
|
|
|
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Repuve\PackageStoreRequest;
|
|
use App\Http\Requests\Repuve\PackageUpdateRequest;
|
|
use App\Models\CatalogTagStatus;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Database\QueryException;
|
|
use App\Models\Package;
|
|
use App\Models\Tag;
|
|
use Illuminate\Routing\Controllers\HasMiddleware;
|
|
|
|
class PackageController extends Controller implements HasMiddleware
|
|
{
|
|
/**
|
|
* Middleware
|
|
*/
|
|
public static function middleware(): array
|
|
{
|
|
return [
|
|
self::can('packages.index', ['index']),
|
|
self::can('packages.show', ['show']),
|
|
self::can('packages.destroy', ['destroy']),
|
|
self::can('packages.box_tags', ['getBoxTags']),
|
|
];
|
|
}
|
|
|
|
|
|
public function index(Request $request)
|
|
{
|
|
try {
|
|
// Si NO hay filtro de caja, no cargar las relaciones de tags para optimizar
|
|
$shouldLoadTags = $request->filled('caja') || $request->filled('box_number');
|
|
|
|
$packages = Package::query();
|
|
|
|
if ($shouldLoadTags) {
|
|
$packages->with([
|
|
'tags:id,folio,tag_number,package_id,status_id,vehicle_id,module_id',
|
|
'tags.status:id,code,name',
|
|
'tags.vehicle:id,placa,niv',
|
|
'tags.module:id,name',
|
|
'user:id,name,username'
|
|
]);
|
|
} else {
|
|
$packages->with('user:id,name,username');
|
|
}
|
|
|
|
$packages->withCount('tags')->orderBy('id', 'ASC');
|
|
|
|
if ($request->filled('lote') || $request->filled('lot')) {
|
|
$loteValue = $request->input('lote') ?? $request->input('lot');
|
|
$packages->where('lot', 'LIKE', '%' . trim($loteValue) . '%');
|
|
}
|
|
|
|
if ($request->filled('caja') || $request->filled('box_number')) {
|
|
$cajaValue = $request->input('caja') ?? $request->input('box_number');
|
|
$packages->where('box_number', 'LIKE', '%' . trim($cajaValue) . '%');
|
|
}
|
|
|
|
$paginatedPackages = $packages->paginate(config('app.pagination'));
|
|
|
|
// Validación si no hay resultados
|
|
if ($paginatedPackages->isEmpty()) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'No se encontraron paquetes con los criterios de búsqueda proporcionados.',
|
|
'filters_applied' => [
|
|
'lote' => $request->input('lote') ?? $request->input('lot'),
|
|
'caja' => $request->input('caja') ?? $request->input('box_number'),
|
|
]
|
|
]);
|
|
}
|
|
|
|
// Si hay filtro de caja, incluir estadísticas de tags
|
|
if ($request->filled('caja') || $request->filled('box_number')) {
|
|
$paginatedPackages->getCollection()->transform(function ($package) {
|
|
return [
|
|
'id' => $package->id,
|
|
'lot' => $package->lot,
|
|
'box_number' => $package->box_number,
|
|
'starting_page' => $package->starting_page,
|
|
'ending_page' => $package->ending_page,
|
|
'created_by' => $package->user ? [
|
|
'id' => $package->user->id,
|
|
'name' => $package->user->name,
|
|
'username' => $package->user->username,
|
|
] : null,
|
|
'estadisticas' => [
|
|
'total' => $package->tags->count(),
|
|
'available' => $package->tags->filter(function($tag) {
|
|
return $tag->status && $tag->status->code === 'available';
|
|
})->count(),
|
|
'assigned' => $package->tags->filter(function($tag) {
|
|
return $tag->status && $tag->status->code === 'assigned';
|
|
})->count(),
|
|
'cancelled' => $package->tags->filter(function($tag) {
|
|
return $tag->status && $tag->status->code === 'cancelled';
|
|
})->count(),
|
|
],
|
|
'tags' => $package->tags->map(function ($tag) {
|
|
return [
|
|
'id' => $tag->id,
|
|
'folio' => $tag->folio,
|
|
'tag_number' => $tag->tag_number,
|
|
'status' => $tag->status ? [
|
|
'id' => $tag->status->id,
|
|
'code' => $tag->status->code,
|
|
'name' => $tag->status->name,
|
|
] : null,
|
|
'vehicle' => $tag->vehicle ? [
|
|
'id' => $tag->vehicle->id,
|
|
'placa' => $tag->vehicle->placa,
|
|
'niv' => $tag->vehicle->niv,
|
|
] : null,
|
|
'module' => $tag->module ? [
|
|
'id' => $tag->module->id,
|
|
'name' => $tag->module->name,
|
|
] : null,
|
|
];
|
|
}),
|
|
];
|
|
});
|
|
}
|
|
return ApiResponse::OK->response([
|
|
'Paquetes' => $paginatedPackages,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al obtener los paquetes',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
|
|
public function store(PackageStoreRequest $request)
|
|
{
|
|
try {
|
|
DB::beginTransaction();
|
|
|
|
// Verificar folios en el mismo lote antes de crear
|
|
$packageIds = Package::where('lot', $request->lot)->pluck('id');
|
|
|
|
$conflicting = Tag::whereIn('package_id', $packageIds)
|
|
->whereRaw('CAST(folio AS UNSIGNED) BETWEEN ? AND ?', [$request->starting_page, $request->ending_page])
|
|
->pluck('folio');
|
|
|
|
if ($conflicting->isNotEmpty()) {
|
|
DB::rollBack();
|
|
return ApiResponse::UNPROCESSABLE_CONTENT->response([
|
|
'starting_page' => [
|
|
'Los folios ' . $conflicting->join(', ') . ' ya están registrados en el lote "' . $request->lot . '".',
|
|
],
|
|
]);
|
|
}
|
|
|
|
$form = $request->validated();
|
|
$form['user_id'] = Auth::id();
|
|
|
|
$package = Package::create($form);
|
|
|
|
$statusAvailable = CatalogTagStatus::where('code', 'available')->firstOrFail();
|
|
|
|
for ($page = $request->starting_page; $page <= $request->ending_page; $page++) {
|
|
Tag::create([
|
|
'folio' => $page,
|
|
'tag_number' => null,
|
|
'package_id' => $package->id,
|
|
'status_id' => $statusAvailable->id,
|
|
]);
|
|
}
|
|
|
|
DB::commit();
|
|
|
|
return ApiResponse::CREATED->response([
|
|
'message' => 'Paquete registrado exitosamente con sus tags',
|
|
'package' => $package->load('tags'),
|
|
'tags_created' => $package->tags()->count(),
|
|
]);
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al crear el paquete',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function show($id)
|
|
{
|
|
try {
|
|
$package = Package::with(['tags'])->findOrFail($id);
|
|
|
|
return ApiResponse::OK->response([
|
|
'package' => $package,
|
|
]);
|
|
} catch (ModelNotFoundException $e) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'Paquete no encontrado.',
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al obtener el paquete',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function update(PackageUpdateRequest $request, $id)
|
|
{
|
|
try {
|
|
$package = Package::with('tags')->findOrFail($id);
|
|
$validated = $request->validated();
|
|
|
|
// Validar si el paquete tiene tags asignados
|
|
$hasTags = $package->tags()->count() > 0;
|
|
|
|
// Si tiene tags, validar que no se cambien los rangos de páginas
|
|
if ($hasTags) {
|
|
if (isset($validated['starting_page']) && $validated['starting_page'] != $package->starting_page) {
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => 'No se puede cambiar el rango inicial porque el paquete ya tiene tags asignados.',
|
|
'current_starting_page' => $package->starting_page,
|
|
'requested_starting_page' => $validated['starting_page'],
|
|
'tags_count' => $package->tags()->count(),
|
|
]);
|
|
}
|
|
|
|
if (isset($validated['ending_page']) && $validated['ending_page'] != $package->ending_page) {
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => 'No se puede cambiar el rango final porque el paquete ya tiene tags asignados.',
|
|
'current_ending_page' => $package->ending_page,
|
|
'requested_ending_page' => $validated['ending_page'],
|
|
'tags_count' => $package->tags()->count(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Validar que la combinación de lote + caja sea única
|
|
if (isset($validated['lot']) || isset($validated['box_number'])) {
|
|
$newLot = $validated['lot'] ?? $package->lot;
|
|
$newBoxNumber = $validated['box_number'] ?? $package->box_number;
|
|
|
|
$existingPackage = Package::where('lot', $newLot)
|
|
->where('box_number', $newBoxNumber)
|
|
->where('id', '!=', $package->id)
|
|
->first();
|
|
|
|
if ($existingPackage) {
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => "Ya existe otro paquete con el lote '{$newLot}' y caja '{$newBoxNumber}'.",
|
|
'existing_package_id' => $existingPackage->id,
|
|
'current_lot' => $package->lot,
|
|
'current_box_number' => $package->box_number,
|
|
'requested_lot' => $newLot,
|
|
'requested_box_number' => $newBoxNumber,
|
|
]);
|
|
}
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
|
|
// Guardar valores anteriores para el log
|
|
$changes = [];
|
|
foreach ($validated as $key => $value) {
|
|
if ($package->$key != $value) {
|
|
$changes[$key] = [
|
|
'old' => $package->$key,
|
|
'new' => $value,
|
|
];
|
|
}
|
|
}
|
|
|
|
// Si no hay cambios
|
|
if (empty($changes)) {
|
|
return ApiResponse::OK->response([
|
|
'message' => 'No se realizaron cambios en el paquete.',
|
|
'package' => [
|
|
'id' => $package->id,
|
|
'lot' => $package->lot,
|
|
'box_number' => $package->box_number,
|
|
'starting_page' => $package->starting_page,
|
|
'ending_page' => $package->ending_page,
|
|
],
|
|
]);
|
|
}
|
|
|
|
$package->update($validated);
|
|
|
|
DB::commit();
|
|
|
|
return ApiResponse::OK->response([
|
|
'message' => 'Paquete actualizado exitosamente',
|
|
'package' => [
|
|
'id' => $package->id,
|
|
'lot' => $package->lot,
|
|
'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'),
|
|
],
|
|
'changes' => $changes,
|
|
]);
|
|
} 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 esa combinación de lote y caja.',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'Error de base de datos al actualizar el paquete',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'Error al actualizar el paquete',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function destroy($id)
|
|
{
|
|
try {
|
|
DB::beginTransaction();
|
|
|
|
$package = Package::findOrFail($id);
|
|
|
|
if ($package->tags()->count() > 0) {
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => 'No se puede eliminar el paquete porque tiene tags asociados.',
|
|
'tags_count' => $package->tags()->count(),
|
|
]);
|
|
}
|
|
|
|
$package->delete();
|
|
|
|
DB::commit();
|
|
|
|
return ApiResponse::OK->response([
|
|
'message' => 'Paquete eliminado exitosamente.',
|
|
]);
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al eliminar el paquete.',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtener tags de una caja específica con paginación
|
|
*/
|
|
public function getBoxTags(Request $request)
|
|
{
|
|
try {
|
|
// Si no se envían parámetros, obtener el primer paquete
|
|
if (!$request->has('lot') && !$request->has('box_number')) {
|
|
$package = Package::with('user')->orderBy('id', 'DESC')->first();
|
|
|
|
if (!$package) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'No se encontraron paquetes en el sistema.',
|
|
]);
|
|
}
|
|
} else {
|
|
// Validar parámetros si se proporcionan
|
|
$validated = $request->validate([
|
|
'lot' => 'required|string',
|
|
'box_number' => 'required|string|max:255',
|
|
]);
|
|
|
|
$lot = $validated['lot'];
|
|
$boxNumber = $validated['box_number'];
|
|
|
|
// Buscar el paquete
|
|
$package = Package::with('user')->where('lot', $lot)
|
|
->where('box_number', $boxNumber)
|
|
->first();
|
|
|
|
if (!$package) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'No se encontró un paquete con el lote y caja especificados.',
|
|
'lot' => $lot,
|
|
'box_number' => $boxNumber,
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Obtener tags con paginación
|
|
$tags = Tag::with(['status:id,code,name', 'vehicle:id,placa,niv', 'module:id,name', 'cancellationLogs.cancellationReason'])
|
|
->where('package_id', $package->id)
|
|
->orderBy('id', 'ASC');
|
|
|
|
// Filtro adicional por status si se proporciona
|
|
if ($request->filled('status')) {
|
|
$tags->whereHas('status', function ($q) use ($request) {
|
|
$q->where('code', $request->input('status'));
|
|
});
|
|
}
|
|
|
|
if($request->filled('module_id')) {
|
|
$tags->where('module_id', $request->input('module_id'));
|
|
}
|
|
|
|
$paginatedTags = $tags->paginate($request->input('per_page', 25));
|
|
|
|
// Transformar tags
|
|
$paginatedTags->getCollection()->transform(function ($tag) {
|
|
return [
|
|
'id' => $tag->id,
|
|
'folio' => $tag->folio,
|
|
'tag_number' => $tag->tag_number,
|
|
'status' => [
|
|
'id' => $tag->status->id,
|
|
'code' => $tag->status->code,
|
|
'name' => $tag->status->name,
|
|
],
|
|
'vehicle' => $tag->vehicle ? [
|
|
'id' => $tag->vehicle->id,
|
|
'placa' => $tag->vehicle->placa,
|
|
'niv' => $tag->vehicle->niv,
|
|
] : null,
|
|
'module' => $tag->module ? [
|
|
'id' => $tag->module->id,
|
|
'name' => $tag->module->name,
|
|
] : null,
|
|
'cancellation_reason' => in_array($tag->status?->code, ['cancelled', 'damaged']) && $tag->cancellationLogs->first() ? [
|
|
'reason' => $tag->cancellationLogs->first()->cancellationReason?->name,
|
|
'observations' => $tag->cancellationLogs->first()->cancellation_observations,
|
|
'cancelled_at' => $tag->cancellationLogs->first()->cancellation_at,
|
|
] : null,
|
|
];
|
|
});
|
|
|
|
return ApiResponse::OK->response([
|
|
'package' => [
|
|
'id' => $package->id,
|
|
'lot' => $package->lot,
|
|
'box_number' => $package->box_number,
|
|
'starting_page' => $package->starting_page,
|
|
'ending_page' => $package->ending_page,
|
|
'created_by' => $package->user ? [
|
|
'id' => $package->user->id,
|
|
'name' => $package->user->name,
|
|
'username' => $package->user->username,
|
|
] : null,
|
|
],
|
|
'tags' => $paginatedTags,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return ApiResponse::INTERNAL_ERROR->response([
|
|
'message' => 'Error al obtener los tags de la caja.',
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
}
|