erp-core-backend/src/modules/inventory/valuation.controller.ts
rckrdmrd ffd5ffe56a fix: Resolve remaining TypeScript build errors (19→0)
- Fix api keys controller scope type handling
- Fix billing-usage invoices/subscriptions undefined assignments
- Fix branches service type casting
- Fix financial controller Zod schemas (accounts camelCase, journals snake_case)
- Fix inventory controller with type assertions for enums
- Fix valuation controller meta type
- Fix notifications service channel type casting
- Fix payment-terminals transactions service undefined assignments
- Fix CircuitBreaker constructor signature

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 02:33:43 -06:00

231 lines
6.2 KiB
TypeScript

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<void> {
try {
const validation = getProductCostSchema.safeParse(req.query);
if (!validation.success) {
throw new ValidationError('Parámetros inválidos', validation.error.errors);
}
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<void> {
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<void> {
try {
const { productId } = req.params;
const validation = productLayersSchema.safeParse(req.query);
if (!validation.success) {
throw new ValidationError('Parámetros inválidos', validation.error.errors);
}
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<void> {
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<void> {
try {
const validation = createLayerSchema.safeParse(req.body);
if (!validation.success) {
throw new ValidationError('Datos inválidos', validation.error.errors);
}
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<void> {
try {
const validation = consumeFifoSchema.safeParse(req.body);
if (!validation.success) {
throw new ValidationError('Datos inválidos', validation.error.errors);
}
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();