@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.
- package/dist_ts/index.d.ts +6 -0
- package/dist_ts/index.js +7 -1
- package/dist_ts/plugins.d.ts +8 -1
- package/dist_ts/plugins.js +10 -2
- package/dist_ts/skr.api.d.ts +70 -0
- package/dist_ts/skr.api.js +354 -3
- package/dist_ts/skr.classes.journalentry.d.ts +1 -0
- package/dist_ts/skr.classes.journalentry.js +55 -15
- package/dist_ts/skr.classes.ledger.d.ts +4 -0
- package/dist_ts/skr.classes.ledger.js +72 -1
- package/dist_ts/skr.classes.reports.js +56 -22
- package/dist_ts/skr.export.accounts.d.ts +53 -0
- package/dist_ts/skr.export.accounts.js +111 -0
- package/dist_ts/skr.export.balances.d.ts +59 -0
- package/dist_ts/skr.export.balances.js +205 -0
- package/dist_ts/skr.export.d.ts +110 -0
- package/dist_ts/skr.export.js +315 -0
- package/dist_ts/skr.export.ledger.d.ts +95 -0
- package/dist_ts/skr.export.ledger.js +164 -0
- package/dist_ts/skr.export.pdf.d.ts +82 -0
- package/dist_ts/skr.export.pdf.js +548 -0
- package/dist_ts/skr.invoice.adapter.d.ts +98 -0
- package/dist_ts/skr.invoice.adapter.js +476 -0
- package/dist_ts/skr.invoice.booking.d.ts +102 -0
- package/dist_ts/skr.invoice.booking.js +556 -0
- package/dist_ts/skr.invoice.entity.d.ts +287 -0
- package/dist_ts/skr.invoice.entity.js +2 -0
- package/dist_ts/skr.invoice.mapper.d.ts +69 -0
- package/dist_ts/skr.invoice.mapper.js +401 -0
- package/dist_ts/skr.invoice.storage.d.ts +140 -0
- package/dist_ts/skr.invoice.storage.js +529 -0
- package/dist_ts/skr.security.d.ts +65 -0
- package/dist_ts/skr.security.js +319 -0
- package/dist_ts/skr.types.d.ts +1 -0
- package/package.json +18 -12
- package/readme.md +461 -132
- package/ts/index.ts +6 -0
- package/ts/plugins.ts +22 -1
- package/ts/skr.api.ts +489 -2
- package/ts/skr.classes.journalentry.ts +63 -14
- package/ts/skr.classes.ledger.ts +85 -0
- package/ts/skr.classes.reports.ts +64 -21
- package/ts/skr.export.accounts.ts +154 -0
- package/ts/skr.export.balances.ts +270 -0
- package/ts/skr.export.ledger.ts +249 -0
- package/ts/skr.export.pdf.ts +601 -0
- package/ts/skr.export.ts +443 -0
- package/ts/skr.invoice.adapter.ts +581 -0
- package/ts/skr.invoice.booking.ts +738 -0
- package/ts/skr.invoice.entity.ts +351 -0
- package/ts/skr.invoice.mapper.ts +486 -0
- package/ts/skr.invoice.storage.ts +710 -0
- package/ts/skr.security.ts +405 -0
- 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
|
+
}
|