@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,300 @@
1
+ import * as plugins from './plugins.js';
2
+ import { getDbSync } from './skr.database.js';
3
+ import { Account } from './skr.classes.account.js';
4
+ import type {
5
+ TSKRType,
6
+ TTransactionStatus,
7
+ ITransactionData,
8
+ } from './skr.types.js';
9
+
10
+ const { SmartDataDbDoc, svDb, unI, index, searchable } = plugins.smartdata;
11
+
12
+ @plugins.smartdata.Collection(() => getDbSync())
13
+ export class Transaction extends SmartDataDbDoc<Transaction, Transaction> {
14
+ @unI()
15
+ public id: string;
16
+
17
+ @svDb()
18
+ @index()
19
+ public transactionNumber: string;
20
+
21
+ @svDb()
22
+ @index()
23
+ public date: Date;
24
+
25
+ @svDb()
26
+ @index()
27
+ public debitAccount: string;
28
+
29
+ @svDb()
30
+ @index()
31
+ public creditAccount: string;
32
+
33
+ @svDb()
34
+ public amount: number;
35
+
36
+ @svDb()
37
+ @searchable()
38
+ public description: string;
39
+
40
+ @svDb()
41
+ @index()
42
+ public reference: string;
43
+
44
+ @svDb()
45
+ @index()
46
+ public skrType: TSKRType;
47
+
48
+ @svDb()
49
+ public vatAmount: number;
50
+
51
+ @svDb()
52
+ public costCenter: string;
53
+
54
+ @svDb()
55
+ @index()
56
+ public status: TTransactionStatus;
57
+
58
+ @svDb()
59
+ public reversalOf: string;
60
+
61
+ @svDb()
62
+ public reversedBy: string;
63
+
64
+ @svDb()
65
+ @index()
66
+ public period: string; // Format: YYYY-MM
67
+
68
+ @svDb()
69
+ public fiscalYear: number;
70
+
71
+ @svDb()
72
+ public createdAt: Date;
73
+
74
+ @svDb()
75
+ public postedAt: Date;
76
+
77
+ @svDb()
78
+ public createdBy: string;
79
+
80
+ constructor(data?: Partial<ITransactionData>) {
81
+ super();
82
+
83
+ if (data) {
84
+ this.id = plugins.smartunique.shortId();
85
+ this.transactionNumber = this.generateTransactionNumber();
86
+ this.date = data.date || new Date();
87
+ this.debitAccount = data.debitAccount || '';
88
+ this.creditAccount = data.creditAccount || '';
89
+ this.amount = data.amount || 0;
90
+ this.description = data.description || '';
91
+ this.reference = data.reference || '';
92
+ this.skrType = data.skrType || 'SKR03';
93
+ this.vatAmount = data.vatAmount || 0;
94
+ this.costCenter = data.costCenter || '';
95
+ this.status = 'pending';
96
+ this.reversalOf = '';
97
+ this.reversedBy = '';
98
+
99
+ // Set period and fiscal year
100
+ const transDate = new Date(this.date);
101
+ this.period = `${transDate.getFullYear()}-${String(transDate.getMonth() + 1).padStart(2, '0')}`;
102
+ this.fiscalYear = transDate.getFullYear();
103
+
104
+ this.createdAt = new Date();
105
+ this.postedAt = null;
106
+ this.createdBy = 'system';
107
+ }
108
+ }
109
+
110
+ private generateTransactionNumber(): string {
111
+ const timestamp = Date.now();
112
+ const random = Math.floor(Math.random() * 1000);
113
+ return `TXN-${timestamp}-${random}`;
114
+ }
115
+
116
+ public static async createTransaction(
117
+ data: ITransactionData,
118
+ ): Promise<Transaction> {
119
+ const transaction = new Transaction(data);
120
+ await transaction.validateAndPost();
121
+ return transaction;
122
+ }
123
+
124
+ public static async getTransactionById(
125
+ id: string,
126
+ ): Promise<Transaction | null> {
127
+ const transaction = await Transaction.getInstance({ id });
128
+ return transaction;
129
+ }
130
+
131
+ public static async getTransactionsByAccount(
132
+ accountNumber: string,
133
+ skrType: TSKRType,
134
+ ): Promise<Transaction[]> {
135
+ const transactionsDebit = await Transaction.getInstances({
136
+ debitAccount: accountNumber,
137
+ skrType,
138
+ status: 'posted',
139
+ });
140
+ const transactionsCredit = await Transaction.getInstances({
141
+ creditAccount: accountNumber,
142
+ skrType,
143
+ status: 'posted',
144
+ });
145
+ const transactions = [...transactionsDebit, ...transactionsCredit];
146
+ return transactions;
147
+ }
148
+
149
+ public static async getTransactionsByPeriod(
150
+ period: string,
151
+ skrType: TSKRType,
152
+ ): Promise<Transaction[]> {
153
+ const transactions = await Transaction.getInstances({
154
+ period,
155
+ skrType,
156
+ status: 'posted',
157
+ });
158
+ return transactions;
159
+ }
160
+
161
+ public static async getTransactionsByDateRange(
162
+ dateFrom: Date,
163
+ dateTo: Date,
164
+ skrType: TSKRType,
165
+ ): Promise<Transaction[]> {
166
+ const allTransactions = await Transaction.getInstances({
167
+ skrType,
168
+ status: 'posted',
169
+ });
170
+ const transactions = allTransactions.filter(
171
+ (t) => t.date >= dateFrom && t.date <= dateTo,
172
+ );
173
+ return transactions;
174
+ }
175
+
176
+ public async validateAndPost(): Promise<void> {
177
+ // Validate transaction
178
+ await this.validateTransaction();
179
+
180
+ // Update account balances
181
+ await this.updateAccountBalances();
182
+
183
+ // Mark as posted
184
+ this.status = 'posted';
185
+ this.postedAt = new Date();
186
+
187
+ await this.save();
188
+ }
189
+
190
+ private async validateTransaction(): Promise<void> {
191
+ // Check if accounts exist
192
+ const debitAccount = await Account.getAccountByNumber(
193
+ this.debitAccount,
194
+ this.skrType,
195
+ );
196
+ const creditAccount = await Account.getAccountByNumber(
197
+ this.creditAccount,
198
+ this.skrType,
199
+ );
200
+
201
+ if (!debitAccount) {
202
+ throw new Error(
203
+ `Debit account ${this.debitAccount} not found for ${this.skrType}`,
204
+ );
205
+ }
206
+
207
+ if (!creditAccount) {
208
+ throw new Error(
209
+ `Credit account ${this.creditAccount} not found for ${this.skrType}`,
210
+ );
211
+ }
212
+
213
+ // Check if accounts are active
214
+ if (!debitAccount.isActive) {
215
+ throw new Error(`Debit account ${this.debitAccount} is not active`);
216
+ }
217
+
218
+ if (!creditAccount.isActive) {
219
+ throw new Error(`Credit account ${this.creditAccount} is not active`);
220
+ }
221
+
222
+ // Validate amount
223
+ if (this.amount <= 0) {
224
+ throw new Error('Transaction amount must be greater than zero');
225
+ }
226
+
227
+ // Check for same account
228
+ if (this.debitAccount === this.creditAccount) {
229
+ throw new Error('Debit and credit accounts cannot be the same');
230
+ }
231
+ }
232
+
233
+ private async updateAccountBalances(): Promise<void> {
234
+ const debitAccount = await Account.getAccountByNumber(
235
+ this.debitAccount,
236
+ this.skrType,
237
+ );
238
+ const creditAccount = await Account.getAccountByNumber(
239
+ this.creditAccount,
240
+ this.skrType,
241
+ );
242
+
243
+ if (debitAccount) {
244
+ await debitAccount.updateBalance(this.amount, 0);
245
+ }
246
+
247
+ if (creditAccount) {
248
+ await creditAccount.updateBalance(0, this.amount);
249
+ }
250
+ }
251
+
252
+ public async reverseTransaction(): Promise<Transaction> {
253
+ if (this.status !== 'posted') {
254
+ throw new Error('Can only reverse posted transactions');
255
+ }
256
+
257
+ if (this.reversedBy) {
258
+ throw new Error('Transaction has already been reversed');
259
+ }
260
+
261
+ // Create reversal transaction
262
+ const reversalData: ITransactionData = {
263
+ date: new Date(),
264
+ debitAccount: this.creditAccount, // Swap accounts
265
+ creditAccount: this.debitAccount, // Swap accounts
266
+ amount: this.amount,
267
+ description: `Reversal of ${this.transactionNumber}: ${this.description}`,
268
+ reference: `REV-${this.transactionNumber}`,
269
+ skrType: this.skrType,
270
+ vatAmount: this.vatAmount,
271
+ costCenter: this.costCenter,
272
+ };
273
+
274
+ const reversalTransaction = new Transaction(reversalData);
275
+ reversalTransaction.reversalOf = this.id;
276
+ await reversalTransaction.validateAndPost();
277
+
278
+ // Update original transaction
279
+ this.reversedBy = reversalTransaction.id;
280
+ this.status = 'reversed';
281
+ await this.save();
282
+
283
+ return reversalTransaction;
284
+ }
285
+
286
+ public async beforeSave(): Promise<void> {
287
+ // Additional validation before saving
288
+ if (!this.debitAccount || !this.creditAccount) {
289
+ throw new Error('Both debit and credit accounts are required');
290
+ }
291
+
292
+ if (!this.date) {
293
+ throw new Error('Transaction date is required');
294
+ }
295
+
296
+ if (!this.description) {
297
+ throw new Error('Transaction description is required');
298
+ }
299
+ }
300
+ }
@@ -0,0 +1,39 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IDatabaseConfig } from './skr.types.js';
3
+
4
+ let dbInstance: plugins.smartdata.SmartdataDb | null = null;
5
+
6
+ export const getDb = async (
7
+ config?: IDatabaseConfig,
8
+ ): Promise<plugins.smartdata.SmartdataDb> => {
9
+ if (!dbInstance) {
10
+ if (!config) {
11
+ throw new Error(
12
+ 'Database configuration required for first initialization',
13
+ );
14
+ }
15
+
16
+ dbInstance = new plugins.smartdata.SmartdataDb({
17
+ mongoDbUrl: config.mongoDbUrl,
18
+ mongoDbName: config.dbName || 'skr_accounting',
19
+ });
20
+
21
+ await dbInstance.init();
22
+ }
23
+
24
+ return dbInstance;
25
+ };
26
+
27
+ export const getDbSync = (): plugins.smartdata.SmartdataDb => {
28
+ if (!dbInstance) {
29
+ throw new Error('Database not initialized. Call getDb() first.');
30
+ }
31
+ return dbInstance;
32
+ };
33
+
34
+ export const closeDb = async (): Promise<void> => {
35
+ if (dbInstance) {
36
+ await dbInstance.close();
37
+ dbInstance = null;
38
+ }
39
+ };
@@ -0,0 +1,154 @@
1
+ export type TAccountType =
2
+ | 'asset'
3
+ | 'liability'
4
+ | 'equity'
5
+ | 'revenue'
6
+ | 'expense';
7
+
8
+ export type TSKRType = 'SKR03' | 'SKR04';
9
+
10
+ export type TTransactionStatus = 'pending' | 'posted' | 'reversed';
11
+
12
+ export type TReportType =
13
+ | 'trial_balance'
14
+ | 'income_statement'
15
+ | 'balance_sheet'
16
+ | 'general_ledger'
17
+ | 'cash_flow';
18
+
19
+ export interface IAccountData {
20
+ accountNumber: string;
21
+ accountName: string;
22
+ accountClass: number;
23
+ accountType: TAccountType;
24
+ skrType: TSKRType;
25
+ description?: string;
26
+ vatRate?: number;
27
+ isActive?: boolean;
28
+ }
29
+
30
+ export interface ITransactionData {
31
+ date: Date;
32
+ debitAccount: string;
33
+ creditAccount: string;
34
+ amount: number;
35
+ description: string;
36
+ reference?: string;
37
+ skrType: TSKRType;
38
+ vatAmount?: number;
39
+ costCenter?: string;
40
+ }
41
+
42
+ export interface IJournalEntry {
43
+ date: Date;
44
+ description: string;
45
+ reference?: string;
46
+ lines: IJournalEntryLine[];
47
+ skrType: TSKRType;
48
+ }
49
+
50
+ export interface IJournalEntryLine {
51
+ accountNumber: string;
52
+ debit?: number;
53
+ credit?: number;
54
+ description?: string;
55
+ costCenter?: string;
56
+ }
57
+
58
+ export interface ITrialBalanceEntry {
59
+ accountNumber: string;
60
+ accountName: string;
61
+ debitBalance: number;
62
+ creditBalance: number;
63
+ netBalance: number;
64
+ }
65
+
66
+ export interface ITrialBalanceReport {
67
+ date: Date;
68
+ skrType: TSKRType;
69
+ entries: ITrialBalanceEntry[];
70
+ totalDebits: number;
71
+ totalCredits: number;
72
+ isBalanced: boolean;
73
+ }
74
+
75
+ export interface IIncomeStatementEntry {
76
+ accountNumber: string;
77
+ accountName: string;
78
+ amount: number;
79
+ percentage?: number;
80
+ }
81
+
82
+ export interface IIncomeStatement {
83
+ date: Date;
84
+ skrType: TSKRType;
85
+ revenue: IIncomeStatementEntry[];
86
+ expenses: IIncomeStatementEntry[];
87
+ totalRevenue: number;
88
+ totalExpenses: number;
89
+ netIncome: number;
90
+ }
91
+
92
+ export interface IBalanceSheetEntry {
93
+ accountNumber: string;
94
+ accountName: string;
95
+ amount: number;
96
+ }
97
+
98
+ export interface IBalanceSheet {
99
+ date: Date;
100
+ skrType: TSKRType;
101
+ assets: {
102
+ current: IBalanceSheetEntry[];
103
+ fixed: IBalanceSheetEntry[];
104
+ totalAssets: number;
105
+ };
106
+ liabilities: {
107
+ current: IBalanceSheetEntry[];
108
+ longTerm: IBalanceSheetEntry[];
109
+ totalLiabilities: number;
110
+ };
111
+ equity: {
112
+ entries: IBalanceSheetEntry[];
113
+ totalEquity: number;
114
+ };
115
+ isBalanced: boolean;
116
+ }
117
+
118
+ export interface IAccountFilter {
119
+ skrType?: TSKRType;
120
+ accountClass?: number;
121
+ accountType?: TAccountType;
122
+ isActive?: boolean;
123
+ searchTerm?: string;
124
+ }
125
+
126
+ export interface ITransactionFilter {
127
+ skrType?: TSKRType;
128
+ dateFrom?: Date;
129
+ dateTo?: Date;
130
+ accountNumber?: string;
131
+ minAmount?: number;
132
+ maxAmount?: number;
133
+ searchTerm?: string;
134
+ }
135
+
136
+ export interface IDatabaseConfig {
137
+ mongoDbUrl: string;
138
+ dbName?: string;
139
+ }
140
+
141
+ export interface IReportParams {
142
+ dateFrom?: Date;
143
+ dateTo?: Date;
144
+ skrType: TSKRType;
145
+ format?: 'json' | 'csv' | 'datev';
146
+ }
147
+
148
+ export interface IAccountBalance {
149
+ accountNumber: string;
150
+ debitTotal: number;
151
+ creditTotal: number;
152
+ balance: number;
153
+ lastUpdated: Date;
154
+ }