@happyvertical/smrt-cli 0.57.1 → 0.57.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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/agents/cookbook-apply.md +19 -5
  3. package/dist/{AuditLogCollection-WDHL-_AF-BLRue0-Z.js → AuditLogCollection-WDHL-_AF-Sh7_M6IY.js} +1 -1
  4. package/dist/{OidcIdentity-BUblgQLs-DQzfrrkW.js → OidcIdentity-BUblgQLs-BWtGe644.js} +2 -2
  5. package/dist/{OidcIdentityCollection-ClOyaHeF-BA4QgOyy.js → OidcIdentityCollection-ClOyaHeF-6I4D0lkk.js} +1 -1
  6. package/dist/{ProfileAssetCollection-CRMcjLJ--BxEDTPbM.js → ProfileAssetCollection-CRMcjLJ--CeOMbB02.js} +3 -3
  7. package/dist/{ProfileCollection-C4INKh7Y-C28k9KAj.js → ProfileCollection-C4INKh7Y-BjWcOewU.js} +17 -17
  8. package/dist/{ProfileLinkCollection-DFfZRsgv-B4oOqnC5.js → ProfileLinkCollection-DFfZRsgv-Fx7AlzK6.js} +2 -2
  9. package/dist/{ProfileMetadataCollection-BIYKy4Y3-D07YJojE.js → ProfileMetadataCollection-BIYKy4Y3-RiM-YgsD.js} +1 -1
  10. package/dist/{ProfileMetafieldCollection-Buz7gzag-1uok776P.js → ProfileMetafieldCollection-Buz7gzag-BR0nRq0I.js} +1 -1
  11. package/dist/{ProfileRelationshipCollection-C4g_QLI4-CzNBurq5.js → ProfileRelationshipCollection-C4g_QLI4-B3j_wGoo.js} +1 -1
  12. package/dist/{ProfileTypeCollection-B7kwi1mU-JJeGJVd9.js → ProfileTypeCollection-B7kwi1mU-Cl5TDjjA.js} +2 -2
  13. package/dist/{apply-BNDIdixP.js → apply-C7kqOLl6.js} +114 -15
  14. package/dist/commands/cookbook/apply.d.ts +54 -6
  15. package/dist/commands/cookbook/apply.d.ts.map +1 -1
  16. package/dist/commands/cookbook/shared.d.ts.map +1 -1
  17. package/dist/commands/cookbook.d.ts.map +1 -1
  18. package/dist/commands/export.d.ts.map +1 -1
  19. package/dist/commands/kitchen/apply.d.ts.map +1 -1
  20. package/dist/{commands-DkW9egBl.js → commands-enfUxRz5.js} +7 -6
  21. package/dist/dist-BW-M9X2A.js +21 -0
  22. package/dist/{dist-CvOObIMo.js → dist-C2eShh2J.js} +333 -27
  23. package/dist/dist-CFZ0g7DU.js +1633 -0
  24. package/dist/{dist-mVN37-aQ.js → dist-Dj79J8vA.js} +3 -3
  25. package/dist/{dist-ByE1kAL-.js → dist-My5opbCg.js} +3 -3
  26. package/dist/index.js +11 -11
  27. package/dist/{oidcProvisioningPrimitives-DNP9EZlU-DrF67XCR.js → oidcProvisioningPrimitives-DNP9EZlU-BDiFOraT.js} +3 -3
  28. package/dist/{resolveIdentity-CkyqvctR-rQR3kTaK.js → resolveIdentity-CkyqvctR-Btj28mbx.js} +14 -14
  29. package/dist/{server-D2wRPnk0.js → server-CVEdVgJg.js} +1 -1
  30. package/package.json +10 -10
  31. package/dist/dist-C7z_OoIl.js +0 -1278
  32. package/dist/dist-TzhaT84W.js +0 -21
@@ -0,0 +1,1633 @@
1
+ import { a as isTenancyEnabled, d as getTenantId, f as isSuperAdminBypass, i as tenantId, l as withTenantGlobalRead, n as resolvePrompt, o as queryGlobal, r as TenantScoped, s as queryWithGlobals, t as definePrompt } from "./dist-Dj79J8vA.js";
2
+ import { ObjectRegistry, SmrtCollection, SmrtHierarchical, SmrtObject, detectEngine, field, foreignKey, smrt } from "@happyvertical/smrt-core";
3
+ import { BackfillTracker } from "@happyvertical/smrt-core/migrations";
4
+ //#region ../ledgers/dist/chunks/Ledger-DwxK6NRF.js
5
+ var __defProp$4 = Object.defineProperty;
6
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
7
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$4(obj, key, {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value
12
+ }) : obj[key] = value;
13
+ var __decorateClass$3 = (decorators, target, key, kind) => {
14
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
15
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
16
+ if (kind && result) __defProp$4(target, key, result);
17
+ return result;
18
+ };
19
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
20
+ var Ledger = class extends SmrtObject {
21
+ tenantId = null;
22
+ name = "";
23
+ currency = "USD";
24
+ balanceMode = "tolerant";
25
+ constructor(options = {}) {
26
+ super(options);
27
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
28
+ if (options.name !== void 0) this.name = options.name;
29
+ if (options.currency !== void 0) this.currency = options.currency;
30
+ if (options.balanceMode !== void 0) this.balanceMode = options.balanceMode;
31
+ }
32
+ async delete() {
33
+ throw new Error("Ledgers cannot be deleted");
34
+ }
35
+ async validateBeforeSave() {
36
+ await super.validateBeforeSave();
37
+ this.requireInsertOnSave({ preserveDerivedSlug: true });
38
+ if (!["tolerant", "exact-integer"].includes(this.balanceMode)) throw new Error("Unknown ledger balance mode");
39
+ if (this.id) {
40
+ const stored = await this.db.get(this.tableName, { id: this.id });
41
+ if (stored && (stored.currency !== this.currency || stored.balance_mode !== this.balanceMode || stored.tenant_id !== this.tenantId)) throw new Error("Ledger currency, balance mode and owner are immutable");
42
+ }
43
+ if (!this.name.trim()) throw new Error("Ledger name is required");
44
+ if (!/^[A-Z]{3}$/.test(this.currency)) throw new Error("Ledger currency must be an uppercase ISO 4217 code");
45
+ }
46
+ };
47
+ __publicField$3(Ledger, "__smrtPackage__", "@happyvertical/smrt-ledgers");
48
+ __decorateClass$3([tenantId({ nullable: true })], Ledger.prototype, "tenantId", 2);
49
+ __decorateClass$3([field({ description: "The accounting scope name." })], Ledger.prototype, "name", 2);
50
+ __decorateClass$3([field({ description: "ISO 4217 currency used by this ledger." })], Ledger.prototype, "currency", 2);
51
+ __decorateClass$3([field({ description: "tolerant accepts decimal units; exact requires integer minor units." })], Ledger.prototype, "balanceMode", 2);
52
+ Ledger = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
53
+ api: { include: [
54
+ "list",
55
+ "get",
56
+ "create",
57
+ "update"
58
+ ] },
59
+ mcp: { include: [
60
+ "list",
61
+ "get",
62
+ "create"
63
+ ] },
64
+ cli: { skipApiCheck: true }
65
+ })], Ledger);
66
+ //#endregion
67
+ //#region ../ledgers/dist/chunks/Account-IFuMKhPt.js
68
+ var __defProp$3 = Object.defineProperty;
69
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
70
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$3(obj, key, {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value
75
+ }) : obj[key] = value;
76
+ var __decorateClass$2 = (decorators, target, key, kind) => {
77
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
78
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
79
+ if (kind && result) __defProp$3(target, key, result);
80
+ return result;
81
+ };
82
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
83
+ var Account = class extends SmrtHierarchical {
84
+ tenantId = null;
85
+ number = "";
86
+ name = "";
87
+ description = "";
88
+ type = "asset";
89
+ ledgerId = null;
90
+ active = true;
91
+ /**
92
+ * Extensible metadata
93
+ */
94
+ metadata = {};
95
+ constructor(options = {}) {
96
+ super(options);
97
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
98
+ if (options.number !== void 0) this.number = options.number;
99
+ if (options.name !== void 0) this.name = options.name;
100
+ if (options.description !== void 0) this.description = options.description;
101
+ if (options.type !== void 0) this.type = options.type;
102
+ if (options.ledgerId !== void 0) this.ledgerId = options.ledgerId;
103
+ if (options.parentId !== void 0) this.parentId = options.parentId;
104
+ if (options.active !== void 0) this.active = options.active;
105
+ if (options.metadata !== void 0) this.metadata = options.metadata;
106
+ }
107
+ async validateBeforeSave() {
108
+ await super.validateBeforeSave();
109
+ this.requireInsertOnSave({ preserveDerivedSlug: true });
110
+ if (this.ledgerId) {
111
+ const { LedgerCollection } = await import("./dist-CFZ0g7DU.js").then((n) => n.t);
112
+ const ledger = await (await LedgerCollection.create(this.options)).get({ id: this.ledgerId }, { cache: false });
113
+ const owner = this.tenantId ?? (isTenancyEnabled() && !isSuperAdminBypass() ? getTenantId() ?? null : null);
114
+ if (!ledger || ledger.tenantId !== owner) throw new Error("Scoped account requires an accessible ledger with the same owner");
115
+ }
116
+ if (this.id) {
117
+ const stored = await this.db.get(this.tableName, { id: this.id });
118
+ if (stored && (stored.ledger_id !== this.ledgerId || stored.ledger_id && (stored.tenant_id !== this.tenantId || stored.type !== this.type))) throw new Error("Ledger account scope, owner and type are immutable");
119
+ }
120
+ }
121
+ async delete(options = {}) {
122
+ if ((this.id ? await this.db.get(this.tableName, { id: this.id }) : null)?.ledger_id) throw new Error("Ledger accounts cannot be deleted; deactivate them instead");
123
+ await super.delete(options);
124
+ }
125
+ /**
126
+ * Check if this is a top-level account (no parent)
127
+ */
128
+ isTopLevel() {
129
+ return this.parentId === null;
130
+ }
131
+ /**
132
+ * Check if this is a debit-normal account (Asset, Expense)
133
+ * Debit-normal accounts increase with debits
134
+ */
135
+ isDebitNormal() {
136
+ return this.type === "asset" || this.type === "expense";
137
+ }
138
+ /**
139
+ * Check if this is a credit-normal account (Liability, Equity, Revenue)
140
+ * Credit-normal accounts increase with credits
141
+ */
142
+ isCreditNormal() {
143
+ return this.type === "liability" || this.type === "equity" || this.type === "revenue";
144
+ }
145
+ /**
146
+ * Get full account path (e.g., "Assets > Current > Cash")
147
+ */
148
+ async getFullPath() {
149
+ return [...(await this.getAncestors()).map((a) => a.name), this.name].join(" > ");
150
+ }
151
+ /**
152
+ * Build tree node for this account and its children
153
+ */
154
+ async toTreeNode() {
155
+ const children = await this.getChildren();
156
+ const childNodes = await Promise.all(children.map((c) => c.toTreeNode()));
157
+ return {
158
+ account: this,
159
+ children: childNodes
160
+ };
161
+ }
162
+ /**
163
+ * Get the current balance of this account
164
+ */
165
+ async getBalance(asOfDate) {
166
+ if (!this.id) return 0;
167
+ const { JournalEntryCollection } = await import("./dist-CFZ0g7DU.js").then((n) => n.n);
168
+ return await (await JournalEntryCollection.create(this.options)).getAccountBalance(this.id, asOfDate);
169
+ }
170
+ /**
171
+ * Create a sub-account under this account
172
+ */
173
+ async createChild(options) {
174
+ if (!this.id) throw new Error("Account must be saved before creating children");
175
+ const { AccountCollection } = await import("./dist-CFZ0g7DU.js").then((n) => n.r);
176
+ const collection = await AccountCollection.create(this.options);
177
+ const parent = await collection.get({ id: this.id }, { cache: false });
178
+ if (!parent) throw new Error("Parent account must be persisted and accessible");
179
+ if (parent.ledgerId && (options.ledgerId !== void 0 && options.ledgerId !== parent.ledgerId || options.tenantId !== void 0 && options.tenantId !== parent.tenantId)) throw new Error("Scoped children must inherit the parent ledger and owner");
180
+ const account = await collection.create({
181
+ ...options,
182
+ ...parent.ledgerId ? {
183
+ ledgerId: parent.ledgerId,
184
+ tenantId: parent.tenantId
185
+ } : {},
186
+ parentId: parent.id,
187
+ type: parent.type
188
+ });
189
+ await account.save();
190
+ return account;
191
+ }
192
+ };
193
+ __publicField$2(Account, "__smrtPackage__", "@happyvertical/smrt-ledgers");
194
+ __decorateClass$2([tenantId({ nullable: true })], Account.prototype, "tenantId", 2);
195
+ __decorateClass$2([field({ description: "The account number in your chart of accounts." })], Account.prototype, "number", 2);
196
+ __decorateClass$2([field({ description: "What the account is called." })], Account.prototype, "name", 2);
197
+ __decorateClass$2([field({ description: "What the account is used for." })], Account.prototype, "description", 2);
198
+ __decorateClass$2([field({ description: "asset, liability, equity, revenue or expense." })], Account.prototype, "type", 2);
199
+ __decorateClass$2([field({
200
+ description: "The ledger policy this account belongs to.",
201
+ nullable: true
202
+ }), foreignKey(Ledger)], Account.prototype, "ledgerId", 2);
203
+ __decorateClass$2([field({ description: "Whether the account is in use; deactivate it instead of deleting it." })], Account.prototype, "active", 2);
204
+ Account = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
205
+ tableName: "ledger_accounts",
206
+ api: { include: [
207
+ "list",
208
+ "get",
209
+ "create",
210
+ "update",
211
+ "delete"
212
+ ] },
213
+ mcp: { include: [
214
+ "list",
215
+ "get",
216
+ "create"
217
+ ] },
218
+ cli: { skipApiCheck: true }
219
+ })], Account);
220
+ //#endregion
221
+ //#region ../ledgers/dist/types.js
222
+ var BALANCE_EPSILON = .001;
223
+ //#endregion
224
+ //#region ../ledgers/dist/index.js
225
+ var __defProp$2 = Object.defineProperty;
226
+ var __exportAll = (all, no_symbols) => {
227
+ let target = {};
228
+ for (var name in all) __defProp$2(target, name, {
229
+ get: all[name],
230
+ enumerable: true
231
+ });
232
+ if (!no_symbols) __defProp$2(target, Symbol.toStringTag, { value: "Module" });
233
+ return target;
234
+ };
235
+ ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-ledgers","packageVersion":"0.57.3","objects":{"@happyvertical/smrt-ledgers:AccountCollection":{"name":"accountcollection","className":"AccountCollection","qualifiedName":"@happyvertical/smrt-ledgers:AccountCollection","collection":"accounts","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/collections/Accounts.ts","packageName":"@happyvertical/smrt-ledgers","fields":{},"methods":{"findByNumber":{"name":"findByNumber","async":true,"parameters":[{"name":"number","type":"string","optional":false}],"returnType":"Promise<Account | null>","isStatic":false,"isPublic":true},"findByType":{"name":"findByType","async":true,"parameters":[{"name":"type","type":"AccountType","optional":false}],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true},"findActive":{"name":"findActive","async":true,"parameters":[],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true},"findTopLevel":{"name":"findTopLevel","async":true,"parameters":[],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true},"findChildren":{"name":"findChildren","async":true,"parameters":[{"name":"parentId","type":"string","optional":false}],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true},"getTree":{"name":"getTree","async":true,"parameters":[],"returnType":"Promise<AccountTree>","isStatic":false,"isPublic":true},"getOrCreateByNumber":{"name":"getOrCreateByNumber","async":true,"parameters":[{"name":"number","type":"string","optional":false},{"name":"defaults","type":"Partial<object>","optional":true,"memberTypes":["string","AccountType","string | null"]}],"returnType":"Promise<Account>","isStatic":false,"isPublic":true},"groupByType":{"name":"groupByType","async":true,"parameters":[],"returnType":"Promise<Record<AccountType, Account[]>>","isStatic":false,"isPublic":true},"getDescendants":{"name":"getDescendants","async":true,"parameters":[{"name":"accountId","type":"string","optional":false}],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true},"findByTenant":{"name":"findByTenant","async":true,"parameters":[{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true},"findGlobal":{"name":"findGlobal","async":true,"parameters":[],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true},"findWithGlobals":{"name":"findWithGlobals","async":true,"parameters":[{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<Account[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"ledger_accounts","api":{"include":["list","get","create","update","delete"]},"mcp":{"include":["list","get","create"]}},"extends":"SmrtCollection","extendsTypeArg":"Account","exportName":"AccountCollection","collectionExportName":"AccountCollectionCollection","schema":{"tableName":"ledger_accounts","ddl":"CREATE TABLE IF NOT EXISTS \\"ledger_accounts\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"parent_id\\" UUID,\\n \\"tenant_id\\" UUID,\\n \\"number\\" TEXT DEFAULT '',\\n \\"name\\" TEXT DEFAULT '',\\n \\"description\\" TEXT DEFAULT '',\\n \\"type\\" TEXT DEFAULT 'asset',\\n \\"ledger_id\\" UUID,\\n \\"active\\" BOOLEAN DEFAULT TRUE,\\n \\"metadata\\" JSON DEFAULT '{}'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"parent_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledger_accounts","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"number":{"type":"TEXT","notNull":false,"unique":false,"default":""},"name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"type":{"type":"TEXT","notNull":false,"unique":false,"default":"asset"},"ledger_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledgers","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"active":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true},"metadata":{"type":"JSON","notNull":false,"unique":false,"default":{}}},"indexes":[{"name":"ledger_accounts_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"ledger_accounts_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"ledger_accounts_parent_id_idx","columns":["parent_id"]},{"name":"ledger_accounts_ledger_id_idx","columns":["ledger_id"]}],"version":"caea63eb"},"importPath":"@happyvertical/smrt-ledgers"},"@happyvertical/smrt-ledgers:JournalEntryCollection":{"name":"journalentrycollection","className":"JournalEntryCollection","qualifiedName":"@happyvertical/smrt-ledgers:JournalEntryCollection","collection":"journalentries","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/collections/JournalEntries.ts","packageName":"@happyvertical/smrt-ledgers","fields":{},"methods":{"findByJournal":{"name":"findByJournal","async":true,"parameters":[{"name":"journalId","type":"string","optional":false}],"returnType":"Promise<JournalEntry[]>","isStatic":false,"isPublic":true},"findByAccount":{"name":"findByAccount","async":true,"parameters":[{"name":"accountId","type":"string","optional":false}],"returnType":"Promise<JournalEntry[]>","isStatic":false,"isPublic":true},"getCounterpartyBalances":{"name":"getCounterpartyBalances","async":true,"parameters":[{"name":"options","type":"PostedJournalMovementOptions","optional":false}],"returnType":"Promise<CounterpartyBalance[]>","isStatic":false,"isPublic":true},"getCounterpartyAging":{"name":"getCounterpartyAging","async":true,"parameters":[{"name":"options","type":"PostedJournalMovementOptions","optional":false},{"name":"asOf","type":"Date","optional":false}],"returnType":"Promise<CounterpartyAgingRow[]>","isStatic":false,"isPublic":true},"getAccountBalance":{"name":"getAccountBalance","async":true,"parameters":[{"name":"accountId","type":"string","optional":false},{"name":"asOfDate","type":"Date","optional":true}],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"getTrialBalance":{"name":"getTrialBalance","async":true,"parameters":[{"name":"asOfDate","type":"Date","optional":true}],"returnType":"Promise<TrialBalanceRow[]>","isStatic":false,"isPublic":true},"getTotalsForDateRange":{"name":"getTotalsForDateRange","async":true,"parameters":[{"name":"start","type":"Date","optional":false},{"name":"end","type":"Date","optional":false}],"returnType":"Promise<object>","isStatic":false,"isPublic":true},"findByAccounts":{"name":"findByAccounts","async":true,"parameters":[{"name":"accountIds","type":"string[]","optional":false}],"returnType":"Promise<JournalEntry[]>","isStatic":false,"isPublic":true},"listPostedMovements":{"name":"listPostedMovements","async":true,"parameters":[{"name":"options","type":"PostedJournalMovementOptions","optional":false}],"returnType":"Promise<PostedJournalMovement[]>","isStatic":false,"isPublic":true},"getAccountLedger":{"name":"getAccountLedger","async":true,"parameters":[{"name":"accountId","type":"string","optional":false}],"returnType":"Promise<Array<object>>","isStatic":false,"isPublic":true},"findByTenant":{"name":"findByTenant","async":true,"parameters":[{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<JournalEntry[]>","isStatic":false,"isPublic":true},"findGlobal":{"name":"findGlobal","async":true,"parameters":[],"returnType":"Promise<JournalEntry[]>","isStatic":false,"isPublic":true},"findWithGlobals":{"name":"findWithGlobals","async":true,"parameters":[{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<JournalEntry[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"JournalEntry","exportName":"JournalEntryCollection","collectionExportName":"JournalEntryCollectionCollection","schema":{"tableName":"journal_entries","ddl":"CREATE TABLE IF NOT EXISTS \\"journal_entries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"journal_id\\" UUID,\\n \\"account_id\\" UUID,\\n \\"counterparty_kind\\" TEXT,\\n \\"counterparty_id\\" TEXT,\\n \\"due_date\\" TIMESTAMP,\\n \\"settles_source_ref\\" TEXT,\\n \\"debit\\" REAL DEFAULT 0,\\n \\"credit\\" REAL DEFAULT 0,\\n \\"currency\\" TEXT DEFAULT 'USD',\\n \\"exchange_rate\\" REAL DEFAULT 1,\\n \\"memo\\" TEXT DEFAULT '',\\n \\"metadata\\" JSON DEFAULT '{}'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"journal_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"journals","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"account_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledger_accounts","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"counterparty_kind":{"type":"TEXT","notNull":false,"unique":false},"counterparty_id":{"type":"TEXT","notNull":false,"unique":false},"due_date":{"type":"TIMESTAMP","notNull":false,"unique":false},"settles_source_ref":{"type":"TEXT","notNull":false,"unique":false},"debit":{"type":"REAL","notNull":false,"unique":false,"default":0},"credit":{"type":"REAL","notNull":false,"unique":false,"default":0},"currency":{"type":"TEXT","notNull":false,"unique":false,"default":"USD"},"exchange_rate":{"type":"REAL","notNull":false,"unique":false,"default":1},"memo":{"type":"TEXT","notNull":false,"unique":false,"default":""},"metadata":{"type":"JSON","notNull":false,"unique":false,"default":{}}},"indexes":[{"name":"journal_entries_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"journal_entries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"journal_entries_journal_id_idx","columns":["journal_id"]},{"name":"journal_entries_account_id_idx","columns":["account_id"]}],"version":"e96b76a2"},"importPath":"@happyvertical/smrt-ledgers"},"@happyvertical/smrt-ledgers:JournalCollection":{"name":"journalcollection","className":"JournalCollection","qualifiedName":"@happyvertical/smrt-ledgers:JournalCollection","collection":"journals","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/collections/Journals.ts","packageName":"@happyvertical/smrt-ledgers","fields":{},"methods":{"findByNumber":{"name":"findByNumber","async":true,"parameters":[{"name":"number","type":"string","optional":false}],"returnType":"Promise<Journal | null>","isStatic":false,"isPublic":true},"findByDateRange":{"name":"findByDateRange","async":true,"parameters":[{"name":"start","type":"Date","optional":false},{"name":"end","type":"Date","optional":false}],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findBySource":{"name":"findBySource","async":true,"parameters":[{"name":"sourceModule","type":"string","optional":false}],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findByStatus":{"name":"findByStatus","async":true,"parameters":[{"name":"status","type":"JournalStatus","optional":false}],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findDrafts":{"name":"findDrafts","async":true,"parameters":[],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findPosted":{"name":"findPosted","async":true,"parameters":[],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"createWithEntries":{"name":"createWithEntries","async":true,"parameters":[{"name":"data","type":"CreateJournalData","optional":false}],"returnType":"Promise<Journal>","isStatic":false,"isPublic":true},"post":{"name":"post","async":true,"parameters":[{"name":"journalId","type":"string","optional":false}],"returnType":"Promise<Journal>","isStatic":false,"isPublic":true},"void":{"name":"void","async":true,"parameters":[{"name":"journalId","type":"string","optional":false},{"name":"reason","type":"string","optional":false}],"returnType":"Promise<Journal>","isStatic":false,"isPublic":true},"findBySourceRef":{"name":"findBySourceRef","async":true,"parameters":[{"name":"sourceRef","type":"string","optional":false}],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findByMonth":{"name":"findByMonth","async":true,"parameters":[{"name":"year","type":"number","optional":false},{"name":"month","type":"number","optional":false}],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findByTenant":{"name":"findByTenant","async":true,"parameters":[{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findGlobal":{"name":"findGlobal","async":true,"parameters":[],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true},"findWithGlobals":{"name":"findWithGlobals","async":true,"parameters":[{"name":"tenantId","type":"string","optional":false}],"returnType":"Promise<Journal[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":["list","get","create"]},"mcp":{"include":["list","get","create"]}},"extends":"SmrtCollection","extendsTypeArg":"Journal","exportName":"JournalCollection","collectionExportName":"JournalCollectionCollection","schema":{"tableName":"journals","ddl":"CREATE TABLE IF NOT EXISTS \\"journals\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"number\\" TEXT DEFAULT '',\\n \\"date\\" TIMESTAMP,\\n \\"description\\" TEXT DEFAULT '',\\n \\"source_module\\" TEXT DEFAULT '',\\n \\"source_ref\\" TEXT,\\n \\"ledger_id\\" UUID,\\n \\"idempotency_key\\" TEXT,\\n \\"request_digest\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'draft',\\n \\"posted_at\\" TIMESTAMP,\\n \\"voided_at\\" TIMESTAMP,\\n \\"void_reason\\" TEXT,\\n \\"metadata\\" JSON DEFAULT '{}'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"number":{"type":"TEXT","notNull":false,"unique":false,"default":""},"date":{"type":"TIMESTAMP","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_module":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_ref":{"type":"TEXT","notNull":false,"unique":false},"ledger_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledgers","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"idempotency_key":{"type":"TEXT","notNull":false,"unique":false},"request_digest":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"draft"},"posted_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"voided_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"void_reason":{"type":"TEXT","notNull":false,"unique":false},"metadata":{"type":"JSON","notNull":false,"unique":false,"default":{}}},"indexes":[{"name":"journals_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"journals_ledger_idempotency_key","columns":["tenant_id","ledger_id","idempotency_key"],"unique":true},{"name":"journals_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"journals_ledger_id_idx","columns":["ledger_id"]}],"version":"1e73dd60"},"importPath":"@happyvertical/smrt-ledgers"},"@happyvertical/smrt-ledgers:LedgerCollection":{"name":"ledgercollection","className":"LedgerCollection","qualifiedName":"@happyvertical/smrt-ledgers:LedgerCollection","collection":"ledgers","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/collections/Ledgers.ts","packageName":"@happyvertical/smrt-ledgers","fields":{},"methods":{},"decoratorConfig":{"api":{"include":["list","get","create","update"]},"mcp":{"include":["list","get","create"]}},"extends":"SmrtCollection","extendsTypeArg":"Ledger","exportName":"LedgerCollection","collectionExportName":"LedgerCollectionCollection","schema":{"tableName":"ledgers","ddl":"CREATE TABLE IF NOT EXISTS \\"ledgers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"name\\" TEXT,\\n \\"currency\\" TEXT,\\n \\"balance_mode\\" TEXT DEFAULT 'tolerant'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"name":{"type":"TEXT","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"balance_mode":{"type":"TEXT","notNull":false,"unique":false,"default":"tolerant"}},"indexes":[{"name":"ledgers_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"ledgers_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"be316e4f"},"importPath":"@happyvertical/smrt-ledgers"},"@happyvertical/smrt-ledgers:Account":{"name":"account","className":"Account","qualifiedName":"@happyvertical/smrt-ledgers:Account","collection":"accounts","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/models/Account.ts","packageName":"@happyvertical/smrt-ledgers","fields":{"parentId":{"type":"foreignKey","required":false,"related":"Account","_meta":{"nullable":true}},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"number":{"type":"text","required":false,"default":"","description":"The account number in your chart of accounts.","_meta":{"description":"The account number in your chart of accounts."}},"name":{"type":"text","required":false,"default":"","description":"What the account is called.","_meta":{"description":"What the account is called."}},"description":{"type":"text","required":false,"default":"","description":"What the account is used for.","_meta":{"description":"What the account is used for."}},"type":{"type":"text","required":false,"default":"asset","description":"asset, liability, equity, revenue or expense.","_meta":{"description":"asset, liability, equity, revenue or expense."},"enum":["asset","liability","equity","revenue","expense"]},"ledgerId":{"type":"foreignKey","required":false,"related":"Ledger","description":"The ledger policy this account belongs to.","_meta":{"description":"The ledger policy this account belongs to.","nullable":true}},"active":{"type":"boolean","required":false,"default":true,"description":"Whether the account is in use; deactivate it instead of deleting it.","_meta":{"description":"Whether the account is in use; deactivate it instead of deleting it."}},"metadata":{"type":"json","required":false,"default":{}}},"methods":{"getParent":{"name":"getParent","async":true,"parameters":[],"returnType":"Promise<this | null>","isStatic":false,"isPublic":true},"getChildren":{"name":"getChildren","async":true,"parameters":[],"returnType":"Promise<this[]>","isStatic":false,"isPublic":true},"getAncestors":{"name":"getAncestors","async":true,"parameters":[],"returnType":"Promise<this[]>","isStatic":false,"isPublic":true},"getDescendants":{"name":"getDescendants","async":true,"parameters":[],"returnType":"Promise<this[]>","isStatic":false,"isPublic":true},"getHierarchy":{"name":"getHierarchy","async":true,"parameters":[],"returnType":"Promise<HierarchyView<this>>","isStatic":false,"isPublic":true},"moveTo":{"name":"moveTo","async":true,"parameters":[{"name":"newParent","type":"this | string | null","optional":false,"unionBranches":[{"type":"this"},{"type":"string"},{"type":"null"}]}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"isTopLevel":{"name":"isTopLevel","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isDebitNormal":{"name":"isDebitNormal","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isCreditNormal":{"name":"isCreditNormal","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"getFullPath":{"name":"getFullPath","async":true,"parameters":[],"returnType":"Promise<string>","isStatic":false,"isPublic":true},"toTreeNode":{"name":"toTreeNode","async":true,"parameters":[],"returnType":"Promise<AccountTreeNode>","isStatic":false,"isPublic":true},"getBalance":{"name":"getBalance","async":true,"parameters":[{"name":"asOfDate","type":"Date","optional":true}],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"createChild":{"name":"createChild","async":true,"parameters":[{"name":"options","type":"Omit<AccountOptions, 'parentId' | 'type'>","optional":false}],"returnType":"Promise<Account>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"ledger_accounts","api":{"include":["list","get","create","update","delete"]},"mcp":{"include":["list","get","create"]},"cli":{"skipApiCheck":true},"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtHierarchical","exportName":"Account","collectionExportName":"AccountCollection","displayLabelField":"name","schema":{"tableName":"ledger_accounts","ddl":"CREATE TABLE IF NOT EXISTS \\"ledger_accounts\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"parent_id\\" UUID,\\n \\"tenant_id\\" UUID,\\n \\"number\\" TEXT DEFAULT '',\\n \\"name\\" TEXT DEFAULT '',\\n \\"description\\" TEXT DEFAULT '',\\n \\"type\\" TEXT DEFAULT 'asset',\\n \\"ledger_id\\" UUID,\\n \\"active\\" BOOLEAN DEFAULT TRUE,\\n \\"metadata\\" JSON DEFAULT '{}'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"parent_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledger_accounts","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"number":{"type":"TEXT","notNull":false,"unique":false,"default":""},"name":{"type":"TEXT","notNull":false,"unique":false,"default":""},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"type":{"type":"TEXT","notNull":false,"unique":false,"default":"asset"},"ledger_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledgers","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"active":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true},"metadata":{"type":"JSON","notNull":false,"unique":false,"default":{}}},"indexes":[{"name":"ledger_accounts_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"ledger_accounts_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"ledger_accounts_parent_id_idx","columns":["parent_id"]},{"name":"ledger_accounts_ledger_id_idx","columns":["ledger_id"]}],"version":"caea63eb"},"importPath":"@happyvertical/smrt-ledgers"},"@happyvertical/smrt-ledgers:Journal":{"name":"journal","className":"Journal","qualifiedName":"@happyvertical/smrt-ledgers:Journal","collection":"journals","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/models/Journal.ts","packageName":"@happyvertical/smrt-ledgers","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"number":{"type":"text","required":false,"default":"","description":"The journal's reference number.","_meta":{"description":"The journal's reference number."}},"date":{"type":"datetime","required":false,"description":"The date of the transaction.","_meta":{"description":"The date of the transaction."}},"description":{"type":"text","required":false,"default":"","description":"What the transaction was.","_meta":{"description":"What the transaction was."}},"sourceModule":{"type":"text","required":false,"default":""},"sourceRef":{"type":"text","required":false},"ledgerId":{"type":"foreignKey","required":false,"related":"Ledger","description":"The ledger policy that governs this journal.","_meta":{"description":"The ledger policy that governs this journal.","nullable":true}},"idempotencyKey":{"type":"text","required":false,"_meta":{"nullable":true}},"requestDigest":{"type":"text","required":false},"status":{"type":"text","required":false,"default":"draft","description":"draft until final, then posted, or voided.","_meta":{"description":"draft until final, then posted, or voided."},"enum":["draft","posted","voided"]},"postedAt":{"type":"datetime","required":false,"description":"When it was posted.","_meta":{"description":"When it was posted."}},"voidedAt":{"type":"datetime","required":false},"voidReason":{"type":"text","required":false},"metadata":{"type":"json","required":false,"default":{}}},"methods":{"isDraft":{"name":"isDraft","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isPosted":{"name":"isPosted","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isVoided":{"name":"isVoided","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isEditable":{"name":"isEditable","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"getEntries":{"name":"getEntries","async":true,"parameters":[],"returnType":"any","isStatic":false,"isPublic":true},"getTotalDebits":{"name":"getTotalDebits","async":true,"parameters":[],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"getTotalCredits":{"name":"getTotalCredits","async":true,"parameters":[],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"isBalanced":{"name":"isBalanced","async":true,"parameters":[],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true},"getLedger":{"name":"getLedger","async":true,"parameters":[],"returnType":"any","isStatic":false,"isPublic":true},"addEntry":{"name":"addEntry","async":true,"parameters":[{"name":"data","type":"JournalEntryData","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"post":{"name":"post","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"void":{"name":"void","async":true,"parameters":[{"name":"reason","type":"string","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"summarize":{"name":"summarize","async":true,"parameters":[],"returnType":"Promise<string>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":["list","get","create"]},"mcp":{"include":["list","get","create"]},"cli":{"skipApiCheck":true},"indexes":[{"name":"journals_ledger_idempotency_key","columns":["tenantId","ledgerId","idempotencyKey"],"unique":true}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Journal","collectionExportName":"JournalCollection","schema":{"tableName":"journals","ddl":"CREATE TABLE IF NOT EXISTS \\"journals\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"number\\" TEXT DEFAULT '',\\n \\"date\\" TIMESTAMP,\\n \\"description\\" TEXT DEFAULT '',\\n \\"source_module\\" TEXT DEFAULT '',\\n \\"source_ref\\" TEXT,\\n \\"ledger_id\\" UUID,\\n \\"idempotency_key\\" TEXT,\\n \\"request_digest\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'draft',\\n \\"posted_at\\" TIMESTAMP,\\n \\"voided_at\\" TIMESTAMP,\\n \\"void_reason\\" TEXT,\\n \\"metadata\\" JSON DEFAULT '{}'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"number":{"type":"TEXT","notNull":false,"unique":false,"default":""},"date":{"type":"TIMESTAMP","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_module":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_ref":{"type":"TEXT","notNull":false,"unique":false},"ledger_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledgers","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"idempotency_key":{"type":"TEXT","notNull":false,"unique":false},"request_digest":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"draft"},"posted_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"voided_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"void_reason":{"type":"TEXT","notNull":false,"unique":false},"metadata":{"type":"JSON","notNull":false,"unique":false,"default":{}}},"indexes":[{"name":"journals_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"journals_ledger_idempotency_key","columns":["tenant_id","ledger_id","idempotency_key"],"unique":true},{"name":"journals_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"journals_ledger_id_idx","columns":["ledger_id"]}],"version":"1e73dd60"},"importPath":"@happyvertical/smrt-ledgers"},"@happyvertical/smrt-ledgers:JournalEntry":{"name":"journalentry","className":"JournalEntry","qualifiedName":"@happyvertical/smrt-ledgers:JournalEntry","collection":"journalentries","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/models/JournalEntry.ts","packageName":"@happyvertical/smrt-ledgers","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"journalId":{"type":"foreignKey","required":false,"related":"Journal","description":"The journal this entry belongs to.","_meta":{"description":"The journal this entry belongs to."}},"accountId":{"type":"foreignKey","required":false,"related":"Account","description":"The account this entry changes.","_meta":{"description":"The account this entry changes."}},"counterpartyKind":{"type":"text","required":false,"description":"Generic counterparty type.","_meta":{"description":"Generic counterparty type.","nullable":true}},"counterpartyId":{"type":"text","required":false,"description":"Generic counterparty identifier.","_meta":{"description":"Generic counterparty identifier.","nullable":true}},"dueDate":{"type":"datetime","required":false,"description":"When this open receivable or payable is due.","_meta":{"description":"When this open receivable or payable is due."}},"settlesSourceRef":{"type":"text","required":false},"debit":{"type":"decimal","required":false,"default":0,"description":"The amount debited. Zero on a credit entry.","_meta":{"description":"The amount debited. Zero on a credit entry."}},"credit":{"type":"decimal","required":false,"default":0,"description":"The amount credited. Zero on a debit entry.","_meta":{"description":"The amount credited. Zero on a debit entry."}},"currency":{"type":"text","required":false,"default":"USD","description":"The currency of the amounts.","_meta":{"description":"The currency of the amounts."}},"exchangeRate":{"type":"decimal","required":false,"default":1},"memo":{"type":"text","required":false,"default":"","description":"A note on this entry.","_meta":{"description":"A note on this entry."}},"metadata":{"type":"json","required":false,"default":{}}},"methods":{"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"isDebit":{"name":"isDebit","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"isCredit":{"name":"isCredit","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"getAmount":{"name":"getAmount","async":false,"parameters":[],"returnType":"number","isStatic":false,"isPublic":true},"getBaseAmount":{"name":"getBaseAmount","async":false,"parameters":[],"returnType":"number","isStatic":false,"isPublic":true},"getJournal":{"name":"getJournal","async":true,"parameters":[],"returnType":"any","isStatic":false,"isPublic":true},"getAccount":{"name":"getAccount","async":true,"parameters":[],"returnType":"any","isStatic":false,"isPublic":true},"getDescription":{"name":"getDescription","async":true,"parameters":[],"returnType":"Promise<string>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"skipApiCheck":true},"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"JournalEntry","collectionExportName":"JournalEntryCollection","schema":{"tableName":"journal_entries","ddl":"CREATE TABLE IF NOT EXISTS \\"journal_entries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"journal_id\\" UUID,\\n \\"account_id\\" UUID,\\n \\"counterparty_kind\\" TEXT,\\n \\"counterparty_id\\" TEXT,\\n \\"due_date\\" TIMESTAMP,\\n \\"settles_source_ref\\" TEXT,\\n \\"debit\\" REAL DEFAULT 0,\\n \\"credit\\" REAL DEFAULT 0,\\n \\"currency\\" TEXT DEFAULT 'USD',\\n \\"exchange_rate\\" REAL DEFAULT 1,\\n \\"memo\\" TEXT DEFAULT '',\\n \\"metadata\\" JSON DEFAULT '{}'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"journal_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"journals","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"account_id":{"type":"UUID","referenceKind":"foreignKey","notNull":false,"unique":false,"foreignKey":{"table":"ledger_accounts","column":"id","onDelete":"NO ACTION","onUpdate":"CASCADE"}},"counterparty_kind":{"type":"TEXT","notNull":false,"unique":false},"counterparty_id":{"type":"TEXT","notNull":false,"unique":false},"due_date":{"type":"TIMESTAMP","notNull":false,"unique":false},"settles_source_ref":{"type":"TEXT","notNull":false,"unique":false},"debit":{"type":"REAL","notNull":false,"unique":false,"default":0},"credit":{"type":"REAL","notNull":false,"unique":false,"default":0},"currency":{"type":"TEXT","notNull":false,"unique":false,"default":"USD"},"exchange_rate":{"type":"REAL","notNull":false,"unique":false,"default":1},"memo":{"type":"TEXT","notNull":false,"unique":false,"default":""},"metadata":{"type":"JSON","notNull":false,"unique":false,"default":{}}},"indexes":[{"name":"journal_entries_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"journal_entries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"journal_entries_journal_id_idx","columns":["journal_id"]},{"name":"journal_entries_account_id_idx","columns":["account_id"]}],"version":"e96b76a2"},"importPath":"@happyvertical/smrt-ledgers"},"@happyvertical/smrt-ledgers:Ledger":{"name":"ledger","className":"Ledger","qualifiedName":"@happyvertical/smrt-ledgers:Ledger","collection":"ledgers","description":"A named accounting scope. Exact ledgers store one currency's minor units and reject fractional entries; legacy journals without a ledger retain the tolerant balance rule for backwards compatibility.","filePath":"/home/runner/work/smrt/smrt/packages/ledgers/src/models/Ledger.ts","packageName":"@happyvertical/smrt-ledgers","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"name":{"type":"text","required":false,"description":"The accounting scope name.","_meta":{"description":"The accounting scope name."}},"currency":{"type":"text","required":false,"description":"ISO 4217 currency used by this ledger.","_meta":{"description":"ISO 4217 currency used by this ledger."}},"balanceMode":{"type":"text","required":false,"default":"tolerant","description":"tolerant accepts decimal units; exact requires integer minor units.","_meta":{"description":"tolerant accepts decimal units; exact requires integer minor units."},"enum":["tolerant","exact-integer"]}},"methods":{"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":{"include":["list","get","create","update"]},"mcp":{"include":["list","get","create"]},"cli":{"skipApiCheck":true},"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"Ledger","collectionExportName":"LedgerCollection","displayLabelField":"name","schema":{"tableName":"ledgers","ddl":"CREATE TABLE IF NOT EXISTS \\"ledgers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"name\\" TEXT,\\n \\"currency\\" TEXT,\\n \\"balance_mode\\" TEXT DEFAULT 'tolerant'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"name":{"type":"TEXT","notNull":false,"unique":false},"currency":{"type":"TEXT","notNull":false,"unique":false},"balance_mode":{"type":"TEXT","notNull":false,"unique":false,"default":"tolerant"}},"indexes":[{"name":"ledgers_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"ledgers_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"be316e4f"},"importPath":"@happyvertical/smrt-ledgers"}},"moduleType":"smrt","recipes":[{"id":"ledgers.bookkeeping","className":"BookkeepingRecipe","label":"Bookkeeping","summary":"Keep your books: a chart of accounts and double-entry journals.","synonyms":["accounting","ledger","general ledger","journals","chart of accounts"],"models":["@happyvertical/smrt-ledgers:Account","@happyvertical/smrt-ledgers:Journal","@happyvertical/smrt-ledgers:JournalEntry"],"nav":[{"label":"Accounts","model":"@happyvertical/smrt-ledgers:Account","icon":"bank","description":"The accounts your books are kept in, like sales, rent and bank."},{"label":"Journals","model":"@happyvertical/smrt-ledgers:Journal","icon":"book","description":"The entries behind your accounts, so every dollar has a trail."}],"requires":[],"section":{"id":"accounting","label":"Accounting","icon":"bank","description":"Your books: accounts, journals and balances."},"options":{"@happyvertical/smrt-ledgers:Account":{"fields":{"metadata":{"visibility":"hidden"},"parentId":{"visibility":"hidden"}}},"@happyvertical/smrt-ledgers:Journal":{"fields":{"sourceModule":{"visibility":"hidden"},"sourceRef":{"visibility":"hidden"},"idempotencyKey":{"visibility":"hidden"},"requestDigest":{"visibility":"hidden"},"voidedAt":{"visibility":"hidden"},"voidReason":{"visibility":"hidden"},"metadata":{"visibility":"hidden"}}},"@happyvertical/smrt-ledgers:JournalEntry":{"fields":{"exchangeRate":{"visibility":"hidden"},"settlesSourceRef":{"visibility":"hidden"},"metadata":{"visibility":"hidden"}}}},"help":{"markdown":"## Overview\\n\\nBookkeeping keeps your accounts. You list the accounts you use (your chart of\\naccounts) and record each transaction as a journal whose entries debit and\\ncredit those accounts.\\n\\n## Tasks\\n\\n### Set up your accounts\\n\\n1. Create a new account.\\n2. Give it a **{field:number}** and a **{field:name}**.\\n3. Choose its **{field:type}**: asset, liability, equity, revenue or expense.\\n\\n### Record a transaction\\n\\n1. Create a new journal and add a **{field:description}**.\\n2. Add entries: pick the account on each and enter a **{field:debit}** or a\\n **{field:credit}**. They must add up to the same total.\\n3. Set the journal's **{field:status}** to posted when it is final. A posted\\n journal is voided, not edited.\\n","fieldRefs":["credit","debit","description","name","number","status","type"]},"demo":{"mode":"server","reasons":["Needs a server because its package does not build for a browser: core migrations (node:os, node:crypto); and its package depends on @happyvertical/smrt-tenancy, which do not build for a browser (#3624, #3626)."]}}],"smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-prompts","@happyvertical/smrt-tenancy"],"browser":{"status":"server-only","issues":["#3624","#3626"],"reason":"core migrations (node:os, node:crypto)","via":["@happyvertical/smrt-tenancy"]},"stampsConstructors":true}`));
236
+ var smrtLedgersJournalSummarizePrompt = definePrompt({
237
+ key: "smrtLedgers.journal.summarize",
238
+ template: `Summarize this accounting journal entry:
239
+ Number: {journalNumber}
240
+ Date: {journalDate}
241
+ Description: {journalDescription}
242
+ Status: {journalStatus}
243
+ Total: {journalTotal}
244
+ Entries: {entryCount}
245
+ Balanced: {journalBalanced}`,
246
+ editable: {
247
+ template: true,
248
+ profile: true,
249
+ model: true,
250
+ params: true
251
+ }
252
+ });
253
+ function promptMessageOptions(ai) {
254
+ return {
255
+ ...ai.params || {},
256
+ ...ai.model ? { model: ai.model } : {},
257
+ ...typeof ai.temperature === "number" ? { temperature: ai.temperature } : {},
258
+ ...typeof ai.maxTokens === "number" ? { maxTokens: ai.maxTokens } : {}
259
+ };
260
+ }
261
+ var Accounts_exports = /* @__PURE__ */ __exportAll({ AccountCollection: () => AccountCollection });
262
+ var AccountCollection = class extends SmrtCollection {
263
+ static _itemClass = Account;
264
+ /**
265
+ * Find account by number
266
+ *
267
+ * @param number - Account number
268
+ * @returns Account or null
269
+ */
270
+ async findByNumber(number) {
271
+ return (await this.list({
272
+ where: { number },
273
+ limit: 1
274
+ }))[0] || null;
275
+ }
276
+ /**
277
+ * Find accounts by type
278
+ *
279
+ * @param type - Account type
280
+ * @returns Array of accounts
281
+ */
282
+ async findByType(type) {
283
+ return await this.list({
284
+ where: { type },
285
+ orderBy: "number ASC"
286
+ });
287
+ }
288
+ /**
289
+ * Find all active accounts
290
+ *
291
+ * @returns Array of active accounts
292
+ */
293
+ async findActive() {
294
+ return await this.list({
295
+ where: { active: true },
296
+ orderBy: "number ASC"
297
+ });
298
+ }
299
+ /**
300
+ * Find top-level accounts (no parent)
301
+ *
302
+ * @returns Array of top-level accounts
303
+ */
304
+ async findTopLevel() {
305
+ return await this.list({
306
+ where: { parentId: null },
307
+ orderBy: "number ASC"
308
+ });
309
+ }
310
+ /**
311
+ * Find direct children of an account
312
+ *
313
+ * @param parentId - Parent account ID
314
+ * @returns Array of child accounts
315
+ */
316
+ async findChildren(parentId) {
317
+ return await this.list({
318
+ where: { parentId },
319
+ orderBy: "number ASC"
320
+ });
321
+ }
322
+ /**
323
+ * Get the complete account tree
324
+ *
325
+ * @returns AccountTree structure
326
+ */
327
+ async getTree() {
328
+ const allAccounts = await this.list({ orderBy: "number ASC" });
329
+ const accountMap = /* @__PURE__ */ new Map();
330
+ const childrenMap = /* @__PURE__ */ new Map();
331
+ for (const account of allAccounts) {
332
+ if (account.id) accountMap.set(account.id, account);
333
+ if (!childrenMap.has(account.parentId || "")) childrenMap.set(account.parentId || "", []);
334
+ childrenMap.get(account.parentId || "")?.push(account);
335
+ }
336
+ const buildNode = (account) => {
337
+ return {
338
+ account,
339
+ children: ((account.id ? childrenMap.get(account.id) : []) || []).map(buildNode)
340
+ };
341
+ };
342
+ return { roots: (childrenMap.get("") || []).map(buildNode) };
343
+ }
344
+ /**
345
+ * Get or create an account by number
346
+ *
347
+ * @param number - Account number
348
+ * @param defaults - Default values if creating
349
+ * @returns Account
350
+ */
351
+ async getOrCreateByNumber(number, defaults = {}) {
352
+ const existing = await this.findByNumber(number);
353
+ if (existing) return existing;
354
+ const account = await this.create({
355
+ number,
356
+ name: defaults.name || number,
357
+ description: defaults.description || "",
358
+ type: defaults.type || "asset",
359
+ parentId: defaults.parentId || null
360
+ });
361
+ await account.save();
362
+ return account;
363
+ }
364
+ /**
365
+ * Find accounts grouped by type
366
+ *
367
+ * @returns Record of type to accounts array
368
+ */
369
+ async groupByType() {
370
+ const accounts = await this.findActive();
371
+ const grouped = {
372
+ asset: [],
373
+ liability: [],
374
+ equity: [],
375
+ revenue: [],
376
+ expense: []
377
+ };
378
+ for (const account of accounts) grouped[account.type].push(account);
379
+ return grouped;
380
+ }
381
+ /**
382
+ * Get all descendants of an account recursively
383
+ *
384
+ * @param accountId - Account ID
385
+ * @returns Array of all descendant accounts
386
+ */
387
+ async getDescendants(accountId) {
388
+ const descendants = [];
389
+ const queue = [accountId];
390
+ while (queue.length > 0) {
391
+ const currentId = queue.shift();
392
+ if (!currentId) continue;
393
+ const children = await this.findChildren(currentId);
394
+ for (const child of children) {
395
+ descendants.push(child);
396
+ if (child.id) queue.push(child.id);
397
+ }
398
+ }
399
+ return descendants;
400
+ }
401
+ /**
402
+ * Find all accounts belonging to a specific tenant
403
+ *
404
+ * @param tenantId - Tenant ID
405
+ * @returns Array of tenant's accounts
406
+ */
407
+ async findByTenant(tenantId) {
408
+ return this.list({ where: { tenantId } });
409
+ }
410
+ /**
411
+ * Find all global accounts (no tenant association).
412
+ *
413
+ * Routes through the shared tenant-global helper so it does not throw under
414
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
415
+ * flagged as an isolation violation). (#1600)
416
+ *
417
+ * @returns Array of global accounts
418
+ */
419
+ async findGlobal() {
420
+ return queryGlobal(this);
421
+ }
422
+ /**
423
+ * Find accounts for a tenant plus all global accounts.
424
+ *
425
+ * Fails closed if an active tenant context requests a different tenant's
426
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
427
+ *
428
+ * @param tenantId - Tenant ID
429
+ * @returns Array of tenant's accounts and global accounts
430
+ */
431
+ async findWithGlobals(tenantId) {
432
+ return queryWithGlobals(this, tenantId, "Account.findWithGlobals");
433
+ }
434
+ };
435
+ var __defProp$1 = Object.defineProperty;
436
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
437
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, {
438
+ enumerable: true,
439
+ configurable: true,
440
+ writable: true,
441
+ value
442
+ }) : obj[key] = value;
443
+ var __decorateClass$1 = (decorators, target, key, kind) => {
444
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
445
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
446
+ if (kind && result) __defProp$1(target, key, result);
447
+ return result;
448
+ };
449
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
450
+ var JournalEntry = class extends SmrtObject {
451
+ tenantId = null;
452
+ journalId = "";
453
+ accountId = "";
454
+ counterpartyKind = null;
455
+ counterpartyId = null;
456
+ dueDate = null;
457
+ /** Source reference of the open item reduced by this settlement line. */
458
+ settlesSourceRef = null;
459
+ debit = 0;
460
+ credit = 0;
461
+ currency = "USD";
462
+ /**
463
+ * Exchange rate to base currency
464
+ */
465
+ exchangeRate = 1;
466
+ memo = "";
467
+ /**
468
+ * Extensible metadata
469
+ */
470
+ metadata = {};
471
+ constructor(options = {}) {
472
+ super(options);
473
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
474
+ if (options.journalId !== void 0) this.journalId = options.journalId;
475
+ if (options.accountId !== void 0) this.accountId = options.accountId;
476
+ if (options.counterpartyKind !== void 0) this.counterpartyKind = options.counterpartyKind;
477
+ if (options.counterpartyId !== void 0) this.counterpartyId = options.counterpartyId;
478
+ if (options.dueDate !== void 0) this.dueDate = options.dueDate;
479
+ if (options.settlesSourceRef !== void 0) this.settlesSourceRef = options.settlesSourceRef;
480
+ if (options.debit !== void 0) this.debit = options.debit;
481
+ if (options.credit !== void 0) this.credit = options.credit;
482
+ if (options.currency !== void 0) this.currency = options.currency;
483
+ if (options.exchangeRate !== void 0) this.exchangeRate = options.exchangeRate;
484
+ if (options.memo !== void 0) this.memo = options.memo;
485
+ if (options.metadata !== void 0) this.metadata = options.metadata;
486
+ }
487
+ async save(options = {}) {
488
+ return this.withTransaction(async () => {
489
+ const journal = await this.getJournal();
490
+ if (!journal) throw new Error("Journal entry requires an accessible parent journal");
491
+ if (journal.ledgerId) {
492
+ if (!journal.isDraft() || !journal.updated_at) throw new Error("Ledger entries are immutable after posting");
493
+ await journal.claimRevision(journal.updated_at);
494
+ }
495
+ return super.save(options);
496
+ });
497
+ }
498
+ /**
499
+ * Validate entry before save
500
+ */
501
+ async validateBeforeSave() {
502
+ await super.validateBeforeSave();
503
+ this.requireInsertOnSave({ preserveDerivedSlug: true });
504
+ const journal = await this.getJournal();
505
+ const owner = this.tenantId ?? (isTenancyEnabled() && !isSuperAdminBypass() ? getTenantId() ?? null : null);
506
+ if (!journal || journal.tenantId !== owner) throw new Error("Journal entry and parent must belong to the same tenant");
507
+ const account = await this.getAccount();
508
+ if (!account || account.ledgerId !== journal.ledgerId || journal.ledgerId && account.tenantId !== journal.tenantId) throw new Error("Journal entry account must belong to its journal ledger");
509
+ if (journal.ledgerId && !journal.isDraft()) throw new Error("Ledger entries are immutable after posting");
510
+ if (this.id) {
511
+ const stored = await this.db.get(this.tableName, { id: this.id });
512
+ if (stored && stored.journal_id !== this.journalId) throw new Error("Journal entry cannot be moved to another journal");
513
+ if (stored && stored.account_id !== this.accountId) {
514
+ const oldAccount = await this.loadAccount(String(stored.account_id));
515
+ if (!oldAccount || oldAccount.ledgerId !== journal.ledgerId) throw new Error("Stored entry account must belong to its journal ledger");
516
+ }
517
+ }
518
+ if (!Number.isFinite(this.debit) || !Number.isFinite(this.credit)) throw new Error("Entry amounts must be finite");
519
+ if (this.debit < 0) throw new Error("Debit amount cannot be negative");
520
+ if (this.credit < 0) throw new Error("Credit amount cannot be negative");
521
+ if (this.debit > 0 && this.credit > 0) throw new Error("Entry cannot have both debit and credit amounts");
522
+ if (this.debit === 0 && this.credit === 0) throw new Error("Entry must have either a debit or credit amount");
523
+ if (this.exchangeRate <= 0) throw new Error("Exchange rate must be positive");
524
+ if (!!this.counterpartyKind !== !!this.counterpartyId) throw new Error("Counterparty kind and ID must be provided together");
525
+ if (this.dueDate && (!this.counterpartyKind || !this.counterpartyId)) throw new Error("Due date requires a counterparty");
526
+ }
527
+ async delete(options = {}) {
528
+ await this.withTransaction(async () => {
529
+ const stored = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
530
+ if (stored) {
531
+ const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
532
+ const journal = await (await JournalCollection.create({ db: this.db })).get({ id: String(stored.journal_id) });
533
+ if (!journal || journal.tenantId !== stored.tenant_id) throw new Error("Journal entry requires its accessible same-tenant parent");
534
+ if (journal.ledgerId) {
535
+ if (!journal.isDraft() || !journal.updated_at) throw new Error("Ledger entries are immutable after posting");
536
+ await journal.claimRevision(journal.updated_at);
537
+ }
538
+ }
539
+ await super.delete(options);
540
+ });
541
+ }
542
+ /**
543
+ * Check if this is a debit entry
544
+ */
545
+ isDebit() {
546
+ return this.debit > 0;
547
+ }
548
+ /**
549
+ * Check if this is a credit entry
550
+ */
551
+ isCredit() {
552
+ return this.credit > 0;
553
+ }
554
+ /**
555
+ * Get the entry amount (positive value regardless of debit/credit)
556
+ */
557
+ getAmount() {
558
+ return this.debit > 0 ? this.debit : this.credit;
559
+ }
560
+ /**
561
+ * Get the amount in base currency
562
+ */
563
+ getBaseAmount() {
564
+ return this.getAmount() * this.exchangeRate;
565
+ }
566
+ /**
567
+ * Get the parent journal
568
+ */
569
+ async getJournal() {
570
+ if (!this.journalId) return null;
571
+ const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
572
+ return await (await JournalCollection.create(this.options)).get({ id: this.journalId }, { cache: false });
573
+ }
574
+ /**
575
+ * Get the account
576
+ */
577
+ async getAccount() {
578
+ if (!this.accountId) return null;
579
+ return this.loadAccount(this.accountId);
580
+ }
581
+ async loadAccount(accountId) {
582
+ const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
583
+ const collection = await AccountCollection.create(this.options);
584
+ const account = await collection.get({ id: accountId }, { cache: false });
585
+ if (account) return account;
586
+ const tenant = getTenantId();
587
+ if (!tenant) return null;
588
+ return withTenantGlobalRead(tenant, async () => {
589
+ return (await collection.list({
590
+ where: {
591
+ id: accountId,
592
+ tenantId: null,
593
+ ledgerId: null
594
+ },
595
+ limit: 1
596
+ }))[0] ?? null;
597
+ });
598
+ }
599
+ /**
600
+ * Get a formatted description of this entry
601
+ */
602
+ async getDescription() {
603
+ const accountName = (await this.getAccount())?.name || "Unknown Account";
604
+ return `${this.isDebit() ? "DR" : "CR"} ${accountName}: $${this.getAmount().toFixed(2)}${this.memo ? ` - ${this.memo}` : ""}`;
605
+ }
606
+ };
607
+ __publicField$1(JournalEntry, "__smrtPackage__", "@happyvertical/smrt-ledgers");
608
+ __decorateClass$1([tenantId({ nullable: true })], JournalEntry.prototype, "tenantId", 2);
609
+ __decorateClass$1([field({ description: "The journal this entry belongs to." }), foreignKey("Journal")], JournalEntry.prototype, "journalId", 2);
610
+ __decorateClass$1([field({ description: "The account this entry changes." }), foreignKey(Account)], JournalEntry.prototype, "accountId", 2);
611
+ __decorateClass$1([field({
612
+ description: "Generic counterparty type.",
613
+ nullable: true
614
+ })], JournalEntry.prototype, "counterpartyKind", 2);
615
+ __decorateClass$1([field({
616
+ description: "Generic counterparty identifier.",
617
+ nullable: true
618
+ })], JournalEntry.prototype, "counterpartyId", 2);
619
+ __decorateClass$1([field({ description: "When this open receivable or payable is due." })], JournalEntry.prototype, "dueDate", 2);
620
+ __decorateClass$1([field({ description: "The amount debited. Zero on a credit entry." })], JournalEntry.prototype, "debit", 2);
621
+ __decorateClass$1([field({ description: "The amount credited. Zero on a debit entry." })], JournalEntry.prototype, "credit", 2);
622
+ __decorateClass$1([field({ description: "The currency of the amounts." })], JournalEntry.prototype, "currency", 2);
623
+ __decorateClass$1([field({ description: "A note on this entry." })], JournalEntry.prototype, "memo", 2);
624
+ JournalEntry = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
625
+ api: { include: ["list", "get"] },
626
+ mcp: { include: ["list", "get"] },
627
+ cli: { skipApiCheck: true }
628
+ })], JournalEntry);
629
+ var JournalEntries_exports = /* @__PURE__ */ __exportAll({ JournalEntryCollection: () => JournalEntryCollection });
630
+ var MAX_MOVEMENT_ACCOUNTS = 1e3;
631
+ function storedUnit(value, label) {
632
+ const number = typeof value === "number" ? value : Number(value);
633
+ if (!Number.isFinite(number) || Math.abs(number) > Number.MAX_SAFE_INTEGER) throw new RangeError(`Posted journal ${label} is outside the supported numeric range.`);
634
+ return number;
635
+ }
636
+ var JournalEntryCollection = class extends SmrtCollection {
637
+ static _itemClass = JournalEntry;
638
+ /**
639
+ * Find entries by journal
640
+ *
641
+ * @param journalId - Journal ID
642
+ * @returns Array of entries
643
+ */
644
+ async findByJournal(journalId) {
645
+ return await this.list({ where: { journalId } });
646
+ }
647
+ /**
648
+ * Find entries by account
649
+ *
650
+ * @param accountId - Account ID
651
+ * @returns Array of entries
652
+ */
653
+ async findByAccount(accountId) {
654
+ return await this.list({ where: { accountId } });
655
+ }
656
+ /** Posted AR/AP net balances by opaque counterparty for selected accounts. */
657
+ async getCounterpartyBalances(options) {
658
+ const movements = await this.listPostedMovements(options);
659
+ const result = /* @__PURE__ */ new Map();
660
+ for (const movement of movements) {
661
+ const entry = await this.get({ id: movement.entryId });
662
+ if (!entry?.counterpartyKind || !entry.counterpartyId) continue;
663
+ const key = JSON.stringify([
664
+ entry.counterpartyKind,
665
+ entry.counterpartyId,
666
+ movement.currency
667
+ ]);
668
+ const row = result.get(key) ?? {
669
+ counterpartyKind: entry.counterpartyKind,
670
+ counterpartyId: entry.counterpartyId,
671
+ currency: movement.currency,
672
+ balance: 0
673
+ };
674
+ row.balance = storedUnit(row.balance + (movement.debit - movement.credit) * (options.normalBalance === "credit" ? -1 : 1), "counterparty balance");
675
+ result.set(key, row);
676
+ }
677
+ return [...result.values()].filter((row) => row.balance !== 0);
678
+ }
679
+ /**
680
+ * Open-item aging. An invoice/allocation line needs a dueDate and sourceRef;
681
+ * a settlement line names that source in settlesSourceRef. This avoids
682
+ * pretending that net counterparty movements are aging.
683
+ */
684
+ async getCounterpartyAging(options, asOf) {
685
+ const movements = await this.listPostedMovements({
686
+ ...options,
687
+ to: asOf
688
+ });
689
+ const rows = /* @__PURE__ */ new Map();
690
+ const balances = /* @__PURE__ */ new Map();
691
+ for (const movement of movements) {
692
+ const entry = await this.get({ id: movement.entryId });
693
+ const source = entry?.settlesSourceRef ?? movement.sourceRef;
694
+ if (!entry?.counterpartyKind || !entry.counterpartyId || !source) continue;
695
+ const key = JSON.stringify([
696
+ entry.counterpartyKind,
697
+ entry.counterpartyId,
698
+ movement.currency,
699
+ source
700
+ ]);
701
+ balances.set(key, storedUnit((balances.get(key) ?? 0) + (movement.debit - movement.credit) * (options.normalBalance === "credit" ? -1 : 1), "open item balance"));
702
+ if (!entry.settlesSourceRef && entry.dueDate) {
703
+ const prior = rows.get(key);
704
+ if (prior && prior.dueDate.getTime() !== entry.dueDate.getTime()) throw new Error("Open item source has conflicting due dates");
705
+ rows.set(key, {
706
+ counterpartyKind: entry.counterpartyKind,
707
+ counterpartyId: entry.counterpartyId,
708
+ currency: movement.currency,
709
+ sourceRef: source,
710
+ dueDate: entry.dueDate,
711
+ balance: 0,
712
+ current: 0,
713
+ days1To30: 0,
714
+ days31To60: 0,
715
+ days61To90: 0,
716
+ daysOver90: 0
717
+ });
718
+ }
719
+ }
720
+ for (const [key, row] of rows) row.balance = balances.get(key) ?? 0;
721
+ for (const row of rows.values()) {
722
+ if (row.balance <= 0) continue;
723
+ const days = Math.floor((asOf.getTime() - row.dueDate.getTime()) / 864e5);
724
+ if (days <= 0) row.current = row.balance;
725
+ else if (days <= 30) row.days1To30 = row.balance;
726
+ else if (days <= 60) row.days31To60 = row.balance;
727
+ else if (days <= 90) row.days61To90 = row.balance;
728
+ else row.daysOver90 = row.balance;
729
+ }
730
+ return [...rows.values()].filter((row) => row.balance > 0);
731
+ }
732
+ /**
733
+ * Get account balance
734
+ *
735
+ * For debit-normal accounts (Asset, Expense): balance = debits - credits
736
+ * For credit-normal accounts (Liability, Equity, Revenue): balance = credits - debits
737
+ *
738
+ * @param accountId - Account ID
739
+ * @param asOfDate - Optional date to calculate balance as of
740
+ * @returns Account balance
741
+ */
742
+ async getAccountBalance(accountId, asOfDate) {
743
+ const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
744
+ const journalCollection = await JournalCollection.create(this.options);
745
+ const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
746
+ const account = await (await AccountCollection.create(this.options)).get({ id: accountId });
747
+ if (!account) throw new Error(`Account not found: ${accountId}`);
748
+ const allEntries = await this.findByAccount(accountId);
749
+ let totalDebits = 0;
750
+ let totalCredits = 0;
751
+ for (const entry of allEntries) {
752
+ const journal = await journalCollection.get({ id: entry.journalId });
753
+ if (!journal) continue;
754
+ if (!journal.isPosted()) continue;
755
+ if (asOfDate && journal.date > asOfDate) continue;
756
+ totalDebits += entry.debit;
757
+ totalCredits += entry.credit;
758
+ }
759
+ if (account.isDebitNormal()) return totalDebits - totalCredits;
760
+ else return totalCredits - totalDebits;
761
+ }
762
+ /**
763
+ * Get trial balance
764
+ *
765
+ * @param asOfDate - Optional date for trial balance
766
+ * @returns Array of trial balance rows
767
+ */
768
+ async getTrialBalance(asOfDate) {
769
+ const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
770
+ const accounts = await (await AccountCollection.create(this.options)).findActive();
771
+ const rows = [];
772
+ for (const account of accounts) {
773
+ if (!account.id) continue;
774
+ const balance = await this.getAccountBalance(account.id, asOfDate);
775
+ if (Math.abs(balance) < .001) continue;
776
+ rows.push({
777
+ accountId: account.id,
778
+ accountNumber: account.number,
779
+ accountName: account.name,
780
+ accountType: account.type,
781
+ debitBalance: account.isDebitNormal() && balance > 0 ? balance : 0,
782
+ creditBalance: account.isCreditNormal() && balance > 0 ? balance : 0
783
+ });
784
+ }
785
+ rows.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber));
786
+ return rows;
787
+ }
788
+ /**
789
+ * Get total debits and credits for a date range
790
+ *
791
+ * @param start - Start date
792
+ * @param end - End date
793
+ * @returns Object with totalDebits and totalCredits
794
+ */
795
+ async getTotalsForDateRange(start, end) {
796
+ const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
797
+ const journals = await (await JournalCollection.create(this.options)).findByDateRange(start, end);
798
+ let totalDebits = 0;
799
+ let totalCredits = 0;
800
+ for (const journal of journals) {
801
+ if (!journal.isPosted()) continue;
802
+ const entries = await this.findByJournal(journal.id);
803
+ for (const entry of entries) {
804
+ totalDebits += entry.debit;
805
+ totalCredits += entry.credit;
806
+ }
807
+ }
808
+ return {
809
+ totalDebits,
810
+ totalCredits
811
+ };
812
+ }
813
+ /**
814
+ * Get entries for multiple accounts
815
+ *
816
+ * @param accountIds - Array of account IDs
817
+ * @returns Array of entries
818
+ */
819
+ async findByAccounts(accountIds) {
820
+ if (accountIds.length === 0) return [];
821
+ return await this.list({ where: { accountId: accountIds } });
822
+ }
823
+ /**
824
+ * Read tenant-owned movements from posted journals only. Amounts deliberately
825
+ * remain raw ledger units; commerce validates its minor-unit convention at
826
+ * the reporting boundary.
827
+ */
828
+ async listPostedMovements(options) {
829
+ if (getTenantId() && getTenantId() !== options.tenantId) throw new Error("Posted movements tenant differs from active context");
830
+ const accountIds = [...new Set(options.accountIds)];
831
+ if (!options.tenantId?.trim() || !options.currency?.trim() || !(options.from instanceof Date) || !(options.to instanceof Date) || !Number.isFinite(options.from.getTime()) || !Number.isFinite(options.to.getTime()) || options.from > options.to) throw new Error("Posted movements require tenant, currency and ordered dates.");
832
+ if (accountIds.length === 0) return [];
833
+ if (accountIds.length > MAX_MOVEMENT_ACCOUNTS) throw new RangeError(`Posted movements allow at most ${MAX_MOVEMENT_ACCOUNTS} accounts per query.`);
834
+ if (accountIds.some((id) => typeof id !== "string" || !id || id.trim() !== id)) throw new Error("Posted movement account IDs must be nonempty.");
835
+ const placeholders = accountIds.map(() => "?").join(", ");
836
+ return (await this.db.query(`SELECT e.id AS entry_id, e.journal_id, e.account_id, e.currency,
837
+ e.debit, e.credit, j.date AS journal_date, j.number AS journal_number,
838
+ j.source_module, j.source_ref
839
+ FROM journal_entries e
840
+ JOIN journals j ON j.id = e.journal_id AND j.tenant_id = e.tenant_id
841
+ WHERE e.tenant_id = ? AND e.account_id IN (${placeholders})
842
+ AND e.currency = ? AND j.status = 'posted'
843
+ AND j.date >= ? AND j.date <= ?
844
+ ORDER BY j.date ASC, j.id ASC, e.id ASC`, options.tenantId, ...accountIds, options.currency, options.from.toISOString(), options.to.toISOString())).rows.map((row) => {
845
+ const debit = storedUnit(row.debit, "debit");
846
+ const credit = storedUnit(row.credit, "credit");
847
+ const movement = credit - debit;
848
+ if (!Number.isFinite(movement) || Math.abs(movement) > Number.MAX_SAFE_INTEGER) throw new RangeError("Posted journal movement is outside the supported numeric range.");
849
+ return {
850
+ tenantId: options.tenantId,
851
+ entryId: String(row.entry_id),
852
+ journalId: String(row.journal_id),
853
+ accountId: String(row.account_id),
854
+ journalDate: new Date(String(row.journal_date)),
855
+ journalNumber: String(row.journal_number),
856
+ sourceModule: String(row.source_module),
857
+ sourceRef: row.source_ref == null ? null : String(row.source_ref),
858
+ currency: String(row.currency),
859
+ debit,
860
+ credit,
861
+ creditMinusDebit: movement
862
+ };
863
+ });
864
+ }
865
+ /**
866
+ * Get the running balance for an account (list of entries with running total)
867
+ *
868
+ * @param accountId - Account ID
869
+ * @returns Array of entries with running balance
870
+ */
871
+ async getAccountLedger(accountId) {
872
+ const { JournalCollection } = await Promise.resolve().then(() => Journals_exports);
873
+ const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
874
+ const journalCollection = await JournalCollection.create(this.options);
875
+ const account = await (await AccountCollection.create(this.options)).get({ id: accountId });
876
+ if (!account) throw new Error(`Account not found: ${accountId}`);
877
+ const entries = await this.findByAccount(accountId);
878
+ const ledger = [];
879
+ let runningBalance = 0;
880
+ const entriesWithJournals = await Promise.all(entries.map(async (entry) => ({
881
+ entry,
882
+ journal: await journalCollection.get({ id: entry.journalId })
883
+ })));
884
+ entriesWithJournals.sort((a, b) => {
885
+ if (!a.journal || !b.journal) return 0;
886
+ return a.journal.date.getTime() - b.journal.date.getTime();
887
+ });
888
+ for (const { entry, journal } of entriesWithJournals) {
889
+ if (!journal?.isPosted()) continue;
890
+ if (account.isDebitNormal()) runningBalance += entry.debit - entry.credit;
891
+ else runningBalance += entry.credit - entry.debit;
892
+ ledger.push({
893
+ entry,
894
+ runningBalance
895
+ });
896
+ }
897
+ return ledger;
898
+ }
899
+ /**
900
+ * Find all journal entries belonging to a specific tenant
901
+ *
902
+ * @param tenantId - Tenant ID
903
+ * @returns Array of tenant's journal entries
904
+ */
905
+ async findByTenant(tenantId) {
906
+ return this.list({ where: { tenantId } });
907
+ }
908
+ /**
909
+ * Find all global journal entries (no tenant association).
910
+ *
911
+ * Routes through the shared tenant-global helper so it does not throw under
912
+ * an active tenant context. (#1600)
913
+ *
914
+ * @returns Array of global journal entries
915
+ */
916
+ async findGlobal() {
917
+ return queryGlobal(this);
918
+ }
919
+ /**
920
+ * Find journal entries for a tenant plus all global entries.
921
+ *
922
+ * Fails closed if an active tenant context requests a different tenant's
923
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
924
+ *
925
+ * @param tenantId - Tenant ID
926
+ * @returns Array of tenant's entries and global entries
927
+ */
928
+ async findWithGlobals(tenantId) {
929
+ return queryWithGlobals(this, tenantId, "JournalEntry.findWithGlobals");
930
+ }
931
+ };
932
+ var __defProp = Object.defineProperty;
933
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
934
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
935
+ enumerable: true,
936
+ configurable: true,
937
+ writable: true,
938
+ value
939
+ }) : obj[key] = value;
940
+ var __decorateClass = (decorators, target, key, kind) => {
941
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
942
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
943
+ if (kind && result) __defProp(target, key, result);
944
+ return result;
945
+ };
946
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
947
+ var Journal = class extends SmrtObject {
948
+ tenantId = null;
949
+ number = "";
950
+ date = /* @__PURE__ */ new Date();
951
+ description = "";
952
+ /**
953
+ * Source module that created this journal (e.g., "smrt-commerce", "manual")
954
+ */
955
+ sourceModule = "";
956
+ /**
957
+ * External reference (e.g., order ID, invoice number)
958
+ */
959
+ sourceRef = null;
960
+ ledgerId = null;
961
+ idempotencyKey = null;
962
+ requestDigest = null;
963
+ status = "draft";
964
+ postedAt = null;
965
+ /**
966
+ * When the journal was voided
967
+ */
968
+ voidedAt = null;
969
+ /**
970
+ * Reason for voiding (if voided)
971
+ */
972
+ voidReason = null;
973
+ /**
974
+ * Extensible metadata
975
+ */
976
+ metadata = {};
977
+ constructor(options = {}) {
978
+ super(options);
979
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
980
+ if (options.number !== void 0) this.number = options.number;
981
+ if (options.date !== void 0) this.date = options.date;
982
+ if (options.description !== void 0) this.description = options.description;
983
+ if (options.sourceModule !== void 0) this.sourceModule = options.sourceModule;
984
+ if (options.sourceRef !== void 0) this.sourceRef = options.sourceRef;
985
+ if (options.ledgerId !== void 0) this.ledgerId = options.ledgerId;
986
+ if (options.idempotencyKey !== void 0) this.idempotencyKey = options.idempotencyKey;
987
+ if (options.requestDigest !== void 0) this.requestDigest = options.requestDigest;
988
+ if (options.status !== void 0) this.status = options.status;
989
+ if (options.postedAt !== void 0) this.postedAt = options.postedAt;
990
+ if (options.voidedAt !== void 0) this.voidedAt = options.voidedAt;
991
+ if (options.voidReason !== void 0) this.voidReason = options.voidReason;
992
+ if (options.metadata !== void 0) this.metadata = options.metadata;
993
+ }
994
+ /**
995
+ * Check if journal is in draft status
996
+ */
997
+ isDraft() {
998
+ return this.status === "draft";
999
+ }
1000
+ /**
1001
+ * Check if journal has been posted
1002
+ */
1003
+ isPosted() {
1004
+ return this.status === "posted";
1005
+ }
1006
+ /**
1007
+ * Check if journal has been voided
1008
+ */
1009
+ isVoided() {
1010
+ return this.status === "voided";
1011
+ }
1012
+ /**
1013
+ * Check if journal can be modified
1014
+ */
1015
+ isEditable() {
1016
+ return this.status === "draft";
1017
+ }
1018
+ /**
1019
+ * Get all entries for this journal
1020
+ */
1021
+ async getEntries() {
1022
+ if (!this.id) return [];
1023
+ const { JournalEntryCollection } = await Promise.resolve().then(() => JournalEntries_exports);
1024
+ return await (await JournalEntryCollection.create(this.options)).findByJournal(this.id);
1025
+ }
1026
+ /**
1027
+ * Calculate total debits
1028
+ */
1029
+ async getTotalDebits() {
1030
+ return (await this.getEntries()).reduce((sum, entry) => sum + entry.debit, 0);
1031
+ }
1032
+ /**
1033
+ * Calculate total credits
1034
+ */
1035
+ async getTotalCredits() {
1036
+ return (await this.getEntries()).reduce((sum, entry) => sum + entry.credit, 0);
1037
+ }
1038
+ /**
1039
+ * Check if journal entries are balanced (debits = credits)
1040
+ */
1041
+ async isBalanced() {
1042
+ const debits = await this.getTotalDebits();
1043
+ const credits = await this.getTotalCredits();
1044
+ const ledger = await this.getLedger();
1045
+ if (!ledger || ledger.balanceMode === "tolerant") return Math.abs(debits - credits) < BALANCE_EPSILON;
1046
+ const entries = await this.getEntries();
1047
+ return Number.isSafeInteger(debits) && Number.isSafeInteger(credits) && entries.every((entry) => Number.isSafeInteger(entry.debit) && Number.isSafeInteger(entry.credit)) && debits === credits;
1048
+ }
1049
+ async getLedger() {
1050
+ if (!this.ledgerId) return null;
1051
+ const { LedgerCollection } = await Promise.resolve().then(() => Ledgers_exports);
1052
+ return (await LedgerCollection.create(this.options)).get({ id: this.ledgerId });
1053
+ }
1054
+ /**
1055
+ * Add an entry to this journal (only if draft)
1056
+ */
1057
+ async addEntry(data) {
1058
+ if (!this.id) throw new Error("Journal must be saved before adding entries");
1059
+ if (!this.isEditable()) throw new Error("Cannot add entries to a posted or voided journal");
1060
+ const { JournalEntryCollection } = await Promise.resolve().then(() => JournalEntries_exports);
1061
+ const entry = await (await JournalEntryCollection.create(this.options)).create({
1062
+ journalId: this.id,
1063
+ tenantId: this.tenantId,
1064
+ accountId: data.accountId,
1065
+ counterpartyKind: data.counterpartyKind ?? null,
1066
+ counterpartyId: data.counterpartyId ?? null,
1067
+ dueDate: data.dueDate ?? null,
1068
+ settlesSourceRef: data.settlesSourceRef ?? null,
1069
+ debit: data.debit || 0,
1070
+ credit: data.credit || 0,
1071
+ currency: data.currency || "USD",
1072
+ exchangeRate: data.exchangeRate || 1,
1073
+ memo: data.memo || ""
1074
+ });
1075
+ await entry.save();
1076
+ if (this.ledgerId) {
1077
+ const parent = await entry.getJournal();
1078
+ this.updated_at = parent?.updated_at ?? this.updated_at;
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Post the journal (finalize and make immutable)
1083
+ */
1084
+ async post() {
1085
+ if (!this.isDraft()) throw new Error("Only draft journals can be posted");
1086
+ await this.validatePosting();
1087
+ this.status = "posted";
1088
+ this.postedAt = /* @__PURE__ */ new Date();
1089
+ await this.save();
1090
+ }
1091
+ /**
1092
+ * Void the journal (mark as cancelled with reason)
1093
+ */
1094
+ async void(reason) {
1095
+ if (this.isVoided()) throw new Error("Journal is already voided");
1096
+ this.status = "voided";
1097
+ this.voidedAt = /* @__PURE__ */ new Date();
1098
+ this.voidReason = reason;
1099
+ await this.save();
1100
+ }
1101
+ async validatePosting() {
1102
+ if (!await this.isBalanced()) {
1103
+ const debits = await this.getTotalDebits();
1104
+ const credits = await this.getTotalCredits();
1105
+ throw new Error(`Journal is not balanced. Debits: ${debits}, Credits: ${credits}`);
1106
+ }
1107
+ const entries = await this.getEntries();
1108
+ if (entries.length === 0) throw new Error("Journal must have at least one entry");
1109
+ const ledger = await this.getLedger();
1110
+ if (this.ledgerId && !ledger) throw new Error(`Ledger not found: ${this.ledgerId}`);
1111
+ if (ledger) {
1112
+ if (ledger.tenantId !== this.tenantId) throw new Error("Journal and ledger must belong to the same tenant");
1113
+ if (entries.some((entry) => entry.currency !== ledger.currency)) throw new Error(`Journal entries must use ledger currency ${ledger.currency}`);
1114
+ }
1115
+ const { AccountCollection } = await Promise.resolve().then(() => Accounts_exports);
1116
+ const accounts = await AccountCollection.create(this.options);
1117
+ for (const entry of entries) {
1118
+ const account = await accounts.get({ id: entry.accountId });
1119
+ if ((this.ledgerId || account?.ledgerId) && (!account || !account.active || account.tenantId !== this.tenantId || entry.tenantId !== this.tenantId || account.ledgerId !== this.ledgerId)) throw new Error("Journal entries must use active accounts owned by the journal ledger");
1120
+ }
1121
+ }
1122
+ async save(options = {}) {
1123
+ return this.withTransaction(async () => {
1124
+ if (this.isPersisted && this.updated_at) {
1125
+ await this.claimRevision(options.expectedUpdatedAt ?? this.updated_at);
1126
+ if (options.expectedUpdatedAt !== void 0) options = {
1127
+ ...options,
1128
+ expectedUpdatedAt: this.updated_at
1129
+ };
1130
+ }
1131
+ return super.save(options);
1132
+ });
1133
+ }
1134
+ async validateBeforeSave() {
1135
+ await super.validateBeforeSave();
1136
+ this.requireInsertOnSave({ preserveDerivedSlug: true });
1137
+ if (this.id) {
1138
+ const stored = await this.db.get(this.tableName, { id: this.id });
1139
+ if (stored && stored.ledger_id !== this.ledgerId) throw new Error("Journal ledger scope is immutable after creation");
1140
+ if (stored?.ledger_id && stored.status !== "draft") throw new Error("Ledger journals are immutable after posting; use a compensating journal");
1141
+ }
1142
+ if (this.status === "posted") {
1143
+ if (this.ledgerId) await this.validatePosting();
1144
+ else for (const entry of await this.getEntries()) if ((await entry.getAccount())?.ledgerId) throw new Error("Scoped accounts require their journal ledger");
1145
+ }
1146
+ }
1147
+ async delete(options = {}) {
1148
+ await this.withTransaction(async () => {
1149
+ if (this.isPersisted && this.updated_at) {
1150
+ await this.claimRevision(options.expectedUpdatedAt ?? this.updated_at);
1151
+ if (options.expectedUpdatedAt !== void 0) options = {
1152
+ ...options,
1153
+ expectedUpdatedAt: this.updated_at
1154
+ };
1155
+ }
1156
+ const stored = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
1157
+ if (stored?.ledger_id && stored.status !== "draft") throw new Error("Ledger journals are immutable after posting");
1158
+ await super.delete(options);
1159
+ });
1160
+ }
1161
+ /**
1162
+ * AI-powered: Generate a summary of this journal.
1163
+ *
1164
+ * Uses the `smrtLedgers.journal.summarize` prompt registered via
1165
+ * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level
1166
+ * overrides of the template, model, and parameters at runtime.
1167
+ *
1168
+ * Only non-PII journal fields (number, date, description, status, balanced
1169
+ * flag) plus aggregate totals and entry count are sent to the AI provider.
1170
+ * Internal foreign-key fields (tenantId, sourceRef, individual entry
1171
+ * account IDs) and the extensible `metadata` blob are intentionally
1172
+ * excluded.
1173
+ *
1174
+ * @returns Generated summary text
1175
+ */
1176
+ async summarize() {
1177
+ const entries = await this.getEntries();
1178
+ const debits = await this.getTotalDebits();
1179
+ const balanced = await this.isBalanced();
1180
+ const db = this.options.db ?? this.options.persistence;
1181
+ const resolvedPrompt = await resolvePrompt(smrtLedgersJournalSummarizePrompt.key, {
1182
+ db,
1183
+ tenantId: this.tenantId,
1184
+ variables: {
1185
+ journalNumber: this.number || "",
1186
+ journalDate: this.date.toISOString().split("T")[0],
1187
+ journalDescription: this.description || "",
1188
+ journalStatus: this.status || "",
1189
+ journalTotal: `$${debits.toFixed(2)}`,
1190
+ entryCount: String(entries.length),
1191
+ journalBalanced: balanced ? "Yes" : "No"
1192
+ }
1193
+ });
1194
+ return (await (await this.getAiClient()).message(resolvedPrompt.text, promptMessageOptions(resolvedPrompt.ai))).trim();
1195
+ }
1196
+ };
1197
+ __publicField(Journal, "__smrtPackage__", "@happyvertical/smrt-ledgers");
1198
+ __decorateClass([tenantId({ nullable: true })], Journal.prototype, "tenantId", 2);
1199
+ __decorateClass([field({ description: "The journal's reference number." })], Journal.prototype, "number", 2);
1200
+ __decorateClass([field({ description: "The date of the transaction." })], Journal.prototype, "date", 2);
1201
+ __decorateClass([field({ description: "What the transaction was." })], Journal.prototype, "description", 2);
1202
+ __decorateClass([field({
1203
+ description: "The ledger policy that governs this journal.",
1204
+ nullable: true
1205
+ }), foreignKey(Ledger)], Journal.prototype, "ledgerId", 2);
1206
+ __decorateClass([field({ nullable: true })], Journal.prototype, "idempotencyKey", 2);
1207
+ __decorateClass([field({ description: "draft until final, then posted, or voided." })], Journal.prototype, "status", 2);
1208
+ __decorateClass([field({ description: "When it was posted." })], Journal.prototype, "postedAt", 2);
1209
+ Journal = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
1210
+ api: { include: [
1211
+ "list",
1212
+ "get",
1213
+ "create"
1214
+ ] },
1215
+ mcp: { include: [
1216
+ "list",
1217
+ "get",
1218
+ "create"
1219
+ ] },
1220
+ cli: { skipApiCheck: true },
1221
+ indexes: [{
1222
+ name: "journals_ledger_idempotency_key",
1223
+ columns: [
1224
+ "tenantId",
1225
+ "ledgerId",
1226
+ "idempotencyKey"
1227
+ ],
1228
+ unique: true
1229
+ }]
1230
+ })], Journal);
1231
+ var Journals_exports = /* @__PURE__ */ __exportAll({ JournalCollection: () => JournalCollection });
1232
+ var JournalCollection = class extends SmrtCollection {
1233
+ static _itemClass = Journal;
1234
+ /**
1235
+ * Generate next journal number
1236
+ *
1237
+ * Uses timestamp and random component to avoid collisions
1238
+ * in concurrent environments without relying on shared state.
1239
+ */
1240
+ generateJournalNumber() {
1241
+ return `JNL-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
1242
+ }
1243
+ /**
1244
+ * Find journal by number
1245
+ *
1246
+ * @param number - Journal number
1247
+ * @returns Journal or null
1248
+ */
1249
+ async findByNumber(number) {
1250
+ return (await this.list({
1251
+ where: { number },
1252
+ limit: 1
1253
+ }))[0] || null;
1254
+ }
1255
+ /**
1256
+ * Find journals by date range
1257
+ *
1258
+ * @param start - Start date
1259
+ * @param end - End date
1260
+ * @returns Array of journals
1261
+ */
1262
+ async findByDateRange(start, end) {
1263
+ return await this.list({
1264
+ where: {
1265
+ "date >=": start.toISOString(),
1266
+ "date <=": end.toISOString()
1267
+ },
1268
+ orderBy: "date ASC"
1269
+ });
1270
+ }
1271
+ /**
1272
+ * Find journals by source module
1273
+ *
1274
+ * @param sourceModule - Source module name
1275
+ * @returns Array of journals
1276
+ */
1277
+ async findBySource(sourceModule) {
1278
+ return await this.list({
1279
+ where: { sourceModule },
1280
+ orderBy: "date DESC"
1281
+ });
1282
+ }
1283
+ /**
1284
+ * Find journals by status
1285
+ *
1286
+ * @param status - Journal status
1287
+ * @returns Array of journals
1288
+ */
1289
+ async findByStatus(status) {
1290
+ return await this.list({
1291
+ where: { status },
1292
+ orderBy: "date DESC"
1293
+ });
1294
+ }
1295
+ /**
1296
+ * Find draft journals
1297
+ *
1298
+ * @returns Array of draft journals
1299
+ */
1300
+ async findDrafts() {
1301
+ return await this.findByStatus("draft");
1302
+ }
1303
+ /**
1304
+ * Find posted journals
1305
+ *
1306
+ * @returns Array of posted journals
1307
+ */
1308
+ async findPosted() {
1309
+ return await this.findByStatus("posted");
1310
+ }
1311
+ /**
1312
+ * Create a complete journal with entries
1313
+ *
1314
+ * @param data - Journal data with entries
1315
+ * @returns Created journal
1316
+ */
1317
+ async createWithEntries(data) {
1318
+ let totalDebits = 0;
1319
+ let totalCredits = 0;
1320
+ for (const entry of data.entries) {
1321
+ totalDebits += entry.debit || 0;
1322
+ totalCredits += entry.credit || 0;
1323
+ }
1324
+ if (Math.abs(totalDebits - totalCredits) >= .001) throw new Error(`Entries are not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`);
1325
+ if (data.entries.length === 0) throw new Error("Journal must have at least one entry");
1326
+ const journalNumber = await this.generateJournalNumber();
1327
+ const journal = await this.create({
1328
+ tenantId: data.tenantId ?? null,
1329
+ number: journalNumber,
1330
+ date: data.date || /* @__PURE__ */ new Date(),
1331
+ description: data.description,
1332
+ sourceModule: data.sourceModule || "manual",
1333
+ sourceRef: data.sourceRef || null,
1334
+ ledgerId: data.ledgerId ?? null,
1335
+ idempotencyKey: data.idempotencyKey ?? null,
1336
+ requestDigest: data.requestDigest ?? null,
1337
+ metadata: data.metadata || {}
1338
+ });
1339
+ await journal.save();
1340
+ for (const entryData of data.entries) await journal.addEntry(entryData);
1341
+ return journal;
1342
+ }
1343
+ /**
1344
+ * Post a journal by ID
1345
+ *
1346
+ * @param journalId - Journal ID
1347
+ * @returns Posted journal
1348
+ */
1349
+ async post(journalId) {
1350
+ const journal = await this.get({ id: journalId });
1351
+ if (!journal) throw new Error(`Journal not found: ${journalId}`);
1352
+ await journal.post();
1353
+ return journal;
1354
+ }
1355
+ /**
1356
+ * Void a journal by ID
1357
+ *
1358
+ * @param journalId - Journal ID
1359
+ * @param reason - Reason for voiding
1360
+ * @returns Voided journal
1361
+ */
1362
+ async void(journalId, reason) {
1363
+ const journal = await this.get({ id: journalId });
1364
+ if (!journal) throw new Error(`Journal not found: ${journalId}`);
1365
+ await journal.void(reason);
1366
+ return journal;
1367
+ }
1368
+ /**
1369
+ * Find journals by source reference
1370
+ *
1371
+ * @param sourceRef - External reference
1372
+ * @returns Array of journals
1373
+ */
1374
+ async findBySourceRef(sourceRef) {
1375
+ return await this.list({
1376
+ where: { sourceRef },
1377
+ orderBy: "date DESC"
1378
+ });
1379
+ }
1380
+ /**
1381
+ * Get journals for a specific month
1382
+ *
1383
+ * @param year - Year
1384
+ * @param month - Month (1-12)
1385
+ * @returns Array of journals
1386
+ */
1387
+ async findByMonth(year, month) {
1388
+ const start = new Date(year, month - 1, 1);
1389
+ const end = new Date(year, month, 0, 23, 59, 59, 999);
1390
+ return await this.findByDateRange(start, end);
1391
+ }
1392
+ /**
1393
+ * Find all journals belonging to a specific tenant
1394
+ *
1395
+ * @param tenantId - Tenant ID
1396
+ * @returns Array of tenant's journals
1397
+ */
1398
+ async findByTenant(tenantId) {
1399
+ return this.list({ where: { tenantId } });
1400
+ }
1401
+ /**
1402
+ * Find all global journals (no tenant association).
1403
+ *
1404
+ * Routes through the shared tenant-global helper so it does not throw under
1405
+ * an active tenant context. (#1600)
1406
+ *
1407
+ * @returns Array of global journals
1408
+ */
1409
+ async findGlobal() {
1410
+ return queryGlobal(this);
1411
+ }
1412
+ /**
1413
+ * Find journals for a tenant plus all global journals.
1414
+ *
1415
+ * Fails closed if an active tenant context requests a different tenant's
1416
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
1417
+ *
1418
+ * @param tenantId - Tenant ID
1419
+ * @returns Array of tenant's journals and global journals
1420
+ */
1421
+ async findWithGlobals(tenantId) {
1422
+ return queryWithGlobals(this, tenantId, "Journal.findWithGlobals");
1423
+ }
1424
+ };
1425
+ var Ledgers_exports = /* @__PURE__ */ __exportAll({ LedgerCollection: () => LedgerCollection });
1426
+ var LedgerCollection = class extends SmrtCollection {
1427
+ static _itemClass = Ledger;
1428
+ };
1429
+ var LEDGER_ACCOUNTS_TABLE = "ledger_accounts";
1430
+ var LEGACY_ACCOUNTS_TABLE = "accounts";
1431
+ var LEGACY_LEDGER_SIGNATURE = ["number", "type"];
1432
+ var STI_DISCRIMINATOR = "_meta_type";
1433
+ var MESSAGING_MARKERS = ["provider_type", "channel_type"];
1434
+ var LedgerAccountsTableMoveError = class extends Error {
1435
+ constructor(message) {
1436
+ super(message);
1437
+ this.name = "LedgerAccountsTableMoveError";
1438
+ }
1439
+ };
1440
+ function quote(identifier) {
1441
+ return `"${identifier.replace(/"/g, "\"\"")}"`;
1442
+ }
1443
+ function timeout(value, fallback) {
1444
+ const milliseconds = value ?? fallback;
1445
+ if (!Number.isInteger(milliseconds) || milliseconds < 0) throw new LedgerAccountsTableMoveError("Migration timeout must be a non-negative integer");
1446
+ return `${milliseconds}ms`;
1447
+ }
1448
+ function assertPostgres(db) {
1449
+ const withConfig = db;
1450
+ if (detectEngine(db.url || withConfig.config?.url || "", withConfig.type || withConfig.config?.type) !== "postgres") throw new LedgerAccountsTableMoveError("The ledger accounts table move supports PostgreSQL only. For a SQLite database whose `accounts` table holds only ledger accounts, run `ALTER TABLE accounts RENAME TO ledger_accounts` before `db:migrate`; see the smrt-ledgers README.");
1451
+ }
1452
+ async function readLegacyShape(db) {
1453
+ const schema = await db.getTableSchema?.(LEGACY_ACCOUNTS_TABLE);
1454
+ if (!schema) return null;
1455
+ const columns = new Set(Object.keys(schema.columns));
1456
+ if (!LEGACY_LEDGER_SIGNATURE.every((column) => columns.has(column))) return null;
1457
+ return {
1458
+ columns,
1459
+ hasDiscriminator: columns.has(STI_DISCRIMINATOR)
1460
+ };
1461
+ }
1462
+ function ledgerRowPredicate(shape) {
1463
+ return shape.hasDiscriminator ? `(${quote(STI_DISCRIMINATOR)} IS NULL OR ${quote(STI_DISCRIMINATOR)} = '')` : "TRUE";
1464
+ }
1465
+ async function count(db, where) {
1466
+ const result = await db.query(`SELECT COUNT(*) AS n FROM ${quote(LEGACY_ACCOUNTS_TABLE)} WHERE ${where}`);
1467
+ return Number(result.rows[0].n);
1468
+ }
1469
+ async function classify(db, shape) {
1470
+ const ledger = ledgerRowPredicate(shape);
1471
+ const pending = await count(db, ledger);
1472
+ const messagingRows = shape.hasDiscriminator ? await count(db, `NOT ${ledger}`) : 0;
1473
+ if (shape.hasDiscriminator) {
1474
+ const ledgerLooking = await count(db, `NOT ${ledger} AND COALESCE(${quote("number")}, '') <> ''`);
1475
+ const markers = MESSAGING_MARKERS.filter((column) => shape.columns.has(column));
1476
+ const messagingLooking = markers.length === 0 ? 0 : await count(db, `${ledger} AND (${markers.map((column) => `COALESCE(${quote(column)}, '') <> ''`).join(" OR ")})`);
1477
+ if (ledgerLooking > 0 || messagingLooking > 0) throw new LedgerAccountsTableMoveError(`Refusing to move ledger accounts: ${ledgerLooking} messaging row(s) carry a ledger account number and ${messagingLooking} ledger row(s) carry a messaging provider or channel in "${LEGACY_ACCOUNTS_TABLE}". Resolve their ownership by hand, then rerun.`);
1478
+ }
1479
+ return {
1480
+ pending,
1481
+ messagingRows
1482
+ };
1483
+ }
1484
+ function registeredReferenceColumns() {
1485
+ const ledger = /* @__PURE__ */ new Set();
1486
+ const accounts = /* @__PURE__ */ new Set();
1487
+ const schemas = ObjectRegistry.getAllSchemasAsDefinitions();
1488
+ for (const [tableName, schema] of Object.entries(schemas)) for (const [columnName, column] of Object.entries(schema.columns ?? {})) {
1489
+ const target = column.foreignKey?.table;
1490
+ if (target === "ledger_accounts") ledger.add(`${tableName}.${columnName}`);
1491
+ else if (target === "accounts") accounts.add(`${tableName}.${columnName}`);
1492
+ }
1493
+ return {
1494
+ ledger,
1495
+ accounts
1496
+ };
1497
+ }
1498
+ function classifyForeignKeys(inbound, references) {
1499
+ const disposition = {
1500
+ composite: [],
1501
+ undeclared: [],
1502
+ detach: [],
1503
+ keep: []
1504
+ };
1505
+ for (const fk of inbound) {
1506
+ const key = `${fk.table}.${fk.column}`;
1507
+ if (fk.arity > 1) disposition.composite.push(fk);
1508
+ else if (references.ledger.has(key)) disposition.detach.push(fk);
1509
+ else if (fk.table === "accounts" || references.accounts.has(key)) disposition.keep.push(fk);
1510
+ else disposition.undeclared.push(fk);
1511
+ }
1512
+ return disposition;
1513
+ }
1514
+ function foreignKeyRefusal(disposition) {
1515
+ if (disposition.composite.length > 0) return `Composite foreign key(s) ${disposition.composite.map((fk) => `${fk.constraint} on ${fk.table} (${fk.arity} columns)`).join(", ")} reference "${LEGACY_ACCOUNTS_TABLE}"; no model can declare one, so the move cannot carry it. Nothing was moved. Drop it, or replace it with a single-column reference, then rerun.`;
1516
+ if (disposition.undeclared.length > 0) return `Foreign key(s) ${disposition.undeclared.map((fk) => `${fk.table}.${fk.column}`).join(", ")} reference "${LEGACY_ACCOUNTS_TABLE}" but no registered model declares them; nothing was moved. Run the move from the application whose models declare them, or retarget each to "${LEDGER_ACCOUNTS_TABLE}" (or drop it) by hand, then rerun.`;
1517
+ }
1518
+ function copySelectList(columns, targetTypes, legacyTypes) {
1519
+ return columns.map((column) => {
1520
+ const to = targetTypes[column];
1521
+ return sameType(legacyTypes[column], to) ? quote(column) : `CAST(CAST(${quote(column)} AS TEXT) AS ${targetType(to)})`;
1522
+ }).join(", ");
1523
+ }
1524
+ async function legacyForeignKeys(db) {
1525
+ return (await db.query(`SELECT con.conname AS constraint_name,
1526
+ rel.relname AS table_name,
1527
+ att.attname AS column_name,
1528
+ array_length(con.conkey, 1) AS arity
1529
+ FROM pg_constraint con
1530
+ JOIN pg_class rel ON rel.oid = con.conrelid
1531
+ JOIN pg_attribute att
1532
+ ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1]
1533
+ WHERE con.contype = 'f'
1534
+ AND con.confrelid = to_regclass(?)
1535
+ ORDER BY rel.relname, con.conname`, quote(LEGACY_ACCOUNTS_TABLE))).rows.map((row) => ({
1536
+ constraint: row.constraint_name,
1537
+ table: row.table_name,
1538
+ column: row.column_name,
1539
+ arity: Number(row.arity)
1540
+ }));
1541
+ }
1542
+ async function planLedgerAccountsTableMove(db) {
1543
+ const shape = await readLegacyShape(db);
1544
+ if (!shape) return {
1545
+ legacyTable: false,
1546
+ pending: 0,
1547
+ messagingRows: 0
1548
+ };
1549
+ return {
1550
+ legacyTable: true,
1551
+ ...await classify(db, shape)
1552
+ };
1553
+ }
1554
+ async function migrateLedgerAccountsTable(db, options = {}) {
1555
+ assertPostgres(db);
1556
+ if (!db.transaction) throw new LedgerAccountsTableMoveError("The ledger accounts table move requires a transaction-capable PostgreSQL adapter");
1557
+ const noop = (remainingLegacyRows = 0) => ({
1558
+ ran: false,
1559
+ moved: 0,
1560
+ remainingLegacyRows,
1561
+ detachedForeignKeys: []
1562
+ });
1563
+ const shape = await readLegacyShape(db);
1564
+ if (!shape) return noop();
1565
+ const target = await db.getTableSchema?.(LEDGER_ACCOUNTS_TABLE);
1566
+ if (!target) throw new LedgerAccountsTableMoveError(`"${LEDGER_ACCOUNTS_TABLE}" does not exist; run db:migrate before moving ledger accounts`);
1567
+ const legacy = await db.getTableSchema?.(LEGACY_ACCOUNTS_TABLE);
1568
+ const copyColumns = Object.keys(target.columns).filter((column) => shape.columns.has(column));
1569
+ const references = registeredReferenceColumns();
1570
+ await new BackfillTracker({ db }).initialize();
1571
+ const backfillName = options.backfillName ?? "@happyvertical/smrt-ledgers:ledger-accounts-table:v1";
1572
+ return db.transaction(async (tx) => {
1573
+ BackfillTracker.inheritInitialization(tx, db);
1574
+ const transactionTracker = new BackfillTracker({ db: tx });
1575
+ await tx.query(`SET LOCAL lock_timeout = '${timeout(options.lockTimeout, 3e4)}'`);
1576
+ await tx.query(`SET LOCAL statement_timeout = '${timeout(options.statementTimeout, 6e4)}'`);
1577
+ await tx.query(`LOCK TABLE ${quote(LEGACY_ACCOUNTS_TABLE)}, ${quote(LEDGER_ACCOUNTS_TABLE)} IN ACCESS EXCLUSIVE MODE`);
1578
+ const { pending, messagingRows } = await classify(tx, shape);
1579
+ if (await transactionTracker.isApplied(backfillName)) {
1580
+ if (pending > 0) throw new LedgerAccountsTableMoveError(`The ledger accounts table move is recorded as applied but "${LEGACY_ACCOUNTS_TABLE}" still holds ${pending} ledger row(s); refusing inconsistent state`);
1581
+ return noop(messagingRows);
1582
+ }
1583
+ const ledger = ledgerRowPredicate(shape);
1584
+ const clash = await tx.query(`SELECT COUNT(*) AS n FROM ${quote(LEGACY_ACCOUNTS_TABLE)} legacy
1585
+ WHERE ${ledger}
1586
+ AND EXISTS (SELECT 1 FROM ${quote(LEDGER_ACCOUNTS_TABLE)} moved
1587
+ WHERE CAST(moved.id AS TEXT) = CAST(legacy.id AS TEXT))`);
1588
+ if (Number(clash.rows[0].n) > 0) throw new LedgerAccountsTableMoveError(`"${LEDGER_ACCOUNTS_TABLE}" already holds ids of ledger rows still in "${LEGACY_ACCOUNTS_TABLE}"; refusing to overwrite`);
1589
+ const disposition = classifyForeignKeys(await legacyForeignKeys(tx), references);
1590
+ const refusal = foreignKeyRefusal(disposition);
1591
+ if (refusal) throw new LedgerAccountsTableMoveError(refusal);
1592
+ for (const fk of disposition.keep) {
1593
+ const survivor = fk.table === "accounts" ? ` AND NOT ${ledger}` : "";
1594
+ const pointing = await tx.query(`SELECT COUNT(*) AS n FROM ${quote(fk.table)}
1595
+ WHERE ${quote(fk.column)} IN
1596
+ (SELECT id FROM ${quote(LEGACY_ACCOUNTS_TABLE)} WHERE ${ledger})${survivor}`);
1597
+ if (Number(pointing.rows[0].n) > 0) throw new LedgerAccountsTableMoveError(`${fk.table}.${fk.column} references ledger accounts in "${LEGACY_ACCOUNTS_TABLE}" but is not a ledger reference; nothing was moved. Resolve those rows by hand, then rerun.`);
1598
+ }
1599
+ const selectList = copySelectList(copyColumns, columnTypes(target), columnTypes(legacy));
1600
+ await tx.query(`INSERT INTO ${quote(LEDGER_ACCOUNTS_TABLE)} (${copyColumns.map(quote).join(", ")})
1601
+ SELECT ${selectList} FROM ${quote(LEGACY_ACCOUNTS_TABLE)} WHERE ${ledger}`);
1602
+ const detached = [];
1603
+ for (const foreignKey of disposition.detach) {
1604
+ await tx.query(`ALTER TABLE ${quote(foreignKey.table)} DROP CONSTRAINT ${quote(foreignKey.constraint)}`);
1605
+ detached.push(`${foreignKey.table}.${foreignKey.column}`);
1606
+ }
1607
+ await tx.query(`DELETE FROM ${quote(LEGACY_ACCOUNTS_TABLE)} WHERE ${ledger}`);
1608
+ await transactionTracker.recordApplied(backfillName, {
1609
+ description: "Moved ledger accounts from the shared accounts table to ledger_accounts (#3098).",
1610
+ packageName: options.packageName ?? "@happyvertical/smrt-ledgers"
1611
+ });
1612
+ return {
1613
+ ran: true,
1614
+ moved: pending,
1615
+ remainingLegacyRows: messagingRows,
1616
+ detachedForeignKeys: detached
1617
+ };
1618
+ });
1619
+ }
1620
+ var SQL_TYPE = /^[A-Za-z][A-Za-z0-9 _]*(\(\d+(,\s*\d+)?\))?(\[\])?$/;
1621
+ function columnTypes(schema) {
1622
+ return Object.fromEntries(Object.entries(schema?.columns ?? {}).map(([name, column]) => [name, column.type]));
1623
+ }
1624
+ function sameType(from, to) {
1625
+ return from !== void 0 && to !== void 0 && from.trim().toLowerCase() === to.trim().toLowerCase();
1626
+ }
1627
+ function targetType(declared) {
1628
+ const type = declared?.trim();
1629
+ if (!type || !SQL_TYPE.test(type)) throw new LedgerAccountsTableMoveError(`Cannot copy into "${LEDGER_ACCOUNTS_TABLE}": unsupported live column type ${JSON.stringify(type)}`);
1630
+ return type;
1631
+ }
1632
+ //#endregion
1633
+ export { migrateLedgerAccountsTable, JournalEntries_exports as n, planLedgerAccountsTableMove, Accounts_exports as r, Ledgers_exports as t };