import * as plugins from './plugins.js'; import * as path from 'path'; import type { IAccountBalance } from './skr.types.js'; // Extended interface for export with additional fields export interface IAccountBalanceExport extends IAccountBalance { openingBalance?: number; transactionCount?: number; } export interface IBalanceExportRow { account_code: string; account_name: string; fiscal_year: number; period?: string; opening_balance: string; closing_balance: string; debit_sum: string; credit_sum: string; balance: string; transaction_count: number; } export class BalancesExporter { private exportPath: string; private balances: IBalanceExportRow[] = []; private fiscalYear: number; constructor(exportPath: string, fiscalYear: number) { this.exportPath = exportPath; this.fiscalYear = fiscalYear; } /** * Adds a balance entry to the export */ public addBalance( accountCode: string, accountName: string, balance: IAccountBalanceExport, period?: string ): void { const exportRow: IBalanceExportRow = { account_code: accountCode, account_name: accountName, fiscal_year: this.fiscalYear, period: period, opening_balance: (balance.openingBalance || 0).toFixed(2), closing_balance: balance.balance.toFixed(2), debit_sum: balance.debitTotal.toFixed(2), credit_sum: balance.creditTotal.toFixed(2), balance: balance.balance.toFixed(2), transaction_count: balance.transactionCount || 0 }; this.balances.push(exportRow); } /** * Exports balances to CSV format */ public async exportToCSV(): Promise { const csvPath = path.join(this.exportPath, 'data', 'accounting', 'balances.csv'); await plugins.smartfile.fs.ensureDir(path.dirname(csvPath)); // Create CSV header const headers = [ 'account_code', 'account_name', 'fiscal_year', 'period', 'opening_balance', 'closing_balance', 'debit_sum', 'credit_sum', 'balance', 'transaction_count' ]; let csvContent = headers.join(',') + '\n'; // Sort balances by account code this.balances.sort((a, b) => a.account_code.localeCompare(b.account_code)); // Add balance rows for (const balance of this.balances) { const row = [ this.escapeCSV(balance.account_code), this.escapeCSV(balance.account_name), balance.fiscal_year.toString(), this.escapeCSV(balance.period || ''), balance.opening_balance, balance.closing_balance, balance.debit_sum, balance.credit_sum, balance.balance, balance.transaction_count.toString() ]; csvContent += row.join(',') + '\n'; } await plugins.smartfile.memory.toFs(csvContent, csvPath); } /** * Exports trial balance (Summen- und Saldenliste) */ public async exportTrialBalance(): Promise { const csvPath = path.join(this.exportPath, 'data', 'accounting', 'trial_balance.csv'); await plugins.smartfile.fs.ensureDir(path.dirname(csvPath)); // Create CSV header for trial balance const headers = [ 'Konto', 'Bezeichnung', 'Anfangssaldo', 'Soll', 'Haben', 'Saldo', 'Endsaldo' ]; let csvContent = headers.join(',') + '\n'; // Add rows with German formatting for (const balance of this.balances) { const row = [ this.escapeCSV(balance.account_code), this.escapeCSV(balance.account_name), this.formatGermanNumber(parseFloat(balance.opening_balance)), this.formatGermanNumber(parseFloat(balance.debit_sum)), this.formatGermanNumber(parseFloat(balance.credit_sum)), this.formatGermanNumber(parseFloat(balance.debit_sum) - parseFloat(balance.credit_sum)), this.formatGermanNumber(parseFloat(balance.closing_balance)) ]; csvContent += row.join(',') + '\n'; } // Add totals row const totalDebit = this.balances.reduce((sum, b) => sum + parseFloat(b.debit_sum), 0); const totalCredit = this.balances.reduce((sum, b) => sum + parseFloat(b.credit_sum), 0); csvContent += '\n'; csvContent += [ 'SUMME', '', '', this.formatGermanNumber(totalDebit), this.formatGermanNumber(totalCredit), this.formatGermanNumber(totalDebit - totalCredit), '' ].join(',') + '\n'; await plugins.smartfile.memory.toFs(csvContent, csvPath); } /** * Exports balances to JSON format */ public async exportToJSON(): Promise { const jsonPath = path.join(this.exportPath, 'data', 'accounting', 'balances.json'); await plugins.smartfile.fs.ensureDir(path.dirname(jsonPath)); const jsonData = { schema_version: '1.0', export_date: new Date().toISOString(), fiscal_year: this.fiscalYear, balances: this.balances, totals: { total_debit: this.balances.reduce((sum, b) => sum + parseFloat(b.debit_sum), 0).toFixed(2), total_credit: this.balances.reduce((sum, b) => sum + parseFloat(b.credit_sum), 0).toFixed(2), account_count: this.balances.length } }; await plugins.smartfile.memory.toFs( JSON.stringify(jsonData, null, 2), jsonPath ); } /** * Generates balance summary for specific account classes */ public async exportClassSummary(): Promise { const csvPath = path.join(this.exportPath, 'data', 'accounting', 'class_summary.csv'); await plugins.smartfile.fs.ensureDir(path.dirname(csvPath)); // Group balances by account class (first digit of account code) const classSummary: { [key: string]: { debit: number; credit: number; balance: number } } = {}; for (const balance of this.balances) { const accountClass = balance.account_code.charAt(0); if (!classSummary[accountClass]) { classSummary[accountClass] = { debit: 0, credit: 0, balance: 0 }; } classSummary[accountClass].debit += parseFloat(balance.debit_sum); classSummary[accountClass].credit += parseFloat(balance.credit_sum); classSummary[accountClass].balance += parseFloat(balance.balance); } // Create CSV let csvContent = 'Kontenklasse,Bezeichnung,Soll,Haben,Saldo\n'; const classNames: { [key: string]: string } = { '0': 'Anlagevermögen', '1': 'Umlaufvermögen', '2': 'Eigenkapital', '3': 'Fremdkapital', '4': 'Betriebliche Erträge', '5': 'Materialaufwand', '6': 'Betriebsaufwand', '7': 'Weitere Aufwendungen', '8': 'Erträge', '9': 'Abschlusskonten' }; for (const [classNum, summary] of Object.entries(classSummary)) { const row = [ classNum, this.escapeCSV(classNames[classNum] || `Klasse ${classNum}`), this.formatGermanNumber(summary.debit), this.formatGermanNumber(summary.credit), this.formatGermanNumber(summary.balance) ]; csvContent += row.join(',') + '\n'; } await plugins.smartfile.memory.toFs(csvContent, csvPath); } /** * Escapes CSV values */ private escapeCSV(value: string): string { if (value.includes(',') || value.includes('"') || value.includes('\n')) { return `"${value.replace(/"/g, '""')}"`; } return value; } /** * Formats number in German format (1.234,56) */ private formatGermanNumber(value: number): string { return value.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } /** * Gets the number of balance entries */ public getBalanceCount(): number { return this.balances.length; } /** * Clears the balances list */ public clear(): void { this.balances = []; } }