import { Response, NextFunction } from 'express'; import { z } from 'zod'; import { currenciesService, CreateCurrencyDto, UpdateCurrencyDto } from './currencies.service.js'; import { countriesService } from './countries.service.js'; import { uomService, CreateUomDto, UpdateUomDto } from './uom.service.js'; import { productCategoriesService, CreateProductCategoryDto, UpdateProductCategoryDto } from './product-categories.service.js'; import { AuthenticatedRequest } from '../../shared/middleware/auth.middleware.js'; import { ValidationError } from '../../shared/errors/index.js'; // Schemas const createCurrencySchema = z.object({ code: z.string().length(3, 'El código debe tener 3 caracteres').toUpperCase(), name: z.string().min(1, 'El nombre es requerido').max(100), symbol: z.string().min(1).max(10), decimal_places: z.number().int().min(0).max(6).optional(), decimals: z.number().int().min(0).max(6).optional(), // Accept camelCase }).refine((data) => data.decimal_places !== undefined || data.decimals !== undefined, { message: 'decimal_places or decimals is required', }); const updateCurrencySchema = z.object({ name: z.string().min(1).max(100).optional(), symbol: z.string().min(1).max(10).optional(), decimal_places: z.number().int().min(0).max(6).optional(), decimals: z.number().int().min(0).max(6).optional(), // Accept camelCase active: z.boolean().optional(), }); const createUomSchema = z.object({ name: z.string().min(1, 'El nombre es requerido').max(100), code: z.string().min(1).max(20), category_id: z.string().uuid().optional(), categoryId: z.string().uuid().optional(), // Accept camelCase uom_type: z.enum(['reference', 'bigger', 'smaller']).optional(), uomType: z.enum(['reference', 'bigger', 'smaller']).optional(), // Accept camelCase ratio: z.number().positive().default(1), }).refine((data) => data.category_id !== undefined || data.categoryId !== undefined, { message: 'category_id or categoryId is required', }); const updateUomSchema = z.object({ name: z.string().min(1).max(100).optional(), ratio: z.number().positive().optional(), active: z.boolean().optional(), }); const createCategorySchema = z.object({ name: z.string().min(1, 'El nombre es requerido').max(100), code: z.string().min(1).max(50), parent_id: z.string().uuid().optional(), parentId: z.string().uuid().optional(), // Accept camelCase }); const updateCategorySchema = z.object({ name: z.string().min(1).max(100).optional(), parent_id: z.string().uuid().optional().nullable(), parentId: z.string().uuid().optional().nullable(), // Accept camelCase active: z.boolean().optional(), }); class CoreController { // ========== CURRENCIES ========== async getCurrencies(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const activeOnly = req.query.active === 'true'; const currencies = await currenciesService.findAll(activeOnly); res.json({ success: true, data: currencies }); } catch (error) { next(error); } } async getCurrency(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const currency = await currenciesService.findById(req.params.id); res.json({ success: true, data: currency }); } catch (error) { next(error); } } async createCurrency(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const parseResult = createCurrencySchema.safeParse(req.body); if (!parseResult.success) { throw new ValidationError('Datos de moneda inválidos', parseResult.error.errors); } const dto: CreateCurrencyDto = parseResult.data; const currency = await currenciesService.create(dto); res.status(201).json({ success: true, data: currency, message: 'Moneda creada exitosamente' }); } catch (error) { next(error); } } async updateCurrency(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const parseResult = updateCurrencySchema.safeParse(req.body); if (!parseResult.success) { throw new ValidationError('Datos de moneda inválidos', parseResult.error.errors); } const dto: UpdateCurrencyDto = parseResult.data; const currency = await currenciesService.update(req.params.id, dto); res.json({ success: true, data: currency, message: 'Moneda actualizada exitosamente' }); } catch (error) { next(error); } } // ========== COUNTRIES ========== async getCountries(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const countries = await countriesService.findAll(); res.json({ success: true, data: countries }); } catch (error) { next(error); } } async getCountry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const country = await countriesService.findById(req.params.id); res.json({ success: true, data: country }); } catch (error) { next(error); } } // ========== UOM CATEGORIES ========== async getUomCategories(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const activeOnly = req.query.active === 'true'; const categories = await uomService.findAllCategories(activeOnly); res.json({ success: true, data: categories }); } catch (error) { next(error); } } async getUomCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const category = await uomService.findCategoryById(req.params.id); res.json({ success: true, data: category }); } catch (error) { next(error); } } // ========== UOM ========== async getUoms(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const activeOnly = req.query.active === 'true'; const categoryId = req.query.category_id as string | undefined; const uoms = await uomService.findAll(categoryId, activeOnly); res.json({ success: true, data: uoms }); } catch (error) { next(error); } } async getUom(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const uom = await uomService.findById(req.params.id); res.json({ success: true, data: uom }); } catch (error) { next(error); } } async createUom(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const parseResult = createUomSchema.safeParse(req.body); if (!parseResult.success) { throw new ValidationError('Datos de UdM inválidos', parseResult.error.errors); } const dto: CreateUomDto = parseResult.data; const uom = await uomService.create(dto); res.status(201).json({ success: true, data: uom, message: 'Unidad de medida creada exitosamente' }); } catch (error) { next(error); } } async updateUom(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const parseResult = updateUomSchema.safeParse(req.body); if (!parseResult.success) { throw new ValidationError('Datos de UdM inválidos', parseResult.error.errors); } const dto: UpdateUomDto = parseResult.data; const uom = await uomService.update(req.params.id, dto); res.json({ success: true, data: uom, message: 'Unidad de medida actualizada exitosamente' }); } catch (error) { next(error); } } // ========== PRODUCT CATEGORIES ========== async getProductCategories(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const activeOnly = req.query.active === 'true'; const parentId = req.query.parent_id as string | undefined; const categories = await productCategoriesService.findAll(req.tenantId!, parentId, activeOnly); res.json({ success: true, data: categories }); } catch (error) { next(error); } } async getProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const category = await productCategoriesService.findById(req.params.id, req.tenantId!); res.json({ success: true, data: category }); } catch (error) { next(error); } } async createProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const parseResult = createCategorySchema.safeParse(req.body); if (!parseResult.success) { throw new ValidationError('Datos de categoría inválidos', parseResult.error.errors); } const dto: CreateProductCategoryDto = parseResult.data; const category = await productCategoriesService.create(dto, req.tenantId!, req.user!.userId); res.status(201).json({ success: true, data: category, message: 'Categoría creada exitosamente' }); } catch (error) { next(error); } } async updateProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const parseResult = updateCategorySchema.safeParse(req.body); if (!parseResult.success) { throw new ValidationError('Datos de categoría inválidos', parseResult.error.errors); } const dto: UpdateProductCategoryDto = parseResult.data; const category = await productCategoriesService.update(req.params.id, dto, req.tenantId!, req.user!.userId); res.json({ success: true, data: category, message: 'Categoría actualizada exitosamente' }); } catch (error) { next(error); } } async deleteProductCategory(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { await productCategoriesService.delete(req.params.id, req.tenantId!); res.json({ success: true, message: 'Categoría eliminada exitosamente' }); } catch (error) { next(error); } } } export const coreController = new CoreController();