import { Response, NextFunction } from 'express'; import { z } from 'zod'; import { valuationService, CreateValuationLayerDto } from './valuation.service.js'; import { AuthenticatedRequest, ValidationError, ApiResponse } from '../../shared/types/index.js'; // ============================================================================ // VALIDATION SCHEMAS // ============================================================================ const getProductCostSchema = z.object({ product_id: z.string().uuid(), company_id: z.string().uuid(), }); const createLayerSchema = z.object({ product_id: z.string().uuid(), company_id: z.string().uuid(), quantity: z.number().positive(), unit_cost: z.number().nonnegative(), stock_move_id: z.string().uuid().optional(), description: z.string().max(255).optional(), }); const consumeFifoSchema = z.object({ product_id: z.string().uuid(), company_id: z.string().uuid(), quantity: z.number().positive(), }); const productLayersSchema = z.object({ company_id: z.string().uuid(), include_empty: z.enum(['true', 'false']).optional(), }); // ============================================================================ // CONTROLLER // ============================================================================ class ValuationController { /** * Get cost for a product based on its valuation method * GET /api/inventory/valuation/cost */ async getProductCost(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const validation = getProductCostSchema.safeParse(req.query); if (!validation.success) { throw new ValidationError('Parámetros inválidos', validation.error.issues); } const { product_id, company_id } = validation.data; const result = await valuationService.getProductCost( product_id, company_id, req.user!.tenantId ); const response: ApiResponse = { success: true, data: result, }; res.json(response); } catch (error) { next(error); } } /** * Get valuation summary for a product * GET /api/inventory/valuation/products/:productId/summary */ async getProductSummary(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const { productId } = req.params; const { company_id } = req.query; if (!company_id || typeof company_id !== 'string') { throw new ValidationError('company_id es requerido'); } const result = await valuationService.getProductValuationSummary( productId, company_id, req.user!.tenantId ); const response: ApiResponse = { success: true, data: result, }; res.json(response); } catch (error) { next(error); } } /** * Get valuation layers for a product * GET /api/inventory/valuation/products/:productId/layers */ async getProductLayers(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const { productId } = req.params; const validation = productLayersSchema.safeParse(req.query); if (!validation.success) { throw new ValidationError('Parámetros inválidos', validation.error.issues); } const { company_id, include_empty } = validation.data; const includeEmpty = include_empty === 'true'; const result = await valuationService.getProductLayers( productId, company_id, req.user!.tenantId, includeEmpty ); const response: ApiResponse = { success: true, data: result, }; res.json(response); } catch (error) { next(error); } } /** * Get company-wide valuation report * GET /api/inventory/valuation/report */ async getCompanyReport(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const { company_id } = req.query; if (!company_id || typeof company_id !== 'string') { throw new ValidationError('company_id es requerido'); } const result = await valuationService.getCompanyValuationReport( company_id, req.user!.tenantId ); const response = { success: true, data: result, meta: { total: result.length, totalValue: result.reduce((sum, p) => sum + Number(p.total_value), 0), }, }; res.json(response); } catch (error) { next(error); } } /** * Create a valuation layer manually (for adjustments) * POST /api/inventory/valuation/layers */ async createLayer(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const validation = createLayerSchema.safeParse(req.body); if (!validation.success) { throw new ValidationError('Datos inválidos', validation.error.issues); } const dto: CreateValuationLayerDto = validation.data; const result = await valuationService.createLayer( dto, req.user!.tenantId, req.user!.userId ); const response: ApiResponse = { success: true, data: result, message: 'Capa de valoración creada', }; res.status(201).json(response); } catch (error) { next(error); } } /** * Consume stock using FIFO (for testing/manual adjustments) * POST /api/inventory/valuation/consume */ async consumeFifo(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const validation = consumeFifoSchema.safeParse(req.body); if (!validation.success) { throw new ValidationError('Datos inválidos', validation.error.issues); } const { product_id, company_id, quantity } = validation.data; const result = await valuationService.consumeFifo( product_id, company_id, quantity, req.user!.tenantId, req.user!.userId ); const response: ApiResponse = { success: true, data: result, message: `Consumidas ${result.layers_consumed.length} capas FIFO`, }; res.json(response); } catch (error) { next(error); } } } export const valuationController = new ValuationController();