@fin.cx/skr 1.2.0 → 1.2.2
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/{npmextra.json → .smartconfig.json} +12 -6
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/index.d.ts +21 -15
- package/dist_ts/index.js +16 -16
- package/dist_ts/plugins.d.ts +15 -5
- package/dist_ts/plugins.js +42 -6
- package/dist_ts/skr.api.js +20 -13
- package/dist_ts/skr.classes.account.d.ts +36 -3
- package/dist_ts/skr.classes.account.js +366 -210
- package/dist_ts/skr.classes.chartofaccounts.d.ts +1 -1
- package/dist_ts/skr.classes.chartofaccounts.js +13 -6
- package/dist_ts/skr.classes.journalentry.d.ts +9 -4
- package/dist_ts/skr.classes.journalentry.js +430 -309
- package/dist_ts/skr.classes.ledger.js +7 -1
- package/dist_ts/skr.classes.reports.js +4 -1
- package/dist_ts/skr.classes.transaction.d.ts +9 -4
- package/dist_ts/skr.classes.transaction.js +315 -244
- package/dist_ts/skr.export.accounts.js +3 -2
- package/dist_ts/skr.export.balances.js +4 -2
- package/dist_ts/skr.export.js +6 -3
- package/dist_ts/skr.export.ledger.js +4 -3
- package/dist_ts/skr.export.pdf.js +6 -3
- package/dist_ts/skr.invoice.adapter.d.ts +2 -0
- package/dist_ts/skr.invoice.adapter.js +22 -10
- package/dist_ts/skr.invoice.booking.js +52 -25
- package/dist_ts/skr.invoice.mapper.js +84 -82
- package/dist_ts/skr.invoice.storage.js +10 -5
- package/dist_ts/skr.postingkeys.d.ts +56 -0
- package/dist_ts/skr.postingkeys.js +196 -0
- package/dist_ts/skr.security.js +24 -21
- package/dist_ts/skr.types.d.ts +18 -0
- package/dist_ts/skr03.data.js +3 -1
- package/dist_ts/skr04.data.js +3 -1
- package/license.md +21 -0
- package/package.json +31 -23
- package/readme.hints.md +54 -1
- package/readme.md +215 -632
- package/readme.plan.md +1 -1
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/index.ts +43 -15
- package/ts/plugins.ts +52 -15
- package/ts/skr.api.ts +9 -5
- package/ts/skr.classes.account.ts +144 -23
- package/ts/skr.classes.chartofaccounts.ts +8 -3
- package/ts/skr.classes.journalentry.ts +117 -24
- package/ts/skr.classes.ledger.ts +4 -0
- package/ts/skr.classes.reports.ts +22 -2
- package/ts/skr.classes.transaction.ts +40 -22
- package/ts/skr.export.pdf.ts +4 -3
- package/ts/skr.invoice.adapter.ts +31 -11
- package/ts/skr.invoice.booking.ts +54 -30
- package/ts/skr.invoice.storage.ts +3 -2
- package/ts/skr.postingkeys.ts +252 -0
- package/ts/skr.security.ts +23 -22
- package/ts/skr.types.ts +26 -0
- package/ts/skr03.data.ts +2 -0
- package/ts/skr04.data.ts +2 -0
package/readme.plan.md
CHANGED
|
@@ -18,7 +18,7 @@ TypeScript module implementing SKR03 and SKR04 German accounting standards for d
|
|
|
18
18
|
- [ ] @push.rocks/smartdata
|
|
19
19
|
- [ ] @git.zone/tstest (dev dependency)
|
|
20
20
|
- [ ] Create tsconfig.json based on @push.rocks/smarthash pattern
|
|
21
|
-
- [ ] Create
|
|
21
|
+
- [ ] Create .smartconfig.json for additional configuration
|
|
22
22
|
- [ ] Create .gitignore file
|
|
23
23
|
- [ ] Create directory structure
|
|
24
24
|
- [ ] ts/ directory for source code
|
package/ts/index.ts
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
export * from './skr.types.js';
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
export { Account } from './skr.classes.account.js';
|
|
3
|
+
export { Transaction } from './skr.classes.transaction.js';
|
|
4
|
+
export { JournalEntry } from './skr.classes.journalentry.js';
|
|
5
|
+
export { ChartOfAccounts } from './skr.classes.chartofaccounts.js';
|
|
6
|
+
export { Ledger } from './skr.classes.ledger.js';
|
|
7
|
+
export { Reports } from './skr.classes.reports.js';
|
|
8
|
+
export { SkrApi } from './skr.api.js';
|
|
9
|
+
export { SKR03_ACCOUNTS, SKR03_ACCOUNT_CLASSES } from './skr03.data.js';
|
|
10
|
+
export { SKR04_ACCOUNTS, SKR04_ACCOUNT_CLASSES } from './skr04.data.js';
|
|
11
|
+
export { SkrExport } from './skr.export.js';
|
|
12
|
+
export type {
|
|
13
|
+
IExportOptions,
|
|
14
|
+
IExportMetadata,
|
|
15
|
+
IBagItManifest,
|
|
16
|
+
IDocumentIndex,
|
|
17
|
+
} from './skr.export.js';
|
|
18
|
+
export { LedgerExporter } from './skr.export.ledger.js';
|
|
19
|
+
export type {
|
|
20
|
+
ITransactionDataExport,
|
|
21
|
+
IJournalEntryExport,
|
|
22
|
+
IJournalEntryLineExport,
|
|
23
|
+
ILedgerEntry,
|
|
24
|
+
ILedgerLine,
|
|
25
|
+
IDocumentRef,
|
|
26
|
+
} from './skr.export.ledger.js';
|
|
27
|
+
export { AccountsExporter } from './skr.export.accounts.js';
|
|
28
|
+
export type {
|
|
29
|
+
IAccountDataExport,
|
|
30
|
+
IAccountExportRow,
|
|
31
|
+
} from './skr.export.accounts.js';
|
|
32
|
+
export { BalancesExporter } from './skr.export.balances.js';
|
|
33
|
+
export type {
|
|
34
|
+
IAccountBalanceExport,
|
|
35
|
+
IBalanceExportRow,
|
|
36
|
+
} from './skr.export.balances.js';
|
|
37
|
+
export { PdfReportGenerator } from './skr.export.pdf.js';
|
|
38
|
+
export type { IPdfReportOptions } from './skr.export.pdf.js';
|
|
39
|
+
export { SecurityManager } from './skr.security.js';
|
|
40
|
+
export type {
|
|
41
|
+
ISigningOptions,
|
|
42
|
+
ISignatureResult,
|
|
43
|
+
ITimestampResponse,
|
|
44
|
+
} from './skr.security.js';
|
package/ts/plugins.ts
CHANGED
|
@@ -3,26 +3,63 @@ import * as smartdata from '@push.rocks/smartdata';
|
|
|
3
3
|
import * as smartunique from '@push.rocks/smartunique';
|
|
4
4
|
import * as smarttime from '@push.rocks/smarttime';
|
|
5
5
|
import * as smartlog from '@push.rocks/smartlog';
|
|
6
|
-
import * as
|
|
6
|
+
import * as smartfsModule from '@push.rocks/smartfs';
|
|
7
7
|
import * as smarthash from '@push.rocks/smarthash';
|
|
8
8
|
import * as smartpath from '@push.rocks/smartpath';
|
|
9
|
-
import * as
|
|
9
|
+
import * as path from 'path';
|
|
10
10
|
|
|
11
11
|
// third party
|
|
12
|
-
import * as nodeForge from 'node-forge';
|
|
13
12
|
import { MerkleTree } from 'merkletreejs';
|
|
14
|
-
import * as einvoice from '@fin.cx/einvoice';
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const smartfs = new smartfsModule.SmartFs(
|
|
15
|
+
new smartfsModule.SmartFsProviderNode(),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const smartfile = {
|
|
19
|
+
fs: {
|
|
20
|
+
ensureDir: async (dirPath: string): Promise<void> => {
|
|
21
|
+
await smartfs.directory(dirPath).create();
|
|
22
|
+
},
|
|
23
|
+
toBuffer: async (filePath: string): Promise<Buffer> => {
|
|
24
|
+
return (await smartfs.file(filePath).read()) as Buffer;
|
|
25
|
+
},
|
|
26
|
+
toStringSync: async (filePath: string): Promise<string> => {
|
|
27
|
+
return (await smartfs.file(filePath).encoding('utf8').read()) as string;
|
|
28
|
+
},
|
|
29
|
+
fileExists: async (filePath: string): Promise<boolean> => {
|
|
30
|
+
return await smartfs.file(filePath).exists();
|
|
31
|
+
},
|
|
32
|
+
listFileTree: async (dirPath: string, pattern: string): Promise<string[]> => {
|
|
33
|
+
const suffix = pattern.replace(/^\*\*\/\*/, '');
|
|
34
|
+
try {
|
|
35
|
+
const entries = await smartfs.directory(dirPath).recursive().list();
|
|
36
|
+
return entries
|
|
37
|
+
.filter((entry) => entry.isFile && entry.path.endsWith(suffix))
|
|
38
|
+
.map((entry) => path.relative(dirPath, entry.path));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof Error && error.message.includes('ENOENT')) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
memory: {
|
|
48
|
+
toFs: async (content: string | Buffer, filePath: string): Promise<void> => {
|
|
49
|
+
await smartfs.directory(path.dirname(filePath)).create();
|
|
50
|
+
await smartfs.file(filePath).write(content);
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
smartdata,
|
|
57
|
+
smartunique,
|
|
58
|
+
smarttime,
|
|
59
|
+
smartlog,
|
|
60
|
+
smartfs,
|
|
61
|
+
smartfile,
|
|
62
|
+
smarthash,
|
|
63
|
+
smartpath,
|
|
26
64
|
MerkleTree,
|
|
27
|
-
einvoice
|
|
28
65
|
};
|
package/ts/skr.api.ts
CHANGED
|
@@ -534,6 +534,10 @@ export class SkrApi {
|
|
|
534
534
|
*/
|
|
535
535
|
private async generatePdfReports(exporter: SkrExport, options: IExportOptions): Promise<void> {
|
|
536
536
|
if (!this.reports) throw new Error('Reports not initialized');
|
|
537
|
+
const skrType = this.currentSKRType;
|
|
538
|
+
if (!skrType) {
|
|
539
|
+
throw new Error('API not initialized. Call initialize() first.');
|
|
540
|
+
}
|
|
537
541
|
|
|
538
542
|
const pdfOptions: IPdfReportOptions = {
|
|
539
543
|
companyName: options.companyInfo?.name || 'Unternehmen',
|
|
@@ -554,19 +558,19 @@ export class SkrApi {
|
|
|
554
558
|
const trialBalance = await this.reports.getTrialBalance({
|
|
555
559
|
dateFrom: options.dateFrom,
|
|
556
560
|
dateTo: options.dateTo,
|
|
557
|
-
skrType
|
|
561
|
+
skrType,
|
|
558
562
|
});
|
|
559
563
|
|
|
560
564
|
const incomeStatement = await this.reports.getIncomeStatement({
|
|
561
565
|
dateFrom: options.dateFrom,
|
|
562
566
|
dateTo: options.dateTo,
|
|
563
|
-
skrType
|
|
567
|
+
skrType,
|
|
564
568
|
});
|
|
565
569
|
|
|
566
570
|
const balanceSheet = await this.reports.getBalanceSheet({
|
|
567
571
|
dateFrom: options.dateFrom,
|
|
568
572
|
dateTo: options.dateTo,
|
|
569
|
-
skrType
|
|
573
|
+
skrType,
|
|
570
574
|
});
|
|
571
575
|
|
|
572
576
|
// Generate PDFs
|
|
@@ -702,7 +706,7 @@ export class SkrApi {
|
|
|
702
706
|
const transaction = await this.postTransaction(transactions[i]);
|
|
703
707
|
results.push(transaction);
|
|
704
708
|
} catch (error) {
|
|
705
|
-
errors.push({ index: i, error: error.message });
|
|
709
|
+
errors.push({ index: i, error: error instanceof Error ? error.message : String(error) });
|
|
706
710
|
}
|
|
707
711
|
}
|
|
708
712
|
|
|
@@ -735,7 +739,7 @@ export class SkrApi {
|
|
|
735
739
|
const account = await this.createAccount(accounts[i]);
|
|
736
740
|
results.push(account);
|
|
737
741
|
} catch (error) {
|
|
738
|
-
errors.push({ index: i, error: error.message });
|
|
742
|
+
errors.push({ index: i, error: error instanceof Error ? error.message : String(error) });
|
|
739
743
|
}
|
|
740
744
|
}
|
|
741
745
|
|
|
@@ -2,65 +2,86 @@ import * as plugins from './plugins.js';
|
|
|
2
2
|
import { getDb, getDbSync } from './skr.database.js';
|
|
3
3
|
import type { TAccountType, TSKRType, IAccountData } from './skr.types.js';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
declare abstract class SmartDataDbDocBase {
|
|
6
|
+
public save(): Promise<void>;
|
|
7
|
+
public delete(): Promise<void>;
|
|
8
|
+
public static getInstance<T>(
|
|
9
|
+
this: new (...args: any[]) => T,
|
|
10
|
+
query: Record<string, any>,
|
|
11
|
+
): Promise<T | null>;
|
|
12
|
+
public static getInstances<T>(
|
|
13
|
+
this: new (...args: any[]) => T,
|
|
14
|
+
query: Record<string, any>,
|
|
15
|
+
): Promise<T[]>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SmartDataDbDoc = plugins.smartdata.SmartDataDbDoc as unknown as typeof SmartDataDbDocBase;
|
|
19
|
+
const Collection = plugins.smartdata.Collection as any;
|
|
20
|
+
const svDb = plugins.smartdata.svDb as any;
|
|
21
|
+
const unI = plugins.smartdata.unI as any;
|
|
22
|
+
const index = plugins.smartdata.index as any;
|
|
23
|
+
const searchable = plugins.smartdata.searchable as any;
|
|
6
24
|
|
|
7
|
-
@
|
|
8
|
-
export class Account extends SmartDataDbDoc
|
|
25
|
+
@Collection(() => getDbSync())
|
|
26
|
+
export class Account extends SmartDataDbDoc {
|
|
9
27
|
@unI()
|
|
10
|
-
public id
|
|
28
|
+
public id!: string;
|
|
11
29
|
|
|
12
30
|
@svDb()
|
|
13
31
|
@index()
|
|
14
|
-
public accountNumber
|
|
32
|
+
public accountNumber!: string;
|
|
15
33
|
|
|
16
34
|
@svDb()
|
|
17
35
|
@searchable()
|
|
18
|
-
public accountName
|
|
36
|
+
public accountName!: string;
|
|
19
37
|
|
|
20
38
|
@svDb()
|
|
21
39
|
@index()
|
|
22
|
-
public accountClass
|
|
40
|
+
public accountClass!: number;
|
|
23
41
|
|
|
24
42
|
@svDb()
|
|
25
|
-
public accountGroup
|
|
43
|
+
public accountGroup!: number;
|
|
26
44
|
|
|
27
45
|
@svDb()
|
|
28
|
-
public accountSubgroup
|
|
46
|
+
public accountSubgroup!: number;
|
|
29
47
|
|
|
30
48
|
@svDb()
|
|
31
|
-
public accountType
|
|
49
|
+
public accountType!: TAccountType;
|
|
32
50
|
|
|
33
51
|
@svDb()
|
|
34
52
|
@index()
|
|
35
|
-
public skrType
|
|
53
|
+
public skrType!: TSKRType;
|
|
36
54
|
|
|
37
55
|
@svDb()
|
|
38
56
|
@searchable()
|
|
39
|
-
public description
|
|
57
|
+
public description!: string;
|
|
40
58
|
|
|
41
59
|
@svDb()
|
|
42
|
-
public vatRate
|
|
60
|
+
public vatRate!: number;
|
|
43
61
|
|
|
44
62
|
@svDb()
|
|
45
|
-
public balance
|
|
63
|
+
public balance!: number;
|
|
46
64
|
|
|
47
65
|
@svDb()
|
|
48
|
-
public debitTotal
|
|
66
|
+
public debitTotal!: number;
|
|
49
67
|
|
|
50
68
|
@svDb()
|
|
51
|
-
public creditTotal
|
|
69
|
+
public creditTotal!: number;
|
|
52
70
|
|
|
53
71
|
@svDb()
|
|
54
|
-
public isActive
|
|
72
|
+
public isActive!: boolean;
|
|
55
73
|
|
|
56
74
|
@svDb()
|
|
57
|
-
public isSystemAccount
|
|
75
|
+
public isSystemAccount!: boolean;
|
|
58
76
|
|
|
59
77
|
@svDb()
|
|
60
|
-
public
|
|
78
|
+
public isAutomaticAccount!: boolean;
|
|
61
79
|
|
|
62
80
|
@svDb()
|
|
63
|
-
public
|
|
81
|
+
public createdAt!: Date;
|
|
82
|
+
|
|
83
|
+
@svDb()
|
|
84
|
+
public updatedAt!: Date;
|
|
64
85
|
|
|
65
86
|
constructor(data?: Partial<IAccountData>) {
|
|
66
87
|
super();
|
|
@@ -90,6 +111,7 @@ export class Account extends SmartDataDbDoc<Account, Account> {
|
|
|
90
111
|
this.debitTotal = 0;
|
|
91
112
|
this.creditTotal = 0;
|
|
92
113
|
this.isSystemAccount = true;
|
|
114
|
+
this.isAutomaticAccount = data.isAutomaticAccount || false;
|
|
93
115
|
this.createdAt = new Date();
|
|
94
116
|
this.updatedAt = new Date();
|
|
95
117
|
}
|
|
@@ -157,6 +179,85 @@ export class Account extends SmartDataDbDoc<Account, Account> {
|
|
|
157
179
|
);
|
|
158
180
|
}
|
|
159
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Check if account number is in debtor range (10000-69999)
|
|
184
|
+
* Debtor accounts (Debitorenkonten) are individual customer accounts
|
|
185
|
+
*/
|
|
186
|
+
public static isInDebtorRange(accountNumber: string): boolean {
|
|
187
|
+
const num = parseInt(accountNumber);
|
|
188
|
+
return num >= 10000 && num <= 69999;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if account number is in creditor range (70000-99999)
|
|
193
|
+
* Creditor accounts (Kreditorenkonten) are individual vendor accounts
|
|
194
|
+
*/
|
|
195
|
+
public static isInCreditorRange(accountNumber: string): boolean {
|
|
196
|
+
const num = parseInt(accountNumber);
|
|
197
|
+
return num >= 70000 && num <= 99999;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if account is an automatic account (Automatikkonto)
|
|
202
|
+
* Automatic accounts like 1400/1600 cannot be posted to directly
|
|
203
|
+
*/
|
|
204
|
+
public static isAutomaticAccount(accountNumber: string, skrType: TSKRType): boolean {
|
|
205
|
+
// SKR03: 1400 (Forderungen), 1600 (Verbindlichkeiten)
|
|
206
|
+
// SKR04: 1400 (Forderungen), 1600 (Verbindlichkeiten)
|
|
207
|
+
// Note: In SKR04, 3300 is "Fahrzeugkosten" (vehicle costs), NOT an automatic account
|
|
208
|
+
if (skrType === 'SKR03') {
|
|
209
|
+
return accountNumber === '1400' || accountNumber === '1600';
|
|
210
|
+
} else {
|
|
211
|
+
return accountNumber === '1400' || accountNumber === '1600';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Validate account for posting - throws error if account cannot be posted to
|
|
217
|
+
*/
|
|
218
|
+
public static async validateAccountForPosting(
|
|
219
|
+
accountNumber: string,
|
|
220
|
+
skrType: TSKRType,
|
|
221
|
+
): Promise<void> {
|
|
222
|
+
// Check if automatic account
|
|
223
|
+
if (Account.isAutomaticAccount(accountNumber, skrType)) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Account ${accountNumber} is an automatic account (Automatikkonto) and cannot be posted to directly. ` +
|
|
226
|
+
`Use debtor accounts (10000-69999) or creditor accounts (70000-99999) instead.`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Get account to verify it exists
|
|
231
|
+
const account = await Account.getAccountByNumber(accountNumber, skrType);
|
|
232
|
+
if (!account) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Account ${accountNumber} not found in ${skrType}. ` +
|
|
235
|
+
`Please create the account before posting.`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check if account is active
|
|
240
|
+
if (!account.isActive) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Account ${accountNumber} is inactive and cannot be posted to.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if this account instance is a debtor account
|
|
249
|
+
*/
|
|
250
|
+
public isDebtorAccount(): boolean {
|
|
251
|
+
return Account.isInDebtorRange(this.accountNumber);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check if this account instance is a creditor account
|
|
256
|
+
*/
|
|
257
|
+
public isCreditorAccount(): boolean {
|
|
258
|
+
return Account.isInCreditorRange(this.accountNumber);
|
|
259
|
+
}
|
|
260
|
+
|
|
160
261
|
public async updateBalance(
|
|
161
262
|
debitAmount: number = 0,
|
|
162
263
|
creditAmount: number = 0,
|
|
@@ -209,19 +310,33 @@ export class Account extends SmartDataDbDoc<Account, Account> {
|
|
|
209
310
|
|
|
210
311
|
public async beforeSave(): Promise<void> {
|
|
211
312
|
// Validate account number format
|
|
212
|
-
|
|
313
|
+
const accountLength = this.accountNumber?.length || 0;
|
|
314
|
+
if (!this.accountNumber || (accountLength !== 4 && accountLength !== 5)) {
|
|
213
315
|
throw new Error(
|
|
214
|
-
`Invalid account number format: ${this.accountNumber}. Must be 4 digits.`,
|
|
316
|
+
`Invalid account number format: ${this.accountNumber}. Must be 4 digits (standard SKR) or 5 digits (debtor/creditor).`,
|
|
215
317
|
);
|
|
216
318
|
}
|
|
217
319
|
|
|
218
320
|
// Validate account number is numeric
|
|
219
|
-
if (!/^\d{4}$/.test(this.accountNumber)) {
|
|
321
|
+
if (!/^\d{4,5}$/.test(this.accountNumber)) {
|
|
220
322
|
throw new Error(
|
|
221
323
|
`Account number must contain only digits: ${this.accountNumber}`,
|
|
222
324
|
);
|
|
223
325
|
}
|
|
224
326
|
|
|
327
|
+
// For 5-digit accounts, validate they are in debtor (10000-69999) or creditor (70000-99999) ranges
|
|
328
|
+
if (accountLength === 5) {
|
|
329
|
+
const accountNum = parseInt(this.accountNumber);
|
|
330
|
+
const isDebtor = accountNum >= 10000 && accountNum <= 69999;
|
|
331
|
+
const isCreditor = accountNum >= 70000 && accountNum <= 99999;
|
|
332
|
+
|
|
333
|
+
if (!isDebtor && !isCreditor) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`5-digit account number ${this.accountNumber} must be in debtor range (10000-69999) or creditor range (70000-99999).`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
225
340
|
// Validate account class matches first digit
|
|
226
341
|
const firstDigit = parseInt(this.accountNumber[0]);
|
|
227
342
|
if (this.accountClass !== firstDigit) {
|
|
@@ -234,5 +349,11 @@ export class Account extends SmartDataDbDoc<Account, Account> {
|
|
|
234
349
|
if (this.skrType !== 'SKR03' && this.skrType !== 'SKR04') {
|
|
235
350
|
throw new Error(`Invalid SKR type: ${this.skrType}`);
|
|
236
351
|
}
|
|
352
|
+
|
|
353
|
+
// Mark automatic accounts (Automatikkonten)
|
|
354
|
+
// These are summary accounts that cannot be posted to directly
|
|
355
|
+
if (Account.isAutomaticAccount(this.accountNumber, this.skrType)) {
|
|
356
|
+
this.isAutomaticAccount = true;
|
|
357
|
+
}
|
|
237
358
|
}
|
|
238
359
|
}
|
|
@@ -262,6 +262,9 @@ export class ChartOfAccounts {
|
|
|
262
262
|
* Search accounts
|
|
263
263
|
*/
|
|
264
264
|
public async searchAccounts(searchTerm: string): Promise<Account[]> {
|
|
265
|
+
if (!this.skrType) {
|
|
266
|
+
throw new Error('SKR type not set. Initialize SKR03 or SKR04 first.');
|
|
267
|
+
}
|
|
265
268
|
return await Account.searchAccounts(searchTerm, this.skrType);
|
|
266
269
|
}
|
|
267
270
|
|
|
@@ -287,10 +290,11 @@ export class ChartOfAccounts {
|
|
|
287
290
|
|
|
288
291
|
// Apply text search if provided
|
|
289
292
|
if (filter?.searchTerm) {
|
|
290
|
-
const
|
|
293
|
+
const searchTerm = filter.searchTerm;
|
|
294
|
+
const lowerSearchTerm = searchTerm.toLowerCase();
|
|
291
295
|
return accounts.filter(
|
|
292
296
|
(account) =>
|
|
293
|
-
account.accountNumber.includes(
|
|
297
|
+
account.accountNumber.includes(searchTerm) ||
|
|
294
298
|
account.accountName.toLowerCase().includes(lowerSearchTerm) ||
|
|
295
299
|
account.description.toLowerCase().includes(lowerSearchTerm),
|
|
296
300
|
);
|
|
@@ -468,9 +472,10 @@ export class ChartOfAccounts {
|
|
|
468
472
|
await this.createCustomAccount(accountData);
|
|
469
473
|
importedCount++;
|
|
470
474
|
} catch (error) {
|
|
475
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
471
476
|
this.logger.log(
|
|
472
477
|
'warn',
|
|
473
|
-
`Failed to import account ${parts[0]}: ${
|
|
478
|
+
`Failed to import account ${parts[0]}: ${errorMessage}`,
|
|
474
479
|
);
|
|
475
480
|
}
|
|
476
481
|
}
|