repuve-backend-v1/app/Http/Controllers/Repuve/PackageController.php
2026-01-19 16:30:21 -06:00

476 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\QueryException;
use App\Models\Package;
use App\Models\Tag;
class PackageController extends Controller
{
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();
$package = Package::create([
'lot' => $request->lot,
'box_number' => $request->box_number,
'starting_page' => $request->starting_page,
'ending_page' => $request->ending_page,
'user_id' => Auth::id(),
]);
// Obtener el status "available" para los tags
$statusAvailable = CatalogTagStatus::where('code', 'available')->first();
if (!$statusAvailable) {
throw new \Exception('No se encontró el status "Disponible" para los tags');
}
$existingTags = Tag::whereHas('package', function ($query) use ($request) {
$query->where('box_number', $request->box_number);
})
->whereBetween('folio', [$request->starting_page, $request->ending_page])
->get(['folio', 'package_id']);
if ($existingTags->isNotEmpty()) {
DB::rollBack();
return ApiResponse::BAD_REQUEST->response([
'message' => 'Ya existen tags en esta caja con folios en el rango especificado.',
'box_number' => $request->box_number,
'starting_page' => $request->starting_page,
'ending_page' => $request->ending_page,
'folios_conflictivos' => $existingTags->pluck('folio')->toArray(),
'total_folios_conflictivos' => $existingTags->count(),
]);
}
// Crear los tags según el rango de páginas
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 (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();
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 (\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::INTERNAL_ERROR->response([
'message' => 'Error de base de datos al actualizar el paquete',
'error' => $e->getMessage(),
]);
} catch (\Exception $e) {
DB::rollBack();
return ApiResponse::INTERNAL_ERROR->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('folio', '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(),
]);
}
}
}