feat(core): initial release of SKR03/SKR04 German accounting standards implementation
- Complete implementation of German standard charts of accounts - SKR03 (Process Structure Principle) for trading/service companies - SKR04 (Financial Classification Principle) for manufacturing companies - Double-entry bookkeeping with MongoDB persistence - Comprehensive reporting suite with DATEV export - Full TypeScript support and type safety
This commit is contained in:
721
ts/skr.classes.reports.ts
Normal file
721
ts/skr.classes.reports.ts
Normal file
@@ -0,0 +1,721 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { Account } from './skr.classes.account.js';
|
||||
import { Transaction } from './skr.classes.transaction.js';
|
||||
import { Ledger } from './skr.classes.ledger.js';
|
||||
import type {
|
||||
TSKRType,
|
||||
ITrialBalanceReport,
|
||||
ITrialBalanceEntry,
|
||||
IIncomeStatement,
|
||||
IIncomeStatementEntry,
|
||||
IBalanceSheet,
|
||||
IBalanceSheetEntry,
|
||||
IReportParams,
|
||||
} from './skr.types.js';
|
||||
|
||||
export class Reports {
|
||||
private logger: plugins.smartlog.Smartlog;
|
||||
private ledger: Ledger;
|
||||
|
||||
constructor(private skrType: TSKRType) {
|
||||
this.logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
company: 'fin.cx',
|
||||
companyunit: 'skr',
|
||||
containerName: 'Reports',
|
||||
environment: 'local',
|
||||
runtime: 'node',
|
||||
zone: 'local',
|
||||
},
|
||||
});
|
||||
this.ledger = new Ledger(skrType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Trial Balance
|
||||
*/
|
||||
public async getTrialBalance(
|
||||
params?: IReportParams,
|
||||
): Promise<ITrialBalanceReport> {
|
||||
this.logger.log('info', 'Generating trial balance');
|
||||
|
||||
const accounts = await Account.getInstances({
|
||||
skrType: this.skrType,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const entries: ITrialBalanceEntry[] = [];
|
||||
let totalDebits = 0;
|
||||
let totalCredits = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
// Get balance for the period if specified
|
||||
const balance = params?.dateTo
|
||||
? await this.ledger.getAccountBalance(
|
||||
account.accountNumber,
|
||||
params.dateTo,
|
||||
)
|
||||
: await this.ledger.getAccountBalance(account.accountNumber);
|
||||
|
||||
if (balance.debitTotal !== 0 || balance.creditTotal !== 0) {
|
||||
const entry: ITrialBalanceEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
debitBalance: balance.debitTotal,
|
||||
creditBalance: balance.creditTotal,
|
||||
netBalance: balance.balance,
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
totalDebits += balance.debitTotal;
|
||||
totalCredits += balance.creditTotal;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort entries by account number
|
||||
entries.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
|
||||
|
||||
const report: ITrialBalanceReport = {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
entries,
|
||||
totalDebits,
|
||||
totalCredits,
|
||||
isBalanced: Math.abs(totalDebits - totalCredits) < 0.01,
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Trial balance generated with ${entries.length} accounts`,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Income Statement (P&L)
|
||||
*/
|
||||
public async getIncomeStatement(
|
||||
params?: IReportParams,
|
||||
): Promise<IIncomeStatement> {
|
||||
this.logger.log('info', 'Generating income statement');
|
||||
|
||||
// Get revenue accounts
|
||||
const revenueAccounts = await Account.getAccountsByType(
|
||||
'revenue',
|
||||
this.skrType,
|
||||
);
|
||||
const expenseAccounts = await Account.getAccountsByType(
|
||||
'expense',
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
const revenueEntries: IIncomeStatementEntry[] = [];
|
||||
const expenseEntries: IIncomeStatementEntry[] = [];
|
||||
let totalRevenue = 0;
|
||||
let totalExpenses = 0;
|
||||
|
||||
// Process revenue accounts
|
||||
for (const account of revenueAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IIncomeStatementEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
revenueEntries.push(entry);
|
||||
totalRevenue += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Process expense accounts
|
||||
for (const account of expenseAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IIncomeStatementEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
expenseEntries.push(entry);
|
||||
totalExpenses += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
revenueEntries.forEach((entry) => {
|
||||
entry.percentage =
|
||||
totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
|
||||
});
|
||||
|
||||
expenseEntries.forEach((entry) => {
|
||||
entry.percentage =
|
||||
totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
|
||||
});
|
||||
|
||||
// Sort entries by account number
|
||||
revenueEntries.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
expenseEntries.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
|
||||
const report: IIncomeStatement = {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
revenue: revenueEntries,
|
||||
expenses: expenseEntries,
|
||||
totalRevenue,
|
||||
totalExpenses,
|
||||
netIncome: totalRevenue - totalExpenses,
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Income statement generated: Revenue ${totalRevenue}, Expenses ${totalExpenses}`,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Balance Sheet
|
||||
*/
|
||||
public async getBalanceSheet(params?: IReportParams): Promise<IBalanceSheet> {
|
||||
this.logger.log('info', 'Generating balance sheet');
|
||||
|
||||
// Get accounts by type
|
||||
const assetAccounts = await Account.getAccountsByType(
|
||||
'asset',
|
||||
this.skrType,
|
||||
);
|
||||
const liabilityAccounts = await Account.getAccountsByType(
|
||||
'liability',
|
||||
this.skrType,
|
||||
);
|
||||
const equityAccounts = await Account.getAccountsByType(
|
||||
'equity',
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Process assets
|
||||
const currentAssets: IBalanceSheetEntry[] = [];
|
||||
const fixedAssets: IBalanceSheetEntry[] = [];
|
||||
let totalAssets = 0;
|
||||
|
||||
for (const account of assetAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
// Classify as current or fixed based on account class
|
||||
if (account.accountClass === 1) {
|
||||
currentAssets.push(entry);
|
||||
} else {
|
||||
fixedAssets.push(entry);
|
||||
}
|
||||
|
||||
totalAssets += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Process liabilities
|
||||
const currentLiabilities: IBalanceSheetEntry[] = [];
|
||||
const longTermLiabilities: IBalanceSheetEntry[] = [];
|
||||
let totalLiabilities = 0;
|
||||
|
||||
for (const account of liabilityAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
// Classify as current or long-term based on account number
|
||||
if (
|
||||
account.accountNumber.startsWith('16') ||
|
||||
account.accountNumber.startsWith('17')
|
||||
) {
|
||||
currentLiabilities.push(entry);
|
||||
} else {
|
||||
longTermLiabilities.push(entry);
|
||||
}
|
||||
|
||||
totalLiabilities += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Process equity
|
||||
const equityEntries: IBalanceSheetEntry[] = [];
|
||||
let totalEquity = 0;
|
||||
|
||||
for (const account of equityAccounts) {
|
||||
const balance = await this.getAccountBalanceForPeriod(account, params);
|
||||
|
||||
if (balance !== 0) {
|
||||
const entry: IBalanceSheetEntry = {
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
amount: Math.abs(balance),
|
||||
};
|
||||
|
||||
equityEntries.push(entry);
|
||||
totalEquity += Math.abs(balance);
|
||||
}
|
||||
}
|
||||
|
||||
// Add current year profit/loss
|
||||
const incomeStatement = await this.getIncomeStatement(params);
|
||||
if (incomeStatement.netIncome !== 0) {
|
||||
equityEntries.push({
|
||||
accountNumber: '9999',
|
||||
accountName: 'Current Year Profit/Loss',
|
||||
amount: Math.abs(incomeStatement.netIncome),
|
||||
});
|
||||
totalEquity += Math.abs(incomeStatement.netIncome);
|
||||
}
|
||||
|
||||
// Sort entries
|
||||
currentAssets.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
fixedAssets.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
|
||||
currentLiabilities.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
longTermLiabilities.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
equityEntries.sort((a, b) =>
|
||||
a.accountNumber.localeCompare(b.accountNumber),
|
||||
);
|
||||
|
||||
const report: IBalanceSheet = {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
assets: {
|
||||
current: currentAssets,
|
||||
fixed: fixedAssets,
|
||||
totalAssets,
|
||||
},
|
||||
liabilities: {
|
||||
current: currentLiabilities,
|
||||
longTerm: longTermLiabilities,
|
||||
totalLiabilities,
|
||||
},
|
||||
equity: {
|
||||
entries: equityEntries,
|
||||
totalEquity,
|
||||
},
|
||||
isBalanced:
|
||||
Math.abs(totalAssets - (totalLiabilities + totalEquity)) < 0.01,
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
'info',
|
||||
`Balance sheet generated: Assets ${totalAssets}, Liabilities ${totalLiabilities}, Equity ${totalEquity}`,
|
||||
);
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account balance for a specific period
|
||||
*/
|
||||
private async getAccountBalanceForPeriod(
|
||||
account: Account,
|
||||
params?: IReportParams,
|
||||
): Promise<number> {
|
||||
let transactions = await Transaction.getTransactionsByAccount(
|
||||
account.accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Apply date filter if provided
|
||||
if (params?.dateFrom || params?.dateTo) {
|
||||
transactions = transactions.filter((transaction) => {
|
||||
if (params.dateFrom && transaction.date < params.dateFrom) return false;
|
||||
if (params.dateTo && transaction.date > params.dateTo) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
let debitTotal = 0;
|
||||
let creditTotal = 0;
|
||||
|
||||
for (const transaction of transactions) {
|
||||
if (transaction.debitAccount === account.accountNumber) {
|
||||
debitTotal += transaction.amount;
|
||||
}
|
||||
if (transaction.creditAccount === account.accountNumber) {
|
||||
creditTotal += transaction.amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate net balance based on account type
|
||||
switch (account.accountType) {
|
||||
case 'asset':
|
||||
case 'expense':
|
||||
return debitTotal - creditTotal;
|
||||
case 'liability':
|
||||
case 'equity':
|
||||
case 'revenue':
|
||||
return creditTotal - debitTotal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate General Ledger report
|
||||
*/
|
||||
public async getGeneralLedger(params?: IReportParams): Promise<any> {
|
||||
this.logger.log('info', 'Generating general ledger');
|
||||
|
||||
const accounts = await Account.getInstances({
|
||||
skrType: this.skrType,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const ledgerEntries = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const transactions = await this.getAccountTransactions(
|
||||
account.accountNumber,
|
||||
params,
|
||||
);
|
||||
|
||||
if (transactions.length > 0) {
|
||||
let runningBalance = 0;
|
||||
const accountEntries = [];
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const isDebit = transaction.debitAccount === account.accountNumber;
|
||||
const amount = transaction.amount;
|
||||
|
||||
// Update running balance based on account type
|
||||
if (
|
||||
account.accountType === 'asset' ||
|
||||
account.accountType === 'expense'
|
||||
) {
|
||||
runningBalance += isDebit ? amount : -amount;
|
||||
} else {
|
||||
runningBalance += isDebit ? -amount : amount;
|
||||
}
|
||||
|
||||
accountEntries.push({
|
||||
date: transaction.date,
|
||||
reference: transaction.reference,
|
||||
description: transaction.description,
|
||||
debit: isDebit ? amount : 0,
|
||||
credit: !isDebit ? amount : 0,
|
||||
balance: runningBalance,
|
||||
});
|
||||
}
|
||||
|
||||
ledgerEntries.push({
|
||||
accountNumber: account.accountNumber,
|
||||
accountName: account.accountName,
|
||||
accountType: account.accountType,
|
||||
entries: accountEntries,
|
||||
finalBalance: runningBalance,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
accounts: ledgerEntries,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account transactions for reporting
|
||||
*/
|
||||
private async getAccountTransactions(
|
||||
accountNumber: string,
|
||||
params?: IReportParams,
|
||||
): Promise<Transaction[]> {
|
||||
let transactions = await Transaction.getTransactionsByAccount(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
|
||||
// Apply date filter
|
||||
if (params?.dateFrom || params?.dateTo) {
|
||||
transactions = transactions.filter((transaction) => {
|
||||
if (params.dateFrom && transaction.date < params.dateFrom) return false;
|
||||
if (params.dateTo && transaction.date > params.dateTo) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by date
|
||||
transactions.sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Cash Flow Statement
|
||||
*/
|
||||
public async getCashFlowStatement(params?: IReportParams): Promise<any> {
|
||||
this.logger.log('info', 'Generating cash flow statement');
|
||||
|
||||
// Get cash and bank accounts
|
||||
const cashAccounts = ['1000', '1100', '1200', '1210']; // Standard cash/bank accounts
|
||||
|
||||
let operatingCashFlow = 0;
|
||||
let investingCashFlow = 0;
|
||||
let financingCashFlow = 0;
|
||||
|
||||
for (const accountNumber of cashAccounts) {
|
||||
const account = await Account.getAccountByNumber(
|
||||
accountNumber,
|
||||
this.skrType,
|
||||
);
|
||||
if (!account) continue;
|
||||
|
||||
const transactions = await this.getAccountTransactions(
|
||||
accountNumber,
|
||||
params,
|
||||
);
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const otherAccount =
|
||||
transaction.debitAccount === accountNumber
|
||||
? transaction.creditAccount
|
||||
: transaction.debitAccount;
|
||||
|
||||
const otherAccountObj = await Account.getAccountByNumber(
|
||||
otherAccount,
|
||||
this.skrType,
|
||||
);
|
||||
if (!otherAccountObj) continue;
|
||||
|
||||
const amount =
|
||||
transaction.debitAccount === accountNumber
|
||||
? transaction.amount
|
||||
: -transaction.amount;
|
||||
|
||||
// Classify cash flow
|
||||
if (
|
||||
otherAccountObj.accountType === 'revenue' ||
|
||||
otherAccountObj.accountType === 'expense'
|
||||
) {
|
||||
operatingCashFlow += amount;
|
||||
} else if (otherAccountObj.accountClass === 0) {
|
||||
// Fixed assets
|
||||
investingCashFlow += amount;
|
||||
} else if (
|
||||
otherAccountObj.accountType === 'liability' ||
|
||||
otherAccountObj.accountType === 'equity'
|
||||
) {
|
||||
financingCashFlow += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
date: params?.dateTo || new Date(),
|
||||
skrType: this.skrType,
|
||||
operatingActivities: operatingCashFlow,
|
||||
investingActivities: investingCashFlow,
|
||||
financingActivities: financingCashFlow,
|
||||
netCashFlow: operatingCashFlow + investingCashFlow + financingCashFlow,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export report to CSV format
|
||||
*/
|
||||
public async exportToCSV(
|
||||
reportType: 'trial_balance' | 'income_statement' | 'balance_sheet',
|
||||
params?: IReportParams,
|
||||
): Promise<string> {
|
||||
let csvContent = '';
|
||||
|
||||
switch (reportType) {
|
||||
case 'trial_balance':
|
||||
const trialBalance = await this.getTrialBalance(params);
|
||||
csvContent = this.trialBalanceToCSV(trialBalance);
|
||||
break;
|
||||
|
||||
case 'income_statement':
|
||||
const incomeStatement = await this.getIncomeStatement(params);
|
||||
csvContent = this.incomeStatementToCSV(incomeStatement);
|
||||
break;
|
||||
|
||||
case 'balance_sheet':
|
||||
const balanceSheet = await this.getBalanceSheet(params);
|
||||
csvContent = this.balanceSheetToCSV(balanceSheet);
|
||||
break;
|
||||
}
|
||||
|
||||
return csvContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert trial balance to CSV
|
||||
*/
|
||||
private trialBalanceToCSV(report: ITrialBalanceReport): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('"Account Number";"Account Name";"Debit";"Credit";"Balance"');
|
||||
|
||||
for (const entry of report.entries) {
|
||||
lines.push(
|
||||
`"${entry.accountNumber}";"${entry.accountName}";${entry.debitBalance};${entry.creditBalance};${entry.netBalance}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`"TOTAL";"";"${report.totalDebits}";"${report.totalCredits}";"""`,
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert income statement to CSV
|
||||
*/
|
||||
private incomeStatementToCSV(report: IIncomeStatement): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('"Type";"Account Number";"Account Name";"Amount";"Percentage"');
|
||||
|
||||
lines.push('"REVENUE";"";"";"";""');
|
||||
for (const entry of report.revenue) {
|
||||
lines.push(
|
||||
`"Revenue";"${entry.accountNumber}";"${entry.accountName}";${entry.amount};${entry.percentage?.toFixed(2)}%`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Revenue";"";"";"${report.totalRevenue}";"""`);
|
||||
lines.push('"";"";"";"";""');
|
||||
|
||||
lines.push('"EXPENSES";"";"";"";""');
|
||||
for (const entry of report.expenses) {
|
||||
lines.push(
|
||||
`"Expense";"${entry.accountNumber}";"${entry.accountName}";${entry.amount};${entry.percentage?.toFixed(2)}%`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Expenses";"";"";"${report.totalExpenses}";"""`);
|
||||
lines.push('"";"";"";"";""');
|
||||
lines.push(`"NET INCOME";"";"";"${report.netIncome}";"""`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert balance sheet to CSV
|
||||
*/
|
||||
private balanceSheetToCSV(report: IBalanceSheet): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('"Category";"Account Number";"Account Name";"Amount"');
|
||||
|
||||
lines.push('"ASSETS";"";"";"";');
|
||||
lines.push('"Current Assets";"";"";"";');
|
||||
for (const entry of report.assets.current) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push('"Fixed Assets";"";"";"";');
|
||||
for (const entry of report.assets.fixed) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Assets";"";"";"${report.assets.totalAssets}"`);
|
||||
lines.push('"";"";"";"";');
|
||||
|
||||
lines.push('"LIABILITIES";"";"";"";');
|
||||
lines.push('"Current Liabilities";"";"";"";');
|
||||
for (const entry of report.liabilities.current) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push('"Long-term Liabilities";"";"";"";');
|
||||
for (const entry of report.liabilities.longTerm) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`"Total Liabilities";"";"";"${report.liabilities.totalLiabilities}"`,
|
||||
);
|
||||
lines.push('"";"";"";"";');
|
||||
|
||||
lines.push('"EQUITY";"";"";"";');
|
||||
for (const entry of report.equity.entries) {
|
||||
lines.push(
|
||||
`"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`"Total Equity";"";"";"${report.equity.totalEquity}"`);
|
||||
lines.push('"";"";"";"";');
|
||||
lines.push(
|
||||
`"Total Liabilities + Equity";"";"";"${report.liabilities.totalLiabilities + report.equity.totalEquity}"`,
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export to DATEV format
|
||||
*/
|
||||
public async exportToDATEV(params?: IReportParams): Promise<string> {
|
||||
// DATEV format is specific to German accounting software
|
||||
// This is a simplified implementation
|
||||
const transactions = await Transaction.getInstances({
|
||||
skrType: this.skrType,
|
||||
status: 'posted',
|
||||
});
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
// DATEV header
|
||||
lines.push('EXTF;510;21;"Buchungsstapel";1;;;;;;;;;;;;;;');
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const date = transaction.date
|
||||
.toISOString()
|
||||
.split('T')[0]
|
||||
.replace(/-/g, '');
|
||||
const line = [
|
||||
transaction.amount.toFixed(2).replace('.', ','),
|
||||
'S',
|
||||
'EUR',
|
||||
'',
|
||||
'',
|
||||
transaction.debitAccount,
|
||||
transaction.creditAccount,
|
||||
'',
|
||||
date,
|
||||
'',
|
||||
transaction.description.substring(0, 60),
|
||||
'',
|
||||
].join(';');
|
||||
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user