@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/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
- import { n as createJournalEntrySchema, r as createAccountSchema, t as createFiscalPeriodSchema } from "./fiscal-period.schema-BQ5wsAq3.mjs";
2
- import { a as isValidJournalType, i as getJournalTypeCodes, n as JOURNAL_TYPES, t as JOURNAL_CODES } from "./journals-oH-FK3g8.mjs";
3
- import { a as generateIncomeStatement, c as calculateTotal, d as generateTrialBalance, f as buildItemFilters, i as generateGeneralLedger, l as computeEndingBalance, m as getFiscalYearStart, n as reopenFiscalPeriod, o as generateBalanceSheet, p as getDateRange, r as generateCashFlow, s as buildAccountTypeMap, t as closeFiscalPeriod, u as isVirtualTaxAccount } from "./fiscal-close-L631E3De.mjs";
4
- import { n as Errors, t as AccountingError } from "./errors-B7yC-Jfw.mjs";
5
- import { n as finalizeSession, r as defaultLogger, t as acquireSession } from "./session-Ba8E3Ufa.mjs";
6
- import { c as getNormalBalance, d as isValidCategory, l as isBalanceSheet, n as CATEGORY_KEYS, t as CATEGORIES, u as isIncomeStatement } from "./categories-CclX7Q94.mjs";
7
1
  import { Money, add, allocate, format, formatPlain, fromDecimal, multiply, parseCents, percentage, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal } from "./money.mjs";
8
- import { n as wireJournalEntryMethods, t as wireAccountMethods } from "./account.repository-kDKwDt0I.mjs";
9
- import { n as fiscalLockPlugin, r as doubleEntryPlugin, t as idempotencyPlugin } from "./idempotency.plugin-v9NQ_ta-.mjs";
2
+ import { n as Errors, t as AccountingError } from "./errors-BoGUSUYL.mjs";
3
+ import { i as doubleEntryPlugin, n as idempotencyPlugin, r as fiscalLockPlugin, t as dateLockPlugin } from "./date-lock.plugin-C8kqPBjh.mjs";
4
+ import { C as DEFAULT_BUCKETS, S as isVirtualTaxAccount, _ as getDateRange, a as defaultLogger, b as calculateTotal, c as buildRevaluationEntry, d as generateGeneralLedger, f as generateDimensionBreakdown, g as buildItemFilters, h as generateBalanceSheet, i as finalizeSession, l as computeRevaluation, m as generateBudgetVsActual, n as reopenFiscalPeriod, o as generateTrialBalance, p as generateCashFlow, r as acquireSession, s as generateRevaluation, t as closeFiscalPeriod, u as generateIncomeStatement, v as getFiscalYearStart, w as generateAgedBalance, x as computeEndingBalance, y as buildAccountTypeMap } from "./fiscal-close-DmPV82e4.mjs";
5
+ import { c as getNormalBalance, d as isValidCategory, l as isBalanceSheet, n as CATEGORY_KEYS, t as CATEGORIES, u as isIncomeStatement } from "./categories-FJlrvzcl.mjs";
6
+ import { n as wireJournalEntryMethods, r as wireAccountMethods, t as wireReconciliationMethods } from "./reconciliation.repository-DgJEDVS-.mjs";
7
+ import { a as createAccountSchema, i as createBudgetSchema, n as createJournalEntrySchema, r as createFiscalPeriodSchema, t as createReconciliationSchema } from "./reconciliation.schema-KScbsXbY.mjs";
8
+ 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";
10
9
  import { i as isValidCurrency, n as getCurrency, r as getMinorUnit, t as CURRENCIES } from "./currencies-W8kQAkm0.mjs";
11
10
  import { defineCountryPack } from "./country/index.mjs";
12
- import { a as exportToCsv, n as quickbooksFieldMap, r as flattenJournalEntries, t as universalFieldMap } from "./exports-I5Xkq-9_.mjs";
11
+ import { a as exportToCsv, i as quickbooksFieldMap, r as universalFieldMap, t as flattenJournalEntries } from "./exports-DoGQQtMQ.mjs";
12
+ import { Schema } from "mongoose";
13
13
  //#region src/engine.ts
14
14
  var AccountingEngine = class {
15
15
  config;
@@ -30,12 +30,19 @@ var AccountingEngine = class {
30
30
  createFiscalPeriodSchema(options) {
31
31
  return createFiscalPeriodSchema(this.config, options);
32
32
  }
33
+ createBudgetSchema(options) {
34
+ return createBudgetSchema(this.config, options);
35
+ }
36
+ createReconciliationSchema(accountModelName, journalEntryModelName, options) {
37
+ return createReconciliationSchema(this.config, accountModelName, journalEntryModelName, options);
38
+ }
33
39
  createReports(models) {
34
- const { Account: AccountModel, JournalEntry: JournalEntryModel } = models;
40
+ const { Account: AccountModel, JournalEntry: JournalEntryModel, Budget: BudgetModel } = models;
35
41
  const { country, config } = this;
36
42
  const orgField = config.multiTenant?.orgField;
37
43
  const fiscalYearStartMonth = config.fiscalYearStartMonth ?? 1;
38
- const retainedEarningsCode = config.retainedEarningsCode;
44
+ const retainedEarningsAccountCode = config.retainedEarningsAccountCode;
45
+ const retainedEarningsDisplayCode = config.retainedEarningsDisplayCode;
39
46
  const currentYearEarningsCode = config.currentYearEarningsCode;
40
47
  return {
41
48
  trialBalance: (params) => generateTrialBalance({
@@ -51,7 +58,8 @@ var AccountingEngine = class {
51
58
  country,
52
59
  orgField,
53
60
  fiscalYearStartMonth,
54
- retainedEarningsCode,
61
+ retainedEarningsAccountCode,
62
+ retainedEarningsDisplayCode,
55
63
  currentYearEarningsCode
56
64
  }, params),
57
65
  incomeStatement: (params) => generateIncomeStatement({
@@ -72,6 +80,35 @@ var AccountingEngine = class {
72
80
  JournalEntryModel,
73
81
  country,
74
82
  orgField
83
+ }, params),
84
+ agedBalance: (params) => generateAgedBalance({
85
+ AccountModel,
86
+ JournalEntryModel,
87
+ country,
88
+ orgField
89
+ }, params),
90
+ dimensionBreakdown: (params) => generateDimensionBreakdown({
91
+ AccountModel,
92
+ JournalEntryModel,
93
+ country,
94
+ orgField
95
+ }, params),
96
+ budgetVsActual: (params) => {
97
+ if (!BudgetModel) throw new Error("Budget model required — pass Budget to createReports()");
98
+ return generateBudgetVsActual({
99
+ AccountModel,
100
+ JournalEntryModel,
101
+ BudgetModel,
102
+ country,
103
+ orgField
104
+ }, params);
105
+ },
106
+ revaluation: (params) => generateRevaluation({
107
+ AccountModel,
108
+ JournalEntryModel,
109
+ country,
110
+ orgField,
111
+ baseCurrency: this.currency
75
112
  }, params)
76
113
  };
77
114
  }
@@ -110,23 +147,22 @@ var AccountingEngine = class {
110
147
  createJournalEntryRepository(createRepository, models, additionalPlugins = []) {
111
148
  const orgField = this.config.multiTenant?.orgField;
112
149
  const { JournalEntryModel, AccountModel, FiscalPeriodModel } = models;
150
+ const jeModel = JournalEntryModel;
113
151
  const plugins = [...additionalPlugins, doubleEntryPlugin({
114
- JournalEntryModel,
152
+ JournalEntryModel: jeModel,
115
153
  AccountModel,
116
154
  orgField
117
155
  })];
118
156
  if (FiscalPeriodModel) plugins.push(fiscalLockPlugin({
119
157
  FiscalPeriodModel,
120
- JournalEntryModel,
158
+ JournalEntryModel: jeModel,
121
159
  orgField
122
160
  }));
123
161
  if (this.config.idempotency) plugins.push(idempotencyPlugin({
124
- JournalEntryModel,
162
+ JournalEntryModel: jeModel,
125
163
  orgField
126
164
  }));
127
- const repository = createRepository(JournalEntryModel, plugins);
128
- wireJournalEntryMethods(repository, JournalEntryModel, orgField, this.config.strictness);
129
- return repository;
165
+ return wireJournalEntryMethods(createRepository(JournalEntryModel, plugins), JournalEntryModel, orgField, this.config.strictness);
130
166
  }
131
167
  /**
132
168
  * Wire post/reverse domain methods onto a mongokit Repository
@@ -143,8 +179,7 @@ var AccountingEngine = class {
143
179
  */
144
180
  wireJournalEntryRepository(repository, JournalEntryModel) {
145
181
  const orgField = this.config.multiTenant?.orgField;
146
- wireJournalEntryMethods(repository, JournalEntryModel, orgField, this.config.strictness);
147
- return repository;
182
+ return wireJournalEntryMethods(repository, JournalEntryModel, orgField, this.config.strictness);
148
183
  }
149
184
  /**
150
185
  * Wire seedAccounts/bulkCreate and posting-account validation onto a
@@ -157,14 +192,77 @@ var AccountingEngine = class {
157
192
  */
158
193
  wireAccountRepository(repository, AccountModel) {
159
194
  const orgField = this.config.multiTenant?.orgField;
160
- wireAccountMethods(repository, AccountModel, this.country, orgField);
161
- return repository;
195
+ return wireAccountMethods(repository, AccountModel, this.country, orgField);
196
+ }
197
+ /**
198
+ * Wire reconcile/unreconcile/getUnreconciled methods onto a mongokit Repository.
199
+ */
200
+ wireReconciliationRepository(repository, ReconciliationModel, JournalEntryModel) {
201
+ const orgField = this.config.multiTenant?.orgField;
202
+ return wireReconciliationMethods(repository, ReconciliationModel, JournalEntryModel, orgField);
162
203
  }
163
204
  };
164
205
  function createAccountingEngine(config) {
165
206
  return new AccountingEngine(config);
166
207
  }
167
208
  //#endregion
168
- export { AccountingEngine, AccountingError, CATEGORIES, CATEGORY_KEYS, CURRENCIES, Errors, JOURNAL_CODES, JOURNAL_TYPES, Money, acquireSession, add, allocate, buildAccountTypeMap, buildItemFilters, calculateTotal, closeFiscalPeriod, computeEndingBalance, createAccountSchema, createAccountingEngine, createFiscalPeriodSchema, createJournalEntrySchema, defaultLogger, defineCountryPack, doubleEntryPlugin, exportToCsv, finalizeSession, fiscalLockPlugin, flattenJournalEntries, format, formatPlain, fromDecimal, generateBalanceSheet, generateCashFlow, generateGeneralLedger, generateIncomeStatement, generateTrialBalance, getCurrency, getDateRange, getFiscalYearStart, getJournalTypeCodes, getMinorUnit, getNormalBalance, idempotencyPlugin, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, isVirtualTaxAccount, multiply, parseCents, percentage, quickbooksFieldMap, reopenFiscalPeriod, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal, universalFieldMap, wireAccountMethods, wireJournalEntryMethods };
169
-
170
- //# sourceMappingURL=index.mjs.map
209
+ //#region src/utils/dimensions.ts
210
+ /**
211
+ * Analytic Dimensions — Helpers for defining analytic dimensions
212
+ * (department, project, cost center) on journal items.
213
+ *
214
+ * Generates Mongoose schema fields and indexes for dimension queries.
215
+ */
216
+ /**
217
+ * Build extraItemFields schema definition for a set of dimensions.
218
+ *
219
+ * Returns a Mongoose schema-compatible object suitable for spreading into
220
+ * `extraItemFields` in JournalSchemaOptions.
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const fields = buildDimensionFields([
225
+ * { field: 'departmentId', label: 'Department', ref: 'Department' },
226
+ * { field: 'projectId', label: 'Project', ref: 'Project' },
227
+ * ]);
228
+ * // => { departmentId: { type: Schema.Types.ObjectId, ref: 'Department', required: false, default: null }, ... }
229
+ * ```
230
+ */
231
+ function buildDimensionFields(dimensions) {
232
+ const fields = {};
233
+ for (const dim of dimensions) {
234
+ const fieldDef = {
235
+ type: Schema.Types.ObjectId,
236
+ required: dim.required ?? false,
237
+ default: null
238
+ };
239
+ if (dim.ref) fieldDef.ref = dim.ref;
240
+ fields[dim.field] = fieldDef;
241
+ }
242
+ return fields;
243
+ }
244
+ /**
245
+ * Build extra indexes for dimension queries.
246
+ *
247
+ * Each dimension gets a compound index on `journalItems.{field}` + `date`
248
+ * for efficient filtered reporting. When `orgField` is provided, it is
249
+ * prepended to each index for multi-tenant scoping.
250
+ *
251
+ * @param dimensions - Array of dimension definitions
252
+ * @param orgField - Optional org-scoping field name (e.g. 'business')
253
+ * @returns Array of index specifications compatible with `extraIndexes` in JournalSchemaOptions
254
+ */
255
+ function buildDimensionIndexes(dimensions, orgField) {
256
+ return dimensions.map((dim) => {
257
+ const fields = {};
258
+ if (orgField) fields[orgField] = 1;
259
+ fields[`journalItems.${dim.field}`] = 1;
260
+ fields.date = -1;
261
+ return {
262
+ fields,
263
+ options: {}
264
+ };
265
+ });
266
+ }
267
+ //#endregion
268
+ export { AccountingEngine, AccountingError, CATEGORIES, CATEGORY_KEYS, CURRENCIES, DEFAULT_BUCKETS, Errors, JOURNAL_CODES, JOURNAL_TYPES, Money, acquireSession, add, allocate, buildAccountTypeMap, buildDimensionFields, buildDimensionIndexes, buildItemFilters, buildRevaluationEntry, calculateTotal, closeFiscalPeriod, computeEndingBalance, computeRevaluation, createAccountSchema, createAccountingEngine, createFiscalPeriodSchema, createJournalEntrySchema, dateLockPlugin, defaultLogger, defineCountryPack, doubleEntryPlugin, exportToCsv, finalizeSession, fiscalLockPlugin, flattenJournalEntries, format, formatPlain, fromDecimal, generateAgedBalance, generateBalanceSheet, generateBudgetVsActual, generateCashFlow, generateDimensionBreakdown, generateGeneralLedger, generateIncomeStatement, generateRevaluation, generateTrialBalance, getCurrency, getCustomJournalTypes, getDateRange, getFiscalYearStart, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, idempotencyPlugin, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, isVirtualTaxAccount, multiply, parseCents, percentage, quickbooksFieldMap, registerJournalType, reopenFiscalPeriod, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal, universalFieldMap, wireAccountMethods, wireJournalEntryMethods, wireReconciliationMethods };
@@ -0,0 +1,103 @@
1
+ import { o as SchemaOptions, r as JournalSchemaOptions, t as AccountingEngineConfig } from "./engine-DF-MtsEr.mjs";
2
+ import mongoose from "mongoose";
3
+
4
+ //#region src/schemas/account.schema.d.ts
5
+ declare function createAccountSchema(config: AccountingEngineConfig, options?: SchemaOptions): mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
6
+ timestamps: true;
7
+ }, {
8
+ [x: number]: any;
9
+ [x: string]: any;
10
+ } & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
11
+ [x: number]: any;
12
+ [x: string]: any;
13
+ } & mongoose.DefaultTimestampProps, {
14
+ id: string;
15
+ }, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
16
+ timestamps: true;
17
+ }> & Omit<{
18
+ [x: number]: any;
19
+ [x: string]: any;
20
+ } & mongoose.DefaultTimestampProps & {
21
+ _id: mongoose.Types.ObjectId;
22
+ } & {
23
+ __v: number;
24
+ }, "id"> & {
25
+ id: string;
26
+ }, unknown, {
27
+ [x: number]: any;
28
+ [x: string]: any;
29
+ createdAt: NativeDate;
30
+ updatedAt: NativeDate;
31
+ } & {
32
+ _id: mongoose.Types.ObjectId;
33
+ } & {
34
+ __v: number;
35
+ }>;
36
+ //#endregion
37
+ //#region src/schemas/fiscal-period.schema.d.ts
38
+ declare function createFiscalPeriodSchema(config: AccountingEngineConfig, options?: SchemaOptions): mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
39
+ timestamps: true;
40
+ }, {
41
+ [x: number]: any;
42
+ [x: string]: any;
43
+ } & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
44
+ [x: number]: any;
45
+ [x: string]: any;
46
+ } & mongoose.DefaultTimestampProps, {
47
+ id: string;
48
+ }, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
49
+ timestamps: true;
50
+ }> & Omit<{
51
+ [x: number]: any;
52
+ [x: string]: any;
53
+ } & mongoose.DefaultTimestampProps & {
54
+ _id: mongoose.Types.ObjectId;
55
+ } & {
56
+ __v: number;
57
+ }, "id"> & {
58
+ id: string;
59
+ }, unknown, {
60
+ [x: number]: any;
61
+ [x: string]: any;
62
+ createdAt: NativeDate;
63
+ updatedAt: NativeDate;
64
+ } & {
65
+ _id: mongoose.Types.ObjectId;
66
+ } & {
67
+ __v: number;
68
+ }>;
69
+ //#endregion
70
+ //#region src/schemas/journal-entry.schema.d.ts
71
+ declare function createJournalEntrySchema(config: AccountingEngineConfig, accountModelName: string, options?: JournalSchemaOptions): mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
72
+ timestamps: true;
73
+ }, {
74
+ [x: number]: any;
75
+ [x: string]: any;
76
+ } & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
77
+ [x: number]: any;
78
+ [x: string]: any;
79
+ } & mongoose.DefaultTimestampProps, {
80
+ id: string;
81
+ }, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
82
+ timestamps: true;
83
+ }> & Omit<{
84
+ [x: number]: any;
85
+ [x: string]: any;
86
+ } & mongoose.DefaultTimestampProps & {
87
+ _id: mongoose.Types.ObjectId;
88
+ } & {
89
+ __v: number;
90
+ }, "id"> & {
91
+ id: string;
92
+ }, unknown, {
93
+ [x: number]: any;
94
+ [x: string]: any;
95
+ createdAt: NativeDate;
96
+ updatedAt: NativeDate;
97
+ } & {
98
+ _id: mongoose.Types.ObjectId;
99
+ } & {
100
+ __v: number;
101
+ }>;
102
+ //#endregion
103
+ export { createFiscalPeriodSchema as n, createAccountSchema as r, createJournalEntrySchema as t };
@@ -77,16 +77,37 @@ const JOURNAL_TYPES = Object.freeze({
77
77
  }
78
78
  });
79
79
  const JOURNAL_CODES = Object.freeze(Object.fromEntries(Object.keys(JOURNAL_TYPES).map((k) => [k, k])));
80
+ const _customTypes = {};
81
+ let _frozen = false;
82
+ /**
83
+ * Register a custom journal type. Must be called **before** schema
84
+ * initialization (`createJournalEntrySchema`). Custom types are
85
+ * automatically included in Mongoose enum validation and all lookup
86
+ * functions.
87
+ */
88
+ function registerJournalType(code, def) {
89
+ if (_frozen) throw new Error("Cannot register journal types after schema initialization");
90
+ if (code in JOURNAL_TYPES) throw new Error(`Cannot override built-in journal type: ${code}`);
91
+ if (def.code !== code) throw new Error(`Journal type code mismatch: key="${code}" but def.code="${def.code}"`);
92
+ if (!def.name || !def.description) throw new Error(`Journal type "${code}" requires non-empty name and description`);
93
+ _customTypes[code] = def;
94
+ }
95
+ /** Returns all custom (non-built-in) journal types. */
96
+ function getCustomJournalTypes() {
97
+ return Object.values(_customTypes);
98
+ }
99
+ /** @internal Lock the registry — called by `createJournalEntrySchema`. */
100
+ function _freezeJournalTypes() {
101
+ _frozen = true;
102
+ }
80
103
  function getJournalTypeCodes() {
81
- return Object.keys(JOURNAL_TYPES);
104
+ return [...Object.keys(JOURNAL_TYPES), ...Object.keys(_customTypes)];
82
105
  }
83
106
  function isValidJournalType(code) {
84
- return code in JOURNAL_TYPES;
107
+ return code in JOURNAL_TYPES || code in _customTypes;
85
108
  }
86
109
  function getJournalType(code) {
87
- return JOURNAL_TYPES[code] ?? null;
110
+ return JOURNAL_TYPES[code] ?? _customTypes[code] ?? null;
88
111
  }
89
112
  //#endregion
90
- export { isValidJournalType as a, getJournalTypeCodes as i, JOURNAL_TYPES as n, getJournalType as r, JOURNAL_CODES as t };
91
-
92
- //# sourceMappingURL=journals-oH-FK3g8.mjs.map
113
+ export { getJournalType as a, registerJournalType as c, getCustomJournalTypes as i, JOURNAL_TYPES as n, getJournalTypeCodes as o, _freezeJournalTypes as r, isValidJournalType as s, JOURNAL_CODES as t };
@@ -21,18 +21,26 @@ declare function extractMainType(key: string): MainType | null;
21
21
  /** Extract statement type from a category key string */
22
22
  declare function extractStatementType(key: string): StatementType | null;
23
23
  //#endregion
24
+ //#region src/constants/currencies.d.ts
25
+ declare const CURRENCIES: Readonly<Record<string, Currency>>;
26
+ declare function getCurrency(code: string): Currency | null;
27
+ declare function isValidCurrency(code: string): boolean;
28
+ declare function getMinorUnit(code: string): number;
29
+ //#endregion
24
30
  //#region src/constants/journals.d.ts
25
31
  declare const JOURNAL_TYPES: Readonly<Record<string, JournalType>>;
26
32
  declare const JOURNAL_CODES: Readonly<Record<string, string>>;
33
+ /**
34
+ * Register a custom journal type. Must be called **before** schema
35
+ * initialization (`createJournalEntrySchema`). Custom types are
36
+ * automatically included in Mongoose enum validation and all lookup
37
+ * functions.
38
+ */
39
+ declare function registerJournalType(code: string, def: JournalType): void;
40
+ /** Returns all custom (non-built-in) journal types. */
41
+ declare function getCustomJournalTypes(): JournalType[];
27
42
  declare function getJournalTypeCodes(): string[];
28
43
  declare function isValidJournalType(code: string): boolean;
29
44
  declare function getJournalType(code: string): JournalType | null;
30
45
  //#endregion
31
- //#region src/constants/currencies.d.ts
32
- declare const CURRENCIES: Readonly<Record<string, Currency>>;
33
- declare function getCurrency(code: string): Currency | null;
34
- declare function isValidCurrency(code: string): boolean;
35
- declare function getMinorUnit(code: string): number;
36
- //#endregion
37
- export { getNormalBalance as _, JOURNAL_CODES as a, isValidCategory as b, getJournalTypeCodes as c, CATEGORY_KEYS as d, categoryKey as f, getCategoryStatementType as g, getCategoryMainType as h, isValidCurrency as i, isValidJournalType as l, extractStatementType as m, getCurrency as n, JOURNAL_TYPES as o, extractMainType as p, getMinorUnit as r, getJournalType as s, CURRENCIES as t, CATEGORIES as u, isBalanceSheet as v, isIncomeStatement as y };
38
- //# sourceMappingURL=currencies-4WAbFRlw.d.mts.map
46
+ export { isValidCategory as S, getCategoryMainType as _, getJournalTypeCodes as a, isBalanceSheet as b, CURRENCIES as c, isValidCurrency as d, CATEGORIES as f, extractStatementType as g, extractMainType as h, getJournalType as i, getCurrency as l, categoryKey as m, JOURNAL_TYPES as n, isValidJournalType as o, CATEGORY_KEYS as p, getCustomJournalTypes as r, registerJournalType as s, JOURNAL_CODES as t, getMinorUnit as u, getCategoryStatementType as v, isIncomeStatement as x, getNormalBalance as y };
@@ -11,5 +11,4 @@ interface Logger {
11
11
  /** Default console-based implementation */
12
12
  declare const defaultLogger: Logger;
13
13
  //#endregion
14
- export { defaultLogger as n, Logger as t };
15
- //# sourceMappingURL=logger-UbTdBb1x.d.mts.map
14
+ export { defaultLogger as n, Logger as t };
package/dist/money.d.mts CHANGED
@@ -125,5 +125,4 @@ declare const Money: {
125
125
  readonly parseCents: typeof parseCents;
126
126
  };
127
127
  //#endregion
128
- export { Money, abs, add, allocate, equals, format, formatPlain, fromDecimal, isNegative, isPositive, isValid, isZero, max, min, multiply, negate, parseCents, percentage, round, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal };
129
- //# sourceMappingURL=money.d.mts.map
128
+ export { Money, abs, add, allocate, equals, format, formatPlain, fromDecimal, isNegative, isPositive, isValid, isZero, max, min, multiply, negate, parseCents, percentage, round, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal };
package/dist/money.mjs CHANGED
@@ -30,7 +30,9 @@ function round(amount) {
30
30
  /** Convert a decimal dollar amount to integer cents: 10.50 → 1050 */
31
31
  function fromDecimal(dollars, minorUnit = 2) {
32
32
  const factor = 10 ** minorUnit;
33
- return Math.round(dollars * factor);
33
+ const cents = Math.round(dollars * factor);
34
+ if (!Number.isSafeInteger(cents)) throw new Error(`Amount ${dollars} exceeds safe integer limit when converted to minor units. Max safe amount: ${Number.MAX_SAFE_INTEGER / factor}`);
35
+ return cents;
34
36
  }
35
37
  /** Convert integer cents to a decimal dollar amount: 1050 → 10.50 */
36
38
  function toDecimal(cents, minorUnit = 2) {
@@ -91,7 +93,7 @@ function allocate(totalCents, ratios) {
91
93
  const ratioSum = ratios.reduce((s, r) => s + r, 0);
92
94
  if (ratioSum === 0) throw new Error("Sum of ratios must be > 0");
93
95
  const allocations = ratios.map((r) => Math.floor(totalCents * r / ratioSum));
94
- let remainder = totalCents - allocations.reduce((s, a) => s + a, 0);
96
+ const remainder = totalCents - allocations.reduce((s, a) => s + a, 0);
95
97
  const fractions = ratios.map((r, i) => ({
96
98
  index: i,
97
99
  frac: totalCents * r / ratioSum - allocations[i]
@@ -164,7 +166,7 @@ function parseCents(input, minorUnit = 2) {
164
166
  if (typeof input === "number") return fromDecimal(input, minorUnit);
165
167
  const cleaned = input.replace(/[$,\s]/g, "");
166
168
  const parsed = parseFloat(cleaned);
167
- if (isNaN(parsed)) throw new Error(`Cannot parse "${input}" as money`);
169
+ if (Number.isNaN(parsed)) throw new Error(`Cannot parse "${input}" as money`);
168
170
  return fromDecimal(parsed, minorUnit);
169
171
  }
170
172
  const Money = {
@@ -193,5 +195,3 @@ const Money = {
193
195
  };
194
196
  //#endregion
195
197
  export { Money, abs, add, allocate, equals, format, formatPlain, fromDecimal, isNegative, isPositive, isValid, isZero, max, min, multiply, negate, parseCents, percentage, round, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal };
196
-
197
- //# sourceMappingURL=money.mjs.map
@@ -1,2 +1,38 @@
1
- import { a as DoubleEntryPluginOptions, i as fiscalLockPlugin, n as idempotencyPlugin, o as doubleEntryPlugin, r as FiscalLockPluginOptions, t as IdempotencyPluginOptions } from "../idempotency.plugin-CPxPt4vX.mjs";
2
- export { type DoubleEntryPluginOptions, type FiscalLockPluginOptions, type IdempotencyPluginOptions, doubleEntryPlugin, fiscalLockPlugin, idempotencyPlugin };
1
+ import { a as DoubleEntryPluginOptions, c as dateLockPlugin, i as fiscalLockPlugin, n as idempotencyPlugin, o as doubleEntryPlugin, r as FiscalLockPluginOptions, s as DateLockPluginOptions, t as IdempotencyPluginOptions } from "../idempotency.plugin-zU-GKJ0-.mjs";
2
+ import { RepositoryInstance } from "@classytic/mongokit";
3
+
4
+ //#region src/utils/tax-hooks.d.ts
5
+ interface TaxLineInput {
6
+ account: unknown;
7
+ amount: number;
8
+ side: 'debit' | 'credit';
9
+ taxCode?: string;
10
+ extraFields?: Record<string, unknown>;
11
+ }
12
+ interface GeneratedTaxLine {
13
+ account: unknown;
14
+ debit: number;
15
+ credit: number;
16
+ label?: string;
17
+ taxDetails?: Array<{
18
+ taxCode: string;
19
+ taxName?: string;
20
+ }>;
21
+ }
22
+ interface TaxLineGenerator {
23
+ generateTaxLines(input: TaxLineInput): GeneratedTaxLine[];
24
+ }
25
+ //#endregion
26
+ //#region src/plugins/tax-hook.plugin.d.ts
27
+ interface TaxHookPluginOptions {
28
+ /** Tax line generator — implements the tax calculation logic */
29
+ generator: TaxLineGenerator;
30
+ /** Only apply tax hooks on posted entries (default: true) */
31
+ onlyOnPost?: boolean;
32
+ }
33
+ declare function taxHookPlugin(options: TaxHookPluginOptions): {
34
+ name: string;
35
+ apply(repo: RepositoryInstance): void;
36
+ };
37
+ //#endregion
38
+ export { type DateLockPluginOptions, type DoubleEntryPluginOptions, type FiscalLockPluginOptions, type IdempotencyPluginOptions, type TaxHookPluginOptions, dateLockPlugin, doubleEntryPlugin, fiscalLockPlugin, idempotencyPlugin, taxHookPlugin };
@@ -1,2 +1,57 @@
1
- import { n as fiscalLockPlugin, r as doubleEntryPlugin, t as idempotencyPlugin } from "../idempotency.plugin-v9NQ_ta-.mjs";
2
- export { doubleEntryPlugin, fiscalLockPlugin, idempotencyPlugin };
1
+ import { i as doubleEntryPlugin, n as idempotencyPlugin, r as fiscalLockPlugin, t as dateLockPlugin } from "../date-lock.plugin-C8kqPBjh.mjs";
2
+ //#region src/utils/tax-hooks.ts
3
+ /**
4
+ * Apply a tax hook to journal items.
5
+ *
6
+ * Iterates each item that has a taxCode in taxDetails, calls
7
+ * `generator.generateTaxLines` for each, and appends the generated
8
+ * tax lines as new journal items.
9
+ *
10
+ * @returns The original items + generated tax items
11
+ */
12
+ function applyTaxHook(items, generator) {
13
+ const taxLines = [];
14
+ for (const item of items) {
15
+ const taxDetails = item.taxDetails;
16
+ if (!taxDetails || taxDetails.length === 0) continue;
17
+ const taxCode = taxDetails.find((td) => td.taxCode != null)?.taxCode;
18
+ if (!taxCode) continue;
19
+ const side = item.debit > 0 ? "debit" : "credit";
20
+ const amount = item.debit > 0 ? item.debit : item.credit;
21
+ const input = {
22
+ account: item.account,
23
+ amount,
24
+ side,
25
+ taxCode
26
+ };
27
+ const generated = generator.generateTaxLines(input);
28
+ for (const line of generated) taxLines.push({
29
+ account: line.account,
30
+ debit: line.debit,
31
+ credit: line.credit,
32
+ label: line.label,
33
+ taxDetails: line.taxDetails
34
+ });
35
+ }
36
+ return [...items, ...taxLines];
37
+ }
38
+ //#endregion
39
+ //#region src/plugins/tax-hook.plugin.ts
40
+ function taxHookPlugin(options) {
41
+ const { generator, onlyOnPost = true } = options;
42
+ return {
43
+ name: "accounting:tax-hook",
44
+ apply(repo) {
45
+ repo.on("before:create", (context) => {
46
+ const data = context.data;
47
+ if (!data) return;
48
+ if (onlyOnPost && data.state !== "posted") return;
49
+ const items = data.journalItems;
50
+ if (!items || items.length === 0) return;
51
+ data.journalItems = applyTaxHook(items, generator);
52
+ });
53
+ }
54
+ };
55
+ }
56
+ //#endregion
57
+ export { dateLockPlugin, doubleEntryPlugin, fiscalLockPlugin, idempotencyPlugin, taxHookPlugin };