@classytic/ledger 0.12.2 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -121,6 +121,38 @@ const engine = createAccountingEngine({
121
121
  });
122
122
  ```
123
123
 
124
+ ### Source provenance — `JournalEntry.sourceRef` (0.13.0+)
125
+
126
+ Every JE carries a typed `sourceRef: { sourceModel, sourceId, label?, kind? }`
127
+ slot for "what produced this whole JE". Per-line back-references live on
128
+ `journalItems[].sourceRef` (settles which document) and
129
+ `journalItems[].linkedRefs[]` (additional docs touched).
130
+
131
+ Add the index for fast source → JEs drill-down:
132
+
133
+ ```ts
134
+ import { createAccountingEngine, ENTRY_SOURCE_INDEX } from '@classytic/ledger';
135
+
136
+ createAccountingEngine({
137
+ schemaOptions: {
138
+ journalEntry: { extraIndexes: [ENTRY_SOURCE_INDEX] },
139
+ },
140
+ });
141
+
142
+ // After import — stamp the back-reference, then query by it.
143
+ await JE.updateMany({ _importRunId: docId }, { $set: { sourceRef: {
144
+ sourceModel: 'SourceDocument', sourceId: docId,
145
+ label: 'INV-2026-001 — Acme Corp', kind: 'xero-invoice',
146
+ }}});
147
+
148
+ // Drill-down. Include `sourceModel` in the predicate so the query
149
+ // planner reliably picks `sourceRef_idx` (the partial index only
150
+ // contains stamped docs; the planner prefers it when both fields are
151
+ // constrained). The sourceId-only form returns identical results but
152
+ // may COLLSCAN on small collections.
153
+ await JE.find({ 'sourceRef.sourceModel': 'SourceDocument', 'sourceRef.sourceId': docId });
154
+ ```
155
+
124
156
  ## Plugins
125
157
 
126
158
  ```ts
@@ -1,2 +1,3 @@
1
- import { a as EntryReversedNotification, c as PeriodLockedNotification, i as SourceRef, l as ReconciliationMismatchNotification, n as SourceBridge, o as NotificationBridge, r as SourceBridgeContext, s as NotificationBridgeContext, t as LedgerBridges, u as ExchangeRateBridge } from "../index-DygMrab0.mjs";
1
+ import { a as PeriodLockedNotification, i as NotificationBridgeContext, n as EntryReversedNotification, o as ReconciliationMismatchNotification, r as NotificationBridge, s as ExchangeRateBridge, t as LedgerBridges } from "../index-Dq3Kt1IG.mjs";
2
+ import { n as SourceBridgeContext, r as SourceRef, t as SourceBridge } from "../source.bridge-CqqsPWfn.mjs";
2
3
  export { EntryReversedNotification, ExchangeRateBridge, LedgerBridges, NotificationBridge, NotificationBridgeContext, PeriodLockedNotification, ReconciliationMismatchNotification, SourceBridge, SourceBridgeContext, SourceRef };
@@ -1,2 +1,2 @@
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-B1CePayM.mjs";
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-DCsI2nFX.mjs";
2
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,2 +1,2 @@
1
- import { i as defineCountryPack, n as CountryPackInput, r as JournalTemplate, t as CountryPack } from "../index-Cr57UKD-.mjs";
1
+ import { i as defineCountryPack, n as CountryPackInput, r as JournalTemplate, t as CountryPack } from "../index-DmRItmfZ.mjs";
2
2
  export { CountryPack, CountryPackInput, JournalTemplate, defineCountryPack };
@@ -1,3 +1,3 @@
1
- import { $ as ReconciliationUnmatchedPayload, A as LedgerEventDefinition, B as InProcessLedgerBusOptions, C as EntryPostedPayloadSchema, D as EntryUnpostedPayloadSchema, E as EntryUnposted, F as ReconciliationUnmatched, G as EntryArchivedPayload, H as createEvent, I as ReconciliationUnmatchedPayloadSchema, J as EntryPostedPayload, K as EntryCreatedPayload, L as ledgerEventDefinitions, M as LedgerEventSchema, N as ReconciliationMatched, O as JournalSeeded, P as ReconciliationMatchedPayloadSchema, Q as ReconciliationMatchedPayload, R as EventLogger, S as EntryPosted, T as EntryReversedPayloadSchema, U as AccountBulkCreatedPayload, V as EventContext, W as AccountSeededPayload, X as EntryUnpostedPayload, Y as EntryReversedPayload, Z as JournalSeededPayload, _ as EntryArchivedPayloadSchema, a as OutboxFailOptions, b as EntryDuplicated, c as OutboxFailurePolicy, d as OutboxWriteOptions, et as LEDGER_EVENTS, f as AccountBulkCreated, g as EntryArchived, h as AccountSeededPayloadSchema, i as OutboxErrorInfo, j as LedgerEventPayloadOf, k as JournalSeededPayloadSchema, l as OutboxOwnershipError, m as AccountSeeded, n as OutboxAcknowledgeOptions, o as OutboxFailureContext, p as AccountBulkCreatedPayloadSchema, q as EntryDuplicatedPayload, r as OutboxClaimOptions, s as OutboxFailureDecision, t as InvalidOutboxEventError, tt as LedgerEventName, u as OutboxStore, v as EntryCreated, w as EntryReversed, x as EntryDuplicatedPayloadSchema, y as EntryCreatedPayloadSchema, z as InProcessLedgerBus } from "../outbox-store-CPLeocPg.mjs";
1
+ import { $ as ReconciliationUnmatchedPayload, A as LedgerEventDefinition, B as InProcessLedgerBusOptions, C as EntryPostedPayloadSchema, D as EntryUnpostedPayloadSchema, E as EntryUnposted, F as ReconciliationUnmatched, G as EntryArchivedPayload, H as createEvent, I as ReconciliationUnmatchedPayloadSchema, J as EntryPostedPayload, K as EntryCreatedPayload, L as ledgerEventDefinitions, M as LedgerEventSchema, N as ReconciliationMatched, O as JournalSeeded, P as ReconciliationMatchedPayloadSchema, Q as ReconciliationMatchedPayload, R as EventLogger, S as EntryPosted, T as EntryReversedPayloadSchema, U as AccountBulkCreatedPayload, V as EventContext, W as AccountSeededPayload, X as EntryUnpostedPayload, Y as EntryReversedPayload, Z as JournalSeededPayload, _ as EntryArchivedPayloadSchema, a as OutboxFailOptions, b as EntryDuplicated, c as OutboxFailurePolicy, d as OutboxWriteOptions, et as LEDGER_EVENTS, f as AccountBulkCreated, g as EntryArchived, h as AccountSeededPayloadSchema, i as OutboxErrorInfo, j as LedgerEventPayloadOf, k as JournalSeededPayloadSchema, l as OutboxOwnershipError, m as AccountSeeded, n as OutboxAcknowledgeOptions, o as OutboxFailureContext, p as AccountBulkCreatedPayloadSchema, q as EntryDuplicatedPayload, r as OutboxClaimOptions, s as OutboxFailureDecision, t as InvalidOutboxEventError, tt as LedgerEventName, u as OutboxStore, v as EntryCreated, w as EntryReversed, x as EntryDuplicatedPayloadSchema, y as EntryCreatedPayloadSchema, z as InProcessLedgerBus } from "../outbox-store-BcCiHMPw.mjs";
2
2
  import { DomainEvent, EventHandler, EventTransport, PublishManyResult } from "@classytic/primitives/events";
3
3
  export { AccountBulkCreated, type AccountBulkCreatedPayload, type AccountBulkCreatedPayloadSchema, AccountSeeded, type AccountSeededPayload, type AccountSeededPayloadSchema, type DomainEvent, EntryArchived, type EntryArchivedPayload, type EntryArchivedPayloadSchema, EntryCreated, type EntryCreatedPayload, type EntryCreatedPayloadSchema, EntryDuplicated, type EntryDuplicatedPayload, type EntryDuplicatedPayloadSchema, EntryPosted, type EntryPostedPayload, type EntryPostedPayloadSchema, EntryReversed, type EntryReversedPayload, type EntryReversedPayloadSchema, EntryUnposted, type EntryUnpostedPayload, type EntryUnpostedPayloadSchema, type EventContext, type EventHandler, type EventLogger, type EventTransport, InProcessLedgerBus, type InProcessLedgerBusOptions, InvalidOutboxEventError, JournalSeeded, type JournalSeededPayload, type JournalSeededPayloadSchema, LEDGER_EVENTS, type LedgerEventDefinition, type LedgerEventName, type LedgerEventPayloadOf, type LedgerEventSchema, type OutboxAcknowledgeOptions, type OutboxClaimOptions, type OutboxErrorInfo, type OutboxFailOptions, type OutboxFailureContext, type OutboxFailureDecision, type OutboxFailurePolicy, OutboxOwnershipError, type OutboxStore, type OutboxWriteOptions, type PublishManyResult, ReconciliationMatched, type ReconciliationMatchedPayload, type ReconciliationMatchedPayloadSchema, ReconciliationUnmatched, type ReconciliationUnmatchedPayload, type ReconciliationUnmatchedPayloadSchema, createEvent, ledgerEventDefinitions };
@@ -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 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-pRW5cZhF.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-CMyOuKd3.mjs";
2
2
  export { CsvOptions, ExportField, ExportFieldMap, FlatJournalRow, PopulatedAccount, PopulatedJournalEntry, PopulatedJournalItem, buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };
@@ -1,12 +1,6 @@
1
+ import { r as SourceRef } from "./source.bridge-CqqsPWfn.mjs";
2
+
1
3
  //#region src/exports/types.d.ts
2
- /**
3
- * Export Types — Field maps, flat rows, and CSV builder configuration.
4
- *
5
- * All monetary values are in integer minor units (cents). For example,
6
- * 10050 represents $100.50. Field maps convert to dollar strings at CSV output.
7
- *
8
- * @module @classytic/ledger/exports
9
- */
10
4
  /**
11
5
  * A populated account object (the shape after .populate('journalItems.account')).
12
6
  * Only the fields the export module reads. Extra fields are ignored.
@@ -31,6 +25,12 @@ interface PopulatedJournalItem {
31
25
  taxCode?: string;
32
26
  taxName?: string;
33
27
  }>;
28
+ /** Per-line source back-ref (0.13.0). Null defaults when unstamped. */
29
+ sourceRef?: SourceRef;
30
+ /** Additional docs this line touches (QBO `LinkedTxn[]` shape). */
31
+ linkedRefs?: SourceRef[];
32
+ /** Free-form per-line provenance. */
33
+ meta?: Record<string, unknown> | null;
34
34
  /** Extra dimension fields from extraItemFields */
35
35
  [key: string]: unknown;
36
36
  }
@@ -49,6 +49,8 @@ interface PopulatedJournalEntry {
49
49
  totalCredit: number;
50
50
  state: 'draft' | 'posted' | 'archived';
51
51
  reversed?: boolean;
52
+ /** Entry-level source back-ref (0.13.0). Null defaults when unstamped. */
53
+ sourceRef?: SourceRef;
52
54
  createdAt?: Date | string;
53
55
  updatedAt?: Date | string;
54
56
  [key: string]: unknown;
@@ -1,4 +1,4 @@
1
- import { t as AccountType } from "./core-B90x0Abq.mjs";
1
+ import { t as AccountType } from "./core-D-2qfAJF.mjs";
2
2
 
3
3
  //#region src/country/index.d.ts
4
4
  /**
@@ -1,3 +1,5 @@
1
+ import { t as SourceBridge } from "./source.bridge-CqqsPWfn.mjs";
2
+
1
3
  //#region src/bridges/exchange-rate.bridge.d.ts
2
4
  /**
3
5
  * Exchange Rate Bridge — host-injected rate sourcing.
@@ -114,50 +116,6 @@ interface NotificationBridge {
114
116
  onReconciliationMismatch?(payload: ReconciliationMismatchNotification, ctx: NotificationBridgeContext): Promise<void>;
115
117
  }
116
118
  //#endregion
117
- //#region src/bridges/source.bridge.d.ts
118
- /**
119
- * SourceBridge — host-implemented resolver for polymorphic external references.
120
- *
121
- * Ledger journal entries commonly carry a `reference`/`externalRef` pointing
122
- * at a source document that lives outside the ledger package — an Invoice,
123
- * Payment, Payroll Run, Stripe Charge, ERP voucher, etc. Storing these as
124
- * opaque `String + sourceModel` (per PACKAGE_RULES §7) keeps the ledger
125
- * transport-agnostic: the same schema works whether the source lives in the
126
- * same Mongo, a different Mongo, Postgres, or an external REST API.
127
- *
128
- * Hosts implement `SourceBridge` to hydrate those refs when building
129
- * enriched views (partner ledger with invoice details, audit trail with
130
- * payment metadata, reconciliation UI with source documents, etc.).
131
- *
132
- * All methods are optional. Features that need resolution degrade gracefully
133
- * when a bridge is not provided.
134
- */
135
- interface SourceRef {
136
- sourceId: string;
137
- sourceModel: string;
138
- }
139
- interface SourceBridgeContext {
140
- organizationId?: unknown;
141
- actorId?: unknown;
142
- [key: string]: unknown;
143
- }
144
- interface SourceBridge {
145
- /**
146
- * Resolve a single external reference.
147
- *
148
- * Return `null` when the source cannot be found (deleted, permission
149
- * denied, wrong model). Do not throw for missing sources — callers
150
- * expect `null` for graceful degradation.
151
- */
152
- resolve?(sourceId: string, sourceModel: string, ctx: SourceBridgeContext): Promise<unknown | null>;
153
- /**
154
- * Batch resolver — avoids N+1 round-trips when enriching a list.
155
- * Key the returned map by `sourceId`. Missing sources may be omitted or
156
- * mapped to `null` at the implementer's discretion.
157
- */
158
- resolveMany?(refs: ReadonlyArray<SourceRef>, ctx: SourceBridgeContext): Promise<Map<string, unknown>>;
159
- }
160
- //#endregion
161
119
  //#region src/bridges/index.d.ts
162
120
  /** Collected bridges exposed as `engine.bridges`. All optional per PACKAGE_RULES §23. */
163
121
  interface LedgerBridges {
@@ -166,4 +124,4 @@ interface LedgerBridges {
166
124
  exchangeRate?: ExchangeRateBridge | undefined;
167
125
  }
168
126
  //#endregion
169
- export { EntryReversedNotification as a, PeriodLockedNotification as c, SourceRef as i, ReconciliationMismatchNotification as l, SourceBridge as n, NotificationBridge as o, SourceBridgeContext as r, NotificationBridgeContext as s, LedgerBridges as t, ExchangeRateBridge as u };
127
+ export { PeriodLockedNotification as a, NotificationBridgeContext as i, EntryReversedNotification as n, ReconciliationMismatchNotification as o, NotificationBridge as r, ExchangeRateBridge as s, LedgerBridges as t };
package/dist/index.d.mts CHANGED
@@ -1,12 +1,13 @@
1
- import { a as EntryReversedNotification, c as PeriodLockedNotification, i as SourceRef, l as ReconciliationMismatchNotification, n as SourceBridge, o as NotificationBridge, r as SourceBridgeContext, s as NotificationBridgeContext, t as LedgerBridges, u as ExchangeRateBridge } from "./index-DygMrab0.mjs";
2
- import { _ as TaxMetadata, a as Cents, c as DateRange, d as JournalType, f as MainType, g as TaxDetail, h as StatementType, i as CategoryKey, l as EntryState, m as ObjectId, n as CashFlowCategory, o as Currency, p as NormalBalance, s as DateOption, t as AccountType, u as JournalItem, v as TotalAccountOp } from "./core-B90x0Abq.mjs";
3
- import { S as isValidCategory, a as getJournalTypeCodes, b as isBalanceSheet, c as CURRENCIES, d as isValidCurrency, f as CATEGORIES, i as getJournalType, l as getCurrency, n as JOURNAL_TYPES, o as isValidJournalType, p as CATEGORY_KEYS, r as getCustomJournalTypes, s as registerJournalType, t as JOURNAL_CODES, u as getMinorUnit, x as isIncomeStatement, y as getNormalBalance } from "./journals-B1CePayM.mjs";
4
- import { i as defineCountryPack, n as CountryPackInput, r as JournalTemplate, t as CountryPack } from "./index-Cr57UKD-.mjs";
5
- import { $ as ReconciliationUnmatchedPayload, A as LedgerEventDefinition, B as InProcessLedgerBusOptions, E as EntryUnposted, F as ReconciliationUnmatched, G as EntryArchivedPayload, H as createEvent, J as EntryPostedPayload, K as EntryCreatedPayload, L as ledgerEventDefinitions, M as LedgerEventSchema, N as ReconciliationMatched, O as JournalSeeded, Q as ReconciliationMatchedPayload, R as EventLogger, S as EntryPosted, U as AccountBulkCreatedPayload, V as EventContext, W as AccountSeededPayload, X as EntryUnpostedPayload, Y as EntryReversedPayload, Z as JournalSeededPayload, a as OutboxFailOptions, b as EntryDuplicated, c as OutboxFailurePolicy, d as OutboxWriteOptions, et as LEDGER_EVENTS, f as AccountBulkCreated, g as EntryArchived, i as OutboxErrorInfo, j as LedgerEventPayloadOf, l as OutboxOwnershipError, m as AccountSeeded, n as OutboxAcknowledgeOptions, o as OutboxFailureContext, q as EntryDuplicatedPayload, r as OutboxClaimOptions, s as OutboxFailureDecision, t as InvalidOutboxEventError, tt as LedgerEventName, u as OutboxStore, v as EntryCreated, w as EntryReversed, z as InProcessLedgerBus } from "./outbox-store-CPLeocPg.mjs";
6
- import { _ as PopulatedJournalEntry, a as exportToCsv, h as FlatJournalRow, i as quickbooksFieldMap, m as ExportFieldMap, p as ExportField, r as universalFieldMap, t as flattenJournalEntries } from "./index-pRW5cZhF.mjs";
1
+ import { a as PeriodLockedNotification, i as NotificationBridgeContext, n as EntryReversedNotification, o as ReconciliationMismatchNotification, r as NotificationBridge, s as ExchangeRateBridge, t as LedgerBridges } from "./index-Dq3Kt1IG.mjs";
2
+ import { n as SourceBridgeContext, r as SourceRef, t as SourceBridge } from "./source.bridge-CqqsPWfn.mjs";
3
+ import { _ as TaxMetadata, a as Cents, c as DateRange, d as JournalType, f as MainType, g as TaxDetail, h as StatementType, i as CategoryKey, l as EntryState, m as ObjectId, n as CashFlowCategory, o as Currency, p as NormalBalance, s as DateOption, t as AccountType, u as JournalItem, v as TotalAccountOp } from "./core-D-2qfAJF.mjs";
4
+ import { S as isValidCategory, a as getJournalTypeCodes, b as isBalanceSheet, c as CURRENCIES, d as isValidCurrency, f as CATEGORIES, i as getJournalType, l as getCurrency, n as JOURNAL_TYPES, o as isValidJournalType, p as CATEGORY_KEYS, r as getCustomJournalTypes, s as registerJournalType, t as JOURNAL_CODES, u as getMinorUnit, x as isIncomeStatement, y as getNormalBalance } from "./journals-DCsI2nFX.mjs";
5
+ import { i as defineCountryPack, n as CountryPackInput, r as JournalTemplate, t as CountryPack } from "./index-DmRItmfZ.mjs";
6
+ import { $ as ReconciliationUnmatchedPayload, A as LedgerEventDefinition, B as InProcessLedgerBusOptions, E as EntryUnposted, F as ReconciliationUnmatched, G as EntryArchivedPayload, H as createEvent, J as EntryPostedPayload, K as EntryCreatedPayload, L as ledgerEventDefinitions, M as LedgerEventSchema, N as ReconciliationMatched, O as JournalSeeded, Q as ReconciliationMatchedPayload, R as EventLogger, S as EntryPosted, U as AccountBulkCreatedPayload, V as EventContext, W as AccountSeededPayload, X as EntryUnpostedPayload, Y as EntryReversedPayload, Z as JournalSeededPayload, a as OutboxFailOptions, b as EntryDuplicated, c as OutboxFailurePolicy, d as OutboxWriteOptions, et as LEDGER_EVENTS, f as AccountBulkCreated, g as EntryArchived, i as OutboxErrorInfo, j as LedgerEventPayloadOf, l as OutboxOwnershipError, m as AccountSeeded, n as OutboxAcknowledgeOptions, o as OutboxFailureContext, q as EntryDuplicatedPayload, r as OutboxClaimOptions, s as OutboxFailureDecision, t as InvalidOutboxEventError, tt as LedgerEventName, u as OutboxStore, v as EntryCreated, w as EntryReversed, z as InProcessLedgerBus } from "./outbox-store-BcCiHMPw.mjs";
7
+ import { _ as PopulatedJournalEntry, a as exportToCsv, h as FlatJournalRow, i as quickbooksFieldMap, m as ExportFieldMap, p as ExportField, r as universalFieldMap, t as flattenJournalEntries } from "./index-CMyOuKd3.mjs";
7
8
  import { Money, abs, add, allocate, equals, format, formatPlain, fromDecimal, isNegative, isPositive, isValid, isZero, max, min, multiply, negate, parseCents, percentage, round, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal } from "./money.mjs";
8
- import { $ as GeneralLedgerAccount, A as generateDimensionBreakdown, B as AgedBalanceOptions, D as DimensionBreakdownParams, E as DimensionBreakdownOptions, F as BudgetVsActualReport, G as DEFAULT_BUCKETS, H as AgedBalanceReport, I as BudgetVsActualRow, J as BalanceSheetReport, K as generateAgedBalance, L as generateBudgetVsActual, M as generateCashFlow, N as BudgetVsActualOptions, O as DimensionBreakdownReport, P as BudgetVsActualParams, Q as ComparativeMode, T as reopenFiscalPeriod, U as AgedBalanceRow, V as AgedBalanceParams, W as AgedBucketConfig, X as CashFlowReport, Y as BalanceSheetSection, Z as CashFlowSection, a as RevaluationReport, at as PeriodColumn, b as generateGeneralLedger, c as RevaluationRate, ct as ReportGroup, d as computeRevaluation, dt as TrialBalanceColumnRow, et as GeneralLedgerReport, f as PartnerLedgerLine, ft as TrialBalanceReport, g as generatePartnerLedger, h as PartnerLedgerReport, ht as defaultLogger, i as RevaluationParams, it as LedgerEntry, k as DimensionBreakdownRow, l as RevaluationResult, lt as ReportLine, m as PartnerLedgerParams, mt as Logger, n as generateTrialBalance, nt as IncomeStatementReport, o as generateRevaluation, ot as ReportAccount, p as PartnerLedgerOptions, pt as TrialBalanceRow, q as BalanceSheetLineSource, r as RevaluationOptions, rt as IncomeStatementSection, s as AccountForeignBalance, st as ReportCategory, tt as IncomeStatementLineSource, u as buildRevaluationEntry, ut as ReportSection, v as generateIncomeStatement, vt as DaybookParams, w as closeFiscalPeriod, yt as DaybookReport, z as generateBalanceSheet } from "./trial-balance-Dy_fAeJV.mjs";
9
- import { A as OpenItem, C as AccountRepository, D as JournalItemRef, E as JournalEntryRepository, F as SeedOptions, I as SeedResult, M as ReconciliationRepository, N as ReverseOptions, O as JournalRepository, P as ReverseResult, S as creditLimitPlugin, T as BulkCreateResult, _ as FxRealizationPluginOptions, a as dailyLockPlugin, b as doubleEntryPlugin, c as periodResolver, d as LockAccountSelector, f as LockHit, g as idempotencyPlugin, i as FiscalLockPluginOptions, j as PostOptions, k as MatchInput, l as createLockPlugin, m as LockResolverContext, n as watermarkResolver, o as fiscalLockPlugin, p as LockResolver, r as DailyLockPluginOptions, s as PeriodResolverOptions, t as WatermarkResolverOptions, u as CreateLockPluginOptions, v as fxRealizationPlugin, w as BulkCreateInput, x as CreditLimitPluginOptions } from "./index-BFPFihTF.mjs";
9
+ import { $ as GeneralLedgerAccount, A as generateDimensionBreakdown, B as AgedBalanceOptions, D as DimensionBreakdownParams, E as DimensionBreakdownOptions, F as BudgetVsActualReport, G as DEFAULT_BUCKETS, H as AgedBalanceReport, I as BudgetVsActualRow, J as BalanceSheetReport, K as generateAgedBalance, L as generateBudgetVsActual, M as generateCashFlow, N as BudgetVsActualOptions, O as DimensionBreakdownReport, P as BudgetVsActualParams, Q as ComparativeMode, T as reopenFiscalPeriod, U as AgedBalanceRow, V as AgedBalanceParams, W as AgedBucketConfig, X as CashFlowReport, Y as BalanceSheetSection, Z as CashFlowSection, a as RevaluationReport, at as PeriodColumn, b as generateGeneralLedger, c as RevaluationRate, ct as ReportGroup, d as computeRevaluation, dt as TrialBalanceColumnRow, et as GeneralLedgerReport, f as PartnerLedgerLine, ft as TrialBalanceReport, g as generatePartnerLedger, h as PartnerLedgerReport, ht as defaultLogger, i as RevaluationParams, it as LedgerEntry, k as DimensionBreakdownRow, l as RevaluationResult, lt as ReportLine, m as PartnerLedgerParams, mt as Logger, n as generateTrialBalance, nt as IncomeStatementReport, o as generateRevaluation, ot as ReportAccount, p as PartnerLedgerOptions, pt as TrialBalanceRow, q as BalanceSheetLineSource, r as RevaluationOptions, rt as IncomeStatementSection, s as AccountForeignBalance, st as ReportCategory, tt as IncomeStatementLineSource, u as buildRevaluationEntry, ut as ReportSection, v as generateIncomeStatement, vt as DaybookParams, w as closeFiscalPeriod, yt as DaybookReport, z as generateBalanceSheet } from "./trial-balance-BS7F7-Xe.mjs";
10
+ import { A as OpenItem, C as AccountRepository, D as JournalItemRef, E as JournalEntryRepository, F as SeedOptions, I as SeedResult, M as ReconciliationRepository, N as ReverseOptions, O as JournalRepository, P as ReverseResult, S as creditLimitPlugin, T as BulkCreateResult, _ as FxRealizationPluginOptions, a as dailyLockPlugin, b as doubleEntryPlugin, c as periodResolver, d as LockAccountSelector, f as LockHit, g as idempotencyPlugin, i as FiscalLockPluginOptions, j as PostOptions, k as MatchInput, l as createLockPlugin, m as LockResolverContext, n as watermarkResolver, o as fiscalLockPlugin, p as LockResolver, r as DailyLockPluginOptions, s as PeriodResolverOptions, t as WatermarkResolverOptions, u as CreateLockPluginOptions, v as fxRealizationPlugin, w as BulkCreateInput, x as CreditLimitPluginOptions } from "./index-DkM0S1kQ.mjs";
10
11
  import { DomainEvent, EventHandler, EventTransport, EventTransport as EventTransport$1, PublishManyResult } from "@classytic/primitives/events";
11
12
  import { PaginationConfig, PluginFunction, PluginType, QueryParser, QueryParserOptions, Repository } from "@classytic/mongokit";
12
13
  import mongoose, { ClientSession, Connection, Model } from "mongoose";
@@ -887,6 +888,16 @@ interface JournalEntryInput {
887
888
  label?: string;
888
889
  date: Date;
889
890
  journalItems: JournalItemInput[];
891
+ /**
892
+ * Entry-level source back-reference (0.13.0+). Set at create time to stamp
893
+ * "what produced this whole JE". Most ingestion paths set this *after*
894
+ * insert via `repo.updateMany({ _importRunId }, { $set: { sourceRef } })`
895
+ * (because the source doc id is known only after the batch lands), but
896
+ * single-shot creators can pass it inline here. Drill-down: query
897
+ * `find({ 'sourceRef.sourceId': id })` — add `ENTRY_SOURCE_INDEX` to
898
+ * `schemaOptions.journalEntry.extraIndexes` for the index.
899
+ */
900
+ sourceRef?: SourceRef;
890
901
  /** Extra fields injected into the entry doc (dimension fields, tags, etc.). */
891
902
  extra?: Record<string, unknown>;
892
903
  }
@@ -901,6 +912,12 @@ interface JournalItemInput {
901
912
  originalCredit?: Cents;
902
913
  matchingNumber?: string;
903
914
  maturityDate?: Date;
915
+ /** "The document this line settles" — primary per-line back-reference. */
916
+ sourceRef?: SourceRef;
917
+ /** Additional docs this line touches (QBO `LinkedTxn[]` shape). */
918
+ linkedRefs?: SourceRef[];
919
+ /** Free-form per-line provenance (cost-center, project code, etc). */
920
+ meta?: Record<string, unknown> | null;
904
921
  }
905
922
  //#endregion
906
923
  //#region src/builders/opening-balance.d.ts
@@ -972,6 +989,30 @@ declare function buildOpeningBalanceEntry(input: OpeningBalanceInput): OpeningBa
972
989
  * },
973
990
  * });
974
991
  */
992
+ /**
993
+ * Recommended opt-in index for the entry-level `sourceRef` field (0.13.0+).
994
+ *
995
+ * Spread into `schemaOptions.journalEntry.extraIndexes` to enable fast
996
+ * "list every JE produced by source document X" lookups — the canonical
997
+ * bookkeeping-UI drill-down from a source document (invoice, bill, bank
998
+ * statement) into its derived journal entries.
999
+ *
1000
+ * Sparse + partial: only JEs where `sourceRef.sourceModel` is a string get
1001
+ * indexed. Unstamped JEs (the default `{ sourceModel: null, sourceId: null }`)
1002
+ * cost zero index storage.
1003
+ *
1004
+ * @example
1005
+ * import { createAccountingEngine, ENTRY_SOURCE_INDEX } from '@classytic/ledger';
1006
+ * createAccountingEngine({
1007
+ * schemaOptions: {
1008
+ * journalEntry: { extraIndexes: [ENTRY_SOURCE_INDEX] },
1009
+ * },
1010
+ * });
1011
+ */
1012
+ declare const ENTRY_SOURCE_INDEX: {
1013
+ fields: Record<string, 1 | -1>;
1014
+ options: Record<string, unknown>;
1015
+ };
975
1016
  declare const LINE_SOURCE_INDEXES: ReadonlyArray<{
976
1017
  fields: Record<string, 1 | -1>;
977
1018
  options: Record<string, unknown>;
@@ -1198,4 +1239,4 @@ interface PostingResult {
1198
1239
  idempotencyKeys?: string[];
1199
1240
  }
1200
1241
  //#endregion
1201
- export { AccountBulkCreated, type AccountBulkCreatedPayload, type AccountCode, type AccountForeignBalance, type AccountRepository, AccountSeeded, type AccountSeededPayload, type AccountSummary, type AccountType, AccountingEngine, type AccountingEngineConfig, AccountingError, type ActorContext, type AgedBalanceOptions, type AgedBalanceParams, type AgedBalanceReport, type AgedBalanceRow, type AgedBucketConfig, type AuditConfig, type BalanceSheetLineSource, type BalanceSheetReport, type BalanceSheetSection, type BudgetVsActualOptions, type BudgetVsActualParams, type BudgetVsActualReport, type BudgetVsActualRow, type BulkCreateInput, type BulkCreateResult, CATEGORIES, CATEGORY_KEYS, CURRENCIES, type CashFlowCategory, type CashFlowReport, type CashFlowSection, type CategoryKey, type Cents, type ComparativeMode, ConcurrencyError, type CountryPack, type CountryPackInput, type CreateLockPluginOptions, type CreditLimitPluginOptions, type Currency, DEFAULT_BUCKETS, type DailyLockPluginOptions, type DateOption, type DateRange, type DimensionBreakdownOptions, type DimensionBreakdownParams, type DimensionBreakdownReport, type DimensionBreakdownRow, type DimensionDefinition, type DomainEvent, DuplicateReferenceError, EntryArchived, type EntryArchivedPayload, EntryCreated, type EntryCreatedPayload, EntryDuplicated, type EntryDuplicatedPayload, EntryPosted, type EntryPostedPayload, EntryReversed, type EntryReversedNotification, type EntryReversedPayload, type EntryState, EntryUnposted, type EntryUnpostedPayload, Errors, type EventContext, type EventHandler, type EventLogger, type EventTransport, type ExchangeRateBridge, type ExportField, type ExportFieldMap, type FieldError, type FiscalLockPluginOptions, type FiscalPeriodSummary, type FlatJournalRow, type FxRealizationPluginOptions, type GeneralLedgerAccount, type GeneralLedgerReport, IdempotencyConflictError, type ImmutableGuardOptions, ImmutableViolationError, InProcessLedgerBus, type InProcessLedgerBusOptions, type IncomeStatementLineSource, type IncomeStatementReport, type IncomeStatementSection, type IntrospectAPI, InvalidOutboxEventError, JOURNAL_CODES, JOURNAL_TYPES, type JournalEntryInput, type JournalEntryRepository, type JournalItem, type JournalItemInput, type JournalItemRef, type JournalRepository, type JournalSchemaOptions, JournalSeeded, type JournalSeededPayload, type JournalTemplate, type JournalType, LEDGER_EVENTS, LINE_SOURCE_INDEXES, type LedgerBridges, type LedgerEntry, type LedgerEventDefinition, type LedgerEventName, type LedgerEventPayloadOf, type LedgerEventSchema, type LedgerModels, type LedgerPaginationConfig, type LedgerRepositories, type LedgerRepositoryPlugins, type LockAccountSelector, type LockHit, type LockResolver, type LockResolverContext, type Logger, type MainType, type MatchHookContext, type MatchHookItem, type MatchInput, type ModelNames, Money, type MultiCurrencyConfig, type MultiTenantConfig, type NormalBalance, type NotificationBridge, type NotificationBridgeContext, type OpenItem, type OpeningBalanceInput, type OpeningBalanceResult, type OutboxAcknowledgeOptions, type OutboxClaimOptions, type OutboxErrorInfo, type OutboxFailOptions, type OutboxFailureContext, type OutboxFailureDecision, type OutboxFailurePolicy, OutboxOwnershipError, type OutboxStore, type OutboxWriteOptions, type PartnerLedgerLine, type PartnerLedgerOptions, type PartnerLedgerParams, type PartnerLedgerReport, type PeriodColumn, type PeriodLockedNotification, type PeriodResolverOptions, type PopulatedJournalEntry, type PostOptions, type PostingContract, type PostingResult, type PublishManyResult, ReconciliationMatched, type ReconciliationMatchedPayload, type ReconciliationMismatchNotification, type ReconciliationRepository, ReconciliationUnmatched, type ReconciliationUnmatchedPayload, type RecordAPI, type RecordAdjustmentInput, type RecordAdjustmentLine, type RecordExpenseInput, type RecordOptions, type RecordPaymentInput, type RecordSaleInput, type RecordTransferInput, type ReportAccount, type ReportCategory, type ReportDescriptor, type ReportGroup, type ReportLine, type ReportSection, type ResolvedModelNames, type RevaluationOptions, type RevaluationParams, type RevaluationRate, type RevaluationReport, type RevaluationResult, type ReverseOptions, type ReverseResult, type SchemaOptions, type SeedOptions, type SeedResult, type Cents$1 as SemanticCents, type SessionResult, type SourceBridge, type SourceBridgeContext, type SourceRef, type StatementType, type StrictnessConfig, type SubledgerJournalItem, type SubledgerPostingInput, type TaxDetail, type TaxMetadata, type TotalAccountOp, type TrialBalanceColumnRow, type TrialBalanceReport, type TrialBalanceRow, type UnmatchHookContext, type WatermarkResolverOptions, 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 };
1242
+ export { AccountBulkCreated, type AccountBulkCreatedPayload, type AccountCode, type AccountForeignBalance, type AccountRepository, AccountSeeded, type AccountSeededPayload, type AccountSummary, type AccountType, AccountingEngine, type AccountingEngineConfig, AccountingError, type ActorContext, type AgedBalanceOptions, type AgedBalanceParams, type AgedBalanceReport, type AgedBalanceRow, type AgedBucketConfig, type AuditConfig, type BalanceSheetLineSource, type BalanceSheetReport, type BalanceSheetSection, type BudgetVsActualOptions, type BudgetVsActualParams, type BudgetVsActualReport, type BudgetVsActualRow, type BulkCreateInput, type BulkCreateResult, CATEGORIES, CATEGORY_KEYS, CURRENCIES, type CashFlowCategory, type CashFlowReport, type CashFlowSection, type CategoryKey, type Cents, type ComparativeMode, ConcurrencyError, type CountryPack, type CountryPackInput, type CreateLockPluginOptions, type CreditLimitPluginOptions, type Currency, DEFAULT_BUCKETS, type DailyLockPluginOptions, type DateOption, type DateRange, type DimensionBreakdownOptions, type DimensionBreakdownParams, type DimensionBreakdownReport, type DimensionBreakdownRow, type DimensionDefinition, type DomainEvent, DuplicateReferenceError, ENTRY_SOURCE_INDEX, EntryArchived, type EntryArchivedPayload, EntryCreated, type EntryCreatedPayload, EntryDuplicated, type EntryDuplicatedPayload, EntryPosted, type EntryPostedPayload, EntryReversed, type EntryReversedNotification, type EntryReversedPayload, type EntryState, EntryUnposted, type EntryUnpostedPayload, Errors, type EventContext, type EventHandler, type EventLogger, type EventTransport, type ExchangeRateBridge, type ExportField, type ExportFieldMap, type FieldError, type FiscalLockPluginOptions, type FiscalPeriodSummary, type FlatJournalRow, type FxRealizationPluginOptions, type GeneralLedgerAccount, type GeneralLedgerReport, IdempotencyConflictError, type ImmutableGuardOptions, ImmutableViolationError, InProcessLedgerBus, type InProcessLedgerBusOptions, type IncomeStatementLineSource, type IncomeStatementReport, type IncomeStatementSection, type IntrospectAPI, InvalidOutboxEventError, JOURNAL_CODES, JOURNAL_TYPES, type JournalEntryInput, type JournalEntryRepository, type JournalItem, type JournalItemInput, type JournalItemRef, type JournalRepository, type JournalSchemaOptions, JournalSeeded, type JournalSeededPayload, type JournalTemplate, type JournalType, LEDGER_EVENTS, LINE_SOURCE_INDEXES, type LedgerBridges, type LedgerEntry, type LedgerEventDefinition, type LedgerEventName, type LedgerEventPayloadOf, type LedgerEventSchema, type LedgerModels, type LedgerPaginationConfig, type LedgerRepositories, type LedgerRepositoryPlugins, type LockAccountSelector, type LockHit, type LockResolver, type LockResolverContext, type Logger, type MainType, type MatchHookContext, type MatchHookItem, type MatchInput, type ModelNames, Money, type MultiCurrencyConfig, type MultiTenantConfig, type NormalBalance, type NotificationBridge, type NotificationBridgeContext, type OpenItem, type OpeningBalanceInput, type OpeningBalanceResult, type OutboxAcknowledgeOptions, type OutboxClaimOptions, type OutboxErrorInfo, type OutboxFailOptions, type OutboxFailureContext, type OutboxFailureDecision, type OutboxFailurePolicy, OutboxOwnershipError, type OutboxStore, type OutboxWriteOptions, type PartnerLedgerLine, type PartnerLedgerOptions, type PartnerLedgerParams, type PartnerLedgerReport, type PeriodColumn, type PeriodLockedNotification, type PeriodResolverOptions, type PopulatedJournalEntry, type PostOptions, type PostingContract, type PostingResult, type PublishManyResult, ReconciliationMatched, type ReconciliationMatchedPayload, type ReconciliationMismatchNotification, type ReconciliationRepository, ReconciliationUnmatched, type ReconciliationUnmatchedPayload, type RecordAPI, type RecordAdjustmentInput, type RecordAdjustmentLine, type RecordExpenseInput, type RecordOptions, type RecordPaymentInput, type RecordSaleInput, type RecordTransferInput, type ReportAccount, type ReportCategory, type ReportDescriptor, type ReportGroup, type ReportLine, type ReportSection, type ResolvedModelNames, type RevaluationOptions, type RevaluationParams, type RevaluationRate, type RevaluationReport, type RevaluationResult, type ReverseOptions, type ReverseResult, type SchemaOptions, type SeedOptions, type SeedResult, type Cents$1 as SemanticCents, type SessionResult, type SourceBridge, type SourceBridgeContext, type SourceRef, type StatementType, type StrictnessConfig, type SubledgerJournalItem, type SubledgerPostingInput, type TaxDetail, type TaxMetadata, type TotalAccountOp, type TrialBalanceColumnRow, type TrialBalanceReport, type TrialBalanceRow, type UnmatchHookContext, type WatermarkResolverOptions, 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 };
package/dist/index.mjs CHANGED
@@ -497,13 +497,42 @@ function createJournalSchema(config, accountModelName, options = {}) {
497
497
  * },
498
498
  * });
499
499
  */
500
+ /**
501
+ * Recommended opt-in index for the entry-level `sourceRef` field (0.13.0+).
502
+ *
503
+ * Spread into `schemaOptions.journalEntry.extraIndexes` to enable fast
504
+ * "list every JE produced by source document X" lookups — the canonical
505
+ * bookkeeping-UI drill-down from a source document (invoice, bill, bank
506
+ * statement) into its derived journal entries.
507
+ *
508
+ * Sparse + partial: only JEs where `sourceRef.sourceModel` is a string get
509
+ * indexed. Unstamped JEs (the default `{ sourceModel: null, sourceId: null }`)
510
+ * cost zero index storage.
511
+ *
512
+ * @example
513
+ * import { createAccountingEngine, ENTRY_SOURCE_INDEX } from '@classytic/ledger';
514
+ * createAccountingEngine({
515
+ * schemaOptions: {
516
+ * journalEntry: { extraIndexes: [ENTRY_SOURCE_INDEX] },
517
+ * },
518
+ * });
519
+ */
520
+ const ENTRY_SOURCE_INDEX = {
521
+ fields: {
522
+ "sourceRef.sourceModel": 1,
523
+ "sourceRef.sourceId": 1
524
+ },
525
+ options: {
526
+ partialFilterExpression: { "sourceRef.sourceModel": { $type: "string" } },
527
+ name: "sourceRef_idx"
528
+ }
529
+ };
500
530
  const LINE_SOURCE_INDEXES = [{
501
531
  fields: {
502
532
  "journalItems.sourceRef.sourceModel": 1,
503
533
  "journalItems.sourceRef.sourceId": 1
504
534
  },
505
535
  options: {
506
- sparse: true,
507
536
  partialFilterExpression: { "journalItems.sourceRef.sourceModel": { $type: "string" } },
508
537
  name: "journalItems_sourceRef_idx"
509
538
  }
@@ -525,7 +554,7 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
525
554
  taxCode: { type: String },
526
555
  taxName: { type: String }
527
556
  }, { _id: false });
528
- const ItemSourceRefSchema = new mongoose.Schema({
557
+ const SourceRefSchema = new mongoose.Schema({
529
558
  sourceModel: {
530
559
  type: String,
531
560
  default: null
@@ -533,6 +562,14 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
533
562
  sourceId: {
534
563
  type: String,
535
564
  default: null
565
+ },
566
+ label: {
567
+ type: String,
568
+ default: null
569
+ },
570
+ kind: {
571
+ type: String,
572
+ default: null
536
573
  }
537
574
  }, { _id: false });
538
575
  const amountValidator = {
@@ -603,11 +640,11 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
603
640
  default: null
604
641
  },
605
642
  sourceRef: {
606
- type: ItemSourceRefSchema,
643
+ type: SourceRefSchema,
607
644
  default: () => ({})
608
645
  },
609
646
  linkedRefs: {
610
- type: [ItemSourceRefSchema],
647
+ type: [SourceRefSchema],
611
648
  default: void 0
612
649
  },
613
650
  ...currencyItemFields,
@@ -688,6 +725,10 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
688
725
  type: mongoose.Schema.Types.Mixed,
689
726
  default: null
690
727
  },
728
+ sourceRef: {
729
+ type: SourceRefSchema,
730
+ default: () => ({})
731
+ },
691
732
  ...extraFields
692
733
  };
693
734
  if (config.audit?.trackActor) {
@@ -783,6 +824,7 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
783
824
  partialFilterExpression: { idempotencyKey: { $type: "string" } }
784
825
  });
785
826
  }
827
+ for (const idx of extraIndexes) schema.index(idx.fields, idx.options);
786
828
  injectTenantField(schema, scope);
787
829
  if (indexes) {
788
830
  schema.index({
@@ -801,7 +843,6 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
801
843
  },
802
844
  name: "journal_text_idx"
803
845
  });
804
- for (const idx of extraIndexes) schema.index(idx.fields, idx.options);
805
846
  return schema;
806
847
  }
807
848
  //#endregion
@@ -3129,4 +3170,4 @@ function buildDimensionIndexes(dimensions, orgField) {
3129
3170
  });
3130
3171
  }
3131
3172
  //#endregion
3132
- 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 };
3173
+ export { AccountBulkCreated, AccountSeeded, AccountingEngine, AccountingError, CATEGORIES, CATEGORY_KEYS, CURRENCIES, ConcurrencyError, DEFAULT_BUCKETS, DuplicateReferenceError, ENTRY_SOURCE_INDEX, 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 };
@@ -1,4 +1,4 @@
1
- import { d as JournalType, f as MainType, h as StatementType, i as CategoryKey, o as Currency, r as Category } from "./core-B90x0Abq.mjs";
1
+ import { d as JournalType, f as MainType, h as StatementType, i as CategoryKey, o as Currency, r as Category } from "./core-D-2qfAJF.mjs";
2
2
 
3
3
  //#region src/constants/categories.d.ts
4
4
  /** All valid categories */
@@ -1,2 +1,2 @@
1
- import { S as creditLimitPlugin, _ as FxRealizationPluginOptions, a as dailyLockPlugin, b as doubleEntryPlugin, c as periodResolver, d as LockAccountSelector, f as LockHit, g as idempotencyPlugin, h as IdempotencyPluginOptions, i as FiscalLockPluginOptions, l as createLockPlugin, m as LockResolverContext, n as watermarkResolver, o as fiscalLockPlugin, p as LockResolver, r as DailyLockPluginOptions, s as PeriodResolverOptions, t as WatermarkResolverOptions, u as CreateLockPluginOptions, v as fxRealizationPlugin, x as CreditLimitPluginOptions, y as DoubleEntryPluginOptions } from "../index-BFPFihTF.mjs";
1
+ import { S as creditLimitPlugin, _ as FxRealizationPluginOptions, a as dailyLockPlugin, b as doubleEntryPlugin, c as periodResolver, d as LockAccountSelector, f as LockHit, g as idempotencyPlugin, h as IdempotencyPluginOptions, i as FiscalLockPluginOptions, l as createLockPlugin, m as LockResolverContext, n as watermarkResolver, o as fiscalLockPlugin, p as LockResolver, r as DailyLockPluginOptions, s as PeriodResolverOptions, t as WatermarkResolverOptions, u as CreateLockPluginOptions, v as fxRealizationPlugin, x as CreditLimitPluginOptions, y as DoubleEntryPluginOptions } from "../index-DkM0S1kQ.mjs";
2
2
  export { type CreateLockPluginOptions, type CreditLimitPluginOptions, type DailyLockPluginOptions, type DoubleEntryPluginOptions, type FiscalLockPluginOptions, type FxRealizationPluginOptions, type IdempotencyPluginOptions, type LockAccountSelector, type LockHit, type LockResolver, type LockResolverContext, type PeriodResolverOptions, type WatermarkResolverOptions, createLockPlugin, creditLimitPlugin, dailyLockPlugin, doubleEntryPlugin, fiscalLockPlugin, fxRealizationPlugin, idempotencyPlugin, periodResolver, watermarkResolver };
@@ -1,2 +1,2 @@
1
- import { A as generateDimensionBreakdown, B as AgedBalanceOptions, C as FiscalReopenResult, D as DimensionBreakdownParams, E as DimensionBreakdownOptions, F as BudgetVsActualReport, G as DEFAULT_BUCKETS, H as AgedBalanceReport, I as BudgetVsActualRow, K as generateAgedBalance, L as generateBudgetVsActual, M as generateCashFlow, N as BudgetVsActualOptions, O as DimensionBreakdownReport, P as BudgetVsActualParams, R as BalanceSheetOptions, S as FiscalCloseResult, T as reopenFiscalPeriod, U as AgedBalanceRow, V as AgedBalanceParams, W as AgedBucketConfig, _ as IncomeStatementOptions, _t as DaybookOptions, a as RevaluationReport, b as generateGeneralLedger, bt as generateDaybook, f as PartnerLedgerLine, g as generatePartnerLedger, gt as DaybookLine, h as PartnerLedgerReport, i as RevaluationParams, j as CashFlowOptions, k as DimensionBreakdownRow, m as PartnerLedgerParams, n as generateTrialBalance, o as generateRevaluation, p as PartnerLedgerOptions, r as RevaluationOptions, t as TrialBalanceOptions, v as generateIncomeStatement, vt as DaybookParams, w as closeFiscalPeriod, x as FiscalCloseOptions, y as GeneralLedgerOptions, yt as DaybookReport, z as generateBalanceSheet } from "../trial-balance-Dy_fAeJV.mjs";
1
+ import { A as generateDimensionBreakdown, B as AgedBalanceOptions, C as FiscalReopenResult, D as DimensionBreakdownParams, E as DimensionBreakdownOptions, F as BudgetVsActualReport, G as DEFAULT_BUCKETS, H as AgedBalanceReport, I as BudgetVsActualRow, K as generateAgedBalance, L as generateBudgetVsActual, M as generateCashFlow, N as BudgetVsActualOptions, O as DimensionBreakdownReport, P as BudgetVsActualParams, R as BalanceSheetOptions, S as FiscalCloseResult, T as reopenFiscalPeriod, U as AgedBalanceRow, V as AgedBalanceParams, W as AgedBucketConfig, _ as IncomeStatementOptions, _t as DaybookOptions, a as RevaluationReport, b as generateGeneralLedger, bt as generateDaybook, f as PartnerLedgerLine, g as generatePartnerLedger, gt as DaybookLine, h as PartnerLedgerReport, i as RevaluationParams, j as CashFlowOptions, k as DimensionBreakdownRow, m as PartnerLedgerParams, n as generateTrialBalance, o as generateRevaluation, p as PartnerLedgerOptions, r as RevaluationOptions, t as TrialBalanceOptions, v as generateIncomeStatement, vt as DaybookParams, w as closeFiscalPeriod, x as FiscalCloseOptions, y as GeneralLedgerOptions, yt as DaybookReport, z as generateBalanceSheet } from "../trial-balance-BS7F7-Xe.mjs";
2
2
  export { type AgedBalanceOptions, type AgedBalanceParams, type AgedBalanceReport, type AgedBalanceRow, type AgedBucketConfig, type BalanceSheetOptions, type BudgetVsActualOptions, type BudgetVsActualParams, type BudgetVsActualReport, type BudgetVsActualRow, type CashFlowOptions, DEFAULT_BUCKETS, type DaybookLine, type DaybookOptions, type DaybookParams, type DaybookReport, type DimensionBreakdownOptions, type DimensionBreakdownParams, type DimensionBreakdownReport, type DimensionBreakdownRow, type FiscalCloseOptions, type FiscalCloseResult, type FiscalReopenResult, type GeneralLedgerOptions, type IncomeStatementOptions, type PartnerLedgerLine, type PartnerLedgerOptions, type PartnerLedgerParams, type PartnerLedgerReport, type RevaluationOptions, type RevaluationParams, type RevaluationReport, type TrialBalanceOptions, closeFiscalPeriod, generateAgedBalance, generateBalanceSheet, generateBudgetVsActual, generateCashFlow, generateDaybook, generateDimensionBreakdown, generateGeneralLedger, generateIncomeStatement, generatePartnerLedger, generateRevaluation, generateTrialBalance, reopenFiscalPeriod };
@@ -0,0 +1,74 @@
1
+ //#region src/bridges/source.bridge.d.ts
2
+ /**
3
+ * SourceBridge — host-implemented resolver for polymorphic external references.
4
+ *
5
+ * Ledger journal entries commonly carry a `reference`/`externalRef` pointing
6
+ * at a source document that lives outside the ledger package — an Invoice,
7
+ * Payment, Payroll Run, Stripe Charge, ERP voucher, etc. Storing these as
8
+ * opaque `String + sourceModel` (per PACKAGE_RULES §7) keeps the ledger
9
+ * transport-agnostic: the same schema works whether the source lives in the
10
+ * same Mongo, a different Mongo, Postgres, or an external REST API.
11
+ *
12
+ * Hosts implement `SourceBridge` to hydrate those refs when building
13
+ * enriched views (partner ledger with invoice details, audit trail with
14
+ * payment metadata, reconciliation UI with source documents, etc.).
15
+ *
16
+ * All methods are optional. Features that need resolution degrade gracefully
17
+ * when a bridge is not provided.
18
+ */
19
+ /**
20
+ * Polymorphic back-reference to a host source document — the universal
21
+ * "what produced this entry" pointer used by both the entry-level
22
+ * `JournalEntry.sourceRef` slot and the line-level
23
+ * `JournalEntry.journalItems[].sourceRef` slot.
24
+ *
25
+ * Required:
26
+ * - `sourceModel` — host model namespace (`"SourceDocument"`,
27
+ * `"BankStatement"`, `"Invoice"`, `"PayrollRun"`, …).
28
+ * - `sourceId` — opaque string identifier (ObjectId hex, ULID, or a
29
+ * human-readable number like `INV-2026-04-001`). String — not ObjectId —
30
+ * because the ledger has no knowledge of consumer model namespaces.
31
+ *
32
+ * Optional denormalization (0.13.0+):
33
+ * - `label` — human-readable name of the source (statement label,
34
+ * invoice number with party). Renders "From: <label>" in audit / drill-down
35
+ * UIs without a follow-up `SourceBridge.resolve()` call.
36
+ * - `kind` — sub-classifier of `sourceModel` (e.g. `"xero-invoice"`,
37
+ * `"qbo-bill"`, `"bank-statement"`). Lets the UI route to the correct
38
+ * detail page without re-querying the source.
39
+ *
40
+ * Both optional fields are denormalized — labels rarely change, and if they
41
+ * do, a one-shot `updateMany({ 'sourceRef.sourceId': id }, { $set: ... })`
42
+ * refreshes stale copies. The tradeoff buys the bookkeeping UI a fast path
43
+ * for "show me every JE produced by this document" without N+1 source-doc
44
+ * fetches.
45
+ */
46
+ interface SourceRef {
47
+ sourceId: string;
48
+ sourceModel: string;
49
+ label?: string | null;
50
+ kind?: string | null;
51
+ }
52
+ interface SourceBridgeContext {
53
+ organizationId?: unknown;
54
+ actorId?: unknown;
55
+ [key: string]: unknown;
56
+ }
57
+ interface SourceBridge {
58
+ /**
59
+ * Resolve a single external reference.
60
+ *
61
+ * Return `null` when the source cannot be found (deleted, permission
62
+ * denied, wrong model). Do not throw for missing sources — callers
63
+ * expect `null` for graceful degradation.
64
+ */
65
+ resolve?(sourceId: string, sourceModel: string, ctx: SourceBridgeContext): Promise<unknown | null>;
66
+ /**
67
+ * Batch resolver — avoids N+1 round-trips when enriching a list.
68
+ * Key the returned map by `sourceId`. Missing sources may be omitted or
69
+ * mapped to `null` at the implementer's discretion.
70
+ */
71
+ resolveMany?(refs: ReadonlyArray<SourceRef>, ctx: SourceBridgeContext): Promise<Map<string, unknown>>;
72
+ }
73
+ //#endregion
74
+ export { SourceBridgeContext as n, SourceRef as r, SourceBridge as t };
@@ -1,5 +1,5 @@
1
- import { c as DateRange } from "./core-B90x0Abq.mjs";
2
- import { t as CountryPack } from "./index-Cr57UKD-.mjs";
1
+ import { c as DateRange } from "./core-D-2qfAJF.mjs";
2
+ import { t as CountryPack } from "./index-DmRItmfZ.mjs";
3
3
  import { ClientSession, Model } from "mongoose";
4
4
 
5
5
  //#region src/reports/daybook.d.ts
package/docs/sync.md CHANGED
@@ -1,330 +1,84 @@
1
- # Sync — Invoice Bridge, Import & Export
1
+ # Sync — Removed in 0.11.0
2
2
 
3
- `@classytic/ledger/sync` is the integration subpath for connecting external systems to the ledger.
3
+ > **The `@classytic/ledger/sync` subpath was removed in `@classytic/ledger@0.11.0`.**
4
+ > Sync orchestration is now host responsibility. This doc page is preserved
5
+ > so links from older releases still resolve to a useful migration pointer
6
+ > instead of a 404.
4
7
 
5
- ```typescript
6
- import {
7
- // Invoice engine bridge (recommended)
8
- createLedgerBridge,
8
+ ## What used to live here
9
9
 
10
- // Import/export pipeline
11
- wireImport,
12
- wireExport,
10
+ The `/sync` subpath shipped with three concerns bundled together:
13
11
 
14
- // Mapper factories (fin-io canonical shapes → JournalEntry)
15
- bankStatementMapper,
16
- invoiceMapper,
17
- journalEntryMapper,
18
- openingBalanceMapper,
19
- } from '@classytic/ledger/sync';
20
- ```
21
-
22
- ---
23
-
24
- ## Invoice Engine Integration
25
-
26
- ### Recommended: `createLedgerBridge()`
27
-
28
- Wire `@classytic/invoice` to `@classytic/ledger` with one call. The bridge handles account mapping, tax lines, credit notes, payments, and reversals — no manual journal wiring needed.
29
-
30
- ```typescript
31
- import { createAccountingEngine } from '@classytic/ledger';
32
- import { createLedgerBridge } from '@classytic/ledger/sync';
33
- import { createInvoiceEngine } from '@classytic/invoice';
34
- import { canadaPack } from '@classytic/ledger-ca';
35
-
36
- // 1. Create the accounting engine
37
- const accounting = createAccountingEngine({
38
- mongoose: connection,
39
- country: canadaPack,
40
- currency: 'CAD',
41
- multiTenant: { orgField: 'organizationId', orgRef: 'Organization' },
42
- idempotency: true,
43
- });
44
-
45
- // 2. Create the bridge — map your chart of accounts once
46
- const bridge = createLedgerBridge(accounting, {
47
- accounts: {
48
- receivable: '1200', // Accounts Receivable
49
- payable: '2000', // Accounts Payable
50
- revenue: '4000', // Revenue
51
- expense: '5000', // Cost of Goods Sold / Expenses
52
- taxPayable: '2100', // HST/GST/VAT Payable
53
- taxReceivable: '1150', // HST/GST/VAT Receivable (Input Tax Credit)
54
- cash: '1000', // Cash / Bank
55
- },
56
- });
57
-
58
- // 3. Pass the bridge to the invoice engine — done
59
- const invoicing = createInvoiceEngine({
60
- mongoose: connection,
61
- ledger: bridge,
62
- // ... other invoice config
63
- });
64
- ```
65
-
66
- From this point, every invoice lifecycle operation automatically posts to the ledger:
67
-
68
- - **`invoicing.services.posting.post(id)`** → creates a balanced journal entry
69
- - **`invoicing.services.payment.recordPayment(input)`** → posts a payment entry (DR Cash, CR AR)
70
- - **`invoicing.services.posting.cancel(id, reason)`** → reverses the journal entry
71
- - **`invoicing.services.posting.void(id, reason)`** → reverses the journal entry (even if partially paid)
72
-
73
- ### How the bridge maps each move type
74
-
75
- | Invoice Move Type | Journal Lines | Journal Type |
76
- |---|---|---|
77
- | `out_invoice` (Customer Invoice) | DR Receivable (total), CR Revenue (per line), CR Tax Payable | `SALES` |
78
- | `in_invoice` (Vendor Bill) | DR Expense (per line), DR Tax Receivable, CR Payable (total) | `PURCHASES` |
79
- | `out_refund` (Customer Credit Note) | CR Receivable, DR Revenue (per line), DR Tax Payable | `SALES` |
80
- | `in_refund` (Vendor Credit Note) | DR Payable, CR Expense (per line), CR Tax Receivable | `PURCHASES` |
81
- | `receipt` (POS Receipt) | DR Receivable/Cash, CR Revenue (per line), CR Tax Payable | `CASH_RECEIPTS` |
82
-
83
- All amounts are integer cents. Tax lines are only added when `taxAmount > 0`.
84
-
85
- ### Payment recording
86
-
87
- When the invoice engine records a payment, the bridge calls `engine.record.payment()`:
88
-
89
- ```
90
- DR Cash (1000) $500.00
91
- CR Receivable (1200) $500.00
92
- ```
93
-
94
- An idempotency key is automatically derived from the payment ID (`payment:{paymentId}`), preventing duplicate journal entries on retry.
95
-
96
- ### Reversal
97
-
98
- When an invoice is cancelled or voided, the bridge calls `engine.repositories.journalEntries.reverse()`, which creates a mirror entry with debits and credits swapped and links both entries bidirectionally.
99
-
100
- ### Bridge configuration options
101
-
102
- #### `receiptAccount`
103
-
104
- Override the debit account for POS receipts. By default, receipts debit the `receivable` account. If your receipts are immediately paid (no A/R), point this at cash:
105
-
106
- ```typescript
107
- createLedgerBridge(accounting, {
108
- accounts: { ... },
109
- receiptAccount: '1000', // Receipts debit Cash directly
110
- });
111
- ```
112
-
113
- #### `resolvePaymentAccounts`
114
-
115
- Custom resolver for payment accounts. Use when you need to determine AR vs AP based on context (e.g., vendor bill payments should clear AP, not AR):
12
+ 1. **`createLedgerBridge(accounting, config)`** `@classytic/invoice` `LedgerBridge` adapter.
13
+ 2. **`wireImport` / `wireExport`** — generic batch importer/exporter pipeline.
14
+ 3. **Mapper factories** — `bankStatementMapper`, `invoiceMapper`, `journalEntryMapper`, `openingBalanceMapper` (fin-io canonical shapes → `JournalEntryInput`).
116
15
 
117
- ```typescript
118
- createLedgerBridge(accounting, {
119
- accounts: { ... },
120
- resolvePaymentAccounts: (input) => {
121
- const isVendor = vendorInvoiceIds.has(input.invoiceId);
122
- return {
123
- receivableOrPayable: isVendor ? '2000' : '1200',
124
- cash: '1000',
125
- };
126
- },
127
- });
128
- ```
129
-
130
- ### Double-entry guarantee
131
-
132
- The bridge uses `engine.record.adjustment()` internally. This routes through the ledger's double-entry plugin, which validates `sum(debits) === sum(credits)` before persisting. If the invoice engine sends unbalanced data, the ledger rejects it with a structured validation error.
133
-
134
- ---
135
-
136
- ### Alternative: Manual wiring (without `createLedgerBridge`)
16
+ All three were moved out of `@classytic/ledger` because:
137
17
 
138
- If you need full control over the journal entry shape — for example, to add dimension fields, use different accounts per line, or handle complex tax scenarios — you can implement the `LedgerBridge` interface yourself:
18
+ - They forced **`@classytic/fin-io`** as an optional peer on every consumer, even when no fin-io shape was ever read.
19
+ - The invoice-bridge adapter imported `@classytic/invoice` types — a cross-package import that violates [`PACKAGE_RULES` P1](../PACKAGE_RULES.md) (no inter-package imports beyond `mongokit` + `primitives`).
20
+ - Only one host ever consumed the subpath, so the cost wasn't worth keeping in a shared library.
139
21
 
140
- ```typescript
141
- import type { LedgerBridge } from '@classytic/ledger/sync';
22
+ ## What stayed (still on the main entry)
142
23
 
143
- const ledgerBridge: LedgerBridge = {
144
- async createJournalEntry(input) {
145
- // Use record.adjustment() for multi-line entries with tax
146
- const entry = await accounting.record.adjustment(input.organizationId, {
147
- date: input.date,
148
- label: `Invoice ${input.invoiceId}`,
149
- journalType: 'SALES',
150
- lines: [
151
- { account: '1200', debit: input.totalAmount },
152
- ...input.lines.map(line => ({
153
- account: '4000',
154
- credit: line.amount,
155
- label: line.description,
156
- })),
157
- ...(input.taxAmount > 0
158
- ? [{ account: '2100', credit: input.taxAmount, label: 'Tax' }]
159
- : []),
160
- ],
161
- }, {
162
- idempotencyKey: input.idempotencyKey,
163
- });
164
- return String((entry as any)._id);
165
- },
24
+ The pure ledger-side primitives that don't pull in fin-io OR invoice types are kept:
166
25
 
167
- async reverseJournalEntry(journalEntryId, reason) {
168
- const { reversal } = await accounting.repositories.journalEntries
169
- .reverse(journalEntryId);
170
- return String((reversal as any)._id);
171
- },
26
+ - `buildOpeningBalanceEntry(input)` — pure helper, re-exported from `@classytic/ledger`.
27
+ - `OpeningBalanceInput`, `OpeningBalanceResult` re-exported from `@classytic/ledger`.
28
+ - `JournalEntryInput`, `JournalItemInput` — describe the `journalEntries.create()` input; inherent to the engine, not a sync concern.
172
29
 
173
- async recordPayment(input) {
174
- const entry = await accounting.record.payment(input.organizationId, {
175
- date: input.date,
176
- amount: input.amount,
177
- fromReceivableAccount: '1200',
178
- toCashAccount: '1000',
179
- label: `Payment ${input.paymentId} for ${input.invoiceId}`,
180
- }, {
181
- idempotencyKey: `payment:${input.paymentId}`,
182
- });
183
- return String((entry as any)._id);
184
- },
185
- };
186
-
187
- // Then pass to the invoice engine
188
- const invoicing = createInvoiceEngine({
189
- mongoose: connection,
190
- ledger: ledgerBridge,
191
- });
30
+ ```ts
31
+ import { buildOpeningBalanceEntry } from '@classytic/ledger';
32
+ import type { JournalEntryInput, JournalItemInput } from '@classytic/ledger';
192
33
  ```
193
34
 
194
- This gives you the same integration but with full control over account resolution, dimension fields, and tax line construction.
195
-
196
- ### Using `LedgerBridge` without `@classytic/invoice`
35
+ ## How to wire an invoice engine today
197
36
 
198
- The bridge types are generic — any invoicing system that calls these 3 methods works:
37
+ Pick one of two paths:
199
38
 
200
- ```typescript
201
- import type { LedgerBridge, LedgerPostInput, LedgerPaymentInput } from '@classytic/ledger/sync';
39
+ ### Option A — Copy the canonical reference implementation
202
40
 
203
- // Use createLedgerBridge() for standard mapping
204
- const bridge: LedgerBridge = createLedgerBridge(accounting, { accounts: { ... } });
41
+ `fajr-be-arc` carries the production-grade implementation at
42
+ `src/shared/ledger-sync/`. The relevant files are:
205
43
 
206
- // Post an invoice
207
- const jeId = await bridge.createJournalEntry({
208
- organizationId: 'org_1',
209
- invoiceId: 'INV-001',
210
- moveType: 'out_invoice',
211
- partnerId: 'customer-123',
212
- date: new Date(),
213
- currency: 'USD',
214
- lines: [
215
- { description: 'Consulting', amount: 100000, taxAmount: 13000, taxCode: 'HST' },
216
- ],
217
- totalAmount: 113000,
218
- taxAmount: 13000,
219
- });
44
+ - `ledger-bridge.ts` implements `@classytic/invoice`'s `LedgerBridge`
45
+ contract on top of an `AccountingEngine`. Includes multi-tenant scope,
46
+ multi-currency, withholding tax, refunds, and an opt-in
47
+ `resolvePaymentAccounts({ debit, credit })` callback for AR/AP-aware
48
+ hosts (clears AR-shape for `out_invoice`, AP-shape for `in_invoice`).
220
49
 
221
- // Record a payment
222
- await bridge.recordPayment({
223
- organizationId: 'org_1',
224
- invoiceId: 'INV-001',
225
- paymentId: 'PAY-001',
226
- amount: 113000,
227
- currency: 'USD',
228
- date: new Date(),
229
- method: 'bank_transfer',
230
- });
50
+ Drop the files into your repo, adjust the import paths, done. The
51
+ implementation is < 600 lines total and stable.
231
52
 
232
- // Reverse (cancel/void)
233
- await bridge.reverseJournalEntry(jeId, 'Invoice cancelled');
234
- ```
235
-
236
- ---
237
-
238
- ## Bank Statement Import
53
+ ### Option B — Implement `LedgerBridge` yourself
239
54
 
240
- Import bank transactions from any format supported by `@classytic/fin-io`:
55
+ Implement the three-method interface against your accounting engine
56
+ directly. The contract lives in `@classytic/invoice/dist/domain/contracts/ledger-bridge.d.ts`:
241
57
 
242
- ```typescript
243
- import { parseOfx } from '@classytic/fin-io/ofx';
244
- import { wireImport, bankStatementMapper } from '@classytic/ledger/sync';
245
-
246
- const parsed = parseOfx(buffer);
247
- if (!parsed.ok) throw new Error(parsed.error);
248
-
249
- const report = await wireImport({
250
- source: parsed.data.flatMap(s => s.transactions),
251
- mapper: bankStatementMapper({
252
- bankAccountId: bankAccount._id,
253
- suspenseAccountId: suspenseAccount._id,
254
- categorize: (txn) => knownVendors[txn.counterparty?.name]?.accountId,
255
- }),
256
- journalEntries: engine.repositories.journalEntries,
257
- context: { organizationId },
258
- }).run();
259
-
260
- console.log(`Imported ${report.inserted}, skipped ${report.skipped} duplicates`);
58
+ ```ts
59
+ export interface LedgerBridge {
60
+ createJournalEntry(input: LedgerPostInput): Promise<string>;
61
+ reverseJournalEntry(jeId: string, reason: string, ctx: LedgerReverseContext): Promise<string>;
62
+ recordPayment(input: LedgerPaymentInput): Promise<string>;
63
+ }
261
64
  ```
262
65
 
263
- ### Available mappers
66
+ A bare-minimum implementation is ~80 lines (one tax line, no withholding,
67
+ no FX). Useful when your COA is small and stable enough that the
68
+ `createLedgerBridge` config map's flexibility is overkill.
264
69
 
265
- | Mapper | Source | Output |
266
- |---|---|---|
267
- | `bankStatementMapper` | `CanonicalTransaction` (OFX, CAMT, MT940, CSV, Plaid) | 2-line JE: Cash ↔ Suspense |
268
- | `invoiceMapper` | `CanonicalInvoice` (QBO, Xero JSON) | Multi-line JE: AR/AP ↔ Revenue/Expense ↔ Tax |
269
- | `journalEntryMapper` | `CanonicalJournalEntry` (QBO, Xero manual journals) | 1:1 mapping |
270
- | `openingBalanceMapper` | `TrialBalanceInput` | Multi-line opening balance entry |
70
+ ## How to wire fin-io imports today
271
71
 
272
- ### Idempotency
72
+ The mapper helpers (`bankStatementMapper`, `invoiceMapper`, etc.) and the
73
+ `wireImport`/`wireExport` pipeline moved to host code along with the
74
+ bridge. The canonical implementation lives in `fajr-be-arc` under
75
+ `src/workflows/ai-bank-import.workflow.ts` (for AI-extracted bank
76
+ statements) and `src/resources/integrations/` (for QBO / Xero sync).
273
77
 
274
- Re-running an import on the same file produces zero duplicates. Each mapper extracts a stable `externalId` from the source record (e.g., OFX `FITID`, CAMT `NtryRef`). The `wireImport` pipeline checks for existing entries before creating.
275
-
276
- For best performance, provide a `findExisting` callback and add a partial index on `{ organizationId: 1, _externalId: 1 }`.
277
-
278
- ---
279
-
280
- ## Export
281
-
282
- Stream ledger data to external formats:
283
-
284
- ```typescript
285
- import { wireExport } from '@classytic/ledger/sync';
286
-
287
- const report = await wireExport({
288
- query: { organizationId: 'org_1', state: 'posted' },
289
- sink: {
290
- fromJournalEntry: (entry) => transformToCSVRow(entry),
291
- emit: async (rows) => csvStream.write(rows),
292
- flush: async () => csvStream.end(),
293
- },
294
- journalEntries: engine.repositories.journalEntries,
295
- options: { batchSize: 500 },
296
- }).run();
297
- ```
78
+ `@classytic/fin-io` is a normal direct dependency in fajr not a peer
79
+ of ledger. Hosts that don't need fin-io shapes don't pull it in.
298
80
 
299
81
  ---
300
82
 
301
- ## Writing Custom Mappers
302
-
303
- Implement `ImportMapper<TRaw>` for any data source:
304
-
305
- ```typescript
306
- import type { ImportMapper } from '@classytic/ledger/sync';
307
-
308
- interface MyPayrollRecord {
309
- id: string;
310
- employeeName: string;
311
- grossPay: number;
312
- taxWithheld: number;
313
- netPay: number;
314
- date: Date;
315
- }
316
-
317
- const payrollMapper: ImportMapper<MyPayrollRecord> = {
318
- externalId: (record) => `payroll:${record.id}`,
319
-
320
- toJournalEntry: (record, ctx) => ({
321
- date: record.date,
322
- label: `Payroll — ${record.employeeName}`,
323
- journalItems: [
324
- { account: salaryExpenseId, debit: record.grossPay, credit: 0 },
325
- { account: taxPayableId, debit: 0, credit: record.taxWithheld },
326
- { account: cashId, debit: 0, credit: record.netPay },
327
- ],
328
- }),
329
- };
330
- ```
83
+ For the architectural rationale + full migration list, see
84
+ [`CHANGELOG.md` → 0.11.0](../CHANGELOG.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/ledger",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "Production-grade double-entry accounting engine for MongoDB — schemas, reports, tax, multi-tenant",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -88,23 +88,23 @@
88
88
  "url": "git+https://github.com/classytic/ledger.git"
89
89
  },
90
90
  "peerDependencies": {
91
- "@classytic/mongokit": ">=3.13.0",
92
- "@classytic/primitives": ">=0.5.0",
93
- "@classytic/repo-core": ">=0.4.0",
94
- "mongoose": ">=9.4.1",
91
+ "@classytic/mongokit": ">=3.14.0",
92
+ "@classytic/primitives": ">=0.6.0",
93
+ "@classytic/repo-core": ">=0.5.0",
94
+ "mongoose": ">=9.6.2",
95
95
  "zod": ">=4.0.0"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@biomejs/biome": "^2.4.12",
99
99
  "@classytic/dev-tools": "^0.2.0",
100
- "@classytic/mongokit": ">=3.13.0",
101
- "@classytic/primitives": ">=0.5.0",
102
- "@classytic/repo-core": ">=0.4.0",
100
+ "@classytic/mongokit": "^3.14.0",
101
+ "@classytic/primitives": "^0.6.0",
102
+ "@classytic/repo-core": "^0.5.0",
103
103
  "@types/node": "^25.5.0",
104
104
  "@vitest/coverage-v8": "^4.1.4",
105
105
  "knip": "^6.4.1",
106
106
  "mongodb-memory-server": "^11.0.1",
107
- "mongoose": "^9.4.1",
107
+ "mongoose": "^9.6.2",
108
108
  "tsdown": "^0.21.8",
109
109
  "typescript": "^5.7.0",
110
110
  "vitest": "^4.1.4",