/** * ConsumoObraController - Controller de consumos de materiales * * Endpoints REST para registro de consumos de materiales por obra. * * @module Inventory */ import { Router, Request, Response, NextFunction } from 'express'; import { DataSource } from 'typeorm'; import { ConsumoObraService, CreateConsumoDto, ConsumoFilters, } from '../services/consumo-obra.service'; import { AuthMiddleware } from '../../auth/middleware/auth.middleware'; import { AuthService } from '../../auth/services/auth.service'; import { ConsumoObra } from '../entities/consumo-obra.entity'; import { User } from '../../core/entities/user.entity'; import { Tenant } from '../../core/entities/tenant.entity'; import { RefreshToken } from '../../auth/entities/refresh-token.entity'; interface ServiceContext { tenantId: string; userId?: string; } /** * Crear router de consumos */ export function createConsumoObraController(dataSource: DataSource): Router { const router = Router(); // Repositorios const consumoRepository = dataSource.getRepository(ConsumoObra); const userRepository = dataSource.getRepository(User); const tenantRepository = dataSource.getRepository(Tenant); const refreshTokenRepository = dataSource.getRepository(RefreshToken); // Servicios const consumoService = new ConsumoObraService(consumoRepository); const authService = new AuthService(userRepository, tenantRepository, refreshTokenRepository as any); const authMiddleware = new AuthMiddleware(authService, dataSource); // Helper para crear contexto de servicio const getContext = (req: Request): ServiceContext => { if (!req.tenantId) { throw new Error('Tenant ID is required'); } return { tenantId: req.tenantId, userId: req.user?.sub, }; }; /** * GET /consumos * Listar consumos con filtros */ router.get('/', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' }); return; } const page = parseInt(req.query.page as string) || 1; const limit = Math.min(parseInt(req.query.limit as string) || 20, 100); const filters: ConsumoFilters = { fraccionamientoId: req.query.fraccionamientoId as string, loteId: req.query.loteId as string, conceptoId: req.query.conceptoId as string, productId: req.query.productId as string, dateFrom: req.query.dateFrom ? new Date(req.query.dateFrom as string) : undefined, dateTo: req.query.dateTo ? new Date(req.query.dateTo as string) : undefined, }; const result = await consumoService.findWithFilters(getContext(req), filters, page, limit); res.status(200).json({ success: true, data: result.data, pagination: { total: result.total, page: result.page, limit: result.limit, totalPages: result.totalPages, }, }); } catch (error) { next(error); } }); /** * GET /consumos/stats/:fraccionamientoId * Obtener estadísticas de consumos por fraccionamiento */ router.get('/stats/:fraccionamientoId', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' }); return; } const stats = await consumoService.getStats(getContext(req), req.params.fraccionamientoId); res.status(200).json({ success: true, data: stats }); } catch (error) { next(error); } }); /** * GET /consumos/:id * Obtener consumo por ID */ router.get('/:id', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' }); return; } const consumo = await consumoService.findById(getContext(req), req.params.id); if (!consumo) { res.status(404).json({ error: 'Not Found', message: 'Consumption record not found' }); return; } res.status(200).json({ success: true, data: consumo }); } catch (error) { next(error); } }); /** * POST /consumos * Registrar consumo de material */ router.post('/', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer', 'resident'), async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' }); return; } const dto: CreateConsumoDto = req.body; if (!dto.fraccionamientoId || !dto.productId || dto.quantity === undefined || !dto.consumptionDate) { res.status(400).json({ error: 'Bad Request', message: 'fraccionamientoId, productId, quantity and consumptionDate are required', }); return; } dto.consumptionDate = new Date(dto.consumptionDate); const consumo = await consumoService.create(getContext(req), dto); res.status(201).json({ success: true, data: consumo }); } catch (error) { next(error); } }); /** * DELETE /consumos/:id * Eliminar registro de consumo (soft delete) */ router.delete('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer'), async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' }); return; } const deleted = await consumoService.softDelete(getContext(req), req.params.id); if (!deleted) { res.status(404).json({ error: 'Not Found', message: 'Consumption record not found' }); return; } res.status(200).json({ success: true, message: 'Consumption record deleted' }); } catch (error) { next(error); } }); return router; } export default createConsumoObraController;