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