@happyvertical/smrt-ledgers 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,36 @@
1
+ # @happyvertical/smrt-ledgers
2
+
3
+ Double-entry accounting with chart of accounts, journal lifecycle, and balance enforcement.
4
+
5
+ ## Models
6
+
7
+ - **Account**: 5 types (asset/liability/equity/revenue/expense). Hierarchical via `parentId`. Debit-normal (asset, expense) vs credit-normal (liability, equity, revenue). Methods: `getAncestors()`, `getFullPath()`, `toTreeNode()`, `getBalance(asOfDate?)`.
8
+ - **Journal**: lifecycle `draft → posted → voided`. **Immutable after posting** (can only void, not edit). `sourceModule`/`sourceRef` for cross-package attribution. Auto-numbered (e.g., "JNL-0001"). `summarize()` is AI-powered via the smrt-prompts registry (see Prompt Registry below).
9
+ - **JournalEntry**: debit XOR credit (not both, validated on save). Multi-currency via `exchangeRate`. Non-negative amounts required.
10
+
11
+ ## Key Methods
12
+
13
+ - **`Journal.summarize()`**: Generates a natural-language summary via the registered `smrtLedgers.journal.summarize` prompt, resolved through `@happyvertical/smrt-prompts`. Tenants can override the template, model, temperature, or other params via `_smrt_prompt_overrides` without code changes. Only non-PII fields (number, date, description, status, aggregate total, entry count, balanced flag) reach the AI provider.
14
+
15
+ ## Prompt Registry
16
+
17
+ Registered at module-load time via `definePrompt()` in `src/prompts.ts`. Side-effect imported from `src/index.ts` so consumers automatically see the prompts after importing any export from this package.
18
+
19
+ | Key | Method | Variables |
20
+ |-----|--------|-----------|
21
+ | `smrtLedgers.journal.summarize` | `Journal.summarize()` | `journalNumber`, `journalDate`, `journalDescription`, `journalStatus`, `journalTotal`, `entryCount`, `journalBalanced` |
22
+
23
+ PII-conscious variable selection: `tenantId`, `sourceRef` (may reference customer/vendor identifiers), per-entry `accountId`/`id`, and the extensible `metadata` blob are intentionally NOT exposed as prompt variables. Tenants who need richer context should override the template via `PromptOverride` and supply their own variables through a custom call site.
24
+
25
+ ## Balance Enforcement
26
+
27
+ `journal.post()` validates `Math.abs(totalDebits - totalCredits) < BALANCE_EPSILON` where `BALANCE_EPSILON = 0.01` (float rounding tolerance). Unbalanced journals cannot be posted.
28
+
29
+ ## Gotchas
30
+
31
+ - **Float tolerance**: 0.01 epsilon for balance check — not exact equality
32
+ - **Entry requires journalId**: save Journal first, then add entries
33
+ - **Posted journals cannot be modified**: only voided (`voidReason`, `voidedAt`)
34
+ - **Account types inherited by children**: child cannot differ from parent type
35
+ - **getBalance() is async**: requires JournalEntryCollection query (not stored on Account)
36
+ - **Optional tenancy** on all models
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @happyvertical/smrt-ledgers
2
+
3
+ Double-entry accounting ledger for the SMRT framework. Hierarchical chart of accounts, journal lifecycle with immutability after posting, and balance enforcement with epsilon tolerance.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @happyvertical/smrt-ledgers
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import {
15
+ Account, AccountCollection,
16
+ Journal, JournalCollection,
17
+ JournalEntry, JournalEntryCollection
18
+ } from '@happyvertical/smrt-ledgers';
19
+
20
+ // Set up chart of accounts
21
+ const accounts = new AccountCollection(db);
22
+ const cash = await accounts.create({
23
+ number: '1000',
24
+ name: 'Cash',
25
+ type: 'asset',
26
+ });
27
+ await cash.save();
28
+
29
+ const revenue = await accounts.create({
30
+ number: '4000',
31
+ name: 'Sales Revenue',
32
+ type: 'revenue',
33
+ });
34
+ await revenue.save();
35
+
36
+ // Create a sub-account under Cash
37
+ const checking = await cash.createChild({
38
+ number: '1010',
39
+ name: 'Checking Account',
40
+ });
41
+
42
+ // Create a balanced journal with entries
43
+ const journals = new JournalCollection(db);
44
+ const journal = await journals.createWithEntries({
45
+ description: 'Cash sale',
46
+ sourceModule: 'manual',
47
+ entries: [
48
+ { accountId: cash.id, debit: 100.00 },
49
+ { accountId: revenue.id, credit: 100.00 },
50
+ ],
51
+ });
52
+
53
+ // Post the journal (validates balance, then immutable)
54
+ await journal.post();
55
+
56
+ // Query balances
57
+ const cashBalance = await cash.getBalance();
58
+
59
+ // Get trial balance across all active accounts
60
+ const entries = new JournalEntryCollection(db);
61
+ const trialBalance = await entries.getTrialBalance();
62
+
63
+ // Void a journal (cannot edit after posting, only void)
64
+ await journal.void('Duplicate entry');
65
+ ```
66
+
67
+ ## Double-Entry Accounting
68
+
69
+ Every journal must balance before it can be posted. The balance check uses `BALANCE_EPSILON = 0.001` to handle floating-point rounding:
70
+
71
+ ```
72
+ Math.abs(totalDebits - totalCredits) < 0.001
73
+ ```
74
+
75
+ Account types follow standard accounting rules:
76
+ - **Debit-normal** (Asset, Expense): balance = debits - credits
77
+ - **Credit-normal** (Liability, Equity, Revenue): balance = credits - debits
78
+
79
+ ### Journal Lifecycle
80
+
81
+ Journals follow a strict `draft -> posted -> voided` lifecycle:
82
+ - **Draft**: editable, entries can be added via `journal.addEntry()`
83
+ - **Posted**: immutable, balance validated, `postedAt` timestamp set
84
+ - **Voided**: marked with `voidReason` and `voidedAt`, cannot be edited or re-posted
85
+
86
+ Each JournalEntry must have either a debit or a credit (not both, not zero). Amounts must be non-negative. Multi-currency is supported via `exchangeRate` on each entry.
87
+
88
+ ## API
89
+
90
+ ### Models
91
+
92
+ | Export | Description |
93
+ |--------|------------|
94
+ | `Account` | Chart of accounts entry with type, number, hierarchical parent, and balance queries |
95
+ | `Journal` | Transaction journal with status lifecycle, auto-numbered (JNL-*), sourceModule/sourceRef for cross-package attribution |
96
+ | `JournalEntry` | Individual debit or credit line within a journal, with currency and exchange rate |
97
+
98
+ ### Collections
99
+
100
+ | Export | Key Methods |
101
+ |--------|------------|
102
+ | `AccountCollection` | `findChildren()`, `findActive()` |
103
+ | `JournalCollection` | `createWithEntries()`, `findByNumber()`, `findByDateRange()`, `findBySource()`, `findByStatus()`, `findDrafts()`, `findPosted()` |
104
+ | `JournalEntryCollection` | `findByJournal()`, `findByAccount()`, `getAccountBalance()`, `getTrialBalance()`, `getAccountLedger()`, `getTotalsForDateRange()` |
105
+
106
+ ### Types
107
+
108
+ | Export | Description |
109
+ |--------|------------|
110
+ | `AccountType` | `'asset'`, `'liability'`, `'equity'`, `'revenue'`, `'expense'` |
111
+ | `JournalStatus` | `'draft'`, `'posted'`, `'voided'` |
112
+ | `AccountOptions` | Options for creating an Account |
113
+ | `JournalOptions` | Options for creating a Journal |
114
+ | `JournalEntryOptions` | Options for creating a JournalEntry |
115
+ | `JournalEntryData` | Entry data for `addEntry()` / `createWithEntries()` |
116
+ | `CreateJournalData` | Full journal + entries creation payload |
117
+ | `TrialBalanceRow` | Row in trial balance report (accountId, number, name, type, debit/credit balances) |
118
+ | `AccountTree` | Tree of account nodes (roots array) |
119
+ | `AccountTreeNode` | Single node in account tree (account + children) |
120
+
121
+ ## Dependencies
122
+
123
+ - `@happyvertical/smrt-core` -- ORM and code generation
124
+ - `@happyvertical/smrt-tenancy` -- multi-tenant scoping