@fin.cx/skr 1.0.0 → 1.2.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 (54) hide show
  1. package/dist_ts/index.d.ts +6 -0
  2. package/dist_ts/index.js +7 -1
  3. package/dist_ts/plugins.d.ts +8 -1
  4. package/dist_ts/plugins.js +10 -2
  5. package/dist_ts/skr.api.d.ts +70 -0
  6. package/dist_ts/skr.api.js +354 -3
  7. package/dist_ts/skr.classes.journalentry.d.ts +1 -0
  8. package/dist_ts/skr.classes.journalentry.js +55 -15
  9. package/dist_ts/skr.classes.ledger.d.ts +4 -0
  10. package/dist_ts/skr.classes.ledger.js +72 -1
  11. package/dist_ts/skr.classes.reports.js +56 -22
  12. package/dist_ts/skr.export.accounts.d.ts +53 -0
  13. package/dist_ts/skr.export.accounts.js +111 -0
  14. package/dist_ts/skr.export.balances.d.ts +59 -0
  15. package/dist_ts/skr.export.balances.js +205 -0
  16. package/dist_ts/skr.export.d.ts +110 -0
  17. package/dist_ts/skr.export.js +315 -0
  18. package/dist_ts/skr.export.ledger.d.ts +95 -0
  19. package/dist_ts/skr.export.ledger.js +164 -0
  20. package/dist_ts/skr.export.pdf.d.ts +82 -0
  21. package/dist_ts/skr.export.pdf.js +548 -0
  22. package/dist_ts/skr.invoice.adapter.d.ts +98 -0
  23. package/dist_ts/skr.invoice.adapter.js +476 -0
  24. package/dist_ts/skr.invoice.booking.d.ts +102 -0
  25. package/dist_ts/skr.invoice.booking.js +556 -0
  26. package/dist_ts/skr.invoice.entity.d.ts +287 -0
  27. package/dist_ts/skr.invoice.entity.js +2 -0
  28. package/dist_ts/skr.invoice.mapper.d.ts +69 -0
  29. package/dist_ts/skr.invoice.mapper.js +401 -0
  30. package/dist_ts/skr.invoice.storage.d.ts +140 -0
  31. package/dist_ts/skr.invoice.storage.js +529 -0
  32. package/dist_ts/skr.security.d.ts +65 -0
  33. package/dist_ts/skr.security.js +319 -0
  34. package/dist_ts/skr.types.d.ts +1 -0
  35. package/package.json +18 -12
  36. package/readme.md +461 -132
  37. package/ts/index.ts +6 -0
  38. package/ts/plugins.ts +22 -1
  39. package/ts/skr.api.ts +489 -2
  40. package/ts/skr.classes.journalentry.ts +63 -14
  41. package/ts/skr.classes.ledger.ts +85 -0
  42. package/ts/skr.classes.reports.ts +64 -21
  43. package/ts/skr.export.accounts.ts +154 -0
  44. package/ts/skr.export.balances.ts +270 -0
  45. package/ts/skr.export.ledger.ts +249 -0
  46. package/ts/skr.export.pdf.ts +601 -0
  47. package/ts/skr.export.ts +443 -0
  48. package/ts/skr.invoice.adapter.ts +581 -0
  49. package/ts/skr.invoice.booking.ts +738 -0
  50. package/ts/skr.invoice.entity.ts +351 -0
  51. package/ts/skr.invoice.mapper.ts +486 -0
  52. package/ts/skr.invoice.storage.ts +710 -0
  53. package/ts/skr.security.ts +405 -0
  54. package/ts/skr.types.ts +1 -0
@@ -0,0 +1,486 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { TSKRType } from './skr.types.js';
3
+ import type {
4
+ IInvoice,
5
+ IInvoiceLine,
6
+ IBookingRules,
7
+ TTaxScenario,
8
+ IVATCategory
9
+ } from './skr.invoice.entity.js';
10
+
11
+ /**
12
+ * Maps invoice data to SKR accounts
13
+ * Handles both SKR03 and SKR04 account mappings
14
+ */
15
+ export class SKRInvoiceMapper {
16
+ private logger: plugins.smartlog.ConsoleLog;
17
+ private skrType: TSKRType;
18
+
19
+ // SKR03 account mappings
20
+ private readonly SKR03_ACCOUNTS = {
21
+ // Control accounts
22
+ vendorControl: '1600', // Verbindlichkeiten aus Lieferungen und Leistungen
23
+ customerControl: '1200', // Forderungen aus Lieferungen und Leistungen
24
+
25
+ // VAT accounts
26
+ inputVAT19: '1576', // Abziehbare Vorsteuer 19%
27
+ inputVAT7: '1571', // Abziehbare Vorsteuer 7%
28
+ outputVAT19: '1776', // Umsatzsteuer 19%
29
+ outputVAT7: '1771', // Umsatzsteuer 7%
30
+ reverseChargeVAT: '1577', // Abziehbare Vorsteuer §13b UStG
31
+ reverseChargePayable: '1787', // Umsatzsteuer §13b UStG
32
+
33
+ // Default expense/revenue accounts
34
+ defaultExpense: '4610', // Werbekosten
35
+ defaultRevenue: '8400', // Erlöse 19% USt
36
+ revenueReduced: '8300', // Erlöse 7% USt
37
+ revenueTaxFree: '8120', // Steuerfreie Umsätze
38
+
39
+ // Common expense accounts by category
40
+ materialExpense: '5000', // Aufwendungen für Roh-, Hilfs- und Betriebsstoffe
41
+ merchandiseExpense: '5400', // Aufwendungen für Waren
42
+ personnelExpense: '6000', // Löhne und Gehälter
43
+ rentExpense: '4200', // Miete
44
+ officeExpense: '4930', // Bürobedarf
45
+ travelExpense: '4670', // Reisekosten
46
+ vehicleExpense: '4530', // Kfz-Kosten
47
+
48
+ // Skonto accounts
49
+ skontoExpense: '4736', // Erhaltene Skonti 19% USt
50
+ skontoRevenue: '8736', // Gewährte Skonti 19% USt
51
+
52
+ // Intra-EU accounts
53
+ intraEUAcquisition: '8125', // Steuerfreie innergemeinschaftliche Erwerbe
54
+ intraEUSupply: '8125' // Steuerfreie innergemeinschaftliche Lieferungen
55
+ };
56
+
57
+ // SKR04 account mappings
58
+ private readonly SKR04_ACCOUNTS = {
59
+ // Control accounts
60
+ vendorControl: '3300', // Verbindlichkeiten aus Lieferungen und Leistungen
61
+ customerControl: '1400', // Forderungen aus Lieferungen und Leistungen
62
+
63
+ // VAT accounts
64
+ inputVAT19: '1406', // Abziehbare Vorsteuer 19%
65
+ inputVAT7: '1401', // Abziehbare Vorsteuer 7%
66
+ outputVAT19: '3806', // Umsatzsteuer 19%
67
+ outputVAT7: '3801', // Umsatzsteuer 7%
68
+ reverseChargeVAT: '1407', // Abziehbare Vorsteuer §13b UStG
69
+ reverseChargePayable: '3837', // Umsatzsteuer §13b UStG
70
+
71
+ // Default expense/revenue accounts
72
+ defaultExpense: '6300', // Sonstige betriebliche Aufwendungen
73
+ defaultRevenue: '4400', // Erlöse 19% USt
74
+ revenueReduced: '4300', // Erlöse 7% USt
75
+ revenueTaxFree: '4120', // Steuerfreie Umsätze
76
+
77
+ // Common expense accounts by category
78
+ materialExpense: '5000', // Aufwendungen für Roh-, Hilfs- und Betriebsstoffe
79
+ merchandiseExpense: '5400', // Aufwendungen für Waren
80
+ personnelExpense: '6000', // Löhne
81
+ rentExpense: '6310', // Miete
82
+ officeExpense: '6815', // Bürobedarf
83
+ travelExpense: '6670', // Reisekosten
84
+ vehicleExpense: '6530', // Kfz-Kosten
85
+
86
+ // Skonto accounts
87
+ skontoExpense: '4736', // Erhaltene Skonti 19% USt
88
+ skontoRevenue: '8736', // Gewährte Skonti 19% USt
89
+
90
+ // Intra-EU accounts
91
+ intraEUAcquisition: '4125', // Steuerfreie innergemeinschaftliche Erwerbe
92
+ intraEUSupply: '4125' // Steuerfreie innergemeinschaftliche Lieferungen
93
+ };
94
+
95
+ // Product category to account mappings
96
+ private readonly CATEGORY_MAPPINGS: Record<string, { skr03: string; skr04: string }> = {
97
+ 'MATERIAL': { skr03: '5000', skr04: '5000' },
98
+ 'MERCHANDISE': { skr03: '5400', skr04: '5400' },
99
+ 'SERVICE': { skr03: '4610', skr04: '6300' },
100
+ 'OFFICE': { skr03: '4930', skr04: '6815' },
101
+ 'IT': { skr03: '4940', skr04: '6825' },
102
+ 'TRAVEL': { skr03: '4670', skr04: '6670' },
103
+ 'VEHICLE': { skr03: '4530', skr04: '6530' },
104
+ 'RENT': { skr03: '4200', skr04: '6310' },
105
+ 'UTILITIES': { skr03: '4240', skr04: '6320' },
106
+ 'INSURANCE': { skr03: '4360', skr04: '6420' },
107
+ 'MARKETING': { skr03: '4610', skr04: '6600' },
108
+ 'CONSULTING': { skr03: '4640', skr04: '6650' },
109
+ 'LEGAL': { skr03: '4790', skr04: '6790' },
110
+ 'TELECOMMUNICATION': { skr03: '4920', skr04: '6805' }
111
+ };
112
+
113
+ constructor(skrType: TSKRType) {
114
+ this.skrType = skrType;
115
+ this.logger = new plugins.smartlog.ConsoleLog();
116
+ }
117
+
118
+ /**
119
+ * Get account mappings for current SKR type
120
+ */
121
+ private getAccounts() {
122
+ return this.skrType === 'SKR03' ? this.SKR03_ACCOUNTS : this.SKR04_ACCOUNTS;
123
+ }
124
+
125
+ /**
126
+ * Map invoice to booking rules
127
+ */
128
+ public mapInvoiceToSKR(
129
+ invoice: IInvoice,
130
+ customMappings?: Partial<IBookingRules>
131
+ ): IBookingRules {
132
+ const accounts = this.getAccounts();
133
+ const taxScenario = invoice.taxScenario || 'domestic_taxed';
134
+
135
+ // Base booking rules
136
+ const bookingRules: IBookingRules = {
137
+ skrType: this.skrType,
138
+
139
+ // Control accounts
140
+ vendorControlAccount: customMappings?.vendorControlAccount || accounts.vendorControl,
141
+ customerControlAccount: customMappings?.customerControlAccount || accounts.customerControl,
142
+
143
+ // VAT accounts
144
+ vatAccounts: {
145
+ inputVAT19: accounts.inputVAT19,
146
+ inputVAT7: accounts.inputVAT7,
147
+ outputVAT19: accounts.outputVAT19,
148
+ outputVAT7: accounts.outputVAT7,
149
+ reverseChargeVAT: accounts.reverseChargeVAT
150
+ },
151
+
152
+ // Default accounts
153
+ defaultExpenseAccount: accounts.defaultExpense,
154
+ defaultRevenueAccount: accounts.defaultRevenue,
155
+
156
+ // Skonto
157
+ skontoMethod: customMappings?.skontoMethod || 'gross',
158
+ skontoExpenseAccount: accounts.skontoExpense,
159
+ skontoRevenueAccount: accounts.skontoRevenue,
160
+
161
+ // Custom mappings
162
+ productCategoryMapping: customMappings?.productCategoryMapping || {},
163
+ vendorMapping: customMappings?.vendorMapping || {},
164
+ customerMapping: customMappings?.customerMapping || {}
165
+ };
166
+
167
+ return bookingRules;
168
+ }
169
+
170
+ /**
171
+ * Map invoice line to SKR account
172
+ */
173
+ public mapInvoiceLineToAccount(
174
+ line: IInvoiceLine,
175
+ invoice: IInvoice,
176
+ bookingRules: IBookingRules
177
+ ): string {
178
+ // Check if account is already specified
179
+ if (line.accountNumber) {
180
+ return line.accountNumber;
181
+ }
182
+
183
+ // For revenue (outbound invoices)
184
+ if (invoice.direction === 'outbound') {
185
+ return this.mapRevenueAccount(line, invoice, bookingRules);
186
+ }
187
+
188
+ // For expenses (inbound invoices)
189
+ return this.mapExpenseAccount(line, invoice, bookingRules);
190
+ }
191
+
192
+ /**
193
+ * Map revenue account based on VAT rate and scenario
194
+ */
195
+ private mapRevenueAccount(
196
+ line: IInvoiceLine,
197
+ invoice: IInvoice,
198
+ bookingRules: IBookingRules
199
+ ): string {
200
+ const accounts = this.getAccounts();
201
+ const vatRate = line.vatCategory.rate;
202
+
203
+ // Check tax scenario
204
+ switch (invoice.taxScenario) {
205
+ case 'intra_eu_supply':
206
+ return accounts.intraEUSupply;
207
+ case 'export':
208
+ case 'domestic_exempt':
209
+ return accounts.revenueTaxFree;
210
+ case 'domestic_taxed':
211
+ default:
212
+ // Map by VAT rate
213
+ if (vatRate === 19) {
214
+ return accounts.defaultRevenue;
215
+ } else if (vatRate === 7) {
216
+ return accounts.revenueReduced;
217
+ } else if (vatRate === 0) {
218
+ return accounts.revenueTaxFree;
219
+ }
220
+ return accounts.defaultRevenue;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Map expense account based on product category and vendor
226
+ */
227
+ private mapExpenseAccount(
228
+ line: IInvoiceLine,
229
+ invoice: IInvoice,
230
+ bookingRules: IBookingRules
231
+ ): string {
232
+ const accounts = this.getAccounts();
233
+
234
+ // Check vendor-specific mapping
235
+ const vendorId = invoice.supplier.id;
236
+ if (bookingRules.vendorMapping && bookingRules.vendorMapping[vendorId]) {
237
+ return bookingRules.vendorMapping[vendorId];
238
+ }
239
+
240
+ // Try to determine category from line description
241
+ const category = this.detectProductCategory(line.description);
242
+ if (category) {
243
+ const mapping = this.CATEGORY_MAPPINGS[category];
244
+ if (mapping) {
245
+ return this.skrType === 'SKR03' ? mapping.skr03 : mapping.skr04;
246
+ }
247
+ }
248
+
249
+ // Check product category mapping
250
+ if (line.productCode && bookingRules.productCategoryMapping) {
251
+ const mappedAccount = bookingRules.productCategoryMapping[line.productCode];
252
+ if (mappedAccount) {
253
+ return mappedAccount;
254
+ }
255
+ }
256
+
257
+ // Default expense account
258
+ return bookingRules.defaultExpenseAccount;
259
+ }
260
+
261
+ /**
262
+ * Detect product category from description
263
+ */
264
+ private detectProductCategory(description: string): string | undefined {
265
+ const lowerDesc = description.toLowerCase();
266
+
267
+ const categoryKeywords: Record<string, string[]> = {
268
+ 'MATERIAL': ['material', 'rohstoff', 'raw material', 'component'],
269
+ 'MERCHANDISE': ['ware', 'merchandise', 'product', 'artikel'],
270
+ 'SERVICE': ['service', 'dienstleistung', 'beratung', 'support'],
271
+ 'OFFICE': ['büro', 'office', 'papier', 'stationery'],
272
+ 'IT': ['software', 'hardware', 'computer', 'lizenz', 'license'],
273
+ 'TRAVEL': ['reise', 'travel', 'hotel', 'flug', 'flight'],
274
+ 'VEHICLE': ['kfz', 'vehicle', 'auto', 'benzin', 'fuel'],
275
+ 'RENT': ['miete', 'rent', 'lease', 'pacht'],
276
+ 'UTILITIES': ['strom', 'wasser', 'gas', 'energie', 'electricity', 'water'],
277
+ 'INSURANCE': ['versicherung', 'insurance'],
278
+ 'MARKETING': ['werbung', 'marketing', 'advertising', 'kampagne'],
279
+ 'CONSULTING': ['beratung', 'consulting', 'advisory'],
280
+ 'LEGAL': ['rechts', 'legal', 'anwalt', 'lawyer', 'notar'],
281
+ 'TELECOMMUNICATION': ['telefon', 'internet', 'mobilfunk', 'telekom']
282
+ };
283
+
284
+ for (const [category, keywords] of Object.entries(categoryKeywords)) {
285
+ if (keywords.some(keyword => lowerDesc.includes(keyword))) {
286
+ return category;
287
+ }
288
+ }
289
+
290
+ return undefined;
291
+ }
292
+
293
+ /**
294
+ * Get VAT account for given VAT category and rate
295
+ */
296
+ public getVATAccount(
297
+ vatCategory: IVATCategory,
298
+ direction: 'input' | 'output',
299
+ taxScenario: TTaxScenario
300
+ ): string {
301
+ const accounts = this.getAccounts();
302
+
303
+ // Handle reverse charge
304
+ if (taxScenario === 'reverse_charge' || vatCategory.code === 'AE') {
305
+ return direction === 'input'
306
+ ? accounts.reverseChargeVAT
307
+ : accounts.reverseChargePayable;
308
+ }
309
+
310
+ // Standard VAT accounts by rate
311
+ if (direction === 'input') {
312
+ if (vatCategory.rate === 19) {
313
+ return accounts.inputVAT19;
314
+ } else if (vatCategory.rate === 7) {
315
+ return accounts.inputVAT7;
316
+ }
317
+ } else {
318
+ if (vatCategory.rate === 19) {
319
+ return accounts.outputVAT19;
320
+ } else if (vatCategory.rate === 7) {
321
+ return accounts.outputVAT7;
322
+ }
323
+ }
324
+
325
+ // Default to 19% if rate is not standard
326
+ return direction === 'input' ? accounts.inputVAT19 : accounts.outputVAT19;
327
+ }
328
+
329
+ /**
330
+ * Get control account for party
331
+ */
332
+ public getControlAccount(
333
+ invoice: IInvoice,
334
+ bookingRules: IBookingRules
335
+ ): string {
336
+ if (invoice.direction === 'inbound') {
337
+ // Check vendor-specific control account
338
+ const vendorId = invoice.supplier.id;
339
+ if (bookingRules.vendorMapping && bookingRules.vendorMapping[vendorId]) {
340
+ const customAccount = bookingRules.vendorMapping[vendorId];
341
+ // Check if it's a control account (starts with 16 for SKR03 or 33 for SKR04)
342
+ if (this.isControlAccount(customAccount)) {
343
+ return customAccount;
344
+ }
345
+ }
346
+ return bookingRules.vendorControlAccount;
347
+ } else {
348
+ // Check customer-specific control account
349
+ const customerId = invoice.customer.id;
350
+ if (bookingRules.customerMapping && bookingRules.customerMapping[customerId]) {
351
+ const customAccount = bookingRules.customerMapping[customerId];
352
+ // Check if it's a control account (starts with 12 for SKR03 or 14 for SKR04)
353
+ if (this.isControlAccount(customAccount)) {
354
+ return customAccount;
355
+ }
356
+ }
357
+ return bookingRules.customerControlAccount;
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Check if account is a control account
363
+ */
364
+ private isControlAccount(accountNumber: string): boolean {
365
+ if (this.skrType === 'SKR03') {
366
+ return accountNumber.startsWith('12') || accountNumber.startsWith('16');
367
+ } else {
368
+ return accountNumber.startsWith('14') || accountNumber.startsWith('33');
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Get skonto accounts
374
+ */
375
+ public getSkontoAccounts(invoice: IInvoice): {
376
+ skontoAccount: string;
377
+ vatCorrectionAccount: string;
378
+ } {
379
+ const accounts = this.getAccounts();
380
+
381
+ if (invoice.direction === 'inbound') {
382
+ // Received skonto (expense reduction)
383
+ return {
384
+ skontoAccount: accounts.skontoExpense,
385
+ vatCorrectionAccount: accounts.inputVAT19 // VAT correction
386
+ };
387
+ } else {
388
+ // Granted skonto (revenue reduction)
389
+ return {
390
+ skontoAccount: accounts.skontoRevenue,
391
+ vatCorrectionAccount: accounts.outputVAT19 // VAT correction
392
+ };
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Validate account number format
398
+ */
399
+ public validateAccountNumber(accountNumber: string): boolean {
400
+ // SKR accounts are typically 4 digits, sometimes with sub-accounts
401
+ const accountPattern = /^\d{4}(\d{0,2})?$/;
402
+ return accountPattern.test(accountNumber);
403
+ }
404
+
405
+ /**
406
+ * Get account description
407
+ */
408
+ public getAccountDescription(accountNumber: string): string {
409
+ // This would typically look up from a complete SKR account database
410
+ // For now, return a basic description
411
+ const commonAccounts: Record<string, string> = {
412
+ // SKR03
413
+ '1200': 'Forderungen aus Lieferungen und Leistungen',
414
+ '1600': 'Verbindlichkeiten aus Lieferungen und Leistungen',
415
+ '1576': 'Abziehbare Vorsteuer 19%',
416
+ '1571': 'Abziehbare Vorsteuer 7%',
417
+ '1776': 'Umsatzsteuer 19%',
418
+ '1771': 'Umsatzsteuer 7%',
419
+ '4610': 'Werbekosten',
420
+ '8400': 'Erlöse 19% USt',
421
+ '8300': 'Erlöse 7% USt',
422
+ // SKR04
423
+ '1400': 'Forderungen aus Lieferungen und Leistungen',
424
+ '3300': 'Verbindlichkeiten aus Lieferungen und Leistungen',
425
+ '1406': 'Abziehbare Vorsteuer 19%',
426
+ '1401': 'Abziehbare Vorsteuer 7%',
427
+ '3806': 'Umsatzsteuer 19%',
428
+ '3801': 'Umsatzsteuer 7%',
429
+ '6300': 'Sonstige betriebliche Aufwendungen',
430
+ '4400': 'Erlöse 19% USt',
431
+ '4300': 'Erlöse 7% USt'
432
+ };
433
+
434
+ return commonAccounts[accountNumber] || `Account ${accountNumber}`;
435
+ }
436
+
437
+ /**
438
+ * Calculate booking confidence score
439
+ */
440
+ public calculateConfidence(
441
+ invoice: IInvoice,
442
+ bookingRules: IBookingRules
443
+ ): number {
444
+ let confidence = 100;
445
+
446
+ // Reduce confidence for missing or uncertain mappings
447
+ invoice.lines.forEach(line => {
448
+ if (!line.accountNumber) {
449
+ confidence -= 10; // No explicit account mapping
450
+ }
451
+
452
+ if (!line.productCode) {
453
+ confidence -= 5; // No product code for mapping
454
+ }
455
+ });
456
+
457
+ // Reduce confidence for complex tax scenarios
458
+ if (invoice.taxScenario === 'reverse_charge' ||
459
+ invoice.taxScenario === 'intra_eu_acquisition') {
460
+ confidence -= 15;
461
+ }
462
+
463
+ // Reduce confidence for mixed VAT rates
464
+ if (invoice.vatBreakdown.length > 1) {
465
+ confidence -= 10;
466
+ }
467
+
468
+ // Reduce confidence if no vendor/customer mapping exists
469
+ if (invoice.direction === 'inbound') {
470
+ if (!bookingRules.vendorMapping?.[invoice.supplier.id]) {
471
+ confidence -= 10;
472
+ }
473
+ } else {
474
+ if (!bookingRules.customerMapping?.[invoice.customer.id]) {
475
+ confidence -= 10;
476
+ }
477
+ }
478
+
479
+ // Reduce confidence for credit notes
480
+ if (invoice.invoiceTypeCode === '381') {
481
+ confidence -= 10;
482
+ }
483
+
484
+ return Math.max(0, confidence);
485
+ }
486
+ }