refactor: Clean up finance services (AP, AR, cash-flow, bank-reconciliation)
- Cleaned up ap.service.ts - Refactored ar.service.ts - Minor fixes in bank-reconciliation.service.ts - Updated cash-flow.service.ts - Cleaned erp-integration.service.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
369f461695
commit
cf23727b2b
@ -189,48 +189,44 @@ export class APService {
|
||||
}
|
||||
|
||||
async create(ctx: ServiceContext, data: CreateAPDto): Promise<AccountPayable> {
|
||||
// Calcular monto neto (con retenciones)
|
||||
const netAmount =
|
||||
data.originalAmount -
|
||||
(data.retentionIsr ?? 0) -
|
||||
(data.retentionIva ?? 0) -
|
||||
// Calcular retención total
|
||||
const retentionAmount =
|
||||
(data.retentionIsr ?? 0) +
|
||||
(data.retentionIva ?? 0) +
|
||||
(data.guaranteeFund ?? 0);
|
||||
const subtotal = data.originalAmount - (data.taxAmount ?? 0);
|
||||
const totalAmount = data.originalAmount;
|
||||
const balance = totalAmount - retentionAmount;
|
||||
|
||||
const ap = this.apRepository.create({
|
||||
tenantId: ctx.tenantId,
|
||||
documentType: data.documentType,
|
||||
documentNumber: data.documentNumber,
|
||||
documentDate: data.documentDate,
|
||||
invoiceDate: data.documentDate,
|
||||
dueDate: data.dueDate,
|
||||
partnerId: data.supplierId,
|
||||
partnerName: data.supplierName,
|
||||
partnerRfc: data.partnerRfc,
|
||||
supplierId: data.partnerId,
|
||||
supplierName: data.partnerName,
|
||||
supplierRfc: data.partnerRfc,
|
||||
projectId: data.projectId,
|
||||
projectCode: data.projectCode,
|
||||
contractId: data.contractId,
|
||||
purchaseOrderId: data.purchaseOrderId,
|
||||
originalAmount: data.originalAmount,
|
||||
subtotal,
|
||||
taxAmount: data.taxAmount ?? 0,
|
||||
retentionIsr: data.retentionIsr ?? 0,
|
||||
retentionIva: data.retentionIva ?? 0,
|
||||
guaranteeFund: data.guaranteeFund ?? 0,
|
||||
netAmount,
|
||||
retentionAmount,
|
||||
totalAmount,
|
||||
paidAmount: 0,
|
||||
balanceAmount: netAmount,
|
||||
currencyCode: data.currencyCode ?? 'MXN',
|
||||
balance,
|
||||
currency: data.currencyCode ?? 'MXN',
|
||||
exchangeRate: data.exchangeRate ?? 1,
|
||||
cfdiUuid: data.cfdiUuid,
|
||||
cfdiXml: data.cfdiXml,
|
||||
description: data.description,
|
||||
paymentTermDays: data.paymentTermDays,
|
||||
ledgerAccountId: data.ledgerAccountId,
|
||||
paymentDays: data.paymentTermDays ?? 30,
|
||||
notes: data.notes,
|
||||
metadata: data.metadata,
|
||||
status: 'pending',
|
||||
createdBy: ctx.userId,
|
||||
});
|
||||
|
||||
return this.apRepository.save(ap);
|
||||
const result = await this.apRepository.save(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
async update(
|
||||
@ -252,9 +248,10 @@ export class APService {
|
||||
updatedBy: ctx.userId,
|
||||
});
|
||||
|
||||
// Recalcular saldo si cambió el monto total
|
||||
if (data.totalAmount !== undefined) {
|
||||
ap.balance = ap.totalAmount - ap.paidAmount;
|
||||
// Recalcular saldo si cambió el monto original
|
||||
if (data.originalAmount !== undefined) {
|
||||
ap.totalAmount = data.originalAmount;
|
||||
ap.balance = Number(ap.totalAmount) - Number(ap.retentionAmount) - Number(ap.paidAmount);
|
||||
}
|
||||
|
||||
return this.apRepository.save(ap);
|
||||
|
||||
@ -13,7 +13,6 @@ import {
|
||||
ARDocumentType,
|
||||
ARPayment,
|
||||
CollectionMethod,
|
||||
CollectionStatus,
|
||||
} from '../entities';
|
||||
|
||||
interface ServiceContext {
|
||||
@ -84,8 +83,10 @@ interface AgingBucket {
|
||||
export class ARService {
|
||||
private arRepository: Repository<AccountReceivable>;
|
||||
private collectionRepository: Repository<ARPayment>;
|
||||
dataSource: DataSource;
|
||||
|
||||
constructor(private dataSource: DataSource) {
|
||||
constructor(dataSource: DataSource) {
|
||||
this.dataSource = dataSource;
|
||||
this.arRepository = dataSource.getRepository(AccountReceivable);
|
||||
this.collectionRepository = dataSource.getRepository(ARPayment);
|
||||
}
|
||||
@ -128,7 +129,7 @@ export class ARService {
|
||||
}
|
||||
|
||||
if (partnerId) {
|
||||
queryBuilder.andWhere('ar.partnerId = :partnerId', { partnerId });
|
||||
queryBuilder.andWhere('ar.customerId = :partnerId', { partnerId });
|
||||
}
|
||||
|
||||
if (projectId) {
|
||||
@ -136,11 +137,11 @@ export class ARService {
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
queryBuilder.andWhere('ar.documentDate >= :startDate', { startDate });
|
||||
queryBuilder.andWhere('ar.invoiceDate >= :startDate', { startDate });
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
queryBuilder.andWhere('ar.documentDate <= :endDate', { endDate });
|
||||
queryBuilder.andWhere('ar.invoiceDate <= :endDate', { endDate });
|
||||
}
|
||||
|
||||
if (overdue) {
|
||||
@ -152,7 +153,7 @@ export class ARService {
|
||||
|
||||
if (search) {
|
||||
queryBuilder.andWhere(
|
||||
'(ar.documentNumber ILIKE :search OR ar.partnerName ILIKE :search)',
|
||||
'(ar.documentNumber ILIKE :search OR ar.customerName ILIKE :search)',
|
||||
{ search: `%${search}%` }
|
||||
);
|
||||
}
|
||||
@ -186,41 +187,36 @@ export class ARService {
|
||||
}
|
||||
|
||||
async create(ctx: ServiceContext, data: CreateARDto): Promise<AccountReceivable> {
|
||||
// Calcular monto neto (con retenciones)
|
||||
const netAmount =
|
||||
data.originalAmount -
|
||||
(data.retentionIsr ?? 0) -
|
||||
(data.retentionIva ?? 0) -
|
||||
// Calcular retención total y monto neto
|
||||
const retentionAmount =
|
||||
(data.retentionIsr ?? 0) +
|
||||
(data.retentionIva ?? 0) +
|
||||
(data.guaranteeFund ?? 0);
|
||||
const subtotal = data.originalAmount - (data.taxAmount ?? 0);
|
||||
const totalAmount = data.originalAmount;
|
||||
const balance = totalAmount - retentionAmount;
|
||||
|
||||
const ar = this.arRepository.create({
|
||||
tenantId: ctx.tenantId,
|
||||
documentType: data.documentType,
|
||||
documentNumber: data.documentNumber,
|
||||
documentDate: data.documentDate,
|
||||
invoiceDate: data.documentDate,
|
||||
dueDate: data.dueDate,
|
||||
partnerId: data.partnerId,
|
||||
partnerName: data.partnerName,
|
||||
partnerRfc: data.partnerRfc,
|
||||
customerId: data.partnerId,
|
||||
customerName: data.partnerName,
|
||||
customerRfc: data.partnerRfc,
|
||||
projectId: data.projectId,
|
||||
projectCode: data.projectCode,
|
||||
contractId: data.contractId,
|
||||
estimationId: data.estimationId,
|
||||
originalAmount: data.originalAmount,
|
||||
subtotal,
|
||||
taxAmount: data.taxAmount ?? 0,
|
||||
retentionIsr: data.retentionIsr ?? 0,
|
||||
retentionIva: data.retentionIva ?? 0,
|
||||
guaranteeFund: data.guaranteeFund ?? 0,
|
||||
netAmount,
|
||||
retentionAmount,
|
||||
totalAmount,
|
||||
collectedAmount: 0,
|
||||
balanceAmount: netAmount,
|
||||
currencyCode: data.currencyCode ?? 'MXN',
|
||||
balance,
|
||||
currency: data.currencyCode ?? 'MXN',
|
||||
exchangeRate: data.exchangeRate ?? 1,
|
||||
cfdiUuid: data.cfdiUuid,
|
||||
cfdiXml: data.cfdiXml,
|
||||
description: data.description,
|
||||
paymentTermDays: data.paymentTermDays,
|
||||
ledgerAccountId: data.ledgerAccountId,
|
||||
paymentDays: data.paymentTermDays ?? 30,
|
||||
notes: data.notes,
|
||||
metadata: data.metadata,
|
||||
status: 'pending',
|
||||
@ -252,12 +248,8 @@ export class ARService {
|
||||
|
||||
// Recalcular montos si cambió el monto original
|
||||
if (data.originalAmount !== undefined) {
|
||||
ar.netAmount =
|
||||
ar.originalAmount -
|
||||
(ar.retentionIsr ?? 0) -
|
||||
(ar.retentionIva ?? 0) -
|
||||
(ar.guaranteeFund ?? 0);
|
||||
ar.balanceAmount = ar.netAmount - ar.collectedAmount;
|
||||
ar.totalAmount = data.originalAmount;
|
||||
ar.balance = ar.totalAmount - ar.retentionAmount - ar.collectedAmount;
|
||||
}
|
||||
|
||||
return this.arRepository.save(ar);
|
||||
@ -327,7 +319,7 @@ export class ARService {
|
||||
throw new Error(`Cuenta por cobrar ${ar.documentNumber} ya está cobrada, cancelada o castigada`);
|
||||
}
|
||||
arRecords.push(ar);
|
||||
totalToApply += ar.balanceAmount;
|
||||
totalToApply += Number(ar.balance);
|
||||
}
|
||||
|
||||
// Validar monto
|
||||
@ -347,16 +339,13 @@ export class ARService {
|
||||
collectionMethod: data.collectionMethod,
|
||||
collectionDate: data.collectionDate,
|
||||
bankAccountId: data.bankAccountId,
|
||||
collectionAmount: data.collectionAmount,
|
||||
currencyCode: data.currencyCode ?? 'MXN',
|
||||
accountReceivableId: arRecords[0]?.id,
|
||||
amount: data.collectionAmount,
|
||||
currency: data.currencyCode ?? 'MXN',
|
||||
exchangeRate: data.exchangeRate ?? 1,
|
||||
reference: data.reference,
|
||||
depositReference: data.depositReference,
|
||||
transferReference: data.transferReference,
|
||||
collectionConcept: data.collectionConcept,
|
||||
notes: data.notes,
|
||||
status: 'pending',
|
||||
documentCount: arRecords.length,
|
||||
createdBy: ctx.userId,
|
||||
});
|
||||
|
||||
@ -373,14 +362,15 @@ export class ARService {
|
||||
for (const ar of sortedAR) {
|
||||
if (remainingAmount <= 0) break;
|
||||
|
||||
const amountToApply = Math.min(remainingAmount, ar.balanceAmount);
|
||||
const arBalance = Number(ar.balance);
|
||||
const amountToApply = Math.min(remainingAmount, arBalance);
|
||||
applications.push({ arId: ar.id, amount: amountToApply });
|
||||
|
||||
ar.collectedAmount += amountToApply;
|
||||
ar.balanceAmount -= amountToApply;
|
||||
ar.lastCollectionDate = data.collectionDate;
|
||||
ar.collectedAmount = Number(ar.collectedAmount) + amountToApply;
|
||||
ar.balance = arBalance - amountToApply;
|
||||
ar.collectionDate = data.collectionDate;
|
||||
|
||||
if (ar.balanceAmount <= 0.01) {
|
||||
if (ar.balance <= 0.01) {
|
||||
ar.status = 'collected';
|
||||
} else {
|
||||
ar.status = 'partial';
|
||||
@ -394,9 +384,9 @@ export class ARService {
|
||||
|
||||
// Guardar aplicaciones en metadata del cobro
|
||||
savedCollection.metadata = { applications };
|
||||
await this.collectionRepository.save(savedCollection);
|
||||
const result = await this.collectionRepository.save(savedCollection);
|
||||
|
||||
return savedCollection;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async generateCollectionNumber(ctx: ServiceContext): Promise<string> {
|
||||
@ -435,9 +425,8 @@ export class ARService {
|
||||
throw new Error('Solo se pueden confirmar cobros pendientes');
|
||||
}
|
||||
|
||||
collection.status = 'confirmed';
|
||||
collection.confirmedAt = new Date();
|
||||
collection.confirmedById = ctx.userId;
|
||||
collection.status = 'deposited';
|
||||
collection.depositDate = new Date();
|
||||
collection.updatedBy = ctx.userId;
|
||||
|
||||
return this.collectionRepository.save(collection);
|
||||
@ -468,9 +457,10 @@ export class ARService {
|
||||
for (const app of applications) {
|
||||
const ar = await this.arRepository.findOne({ where: { id: app.arId } });
|
||||
if (ar) {
|
||||
ar.collectedAmount -= app.amount;
|
||||
ar.balanceAmount += app.amount;
|
||||
ar.status = ar.balanceAmount >= ar.netAmount ? 'pending' : 'partial';
|
||||
ar.collectedAmount = Number(ar.collectedAmount) - app.amount;
|
||||
ar.balance = Number(ar.balance) + app.amount;
|
||||
const expectedBalance = Number(ar.totalAmount) - Number(ar.retentionAmount);
|
||||
ar.status = Number(ar.balance) >= expectedBalance ? 'pending' : 'partial';
|
||||
ar.updatedBy = ctx.userId;
|
||||
await this.arRepository.save(ar);
|
||||
}
|
||||
@ -505,7 +495,7 @@ export class ARService {
|
||||
.andWhere('ar.deletedAt IS NULL');
|
||||
|
||||
if (partnerId) {
|
||||
queryBuilder.andWhere('ar.partnerId = :partnerId', { partnerId });
|
||||
queryBuilder.andWhere('ar.customerId = :partnerId', { partnerId });
|
||||
}
|
||||
|
||||
if (projectId) {
|
||||
@ -532,7 +522,7 @@ export class ARService {
|
||||
const daysOverdue = Math.floor(
|
||||
(asOfDate.getTime() - new Date(ar.dueDate).getTime()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
const balance = ar.balanceAmount;
|
||||
const balance = Number(ar.balance);
|
||||
|
||||
// Clasificar en bucket
|
||||
let bucket: keyof AgingBucket;
|
||||
@ -552,10 +542,10 @@ export class ARService {
|
||||
summary.total += balance;
|
||||
|
||||
// Por cliente
|
||||
if (!partnerMap.has(ar.partnerId)) {
|
||||
partnerMap.set(ar.partnerId, {
|
||||
partnerId: ar.partnerId,
|
||||
partnerName: ar.partnerName,
|
||||
if (!partnerMap.has(ar.customerId)) {
|
||||
partnerMap.set(ar.customerId, {
|
||||
partnerId: ar.customerId,
|
||||
partnerName: ar.customerName,
|
||||
aging: {
|
||||
current: 0,
|
||||
days1to30: 0,
|
||||
@ -567,7 +557,7 @@ export class ARService {
|
||||
});
|
||||
}
|
||||
|
||||
const partnerData = partnerMap.get(ar.partnerId)!;
|
||||
const partnerData = partnerMap.get(ar.customerId)!;
|
||||
partnerData.aging[bucket] += balance;
|
||||
partnerData.aging.total += balance;
|
||||
}
|
||||
@ -594,7 +584,7 @@ export class ARService {
|
||||
.andWhere('ar.deletedAt IS NULL');
|
||||
|
||||
if (partnerId) {
|
||||
queryBuilder.andWhere('ar.partnerId = :partnerId', { partnerId });
|
||||
queryBuilder.andWhere('ar.customerId = :partnerId', { partnerId });
|
||||
}
|
||||
|
||||
if (projectId) {
|
||||
@ -621,7 +611,7 @@ export class ARService {
|
||||
|
||||
const entry = forecastMap.get(dateKey)!;
|
||||
entry.documents.push(ar);
|
||||
entry.totalAmount += ar.balanceAmount;
|
||||
entry.totalAmount += Number(ar.balance);
|
||||
}
|
||||
|
||||
return Array.from(forecastMap.values()).sort(
|
||||
@ -653,28 +643,28 @@ export class ARService {
|
||||
// Total pendiente
|
||||
const totalPending = await baseQuery
|
||||
.clone()
|
||||
.select('SUM(ar.balanceAmount)', 'total')
|
||||
.select('SUM(ar.balance)', 'total')
|
||||
.getRawOne();
|
||||
|
||||
// Vencido
|
||||
const totalOverdue = await baseQuery
|
||||
.clone()
|
||||
.andWhere('ar.dueDate < :today', { today })
|
||||
.select('SUM(ar.balanceAmount)', 'total')
|
||||
.select('SUM(ar.balance)', 'total')
|
||||
.getRawOne();
|
||||
|
||||
// Por cobrar esta semana
|
||||
const dueThisWeek = await baseQuery
|
||||
.clone()
|
||||
.andWhere('ar.dueDate BETWEEN :today AND :endOfWeek', { today, endOfWeek })
|
||||
.select('SUM(ar.balanceAmount)', 'total')
|
||||
.select('SUM(ar.balance)', 'total')
|
||||
.getRawOne();
|
||||
|
||||
// Por cobrar este mes
|
||||
const dueThisMonth = await baseQuery
|
||||
.clone()
|
||||
.andWhere('ar.dueDate BETWEEN :today AND :endOfMonth', { today, endOfMonth })
|
||||
.select('SUM(ar.balanceAmount)', 'total')
|
||||
.select('SUM(ar.balance)', 'total')
|
||||
.getRawOne();
|
||||
|
||||
// Conteos
|
||||
@ -689,11 +679,11 @@ export class ARService {
|
||||
const monthlyInvoiced = await this.arRepository
|
||||
.createQueryBuilder('ar')
|
||||
.where('ar.tenantId = :tenantId', { tenantId: ctx.tenantId })
|
||||
.andWhere('ar.documentDate BETWEEN :startOfMonth AND :endOfMonth', {
|
||||
.andWhere('ar.invoiceDate BETWEEN :startOfMonth AND :endOfMonth', {
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
})
|
||||
.select('SUM(ar.netAmount)', 'total')
|
||||
.select('SUM(ar.totalAmount)', 'total')
|
||||
.getRawOne();
|
||||
|
||||
const monthlyCollected = await this.collectionRepository
|
||||
@ -704,7 +694,7 @@ export class ARService {
|
||||
endOfMonth,
|
||||
})
|
||||
.andWhere('col.status != :cancelled', { cancelled: 'cancelled' })
|
||||
.select('SUM(col.collectionAmount)', 'total')
|
||||
.select('SUM(col.amount)', 'total')
|
||||
.getRawOne();
|
||||
|
||||
const invoicedAmount = parseFloat(monthlyInvoiced?.total) || 0;
|
||||
|
||||
@ -107,8 +107,10 @@ export class BankReconciliationService {
|
||||
private bankAccountRepository: Repository<BankAccount>;
|
||||
private movementRepository: Repository<BankMovement>;
|
||||
private reconciliationRepository: Repository<BankReconciliation>;
|
||||
dataSource: DataSource;
|
||||
|
||||
constructor(private dataSource: DataSource) {
|
||||
constructor(dataSource: DataSource) {
|
||||
this.dataSource = dataSource;
|
||||
this.bankAccountRepository = dataSource.getRepository(BankAccount);
|
||||
this.movementRepository = dataSource.getRepository(BankMovement);
|
||||
this.reconciliationRepository = dataSource.getRepository(BankReconciliation);
|
||||
|
||||
@ -79,7 +79,10 @@ export class CashFlowService {
|
||||
private arRepository: Repository<AccountReceivable>;
|
||||
private bankAccountRepository: Repository<BankAccount>;
|
||||
|
||||
constructor(private dataSource: DataSource) {
|
||||
dataSource: DataSource;
|
||||
|
||||
constructor(dataSource: DataSource) {
|
||||
this.dataSource = dataSource;
|
||||
this.projectionRepository = dataSource.getRepository(CashFlowProjection);
|
||||
this.apRepository = dataSource.getRepository(AccountPayable);
|
||||
this.arRepository = dataSource.getRepository(AccountReceivable);
|
||||
@ -347,10 +350,10 @@ export class CashFlowService {
|
||||
const arRecords = await arQuery.getMany();
|
||||
const incomeEstimations = arRecords
|
||||
.filter((ar) => ar.documentType === 'estimation')
|
||||
.reduce((sum, ar) => sum + Number(ar.balanceAmount), 0);
|
||||
.reduce((sum, ar) => sum + Number(ar.balance), 0);
|
||||
const incomeSales = arRecords
|
||||
.filter((ar) => ar.documentType !== 'estimation')
|
||||
.reduce((sum, ar) => sum + Number(ar.balanceAmount), 0);
|
||||
.reduce((sum, ar) => sum + Number(ar.balance), 0);
|
||||
|
||||
// Obtener pagos esperados (AP por vencer en el periodo)
|
||||
const apQuery = this.apRepository
|
||||
@ -367,7 +370,7 @@ export class CashFlowService {
|
||||
const apRecords = await apQuery.getMany();
|
||||
const expenseSuppliers = apRecords
|
||||
.filter((ap) => ap.documentType === 'invoice')
|
||||
.reduce((sum, ap) => sum + Number(ap.balanceAmount), 0);
|
||||
.reduce((sum, ap) => sum + Number(ap.balance), 0);
|
||||
|
||||
// Determinar año y periodo fiscal
|
||||
const fiscalYear = periodStart.getFullYear();
|
||||
@ -401,16 +404,16 @@ export class CashFlowService {
|
||||
incomeBreakdown: arRecords.map((ar) => ({
|
||||
id: ar.id,
|
||||
documentNumber: ar.documentNumber,
|
||||
partnerName: ar.partnerName,
|
||||
partnerName: ar.customerName,
|
||||
dueDate: ar.dueDate,
|
||||
amount: ar.balanceAmount,
|
||||
amount: ar.balance,
|
||||
})),
|
||||
expenseBreakdown: apRecords.map((ap) => ({
|
||||
id: ap.id,
|
||||
documentNumber: ap.documentNumber,
|
||||
partnerName: ap.partnerName,
|
||||
partnerName: ap.supplierName,
|
||||
dueDate: ap.dueDate,
|
||||
amount: ap.balanceAmount,
|
||||
amount: ap.balance,
|
||||
})),
|
||||
notes: `Proyección automática generada el ${new Date().toISOString()}`,
|
||||
});
|
||||
|
||||
@ -9,11 +9,9 @@
|
||||
import { DataSource, Repository, IsNull, Between } from 'typeorm';
|
||||
import {
|
||||
ChartOfAccounts,
|
||||
AccountType,
|
||||
AccountingEntry,
|
||||
AccountingEntryLine,
|
||||
AccountPayable,
|
||||
AccountReceivable,
|
||||
BankMovement,
|
||||
} from '../entities';
|
||||
|
||||
interface ServiceContext {
|
||||
@ -21,13 +19,6 @@ interface ServiceContext {
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
interface ExportConfig {
|
||||
format: 'csv' | 'xml' | 'json' | 'txt';
|
||||
encoding?: string;
|
||||
delimiter?: string;
|
||||
includeHeaders?: boolean;
|
||||
}
|
||||
|
||||
interface SAPExportEntry {
|
||||
BUKRS: string; // Sociedad
|
||||
BELNR: string; // Número de documento
|
||||
@ -86,17 +77,13 @@ export class ERPIntegrationService {
|
||||
private accountRepository: Repository<ChartOfAccounts>;
|
||||
private entryRepository: Repository<AccountingEntry>;
|
||||
private lineRepository: Repository<AccountingEntryLine>;
|
||||
private apRepository: Repository<AccountPayable>;
|
||||
private arRepository: Repository<AccountReceivable>;
|
||||
private movementRepository: Repository<BankMovement>;
|
||||
dataSource: DataSource;
|
||||
|
||||
constructor(private dataSource: DataSource) {
|
||||
constructor(dataSource: DataSource) {
|
||||
this.dataSource = dataSource;
|
||||
this.accountRepository = dataSource.getRepository(ChartOfAccounts);
|
||||
this.entryRepository = dataSource.getRepository(AccountingEntry);
|
||||
this.lineRepository = dataSource.getRepository(AccountingEntryLine);
|
||||
this.apRepository = dataSource.getRepository(AccountPayable);
|
||||
this.arRepository = dataSource.getRepository(AccountReceivable);
|
||||
this.movementRepository = dataSource.getRepository(BankMovement);
|
||||
}
|
||||
|
||||
// ==================== EXPORTACIÓN SAP ====================
|
||||
@ -111,7 +98,7 @@ export class ERPIntegrationService {
|
||||
journalNumber?: number;
|
||||
} = {}
|
||||
): Promise<ExportResult> {
|
||||
const { companyCode = '1000', documentType = 'SA', journalNumber = 1 } = options;
|
||||
const { companyCode = '1000' } = options;
|
||||
|
||||
const entries = await this.entryRepository.find({
|
||||
where: {
|
||||
@ -132,7 +119,7 @@ export class ERPIntegrationService {
|
||||
BLDAT: this.formatDateSAP(entry.entryDate),
|
||||
BUDAT: this.formatDateSAP(entry.entryDate),
|
||||
MONAT: entry.fiscalPeriod,
|
||||
WAERS: entry.currencyCode,
|
||||
WAERS: entry.currency,
|
||||
KURSF: entry.exchangeRate,
|
||||
BKTXT: entry.description.slice(0, 25),
|
||||
lines: (entry.lines || []).map((line, idx) => ({
|
||||
@ -142,7 +129,7 @@ export class ERPIntegrationService {
|
||||
WRBTR: Math.abs(line.debit || line.credit),
|
||||
DMBTR: Math.abs(line.debit || line.credit) * entry.exchangeRate,
|
||||
SGTXT: (line.description || entry.description).slice(0, 50),
|
||||
ZUONR: line.reference?.slice(0, 18),
|
||||
ZUONR: line.description?.slice(0, 18),
|
||||
KOSTL: line.costCenterId?.slice(0, 10),
|
||||
PROJK: line.projectId?.slice(0, 24),
|
||||
})),
|
||||
@ -211,7 +198,7 @@ export class ERPIntegrationService {
|
||||
diario?: number;
|
||||
} = {}
|
||||
): Promise<ExportResult> {
|
||||
const { polizaTipo = 1, diario = 1 } = options; // 1=Diario
|
||||
const { diario = 1 } = options; // 1=Diario
|
||||
|
||||
const entries = await this.entryRepository.find({
|
||||
where: {
|
||||
@ -236,7 +223,7 @@ export class ERPIntegrationService {
|
||||
Concepto: (line.description || entry.description).slice(0, 200),
|
||||
Cargo: line.debit,
|
||||
Abono: line.credit,
|
||||
Referencia: line.reference?.slice(0, 20),
|
||||
Referencia: line.description?.slice(0, 20),
|
||||
Diario: diario,
|
||||
})),
|
||||
}));
|
||||
@ -345,7 +332,8 @@ ${entries.map((entry) => this.generatePolizaXML(entry)).join('\n')}
|
||||
|
||||
private generatePolizaXML(entry: AccountingEntry): string {
|
||||
const fecha = new Date(entry.entryDate).toISOString().split('T')[0];
|
||||
const tipoPoliza = this.mapEntryTypeToCFDI(entry.entryType);
|
||||
// Map entry type for CFDI (reserved for future use)
|
||||
this.mapEntryTypeToCFDI(entry.entryType);
|
||||
|
||||
const transacciones = (entry.lines || [])
|
||||
.map((line) => {
|
||||
@ -626,7 +614,7 @@ ${cuentas}
|
||||
nature: 'debit' | 'credit';
|
||||
level: number;
|
||||
parentCode?: string;
|
||||
satCode?: string;
|
||||
sapCode?: string;
|
||||
}[] = [];
|
||||
|
||||
if (format === 'json') {
|
||||
@ -643,7 +631,7 @@ ${cuentas}
|
||||
type: parts[2] as AccountType,
|
||||
nature: parts[3] as 'debit' | 'credit',
|
||||
level: parseInt(parts[4]) || 1,
|
||||
satCode: parts[5],
|
||||
sapCode: parts[5],
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -676,7 +664,7 @@ ${cuentas}
|
||||
accountType: acc.type,
|
||||
nature: acc.nature,
|
||||
level: acc.level,
|
||||
sapCode: acc.sapCode,
|
||||
satCode: acc.sapCode,
|
||||
allowsDirectPosting: true,
|
||||
status: 'active',
|
||||
initialBalance: 0,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user