@classytic/ledger 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +161 -64
  2. package/dist/{categories-CclX7Q94.mjs → categories-FJlrvzcl.mjs} +0 -2
  3. package/dist/constants/index.d.mts +2 -2
  4. package/dist/constants/index.mjs +3 -3
  5. package/dist/core-8Xfnpn6g.d.mts +1 -2
  6. package/dist/country/index.d.mts +1 -1
  7. package/dist/country/index.mjs +0 -2
  8. package/dist/currencies-W8kQAkm0.mjs +0 -2
  9. package/dist/{idempotency.plugin-v9NQ_ta-.mjs → date-lock.plugin-C8kqPBjh.mjs} +51 -11
  10. package/dist/{engine-BzBMpWuy.d.mts → engine-DF-MtsEr.d.mts} +10 -6
  11. package/dist/{errors-B7yC-Jfw.mjs → errors-BoGUSUYL.mjs} +0 -2
  12. package/dist/exports/index.d.mts +1 -1
  13. package/dist/exports/index.mjs +1 -1
  14. package/dist/{exports-I5Xkq-9_.mjs → exports-DoGQQtMQ.mjs} +96 -77
  15. package/dist/{fiscal-close-L631E3De.mjs → fiscal-close-DmPV82e4.mjs} +1000 -286
  16. package/dist/{idempotency.plugin-CPxPt4vX.d.mts → idempotency.plugin-zU-GKJ0-.d.mts} +19 -17
  17. package/dist/{index-ZnSiqHYV.d.mts → index-CxZqRaOU.d.mts} +20 -6
  18. package/dist/{index-BPukb3L8.d.mts → index-J-XIbXH-.d.mts} +7 -8
  19. package/dist/index.d.mts +280 -58
  20. package/dist/index.mjs +123 -25
  21. package/dist/journal-entry.schema-B1CzLwC3.d.mts +103 -0
  22. package/dist/{journals-oH-FK3g8.mjs → journals-BcMn71Cq.mjs} +27 -6
  23. package/dist/{currencies-4WAbFRlw.d.mts → journals-DTipb_rz.d.mts} +16 -8
  24. package/dist/logger-UbTdBb1x.d.mts +1 -2
  25. package/dist/money.d.mts +1 -2
  26. package/dist/money.mjs +5 -5
  27. package/dist/plugins/index.d.mts +38 -2
  28. package/dist/plugins/index.mjs +57 -2
  29. package/dist/reconciliation.repository-DEybU_Ok.d.mts +135 -0
  30. package/dist/{account.repository-kDKwDt0I.mjs → reconciliation.repository-DgJEDVS-.mjs} +361 -210
  31. package/dist/{fiscal-period.schema-BQ5wsAq3.mjs → reconciliation.schema-KScbsXbY.mjs} +235 -90
  32. package/dist/reports/index.d.mts +2 -2
  33. package/dist/reports/index.mjs +2 -2
  34. package/dist/repositories/index.d.mts +2 -2
  35. package/dist/repositories/index.mjs +2 -2
  36. package/dist/schemas/index.d.mts +71 -2
  37. package/dist/schemas/index.mjs +2 -2
  38. package/dist/tenant-guard-CAxXoWuS.mjs +13 -0
  39. package/dist/trial-balance-DcQ0xj_4.d.mts +530 -0
  40. package/docs/reports.md +1 -1
  41. package/package.json +14 -6
  42. package/dist/account.repository-C7gwFLfM.d.mts +0 -29
  43. package/dist/account.repository-C7gwFLfM.d.mts.map +0 -1
  44. package/dist/account.repository-kDKwDt0I.mjs.map +0 -1
  45. package/dist/categories-CclX7Q94.mjs.map +0 -1
  46. package/dist/core-8Xfnpn6g.d.mts.map +0 -1
  47. package/dist/country/index.mjs.map +0 -1
  48. package/dist/currencies-4WAbFRlw.d.mts.map +0 -1
  49. package/dist/currencies-W8kQAkm0.mjs.map +0 -1
  50. package/dist/engine-BzBMpWuy.d.mts.map +0 -1
  51. package/dist/errors-B7yC-Jfw.mjs.map +0 -1
  52. package/dist/exports-I5Xkq-9_.mjs.map +0 -1
  53. package/dist/fiscal-close-L631E3De.mjs.map +0 -1
  54. package/dist/fiscal-close-dNlzB37y.d.mts +0 -270
  55. package/dist/fiscal-close-dNlzB37y.d.mts.map +0 -1
  56. package/dist/fiscal-period.schema-BQ5wsAq3.mjs.map +0 -1
  57. package/dist/fiscal-period.schema-BRdKAjrr.d.mts +0 -38
  58. package/dist/fiscal-period.schema-BRdKAjrr.d.mts.map +0 -1
  59. package/dist/idempotency.plugin-CPxPt4vX.d.mts.map +0 -1
  60. package/dist/idempotency.plugin-v9NQ_ta-.mjs.map +0 -1
  61. package/dist/index-BPukb3L8.d.mts.map +0 -1
  62. package/dist/index-ZnSiqHYV.d.mts.map +0 -1
  63. package/dist/index.d.mts.map +0 -1
  64. package/dist/index.mjs.map +0 -1
  65. package/dist/journals-oH-FK3g8.mjs.map +0 -1
  66. package/dist/logger-UbTdBb1x.d.mts.map +0 -1
  67. package/dist/money.d.mts.map +0 -1
  68. package/dist/money.mjs.map +0 -1
  69. package/dist/session-Ba8E3Ufa.mjs +0 -84
  70. package/dist/session-Ba8E3Ufa.mjs.map +0 -1
package/README.md CHANGED
@@ -1,26 +1,15 @@
1
1
  # @classytic/ledger
2
2
 
3
- Production-grade double-entry accounting engine for MongoDB. Built on [@classytic/mongokit](../mongokit). Designed for multi-tenant SaaS, AI-powered finance, and global tax compliance.
4
-
5
- ## Features
6
-
7
- - **Double-entry bookkeeping** with balance validation and posted-entry protection (optionally immutable via strictness config)
8
- - **Multi-tenant** isolation via configurable org field
9
- - **Country packs** for localized chart of accounts and tax codes
10
- - **Financial reports** — trial balance, balance sheet, income statement, general ledger, cash flow
11
- - **Fiscal period management** — close and reopen with automatic year-end entries, overlap protection
12
- - **CSV export** — QuickBooks-compatible and universal field maps
13
- - **Cents-based Money** arithmetic for precision
14
- - **Plugin system** — fiscal lock, double-entry validation, idempotency (via mongokit hooks)
15
- - **Dimension fields** — custom fields on journal items (departmentId, projectId, etc.) preserved through all workflows
16
- - **Dimension filters** — filter all reports by custom journal item fields
17
- - **Strictness controls** — configurable immutability, actor tracking, and approval requirements
18
- - **Subledger contracts** — typed interfaces for integrating billing, inventory, payroll, and other subledgers
3
+ Embeddable double-entry accounting engine for MongoDB. Integer-cents arithmetic, plugin-based, country-agnostic.
4
+
5
+ Build QuickBooks, Xero, or TaxCycle-grade apps — the engine handles the accounting, you handle the UX.
19
6
 
20
7
  ## Install
21
8
 
22
9
  ```bash
23
10
  npm install @classytic/ledger @classytic/mongokit mongoose
11
+ npm install @classytic/ledger-ca # Canada (GIFI, GST/HST, CRA)
12
+ npm install @classytic/ledger-bd # Bangladesh (BFRS, VAT/TDS, Mushak)
24
13
  ```
25
14
 
26
15
  ## Quick Start
@@ -28,80 +17,188 @@ npm install @classytic/ledger @classytic/mongokit mongoose
28
17
  ```typescript
29
18
  import { createAccountingEngine } from '@classytic/ledger';
30
19
  import { canadaPack } from '@classytic/ledger-ca';
31
- import mongoose from 'mongoose';
32
20
 
33
- // 1. Create engine
34
21
  const accounting = createAccountingEngine({
35
22
  country: canadaPack,
36
23
  currency: 'CAD',
37
- multiTenant: { orgField: 'business', orgRef: 'Business' },
38
- audit: { trackActor: true },
39
- idempotency: true,
40
- strictness: { immutable: true, requireActor: true },
24
+ multiTenant: { orgField: 'organization', orgRef: 'Organization' },
41
25
  });
42
26
 
43
- // 2. Create schemas & models
27
+ // Schemas
44
28
  const Account = mongoose.model('Account', accounting.createAccountSchema());
45
- const JournalEntry = mongoose.model('JournalEntry', accounting.createJournalEntrySchema('Account', {
46
- extraItemFields: {
47
- departmentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Department' },
48
- },
49
- }));
29
+ const JournalEntry = mongoose.model('JournalEntry', accounting.createJournalEntrySchema('Account'));
50
30
  const FiscalPeriod = mongoose.model('FiscalPeriod', accounting.createFiscalPeriodSchema());
51
31
 
52
- // 3. Wire repositories (adds post, reverse, duplicate, unpost, seedAccounts, bulkCreate)
53
- import { createRepository } from '@classytic/mongokit';
32
+ // Reports
33
+ const reports = accounting.createReports({ Account, JournalEntry });
34
+ const bs = await reports.balanceSheet({ organizationId, dateOption: 'year', dateValue: 2025 });
35
+ ```
54
36
 
55
- const journalRepo = accounting.createJournalEntryRepository(
56
- createRepository,
57
- { JournalEntryModel: JournalEntry, AccountModel: Account, FiscalPeriodModel: FiscalPeriod },
58
- );
37
+ ## Core Features
38
+
39
+ **Accounting Engine**
40
+ - Double-entry validation with balance enforcement
41
+ - Integer-cents storage — zero floating-point drift
42
+ - Draft → Posted → Reversed state machine
43
+ - Configurable immutability (corrections only via reversal)
44
+ - Multi-tenant isolation at every layer
45
+ - Country packs for localized charts of accounts and tax codes
46
+
47
+ **10 Reports**
48
+ - Trial Balance (3-column: initial + period + ending)
49
+ - Balance Sheet (with computed retained earnings)
50
+ - Income Statement (revenue, COGS, gross profit, operating expenses, net income)
51
+ - General Ledger (per-account with running balances)
52
+ - Cash Flow (Operating / Investing / Financing)
53
+ - Aged Receivable / Payable (configurable buckets: current, 30, 60, 90+)
54
+ - Budget vs Actual (variance analysis)
55
+ - Dimension Breakdown (by department, project, cost center)
56
+ - Foreign Exchange Revaluation (unrealized gain/loss computation)
57
+ - Fiscal Year Close / Reopen (automatic closing entries)
58
+
59
+ **Plugins**
60
+ - `doubleEntryPlugin` — validates debits = credits, account existence, tenant integrity
61
+ - `fiscalLockPlugin` — prevents posting to closed fiscal periods
62
+ - `dateLockPlugin` — blocks entries before a configurable lock date
63
+ - `taxHookPlugin` — auto-generates tax lines via user-defined `TaxLineGenerator`
64
+ - `idempotencyPlugin` — prevents duplicate entries by key
65
+
66
+ **Utilities**
67
+ - `Money` — cents arithmetic, tax splitting, allocation with zero-sum guarantee
68
+ - `buildDimensionFields` — schema helpers for analytic dimensions
69
+ - `suggestMatches` — reconciliation matching suggestions
70
+ - `computeRevaluation` — FX gain/loss computation
71
+
72
+ ## Engine Configuration
59
73
 
60
- const accountRepo = createRepository(Account, []);
61
- accounting.wireAccountRepository(accountRepo, Account);
74
+ ```typescript
75
+ createAccountingEngine({
76
+ country: canadaPack, // required
77
+ currency: 'CAD', // required — base/functional currency
78
+ multiTenant: { orgField, orgRef }, // optional — multi-tenant scoping
79
+ multiCurrency: { enabled: true, currencies: ['USD', 'EUR'] },
80
+ fiscalYearStartMonth: 1, // 1=Jan (default), 4=Apr, 7=Jul
81
+ retainedEarningsAccountCode: '3600', // overrides country pack
82
+ audit: { trackActor: true },
83
+ idempotency: true,
84
+ strictness: {
85
+ immutable: true, // disable unpost, corrections via reverse only
86
+ requireActor: true, // actorId required on post/reverse
87
+ requireApproval: true // entries must be approved before posting
88
+ },
89
+ });
90
+ ```
62
91
 
63
- // 4. Generate reports (with optional dimension filters)
64
- const reports = accounting.createReports({ Account, JournalEntry });
65
- const bs = await reports.balanceSheet({
66
- organizationId: orgId,
67
- dateOption: 'year',
68
- dateValue: 2025,
69
- filters: { 'journalItems.departmentId': deptId },
92
+ ## Reports API
93
+
94
+ ```typescript
95
+ const reports = accounting.createReports({ Account, JournalEntry, Budget });
96
+
97
+ // All reports accept: { organizationId, dateOption, dateValue, filters? }
98
+ await reports.trialBalance({ ... });
99
+ await reports.balanceSheet({ ... });
100
+ await reports.incomeStatement({ ... });
101
+ await reports.generalLedger({ ... });
102
+ await reports.cashFlow({ ... });
103
+ await reports.agedBalance({ type: 'receivable', asOfDate: new Date() });
104
+ await reports.budgetVsActual({ ... }); // requires Budget model
105
+ await reports.dimensionBreakdown({ dimension: 'departmentId', ... });
106
+ await reports.revaluation({ rates: [{ currency: 'USD', rate: 1.40 }], ... });
107
+ ```
108
+
109
+ All report data is sorted by account code. All monetary values are integer cents — use `Money.toDecimal()` at your API boundary.
110
+
111
+ ## Schemas
112
+
113
+ ```typescript
114
+ accounting.createAccountSchema(options?)
115
+ accounting.createJournalEntrySchema(accountModelName, {
116
+ extraItemFields: { departmentId: { type: ObjectId, ref: 'Department' } },
117
+ })
118
+ accounting.createFiscalPeriodSchema(options?)
119
+ accounting.createBudgetSchema(options?)
120
+ accounting.createReconciliationSchema(accountModelName, journalEntryModelName, options?)
121
+ ```
122
+
123
+ ## Plugins
124
+
125
+ ```typescript
126
+ import { dateLockPlugin, taxHookPlugin } from '@classytic/ledger';
127
+
128
+ // Date lock — block posting before a date
129
+ dateLockPlugin({
130
+ getLockDate: async (orgId) => db.getOrgLockDate(orgId),
131
+ JournalEntryModel,
132
+ });
133
+
134
+ // Tax hook — auto-generate tax lines
135
+ taxHookPlugin({
136
+ generator: {
137
+ generateTaxLines(input) {
138
+ if (!input.taxCode) return [];
139
+ const tax = Money.percentage(input.amount, 1300); // 13%
140
+ return [{ account: hstAccountId, debit: 0, credit: tax, taxDetails: [{ taxCode: 'HST' }] }];
141
+ },
142
+ },
70
143
  });
71
144
  ```
72
145
 
73
146
  ## Subpath Exports
74
147
 
75
- | Import path | Contents |
76
- |---|---|
77
- | `@classytic/ledger` | Engine, Money, schemas, plugins, reports, repositories, constants, types |
78
- | `@classytic/ledger/money` | `Money` class (cents-based arithmetic) |
79
- | `@classytic/ledger/schemas` | `createAccountSchema`, `createJournalEntrySchema`, `createFiscalPeriodSchema` |
80
- | `@classytic/ledger/reports` | Report generators (trial balance, balance sheet, etc.) |
81
- | `@classytic/ledger/plugins` | `doubleEntryPlugin`, `fiscalLockPlugin`, `idempotencyPlugin` |
82
- | `@classytic/ledger/repositories` | `wireJournalEntryMethods`, `wireAccountMethods` |
83
- | `@classytic/ledger/exports` | CSV export: `exportToCsv`, `flattenJournalEntries`, field maps |
84
- | `@classytic/ledger/constants` | Categories, journal types, currencies |
148
+ | Path | Contents |
149
+ |------|----------|
150
+ | `@classytic/ledger` | Engine, Money, all schemas, plugins, reports, types |
151
+ | `@classytic/ledger/money` | `Money` class |
152
+ | `@classytic/ledger/schemas` | Schema factories |
153
+ | `@classytic/ledger/reports` | Report generators |
154
+ | `@classytic/ledger/plugins` | All plugins |
155
+ | `@classytic/ledger/repositories` | Repository wiring |
156
+ | `@classytic/ledger/exports` | CSV export + QuickBooks field maps |
85
157
  | `@classytic/ledger/country` | `defineCountryPack`, `CountryPack` interface |
158
+ | `@classytic/ledger/constants` | Categories, journal types, currencies |
159
+
160
+ ## Country Packs
86
161
 
87
- ## Documentation
162
+ Build your own or use an existing one:
163
+
164
+ ```typescript
165
+ import { defineCountryPack } from '@classytic/ledger';
166
+
167
+ export const myPack = defineCountryPack({
168
+ code: 'US',
169
+ name: 'United States',
170
+ defaultCurrency: 'USD',
171
+ retainedEarningsAccountCode: '3200',
172
+ accountTypes: [ /* your chart of accounts */ ],
173
+ taxCodes: { /* your tax codes */ },
174
+ taxCodesByRegion: {},
175
+ regions: [],
176
+ });
177
+ ```
178
+
179
+ Available packs: `@classytic/ledger-ca` (Canada), `@classytic/ledger-bd` (Bangladesh).
180
+
181
+ ## Testing
182
+
183
+ 949 tests covering unit, integration, and end-to-end scenarios:
184
+
185
+ ```bash
186
+ npm test # run all
187
+ npx vitest run tests/e2e/ # e2e scenarios only
188
+ ```
88
189
 
89
- - [Engine & Configuration](docs/engine.md)
90
- - [Schemas](docs/schemas.md)
91
- - [Repositories](docs/repositories.md)
92
- - [Reports](docs/reports.md)
93
- - [Plugins](docs/plugins.md)
94
- - [Exports](docs/exports.md)
95
- - [Country Packs](docs/country-packs.md)
96
- - [Money](docs/money.md)
97
- - [Subledger Integration](docs/subledger-integration.md)
190
+ E2E suites include:
191
+ - Canadian small business full-year lifecycle
192
+ - Multi-currency trading with FX revaluation
193
+ - All plugins + dimensions + budgets + fiscal close
194
+ - O-Level / A-Level / university textbook accounting problems
98
195
 
99
196
  ## Requirements
100
197
 
101
198
  - Node.js >= 22
102
199
  - MongoDB (replica set recommended for transactions)
103
200
  - Mongoose >= 9
104
- - @classytic/mongokit >= 3.3.2
201
+ - @classytic/mongokit >= 3
105
202
 
106
203
  ## License
107
204
 
@@ -66,5 +66,3 @@ function extractStatementType(key) {
66
66
  }
67
67
  //#endregion
68
68
  export { extractStatementType as a, getNormalBalance as c, isValidCategory as d, extractMainType as i, isBalanceSheet as l, CATEGORY_KEYS as n, getCategoryMainType as o, categoryKey as r, getCategoryStatementType as s, CATEGORIES as t, isIncomeStatement as u };
69
-
70
- //# sourceMappingURL=categories-CclX7Q94.mjs.map
@@ -1,2 +1,2 @@
1
- import { _ as getNormalBalance, a as JOURNAL_CODES, b as isValidCategory, c as getJournalTypeCodes, d as CATEGORY_KEYS, f as categoryKey, g as getCategoryStatementType, h as getCategoryMainType, i as isValidCurrency, l as isValidJournalType, m as extractStatementType, n as getCurrency, o as JOURNAL_TYPES, p as extractMainType, r as getMinorUnit, s as getJournalType, t as CURRENCIES, u as CATEGORIES, v as isBalanceSheet, y as isIncomeStatement } from "../currencies-4WAbFRlw.mjs";
2
- export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType };
1
+ import { S as isValidCategory, _ as getCategoryMainType, a as getJournalTypeCodes, b as isBalanceSheet, c as CURRENCIES, d as isValidCurrency, f as CATEGORIES, g as extractStatementType, h as extractMainType, i as getJournalType, l as getCurrency, m as categoryKey, n as JOURNAL_TYPES, o as isValidJournalType, p as CATEGORY_KEYS, r as getCustomJournalTypes, s as registerJournalType, t as JOURNAL_CODES, u as getMinorUnit, v as getCategoryStatementType, x as isIncomeStatement, y as getNormalBalance } from "../journals-DTipb_rz.mjs";
2
+ export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getCustomJournalTypes, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, registerJournalType };
@@ -1,4 +1,4 @@
1
- import { a as isValidJournalType, i as getJournalTypeCodes, n as JOURNAL_TYPES, r as getJournalType, t as JOURNAL_CODES } from "../journals-oH-FK3g8.mjs";
2
- import { a as extractStatementType, c as getNormalBalance, d as isValidCategory, i as extractMainType, l as isBalanceSheet, n as CATEGORY_KEYS, o as getCategoryMainType, r as categoryKey, s as getCategoryStatementType, t as CATEGORIES, u as isIncomeStatement } from "../categories-CclX7Q94.mjs";
1
+ import { a as extractStatementType, c as getNormalBalance, d as isValidCategory, i as extractMainType, l as isBalanceSheet, n as CATEGORY_KEYS, o as getCategoryMainType, r as categoryKey, s as getCategoryStatementType, t as CATEGORIES, u as isIncomeStatement } from "../categories-FJlrvzcl.mjs";
2
+ import { a as getJournalType, c as registerJournalType, i as getCustomJournalTypes, n as JOURNAL_TYPES, o as getJournalTypeCodes, s as isValidJournalType, t as JOURNAL_CODES } from "../journals-BcMn71Cq.mjs";
3
3
  import { i as isValidCurrency, n as getCurrency, r as getMinorUnit, t as CURRENCIES } from "../currencies-W8kQAkm0.mjs";
4
- export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType };
4
+ export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getCustomJournalTypes, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, registerJournalType };
@@ -100,5 +100,4 @@ interface DateRange {
100
100
  endDate: Date;
101
101
  }
102
102
  //#endregion
103
- export { TaxMetadata as _, Cents as a, DateRange as c, JournalType as d, MainType as f, TaxDetail as g, StatementType as h, CategoryKey as i, EntryState as l, ObjectId as m, CashFlowCategory as n, Currency as o, NormalBalance as p, Category as r, DateOption as s, AccountType as t, JournalItem as u, TotalAccountOp as v };
104
- //# sourceMappingURL=core-8Xfnpn6g.d.mts.map
103
+ export { TaxMetadata as _, Cents as a, DateRange as c, JournalType as d, MainType as f, TaxDetail as g, StatementType as h, CategoryKey as i, EntryState as l, ObjectId as m, CashFlowCategory as n, Currency as o, NormalBalance as p, Category as r, DateOption as s, AccountType as t, JournalItem as u, TotalAccountOp as v };
@@ -1,2 +1,2 @@
1
- import { a as TaxReportLine, i as TaxCodesByRegion, n as CountryPackInput, o as TaxReportTemplate, r as TaxCode, s as defineCountryPack, t as CountryPack } from "../index-ZnSiqHYV.mjs";
1
+ import { a as TaxReportLine, i as TaxCodesByRegion, n as CountryPackInput, o as TaxReportTemplate, r as TaxCode, s as defineCountryPack, t as CountryPack } from "../index-CxZqRaOU.mjs";
2
2
  export { CountryPack, CountryPackInput, TaxCode, TaxCodesByRegion, TaxReportLine, TaxReportTemplate, defineCountryPack };
@@ -23,5 +23,3 @@ function defineCountryPack(input) {
23
23
  }
24
24
  //#endregion
25
25
  export { defineCountryPack };
26
-
27
- //# sourceMappingURL=index.mjs.map
@@ -78,5 +78,3 @@ function getMinorUnit(code) {
78
78
  }
79
79
  //#endregion
80
80
  export { isValidCurrency as i, getCurrency as n, getMinorUnit as r, CURRENCIES as t };
81
-
82
- //# sourceMappingURL=currencies-W8kQAkm0.mjs.map
@@ -1,4 +1,4 @@
1
- import { n as Errors } from "./errors-B7yC-Jfw.mjs";
1
+ import { n as Errors } from "./errors-BoGUSUYL.mjs";
2
2
  //#region src/plugins/double-entry.plugin.ts
3
3
  function doubleEntryPlugin(options = {}) {
4
4
  const { onlyOnPost = true, JournalEntryModel, AccountModel, orgField } = options;
@@ -36,7 +36,7 @@ function doubleEntryPlugin(options = {}) {
36
36
  const accountIds = items.map((i) => i.account).filter((a) => a != null && a !== "");
37
37
  if (accountIds.length === 0) throw Errors.validation("Posted entry has items with missing accounts.");
38
38
  const selectFields = orgField ? `_id ${orgField}` : "_id";
39
- const accounts = await AccountModel.find({ _id: { $in: accountIds } }).select(selectFields).session(context.session ?? null).lean();
39
+ const accounts = await AccountModel?.find({ _id: { $in: accountIds } }).select(selectFields).session(context.session ?? null).lean();
40
40
  const foundIds = new Set(accounts.map((a) => String(a._id)));
41
41
  const missingCount = accountIds.filter((id) => !foundIds.has(String(id))).length;
42
42
  if (missingCount > 0) throw Errors.validation(`${missingCount} item(s) reference non-existent accounts.`);
@@ -80,8 +80,8 @@ function doubleEntryPlugin(options = {}) {
80
80
  ...existing
81
81
  }, context);
82
82
  };
83
- repo.on("before:create", (payload) => validate(payload));
84
- repo.on("before:update", (payload) => validateUpdate(payload));
83
+ repo.on("before:create", validate);
84
+ repo.on("before:update", validateUpdate);
85
85
  }
86
86
  };
87
87
  }
@@ -132,8 +132,8 @@ function fiscalLockPlugin(options) {
132
132
  throw Errors.fiscal(`Cannot post entry dated ${entryDate.toISOString().split("T")[0]}: fiscal period "${period.name}" is closed.`);
133
133
  }
134
134
  };
135
- repo.on("before:create", (payload) => checkPeriod(payload, false));
136
- repo.on("before:update", (payload) => checkPeriod(payload, true));
135
+ repo.on("before:create", (ctx) => checkPeriod(ctx, false));
136
+ repo.on("before:update", (ctx) => checkPeriod(ctx, true));
137
137
  }
138
138
  };
139
139
  }
@@ -144,8 +144,7 @@ function idempotencyPlugin(options) {
144
144
  return {
145
145
  name: "accounting:idempotency",
146
146
  apply(repo) {
147
- repo.on("before:create", async (raw) => {
148
- const context = raw;
147
+ repo.on("before:create", async (context) => {
149
148
  const data = context.data;
150
149
  if (!data?.idempotencyKey) return;
151
150
  const query = { idempotencyKey: data.idempotencyKey };
@@ -157,6 +156,47 @@ function idempotencyPlugin(options) {
157
156
  };
158
157
  }
159
158
  //#endregion
160
- export { fiscalLockPlugin as n, doubleEntryPlugin as r, idempotencyPlugin as t };
161
-
162
- //# sourceMappingURL=idempotency.plugin-v9NQ_ta-.mjs.map
159
+ //#region src/plugins/date-lock.plugin.ts
160
+ function dateLockPlugin(options) {
161
+ const { getLockDate, JournalEntryModel, orgField } = options;
162
+ return {
163
+ name: "accounting:date-lock",
164
+ apply(repo) {
165
+ const checkLock = async (context, isUpdate) => {
166
+ const data = context.data;
167
+ if (!data) return;
168
+ if (data.state !== "posted") return;
169
+ const session = context.session ?? null;
170
+ let entryDate;
171
+ let persistedDoc = null;
172
+ if (data.date) entryDate = new Date(data.date);
173
+ else if (!isUpdate) entryDate = /* @__PURE__ */ new Date();
174
+ else {
175
+ if (!context.id) throw new Error("dateLockPlugin: update context is missing \"id\". Cannot validate date lock without document ID.");
176
+ const selectFields = orgField ? `date ${orgField}` : "date";
177
+ persistedDoc = await JournalEntryModel.findById(context.id).select(selectFields).session(session).lean();
178
+ if (persistedDoc?.date) entryDate = new Date(persistedDoc.date);
179
+ }
180
+ if (!entryDate) return;
181
+ let orgValue;
182
+ if (orgField) {
183
+ orgValue = data[orgField] ?? context[orgField];
184
+ if (!orgValue && isUpdate) {
185
+ if (persistedDoc) orgValue = persistedDoc[orgField];
186
+ else if (context.id) {
187
+ const persisted = await JournalEntryModel.findById(context.id).select(orgField).session(session).lean();
188
+ if (persisted) orgValue = persisted[orgField];
189
+ }
190
+ }
191
+ }
192
+ const lockDate = await getLockDate(orgValue, session ?? void 0);
193
+ if (!lockDate) return;
194
+ if (entryDate < lockDate) throw Errors.fiscal(`Cannot post entry dated ${entryDate.toISOString().split("T")[0]}: date is before lock date ${lockDate.toISOString().split("T")[0]}.`);
195
+ };
196
+ repo.on("before:create", (ctx) => checkLock(ctx, false));
197
+ repo.on("before:update", (ctx) => checkLock(ctx, true));
198
+ }
199
+ };
200
+ }
201
+ //#endregion
202
+ export { doubleEntryPlugin as i, idempotencyPlugin as n, fiscalLockPlugin as r, dateLockPlugin as t };
@@ -1,4 +1,4 @@
1
- import { t as CountryPack } from "./index-ZnSiqHYV.mjs";
1
+ import { t as CountryPack } from "./index-CxZqRaOU.mjs";
2
2
  import { t as Logger } from "./logger-UbTdBb1x.mjs";
3
3
 
4
4
  //#region src/types/engine.d.ts
@@ -80,9 +80,14 @@ interface AccountingEngineConfig {
80
80
  multiCurrency?: MultiCurrencyConfig;
81
81
  /** Fiscal year start month (1-12, default: 1 = January) */
82
82
  fiscalYearStartMonth?: number;
83
- /** Display code for prior retained earnings on balance sheet (default: '3660') */
84
- retainedEarningsCode?: string;
85
- /** Display code for current year net income on balance sheet (default: '3680') */
83
+ /**
84
+ * The retained earnings account code (e.g. '3600' CA, '3310' BD).
85
+ * Overrides the country pack value. See CountryPack.retainedEarningsAccountCode.
86
+ */
87
+ retainedEarningsAccountCode?: string;
88
+ /** Display code for the "Previous Years Retained Earnings" line. Overrides country pack. */
89
+ retainedEarningsDisplayCode?: string;
90
+ /** Display code for current year net income line. Overrides country pack. */
86
91
  currentYearEarningsCode?: string;
87
92
  /** Logger instance. Defaults to console-based logger. */
88
93
  logger?: Logger;
@@ -94,5 +99,4 @@ interface AccountingEngineConfig {
94
99
  strictness?: StrictnessConfig;
95
100
  }
96
101
  //#endregion
97
- export { MultiTenantConfig as a, MultiCurrencyConfig as i, AuditConfig as n, SchemaOptions as o, JournalSchemaOptions as r, StrictnessConfig as s, AccountingEngineConfig as t };
98
- //# sourceMappingURL=engine-BzBMpWuy.d.mts.map
102
+ export { MultiTenantConfig as a, MultiCurrencyConfig as i, AuditConfig as n, SchemaOptions as o, JournalSchemaOptions as r, StrictnessConfig as s, AccountingEngineConfig as t };
@@ -24,5 +24,3 @@ const Errors = {
24
24
  };
25
25
  //#endregion
26
26
  export { Errors as n, AccountingError as t };
27
-
28
- //# sourceMappingURL=errors-B7yC-Jfw.mjs.map
@@ -1,2 +1,2 @@
1
- import { _ as PopulatedJournalEntry, a as exportToCsv, c as getHeaders, d as serializeCsv, f as CsvOptions, g as PopulatedAccount, h as FlatJournalRow, i as flattenJournalEntry, l as buildCsv, m as ExportFieldMap, n as quickbooksFieldMap, o as extractAllRows, p as ExportField, r as flattenJournalEntries, s as extractRow, t as universalFieldMap, u as escapeCell, v as PopulatedJournalItem } from "../index-BPukb3L8.mjs";
1
+ import { _ as PopulatedJournalEntry, a as exportToCsv, c as getHeaders, d as serializeCsv, f as CsvOptions, g as PopulatedAccount, h as FlatJournalRow, i as quickbooksFieldMap, l as buildCsv, m as ExportFieldMap, n as flattenJournalEntry, o as extractAllRows, p as ExportField, r as universalFieldMap, s as extractRow, t as flattenJournalEntries, u as escapeCell, v as PopulatedJournalItem } from "../index-J-XIbXH-.mjs";
2
2
  export { CsvOptions, ExportField, ExportFieldMap, FlatJournalRow, PopulatedAccount, PopulatedJournalEntry, PopulatedJournalItem, buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };
@@ -1,2 +1,2 @@
1
- import { a as exportToCsv, c as getHeaders, d as serializeCsv, i as flattenJournalEntry, l as buildCsv, n as quickbooksFieldMap, o as extractAllRows, r as flattenJournalEntries, s as extractRow, t as universalFieldMap, u as escapeCell } from "../exports-I5Xkq-9_.mjs";
1
+ import { a as exportToCsv, c as getHeaders, d as serializeCsv, i as quickbooksFieldMap, l as buildCsv, n as flattenJournalEntry, o as extractAllRows, r as universalFieldMap, s as extractRow, t as flattenJournalEntries, u as escapeCell } from "../exports-DoGQQtMQ.mjs";
2
2
  export { buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };