erp-core-backend-v2/src/modules/financial/financial.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

756 lines
29 KiB
TypeScript

import { Response, NextFunction } from 'express';
import { z } from 'zod';
import { accountsService, CreateAccountDto, UpdateAccountDto, AccountFilters } from './accounts.service.js';
import { journalsService, CreateJournalDto, UpdateJournalDto, JournalFilters } from './journals.service.js';
import { journalEntriesService, CreateJournalEntryDto, UpdateJournalEntryDto, JournalEntryFilters } from './journal-entries.service.js';
import { invoicesService, CreateInvoiceDto, UpdateInvoiceDto, CreateInvoiceLineDto, UpdateInvoiceLineDto, InvoiceFilters } from './invoices.service.js';
import { paymentsService, CreatePaymentDto, UpdatePaymentDto, ReconcileDto, PaymentFilters } from './payments.service.js';
import { taxesService, CreateTaxDto, UpdateTaxDto, TaxFilters } from './taxes.service.js';
import { AuthenticatedRequest } from '../../shared/middleware/auth.middleware.js';
import { ValidationError } from '../../shared/errors/index.js';
// Schemas - Accounts use camelCase DTOs
const createAccountSchema = z.object({
companyId: z.string().uuid(),
code: z.string().min(1).max(50),
name: z.string().min(1).max(255),
accountTypeId: z.string().uuid(),
parentId: z.string().uuid().optional(),
currencyId: z.string().uuid().optional(),
isReconcilable: z.boolean().default(false),
notes: z.string().optional(),
});
const updateAccountSchema = z.object({
name: z.string().min(1).max(255).optional(),
parentId: z.string().uuid().optional().nullable(),
currencyId: z.string().uuid().optional().nullable(),
isReconcilable: z.boolean().optional(),
isDeprecated: z.boolean().optional(),
notes: z.string().optional().nullable(),
});
const accountQuerySchema = z.object({
companyId: z.string().uuid().optional(),
accountTypeId: z.string().uuid().optional(),
parentId: z.string().optional(),
isDeprecated: z.coerce.boolean().optional(),
search: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(50),
});
// Journals and Journal Entries use snake_case DTOs
const createJournalSchema = z.object({
company_id: z.string().uuid(),
name: z.string().min(1).max(255),
code: z.string().min(1).max(20),
journal_type: z.enum(['sale', 'purchase', 'cash', 'bank', 'general']),
default_account_id: z.string().uuid().optional(),
sequence_id: z.string().uuid().optional(),
currency_id: z.string().uuid().optional(),
});
const updateJournalSchema = z.object({
name: z.string().min(1).max(255).optional(),
default_account_id: z.string().uuid().optional().nullable(),
sequence_id: z.string().uuid().optional().nullable(),
currency_id: z.string().uuid().optional().nullable(),
active: z.boolean().optional(),
});
const journalQuerySchema = z.object({
company_id: z.string().uuid().optional(),
journal_type: z.enum(['sale', 'purchase', 'cash', 'bank', 'general']).optional(),
active: z.coerce.boolean().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(50),
});
const journalEntryLineSchema = z.object({
account_id: z.string().uuid(),
partner_id: z.string().uuid().optional(),
debit: z.number().min(0).default(0),
credit: z.number().min(0).default(0),
description: z.string().optional(),
ref: z.string().optional(),
});
const createJournalEntrySchema = z.object({
company_id: z.string().uuid(),
journal_id: z.string().uuid(),
name: z.string().min(1).max(100),
ref: z.string().max(255).optional(),
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
notes: z.string().optional(),
lines: z.array(journalEntryLineSchema).min(2),
});
const updateJournalEntrySchema = z.object({
ref: z.string().max(255).optional().nullable(),
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
notes: z.string().optional().nullable(),
lines: z.array(journalEntryLineSchema).min(2).optional(),
});
const journalEntryQuerySchema = z.object({
company_id: z.string().uuid().optional(),
journal_id: z.string().uuid().optional(),
status: z.enum(['draft', 'posted', 'cancelled']).optional(),
date_from: z.string().optional(),
date_to: z.string().optional(),
search: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
});
// ========== INVOICE SCHEMAS ==========
const createInvoiceSchema = z.object({
company_id: z.string().uuid(),
partner_id: z.string().uuid(),
invoice_type: z.enum(['customer', 'supplier']),
currency_id: z.string().uuid(),
invoice_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
due_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
payment_term_id: z.string().uuid().optional(),
journal_id: z.string().uuid().optional(),
ref: z.string().optional(),
notes: z.string().optional(),
});
const updateInvoiceSchema = z.object({
partner_id: z.string().uuid().optional(),
currency_id: z.string().uuid().optional(),
invoice_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
due_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
payment_term_id: z.string().uuid().optional().nullable(),
journal_id: z.string().uuid().optional().nullable(),
ref: z.string().optional().nullable(),
notes: z.string().optional().nullable(),
});
const invoiceQuerySchema = z.object({
company_id: z.string().uuid().optional(),
partner_id: z.string().uuid().optional(),
invoice_type: z.enum(['customer', 'supplier']).optional(),
status: z.enum(['draft', 'open', 'paid', 'cancelled']).optional(),
date_from: z.string().optional(),
date_to: z.string().optional(),
search: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
});
const createInvoiceLineSchema = z.object({
product_id: z.string().uuid().optional(),
description: z.string().min(1),
quantity: z.number().positive(),
uom_id: z.string().uuid().optional(),
price_unit: z.number().min(0),
tax_ids: z.array(z.string().uuid()).optional(),
account_id: z.string().uuid().optional(),
});
const updateInvoiceLineSchema = z.object({
product_id: z.string().uuid().optional().nullable(),
description: z.string().min(1).optional(),
quantity: z.number().positive().optional(),
uom_id: z.string().uuid().optional().nullable(),
price_unit: z.number().min(0).optional(),
tax_ids: z.array(z.string().uuid()).optional(),
account_id: z.string().uuid().optional().nullable(),
});
// ========== PAYMENT SCHEMAS ==========
const createPaymentSchema = z.object({
company_id: z.string().uuid(),
partner_id: z.string().uuid(),
payment_type: z.enum(['inbound', 'outbound']),
payment_method: z.enum(['cash', 'bank_transfer', 'check', 'card', 'other']),
amount: z.number().positive(),
currency_id: z.string().uuid(),
payment_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
ref: z.string().optional(),
journal_id: z.string().uuid(),
notes: z.string().optional(),
});
const updatePaymentSchema = z.object({
partner_id: z.string().uuid().optional(),
payment_method: z.enum(['cash', 'bank_transfer', 'check', 'card', 'other']).optional(),
amount: z.number().positive().optional(),
currency_id: z.string().uuid().optional(),
payment_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
ref: z.string().optional().nullable(),
journal_id: z.string().uuid().optional(),
notes: z.string().optional().nullable(),
});
const reconcilePaymentSchema = z.object({
invoices: z.array(z.object({
invoice_id: z.string().uuid(),
amount: z.number().positive(),
})).min(1),
});
const paymentQuerySchema = z.object({
company_id: z.string().uuid().optional(),
partner_id: z.string().uuid().optional(),
payment_type: z.enum(['inbound', 'outbound']).optional(),
payment_method: z.enum(['cash', 'bank_transfer', 'check', 'card', 'other']).optional(),
status: z.enum(['draft', 'posted', 'reconciled', 'cancelled']).optional(),
date_from: z.string().optional(),
date_to: z.string().optional(),
search: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
});
// ========== TAX SCHEMAS ==========
const createTaxSchema = z.object({
company_id: z.string().uuid(),
name: z.string().min(1).max(100),
code: z.string().min(1).max(20),
tax_type: z.enum(['sales', 'purchase', 'all']),
amount: z.number().min(0).max(100),
included_in_price: z.boolean().default(false),
});
const updateTaxSchema = z.object({
name: z.string().min(1).max(100).optional(),
code: z.string().min(1).max(20).optional(),
tax_type: z.enum(['sales', 'purchase', 'all']).optional(),
amount: z.number().min(0).max(100).optional(),
included_in_price: z.boolean().optional(),
active: z.boolean().optional(),
});
const taxQuerySchema = z.object({
company_id: z.string().uuid().optional(),
tax_type: z.enum(['sales', 'purchase', 'all']).optional(),
active: z.coerce.boolean().optional(),
search: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20),
});
class FinancialController {
// ========== ACCOUNT TYPES ==========
async getAccountTypes(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const accountTypes = await accountsService.findAllAccountTypes();
res.json({ success: true, data: accountTypes });
} catch (error) {
next(error);
}
}
// ========== ACCOUNTS ==========
async getAccounts(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const queryResult = accountQuerySchema.safeParse(req.query);
if (!queryResult.success) {
throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors);
}
const filters: AccountFilters = queryResult.data;
const result = await accountsService.findAll(req.tenantId!, filters);
res.json({
success: true,
data: result.data,
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 50)) },
});
} catch (error) {
next(error);
}
}
async getAccount(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const account = await accountsService.findById(req.params.id, req.tenantId!);
res.json({ success: true, data: account });
} catch (error) {
next(error);
}
}
async createAccount(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = createAccountSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de cuenta inválidos', parseResult.error.errors);
}
const dto: CreateAccountDto = parseResult.data;
const account = await accountsService.create(dto, req.tenantId!, req.user!.userId);
res.status(201).json({ success: true, data: account, message: 'Cuenta creada exitosamente' });
} catch (error) {
next(error);
}
}
async updateAccount(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = updateAccountSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de cuenta inválidos', parseResult.error.errors);
}
const dto: UpdateAccountDto = parseResult.data;
const account = await accountsService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ success: true, data: account, message: 'Cuenta actualizada exitosamente' });
} catch (error) {
next(error);
}
}
async deleteAccount(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
await accountsService.delete(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, message: 'Cuenta eliminada exitosamente' });
} catch (error) {
next(error);
}
}
async getAccountBalance(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const balance = await accountsService.getBalance(req.params.id, req.tenantId!);
res.json({ success: true, data: balance });
} catch (error) {
next(error);
}
}
// ========== JOURNALS ==========
async getJournals(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const queryResult = journalQuerySchema.safeParse(req.query);
if (!queryResult.success) {
throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors);
}
const filters: JournalFilters = queryResult.data;
const result = await journalsService.findAll(req.tenantId!, filters);
res.json({
success: true,
data: result.data,
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 50)) },
});
} catch (error) {
next(error);
}
}
async getJournal(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const journal = await journalsService.findById(req.params.id, req.tenantId!);
res.json({ success: true, data: journal });
} catch (error) {
next(error);
}
}
async createJournal(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = createJournalSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de diario inválidos', parseResult.error.errors);
}
const dto: CreateJournalDto = parseResult.data;
const journal = await journalsService.create(dto, req.tenantId!, req.user!.userId);
res.status(201).json({ success: true, data: journal, message: 'Diario creado exitosamente' });
} catch (error) {
next(error);
}
}
async updateJournal(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = updateJournalSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de diario inválidos', parseResult.error.errors);
}
const dto: UpdateJournalDto = parseResult.data;
const journal = await journalsService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ success: true, data: journal, message: 'Diario actualizado exitosamente' });
} catch (error) {
next(error);
}
}
async deleteJournal(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
await journalsService.delete(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, message: 'Diario eliminado exitosamente' });
} catch (error) {
next(error);
}
}
// ========== JOURNAL ENTRIES ==========
async getJournalEntries(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const queryResult = journalEntryQuerySchema.safeParse(req.query);
if (!queryResult.success) {
throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors);
}
const filters: JournalEntryFilters = queryResult.data;
const result = await journalEntriesService.findAll(req.tenantId!, filters);
res.json({
success: true,
data: result.data,
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 20)) },
});
} catch (error) {
next(error);
}
}
async getJournalEntry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const entry = await journalEntriesService.findById(req.params.id, req.tenantId!);
res.json({ success: true, data: entry });
} catch (error) {
next(error);
}
}
async createJournalEntry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = createJournalEntrySchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de póliza inválidos', parseResult.error.errors);
}
const dto: CreateJournalEntryDto = parseResult.data;
const entry = await journalEntriesService.create(dto, req.tenantId!, req.user!.userId);
res.status(201).json({ success: true, data: entry, message: 'Póliza creada exitosamente' });
} catch (error) {
next(error);
}
}
async updateJournalEntry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = updateJournalEntrySchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de póliza inválidos', parseResult.error.errors);
}
const dto: UpdateJournalEntryDto = parseResult.data;
const entry = await journalEntriesService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ success: true, data: entry, message: 'Póliza actualizada exitosamente' });
} catch (error) {
next(error);
}
}
async postJournalEntry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const entry = await journalEntriesService.post(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, data: entry, message: 'Póliza publicada exitosamente' });
} catch (error) {
next(error);
}
}
async cancelJournalEntry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const entry = await journalEntriesService.cancel(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, data: entry, message: 'Póliza cancelada exitosamente' });
} catch (error) {
next(error);
}
}
async deleteJournalEntry(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
await journalEntriesService.delete(req.params.id, req.tenantId!);
res.json({ success: true, message: 'Póliza eliminada exitosamente' });
} catch (error) {
next(error);
}
}
// ========== INVOICES ==========
async getInvoices(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const queryResult = invoiceQuerySchema.safeParse(req.query);
if (!queryResult.success) {
throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors);
}
const filters: InvoiceFilters = queryResult.data;
const result = await invoicesService.findAll(req.tenantId!, filters);
res.json({
success: true,
data: result.data,
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 20)) },
});
} catch (error) {
next(error);
}
}
async getInvoice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const invoice = await invoicesService.findById(req.params.id, req.tenantId!);
res.json({ success: true, data: invoice });
} catch (error) {
next(error);
}
}
async createInvoice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = createInvoiceSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de factura inválidos', parseResult.error.errors);
}
const dto: CreateInvoiceDto = parseResult.data;
const invoice = await invoicesService.create(dto, req.tenantId!, req.user!.userId);
res.status(201).json({ success: true, data: invoice, message: 'Factura creada exitosamente' });
} catch (error) {
next(error);
}
}
async updateInvoice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = updateInvoiceSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de factura inválidos', parseResult.error.errors);
}
const dto: UpdateInvoiceDto = parseResult.data;
const invoice = await invoicesService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ success: true, data: invoice, message: 'Factura actualizada exitosamente' });
} catch (error) {
next(error);
}
}
async validateInvoice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const invoice = await invoicesService.validate(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, data: invoice, message: 'Factura validada exitosamente' });
} catch (error) {
next(error);
}
}
async cancelInvoice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const invoice = await invoicesService.cancel(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, data: invoice, message: 'Factura cancelada exitosamente' });
} catch (error) {
next(error);
}
}
async deleteInvoice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
await invoicesService.delete(req.params.id, req.tenantId!);
res.json({ success: true, message: 'Factura eliminada exitosamente' });
} catch (error) {
next(error);
}
}
// ========== INVOICE LINES ==========
async addInvoiceLine(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = createInvoiceLineSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de línea inválidos', parseResult.error.errors);
}
const dto: CreateInvoiceLineDto = parseResult.data;
const line = await invoicesService.addLine(req.params.id, dto, req.tenantId!);
res.status(201).json({ success: true, data: line, message: 'Línea agregada exitosamente' });
} catch (error) {
next(error);
}
}
async updateInvoiceLine(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = updateInvoiceLineSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de línea inválidos', parseResult.error.errors);
}
const dto: UpdateInvoiceLineDto = parseResult.data;
const line = await invoicesService.updateLine(req.params.id, req.params.lineId, dto, req.tenantId!);
res.json({ success: true, data: line, message: 'Línea actualizada exitosamente' });
} catch (error) {
next(error);
}
}
async removeInvoiceLine(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
await invoicesService.removeLine(req.params.id, req.params.lineId, req.tenantId!);
res.json({ success: true, message: 'Línea eliminada exitosamente' });
} catch (error) {
next(error);
}
}
// ========== PAYMENTS ==========
async getPayments(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const queryResult = paymentQuerySchema.safeParse(req.query);
if (!queryResult.success) {
throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors);
}
const filters: PaymentFilters = queryResult.data;
const result = await paymentsService.findAll(req.tenantId!, filters);
res.json({
success: true,
data: result.data,
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 20)) },
});
} catch (error) {
next(error);
}
}
async getPayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const payment = await paymentsService.findById(req.params.id, req.tenantId!);
res.json({ success: true, data: payment });
} catch (error) {
next(error);
}
}
async createPayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = createPaymentSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de pago inválidos', parseResult.error.errors);
}
const dto: CreatePaymentDto = parseResult.data;
const payment = await paymentsService.create(dto, req.tenantId!, req.user!.userId);
res.status(201).json({ success: true, data: payment, message: 'Pago creado exitosamente' });
} catch (error) {
next(error);
}
}
async updatePayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = updatePaymentSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de pago inválidos', parseResult.error.errors);
}
const dto: UpdatePaymentDto = parseResult.data;
const payment = await paymentsService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ success: true, data: payment, message: 'Pago actualizado exitosamente' });
} catch (error) {
next(error);
}
}
async postPayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const payment = await paymentsService.post(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, data: payment, message: 'Pago publicado exitosamente' });
} catch (error) {
next(error);
}
}
async reconcilePayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = reconcilePaymentSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de conciliación inválidos', parseResult.error.errors);
}
const dto: ReconcileDto = parseResult.data;
const payment = await paymentsService.reconcile(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ success: true, data: payment, message: 'Pago conciliado exitosamente' });
} catch (error) {
next(error);
}
}
async cancelPayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const payment = await paymentsService.cancel(req.params.id, req.tenantId!, req.user!.userId);
res.json({ success: true, data: payment, message: 'Pago cancelado exitosamente' });
} catch (error) {
next(error);
}
}
async deletePayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
await paymentsService.delete(req.params.id, req.tenantId!);
res.json({ success: true, message: 'Pago eliminado exitosamente' });
} catch (error) {
next(error);
}
}
// ========== TAXES ==========
async getTaxes(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const queryResult = taxQuerySchema.safeParse(req.query);
if (!queryResult.success) {
throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors);
}
const filters: TaxFilters = queryResult.data;
const result = await taxesService.findAll(req.tenantId!, filters);
res.json({
success: true,
data: result.data,
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 20)) },
});
} catch (error) {
next(error);
}
}
async getTax(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tax = await taxesService.findById(req.params.id, req.tenantId!);
res.json({ success: true, data: tax });
} catch (error) {
next(error);
}
}
async createTax(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = createTaxSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de impuesto inválidos', parseResult.error.errors);
}
const dto: CreateTaxDto = parseResult.data;
const tax = await taxesService.create(dto, req.tenantId!, req.user!.userId);
res.status(201).json({ success: true, data: tax, message: 'Impuesto creado exitosamente' });
} catch (error) {
next(error);
}
}
async updateTax(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const parseResult = updateTaxSchema.safeParse(req.body);
if (!parseResult.success) {
throw new ValidationError('Datos de impuesto inválidos', parseResult.error.errors);
}
const dto: UpdateTaxDto = parseResult.data;
const tax = await taxesService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ success: true, data: tax, message: 'Impuesto actualizado exitosamente' });
} catch (error) {
next(error);
}
}
async deleteTax(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
await taxesService.delete(req.params.id, req.tenantId!);
res.json({ success: true, message: 'Impuesto eliminado exitosamente' });
} catch (error) {
next(error);
}
}
}
export const financialController = new FinancialController();