@classytic/ledger 0.10.2 → 0.11.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 (31) hide show
  1. package/dist/bridges/index.d.mts +1 -1
  2. package/dist/constants/index.d.mts +1 -1
  3. package/dist/constants/index.mjs +2 -2
  4. package/dist/{core-DwjkrRkJ.d.mts → core-B7uVjqGS.d.mts} +25 -0
  5. package/dist/country/index.d.mts +1 -1
  6. package/dist/events/index.d.mts +1 -1
  7. package/dist/exports/index.d.mts +1 -1
  8. package/dist/exports/index.mjs +1 -1
  9. package/dist/{fx-realization.plugin-Dzqzi3u0.mjs → fx-realization.plugin-DY3pPxIi.mjs} +70 -1
  10. package/dist/{index-ClLwzNRF.d.mts → index-BFPFihTF.d.mts} +8 -0
  11. package/dist/{index-08IpHhrU.d.mts → index-Dd7HknPP.d.mts} +1 -1
  12. package/dist/index.d.mts +120 -24
  13. package/dist/index.mjs +375 -165
  14. package/dist/{journals-DUpWwFt1.d.mts → journals-CTrAuzdk.d.mts} +1 -1
  15. package/dist/{partner-ledger-CR0geilx.mjs → partner-ledger-B0eym6Ss.mjs} +951 -213
  16. package/dist/plugins/index.d.mts +1 -1
  17. package/dist/plugins/index.mjs +1 -1
  18. package/dist/reports/index.d.mts +2 -2
  19. package/dist/reports/index.mjs +2 -2
  20. package/dist/{trial-balance-DyNm5bFu.d.mts → trial-balance-UXV2PN6x.d.mts} +280 -75
  21. package/package.json +8 -20
  22. package/dist/opening-balance-1cixYh6Y.mjs +0 -60
  23. package/dist/sync/index.d.mts +0 -324
  24. package/dist/sync/index.mjs +0 -530
  25. package/dist/sync-JvchM3FO.d.mts +0 -152
  26. /package/dist/{categories-FJlrvzcl.mjs → categories-CclX7Q94.mjs} +0 -0
  27. /package/dist/{currencies-Jo5oaM_4.mjs → currencies-OuPHPyS2.mjs} +0 -0
  28. /package/dist/{exports-C30yRapf.mjs → exports-B3whucXe.mjs} +0 -0
  29. /package/dist/{index-Bl0gP9lD.d.mts → index-DygMrab0.d.mts} +0 -0
  30. /package/dist/{index-J-XIbXH-.d.mts → index-pRW5cZhF.d.mts} +0 -0
  31. /package/dist/{outbox-store-BcCiHMPw.d.mts → outbox-store-CPLeocPg.d.mts} +0 -0
package/dist/index.mjs CHANGED
@@ -1,16 +1,15 @@
1
1
  import { _ as LEDGER_EVENTS, a as EntryArchived, c as EntryPosted, d as JournalSeeded, f as ReconciliationMatched, g as createEvent, h as InProcessLedgerBus, i as AccountSeeded, l as EntryReversed, m as ledgerEventDefinitions, n as OutboxOwnershipError, o as EntryCreated, p as ReconciliationUnmatched, r as AccountBulkCreated, s as EntryDuplicated, t as InvalidOutboxEventError, u as EntryUnposted } from "./outbox-store-BbKdQ2eT.mjs";
2
2
  import { a as IdempotencyConflictError, i as Errors, n as ConcurrencyError, o as ImmutableViolationError, r as DuplicateReferenceError, s as classifyDuplicateKey, t as AccountingError } from "./errors-vXd932rB.mjs";
3
- import { a as JOURNAL_CODES, c as getCustomJournalTypes, d as isValidJournalType, f as registerJournalType, i as isValidCurrency, l as getJournalType, n as getCurrency, o as JOURNAL_TYPES, r as getMinorUnit, s as _freezeJournalTypes, t as CURRENCIES, u as getJournalTypeCodes } from "./currencies-Jo5oaM_4.mjs";
3
+ import { a as watermarkResolver, c as idempotencyPlugin, i as fiscalLockPlugin, l as doubleEntryPlugin, n as creditLimitPlugin, o as periodResolver, r as dailyLockPlugin, s as createLockPlugin, t as fxRealizationPlugin, u as isReverseMarkClaim } from "./fx-realization.plugin-DY3pPxIi.mjs";
4
+ import { a as JOURNAL_CODES, c as getCustomJournalTypes, d as isValidJournalType, f as registerJournalType, i as isValidCurrency, l as getJournalType, n as getCurrency, o as JOURNAL_TYPES, r as getMinorUnit, s as _freezeJournalTypes, t as CURRENCIES, u as getJournalTypeCodes } from "./currencies-OuPHPyS2.mjs";
4
5
  import { Money, add, allocate, format, formatPlain, fromDecimal, multiply, parseCents, percentage, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal } from "./money.mjs";
5
- import { C as isVirtualTaxAccount, E as requireOrgScope, S as computeEndingBalance, T as generateAgedBalance, _ as buildItemFilters, a as finalizeSession, b as buildAccountTypeMap, c as generateRevaluation, d as generateIncomeStatement, f as generateGeneralLedger, g as generateBalanceSheet, h as generateBudgetVsActual, i as acquireSession, l as buildRevaluationEntry, m as generateCashFlow, n as closeFiscalPeriod, o as defaultLogger, p as generateDimensionBreakdown, r as reopenFiscalPeriod, s as generateTrialBalance, t as generatePartnerLedger, u as computeRevaluation, v as getDateRange, w as DEFAULT_BUCKETS, x as calculateTotal, y as getFiscalYearStart } from "./partner-ledger-CR0geilx.mjs";
6
- import { c as getNormalBalance, d as isValidCategory, l as isBalanceSheet, n as CATEGORY_KEYS, t as CATEGORIES, u as isIncomeStatement } from "./categories-FJlrvzcl.mjs";
7
- import { a as watermarkResolver, c as idempotencyPlugin, i as fiscalLockPlugin, l as doubleEntryPlugin, n as creditLimitPlugin, o as periodResolver, r as dailyLockPlugin, s as createLockPlugin, t as fxRealizationPlugin } from "./fx-realization.plugin-Dzqzi3u0.mjs";
8
- import { t as buildOpeningBalanceEntry } from "./opening-balance-1cixYh6Y.mjs";
6
+ import { C as computeEndingBalance, D as requireOrgScope, E as generateAgedBalance, S as calculateTotal, T as DEFAULT_BUCKETS, _ as generateBalanceSheet, a as finalizeSession, b as getFiscalYearStart, c as generateRevaluation, d as generateIncomeStatement, f as generateGeneralLedger, g as generateBudgetVsActual, h as generateCashFlow, i as acquireSession, l as buildRevaluationEntry, m as generateDaybook, n as closeFiscalPeriod, o as defaultLogger, p as generateDimensionBreakdown, r as reopenFiscalPeriod, s as generateTrialBalance, t as generatePartnerLedger, u as computeRevaluation, v as buildItemFilters, w as isVirtualTaxAccount, x as buildAccountTypeMap, y as getDateRange } from "./partner-ledger-B0eym6Ss.mjs";
7
+ import { c as getNormalBalance, d as isValidCategory, l as isBalanceSheet, n as CATEGORY_KEYS, t as CATEGORIES, u as isIncomeStatement } from "./categories-CclX7Q94.mjs";
9
8
  import { defineCountryPack } from "./country/index.mjs";
10
- import { a as exportToCsv, i as quickbooksFieldMap, r as universalFieldMap, t as flattenJournalEntries } from "./exports-C30yRapf.mjs";
9
+ import { a as exportToCsv, i as quickbooksFieldMap, r as universalFieldMap, t as flattenJournalEntries } from "./exports-B3whucXe.mjs";
11
10
  import { QueryParser, Repository, getNextSequence, multiTenantPlugin, withTransaction } from "@classytic/mongokit";
12
11
  import mongoose, { Schema } from "mongoose";
13
- import { resolveTenantConfig } from "@classytic/primitives/tenant";
12
+ import { resolveTenantConfig } from "@classytic/repo-core/tenant";
14
13
  //#region src/plugins/immutable-guard.plugin.ts
15
14
  /**
16
15
  * Returns a mongokit plugin function. Install only when
@@ -35,13 +34,24 @@ function immutableGuardPlugin(options) {
35
34
  if (orgField && ctx.query && orgField in ctx.query) query[orgField] = ctx.query[orgField];
36
35
  if ((await JournalEntryModel.findOne(query).select({ state: 1 }).lean())?.state === "posted") throw new ImmutableViolationError(id);
37
36
  });
37
+ repo.on("before:claim", async (rawCtx) => {
38
+ const ctx = rawCtx;
39
+ if (ctx._ledgerInternal) return;
40
+ if (isReverseMarkClaim(ctx)) return;
41
+ const transition = ctx.transition;
42
+ if (!transition) return;
43
+ if ((transition.field ?? "state") !== "state") return;
44
+ const fromSpec = transition.from;
45
+ if (!(Array.isArray(fromSpec) ? fromSpec.includes("posted") : fromSpec === "posted")) return;
46
+ throw new ImmutableViolationError(ctx.id);
47
+ });
38
48
  };
39
49
  }
40
50
  //#endregion
41
51
  //#region src/models/inject-tenant.ts
42
52
  /**
43
53
  * Mongoose-specific adapter around `resolveTenantConfig()` from
44
- * `@classytic/primitives/tenant`. The pure resolution lives in primitives
54
+ * `@classytic/repo-core/tenant`. The pure resolution lives in primitives
45
55
  * (zero runtime deps) — this file only handles the Mongoose schema
46
56
  * mutations (add field, prepend tenant onto compound indexes) that
47
57
  * primitives can't own without a mongoose dependency.
@@ -134,31 +144,28 @@ function buildCurrencyField(config) {
134
144
  *
135
145
  * Creates a Mongoose schema for Chart of Accounts that is:
136
146
  * - Multi-tenant aware (adds org field + compound indexes when configured)
137
- * - Validates accountTypeCode against the country pack
138
147
  * - Supports accountNumber (unique per org) and name (user-facing display)
139
148
  * - Lean: no cached balances — always computed from journal entries
149
+ *
150
+ * Country-specific validation (accountTypeCode against the country pack) is
151
+ * intentionally NOT done at the schema layer — schema validators are baked in
152
+ * at model-registration time and would bleed across country engines that share
153
+ * the same connection. Validation lives in wireAccountMethods.before:create.
140
154
  */
141
155
  function createAccountSchema(config, options = {}) {
142
- const { country } = config;
143
156
  const scope = resolveLedgerTenant(config);
144
157
  const { indexes = true, extraFields = {}, extraIndexes = [] } = options;
145
158
  const fields = {
146
159
  accountTypeCode: {
147
160
  type: String,
148
- required: true,
149
- validate: {
150
- validator: (code) => country.isValidAccountType(code),
151
- message: (props) => `"${props.value}" is not a valid account type code for ${country.name}.`
152
- }
161
+ required: true
153
162
  },
154
163
  accountNumber: {
155
164
  type: String,
156
- required: true,
157
165
  trim: true
158
166
  },
159
167
  name: {
160
168
  type: String,
161
- required: true,
162
169
  trim: true
163
170
  },
164
171
  active: {
@@ -168,6 +175,25 @@ function createAccountSchema(config, options = {}) {
168
175
  isCashAccount: {
169
176
  type: Boolean,
170
177
  default: false
178
+ },
179
+ /**
180
+ * Optional per-account override for Cash Flow Statement classification.
181
+ * Wins over the country-pack `account_type` taxonomy. Use case: a
182
+ * "Long-term deferred revenue" account whose type would default to
183
+ * Financing but the business intent is Operating. Mirrors Xero's
184
+ * per-account Cash Flow category override; one of:
185
+ * 'operating' | 'investing' | 'financing' | 'excluded'
186
+ * `null` (default) → fall back to country-pack inference.
187
+ */
188
+ cashflowSection: {
189
+ type: String,
190
+ enum: [
191
+ "operating",
192
+ "investing",
193
+ "financing",
194
+ "excluded"
195
+ ],
196
+ default: null
171
197
  }
172
198
  };
173
199
  const currencyField = buildCurrencyField(config);
@@ -176,10 +202,6 @@ function createAccountSchema(config, options = {}) {
176
202
  const schema = new mongoose.Schema(fields, { timestamps: true });
177
203
  schema.pre("validate", function() {
178
204
  if (!this.accountNumber && this.accountTypeCode) this.accountNumber = this.accountTypeCode;
179
- if (!this.name && this.accountTypeCode) {
180
- const at = country.getAccountType(this.accountTypeCode);
181
- this.name = at?.name ?? this.accountTypeCode;
182
- }
183
205
  });
184
206
  if (indexes) {
185
207
  schema.index({ active: 1 });
@@ -192,13 +214,6 @@ function createAccountSchema(config, options = {}) {
192
214
  }
193
215
  //#endregion
194
216
  //#region src/schemas/budget.schema.ts
195
- /**
196
- * Budget Schema Factory
197
- *
198
- * Creates a Mongoose schema for budget records.
199
- * Each record represents a budgeted amount for an account over a specific period.
200
- * All monetary amounts are in integer cents.
201
- */
202
217
  function createBudgetSchema(config, options = {}) {
203
218
  const scope = resolveLedgerTenant(config);
204
219
  const { indexes = true, extraFields = {}, extraIndexes = [] } = options;
@@ -228,6 +243,10 @@ function createBudgetSchema(config, options = {}) {
228
243
  type: String,
229
244
  default: null
230
245
  },
246
+ approvals: {
247
+ type: mongoose.Schema.Types.Mixed,
248
+ default: null
249
+ },
231
250
  ...extraFields
232
251
  };
233
252
  const schema = new mongoose.Schema(fields, { timestamps: true });
@@ -457,6 +476,47 @@ function createJournalSchema(config, accountModelName, options = {}) {
457
476
  * - Double-entry validation on post
458
477
  * - Optimized indexes for high-load reporting
459
478
  */
479
+ /**
480
+ * Recommended opt-in indexes for the line-level provenance fields
481
+ * (`journalItems.sourceRef.*` + `journalItems.linkedRefs.*`).
482
+ *
483
+ * Schema fields ship in the core schema unconditionally. Index creation
484
+ * costs writes on every JE insert, so the package ships them as opt-in:
485
+ * spread this into `schemaOptions.journalEntry.extraIndexes` to enable
486
+ * fast `/by-source` lookups against the line-level slots.
487
+ *
488
+ * Both indexes are sparse + partial — only lines that actually carry a
489
+ * sourceModel are indexed. Hosts that never write line-level provenance
490
+ * pay zero index storage / no insert overhead.
491
+ *
492
+ * @example
493
+ * import { createAccountingEngine, LINE_SOURCE_INDEXES } from '@classytic/ledger';
494
+ * createAccountingEngine({
495
+ * schemaOptions: {
496
+ * journalEntry: { extraIndexes: [...LINE_SOURCE_INDEXES] },
497
+ * },
498
+ * });
499
+ */
500
+ const LINE_SOURCE_INDEXES = [{
501
+ fields: {
502
+ "journalItems.sourceRef.sourceModel": 1,
503
+ "journalItems.sourceRef.sourceId": 1
504
+ },
505
+ options: {
506
+ sparse: true,
507
+ partialFilterExpression: { "journalItems.sourceRef.sourceModel": { $type: "string" } },
508
+ name: "journalItems_sourceRef_idx"
509
+ }
510
+ }, {
511
+ fields: {
512
+ "journalItems.linkedRefs.sourceModel": 1,
513
+ "journalItems.linkedRefs.sourceId": 1
514
+ },
515
+ options: {
516
+ sparse: true,
517
+ name: "journalItems_linkedRefs_idx"
518
+ }
519
+ }];
460
520
  function createJournalEntrySchema(config, accountModelName, options = {}) {
461
521
  const { multiTenant } = config;
462
522
  const scope = resolveLedgerTenant(config);
@@ -465,6 +525,16 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
465
525
  taxCode: { type: String },
466
526
  taxName: { type: String }
467
527
  }, { _id: false });
528
+ const ItemSourceRefSchema = new mongoose.Schema({
529
+ sourceModel: {
530
+ type: String,
531
+ default: null
532
+ },
533
+ sourceId: {
534
+ type: String,
535
+ default: null
536
+ }
537
+ }, { _id: false });
468
538
  const amountValidator = {
469
539
  validator: (v) => Number.isInteger(v) && v >= 0,
470
540
  message: "{PATH} must be a non-negative integer (cents), got {VALUE}"
@@ -528,6 +598,18 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
528
598
  type: Date,
529
599
  default: null
530
600
  },
601
+ meta: {
602
+ type: mongoose.Schema.Types.Mixed,
603
+ default: null
604
+ },
605
+ sourceRef: {
606
+ type: ItemSourceRefSchema,
607
+ default: () => ({})
608
+ },
609
+ linkedRefs: {
610
+ type: [ItemSourceRefSchema],
611
+ default: void 0
612
+ },
531
613
  ...currencyItemFields,
532
614
  ...extraItemFields
533
615
  }, { _id: false });
@@ -602,6 +684,10 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
602
684
  ref: "JournalEntry",
603
685
  default: null
604
686
  },
687
+ approvals: {
688
+ type: mongoose.Schema.Types.Mixed,
689
+ default: null
690
+ },
605
691
  ...extraFields
606
692
  };
607
693
  if (config.audit?.trackActor) {
@@ -704,14 +790,6 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
704
790
  state: 1
705
791
  });
706
792
  schema.index({ reversed: 1 });
707
- if (config.idempotency) {
708
- const ttlSeconds = typeof config.idempotencyTtlSeconds === "number" && config.idempotencyTtlSeconds > 0 ? config.idempotencyTtlSeconds : 86400;
709
- schema.index({ createdAt: 1 }, {
710
- name: "idempotency_ttl_idx",
711
- expireAfterSeconds: ttlSeconds,
712
- partialFilterExpression: { idempotencyKey: { $type: "string" } }
713
- });
714
- }
715
793
  }
716
794
  if (textSearch) schema.index({
717
795
  referenceNumber: "text",
@@ -899,9 +977,14 @@ function createModels(connection, config) {
899
977
  };
900
978
  }
901
979
  //#endregion
902
- //#region src/repositories/account.repository.ts
903
- async function safePublish$3(events, outboxStore, type, payload, ctx) {
904
- const event = createEvent(type, payload, ctx);
980
+ //#region src/utils/safe-publish.ts
981
+ /**
982
+ * Persist and publish a ledger domain event without letting delivery failures
983
+ * break the write path. When an outbox is configured, the outbox row is saved
984
+ * first and participates in the caller's mongoose session.
985
+ */
986
+ async function safePublish(events, outboxStore, type, payload, ctx, meta) {
987
+ const event = createEvent(type, payload, ctx, meta);
905
988
  if (outboxStore) try {
906
989
  await outboxStore.save(event, { session: ctx?.session ?? void 0 });
907
990
  } catch {}
@@ -909,6 +992,8 @@ async function safePublish$3(events, outboxStore, type, payload, ctx) {
909
992
  await events.publish(event);
910
993
  } catch {}
911
994
  }
995
+ //#endregion
996
+ //#region src/repositories/account.repository.ts
912
997
  function isDuplicateKeyBulkError(err) {
913
998
  if (!err || typeof err !== "object") return false;
914
999
  const e = err;
@@ -928,9 +1013,32 @@ function isDuplicateKeyBulkError(err) {
928
1013
  function wireAccountMethods(repository, country, orgField, integrations = {}) {
929
1014
  const events = integrations.events;
930
1015
  const outboxStore = integrations.outboxStore;
931
- repository.on("before:create", (ctx) => {
932
- const code = ctx.data?.accountTypeCode;
1016
+ const journalEntryModel = integrations.journalEntryModel;
1017
+ repository.on("before:create", async (ctx) => {
1018
+ const data = ctx.data;
1019
+ const code = data?.accountTypeCode;
933
1020
  if (code && !country.isPostingAccount(code)) throw Errors.validation(`Cannot create account with type "${code}" — it is a structural group or calculated total, not a posting account.`);
1021
+ if (data && !data.name && code) {
1022
+ const at = country.getAccountType(code);
1023
+ if (at) data.name = at.name ?? code;
1024
+ }
1025
+ if (!data) return;
1026
+ const accountNumber = data.accountNumber ?? code;
1027
+ if (!accountNumber) return;
1028
+ const filter = { accountNumber };
1029
+ if (orgField) {
1030
+ const orgId = data[orgField] ?? ctx.organizationId;
1031
+ if (orgId != null) filter[orgField] = orgId;
1032
+ }
1033
+ if (await repository.getByQuery(filter, {
1034
+ throwOnNotFound: false,
1035
+ lean: true
1036
+ })) throw Errors.validation(`Account number "${accountNumber}" already exists. Provide a custom accountNumber (e.g. "${accountNumber}-NORTH") to create a sub-account.`);
1037
+ });
1038
+ if (journalEntryModel) repository.on("before:delete", async (ctx) => {
1039
+ const id = ctx.id;
1040
+ if (id == null) return;
1041
+ if (await journalEntryModel.exists({ "journalItems.account": id })) throw Errors.validation(`Cannot delete account ${String(id)} — it is referenced by one or more journal entries. Set active: false to retire it instead.`);
934
1042
  });
935
1043
  /**
936
1044
  * Seed standard posting accounts for an organization.
@@ -977,7 +1085,7 @@ function wireAccountMethods(repository, country, orgField, integrations = {}) {
977
1085
  };
978
1086
  } else throw err;
979
1087
  }
980
- await safePublish$3(events, outboxStore, LEDGER_EVENTS.ACCOUNT_SEEDED, {
1088
+ await safePublish(events, outboxStore, LEDGER_EVENTS.ACCOUNT_SEEDED, {
981
1089
  created: result.created,
982
1090
  skipped: result.skipped,
983
1091
  organizationId: orgId
@@ -1127,7 +1235,7 @@ function wireAccountMethods(repository, country, orgField, integrations = {}) {
1127
1235
  skipped: results.skipped.length,
1128
1236
  errors: results.errors.length
1129
1237
  };
1130
- await safePublish$3(events, outboxStore, LEDGER_EVENTS.ACCOUNT_BULK_CREATED, {
1238
+ await safePublish(events, outboxStore, LEDGER_EVENTS.ACCOUNT_BULK_CREATED, {
1131
1239
  created: summary.created,
1132
1240
  skipped: summary.skipped,
1133
1241
  errors: summary.errors,
@@ -1151,15 +1259,6 @@ function wireAccountMethods(repository, country, orgField, integrations = {}) {
1151
1259
  }
1152
1260
  //#endregion
1153
1261
  //#region src/repositories/journal.repository.ts
1154
- async function safePublish$2(events, outboxStore, type, payload, ctx) {
1155
- const event = createEvent(type, payload, ctx);
1156
- if (outboxStore) try {
1157
- await outboxStore.save(event);
1158
- } catch {}
1159
- if (events) try {
1160
- await events.publish(event);
1161
- } catch {}
1162
- }
1163
1262
  /**
1164
1263
  * Lean default set used when a country pack doesn't provide
1165
1264
  * `journalTemplates`. Covers the Stripe/QuickBooks/Xero baseline.
@@ -1231,7 +1330,7 @@ function wireJournalMethods(repository, country, orgField, integrations = {}) {
1231
1330
  await create(data);
1232
1331
  created += 1;
1233
1332
  }
1234
- await safePublish$2(events, outboxStore, LEDGER_EVENTS.JOURNAL_SEEDED, {
1333
+ await safePublish(events, outboxStore, LEDGER_EVENTS.JOURNAL_SEEDED, {
1235
1334
  created,
1236
1335
  skipped,
1237
1336
  organizationId: orgId
@@ -1265,25 +1364,6 @@ function wireJournalMethods(repository, country, orgField, integrations = {}) {
1265
1364
  }
1266
1365
  //#endregion
1267
1366
  //#region src/repositories/journal-entry.repository.ts
1268
- /**
1269
- * Publish a domain event. When an outbox store is provided, first persist
1270
- * the event inside the caller's session (so outbox + ledger write commit
1271
- * atomically), then fire-and-forget publish to the transport. Without an
1272
- * outbox, publish-only, still fire-and-forget — transport errors never
1273
- * propagate into ledger mutations.
1274
- *
1275
- * Tracks PACKAGE_RULES §16 (host-composed transactional outbox) and §14
1276
- * (domain verbs publish via injected transport).
1277
- */
1278
- async function safePublish$1(events, outboxStore, type, payload, ctx, meta) {
1279
- const event = createEvent(type, payload, ctx, meta);
1280
- if (outboxStore) try {
1281
- await outboxStore.save(event, { session: ctx?.session ?? void 0 });
1282
- } catch {}
1283
- if (events) try {
1284
- await events.publish(event);
1285
- } catch {}
1286
- }
1287
1367
  /** Keys that are either handled explicitly or must not be copied */
1288
1368
  const ITEM_CORE_KEYS = new Set([
1289
1369
  "account",
@@ -1311,7 +1391,8 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1311
1391
  const outboxStore = integrations.outboxStore;
1312
1392
  const getByQuery = repository.getByQuery.bind(repository);
1313
1393
  const baseCreate = repository.create.bind(repository);
1314
- const update = repository.update.bind(repository);
1394
+ repository.update.bind(repository);
1395
+ const claim = repository.claim.bind(repository);
1315
1396
  const withTransaction$1 = (fn, opts) => withTransaction(repository.Model.db, fn, opts);
1316
1397
  const raceSafeCreate = async (data, options) => {
1317
1398
  const input = data;
@@ -1416,13 +1497,37 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1416
1497
  return await getByQuery(query, opts);
1417
1498
  }
1418
1499
  /**
1500
+ * Build the options bag passed to `repo.claim()` from the call's
1501
+ * org/actor/session context. mongokit's `multiTenantPlugin` reads
1502
+ * `options.organizationId`, audit plugins read `options.userId`, and
1503
+ * the transaction layer reads `options.session` — same shape we'd
1504
+ * forward via `repoOptionsFromCtx(ctx)` from a host route.
1505
+ */
1506
+ function buildClaimOptions(orgId, actorId, session) {
1507
+ const opts = {};
1508
+ if (session) opts.session = session;
1509
+ if (orgField && orgId != null) opts.organizationId = orgId;
1510
+ if (actorId !== void 0 && actorId !== null) opts.userId = actorId;
1511
+ return opts;
1512
+ }
1513
+ /**
1514
+ * Build the `where` predicate for a state-transition claim. Encodes the
1515
+ * tenant-scope guard so the CAS only matches docs in the caller's org.
1516
+ */
1517
+ function buildClaimWhere(orgId, extra) {
1518
+ const where = { ...extra ?? {} };
1519
+ if (orgField && orgId != null) where[orgField] = orgId;
1520
+ return where;
1521
+ }
1522
+ /**
1419
1523
  * Post an entry (draft → posted).
1420
1524
  * Validates items, balance, and accounts before changing state.
1421
1525
  */
1422
1526
  repository.post = async (id, orgId, options = {}) => {
1423
1527
  if (strictness?.requireActor && !options.actorId) throw Errors.validation("actorId is required for post operations.");
1424
1528
  requireOrgScope(orgField, orgId);
1425
- const entry = await findEntry(buildQuery(id, orgId), {
1529
+ const query = buildQuery(id, orgId);
1530
+ const entry = await findEntry(query, {
1426
1531
  session: options.session,
1427
1532
  populate: "journalItems.account"
1428
1533
  });
@@ -1458,17 +1563,22 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1458
1563
  const totalDebit = entry.journalItems.reduce((s, i) => s + (i.debit || 0), 0);
1459
1564
  const totalCredit = entry.journalItems.reduce((s, i) => s + (i.credit || 0), 0);
1460
1565
  if (totalDebit !== totalCredit) throw Errors.validation(`Entry is not balanced. Debit: ${totalDebit}, Credit: ${totalCredit}`);
1461
- const patch = {
1462
- state: "posted",
1463
- stateChangedAt: /* @__PURE__ */ new Date()
1464
- };
1465
- if (options.actorId) patch.postedBy = options.actorId;
1466
- const updateOptions = {
1467
- _ledgerInternal: "post",
1468
- ...options.session ? { session: options.session } : {}
1469
- };
1470
- const final = await update(entry._id, patch, updateOptions) ?? entry;
1471
- await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_POSTED, {
1566
+ const $set = { stateChangedAt: /* @__PURE__ */ new Date() };
1567
+ if (options.actorId) $set.postedBy = options.actorId;
1568
+ const claimed = await claim(entry._id, {
1569
+ field: "state",
1570
+ from: "draft",
1571
+ to: "posted",
1572
+ where: buildClaimWhere(orgId)
1573
+ }, { $set }, buildClaimOptions(orgId, options.actorId, options.session ?? null));
1574
+ let final;
1575
+ if (!claimed) {
1576
+ const reread = await findEntry(query, { session: options.session });
1577
+ if (reread && reread.state === "posted") final = reread;
1578
+ else if (reread) throw new ConcurrencyError("JournalEntry", String(entry._id));
1579
+ else throw Errors.notFound("Entry not found");
1580
+ } else final = claimed;
1581
+ await safePublish(events, outboxStore, LEDGER_EVENTS.ENTRY_POSTED, {
1472
1582
  entryId: final._id,
1473
1583
  referenceNumber: final.referenceNumber,
1474
1584
  postedBy: options.actorId,
@@ -1494,19 +1604,25 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1494
1604
  if (strictness?.immutable) throw Errors.immutable("Unpost is disabled in strict mode. Use reverse() to correct posted entries.");
1495
1605
  if (strictness?.requireActor && !options.actorId) throw Errors.validation("actorId is required for unpost operations.");
1496
1606
  requireOrgScope(orgField, orgId);
1497
- const entry = await findEntry(buildQuery(id, orgId), { session: options.session });
1607
+ const query = buildQuery(id, orgId);
1608
+ const entry = await findEntry(query, { session: options.session });
1498
1609
  if (!entry) throw Errors.notFound("Entry not found");
1499
1610
  if (entry.state !== "posted") throw Errors.validation("Only posted entries can be unposted");
1500
1611
  if (entry.reversed) throw Errors.validation("Cannot unpost a reversed entry. The reversal entry is still posted and linked to this entry. Reverse the reversal entry first, or create a new correcting entry instead.");
1501
- const updateOptions = {
1502
- _ledgerInternal: "unpost",
1503
- ...options.session ? { session: options.session } : {}
1504
- };
1505
- const final = await update(entry._id, {
1506
- state: "draft",
1507
- stateChangedAt: /* @__PURE__ */ new Date()
1508
- }, updateOptions) ?? entry;
1509
- await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_UNPOSTED, {
1612
+ const claimed = await claim(entry._id, {
1613
+ field: "state",
1614
+ from: "posted",
1615
+ to: "draft",
1616
+ where: buildClaimWhere(orgId, { reversed: { $ne: true } })
1617
+ }, { $set: { stateChangedAt: /* @__PURE__ */ new Date() } }, buildClaimOptions(orgId, options.actorId, options.session ?? null));
1618
+ let final;
1619
+ if (!claimed) {
1620
+ const reread = await findEntry(query, { session: options.session });
1621
+ if (reread && reread.state === "draft") final = reread;
1622
+ else if (reread) throw new ConcurrencyError("JournalEntry", String(entry._id));
1623
+ else throw Errors.notFound("Entry not found");
1624
+ } else final = claimed;
1625
+ await safePublish(events, outboxStore, LEDGER_EVENTS.ENTRY_UNPOSTED, {
1510
1626
  entryId: final._id,
1511
1627
  unpostedBy: options.actorId,
1512
1628
  organizationId: orgId
@@ -1528,18 +1644,24 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1528
1644
  repository.archive = async (id, orgId, options = {}) => {
1529
1645
  if (strictness?.requireActor && !options.actorId) throw Errors.validation("actorId is required for archive operations.");
1530
1646
  requireOrgScope(orgField, orgId);
1531
- const entry = await findEntry(buildQuery(id, orgId), { session: options.session });
1647
+ const query = buildQuery(id, orgId);
1648
+ const entry = await findEntry(query, { session: options.session });
1532
1649
  if (!entry) throw Errors.notFound("Entry not found");
1533
1650
  if (entry.state !== "draft") throw Errors.validation("Only draft entries can be archived");
1534
- const updateOptions = {
1535
- _ledgerInternal: "archive",
1536
- ...options.session ? { session: options.session } : {}
1537
- };
1538
- const final = await update(entry._id, {
1539
- state: "archived",
1540
- stateChangedAt: /* @__PURE__ */ new Date()
1541
- }, updateOptions) ?? entry;
1542
- await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_ARCHIVED, {
1651
+ const claimed = await claim(entry._id, {
1652
+ field: "state",
1653
+ from: "draft",
1654
+ to: "archived",
1655
+ where: buildClaimWhere(orgId)
1656
+ }, { $set: { stateChangedAt: /* @__PURE__ */ new Date() } }, buildClaimOptions(orgId, options.actorId, options.session ?? null));
1657
+ let final;
1658
+ if (!claimed) {
1659
+ const reread = await findEntry(query, { session: options.session });
1660
+ if (reread && reread.state === "archived") final = reread;
1661
+ else if (reread) throw new ConcurrencyError("JournalEntry", String(entry._id));
1662
+ else throw Errors.notFound("Entry not found");
1663
+ } else final = claimed;
1664
+ await safePublish(events, outboxStore, LEDGER_EVENTS.ENTRY_ARCHIVED, {
1543
1665
  entryId: final._id,
1544
1666
  archivedBy: options.actorId,
1545
1667
  organizationId: orgId
@@ -1584,7 +1706,7 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1584
1706
  copyExtraTopLevel(entry, duplicateData);
1585
1707
  const duplicated = await create(duplicateData, options.session ? { session: options.session } : {});
1586
1708
  const dup = duplicated;
1587
- await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_DUPLICATED, {
1709
+ await safePublish(events, outboxStore, LEDGER_EVENTS.ENTRY_DUPLICATED, {
1588
1710
  sourceEntryId: entry._id,
1589
1711
  duplicateEntryId: dup._id,
1590
1712
  organizationId: orgId
@@ -1638,29 +1760,39 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1638
1760
  const totalCredit = reversalItems.reduce((s, i) => s + i.credit, 0);
1639
1761
  const reversalData = {
1640
1762
  journalType: entry.journalType ?? "MISC",
1641
- state: "posted",
1642
1763
  date: options.reversalDate ?? /* @__PURE__ */ new Date(),
1643
1764
  label: `Reversal of ${entry.referenceNumber ?? entry._id}`,
1644
1765
  journalItems: reversalItems,
1645
1766
  totalDebit,
1646
1767
  totalCredit,
1647
- reversalOf: entry._id,
1648
- stateChangedAt: /* @__PURE__ */ new Date()
1768
+ reversalOf: entry._id
1649
1769
  };
1650
1770
  copyExtraTopLevel(entry, reversalData);
1651
- if (options.actorId) reversalData.postedBy = options.actorId;
1652
- const reversalEntry = await create(reversalData, session ? { session } : {});
1653
- const markPatch = {
1771
+ let reversalEntry = await create(reversalData, session ? { session } : {});
1772
+ const postFn = repository.post;
1773
+ if (options.autoPost && postFn) {
1774
+ const posted = await postFn(reversalEntry._id, orgId, {
1775
+ actorId: options.actorId,
1776
+ ...session ? { session } : {}
1777
+ });
1778
+ if (posted) reversalEntry = posted;
1779
+ }
1780
+ const $set = {
1654
1781
  reversed: true,
1655
1782
  reversedBy: reversalEntry._id
1656
1783
  };
1657
- if (options.actorId) markPatch.reversedByUser = options.actorId;
1658
- const markOptions = {
1659
- _ledgerInternal: "reverseMark",
1660
- ...session ? { session } : {}
1661
- };
1662
- const original = await update(entry._id, markPatch, markOptions) ?? entry;
1663
- await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_REVERSED, {
1784
+ if (options.actorId) $set.reversedByUser = options.actorId;
1785
+ const claimOpts = {};
1786
+ if (session) claimOpts.session = session;
1787
+ if (orgField && orgId != null) claimOpts.organizationId = orgId;
1788
+ if (options.actorId) claimOpts.userId = options.actorId;
1789
+ const original = await claim(entry._id, {
1790
+ field: "state",
1791
+ from: "posted",
1792
+ to: "posted",
1793
+ where: { reversed: { $ne: true } }
1794
+ }, { $set }, claimOpts) ?? entry;
1795
+ await safePublish(events, outboxStore, LEDGER_EVENTS.ENTRY_REVERSED, {
1664
1796
  originalEntryId: original._id,
1665
1797
  reversalEntryId: reversalEntry._id,
1666
1798
  reversalDate: reversalData.date ?? /* @__PURE__ */ new Date(),
@@ -1703,44 +1835,54 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
1703
1835
  }
1704
1836
  //#endregion
1705
1837
  //#region src/repositories/reconciliation.repository.ts
1706
- async function safePublish(events, outboxStore, type, payload, ctx) {
1707
- const event = createEvent(type, payload, ctx);
1708
- if (outboxStore) try {
1709
- await outboxStore.save(event, { session: ctx?.session ?? void 0 });
1710
- } catch {}
1711
- if (events) try {
1712
- await events.publish(event);
1713
- } catch {}
1714
- }
1715
1838
  /**
1716
- * Default matching-number generatoratomic counter stored in the
1717
- * reconciliation collection. Uses a dedicated sentinel document keyed
1718
- * `{ matchingNumber: '__counter__', [org]: orgId }` so each org has its
1719
- * own counter, safe under concurrent match calls.
1839
+ * Reconciliation Repository Factory (0.6.0 item-level open-item matching)
1840
+ *
1841
+ * Implements the three new primitives:
1842
+ *
1843
+ * - `match({ account, items, ... })` stamps a shared matchingNumber onto
1844
+ * every referenced item and creates a reconciliation document.
1845
+ * Triggers `after:match` hook for downstream plugins (fxRealization,
1846
+ * cash-basis exigibility).
1847
+ *
1848
+ * - `unmatch({ matchingNumber })` clears the matching number from every
1849
+ * referenced item and removes the reconciliation. If an FX realization
1850
+ * entry was booked, it is reversed via journalEntries.reverse.
1851
+ *
1852
+ * - `getOpenItems({ accountId })` returns posted journal items against
1853
+ * the account that have no matchingNumber yet. Backed by the sparse
1854
+ * index on `journalItems.matchingNumber`.
1855
+ *
1856
+ * Matching numbers auto-generate as `RECN-{n}` if the caller doesn't
1857
+ * supply one. Uniqueness is enforced by the org-scoped unique index on
1858
+ * the reconciliation collection.
1720
1859
  */
1721
- async function nextMatchingNumber(ReconciliationModel, orgField, orgId, session) {
1722
- const counterQuery = { matchingNumber: "__counter__" };
1723
- if (orgField && orgId != null) counterQuery[orgField] = orgId;
1724
- const seq = (await ReconciliationModel.findOneAndUpdate(counterQuery, {
1725
- $inc: { seq: 1 },
1726
- $setOnInsert: {
1727
- account: null,
1728
- items: [{
1729
- entry: null,
1730
- itemIndex: 0
1731
- }, {
1732
- entry: null,
1733
- itemIndex: 1
1734
- }],
1735
- debitTotal: 0,
1736
- creditTotal: 0
1737
- }
1738
- }, {
1739
- new: true,
1740
- upsert: true,
1741
- session,
1742
- strict: false
1743
- }).lean())?.seq ?? 1;
1860
+ /**
1861
+ * Default matching-number generator delegates to mongokit's
1862
+ * `getNextSequence(counterKey, 1, connection, session)` which atomically
1863
+ * bumps a counter row in the shared `_mongokit_counters` collection. Same
1864
+ * primitive that backs `journalEntries.referenceNumber` allocation in this
1865
+ * package (see `journal-entry.schema.ts:431`) and the per-package id
1866
+ * generators in `@classytic/invoice`, `order`, `cart`, `revenue`.
1867
+ *
1868
+ * Why this replaced the in-collection sentinel pattern (pre-0.10.4): the
1869
+ * sentinel approach stored the counter as a synthetic `matchingNumber:
1870
+ * '__counter__'` doc in the reconciliation collection itself, with the
1871
+ * counter slot as `seq`. After the 0.10.x refactor that routed the bump
1872
+ * through `repository.findOneAndUpdate(...)` (to flow through the plugin
1873
+ * pipeline), mongoose strict mode silently dropped `$inc: { seq: 1 }`
1874
+ * because `seq` was never declared in the schema — every call returned
1875
+ * `undefined`, fell back to `1`, and every match resolved to `RECN-000001`.
1876
+ * The first match per engine succeeded; the second collided on the unique
1877
+ * index. `getNextSequence` sidesteps this entirely: dedicated counter
1878
+ * collection, no schema pollution, session-aware (counter rolls back if
1879
+ * the calling transaction aborts), multi-tenant via key prefix.
1880
+ */
1881
+ async function nextMatchingNumber(connection, orgField, orgId, session) {
1882
+ let orgScope = "global";
1883
+ if (orgField && orgId != null) orgScope = typeof orgId.toHexString === "function" ? orgId.toHexString() : String(orgId);
1884
+ else if (orgField) orgScope = "unscoped";
1885
+ const seq = await getNextSequence(`ledger:${orgScope}:matchingNumber`, 1, connection, session ?? void 0);
1744
1886
  return `RECN-${String(seq).padStart(6, "0")}`;
1745
1887
  }
1746
1888
  function wireReconciliationMethods(repository, ReconciliationModel, JournalEntryModel, orgField, integrations = {}) {
@@ -1794,7 +1936,7 @@ function wireReconciliationMethods(repository, ReconciliationModel, JournalEntry
1794
1936
  const difference = debitTotal - creditTotal;
1795
1937
  const isFullReconcile = difference === 0;
1796
1938
  const sharedCurrency = currencies.size === 1 ? Array.from(currencies)[0] : null;
1797
- if (!matchingNumber) matchingNumber = await nextMatchingNumber(ReconciliationModel, orgField, organizationId, session);
1939
+ if (!matchingNumber) matchingNumber = await nextMatchingNumber(ReconciliationModel.db, orgField, organizationId, session);
1798
1940
  const hookCtx = {
1799
1941
  input,
1800
1942
  items: itemSnapshots,
@@ -1884,7 +2026,7 @@ function wireReconciliationMethods(repository, ReconciliationModel, JournalEntry
1884
2026
  } }));
1885
2027
  if (bulkOps.length > 0) await JournalEntryModel.bulkWrite(bulkOps, { session: session ?? void 0 });
1886
2028
  const result = await deleteById(String(existing._id));
1887
- if (!result.success) throw Errors.notFound("Failed to delete reconciliation record");
2029
+ if (!result) throw Errors.notFound("Failed to delete reconciliation record");
1888
2030
  await emitHook("after:unmatch", unmatchCtx);
1889
2031
  await safePublish(events, outboxStore, LEDGER_EVENTS.RECONCILIATION_UNMATCHED, {
1890
2032
  matchingNumber,
@@ -1979,13 +2121,17 @@ function createRepositories(models, config, plugins = {}, pagination = {}, integ
1979
2121
  const strictness = config.strictness;
1980
2122
  const country = config.country;
1981
2123
  const { events, bridges, outboxStore } = integrations;
2124
+ const tenantFieldType = config.tenantFieldType ?? "objectId";
1982
2125
  const tenantPlugins = [];
1983
- if (orgField && config.multiTenant?.plugin) tenantPlugins.push(multiTenantPlugin({
1984
- tenantField: orgField,
1985
- contextKey: "organizationId",
1986
- required: config.multiTenant.required ?? false,
1987
- fieldType: config.tenantFieldType ?? "string"
1988
- }));
2126
+ if (orgField && config.multiTenant?.plugin) {
2127
+ const contextKey = config.multiTenant.contextKey ?? "organizationId";
2128
+ tenantPlugins.push(multiTenantPlugin({
2129
+ tenantField: orgField,
2130
+ contextKey,
2131
+ required: config.multiTenant.required ?? false,
2132
+ fieldType: tenantFieldType
2133
+ }));
2134
+ }
1989
2135
  const accountPagination = pagination.account ?? {};
1990
2136
  const jePagination = pagination.journalEntry ?? {};
1991
2137
  const fpPagination = pagination.fiscalPeriod ?? {};
@@ -1994,7 +2140,8 @@ function createRepositories(models, config, plugins = {}, pagination = {}, integ
1994
2140
  const accounts = wireAccountMethods(new Repository(models.Account, [...tenantPlugins, ...plugins.account ?? []], accountPagination), country, orgField, {
1995
2141
  events,
1996
2142
  bridges,
1997
- outboxStore
2143
+ outboxStore,
2144
+ journalEntryModel: models.JournalEntry
1998
2145
  });
1999
2146
  const jePlugins = [
2000
2147
  ...tenantPlugins,
@@ -2368,6 +2515,65 @@ function buildIntrospectAPI({ models, country, config }) {
2368
2515
  };
2369
2516
  }
2370
2517
  //#endregion
2518
+ //#region src/builders/opening-balance.ts
2519
+ function buildOpeningBalanceEntry(input) {
2520
+ const { cutoverDate, balances, equityAccountCode } = input;
2521
+ const dateStr = cutoverDate.toISOString().split("T")[0];
2522
+ const label = input.label ?? `Opening Balance — Cutover ${dateStr}`;
2523
+ const items = [];
2524
+ let totalDebit = 0;
2525
+ let totalCredit = 0;
2526
+ for (const { accountCode, balance } of balances) {
2527
+ if (balance === 0) continue;
2528
+ if (balance > 0) {
2529
+ items.push({
2530
+ account: accountCode,
2531
+ debit: balance,
2532
+ credit: 0,
2533
+ label: "Opening balance"
2534
+ });
2535
+ totalDebit += balance;
2536
+ } else {
2537
+ const absBalance = Math.abs(balance);
2538
+ items.push({
2539
+ account: accountCode,
2540
+ debit: 0,
2541
+ credit: absBalance,
2542
+ label: "Opening balance"
2543
+ });
2544
+ totalCredit += absBalance;
2545
+ }
2546
+ }
2547
+ const residual = totalDebit - totalCredit;
2548
+ const lineCount = items.length;
2549
+ if (residual > 0) items.push({
2550
+ account: equityAccountCode,
2551
+ debit: 0,
2552
+ credit: residual,
2553
+ label: "Opening balance equity (contra)"
2554
+ });
2555
+ else if (residual < 0) items.push({
2556
+ account: equityAccountCode,
2557
+ debit: Math.abs(residual),
2558
+ credit: 0,
2559
+ label: "Opening balance equity (contra)"
2560
+ });
2561
+ return {
2562
+ entry: {
2563
+ date: cutoverDate,
2564
+ label,
2565
+ journalType: "GENERAL",
2566
+ journalItems: items,
2567
+ extra: {
2568
+ _externalId: `opening-balance:${dateStr}`,
2569
+ _importSource: "opening-balance"
2570
+ }
2571
+ },
2572
+ residual,
2573
+ lineCount
2574
+ };
2575
+ }
2576
+ //#endregion
2371
2577
  //#region src/semantic/record.ts
2372
2578
  function buildRecordAPI({ models, repositories, config }) {
2373
2579
  const AccountModel = models.Account;
@@ -2813,6 +3019,10 @@ var AccountingEngine = class {
2813
3019
  country,
2814
3020
  orgField
2815
3021
  }, params),
3022
+ daybook: (params) => generateDaybook({
3023
+ JournalEntryModel,
3024
+ orgField
3025
+ }, params),
2816
3026
  agedBalance: (params) => generateAgedBalance({
2817
3027
  AccountModel,
2818
3028
  JournalEntryModel,
@@ -2905,4 +3115,4 @@ function buildDimensionIndexes(dimensions, orgField) {
2905
3115
  });
2906
3116
  }
2907
3117
  //#endregion
2908
- export { AccountBulkCreated, AccountSeeded, AccountingEngine, AccountingError, CATEGORIES, CATEGORY_KEYS, CURRENCIES, ConcurrencyError, DEFAULT_BUCKETS, DuplicateReferenceError, EntryArchived, EntryCreated, EntryDuplicated, EntryPosted, EntryReversed, EntryUnposted, Errors, IdempotencyConflictError, ImmutableViolationError, InProcessLedgerBus, InvalidOutboxEventError, JOURNAL_CODES, JOURNAL_TYPES, JournalSeeded, LEDGER_EVENTS, Money, OutboxOwnershipError, ReconciliationMatched, ReconciliationUnmatched, acquireSession, add, allocate, buildAccountTypeMap, buildDimensionFields, buildDimensionIndexes, buildItemFilters, buildRevaluationEntry, calculateTotal, classifyDuplicateKey, closeFiscalPeriod, computeEndingBalance, computeRevaluation, createAccountingEngine, createEvent, createLockPlugin, createModels, createRepositories, creditLimitPlugin, dailyLockPlugin, defaultLogger, defineCountryPack, doubleEntryPlugin, exportToCsv, finalizeSession, fiscalLockPlugin, flattenJournalEntries, format, formatPlain, fromDecimal, fxRealizationPlugin, generateAgedBalance, generateBalanceSheet, generateBudgetVsActual, generateCashFlow, generateDimensionBreakdown, generateGeneralLedger, generateIncomeStatement, generatePartnerLedger, generateRevaluation, generateTrialBalance, getCurrency, getCustomJournalTypes, getDateRange, getFiscalYearStart, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, idempotencyPlugin, immutableGuardPlugin, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, isVirtualTaxAccount, ledgerEventDefinitions, multiply, parseCents, percentage, periodResolver, quickbooksFieldMap, registerJournalType, reopenFiscalPeriod, resolveModelNames, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal, universalFieldMap, watermarkResolver };
3118
+ export { AccountBulkCreated, AccountSeeded, AccountingEngine, AccountingError, CATEGORIES, CATEGORY_KEYS, CURRENCIES, ConcurrencyError, DEFAULT_BUCKETS, DuplicateReferenceError, EntryArchived, EntryCreated, EntryDuplicated, EntryPosted, EntryReversed, EntryUnposted, Errors, IdempotencyConflictError, ImmutableViolationError, InProcessLedgerBus, InvalidOutboxEventError, JOURNAL_CODES, JOURNAL_TYPES, JournalSeeded, LEDGER_EVENTS, LINE_SOURCE_INDEXES, Money, OutboxOwnershipError, ReconciliationMatched, ReconciliationUnmatched, acquireSession, add, allocate, buildAccountTypeMap, buildDimensionFields, buildDimensionIndexes, buildItemFilters, buildOpeningBalanceEntry, buildRevaluationEntry, calculateTotal, classifyDuplicateKey, closeFiscalPeriod, computeEndingBalance, computeRevaluation, createAccountingEngine, createEvent, createLockPlugin, createModels, createRepositories, creditLimitPlugin, dailyLockPlugin, defaultLogger, defineCountryPack, doubleEntryPlugin, exportToCsv, finalizeSession, fiscalLockPlugin, flattenJournalEntries, format, formatPlain, fromDecimal, fxRealizationPlugin, generateAgedBalance, generateBalanceSheet, generateBudgetVsActual, generateCashFlow, generateDimensionBreakdown, generateGeneralLedger, generateIncomeStatement, generatePartnerLedger, generateRevaluation, generateTrialBalance, getCurrency, getCustomJournalTypes, getDateRange, getFiscalYearStart, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, idempotencyPlugin, immutableGuardPlugin, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, isVirtualTaxAccount, ledgerEventDefinitions, multiply, parseCents, percentage, periodResolver, quickbooksFieldMap, registerJournalType, reopenFiscalPeriod, resolveModelNames, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal, universalFieldMap, watermarkResolver };