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>
This commit is contained in:
rckrdmrd 2026-01-18 02:33:43 -06:00
parent d616370440
commit ffd5ffe56a
9 changed files with 75 additions and 67 deletions

View File

@ -181,7 +181,10 @@ class ApiKeysController {
} }
const dto: UpdateApiKeyDto = { const dto: UpdateApiKeyDto = {
...validation.data, name: validation.data.name,
scope: validation.data.scope ?? undefined,
allowed_ips: validation.data.allowed_ips ?? undefined,
is_active: validation.data.is_active,
expiration_date: validation.data.expiration_date expiration_date: validation.data.expiration_date
? new Date(validation.data.expiration_date) ? new Date(validation.data.expiration_date)
: validation.data.expiration_date === null : validation.data.expiration_date === null

View File

@ -76,15 +76,14 @@ export class InvoicesService {
const discount = itemTotal * ((itemDto.discountPercent || 0) / 100); const discount = itemTotal * ((itemDto.discountPercent || 0) / 100);
const item = this.itemRepository.create({ const item = this.itemRepository.create({
invoiceId: savedInvoice.id,
itemType: itemDto.itemType, itemType: itemDto.itemType,
description: itemDto.description, description: itemDto.description,
quantity: itemDto.quantity, quantity: itemDto.quantity,
unitPrice: itemDto.unitPrice, unitPrice: itemDto.unitPrice,
discountPercent: itemDto.discountPercent || 0,
subtotal: itemTotal - discount, subtotal: itemTotal - discount,
metadata: itemDto.metadata || {}, metadata: itemDto.metadata || {},
}); });
item.invoiceId = savedInvoice.id;
await this.itemRepository.save(item); await this.itemRepository.save(item);
} }
@ -310,7 +309,7 @@ export class InvoicesService {
invoice.paidAmount = newPaidAmount; invoice.paidAmount = newPaidAmount;
invoice.paymentMethod = dto.paymentMethod; invoice.paymentMethod = dto.paymentMethod;
invoice.paymentReference = dto.paymentReference; invoice.paymentReference = dto.paymentReference || '';
if (newPaidAmount >= total) { if (newPaidAmount >= total) {
invoice.status = 'paid'; invoice.status = 'paid';

View File

@ -135,7 +135,7 @@ export class SubscriptionsService {
throw new Error('Subscription is already cancelled'); throw new Error('Subscription is already cancelled');
} }
subscription.cancellationReason = dto.reason; subscription.cancellationReason = dto.reason || '';
subscription.cancelledAt = new Date(); subscription.cancelledAt = new Date();
if (dto.cancelImmediately) { if (dto.cancelImmediately) {

View File

@ -258,13 +258,17 @@ export class BranchesService {
} }
const assignment = this.assignmentRepository.create({ const assignment = this.assignmentRepository.create({
...dto, userId: dto.userId,
branchId: dto.branchId,
tenantId, tenantId,
assignmentType: (dto.assignmentType || 'primary') as any,
branchRole: dto.branchRole as any,
permissions: dto.permissions || [],
validUntil: dto.validUntil ? new Date(dto.validUntil) : undefined, validUntil: dto.validUntil ? new Date(dto.validUntil) : undefined,
createdBy: assignedBy, createdBy: assignedBy,
} as any); });
return this.assignmentRepository.save(assignment); return this.assignmentRepository.save(assignment) as Promise<UserBranchAssignment>;
} }
async unassignUser(userId: string, branchId: string): Promise<boolean> { async unassignUser(userId: string, branchId: string): Promise<boolean> {

View File

@ -9,37 +9,39 @@ import { taxesService, CreateTaxDto, UpdateTaxDto, TaxFilters } from './taxes.se
import { AuthenticatedRequest } from '../../shared/middleware/auth.middleware.js'; import { AuthenticatedRequest } from '../../shared/middleware/auth.middleware.js';
import { ValidationError } from '../../shared/errors/index.js'; import { ValidationError } from '../../shared/errors/index.js';
// Schemas // Schemas - Accounts use camelCase DTOs
const createAccountSchema = z.object({ const createAccountSchema = z.object({
company_id: z.string().uuid(), companyId: z.string().uuid(),
code: z.string().min(1).max(50), code: z.string().min(1).max(50),
name: z.string().min(1).max(255), name: z.string().min(1).max(255),
account_type_id: z.string().uuid(), accountTypeId: z.string().uuid(),
parent_id: z.string().uuid().optional(), parentId: z.string().uuid().optional(),
currency_id: z.string().uuid().optional(), currencyId: z.string().uuid().optional(),
is_reconcilable: z.boolean().default(false), isReconcilable: z.boolean().default(false),
notes: z.string().optional(), notes: z.string().optional(),
}); });
const updateAccountSchema = z.object({ const updateAccountSchema = z.object({
name: z.string().min(1).max(255).optional(), name: z.string().min(1).max(255).optional(),
parent_id: z.string().uuid().optional().nullable(), parentId: z.string().uuid().optional().nullable(),
currency_id: z.string().uuid().optional().nullable(), currencyId: z.string().uuid().optional().nullable(),
is_reconcilable: z.boolean().optional(), isReconcilable: z.boolean().optional(),
is_deprecated: z.boolean().optional(), isDeprecated: z.boolean().optional(),
notes: z.string().optional().nullable(), notes: z.string().optional().nullable(),
}); });
const accountQuerySchema = z.object({ const accountQuerySchema = z.object({
company_id: z.string().uuid().optional(), companyId: z.string().uuid().optional(),
account_type_id: z.string().uuid().optional(), accountTypeId: z.string().uuid().optional(),
parent_id: z.string().optional(), parentId: z.string().optional(),
is_deprecated: z.coerce.boolean().optional(), isDeprecated: z.coerce.boolean().optional(),
search: z.string().optional(), search: z.string().optional(),
page: z.coerce.number().int().positive().default(1), page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(50), limit: z.coerce.number().int().positive().max(100).default(50),
}); });
// Journals and Journal Entries use snake_case DTOs
const createJournalSchema = z.object({ const createJournalSchema = z.object({
company_id: z.string().uuid(), company_id: z.string().uuid(),
name: z.string().min(1).max(255), name: z.string().min(1).max(255),

View File

@ -15,19 +15,19 @@ const createProductSchema = z.object({
code: z.string().max(100).optional(), code: z.string().max(100).optional(),
barcode: z.string().max(100).optional(), barcode: z.string().max(100).optional(),
description: z.string().optional(), description: z.string().optional(),
product_type: z.enum(['storable', 'consumable', 'service']).default('storable'), productType: z.enum(['storable', 'consumable', 'service']).default('storable'),
tracking: z.enum(['none', 'lot', 'serial']).default('none'), tracking: z.enum(['none', 'lot', 'serial']).default('none'),
category_id: z.string().uuid().optional(), categoryId: z.string().uuid().optional(),
uom_id: z.string().uuid({ message: 'La unidad de medida es requerida' }), uomId: z.string().uuid({ message: 'La unidad de medida es requerida' }),
purchase_uom_id: z.string().uuid().optional(), purchaseUomId: z.string().uuid().optional(),
cost_price: z.number().min(0).default(0), costPrice: z.number().min(0).default(0),
list_price: z.number().min(0).default(0), listPrice: z.number().min(0).default(0),
valuation_method: z.enum(['standard', 'fifo', 'average']).default('fifo'), valuationMethod: z.enum(['standard', 'fifo', 'average']).default('fifo'),
weight: z.number().min(0).optional(), weight: z.number().min(0).optional(),
volume: z.number().min(0).optional(), volume: z.number().min(0).optional(),
can_be_sold: z.boolean().default(true), canBeSold: z.boolean().default(true),
can_be_purchased: z.boolean().default(true), canBePurchased: z.boolean().default(true),
image_url: z.string().url().max(500).optional(), imageUrl: z.string().url().max(500).optional(),
}); });
const updateProductSchema = z.object({ const updateProductSchema = z.object({
@ -35,26 +35,26 @@ const updateProductSchema = z.object({
barcode: z.string().max(100).optional().nullable(), barcode: z.string().max(100).optional().nullable(),
description: z.string().optional().nullable(), description: z.string().optional().nullable(),
tracking: z.enum(['none', 'lot', 'serial']).optional(), tracking: z.enum(['none', 'lot', 'serial']).optional(),
category_id: z.string().uuid().optional().nullable(), categoryId: z.string().uuid().optional().nullable(),
uom_id: z.string().uuid().optional(), uomId: z.string().uuid().optional(),
purchase_uom_id: z.string().uuid().optional().nullable(), purchaseUomId: z.string().uuid().optional().nullable(),
cost_price: z.number().min(0).optional(), costPrice: z.number().min(0).optional(),
list_price: z.number().min(0).optional(), listPrice: z.number().min(0).optional(),
valuation_method: z.enum(['standard', 'fifo', 'average']).optional(), valuationMethod: z.enum(['standard', 'fifo', 'average']).optional(),
weight: z.number().min(0).optional().nullable(), weight: z.number().min(0).optional().nullable(),
volume: z.number().min(0).optional().nullable(), volume: z.number().min(0).optional().nullable(),
can_be_sold: z.boolean().optional(), canBeSold: z.boolean().optional(),
can_be_purchased: z.boolean().optional(), canBePurchased: z.boolean().optional(),
image_url: z.string().url().max(500).optional().nullable(), imageUrl: z.string().url().max(500).optional().nullable(),
active: z.boolean().optional(), active: z.boolean().optional(),
}); });
const productQuerySchema = z.object({ const productQuerySchema = z.object({
search: z.string().optional(), search: z.string().optional(),
category_id: z.string().uuid().optional(), categoryId: z.string().uuid().optional(),
product_type: z.enum(['storable', 'consumable', 'service']).optional(), productType: z.enum(['storable', 'consumable', 'service']).optional(),
can_be_sold: z.coerce.boolean().optional(), canBeSold: z.coerce.boolean().optional(),
can_be_purchased: z.coerce.boolean().optional(), canBePurchased: z.coerce.boolean().optional(),
active: z.coerce.boolean().optional(), active: z.coerce.boolean().optional(),
page: z.coerce.number().int().positive().default(1), page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(20), limit: z.coerce.number().int().positive().max(100).default(20),
@ -62,22 +62,22 @@ const productQuerySchema = z.object({
// Warehouse schemas // Warehouse schemas
const createWarehouseSchema = z.object({ const createWarehouseSchema = z.object({
company_id: z.string().uuid({ message: 'La empresa es requerida' }), companyId: z.string().uuid({ message: 'La empresa es requerida' }),
name: z.string().min(1, 'El nombre es requerido').max(255), name: z.string().min(1, 'El nombre es requerido').max(255),
code: z.string().min(1).max(20), code: z.string().min(1).max(20),
address_id: z.string().uuid().optional(), addressId: z.string().uuid().optional(),
is_default: z.boolean().default(false), isDefault: z.boolean().default(false),
}); });
const updateWarehouseSchema = z.object({ const updateWarehouseSchema = z.object({
name: z.string().min(1).max(255).optional(), name: z.string().min(1).max(255).optional(),
address_id: z.string().uuid().optional().nullable(), addressId: z.string().uuid().optional().nullable(),
is_default: z.boolean().optional(), isDefault: z.boolean().optional(),
active: z.boolean().optional(), active: z.boolean().optional(),
}); });
const warehouseQuerySchema = z.object({ const warehouseQuerySchema = z.object({
company_id: z.string().uuid().optional(), companyId: z.string().uuid().optional(),
active: z.coerce.boolean().optional(), active: z.coerce.boolean().optional(),
page: z.coerce.number().int().positive().default(1), page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(50), limit: z.coerce.number().int().positive().max(100).default(50),
@ -232,7 +232,7 @@ class InventoryController {
throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors); throw new ValidationError('Parámetros de consulta inválidos', queryResult.error.errors);
} }
const filters: ProductFilters = queryResult.data; const filters = queryResult.data as ProductFilters;
const result = await productsService.findAll(req.tenantId!, filters); const result = await productsService.findAll(req.tenantId!, filters);
res.json({ res.json({
@ -266,7 +266,7 @@ class InventoryController {
throw new ValidationError('Datos de producto inválidos', parseResult.error.errors); throw new ValidationError('Datos de producto inválidos', parseResult.error.errors);
} }
const dto: CreateProductDto = parseResult.data; const dto = parseResult.data as CreateProductDto;
const product = await productsService.create(dto, req.tenantId!, req.user!.userId); const product = await productsService.create(dto, req.tenantId!, req.user!.userId);
res.status(201).json({ res.status(201).json({
@ -286,7 +286,7 @@ class InventoryController {
throw new ValidationError('Datos de producto inválidos', parseResult.error.errors); throw new ValidationError('Datos de producto inválidos', parseResult.error.errors);
} }
const dto: UpdateProductDto = parseResult.data; const dto = parseResult.data as UpdateProductDto;
const product = await productsService.update(req.params.id, dto, req.tenantId!, req.user!.userId); const product = await productsService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
res.json({ res.json({

View File

@ -147,7 +147,7 @@ class ValuationController {
req.user!.tenantId req.user!.tenantId
); );
const response: ApiResponse = { const response = {
success: true, success: true,
data: result, data: result,
meta: { meta: {

View File

@ -50,9 +50,10 @@ export class NotificationsService {
channelType: string, channelType: string,
tenantId?: string tenantId?: string
): Promise<NotificationTemplate | null> { ): Promise<NotificationTemplate | null> {
const channel = channelType as any;
const where: FindOptionsWhere<NotificationTemplate>[] = tenantId const where: FindOptionsWhere<NotificationTemplate>[] = tenantId
? [{ code, channelType, tenantId }, { code, channelType, tenantId: undefined }] ? [{ code, channelType: channel, tenantId }, { code, channelType: channel, tenantId: undefined as any }]
: [{ code, channelType }]; : [{ code, channelType: channel }];
return this.templateRepository.findOne({ return this.templateRepository.findOne({
where, where,

View File

@ -90,12 +90,12 @@ export class TransactionsService {
// Update transaction with result // Update transaction with result
transaction.status = providerResult.status; transaction.status = providerResult.status;
transaction.externalTransactionId = providerResult.externalTransactionId; transaction.externalTransactionId = providerResult.externalTransactionId || '';
transaction.paymentMethod = providerResult.paymentMethod; transaction.paymentMethod = providerResult.paymentMethod || transaction.paymentMethod;
transaction.cardBrand = providerResult.cardBrand; transaction.cardBrand = providerResult.cardBrand || '';
transaction.cardLastFour = providerResult.cardLastFour; transaction.cardLastFour = providerResult.cardLastFour || '';
transaction.receiptUrl = providerResult.receiptUrl; transaction.receiptUrl = providerResult.receiptUrl || '';
transaction.providerResponse = providerResult.rawResponse; transaction.providerResponse = providerResult.rawResponse || {};
if (providerResult.status === 'completed') { if (providerResult.status === 'completed') {
transaction.completedAt = new Date(); transaction.completedAt = new Date();
@ -105,7 +105,7 @@ export class TransactionsService {
healthStatus: 'healthy', healthStatus: 'healthy',
}); });
} else if (providerResult.status === 'failed') { } else if (providerResult.status === 'failed') {
transaction.failureReason = providerResult.error; transaction.failureReason = providerResult.error || '';
} }
await this.transactionRepository.save(transaction); await this.transactionRepository.save(transaction);
@ -390,10 +390,9 @@ export class TransactionsService {
if (!this.circuitBreakers.has(terminalId)) { if (!this.circuitBreakers.has(terminalId)) {
this.circuitBreakers.set( this.circuitBreakers.set(
terminalId, terminalId,
new CircuitBreaker({ new CircuitBreaker(`terminal-${terminalId}`, {
name: `terminal-${terminalId}`,
failureThreshold: 3, failureThreshold: 3,
successThreshold: 2, halfOpenRequests: 2,
resetTimeout: 30000, // 30 seconds resetTimeout: 30000, // 30 seconds
}) })
); );