@happyvertical/smrt-ledgers 0.37.1 → 0.37.3
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/index.js +1018 -1141
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/smrt-knowledge.json +7 -7
- package/dist/types.js +6 -5
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
import { ObjectRegistry, smrt, SmrtHierarchical, SmrtCollection, foreignKey, SmrtObject } from "@happyvertical/smrt-core";
|
|
2
|
-
import { definePrompt, resolvePrompt } from "@happyvertical/smrt-prompts";
|
|
3
|
-
import { tenantId, TenantScoped, queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
|
|
4
1
|
import { BALANCE_EPSILON } from "./types.js";
|
|
5
|
-
ObjectRegistry
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { ObjectRegistry, SmrtCollection, SmrtHierarchical, SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
3
|
+
import { definePrompt, resolvePrompt } from "@happyvertical/smrt-prompts";
|
|
4
|
+
import { TenantScoped, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __defProp$3 = Object.defineProperty;
|
|
7
|
+
var __exportAll = (all, no_symbols) => {
|
|
8
|
+
let target = {};
|
|
9
|
+
for (var name in all) __defProp$3(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true
|
|
12
|
+
});
|
|
13
|
+
if (!no_symbols) __defProp$3(target, Symbol.toStringTag, { value: "Module" });
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/__smrt-register__.ts
|
|
18
|
+
ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/prompts.ts
|
|
21
|
+
var smrtLedgersJournalSummarizePrompt = definePrompt({
|
|
22
|
+
key: "smrtLedgers.journal.summarize",
|
|
23
|
+
template: `Summarize this accounting journal entry:
|
|
11
24
|
Number: {journalNumber}
|
|
12
25
|
Date: {journalDate}
|
|
13
26
|
Description: {journalDescription}
|
|
@@ -15,1154 +28,1018 @@ Status: {journalStatus}
|
|
|
15
28
|
Total: {journalTotal}
|
|
16
29
|
Entries: {entryCount}
|
|
17
30
|
Balanced: {journalBalanced}`,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
editable: {
|
|
32
|
+
template: true,
|
|
33
|
+
profile: true,
|
|
34
|
+
model: true,
|
|
35
|
+
params: true
|
|
36
|
+
}
|
|
24
37
|
});
|
|
25
38
|
function promptMessageOptions(ai) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
return {
|
|
40
|
+
...ai.params || {},
|
|
41
|
+
...ai.model ? { model: ai.model } : {},
|
|
42
|
+
...typeof ai.temperature === "number" ? { temperature: ai.temperature } : {},
|
|
43
|
+
...typeof ai.maxTokens === "number" ? { maxTokens: ai.maxTokens } : {}
|
|
44
|
+
};
|
|
32
45
|
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/models/Account.ts
|
|
33
48
|
var __defProp$2 = Object.defineProperty;
|
|
34
49
|
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
35
50
|
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (kind && result) __defProp$2(target, key, result);
|
|
41
|
-
return result;
|
|
51
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
52
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
53
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
54
|
+
return result;
|
|
42
55
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
throw new Error("Account must be saved before creating children");
|
|
139
|
-
}
|
|
140
|
-
const { AccountCollection: AccountCollection2 } = await Promise.resolve().then(() => Accounts);
|
|
141
|
-
const collection = await AccountCollection2.create(this.options);
|
|
142
|
-
const account = await collection.create({
|
|
143
|
-
...options,
|
|
144
|
-
parentId: this.id,
|
|
145
|
-
type: this.type
|
|
146
|
-
// Inherit type from parent
|
|
147
|
-
});
|
|
148
|
-
await account.save();
|
|
149
|
-
return account;
|
|
150
|
-
}
|
|
56
|
+
var Account = class extends SmrtHierarchical {
|
|
57
|
+
tenantId = null;
|
|
58
|
+
/**
|
|
59
|
+
* Account number (e.g., "1000", "5030")
|
|
60
|
+
*/
|
|
61
|
+
number = "";
|
|
62
|
+
/**
|
|
63
|
+
* Account name (e.g., "Cash", "Coffee Expense")
|
|
64
|
+
*/
|
|
65
|
+
name = "";
|
|
66
|
+
/**
|
|
67
|
+
* Account description
|
|
68
|
+
*/
|
|
69
|
+
description = "";
|
|
70
|
+
/**
|
|
71
|
+
* Account type - one of the 5 core types
|
|
72
|
+
*/
|
|
73
|
+
type = "asset";
|
|
74
|
+
/**
|
|
75
|
+
* Whether the account is active
|
|
76
|
+
*/
|
|
77
|
+
active = true;
|
|
78
|
+
/**
|
|
79
|
+
* Extensible metadata
|
|
80
|
+
*/
|
|
81
|
+
metadata = {};
|
|
82
|
+
constructor(options = {}) {
|
|
83
|
+
super(options);
|
|
84
|
+
if (options.number !== void 0) this.number = options.number;
|
|
85
|
+
if (options.name !== void 0) this.name = options.name;
|
|
86
|
+
if (options.description !== void 0) this.description = options.description;
|
|
87
|
+
if (options.type !== void 0) this.type = options.type;
|
|
88
|
+
if (options.parentId !== void 0) this.parentId = options.parentId;
|
|
89
|
+
if (options.active !== void 0) this.active = options.active;
|
|
90
|
+
if (options.metadata !== void 0) this.metadata = options.metadata;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if this is a top-level account (no parent)
|
|
94
|
+
*/
|
|
95
|
+
isTopLevel() {
|
|
96
|
+
return this.parentId === null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check if this is a debit-normal account (Asset, Expense)
|
|
100
|
+
* Debit-normal accounts increase with debits
|
|
101
|
+
*/
|
|
102
|
+
isDebitNormal() {
|
|
103
|
+
return this.type === "asset" || this.type === "expense";
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if this is a credit-normal account (Liability, Equity, Revenue)
|
|
107
|
+
* Credit-normal accounts increase with credits
|
|
108
|
+
*/
|
|
109
|
+
isCreditNormal() {
|
|
110
|
+
return this.type === "liability" || this.type === "equity" || this.type === "revenue";
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get full account path (e.g., "Assets > Current > Cash")
|
|
114
|
+
*/
|
|
115
|
+
async getFullPath() {
|
|
116
|
+
return [...(await this.getAncestors()).map((a) => a.name), this.name].join(" > ");
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build tree node for this account and its children
|
|
120
|
+
*/
|
|
121
|
+
async toTreeNode() {
|
|
122
|
+
const children = await this.getChildren();
|
|
123
|
+
const childNodes = await Promise.all(children.map((c) => c.toTreeNode()));
|
|
124
|
+
return {
|
|
125
|
+
account: this,
|
|
126
|
+
children: childNodes
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get the current balance of this account
|
|
131
|
+
*/
|
|
132
|
+
async getBalance(asOfDate) {
|
|
133
|
+
if (!this.id) return 0;
|
|
134
|
+
const { JournalEntryCollection } = await Promise.resolve().then(() => JournalEntries_exports);
|
|
135
|
+
return await (await JournalEntryCollection.create(this.options)).getAccountBalance(this.id, asOfDate);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Create a sub-account under this account
|
|
139
|
+
*/
|
|
140
|
+
async createChild(options) {
|
|
141
|
+
if (!this.id) throw new Error("Account must be saved before creating children");
|
|
142
|
+
const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
|
|
143
|
+
const account = await (await AccountCollection.create(this.options)).create({
|
|
144
|
+
...options,
|
|
145
|
+
parentId: this.id,
|
|
146
|
+
type: this.type
|
|
147
|
+
});
|
|
148
|
+
await account.save();
|
|
149
|
+
return account;
|
|
150
|
+
}
|
|
151
151
|
};
|
|
152
|
-
__decorateClass$2([
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
347
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
348
|
-
*
|
|
349
|
-
* @param tenantId - Tenant ID
|
|
350
|
-
* @returns Array of tenant's accounts and global accounts
|
|
351
|
-
*/
|
|
352
|
-
async findWithGlobals(tenantId2) {
|
|
353
|
-
return queryWithGlobals(this, tenantId2, "Account.findWithGlobals");
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
const Accounts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
357
|
-
__proto__: null,
|
|
358
|
-
AccountCollection
|
|
359
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
152
|
+
__decorateClass$2([tenantId({ nullable: true })], Account.prototype, "tenantId", 2);
|
|
153
|
+
Account = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
|
|
154
|
+
api: { include: [
|
|
155
|
+
"list",
|
|
156
|
+
"get",
|
|
157
|
+
"create",
|
|
158
|
+
"update",
|
|
159
|
+
"delete"
|
|
160
|
+
] },
|
|
161
|
+
mcp: { include: [
|
|
162
|
+
"list",
|
|
163
|
+
"get",
|
|
164
|
+
"create"
|
|
165
|
+
] },
|
|
166
|
+
cli: true
|
|
167
|
+
})], Account);
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/collections/Accounts.ts
|
|
170
|
+
var Accounts_exports = /* @__PURE__ */ __exportAll({ AccountCollection: () => AccountCollection });
|
|
171
|
+
var AccountCollection = class extends SmrtCollection {
|
|
172
|
+
static _itemClass = Account;
|
|
173
|
+
/**
|
|
174
|
+
* Find account by number
|
|
175
|
+
*
|
|
176
|
+
* @param number - Account number
|
|
177
|
+
* @returns Account or null
|
|
178
|
+
*/
|
|
179
|
+
async findByNumber(number) {
|
|
180
|
+
return (await this.list({
|
|
181
|
+
where: { number },
|
|
182
|
+
limit: 1
|
|
183
|
+
}))[0] || null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Find accounts by type
|
|
187
|
+
*
|
|
188
|
+
* @param type - Account type
|
|
189
|
+
* @returns Array of accounts
|
|
190
|
+
*/
|
|
191
|
+
async findByType(type) {
|
|
192
|
+
return await this.list({
|
|
193
|
+
where: { type },
|
|
194
|
+
orderBy: "number ASC"
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Find all active accounts
|
|
199
|
+
*
|
|
200
|
+
* @returns Array of active accounts
|
|
201
|
+
*/
|
|
202
|
+
async findActive() {
|
|
203
|
+
return await this.list({
|
|
204
|
+
where: { active: true },
|
|
205
|
+
orderBy: "number ASC"
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Find top-level accounts (no parent)
|
|
210
|
+
*
|
|
211
|
+
* @returns Array of top-level accounts
|
|
212
|
+
*/
|
|
213
|
+
async findTopLevel() {
|
|
214
|
+
return await this.list({
|
|
215
|
+
where: { parentId: null },
|
|
216
|
+
orderBy: "number ASC"
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Find direct children of an account
|
|
221
|
+
*
|
|
222
|
+
* @param parentId - Parent account ID
|
|
223
|
+
* @returns Array of child accounts
|
|
224
|
+
*/
|
|
225
|
+
async findChildren(parentId) {
|
|
226
|
+
return await this.list({
|
|
227
|
+
where: { parentId },
|
|
228
|
+
orderBy: "number ASC"
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get the complete account tree
|
|
233
|
+
*
|
|
234
|
+
* @returns AccountTree structure
|
|
235
|
+
*/
|
|
236
|
+
async getTree() {
|
|
237
|
+
const allAccounts = await this.list({ orderBy: "number ASC" });
|
|
238
|
+
const accountMap = /* @__PURE__ */ new Map();
|
|
239
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
240
|
+
for (const account of allAccounts) {
|
|
241
|
+
if (account.id) accountMap.set(account.id, account);
|
|
242
|
+
if (!childrenMap.has(account.parentId || "")) childrenMap.set(account.parentId || "", []);
|
|
243
|
+
childrenMap.get(account.parentId || "")?.push(account);
|
|
244
|
+
}
|
|
245
|
+
const buildNode = (account) => {
|
|
246
|
+
return {
|
|
247
|
+
account,
|
|
248
|
+
children: ((account.id ? childrenMap.get(account.id) : []) || []).map(buildNode)
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
return { roots: (childrenMap.get("") || []).map(buildNode) };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get or create an account by number
|
|
255
|
+
*
|
|
256
|
+
* @param number - Account number
|
|
257
|
+
* @param defaults - Default values if creating
|
|
258
|
+
* @returns Account
|
|
259
|
+
*/
|
|
260
|
+
async getOrCreateByNumber(number, defaults = {}) {
|
|
261
|
+
const existing = await this.findByNumber(number);
|
|
262
|
+
if (existing) return existing;
|
|
263
|
+
const account = await this.create({
|
|
264
|
+
number,
|
|
265
|
+
name: defaults.name || number,
|
|
266
|
+
description: defaults.description || "",
|
|
267
|
+
type: defaults.type || "asset",
|
|
268
|
+
parentId: defaults.parentId || null
|
|
269
|
+
});
|
|
270
|
+
await account.save();
|
|
271
|
+
return account;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Find accounts grouped by type
|
|
275
|
+
*
|
|
276
|
+
* @returns Record of type to accounts array
|
|
277
|
+
*/
|
|
278
|
+
async groupByType() {
|
|
279
|
+
const accounts = await this.findActive();
|
|
280
|
+
const grouped = {
|
|
281
|
+
asset: [],
|
|
282
|
+
liability: [],
|
|
283
|
+
equity: [],
|
|
284
|
+
revenue: [],
|
|
285
|
+
expense: []
|
|
286
|
+
};
|
|
287
|
+
for (const account of accounts) grouped[account.type].push(account);
|
|
288
|
+
return grouped;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get all descendants of an account recursively
|
|
292
|
+
*
|
|
293
|
+
* @param accountId - Account ID
|
|
294
|
+
* @returns Array of all descendant accounts
|
|
295
|
+
*/
|
|
296
|
+
async getDescendants(accountId) {
|
|
297
|
+
const descendants = [];
|
|
298
|
+
const queue = [accountId];
|
|
299
|
+
while (queue.length > 0) {
|
|
300
|
+
const currentId = queue.shift();
|
|
301
|
+
if (!currentId) continue;
|
|
302
|
+
const children = await this.findChildren(currentId);
|
|
303
|
+
for (const child of children) {
|
|
304
|
+
descendants.push(child);
|
|
305
|
+
if (child.id) queue.push(child.id);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return descendants;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Find all accounts belonging to a specific tenant
|
|
312
|
+
*
|
|
313
|
+
* @param tenantId - Tenant ID
|
|
314
|
+
* @returns Array of tenant's accounts
|
|
315
|
+
*/
|
|
316
|
+
async findByTenant(tenantId) {
|
|
317
|
+
return this.list({ where: { tenantId } });
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Find all global accounts (no tenant association).
|
|
321
|
+
*
|
|
322
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
323
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
324
|
+
* flagged as an isolation violation). (#1600)
|
|
325
|
+
*
|
|
326
|
+
* @returns Array of global accounts
|
|
327
|
+
*/
|
|
328
|
+
async findGlobal() {
|
|
329
|
+
return queryGlobal(this);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Find accounts for a tenant plus all global accounts.
|
|
333
|
+
*
|
|
334
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
335
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
336
|
+
*
|
|
337
|
+
* @param tenantId - Tenant ID
|
|
338
|
+
* @returns Array of tenant's accounts and global accounts
|
|
339
|
+
*/
|
|
340
|
+
async findWithGlobals(tenantId) {
|
|
341
|
+
return queryWithGlobals(this, tenantId, "Account.findWithGlobals");
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/models/JournalEntry.ts
|
|
360
346
|
var __defProp$1 = Object.defineProperty;
|
|
361
347
|
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
362
348
|
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (kind && result) __defProp$1(target, key, result);
|
|
368
|
-
return result;
|
|
349
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
350
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
351
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
352
|
+
return result;
|
|
369
353
|
};
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
async getAccount() {
|
|
468
|
-
if (!this.accountId) return null;
|
|
469
|
-
const { AccountCollection: AccountCollection2 } = await Promise.resolve().then(() => Accounts);
|
|
470
|
-
const collection = await AccountCollection2.create(this.options);
|
|
471
|
-
return await collection.get({ id: this.accountId });
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Get a formatted description of this entry
|
|
475
|
-
*/
|
|
476
|
-
async getDescription() {
|
|
477
|
-
const account = await this.getAccount();
|
|
478
|
-
const accountName = account?.name || "Unknown Account";
|
|
479
|
-
const type = this.isDebit() ? "DR" : "CR";
|
|
480
|
-
const amount = this.getAmount().toFixed(2);
|
|
481
|
-
return `${type} ${accountName}: $${amount}${this.memo ? ` - ${this.memo}` : ""}`;
|
|
482
|
-
}
|
|
354
|
+
var JournalEntry = class extends SmrtObject {
|
|
355
|
+
tenantId = null;
|
|
356
|
+
journalId = "";
|
|
357
|
+
accountId = "";
|
|
358
|
+
/**
|
|
359
|
+
* Debit amount (left side)
|
|
360
|
+
*/
|
|
361
|
+
debit = 0;
|
|
362
|
+
/**
|
|
363
|
+
* Credit amount (right side)
|
|
364
|
+
*/
|
|
365
|
+
credit = 0;
|
|
366
|
+
/**
|
|
367
|
+
* Currency code (e.g., "USD", "CAD", "EUR")
|
|
368
|
+
*/
|
|
369
|
+
currency = "USD";
|
|
370
|
+
/**
|
|
371
|
+
* Exchange rate to base currency
|
|
372
|
+
*/
|
|
373
|
+
exchangeRate = 1;
|
|
374
|
+
/**
|
|
375
|
+
* Line memo/description
|
|
376
|
+
*/
|
|
377
|
+
memo = "";
|
|
378
|
+
/**
|
|
379
|
+
* Extensible metadata
|
|
380
|
+
*/
|
|
381
|
+
metadata = {};
|
|
382
|
+
constructor(options = {}) {
|
|
383
|
+
super(options);
|
|
384
|
+
if (options.journalId !== void 0) this.journalId = options.journalId;
|
|
385
|
+
if (options.accountId !== void 0) this.accountId = options.accountId;
|
|
386
|
+
if (options.debit !== void 0) this.debit = options.debit;
|
|
387
|
+
if (options.credit !== void 0) this.credit = options.credit;
|
|
388
|
+
if (options.currency !== void 0) this.currency = options.currency;
|
|
389
|
+
if (options.exchangeRate !== void 0) this.exchangeRate = options.exchangeRate;
|
|
390
|
+
if (options.memo !== void 0) this.memo = options.memo;
|
|
391
|
+
if (options.metadata !== void 0) this.metadata = options.metadata;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Validate entry before save
|
|
395
|
+
*/
|
|
396
|
+
async validateBeforeSave() {
|
|
397
|
+
await super.validateBeforeSave();
|
|
398
|
+
if (this.debit < 0) throw new Error("Debit amount cannot be negative");
|
|
399
|
+
if (this.credit < 0) throw new Error("Credit amount cannot be negative");
|
|
400
|
+
if (this.debit > 0 && this.credit > 0) throw new Error("Entry cannot have both debit and credit amounts");
|
|
401
|
+
if (this.debit === 0 && this.credit === 0) throw new Error("Entry must have either a debit or credit amount");
|
|
402
|
+
if (this.exchangeRate <= 0) throw new Error("Exchange rate must be positive");
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Check if this is a debit entry
|
|
406
|
+
*/
|
|
407
|
+
isDebit() {
|
|
408
|
+
return this.debit > 0;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Check if this is a credit entry
|
|
412
|
+
*/
|
|
413
|
+
isCredit() {
|
|
414
|
+
return this.credit > 0;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get the entry amount (positive value regardless of debit/credit)
|
|
418
|
+
*/
|
|
419
|
+
getAmount() {
|
|
420
|
+
return this.debit > 0 ? this.debit : this.credit;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get the amount in base currency
|
|
424
|
+
*/
|
|
425
|
+
getBaseAmount() {
|
|
426
|
+
return this.getAmount() * this.exchangeRate;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get the parent journal
|
|
430
|
+
*/
|
|
431
|
+
async getJournal() {
|
|
432
|
+
if (!this.journalId) return null;
|
|
433
|
+
const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
|
|
434
|
+
return await (await JournalCollection.create(this.options)).get({ id: this.journalId });
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Get the account
|
|
438
|
+
*/
|
|
439
|
+
async getAccount() {
|
|
440
|
+
if (!this.accountId) return null;
|
|
441
|
+
const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
|
|
442
|
+
return await (await AccountCollection.create(this.options)).get({ id: this.accountId });
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Get a formatted description of this entry
|
|
446
|
+
*/
|
|
447
|
+
async getDescription() {
|
|
448
|
+
const accountName = (await this.getAccount())?.name || "Unknown Account";
|
|
449
|
+
return `${this.isDebit() ? "DR" : "CR"} ${accountName}: $${this.getAmount().toFixed(2)}${this.memo ? ` - ${this.memo}` : ""}`;
|
|
450
|
+
}
|
|
483
451
|
};
|
|
484
|
-
__decorateClass$1([
|
|
485
|
-
|
|
486
|
-
], JournalEntry.prototype, "
|
|
487
|
-
__decorateClass$1([
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
return this.list({ where: { tenantId: tenantId2 } });
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Find all global journal entries (no tenant association).
|
|
679
|
-
*
|
|
680
|
-
* Routes through the shared tenant-global helper so it does not throw under
|
|
681
|
-
* an active tenant context. (#1600)
|
|
682
|
-
*
|
|
683
|
-
* @returns Array of global journal entries
|
|
684
|
-
*/
|
|
685
|
-
async findGlobal() {
|
|
686
|
-
return queryGlobal(this);
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Find journal entries for a tenant plus all global entries.
|
|
690
|
-
*
|
|
691
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
692
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
693
|
-
*
|
|
694
|
-
* @param tenantId - Tenant ID
|
|
695
|
-
* @returns Array of tenant's entries and global entries
|
|
696
|
-
*/
|
|
697
|
-
async findWithGlobals(tenantId2) {
|
|
698
|
-
return queryWithGlobals(
|
|
699
|
-
this,
|
|
700
|
-
tenantId2,
|
|
701
|
-
"JournalEntry.findWithGlobals"
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
const JournalEntries = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
706
|
-
__proto__: null,
|
|
707
|
-
JournalEntryCollection
|
|
708
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
452
|
+
__decorateClass$1([tenantId({ nullable: true })], JournalEntry.prototype, "tenantId", 2);
|
|
453
|
+
__decorateClass$1([foreignKey("Journal")], JournalEntry.prototype, "journalId", 2);
|
|
454
|
+
__decorateClass$1([foreignKey("Account")], JournalEntry.prototype, "accountId", 2);
|
|
455
|
+
JournalEntry = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
|
|
456
|
+
api: { include: ["list", "get"] },
|
|
457
|
+
mcp: { include: ["list", "get"] },
|
|
458
|
+
cli: true
|
|
459
|
+
})], JournalEntry);
|
|
460
|
+
//#endregion
|
|
461
|
+
//#region src/collections/JournalEntries.ts
|
|
462
|
+
var JournalEntries_exports = /* @__PURE__ */ __exportAll({ JournalEntryCollection: () => JournalEntryCollection });
|
|
463
|
+
var JournalEntryCollection = class extends SmrtCollection {
|
|
464
|
+
static _itemClass = JournalEntry;
|
|
465
|
+
/**
|
|
466
|
+
* Find entries by journal
|
|
467
|
+
*
|
|
468
|
+
* @param journalId - Journal ID
|
|
469
|
+
* @returns Array of entries
|
|
470
|
+
*/
|
|
471
|
+
async findByJournal(journalId) {
|
|
472
|
+
return await this.list({ where: { journalId } });
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Find entries by account
|
|
476
|
+
*
|
|
477
|
+
* @param accountId - Account ID
|
|
478
|
+
* @returns Array of entries
|
|
479
|
+
*/
|
|
480
|
+
async findByAccount(accountId) {
|
|
481
|
+
return await this.list({ where: { accountId } });
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get account balance
|
|
485
|
+
*
|
|
486
|
+
* For debit-normal accounts (Asset, Expense): balance = debits - credits
|
|
487
|
+
* For credit-normal accounts (Liability, Equity, Revenue): balance = credits - debits
|
|
488
|
+
*
|
|
489
|
+
* @param accountId - Account ID
|
|
490
|
+
* @param asOfDate - Optional date to calculate balance as of
|
|
491
|
+
* @returns Account balance
|
|
492
|
+
*/
|
|
493
|
+
async getAccountBalance(accountId, asOfDate) {
|
|
494
|
+
const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
|
|
495
|
+
const journalCollection = await JournalCollection.create(this.options);
|
|
496
|
+
const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
|
|
497
|
+
const account = await (await AccountCollection.create(this.options)).get({ id: accountId });
|
|
498
|
+
if (!account) throw new Error(`Account not found: ${accountId}`);
|
|
499
|
+
const allEntries = await this.findByAccount(accountId);
|
|
500
|
+
let totalDebits = 0;
|
|
501
|
+
let totalCredits = 0;
|
|
502
|
+
for (const entry of allEntries) {
|
|
503
|
+
const journal = await journalCollection.get({ id: entry.journalId });
|
|
504
|
+
if (!journal) continue;
|
|
505
|
+
if (!journal.isPosted()) continue;
|
|
506
|
+
if (asOfDate && journal.date > asOfDate) continue;
|
|
507
|
+
totalDebits += entry.debit;
|
|
508
|
+
totalCredits += entry.credit;
|
|
509
|
+
}
|
|
510
|
+
if (account.isDebitNormal()) return totalDebits - totalCredits;
|
|
511
|
+
else return totalCredits - totalDebits;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Get trial balance
|
|
515
|
+
*
|
|
516
|
+
* @param asOfDate - Optional date for trial balance
|
|
517
|
+
* @returns Array of trial balance rows
|
|
518
|
+
*/
|
|
519
|
+
async getTrialBalance(asOfDate) {
|
|
520
|
+
const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
|
|
521
|
+
const accounts = await (await AccountCollection.create(this.options)).findActive();
|
|
522
|
+
const rows = [];
|
|
523
|
+
for (const account of accounts) {
|
|
524
|
+
if (!account.id) continue;
|
|
525
|
+
const balance = await this.getAccountBalance(account.id, asOfDate);
|
|
526
|
+
if (Math.abs(balance) < .001) continue;
|
|
527
|
+
rows.push({
|
|
528
|
+
accountId: account.id,
|
|
529
|
+
accountNumber: account.number,
|
|
530
|
+
accountName: account.name,
|
|
531
|
+
accountType: account.type,
|
|
532
|
+
debitBalance: account.isDebitNormal() && balance > 0 ? balance : 0,
|
|
533
|
+
creditBalance: account.isCreditNormal() && balance > 0 ? balance : 0
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
rows.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
|
|
537
|
+
return rows;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Get total debits and credits for a date range
|
|
541
|
+
*
|
|
542
|
+
* @param start - Start date
|
|
543
|
+
* @param end - End date
|
|
544
|
+
* @returns Object with totalDebits and totalCredits
|
|
545
|
+
*/
|
|
546
|
+
async getTotalsForDateRange(start, end) {
|
|
547
|
+
const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
|
|
548
|
+
const journals = await (await JournalCollection.create(this.options)).findByDateRange(start, end);
|
|
549
|
+
let totalDebits = 0;
|
|
550
|
+
let totalCredits = 0;
|
|
551
|
+
for (const journal of journals) {
|
|
552
|
+
if (!journal.isPosted()) continue;
|
|
553
|
+
const entries = await this.findByJournal(journal.id);
|
|
554
|
+
for (const entry of entries) {
|
|
555
|
+
totalDebits += entry.debit;
|
|
556
|
+
totalCredits += entry.credit;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
totalDebits,
|
|
561
|
+
totalCredits
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Get entries for multiple accounts
|
|
566
|
+
*
|
|
567
|
+
* @param accountIds - Array of account IDs
|
|
568
|
+
* @returns Array of entries
|
|
569
|
+
*/
|
|
570
|
+
async findByAccounts(accountIds) {
|
|
571
|
+
if (accountIds.length === 0) return [];
|
|
572
|
+
return await this.list({ where: { accountId: accountIds } });
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Get the running balance for an account (list of entries with running total)
|
|
576
|
+
*
|
|
577
|
+
* @param accountId - Account ID
|
|
578
|
+
* @returns Array of entries with running balance
|
|
579
|
+
*/
|
|
580
|
+
async getAccountLedger(accountId) {
|
|
581
|
+
const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
|
|
582
|
+
const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
|
|
583
|
+
const journalCollection = await JournalCollection.create(this.options);
|
|
584
|
+
const account = await (await AccountCollection.create(this.options)).get({ id: accountId });
|
|
585
|
+
if (!account) throw new Error(`Account not found: ${accountId}`);
|
|
586
|
+
const entries = await this.findByAccount(accountId);
|
|
587
|
+
const ledger = [];
|
|
588
|
+
let runningBalance = 0;
|
|
589
|
+
const entriesWithJournals = await Promise.all(entries.map(async (entry) => ({
|
|
590
|
+
entry,
|
|
591
|
+
journal: await journalCollection.get({ id: entry.journalId })
|
|
592
|
+
})));
|
|
593
|
+
entriesWithJournals.sort((a, b) => {
|
|
594
|
+
if (!a.journal || !b.journal) return 0;
|
|
595
|
+
return a.journal.date.getTime() - b.journal.date.getTime();
|
|
596
|
+
});
|
|
597
|
+
for (const { entry, journal } of entriesWithJournals) {
|
|
598
|
+
if (!journal || !journal.isPosted()) continue;
|
|
599
|
+
if (account.isDebitNormal()) runningBalance += entry.debit - entry.credit;
|
|
600
|
+
else runningBalance += entry.credit - entry.debit;
|
|
601
|
+
ledger.push({
|
|
602
|
+
entry,
|
|
603
|
+
runningBalance
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return ledger;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Find all journal entries belonging to a specific tenant
|
|
610
|
+
*
|
|
611
|
+
* @param tenantId - Tenant ID
|
|
612
|
+
* @returns Array of tenant's journal entries
|
|
613
|
+
*/
|
|
614
|
+
async findByTenant(tenantId) {
|
|
615
|
+
return this.list({ where: { tenantId } });
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Find all global journal entries (no tenant association).
|
|
619
|
+
*
|
|
620
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
621
|
+
* an active tenant context. (#1600)
|
|
622
|
+
*
|
|
623
|
+
* @returns Array of global journal entries
|
|
624
|
+
*/
|
|
625
|
+
async findGlobal() {
|
|
626
|
+
return queryGlobal(this);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Find journal entries for a tenant plus all global entries.
|
|
630
|
+
*
|
|
631
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
632
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
633
|
+
*
|
|
634
|
+
* @param tenantId - Tenant ID
|
|
635
|
+
* @returns Array of tenant's entries and global entries
|
|
636
|
+
*/
|
|
637
|
+
async findWithGlobals(tenantId) {
|
|
638
|
+
return queryWithGlobals(this, tenantId, "JournalEntry.findWithGlobals");
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
//#endregion
|
|
642
|
+
//#region src/models/Journal.ts
|
|
709
643
|
var __defProp = Object.defineProperty;
|
|
710
644
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
711
645
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (kind && result) __defProp(target, key, result);
|
|
717
|
-
return result;
|
|
646
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
647
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
648
|
+
if (kind && result) __defProp(target, key, result);
|
|
649
|
+
return result;
|
|
718
650
|
};
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
*/
|
|
904
|
-
async summarize() {
|
|
905
|
-
const entries = await this.getEntries();
|
|
906
|
-
const debits = await this.getTotalDebits();
|
|
907
|
-
const balanced = await this.isBalanced();
|
|
908
|
-
const db = this.options.db ?? this.options.persistence;
|
|
909
|
-
const resolvedPrompt = await resolvePrompt(
|
|
910
|
-
smrtLedgersJournalSummarizePrompt.key,
|
|
911
|
-
{
|
|
912
|
-
db,
|
|
913
|
-
tenantId: this.tenantId,
|
|
914
|
-
variables: {
|
|
915
|
-
journalNumber: this.number || "",
|
|
916
|
-
journalDate: this.date.toISOString().split("T")[0],
|
|
917
|
-
journalDescription: this.description || "",
|
|
918
|
-
journalStatus: this.status || "",
|
|
919
|
-
// Currency prefix folded into the value rather than the template
|
|
920
|
-
// — see the `smrtLedgersJournalSummarizePrompt` comment block.
|
|
921
|
-
journalTotal: `$${debits.toFixed(2)}`,
|
|
922
|
-
entryCount: String(entries.length),
|
|
923
|
-
journalBalanced: balanced ? "Yes" : "No"
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
);
|
|
927
|
-
const ai = await this.getAiClient();
|
|
928
|
-
const response = await ai.message(
|
|
929
|
-
resolvedPrompt.text,
|
|
930
|
-
promptMessageOptions(resolvedPrompt.ai)
|
|
931
|
-
);
|
|
932
|
-
return response.trim();
|
|
933
|
-
}
|
|
651
|
+
var Journal = class extends SmrtObject {
|
|
652
|
+
tenantId = null;
|
|
653
|
+
/**
|
|
654
|
+
* Journal number (auto-generated sequence, e.g., "JNL-0001")
|
|
655
|
+
*/
|
|
656
|
+
number = "";
|
|
657
|
+
/**
|
|
658
|
+
* Transaction date
|
|
659
|
+
*/
|
|
660
|
+
date = /* @__PURE__ */ new Date();
|
|
661
|
+
/**
|
|
662
|
+
* Description of the financial event
|
|
663
|
+
*/
|
|
664
|
+
description = "";
|
|
665
|
+
/**
|
|
666
|
+
* Source module that created this journal (e.g., "smrt-commerce", "manual")
|
|
667
|
+
*/
|
|
668
|
+
sourceModule = "";
|
|
669
|
+
/**
|
|
670
|
+
* External reference (e.g., order ID, invoice number)
|
|
671
|
+
*/
|
|
672
|
+
sourceRef = null;
|
|
673
|
+
/**
|
|
674
|
+
* Journal status: draft, posted, voided
|
|
675
|
+
*/
|
|
676
|
+
status = "draft";
|
|
677
|
+
/**
|
|
678
|
+
* When the journal was posted (finalized)
|
|
679
|
+
*/
|
|
680
|
+
postedAt = null;
|
|
681
|
+
/**
|
|
682
|
+
* When the journal was voided
|
|
683
|
+
*/
|
|
684
|
+
voidedAt = null;
|
|
685
|
+
/**
|
|
686
|
+
* Reason for voiding (if voided)
|
|
687
|
+
*/
|
|
688
|
+
voidReason = null;
|
|
689
|
+
/**
|
|
690
|
+
* Extensible metadata
|
|
691
|
+
*/
|
|
692
|
+
metadata = {};
|
|
693
|
+
constructor(options = {}) {
|
|
694
|
+
super(options);
|
|
695
|
+
if (options.number !== void 0) this.number = options.number;
|
|
696
|
+
if (options.date !== void 0) this.date = options.date;
|
|
697
|
+
if (options.description !== void 0) this.description = options.description;
|
|
698
|
+
if (options.sourceModule !== void 0) this.sourceModule = options.sourceModule;
|
|
699
|
+
if (options.sourceRef !== void 0) this.sourceRef = options.sourceRef;
|
|
700
|
+
if (options.status !== void 0) this.status = options.status;
|
|
701
|
+
if (options.postedAt !== void 0) this.postedAt = options.postedAt;
|
|
702
|
+
if (options.voidedAt !== void 0) this.voidedAt = options.voidedAt;
|
|
703
|
+
if (options.voidReason !== void 0) this.voidReason = options.voidReason;
|
|
704
|
+
if (options.metadata !== void 0) this.metadata = options.metadata;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Check if journal is in draft status
|
|
708
|
+
*/
|
|
709
|
+
isDraft() {
|
|
710
|
+
return this.status === "draft";
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Check if journal has been posted
|
|
714
|
+
*/
|
|
715
|
+
isPosted() {
|
|
716
|
+
return this.status === "posted";
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Check if journal has been voided
|
|
720
|
+
*/
|
|
721
|
+
isVoided() {
|
|
722
|
+
return this.status === "voided";
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Check if journal can be modified
|
|
726
|
+
*/
|
|
727
|
+
isEditable() {
|
|
728
|
+
return this.status === "draft";
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Get all entries for this journal
|
|
732
|
+
*/
|
|
733
|
+
async getEntries() {
|
|
734
|
+
if (!this.id) return [];
|
|
735
|
+
const { JournalEntryCollection } = await Promise.resolve().then(() => JournalEntries_exports);
|
|
736
|
+
return await (await JournalEntryCollection.create(this.options)).findByJournal(this.id);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Calculate total debits
|
|
740
|
+
*/
|
|
741
|
+
async getTotalDebits() {
|
|
742
|
+
return (await this.getEntries()).reduce((sum, entry) => sum + entry.debit, 0);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Calculate total credits
|
|
746
|
+
*/
|
|
747
|
+
async getTotalCredits() {
|
|
748
|
+
return (await this.getEntries()).reduce((sum, entry) => sum + entry.credit, 0);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Check if journal entries are balanced (debits = credits)
|
|
752
|
+
*/
|
|
753
|
+
async isBalanced() {
|
|
754
|
+
const debits = await this.getTotalDebits();
|
|
755
|
+
const credits = await this.getTotalCredits();
|
|
756
|
+
return Math.abs(debits - credits) < BALANCE_EPSILON;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Add an entry to this journal (only if draft)
|
|
760
|
+
*/
|
|
761
|
+
async addEntry(data) {
|
|
762
|
+
if (!this.id) throw new Error("Journal must be saved before adding entries");
|
|
763
|
+
if (!this.isEditable()) throw new Error("Cannot add entries to a posted or voided journal");
|
|
764
|
+
const { JournalEntryCollection } = await Promise.resolve().then(() => JournalEntries_exports);
|
|
765
|
+
await (await (await JournalEntryCollection.create(this.options)).create({
|
|
766
|
+
journalId: this.id,
|
|
767
|
+
accountId: data.accountId,
|
|
768
|
+
debit: data.debit || 0,
|
|
769
|
+
credit: data.credit || 0,
|
|
770
|
+
currency: data.currency || "USD",
|
|
771
|
+
exchangeRate: data.exchangeRate || 1,
|
|
772
|
+
memo: data.memo || ""
|
|
773
|
+
})).save();
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Post the journal (finalize and make immutable)
|
|
777
|
+
*/
|
|
778
|
+
async post() {
|
|
779
|
+
if (!this.isDraft()) throw new Error("Only draft journals can be posted");
|
|
780
|
+
if (!await this.isBalanced()) {
|
|
781
|
+
const debits = await this.getTotalDebits();
|
|
782
|
+
const credits = await this.getTotalCredits();
|
|
783
|
+
throw new Error(`Journal is not balanced. Debits: ${debits}, Credits: ${credits}`);
|
|
784
|
+
}
|
|
785
|
+
if ((await this.getEntries()).length === 0) throw new Error("Journal must have at least one entry");
|
|
786
|
+
this.status = "posted";
|
|
787
|
+
this.postedAt = /* @__PURE__ */ new Date();
|
|
788
|
+
await this.save();
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Void the journal (mark as cancelled with reason)
|
|
792
|
+
*/
|
|
793
|
+
async void(reason) {
|
|
794
|
+
if (this.isVoided()) throw new Error("Journal is already voided");
|
|
795
|
+
this.status = "voided";
|
|
796
|
+
this.voidedAt = /* @__PURE__ */ new Date();
|
|
797
|
+
this.voidReason = reason;
|
|
798
|
+
await this.save();
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* AI-powered: Generate a summary of this journal.
|
|
802
|
+
*
|
|
803
|
+
* Uses the `smrtLedgers.journal.summarize` prompt registered via
|
|
804
|
+
* `@happyvertical/smrt-prompts`, allowing tenant- or instance-level
|
|
805
|
+
* overrides of the template, model, and parameters at runtime.
|
|
806
|
+
*
|
|
807
|
+
* Only non-PII journal fields (number, date, description, status, balanced
|
|
808
|
+
* flag) plus aggregate totals and entry count are sent to the AI provider.
|
|
809
|
+
* Internal foreign-key fields (tenantId, sourceRef, individual entry
|
|
810
|
+
* account IDs) and the extensible `metadata` blob are intentionally
|
|
811
|
+
* excluded.
|
|
812
|
+
*
|
|
813
|
+
* @returns Generated summary text
|
|
814
|
+
*/
|
|
815
|
+
async summarize() {
|
|
816
|
+
const entries = await this.getEntries();
|
|
817
|
+
const debits = await this.getTotalDebits();
|
|
818
|
+
const balanced = await this.isBalanced();
|
|
819
|
+
const db = this.options.db ?? this.options.persistence;
|
|
820
|
+
const resolvedPrompt = await resolvePrompt(smrtLedgersJournalSummarizePrompt.key, {
|
|
821
|
+
db,
|
|
822
|
+
tenantId: this.tenantId,
|
|
823
|
+
variables: {
|
|
824
|
+
journalNumber: this.number || "",
|
|
825
|
+
journalDate: this.date.toISOString().split("T")[0],
|
|
826
|
+
journalDescription: this.description || "",
|
|
827
|
+
journalStatus: this.status || "",
|
|
828
|
+
journalTotal: `$${debits.toFixed(2)}`,
|
|
829
|
+
entryCount: String(entries.length),
|
|
830
|
+
journalBalanced: balanced ? "Yes" : "No"
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
return (await (await this.getAiClient()).message(resolvedPrompt.text, promptMessageOptions(resolvedPrompt.ai))).trim();
|
|
834
|
+
}
|
|
934
835
|
};
|
|
935
|
-
__decorateClass([
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
],
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
/**
|
|
1142
|
-
* Find journals for a tenant plus all global journals.
|
|
1143
|
-
*
|
|
1144
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
1145
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1146
|
-
*
|
|
1147
|
-
* @param tenantId - Tenant ID
|
|
1148
|
-
* @returns Array of tenant's journals and global journals
|
|
1149
|
-
*/
|
|
1150
|
-
async findWithGlobals(tenantId2) {
|
|
1151
|
-
return queryWithGlobals(this, tenantId2, "Journal.findWithGlobals");
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
const Journals = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1155
|
-
__proto__: null,
|
|
1156
|
-
JournalCollection
|
|
1157
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
1158
|
-
export {
|
|
1159
|
-
Account,
|
|
1160
|
-
AccountCollection,
|
|
1161
|
-
Journal,
|
|
1162
|
-
JournalCollection,
|
|
1163
|
-
JournalEntry,
|
|
1164
|
-
JournalEntryCollection,
|
|
1165
|
-
promptMessageOptions,
|
|
1166
|
-
smrtLedgersJournalSummarizePrompt
|
|
836
|
+
__decorateClass([tenantId({ nullable: true })], Journal.prototype, "tenantId", 2);
|
|
837
|
+
Journal = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
838
|
+
api: { include: [
|
|
839
|
+
"list",
|
|
840
|
+
"get",
|
|
841
|
+
"create"
|
|
842
|
+
] },
|
|
843
|
+
mcp: { include: [
|
|
844
|
+
"list",
|
|
845
|
+
"get",
|
|
846
|
+
"create"
|
|
847
|
+
] },
|
|
848
|
+
cli: true
|
|
849
|
+
})], Journal);
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region src/collections/Journals.ts
|
|
852
|
+
var Journals_exports = /* @__PURE__ */ __exportAll({ JournalCollection: () => JournalCollection });
|
|
853
|
+
var JournalCollection = class extends SmrtCollection {
|
|
854
|
+
static _itemClass = Journal;
|
|
855
|
+
/**
|
|
856
|
+
* Generate next journal number
|
|
857
|
+
*
|
|
858
|
+
* Uses timestamp and random component to avoid collisions
|
|
859
|
+
* in concurrent environments without relying on shared state.
|
|
860
|
+
*/
|
|
861
|
+
generateJournalNumber() {
|
|
862
|
+
return `JNL-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Find journal by number
|
|
866
|
+
*
|
|
867
|
+
* @param number - Journal number
|
|
868
|
+
* @returns Journal or null
|
|
869
|
+
*/
|
|
870
|
+
async findByNumber(number) {
|
|
871
|
+
return (await this.list({
|
|
872
|
+
where: { number },
|
|
873
|
+
limit: 1
|
|
874
|
+
}))[0] || null;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Find journals by date range
|
|
878
|
+
*
|
|
879
|
+
* @param start - Start date
|
|
880
|
+
* @param end - End date
|
|
881
|
+
* @returns Array of journals
|
|
882
|
+
*/
|
|
883
|
+
async findByDateRange(start, end) {
|
|
884
|
+
return await this.list({
|
|
885
|
+
where: {
|
|
886
|
+
"date >=": start.toISOString(),
|
|
887
|
+
"date <=": end.toISOString()
|
|
888
|
+
},
|
|
889
|
+
orderBy: "date ASC"
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Find journals by source module
|
|
894
|
+
*
|
|
895
|
+
* @param sourceModule - Source module name
|
|
896
|
+
* @returns Array of journals
|
|
897
|
+
*/
|
|
898
|
+
async findBySource(sourceModule) {
|
|
899
|
+
return await this.list({
|
|
900
|
+
where: { sourceModule },
|
|
901
|
+
orderBy: "date DESC"
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Find journals by status
|
|
906
|
+
*
|
|
907
|
+
* @param status - Journal status
|
|
908
|
+
* @returns Array of journals
|
|
909
|
+
*/
|
|
910
|
+
async findByStatus(status) {
|
|
911
|
+
return await this.list({
|
|
912
|
+
where: { status },
|
|
913
|
+
orderBy: "date DESC"
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Find draft journals
|
|
918
|
+
*
|
|
919
|
+
* @returns Array of draft journals
|
|
920
|
+
*/
|
|
921
|
+
async findDrafts() {
|
|
922
|
+
return await this.findByStatus("draft");
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Find posted journals
|
|
926
|
+
*
|
|
927
|
+
* @returns Array of posted journals
|
|
928
|
+
*/
|
|
929
|
+
async findPosted() {
|
|
930
|
+
return await this.findByStatus("posted");
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Create a complete journal with entries
|
|
934
|
+
*
|
|
935
|
+
* @param data - Journal data with entries
|
|
936
|
+
* @returns Created journal
|
|
937
|
+
*/
|
|
938
|
+
async createWithEntries(data) {
|
|
939
|
+
let totalDebits = 0;
|
|
940
|
+
let totalCredits = 0;
|
|
941
|
+
for (const entry of data.entries) {
|
|
942
|
+
totalDebits += entry.debit || 0;
|
|
943
|
+
totalCredits += entry.credit || 0;
|
|
944
|
+
}
|
|
945
|
+
if (Math.abs(totalDebits - totalCredits) >= .001) throw new Error(`Entries are not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`);
|
|
946
|
+
if (data.entries.length === 0) throw new Error("Journal must have at least one entry");
|
|
947
|
+
const journalNumber = await this.generateJournalNumber();
|
|
948
|
+
const journal = await this.create({
|
|
949
|
+
number: journalNumber,
|
|
950
|
+
date: data.date || /* @__PURE__ */ new Date(),
|
|
951
|
+
description: data.description,
|
|
952
|
+
sourceModule: data.sourceModule || "manual",
|
|
953
|
+
sourceRef: data.sourceRef || null,
|
|
954
|
+
metadata: data.metadata || {}
|
|
955
|
+
});
|
|
956
|
+
await journal.save();
|
|
957
|
+
for (const entryData of data.entries) await journal.addEntry(entryData);
|
|
958
|
+
return journal;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Post a journal by ID
|
|
962
|
+
*
|
|
963
|
+
* @param journalId - Journal ID
|
|
964
|
+
* @returns Posted journal
|
|
965
|
+
*/
|
|
966
|
+
async post(journalId) {
|
|
967
|
+
const journal = await this.get({ id: journalId });
|
|
968
|
+
if (!journal) throw new Error(`Journal not found: ${journalId}`);
|
|
969
|
+
await journal.post();
|
|
970
|
+
return journal;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Void a journal by ID
|
|
974
|
+
*
|
|
975
|
+
* @param journalId - Journal ID
|
|
976
|
+
* @param reason - Reason for voiding
|
|
977
|
+
* @returns Voided journal
|
|
978
|
+
*/
|
|
979
|
+
async void(journalId, reason) {
|
|
980
|
+
const journal = await this.get({ id: journalId });
|
|
981
|
+
if (!journal) throw new Error(`Journal not found: ${journalId}`);
|
|
982
|
+
await journal.void(reason);
|
|
983
|
+
return journal;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Find journals by source reference
|
|
987
|
+
*
|
|
988
|
+
* @param sourceRef - External reference
|
|
989
|
+
* @returns Array of journals
|
|
990
|
+
*/
|
|
991
|
+
async findBySourceRef(sourceRef) {
|
|
992
|
+
return await this.list({
|
|
993
|
+
where: { sourceRef },
|
|
994
|
+
orderBy: "date DESC"
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get journals for a specific month
|
|
999
|
+
*
|
|
1000
|
+
* @param year - Year
|
|
1001
|
+
* @param month - Month (1-12)
|
|
1002
|
+
* @returns Array of journals
|
|
1003
|
+
*/
|
|
1004
|
+
async findByMonth(year, month) {
|
|
1005
|
+
const start = new Date(year, month - 1, 1);
|
|
1006
|
+
const end = new Date(year, month, 0, 23, 59, 59, 999);
|
|
1007
|
+
return await this.findByDateRange(start, end);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Find all journals belonging to a specific tenant
|
|
1011
|
+
*
|
|
1012
|
+
* @param tenantId - Tenant ID
|
|
1013
|
+
* @returns Array of tenant's journals
|
|
1014
|
+
*/
|
|
1015
|
+
async findByTenant(tenantId) {
|
|
1016
|
+
return this.list({ where: { tenantId } });
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Find all global journals (no tenant association).
|
|
1020
|
+
*
|
|
1021
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
1022
|
+
* an active tenant context. (#1600)
|
|
1023
|
+
*
|
|
1024
|
+
* @returns Array of global journals
|
|
1025
|
+
*/
|
|
1026
|
+
async findGlobal() {
|
|
1027
|
+
return queryGlobal(this);
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Find journals for a tenant plus all global journals.
|
|
1031
|
+
*
|
|
1032
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
1033
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1034
|
+
*
|
|
1035
|
+
* @param tenantId - Tenant ID
|
|
1036
|
+
* @returns Array of tenant's journals and global journals
|
|
1037
|
+
*/
|
|
1038
|
+
async findWithGlobals(tenantId) {
|
|
1039
|
+
return queryWithGlobals(this, tenantId, "Journal.findWithGlobals");
|
|
1040
|
+
}
|
|
1167
1041
|
};
|
|
1168
|
-
//#
|
|
1042
|
+
//#endregion
|
|
1043
|
+
export { Account, AccountCollection, Journal, JournalCollection, JournalEntry, JournalEntryCollection, promptMessageOptions, smrtLedgersJournalSummarizePrompt };
|
|
1044
|
+
|
|
1045
|
+
//# sourceMappingURL=index.js.map
|