@fin.cx/skr 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist_ts/index.d.ts +10 -0
  2. package/dist_ts/index.js +11 -0
  3. package/dist_ts/plugins.d.ts +5 -0
  4. package/dist_ts/plugins.js +7 -0
  5. package/dist_ts/skr.api.d.ts +181 -0
  6. package/dist_ts/skr.api.js +402 -0
  7. package/dist_ts/skr.classes.account.d.ts +34 -0
  8. package/dist_ts/skr.classes.account.js +224 -0
  9. package/dist_ts/skr.classes.chartofaccounts.d.ts +99 -0
  10. package/dist_ts/skr.classes.chartofaccounts.js +377 -0
  11. package/dist_ts/skr.classes.journalentry.d.ts +33 -0
  12. package/dist_ts/skr.classes.journalentry.js +296 -0
  13. package/dist_ts/skr.classes.ledger.d.ts +61 -0
  14. package/dist_ts/skr.classes.ledger.js +363 -0
  15. package/dist_ts/skr.classes.reports.d.ts +55 -0
  16. package/dist_ts/skr.classes.reports.js +514 -0
  17. package/dist_ts/skr.classes.transaction.d.ts +37 -0
  18. package/dist_ts/skr.classes.transaction.js +263 -0
  19. package/dist_ts/skr.database.d.ts +5 -0
  20. package/dist_ts/skr.database.js +28 -0
  21. package/dist_ts/skr.types.d.ts +126 -0
  22. package/dist_ts/skr.types.js +2 -0
  23. package/dist_ts/skr03.data.d.ts +18 -0
  24. package/dist_ts/skr03.data.js +890 -0
  25. package/dist_ts/skr04.data.d.ts +18 -0
  26. package/dist_ts/skr04.data.js +912 -0
  27. package/npmextra.json +17 -0
  28. package/package.json +61 -0
  29. package/readme.hints.md +3 -0
  30. package/readme.md +409 -0
  31. package/readme.plan.md +243 -0
  32. package/ts/index.ts +10 -0
  33. package/ts/plugins.ts +7 -0
  34. package/ts/skr.api.ts +533 -0
  35. package/ts/skr.classes.account.ts +238 -0
  36. package/ts/skr.classes.chartofaccounts.ts +508 -0
  37. package/ts/skr.classes.journalentry.ts +318 -0
  38. package/ts/skr.classes.ledger.ts +528 -0
  39. package/ts/skr.classes.reports.ts +721 -0
  40. package/ts/skr.classes.transaction.ts +300 -0
  41. package/ts/skr.database.ts +39 -0
  42. package/ts/skr.types.ts +154 -0
  43. package/ts/skr03.data.ts +901 -0
  44. package/ts/skr04.data.ts +923 -0
@@ -0,0 +1,721 @@
1
+ import * as plugins from './plugins.js';
2
+ import { Account } from './skr.classes.account.js';
3
+ import { Transaction } from './skr.classes.transaction.js';
4
+ import { Ledger } from './skr.classes.ledger.js';
5
+ import type {
6
+ TSKRType,
7
+ ITrialBalanceReport,
8
+ ITrialBalanceEntry,
9
+ IIncomeStatement,
10
+ IIncomeStatementEntry,
11
+ IBalanceSheet,
12
+ IBalanceSheetEntry,
13
+ IReportParams,
14
+ } from './skr.types.js';
15
+
16
+ export class Reports {
17
+ private logger: plugins.smartlog.Smartlog;
18
+ private ledger: Ledger;
19
+
20
+ constructor(private skrType: TSKRType) {
21
+ this.logger = new plugins.smartlog.Smartlog({
22
+ logContext: {
23
+ company: 'fin.cx',
24
+ companyunit: 'skr',
25
+ containerName: 'Reports',
26
+ environment: 'local',
27
+ runtime: 'node',
28
+ zone: 'local',
29
+ },
30
+ });
31
+ this.ledger = new Ledger(skrType);
32
+ }
33
+
34
+ /**
35
+ * Generate Trial Balance
36
+ */
37
+ public async getTrialBalance(
38
+ params?: IReportParams,
39
+ ): Promise<ITrialBalanceReport> {
40
+ this.logger.log('info', 'Generating trial balance');
41
+
42
+ const accounts = await Account.getInstances({
43
+ skrType: this.skrType,
44
+ isActive: true,
45
+ });
46
+
47
+ const entries: ITrialBalanceEntry[] = [];
48
+ let totalDebits = 0;
49
+ let totalCredits = 0;
50
+
51
+ for (const account of accounts) {
52
+ // Get balance for the period if specified
53
+ const balance = params?.dateTo
54
+ ? await this.ledger.getAccountBalance(
55
+ account.accountNumber,
56
+ params.dateTo,
57
+ )
58
+ : await this.ledger.getAccountBalance(account.accountNumber);
59
+
60
+ if (balance.debitTotal !== 0 || balance.creditTotal !== 0) {
61
+ const entry: ITrialBalanceEntry = {
62
+ accountNumber: account.accountNumber,
63
+ accountName: account.accountName,
64
+ debitBalance: balance.debitTotal,
65
+ creditBalance: balance.creditTotal,
66
+ netBalance: balance.balance,
67
+ };
68
+
69
+ entries.push(entry);
70
+ totalDebits += balance.debitTotal;
71
+ totalCredits += balance.creditTotal;
72
+ }
73
+ }
74
+
75
+ // Sort entries by account number
76
+ entries.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
77
+
78
+ const report: ITrialBalanceReport = {
79
+ date: params?.dateTo || new Date(),
80
+ skrType: this.skrType,
81
+ entries,
82
+ totalDebits,
83
+ totalCredits,
84
+ isBalanced: Math.abs(totalDebits - totalCredits) < 0.01,
85
+ };
86
+
87
+ this.logger.log(
88
+ 'info',
89
+ `Trial balance generated with ${entries.length} accounts`,
90
+ );
91
+ return report;
92
+ }
93
+
94
+ /**
95
+ * Generate Income Statement (P&L)
96
+ */
97
+ public async getIncomeStatement(
98
+ params?: IReportParams,
99
+ ): Promise<IIncomeStatement> {
100
+ this.logger.log('info', 'Generating income statement');
101
+
102
+ // Get revenue accounts
103
+ const revenueAccounts = await Account.getAccountsByType(
104
+ 'revenue',
105
+ this.skrType,
106
+ );
107
+ const expenseAccounts = await Account.getAccountsByType(
108
+ 'expense',
109
+ this.skrType,
110
+ );
111
+
112
+ const revenueEntries: IIncomeStatementEntry[] = [];
113
+ const expenseEntries: IIncomeStatementEntry[] = [];
114
+ let totalRevenue = 0;
115
+ let totalExpenses = 0;
116
+
117
+ // Process revenue accounts
118
+ for (const account of revenueAccounts) {
119
+ const balance = await this.getAccountBalanceForPeriod(account, params);
120
+
121
+ if (balance !== 0) {
122
+ const entry: IIncomeStatementEntry = {
123
+ accountNumber: account.accountNumber,
124
+ accountName: account.accountName,
125
+ amount: Math.abs(balance),
126
+ };
127
+
128
+ revenueEntries.push(entry);
129
+ totalRevenue += Math.abs(balance);
130
+ }
131
+ }
132
+
133
+ // Process expense accounts
134
+ for (const account of expenseAccounts) {
135
+ const balance = await this.getAccountBalanceForPeriod(account, params);
136
+
137
+ if (balance !== 0) {
138
+ const entry: IIncomeStatementEntry = {
139
+ accountNumber: account.accountNumber,
140
+ accountName: account.accountName,
141
+ amount: Math.abs(balance),
142
+ };
143
+
144
+ expenseEntries.push(entry);
145
+ totalExpenses += Math.abs(balance);
146
+ }
147
+ }
148
+
149
+ // Calculate percentages
150
+ revenueEntries.forEach((entry) => {
151
+ entry.percentage =
152
+ totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
153
+ });
154
+
155
+ expenseEntries.forEach((entry) => {
156
+ entry.percentage =
157
+ totalRevenue > 0 ? (entry.amount / totalRevenue) * 100 : 0;
158
+ });
159
+
160
+ // Sort entries by account number
161
+ revenueEntries.sort((a, b) =>
162
+ a.accountNumber.localeCompare(b.accountNumber),
163
+ );
164
+ expenseEntries.sort((a, b) =>
165
+ a.accountNumber.localeCompare(b.accountNumber),
166
+ );
167
+
168
+ const report: IIncomeStatement = {
169
+ date: params?.dateTo || new Date(),
170
+ skrType: this.skrType,
171
+ revenue: revenueEntries,
172
+ expenses: expenseEntries,
173
+ totalRevenue,
174
+ totalExpenses,
175
+ netIncome: totalRevenue - totalExpenses,
176
+ };
177
+
178
+ this.logger.log(
179
+ 'info',
180
+ `Income statement generated: Revenue ${totalRevenue}, Expenses ${totalExpenses}`,
181
+ );
182
+ return report;
183
+ }
184
+
185
+ /**
186
+ * Generate Balance Sheet
187
+ */
188
+ public async getBalanceSheet(params?: IReportParams): Promise<IBalanceSheet> {
189
+ this.logger.log('info', 'Generating balance sheet');
190
+
191
+ // Get accounts by type
192
+ const assetAccounts = await Account.getAccountsByType(
193
+ 'asset',
194
+ this.skrType,
195
+ );
196
+ const liabilityAccounts = await Account.getAccountsByType(
197
+ 'liability',
198
+ this.skrType,
199
+ );
200
+ const equityAccounts = await Account.getAccountsByType(
201
+ 'equity',
202
+ this.skrType,
203
+ );
204
+
205
+ // Process assets
206
+ const currentAssets: IBalanceSheetEntry[] = [];
207
+ const fixedAssets: IBalanceSheetEntry[] = [];
208
+ let totalAssets = 0;
209
+
210
+ for (const account of assetAccounts) {
211
+ const balance = await this.getAccountBalanceForPeriod(account, params);
212
+
213
+ if (balance !== 0) {
214
+ const entry: IBalanceSheetEntry = {
215
+ accountNumber: account.accountNumber,
216
+ accountName: account.accountName,
217
+ amount: Math.abs(balance),
218
+ };
219
+
220
+ // Classify as current or fixed based on account class
221
+ if (account.accountClass === 1) {
222
+ currentAssets.push(entry);
223
+ } else {
224
+ fixedAssets.push(entry);
225
+ }
226
+
227
+ totalAssets += Math.abs(balance);
228
+ }
229
+ }
230
+
231
+ // Process liabilities
232
+ const currentLiabilities: IBalanceSheetEntry[] = [];
233
+ const longTermLiabilities: IBalanceSheetEntry[] = [];
234
+ let totalLiabilities = 0;
235
+
236
+ for (const account of liabilityAccounts) {
237
+ const balance = await this.getAccountBalanceForPeriod(account, params);
238
+
239
+ if (balance !== 0) {
240
+ const entry: IBalanceSheetEntry = {
241
+ accountNumber: account.accountNumber,
242
+ accountName: account.accountName,
243
+ amount: Math.abs(balance),
244
+ };
245
+
246
+ // Classify as current or long-term based on account number
247
+ if (
248
+ account.accountNumber.startsWith('16') ||
249
+ account.accountNumber.startsWith('17')
250
+ ) {
251
+ currentLiabilities.push(entry);
252
+ } else {
253
+ longTermLiabilities.push(entry);
254
+ }
255
+
256
+ totalLiabilities += Math.abs(balance);
257
+ }
258
+ }
259
+
260
+ // Process equity
261
+ const equityEntries: IBalanceSheetEntry[] = [];
262
+ let totalEquity = 0;
263
+
264
+ for (const account of equityAccounts) {
265
+ const balance = await this.getAccountBalanceForPeriod(account, params);
266
+
267
+ if (balance !== 0) {
268
+ const entry: IBalanceSheetEntry = {
269
+ accountNumber: account.accountNumber,
270
+ accountName: account.accountName,
271
+ amount: Math.abs(balance),
272
+ };
273
+
274
+ equityEntries.push(entry);
275
+ totalEquity += Math.abs(balance);
276
+ }
277
+ }
278
+
279
+ // Add current year profit/loss
280
+ const incomeStatement = await this.getIncomeStatement(params);
281
+ if (incomeStatement.netIncome !== 0) {
282
+ equityEntries.push({
283
+ accountNumber: '9999',
284
+ accountName: 'Current Year Profit/Loss',
285
+ amount: Math.abs(incomeStatement.netIncome),
286
+ });
287
+ totalEquity += Math.abs(incomeStatement.netIncome);
288
+ }
289
+
290
+ // Sort entries
291
+ currentAssets.sort((a, b) =>
292
+ a.accountNumber.localeCompare(b.accountNumber),
293
+ );
294
+ fixedAssets.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
295
+ currentLiabilities.sort((a, b) =>
296
+ a.accountNumber.localeCompare(b.accountNumber),
297
+ );
298
+ longTermLiabilities.sort((a, b) =>
299
+ a.accountNumber.localeCompare(b.accountNumber),
300
+ );
301
+ equityEntries.sort((a, b) =>
302
+ a.accountNumber.localeCompare(b.accountNumber),
303
+ );
304
+
305
+ const report: IBalanceSheet = {
306
+ date: params?.dateTo || new Date(),
307
+ skrType: this.skrType,
308
+ assets: {
309
+ current: currentAssets,
310
+ fixed: fixedAssets,
311
+ totalAssets,
312
+ },
313
+ liabilities: {
314
+ current: currentLiabilities,
315
+ longTerm: longTermLiabilities,
316
+ totalLiabilities,
317
+ },
318
+ equity: {
319
+ entries: equityEntries,
320
+ totalEquity,
321
+ },
322
+ isBalanced:
323
+ Math.abs(totalAssets - (totalLiabilities + totalEquity)) < 0.01,
324
+ };
325
+
326
+ this.logger.log(
327
+ 'info',
328
+ `Balance sheet generated: Assets ${totalAssets}, Liabilities ${totalLiabilities}, Equity ${totalEquity}`,
329
+ );
330
+ return report;
331
+ }
332
+
333
+ /**
334
+ * Get account balance for a specific period
335
+ */
336
+ private async getAccountBalanceForPeriod(
337
+ account: Account,
338
+ params?: IReportParams,
339
+ ): Promise<number> {
340
+ let transactions = await Transaction.getTransactionsByAccount(
341
+ account.accountNumber,
342
+ this.skrType,
343
+ );
344
+
345
+ // Apply date filter if provided
346
+ if (params?.dateFrom || params?.dateTo) {
347
+ transactions = transactions.filter((transaction) => {
348
+ if (params.dateFrom && transaction.date < params.dateFrom) return false;
349
+ if (params.dateTo && transaction.date > params.dateTo) return false;
350
+ return true;
351
+ });
352
+ }
353
+
354
+ let debitTotal = 0;
355
+ let creditTotal = 0;
356
+
357
+ for (const transaction of transactions) {
358
+ if (transaction.debitAccount === account.accountNumber) {
359
+ debitTotal += transaction.amount;
360
+ }
361
+ if (transaction.creditAccount === account.accountNumber) {
362
+ creditTotal += transaction.amount;
363
+ }
364
+ }
365
+
366
+ // Calculate net balance based on account type
367
+ switch (account.accountType) {
368
+ case 'asset':
369
+ case 'expense':
370
+ return debitTotal - creditTotal;
371
+ case 'liability':
372
+ case 'equity':
373
+ case 'revenue':
374
+ return creditTotal - debitTotal;
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Generate General Ledger report
380
+ */
381
+ public async getGeneralLedger(params?: IReportParams): Promise<any> {
382
+ this.logger.log('info', 'Generating general ledger');
383
+
384
+ const accounts = await Account.getInstances({
385
+ skrType: this.skrType,
386
+ isActive: true,
387
+ });
388
+
389
+ const ledgerEntries = [];
390
+
391
+ for (const account of accounts) {
392
+ const transactions = await this.getAccountTransactions(
393
+ account.accountNumber,
394
+ params,
395
+ );
396
+
397
+ if (transactions.length > 0) {
398
+ let runningBalance = 0;
399
+ const accountEntries = [];
400
+
401
+ for (const transaction of transactions) {
402
+ const isDebit = transaction.debitAccount === account.accountNumber;
403
+ const amount = transaction.amount;
404
+
405
+ // Update running balance based on account type
406
+ if (
407
+ account.accountType === 'asset' ||
408
+ account.accountType === 'expense'
409
+ ) {
410
+ runningBalance += isDebit ? amount : -amount;
411
+ } else {
412
+ runningBalance += isDebit ? -amount : amount;
413
+ }
414
+
415
+ accountEntries.push({
416
+ date: transaction.date,
417
+ reference: transaction.reference,
418
+ description: transaction.description,
419
+ debit: isDebit ? amount : 0,
420
+ credit: !isDebit ? amount : 0,
421
+ balance: runningBalance,
422
+ });
423
+ }
424
+
425
+ ledgerEntries.push({
426
+ accountNumber: account.accountNumber,
427
+ accountName: account.accountName,
428
+ accountType: account.accountType,
429
+ entries: accountEntries,
430
+ finalBalance: runningBalance,
431
+ });
432
+ }
433
+ }
434
+
435
+ return {
436
+ date: params?.dateTo || new Date(),
437
+ skrType: this.skrType,
438
+ accounts: ledgerEntries,
439
+ };
440
+ }
441
+
442
+ /**
443
+ * Get account transactions for reporting
444
+ */
445
+ private async getAccountTransactions(
446
+ accountNumber: string,
447
+ params?: IReportParams,
448
+ ): Promise<Transaction[]> {
449
+ let transactions = await Transaction.getTransactionsByAccount(
450
+ accountNumber,
451
+ this.skrType,
452
+ );
453
+
454
+ // Apply date filter
455
+ if (params?.dateFrom || params?.dateTo) {
456
+ transactions = transactions.filter((transaction) => {
457
+ if (params.dateFrom && transaction.date < params.dateFrom) return false;
458
+ if (params.dateTo && transaction.date > params.dateTo) return false;
459
+ return true;
460
+ });
461
+ }
462
+
463
+ // Sort by date
464
+ transactions.sort((a, b) => a.date.getTime() - b.date.getTime());
465
+
466
+ return transactions;
467
+ }
468
+
469
+ /**
470
+ * Generate Cash Flow Statement
471
+ */
472
+ public async getCashFlowStatement(params?: IReportParams): Promise<any> {
473
+ this.logger.log('info', 'Generating cash flow statement');
474
+
475
+ // Get cash and bank accounts
476
+ const cashAccounts = ['1000', '1100', '1200', '1210']; // Standard cash/bank accounts
477
+
478
+ let operatingCashFlow = 0;
479
+ let investingCashFlow = 0;
480
+ let financingCashFlow = 0;
481
+
482
+ for (const accountNumber of cashAccounts) {
483
+ const account = await Account.getAccountByNumber(
484
+ accountNumber,
485
+ this.skrType,
486
+ );
487
+ if (!account) continue;
488
+
489
+ const transactions = await this.getAccountTransactions(
490
+ accountNumber,
491
+ params,
492
+ );
493
+
494
+ for (const transaction of transactions) {
495
+ const otherAccount =
496
+ transaction.debitAccount === accountNumber
497
+ ? transaction.creditAccount
498
+ : transaction.debitAccount;
499
+
500
+ const otherAccountObj = await Account.getAccountByNumber(
501
+ otherAccount,
502
+ this.skrType,
503
+ );
504
+ if (!otherAccountObj) continue;
505
+
506
+ const amount =
507
+ transaction.debitAccount === accountNumber
508
+ ? transaction.amount
509
+ : -transaction.amount;
510
+
511
+ // Classify cash flow
512
+ if (
513
+ otherAccountObj.accountType === 'revenue' ||
514
+ otherAccountObj.accountType === 'expense'
515
+ ) {
516
+ operatingCashFlow += amount;
517
+ } else if (otherAccountObj.accountClass === 0) {
518
+ // Fixed assets
519
+ investingCashFlow += amount;
520
+ } else if (
521
+ otherAccountObj.accountType === 'liability' ||
522
+ otherAccountObj.accountType === 'equity'
523
+ ) {
524
+ financingCashFlow += amount;
525
+ }
526
+ }
527
+ }
528
+
529
+ return {
530
+ date: params?.dateTo || new Date(),
531
+ skrType: this.skrType,
532
+ operatingActivities: operatingCashFlow,
533
+ investingActivities: investingCashFlow,
534
+ financingActivities: financingCashFlow,
535
+ netCashFlow: operatingCashFlow + investingCashFlow + financingCashFlow,
536
+ };
537
+ }
538
+
539
+ /**
540
+ * Export report to CSV format
541
+ */
542
+ public async exportToCSV(
543
+ reportType: 'trial_balance' | 'income_statement' | 'balance_sheet',
544
+ params?: IReportParams,
545
+ ): Promise<string> {
546
+ let csvContent = '';
547
+
548
+ switch (reportType) {
549
+ case 'trial_balance':
550
+ const trialBalance = await this.getTrialBalance(params);
551
+ csvContent = this.trialBalanceToCSV(trialBalance);
552
+ break;
553
+
554
+ case 'income_statement':
555
+ const incomeStatement = await this.getIncomeStatement(params);
556
+ csvContent = this.incomeStatementToCSV(incomeStatement);
557
+ break;
558
+
559
+ case 'balance_sheet':
560
+ const balanceSheet = await this.getBalanceSheet(params);
561
+ csvContent = this.balanceSheetToCSV(balanceSheet);
562
+ break;
563
+ }
564
+
565
+ return csvContent;
566
+ }
567
+
568
+ /**
569
+ * Convert trial balance to CSV
570
+ */
571
+ private trialBalanceToCSV(report: ITrialBalanceReport): string {
572
+ const lines: string[] = [];
573
+ lines.push('"Account Number";"Account Name";"Debit";"Credit";"Balance"');
574
+
575
+ for (const entry of report.entries) {
576
+ lines.push(
577
+ `"${entry.accountNumber}";"${entry.accountName}";${entry.debitBalance};${entry.creditBalance};${entry.netBalance}`,
578
+ );
579
+ }
580
+
581
+ lines.push(
582
+ `"TOTAL";"";"${report.totalDebits}";"${report.totalCredits}";"""`,
583
+ );
584
+
585
+ return lines.join('\n');
586
+ }
587
+
588
+ /**
589
+ * Convert income statement to CSV
590
+ */
591
+ private incomeStatementToCSV(report: IIncomeStatement): string {
592
+ const lines: string[] = [];
593
+ lines.push('"Type";"Account Number";"Account Name";"Amount";"Percentage"');
594
+
595
+ lines.push('"REVENUE";"";"";"";""');
596
+ for (const entry of report.revenue) {
597
+ lines.push(
598
+ `"Revenue";"${entry.accountNumber}";"${entry.accountName}";${entry.amount};${entry.percentage?.toFixed(2)}%`,
599
+ );
600
+ }
601
+
602
+ lines.push(`"Total Revenue";"";"";"${report.totalRevenue}";"""`);
603
+ lines.push('"";"";"";"";""');
604
+
605
+ lines.push('"EXPENSES";"";"";"";""');
606
+ for (const entry of report.expenses) {
607
+ lines.push(
608
+ `"Expense";"${entry.accountNumber}";"${entry.accountName}";${entry.amount};${entry.percentage?.toFixed(2)}%`,
609
+ );
610
+ }
611
+
612
+ lines.push(`"Total Expenses";"";"";"${report.totalExpenses}";"""`);
613
+ lines.push('"";"";"";"";""');
614
+ lines.push(`"NET INCOME";"";"";"${report.netIncome}";"""`);
615
+
616
+ return lines.join('\n');
617
+ }
618
+
619
+ /**
620
+ * Convert balance sheet to CSV
621
+ */
622
+ private balanceSheetToCSV(report: IBalanceSheet): string {
623
+ const lines: string[] = [];
624
+ lines.push('"Category";"Account Number";"Account Name";"Amount"');
625
+
626
+ lines.push('"ASSETS";"";"";"";');
627
+ lines.push('"Current Assets";"";"";"";');
628
+ for (const entry of report.assets.current) {
629
+ lines.push(
630
+ `"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
631
+ );
632
+ }
633
+
634
+ lines.push('"Fixed Assets";"";"";"";');
635
+ for (const entry of report.assets.fixed) {
636
+ lines.push(
637
+ `"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
638
+ );
639
+ }
640
+
641
+ lines.push(`"Total Assets";"";"";"${report.assets.totalAssets}"`);
642
+ lines.push('"";"";"";"";');
643
+
644
+ lines.push('"LIABILITIES";"";"";"";');
645
+ lines.push('"Current Liabilities";"";"";"";');
646
+ for (const entry of report.liabilities.current) {
647
+ lines.push(
648
+ `"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
649
+ );
650
+ }
651
+
652
+ lines.push('"Long-term Liabilities";"";"";"";');
653
+ for (const entry of report.liabilities.longTerm) {
654
+ lines.push(
655
+ `"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
656
+ );
657
+ }
658
+
659
+ lines.push(
660
+ `"Total Liabilities";"";"";"${report.liabilities.totalLiabilities}"`,
661
+ );
662
+ lines.push('"";"";"";"";');
663
+
664
+ lines.push('"EQUITY";"";"";"";');
665
+ for (const entry of report.equity.entries) {
666
+ lines.push(
667
+ `"";"${entry.accountNumber}";"${entry.accountName}";${entry.amount}`,
668
+ );
669
+ }
670
+
671
+ lines.push(`"Total Equity";"";"";"${report.equity.totalEquity}"`);
672
+ lines.push('"";"";"";"";');
673
+ lines.push(
674
+ `"Total Liabilities + Equity";"";"";"${report.liabilities.totalLiabilities + report.equity.totalEquity}"`,
675
+ );
676
+
677
+ return lines.join('\n');
678
+ }
679
+
680
+ /**
681
+ * Export to DATEV format
682
+ */
683
+ public async exportToDATEV(params?: IReportParams): Promise<string> {
684
+ // DATEV format is specific to German accounting software
685
+ // This is a simplified implementation
686
+ const transactions = await Transaction.getInstances({
687
+ skrType: this.skrType,
688
+ status: 'posted',
689
+ });
690
+
691
+ const lines: string[] = [];
692
+
693
+ // DATEV header
694
+ lines.push('EXTF;510;21;"Buchungsstapel";1;;;;;;;;;;;;;;');
695
+
696
+ for (const transaction of transactions) {
697
+ const date = transaction.date
698
+ .toISOString()
699
+ .split('T')[0]
700
+ .replace(/-/g, '');
701
+ const line = [
702
+ transaction.amount.toFixed(2).replace('.', ','),
703
+ 'S',
704
+ 'EUR',
705
+ '',
706
+ '',
707
+ transaction.debitAccount,
708
+ transaction.creditAccount,
709
+ '',
710
+ date,
711
+ '',
712
+ transaction.description.substring(0, 60),
713
+ '',
714
+ ].join(';');
715
+
716
+ lines.push(line);
717
+ }
718
+
719
+ return lines.join('\n');
720
+ }
721
+ }