diff --git a/src/modules/gestion-flota/controllers/index.ts b/src/modules/gestion-flota/controllers/index.ts index 7e3e542..344f65d 100644 --- a/src/modules/gestion-flota/controllers/index.ts +++ b/src/modules/gestion-flota/controllers/index.ts @@ -1 +1,6 @@ +/** + * Gestion Flota Controllers + */ export { ProductsController, CategoriesController } from './products.controller'; +export * from './unidades.controller'; +export * from './operadores.controller'; diff --git a/src/modules/gestion-flota/controllers/operadores.controller.ts b/src/modules/gestion-flota/controllers/operadores.controller.ts new file mode 100644 index 0000000..20b8c35 --- /dev/null +++ b/src/modules/gestion-flota/controllers/operadores.controller.ts @@ -0,0 +1,266 @@ +import { Request, Response, NextFunction, Router } from 'express'; +import { OperadoresService, CreateOperadorDto, UpdateOperadorDto } from '../services/operadores.service'; +import { TipoLicencia, EstadoOperador } from '../entities'; + +export class OperadoresController { + public router: Router; + + constructor(private readonly operadoresService: OperadoresService) { + this.router = Router(); + this.initializeRoutes(); + } + + private initializeRoutes(): void { + // CRUD básico + this.router.get('/', this.findAll.bind(this)); + this.router.get('/disponibles', this.getOperadoresDisponibles.bind(this)); + this.router.get('/licencia-por-vencer', this.getOperadoresLicenciaPorVencer.bind(this)); + this.router.get('/licencia-vencida', this.getOperadoresLicenciaVencida.bind(this)); + this.router.get('/:id', this.findOne.bind(this)); + this.router.get('/empleado/:numeroEmpleado', this.findByNumeroEmpleado.bind(this)); + this.router.get('/licencia/:numeroLicencia', this.findByLicencia.bind(this)); + this.router.post('/', this.create.bind(this)); + this.router.patch('/:id', this.update.bind(this)); + this.router.delete('/:id', this.delete.bind(this)); + + // Operaciones específicas + this.router.post('/:id/estado', this.cambiarEstado.bind(this)); + } + + private async findAll(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { search, estado, estados, tipoLicencia, sucursalId, limit, offset } = req.query; + + const result = await this.operadoresService.findAll({ + tenantId, + search: search as string, + estado: estado as EstadoOperador, + estados: estados ? (estados as string).split(',') as EstadoOperador[] : undefined, + tipoLicencia: tipoLicencia as TipoLicencia, + sucursalId: sucursalId as string, + limit: limit ? parseInt(limit as string, 10) : undefined, + offset: offset ? parseInt(offset as string, 10) : undefined, + }); + + res.json(result); + } catch (error) { + next(error); + } + } + + private async findOne(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const operador = await this.operadoresService.findOne(id, tenantId); + + if (!operador) { + res.status(404).json({ error: 'Operador no encontrado' }); + return; + } + + res.json({ data: operador }); + } catch (error) { + next(error); + } + } + + private async findByNumeroEmpleado(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { numeroEmpleado } = req.params; + const operador = await this.operadoresService.findByNumeroEmpleado(numeroEmpleado, tenantId); + + if (!operador) { + res.status(404).json({ error: 'Operador no encontrado' }); + return; + } + + res.json({ data: operador }); + } catch (error) { + next(error); + } + } + + private async findByLicencia(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { numeroLicencia } = req.params; + const operador = await this.operadoresService.findByLicencia(numeroLicencia, tenantId); + + if (!operador) { + res.status(404).json({ error: 'Operador no encontrado' }); + return; + } + + res.json({ data: operador }); + } catch (error) { + next(error); + } + } + + private async create(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const dto: CreateOperadorDto = req.body; + const operador = await this.operadoresService.create(tenantId, dto, userId); + res.status(201).json({ data: operador }); + } catch (error) { + next(error); + } + } + + private async update(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const dto: UpdateOperadorDto = req.body; + const operador = await this.operadoresService.update(id, tenantId, dto, userId); + + if (!operador) { + res.status(404).json({ error: 'Operador no encontrado' }); + return; + } + + res.json({ data: operador }); + } catch (error) { + next(error); + } + } + + private async delete(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const deleted = await this.operadoresService.delete(id, tenantId); + + if (!deleted) { + res.status(404).json({ error: 'Operador no encontrado' }); + return; + } + + res.status(204).send(); + } catch (error) { + next(error); + } + } + + private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { estado } = req.body; + + if (!estado) { + res.status(400).json({ error: 'Estado es requerido' }); + return; + } + + const operador = await this.operadoresService.cambiarEstado(id, tenantId, estado, userId); + + if (!operador) { + res.status(404).json({ error: 'Operador no encontrado' }); + return; + } + + res.json({ data: operador }); + } catch (error) { + next(error); + } + } + + private async getOperadoresDisponibles(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const operadores = await this.operadoresService.getOperadoresDisponibles(tenantId); + res.json({ data: operadores }); + } catch (error) { + next(error); + } + } + + private async getOperadoresLicenciaPorVencer(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { dias } = req.query; + const diasAntelacion = dias ? parseInt(dias as string, 10) : 30; + const operadores = await this.operadoresService.getOperadoresLicenciaPorVencer(tenantId, diasAntelacion); + res.json({ data: operadores }); + } catch (error) { + next(error); + } + } + + private async getOperadoresLicenciaVencida(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const operadores = await this.operadoresService.getOperadoresLicenciaVencida(tenantId); + res.json({ data: operadores }); + } catch (error) { + next(error); + } + } +} diff --git a/src/modules/gestion-flota/controllers/unidades.controller.ts b/src/modules/gestion-flota/controllers/unidades.controller.ts new file mode 100644 index 0000000..c92500a --- /dev/null +++ b/src/modules/gestion-flota/controllers/unidades.controller.ts @@ -0,0 +1,301 @@ +import { Request, Response, NextFunction, Router } from 'express'; +import { UnidadesService, CreateUnidadDto, UpdateUnidadDto } from '../services/unidades.service'; +import { TipoUnidad, EstadoUnidad } from '../entities'; + +export class UnidadesController { + public router: Router; + + constructor(private readonly unidadesService: UnidadesService) { + this.router = Router(); + this.initializeRoutes(); + } + + private initializeRoutes(): void { + // CRUD básico + this.router.get('/', this.findAll.bind(this)); + this.router.get('/disponibles', this.getUnidadesDisponibles.bind(this)); + this.router.get('/seguro-por-vencer', this.getUnidadesSeguroPorVencer.bind(this)); + this.router.get('/verificacion-por-vencer', this.getUnidadesVerificacionPorVencer.bind(this)); + this.router.get('/:id', this.findOne.bind(this)); + this.router.get('/numero-economico/:numeroEconomico', this.findByNumeroEconomico.bind(this)); + this.router.get('/placas/:placas', this.findByPlacas.bind(this)); + this.router.post('/', this.create.bind(this)); + this.router.patch('/:id', this.update.bind(this)); + this.router.delete('/:id', this.delete.bind(this)); + + // Operaciones específicas + this.router.post('/:id/estado', this.cambiarEstado.bind(this)); + this.router.post('/:id/odometro', this.actualizarOdometro.bind(this)); + } + + private async findAll(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { search, tipo, estado, estados, sucursalId, limit, offset } = req.query; + + const result = await this.unidadesService.findAll({ + tenantId, + search: search as string, + tipo: tipo as TipoUnidad, + estado: estado as EstadoUnidad, + estados: estados ? (estados as string).split(',') as EstadoUnidad[] : undefined, + sucursalId: sucursalId as string, + limit: limit ? parseInt(limit as string, 10) : undefined, + offset: offset ? parseInt(offset as string, 10) : undefined, + }); + + res.json(result); + } catch (error) { + next(error); + } + } + + private async findOne(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const unidad = await this.unidadesService.findOne(id, tenantId); + + if (!unidad) { + res.status(404).json({ error: 'Unidad no encontrada' }); + return; + } + + res.json({ data: unidad }); + } catch (error) { + next(error); + } + } + + private async findByNumeroEconomico(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { numeroEconomico } = req.params; + const unidad = await this.unidadesService.findByNumeroEconomico(numeroEconomico, tenantId); + + if (!unidad) { + res.status(404).json({ error: 'Unidad no encontrada' }); + return; + } + + res.json({ data: unidad }); + } catch (error) { + next(error); + } + } + + private async findByPlacas(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { placas } = req.params; + const unidad = await this.unidadesService.findByPlacas(placas, tenantId); + + if (!unidad) { + res.status(404).json({ error: 'Unidad no encontrada' }); + return; + } + + res.json({ data: unidad }); + } catch (error) { + next(error); + } + } + + private async create(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const dto: CreateUnidadDto = req.body; + const unidad = await this.unidadesService.create(tenantId, dto, userId); + res.status(201).json({ data: unidad }); + } catch (error) { + next(error); + } + } + + private async update(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const dto: UpdateUnidadDto = req.body; + const unidad = await this.unidadesService.update(id, tenantId, dto, userId); + + if (!unidad) { + res.status(404).json({ error: 'Unidad no encontrada' }); + return; + } + + res.json({ data: unidad }); + } catch (error) { + next(error); + } + } + + private async delete(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const deleted = await this.unidadesService.delete(id, tenantId); + + if (!deleted) { + res.status(404).json({ error: 'Unidad no encontrada' }); + return; + } + + res.status(204).send(); + } catch (error) { + next(error); + } + } + + private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { estado } = req.body; + + if (!estado) { + res.status(400).json({ error: 'Estado es requerido' }); + return; + } + + const unidad = await this.unidadesService.cambiarEstado(id, tenantId, estado, userId); + + if (!unidad) { + res.status(404).json({ error: 'Unidad no encontrada' }); + return; + } + + res.json({ data: unidad }); + } catch (error) { + next(error); + } + } + + private async actualizarOdometro(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { odometro } = req.body; + + if (odometro === undefined) { + res.status(400).json({ error: 'Odómetro es requerido' }); + return; + } + + const unidad = await this.unidadesService.actualizarOdometro(id, tenantId, odometro, userId); + + if (!unidad) { + res.status(404).json({ error: 'Unidad no encontrada' }); + return; + } + + res.json({ data: unidad }); + } catch (error) { + next(error); + } + } + + private async getUnidadesDisponibles(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { tipo } = req.query; + const unidades = await this.unidadesService.getUnidadesDisponibles(tenantId, tipo as TipoUnidad); + res.json({ data: unidades }); + } catch (error) { + next(error); + } + } + + private async getUnidadesSeguroPorVencer(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { dias } = req.query; + const diasAntelacion = dias ? parseInt(dias as string, 10) : 30; + const unidades = await this.unidadesService.getUnidadesSeguroPorVencer(tenantId, diasAntelacion); + res.json({ data: unidades }); + } catch (error) { + next(error); + } + } + + private async getUnidadesVerificacionPorVencer(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { dias } = req.query; + const diasAntelacion = dias ? parseInt(dias as string, 10) : 30; + const unidades = await this.unidadesService.getUnidadesVerificacionPorVencer(tenantId, diasAntelacion); + res.json({ data: unidades }); + } catch (error) { + next(error); + } + } +} diff --git a/src/modules/ordenes-transporte/controllers/index.ts b/src/modules/ordenes-transporte/controllers/index.ts index af52666..54bc897 100644 --- a/src/modules/ordenes-transporte/controllers/index.ts +++ b/src/modules/ordenes-transporte/controllers/index.ts @@ -2,6 +2,9 @@ import { Request, Response, NextFunction, Router } from 'express'; import { SalesService } from '../services'; import { CreateQuotationDto, UpdateQuotationDto, CreateSalesOrderDto, UpdateSalesOrderDto } from '../dto'; +// Export transport-specific controller +export * from './ordenes-transporte.controller'; + export class QuotationsController { public router: Router; constructor(private readonly salesService: SalesService) { diff --git a/src/modules/ordenes-transporte/controllers/ordenes-transporte.controller.ts b/src/modules/ordenes-transporte/controllers/ordenes-transporte.controller.ts new file mode 100644 index 0000000..fff8957 --- /dev/null +++ b/src/modules/ordenes-transporte/controllers/ordenes-transporte.controller.ts @@ -0,0 +1,332 @@ +import { Request, Response, NextFunction, Router } from 'express'; +import { OrdenesTransporteService, CreateOrdenTransporteDto, UpdateOrdenTransporteDto } from '../services/ordenes-transporte.service'; +import { EstadoOrdenTransporte, ModalidadServicio } from '../entities'; + +export class OrdenesTransporteController { + public router: Router; + + constructor(private readonly otService: OrdenesTransporteService) { + this.router = Router(); + this.initializeRoutes(); + } + + private initializeRoutes(): void { + // CRUD básico + this.router.get('/', this.findAll.bind(this)); + this.router.get('/pendientes', this.getOtsPendientes.bind(this)); + this.router.get('/programacion', this.getOtsParaProgramacion.bind(this)); + this.router.get('/cliente/:clienteId', this.getOtsCliente.bind(this)); + this.router.get('/:id', this.findOne.bind(this)); + this.router.get('/numero/:numeroOt', this.findByNumeroOt.bind(this)); + this.router.post('/', this.create.bind(this)); + this.router.patch('/:id', this.update.bind(this)); + + // Operaciones de estado + this.router.post('/:id/estado', this.cambiarEstado.bind(this)); + this.router.post('/:id/confirmar', this.confirmar.bind(this)); + this.router.post('/:id/asignar', this.asignar.bind(this)); + this.router.post('/:id/cancelar', this.cancelar.bind(this)); + } + + private async findAll(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { + search, + estado, + estados, + clienteId, + modalidad, + fechaDesde, + fechaHasta, + limit, + offset, + } = req.query; + + const result = await this.otService.findAll({ + tenantId, + search: search as string, + estado: estado as EstadoOrdenTransporte, + estados: estados ? (estados as string).split(',') as EstadoOrdenTransporte[] : undefined, + clienteId: clienteId as string, + modalidad: modalidad as ModalidadServicio, + fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined, + fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined, + limit: limit ? parseInt(limit as string, 10) : undefined, + offset: offset ? parseInt(offset as string, 10) : undefined, + }); + + res.json(result); + } catch (error) { + next(error); + } + } + + private async findOne(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const ot = await this.otService.findOne(id, tenantId); + + if (!ot) { + res.status(404).json({ error: 'Orden de transporte no encontrada' }); + return; + } + + res.json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async findByNumeroOt(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { numeroOt } = req.params; + const ot = await this.otService.findByNumeroOt(numeroOt, tenantId); + + if (!ot) { + res.status(404).json({ error: 'Orden de transporte no encontrada' }); + return; + } + + res.json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async create(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const dto: CreateOrdenTransporteDto = req.body; + const ot = await this.otService.create(tenantId, dto, userId); + res.status(201).json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async update(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const dto: UpdateOrdenTransporteDto = req.body; + const ot = await this.otService.update(id, tenantId, dto, userId); + + if (!ot) { + res.status(404).json({ error: 'Orden de transporte no encontrada' }); + return; + } + + res.json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { estado } = req.body; + + if (!estado) { + res.status(400).json({ error: 'Estado es requerido' }); + return; + } + + const ot = await this.otService.cambiarEstado(id, tenantId, estado, userId); + + if (!ot) { + res.status(404).json({ error: 'Orden de transporte no encontrada' }); + return; + } + + res.json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async confirmar(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const ot = await this.otService.confirmar(id, tenantId, userId); + + if (!ot) { + res.status(404).json({ error: 'Orden de transporte no encontrada' }); + return; + } + + res.json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async asignar(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { viajeId } = req.body; + + if (!viajeId) { + res.status(400).json({ error: 'viajeId es requerido' }); + return; + } + + const ot = await this.otService.asignar(id, tenantId, viajeId, userId); + + if (!ot) { + res.status(404).json({ error: 'Orden de transporte no encontrada' }); + return; + } + + res.json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async cancelar(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { motivo } = req.body; + + if (!motivo) { + res.status(400).json({ error: 'Motivo de cancelación es requerido' }); + return; + } + + const ot = await this.otService.cancelar(id, tenantId, motivo, userId); + + if (!ot) { + res.status(404).json({ error: 'Orden de transporte no encontrada' }); + return; + } + + res.json({ data: ot }); + } catch (error) { + next(error); + } + } + + private async getOtsPendientes(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const ots = await this.otService.getOtsPendientes(tenantId); + res.json({ data: ots }); + } catch (error) { + next(error); + } + } + + private async getOtsCliente(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { clienteId } = req.params; + const { limit } = req.query; + + const ots = await this.otService.getOtsCliente( + clienteId, + tenantId, + limit ? parseInt(limit as string, 10) : undefined + ); + res.json({ data: ots }); + } catch (error) { + next(error); + } + } + + private async getOtsParaProgramacion(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { fecha } = req.query; + + if (!fecha) { + res.status(400).json({ error: 'Fecha es requerida' }); + return; + } + + const ots = await this.otService.getOtsParaProgramacion(tenantId, new Date(fecha as string)); + res.json({ data: ots }); + } catch (error) { + next(error); + } + } +} diff --git a/src/modules/tracking/controllers/index.ts b/src/modules/tracking/controllers/index.ts index 321114f..3290fe7 100644 --- a/src/modules/tracking/controllers/index.ts +++ b/src/modules/tracking/controllers/index.ts @@ -1,5 +1,4 @@ /** * Tracking Controllers */ -// TODO: Implement controllers -// - tracking.controller.ts +export * from './tracking.controller'; diff --git a/src/modules/tracking/controllers/tracking.controller.ts b/src/modules/tracking/controllers/tracking.controller.ts new file mode 100644 index 0000000..c046288 --- /dev/null +++ b/src/modules/tracking/controllers/tracking.controller.ts @@ -0,0 +1,323 @@ +import { Request, Response, NextFunction, Router } from 'express'; +import { TrackingService, CreateEventoDto, CreateGeocercaDto } from '../services/tracking.service'; +import { TipoEventoTracking, TipoGeocerca } from '../entities'; + +export class TrackingController { + public router: Router; + + constructor(private readonly trackingService: TrackingService) { + this.router = Router(); + this.initializeRoutes(); + } + + private initializeRoutes(): void { + // Eventos/Posiciones + this.router.get('/eventos', this.findEventos.bind(this)); + this.router.get('/posiciones-actuales', this.getPosicionesActuales.bind(this)); + this.router.get('/unidad/:unidadId/ultima-posicion', this.getUltimaPosicion.bind(this)); + this.router.get('/unidad/:unidadId/historial', this.getHistorialPosiciones.bind(this)); + this.router.get('/viaje/:viajeId/ruta', this.getRutaViaje.bind(this)); + this.router.get('/viaje/:viajeId/eventos', this.getEventosViaje.bind(this)); + this.router.post('/eventos', this.registrarEvento.bind(this)); + this.router.post('/posicion', this.registrarPosicion.bind(this)); + + // Geocercas + this.router.get('/geocercas', this.findAllGeocercas.bind(this)); + this.router.get('/geocercas/:id', this.findGeocerca.bind(this)); + this.router.post('/geocercas', this.createGeocerca.bind(this)); + this.router.patch('/geocercas/:id', this.updateGeocerca.bind(this)); + this.router.delete('/geocercas/:id', this.deleteGeocerca.bind(this)); + } + + // ==================== Eventos/Posiciones ==================== + + private async findEventos(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { unidadId, viajeId, tipoEvento, fechaDesde, fechaHasta, limit, offset } = req.query; + + const result = await this.trackingService.findEventos({ + tenantId, + unidadId: unidadId as string, + viajeId: viajeId as string, + tipoEvento: tipoEvento as TipoEventoTracking, + fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined, + fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined, + limit: limit ? parseInt(limit as string, 10) : undefined, + offset: offset ? parseInt(offset as string, 10) : undefined, + }); + + res.json(result); + } catch (error) { + next(error); + } + } + + private async registrarEvento(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const dto: CreateEventoDto = req.body; + const evento = await this.trackingService.registrarEvento(tenantId, dto); + res.status(201).json({ data: evento }); + } catch (error) { + next(error); + } + } + + private async registrarPosicion(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { unidadId, latitud, longitud, velocidad, rumbo, odometro, viajeId, operadorId } = req.body; + + if (!unidadId || latitud === undefined || longitud === undefined) { + res.status(400).json({ error: 'unidadId, latitud y longitud son requeridos' }); + return; + } + + const evento = await this.trackingService.registrarPosicion(tenantId, unidadId, { + latitud, + longitud, + velocidad, + rumbo, + odometro, + viajeId, + operadorId, + }); + + res.status(201).json({ data: evento }); + } catch (error) { + next(error); + } + } + + private async getUltimaPosicion(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { unidadId } = req.params; + const posicion = await this.trackingService.getUltimaPosicion(unidadId, tenantId); + + if (!posicion) { + res.status(404).json({ error: 'No hay posiciones registradas para esta unidad' }); + return; + } + + res.json({ data: posicion }); + } catch (error) { + next(error); + } + } + + private async getHistorialPosiciones(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { unidadId } = req.params; + const { fechaDesde, fechaHasta } = req.query; + + if (!fechaDesde || !fechaHasta) { + res.status(400).json({ error: 'fechaDesde y fechaHasta son requeridos' }); + return; + } + + const posiciones = await this.trackingService.getHistorialPosiciones( + unidadId, + tenantId, + new Date(fechaDesde as string), + new Date(fechaHasta as string) + ); + + res.json({ data: posiciones }); + } catch (error) { + next(error); + } + } + + private async getRutaViaje(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { viajeId } = req.params; + const ruta = await this.trackingService.getRutaViaje(viajeId, tenantId); + res.json({ data: ruta }); + } catch (error) { + next(error); + } + } + + private async getEventosViaje(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { viajeId } = req.params; + const eventos = await this.trackingService.getEventosViaje(viajeId, tenantId); + res.json({ data: eventos }); + } catch (error) { + next(error); + } + } + + private async getPosicionesActuales(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { unidadIds } = req.query; + const ids = unidadIds ? (unidadIds as string).split(',') : undefined; + + const posiciones = await this.trackingService.getPosicionesActuales(tenantId, ids); + res.json({ data: posiciones }); + } catch (error) { + next(error); + } + } + + // ==================== Geocercas ==================== + + private async findAllGeocercas(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { tipo, activa, clienteId, limit, offset } = req.query; + + const result = await this.trackingService.findAllGeocercas({ + tenantId, + tipo: tipo as TipoGeocerca, + activa: activa ? activa === 'true' : undefined, + clienteId: clienteId as string, + limit: limit ? parseInt(limit as string, 10) : undefined, + offset: offset ? parseInt(offset as string, 10) : undefined, + }); + + res.json(result); + } catch (error) { + next(error); + } + } + + private async findGeocerca(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const geocerca = await this.trackingService.findGeocerca(id, tenantId); + + if (!geocerca) { + res.status(404).json({ error: 'Geocerca no encontrada' }); + return; + } + + res.json({ data: geocerca }); + } catch (error) { + next(error); + } + } + + private async createGeocerca(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const dto: CreateGeocercaDto = req.body; + const geocerca = await this.trackingService.createGeocerca(tenantId, dto, userId); + res.status(201).json({ data: geocerca }); + } catch (error) { + next(error); + } + } + + private async updateGeocerca(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const dto: Partial = req.body; + const geocerca = await this.trackingService.updateGeocerca(id, tenantId, dto, userId); + + if (!geocerca) { + res.status(404).json({ error: 'Geocerca no encontrada' }); + return; + } + + res.json({ data: geocerca }); + } catch (error) { + next(error); + } + } + + private async deleteGeocerca(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const deleted = await this.trackingService.deleteGeocerca(id, tenantId); + + if (!deleted) { + res.status(404).json({ error: 'Geocerca no encontrada' }); + return; + } + + res.status(204).send(); + } catch (error) { + next(error); + } + } +} diff --git a/src/modules/viajes/controllers/index.ts b/src/modules/viajes/controllers/index.ts new file mode 100644 index 0000000..fdd01d5 --- /dev/null +++ b/src/modules/viajes/controllers/index.ts @@ -0,0 +1,4 @@ +/** + * Viajes Controllers + */ +export * from './viajes.controller'; diff --git a/src/modules/viajes/controllers/viajes.controller.ts b/src/modules/viajes/controllers/viajes.controller.ts new file mode 100644 index 0000000..e012798 --- /dev/null +++ b/src/modules/viajes/controllers/viajes.controller.ts @@ -0,0 +1,339 @@ +import { Request, Response, NextFunction, Router } from 'express'; +import { ViajesService, CreateViajeDto, UpdateViajeDto } from '../services/viajes.service'; +import { EstadoViaje } from '../entities'; + +export class ViajesController { + public router: Router; + + constructor(private readonly viajesService: ViajesService) { + this.router = Router(); + this.initializeRoutes(); + } + + private initializeRoutes(): void { + // CRUD básico + this.router.get('/', this.findAll.bind(this)); + this.router.get('/en-progreso', this.getViajesEnProgreso.bind(this)); + this.router.get('/operador/:operadorId', this.getViajesOperador.bind(this)); + this.router.get('/unidad/:unidadId', this.getViajesUnidad.bind(this)); + this.router.get('/:id', this.findOne.bind(this)); + this.router.get('/numero/:numeroViaje', this.findByNumero.bind(this)); + this.router.post('/', this.create.bind(this)); + this.router.patch('/:id', this.update.bind(this)); + + // Operaciones de estado + this.router.post('/:id/estado', this.cambiarEstado.bind(this)); + this.router.post('/:id/despachar', this.despachar.bind(this)); + this.router.post('/:id/iniciar-transito', this.iniciarTransito.bind(this)); + this.router.post('/:id/registrar-llegada', this.registrarLlegada.bind(this)); + this.router.post('/:id/registrar-entrega', this.registrarEntrega.bind(this)); + } + + private async findAll(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { + search, + estado, + estados, + unidadId, + operadorId, + clienteId, + fechaDesde, + fechaHasta, + limit, + offset, + } = req.query; + + const result = await this.viajesService.findAll({ + tenantId, + search: search as string, + estado: estado as EstadoViaje, + estados: estados ? (estados as string).split(',') as EstadoViaje[] : undefined, + unidadId: unidadId as string, + operadorId: operadorId as string, + clienteId: clienteId as string, + fechaDesde: fechaDesde ? new Date(fechaDesde as string) : undefined, + fechaHasta: fechaHasta ? new Date(fechaHasta as string) : undefined, + limit: limit ? parseInt(limit as string, 10) : undefined, + offset: offset ? parseInt(offset as string, 10) : undefined, + }); + + res.json(result); + } catch (error) { + next(error); + } + } + + private async findOne(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const viaje = await this.viajesService.findOne(id, tenantId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async findByNumero(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { numeroViaje } = req.params; + const viaje = await this.viajesService.findByNumero(numeroViaje, tenantId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async create(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const dto: CreateViajeDto = req.body; + const viaje = await this.viajesService.create(tenantId, dto, userId); + res.status(201).json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async update(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const dto: UpdateViajeDto = req.body; + const viaje = await this.viajesService.update(id, tenantId, dto, userId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async cambiarEstado(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { estado } = req.body; + + if (!estado) { + res.status(400).json({ error: 'Estado es requerido' }); + return; + } + + const viaje = await this.viajesService.cambiarEstado(id, tenantId, estado, userId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async despachar(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { kmInicial, sellos } = req.body; + + const viaje = await this.viajesService.despachar(id, tenantId, { kmInicial, sellos }, userId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async iniciarTransito(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const viaje = await this.viajesService.iniciarTransito(id, tenantId, userId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async registrarLlegada(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { kmFinal } = req.body; + + const viaje = await this.viajesService.registrarLlegada(id, tenantId, { kmFinal }, userId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async registrarEntrega(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + const userId = req.headers['x-user-id'] as string; + + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { id } = req.params; + const { kmFinal, observaciones } = req.body; + + const viaje = await this.viajesService.registrarEntrega(id, tenantId, { kmFinal, observaciones }, userId); + + if (!viaje) { + res.status(404).json({ error: 'Viaje no encontrado' }); + return; + } + + res.json({ data: viaje }); + } catch (error) { + next(error); + } + } + + private async getViajesEnProgreso(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const viajes = await this.viajesService.getViajesEnProgreso(tenantId); + res.json({ data: viajes }); + } catch (error) { + next(error); + } + } + + private async getViajesOperador(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { operadorId } = req.params; + const viajes = await this.viajesService.getViajesOperador(operadorId, tenantId); + res.json({ data: viajes }); + } catch (error) { + next(error); + } + } + + private async getViajesUnidad(req: Request, res: Response, next: NextFunction): Promise { + try { + const tenantId = req.headers['x-tenant-id'] as string; + if (!tenantId) { + res.status(400).json({ error: 'Tenant ID is required' }); + return; + } + + const { unidadId } = req.params; + const viajes = await this.viajesService.getViajesUnidad(unidadId, tenantId); + res.json({ data: viajes }); + } catch (error) { + next(error); + } + } +}