@classytic/ledger 0.5.0 → 0.6.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.
@@ -0,0 +1,513 @@
1
+ import { ClientSession, Model } from "mongoose";
2
+ import * as _classytic_mongokit0 from "@classytic/mongokit";
3
+ import { Repository, RepositoryContext, RepositoryInstance } from "@classytic/mongokit";
4
+
5
+ //#region src/types/repositories.d.ts
6
+ interface PostOptions {
7
+ session?: ClientSession | null;
8
+ /** Actor performing this operation (required when strictness.requireActor is enabled) */
9
+ actorId?: unknown;
10
+ }
11
+ interface ReverseOptions extends PostOptions {
12
+ /** Date for the reversal entry (defaults to now) */
13
+ reversalDate?: Date;
14
+ }
15
+ interface SeedOptions {
16
+ session?: ClientSession | null;
17
+ }
18
+ interface SeedResult {
19
+ created: number;
20
+ skipped: number;
21
+ }
22
+ interface BulkCreateInput {
23
+ accountTypeCode?: string;
24
+ accountNumber?: string;
25
+ name?: string;
26
+ active?: boolean;
27
+ isCashAccount?: boolean;
28
+ }
29
+ /**
30
+ * Generic over the account document type so consumers get real
31
+ * IntelliSense on `created[0].accountNumber` instead of `unknown`.
32
+ * Defaults to `Record<string, unknown>` for backwards-compatible call sites.
33
+ */
34
+ interface BulkCreateResult<TAccount = Record<string, unknown>> {
35
+ summary: {
36
+ total: number;
37
+ created: number;
38
+ skipped: number;
39
+ errors: number;
40
+ };
41
+ created: TAccount[];
42
+ skipped: TAccount[];
43
+ errors: TAccount[];
44
+ }
45
+ /**
46
+ * Generic over the journal-entry document type — `{ original, reversal }`
47
+ * are the actual persisted shapes, so callers can access `._id`, `.state`,
48
+ * `.reversed`, etc. without casts.
49
+ */
50
+ interface ReverseResult<TEntry = Record<string, unknown>> {
51
+ original: TEntry;
52
+ reversal: TEntry;
53
+ }
54
+ /**
55
+ * Journal Entry Repository — extends mongokit Repository with accounting domain methods.
56
+ *
57
+ * Inherits ALL Repository<TDoc> methods: create, getById, getAll, update,
58
+ * delete, count, exists, distinct, aggregate, withTransaction, etc.
59
+ */
60
+ interface JournalEntryRepository<TDoc = Record<string, unknown>> extends Repository<TDoc> {
61
+ /** Post an entry (draft → posted). Validates items, balance, and accounts. */
62
+ post(id: unknown, orgId?: unknown, options?: PostOptions): Promise<TDoc>;
63
+ /** Unpost an entry (posted → draft). Resets state for re-editing. */
64
+ unpost(id: unknown, orgId?: unknown, options?: PostOptions): Promise<TDoc>;
65
+ /** Archive a draft entry (draft → archived). Preserves audit trail. */
66
+ archive(id: unknown, orgId?: unknown, options?: PostOptions): Promise<TDoc>;
67
+ /** Duplicate an entry as a new draft. Copies items, type, and label. */
68
+ duplicate(id: unknown, orgId?: unknown, options?: PostOptions): Promise<TDoc>;
69
+ /** Reverse a posted entry. Creates mirror entry with flipped debits/credits. */
70
+ reverse(id: unknown, orgId?: unknown, options?: ReverseOptions): Promise<ReverseResult<TDoc>>;
71
+ }
72
+ /**
73
+ * Account Repository — extends mongokit Repository with seed and bulk operations.
74
+ */
75
+ interface AccountRepository<TDoc = Record<string, unknown>> extends Repository<TDoc> {
76
+ /** Seed standard posting accounts for an org from the country pack. */
77
+ seedAccounts(orgId: unknown, options?: SeedOptions): Promise<SeedResult>;
78
+ /** Bulk create accounts with validation and skip-if-exists logic. */
79
+ bulkCreate(accounts: BulkCreateInput[], orgId: unknown): Promise<BulkCreateResult<TDoc>>;
80
+ }
81
+ /**
82
+ * Reference to a specific item inside a journal entry. Positional index
83
+ * because journal items are embedded sub-documents.
84
+ */
85
+ interface JournalItemRef {
86
+ /** Journal entry id. */
87
+ entry: unknown;
88
+ /** Zero-based index into the entry's `journalItems` array. */
89
+ itemIndex: number;
90
+ }
91
+ interface MatchInput {
92
+ /** The account whose items are being matched (sanity checked). */
93
+ account: unknown;
94
+ /** Two or more items to match. Must share `account`. */
95
+ items: JournalItemRef[];
96
+ /**
97
+ * Optional caller-provided matching number. When omitted, the repository
98
+ * generates one via its counter. Must be unique per org.
99
+ */
100
+ matchingNumber?: string;
101
+ note?: string;
102
+ reconciledBy?: string;
103
+ organizationId?: unknown;
104
+ session?: ClientSession | null;
105
+ }
106
+ interface OpenItem {
107
+ entry: unknown;
108
+ itemIndex: number;
109
+ debit: number;
110
+ credit: number;
111
+ date?: Date;
112
+ account: unknown;
113
+ [key: string]: unknown;
114
+ }
115
+ interface ReconciliationRepository<TDoc = Record<string, unknown>> extends Repository<TDoc> {
116
+ /**
117
+ * Match two or more journal items against each other. Stamps a
118
+ * shared `matchingNumber` onto every referenced item and creates a
119
+ * reconciliation document. When debit/credit totals balance, the
120
+ * reconciliation is flagged `isFullReconcile: true`.
121
+ *
122
+ * Fires `after:match` hook — the fxRealizationPlugin listens here.
123
+ */
124
+ match(input: MatchInput): Promise<TDoc>;
125
+ /**
126
+ * Unwind a matching group. Clears `matchingNumber` on every referenced
127
+ * item and deletes the reconciliation document. The FX realization
128
+ * entry (if any) is reversed via `journalEntries.reverse`.
129
+ */
130
+ unmatch(input: {
131
+ matchingNumber: string;
132
+ organizationId?: unknown;
133
+ session?: ClientSession | null;
134
+ }): Promise<{
135
+ success: boolean;
136
+ }>;
137
+ /**
138
+ * Find all posted journal items referencing the given account that
139
+ * do NOT yet have a matching number. Returns lean items with
140
+ * `{ entry, itemIndex, debit, credit, date }` plus any extra fields
141
+ * (dimension, maturityDate, currency).
142
+ *
143
+ * `filter` lets you narrow further by any journal-item field —
144
+ * commonly used to scope by `partnerId` for supplier/customer
145
+ * subsidiary ledger queries:
146
+ *
147
+ * `getOpenItems({ accountId: apControlId, filter: { partnerId: 'sup-1' } })`
148
+ *
149
+ * `asOfDate` restricts to items posted on or before that date,
150
+ * giving historical open-item snapshots — useful for aged-balance
151
+ * reports run as of a previous month-end.
152
+ */
153
+ getOpenItems(params: {
154
+ accountId: unknown;
155
+ organizationId?: unknown; /** Extra equality filters on the journal item (e.g. `{ partnerId: 'X' }`). */
156
+ filter?: Record<string, unknown>; /** Only consider items from entries dated on or before this date. */
157
+ asOfDate?: Date;
158
+ limit?: number;
159
+ skip?: number;
160
+ }): Promise<OpenItem[]>;
161
+ }
162
+ interface JournalRepository<TDoc = Record<string, unknown>> extends Repository<TDoc> {
163
+ /**
164
+ * Seed the organization's default journals from the country pack's
165
+ * `journalTemplates`. Idempotent — skips journals that already exist.
166
+ */
167
+ seedDefaults(orgId: unknown): Promise<SeedResult>;
168
+ /**
169
+ * Atomically increment the journal's sequence counter and return a
170
+ * formatted reference number (e.g. `'INV/2026/03/0042'`). Safe under
171
+ * concurrent posts — uses `$inc` inside `findOneAndUpdate`.
172
+ */
173
+ nextSequenceNumber(journalId: unknown, orgId?: unknown): Promise<string>;
174
+ }
175
+ //#endregion
176
+ //#region src/plugins/credit-limit.plugin.d.ts
177
+ interface CreditLimitPluginOptions {
178
+ /** The A/R control account that this limit guards. */
179
+ arControlAccountId: unknown;
180
+ /**
181
+ * Resolve the partner's credit limit in cents. Return `null` for
182
+ * "no limit" (skip the check), `0` for "cash-only customer".
183
+ */
184
+ getCreditLimit: (partnerId: unknown, session: ClientSession | null) => Promise<number | null> | number | null;
185
+ /** Field name for the partner ref on each journal item. Default: `'partnerId'`. */
186
+ partnerField?: string;
187
+ /** JournalEntry model — required to sum existing exposure. */
188
+ JournalEntryModel: Model<unknown>;
189
+ /** Multi-tenant scope field. */
190
+ orgField?: string;
191
+ /**
192
+ * Optional tolerance in cents — allow up to N cents over the limit.
193
+ * Defaults to 0 (strict).
194
+ */
195
+ toleranceCents?: number;
196
+ }
197
+ declare function creditLimitPlugin(options: CreditLimitPluginOptions): {
198
+ name: string;
199
+ apply(repo: RepositoryInstance): void;
200
+ };
201
+ //#endregion
202
+ //#region src/types/mongokit-augmentation.d.ts
203
+ /**
204
+ * Module augmentation for `@classytic/mongokit`.
205
+ *
206
+ * Ledger's state-transition methods (post, unpost, archive, reverseMark) tag
207
+ * their `repository.update()` call with a `_ledgerInternal` flag so the
208
+ * double-entry immutability guard can distinguish legitimate transitions from
209
+ * arbitrary edits. This file types that flag onto both `RepositoryContext`
210
+ * (what plugins observe) and `SessionOptions` (what callers pass) so consumers
211
+ * and plugin authors get full IntelliSense without casts.
212
+ *
213
+ * This file is side-effect only — importing it anywhere in the package is
214
+ * enough to activate the augmentation. `src/types/index.ts` re-exports it.
215
+ */
216
+ type LedgerInternalOp = 'post' | 'unpost' | 'archive' | 'reverseMark' | 'fxRealize' | 'cashBasisRealize';
217
+ declare module '@classytic/mongokit' {
218
+ interface RepositoryContext {
219
+ /**
220
+ * Set by ledger's repository methods (post, unpost, archive, reverseMark)
221
+ * to signal a legitimate internal state transition. Plugins observing
222
+ * `before:update` can read this to distinguish from arbitrary edits.
223
+ *
224
+ * External `repository.update()` callers cannot spoof this flag because
225
+ * it is only set by ledger's own repo methods, never surfaced in the
226
+ * public API.
227
+ */
228
+ _ledgerInternal?: LedgerInternalOp;
229
+ }
230
+ interface SessionOptions {
231
+ /**
232
+ * Ledger-internal flag — see `RepositoryContext._ledgerInternal`.
233
+ * Typed here so ledger's repo methods can pass it without casts.
234
+ * Consumers should never set this directly.
235
+ */
236
+ _ledgerInternal?: LedgerInternalOp;
237
+ }
238
+ }
239
+ //#endregion
240
+ //#region src/plugins/double-entry.plugin.d.ts
241
+ interface DoubleEntryPluginOptions {
242
+ /** Only enforce on posted entries (default: true) */
243
+ onlyOnPost?: boolean;
244
+ /** Mongoose model — required to validate partial updates that only set state */
245
+ JournalEntryModel?: Model<unknown>;
246
+ /** Account model — when provided, posted creates verify account existence + tenant scoping */
247
+ AccountModel?: Model<unknown>;
248
+ /** Multi-tenant org field name (e.g. 'business'). Required for tenant-account integrity checks. */
249
+ orgField?: string;
250
+ }
251
+ declare function doubleEntryPlugin(options?: DoubleEntryPluginOptions): {
252
+ name: string;
253
+ apply(repo: RepositoryInstance): void;
254
+ };
255
+ //#endregion
256
+ //#region src/plugins/fx-realization.plugin.d.ts
257
+ interface FxRealizationPluginOptions {
258
+ /** Repository used to create the balancing FX entry. */
259
+ journalEntries: JournalEntryRepository<Record<string, unknown>>;
260
+ /** Account id for realized FX gains (income). */
261
+ realizedGainAccount: unknown;
262
+ /** Account id for realized FX losses (expense). */
263
+ realizedLossAccount: unknown;
264
+ /** Base/functional currency — FX is computed relative to this. */
265
+ baseCurrency: string;
266
+ /** Multi-tenant org field. */
267
+ orgField?: string;
268
+ }
269
+ declare function fxRealizationPlugin(options: FxRealizationPluginOptions): {
270
+ name: string;
271
+ apply(repo: RepositoryInstance): void;
272
+ };
273
+ //#endregion
274
+ //#region src/plugins/idempotency.plugin.d.ts
275
+ interface IdempotencyPluginOptions {
276
+ /** Mongoose model for journal entries */
277
+ JournalEntryModel: Model<unknown>;
278
+ /** Multi-tenant org field name */
279
+ orgField?: string;
280
+ }
281
+ declare function idempotencyPlugin(options: IdempotencyPluginOptions): {
282
+ name: string;
283
+ apply(repo: RepositoryInstance): void;
284
+ };
285
+ //#endregion
286
+ //#region src/plugins/lock/types.d.ts
287
+ /**
288
+ * The resolved "who did what, when" for a locked slice of ledger.
289
+ * Returned by a `LockResolver` to describe why an entry is blocked.
290
+ */
291
+ interface LockHit {
292
+ /** Scope that this hit belongs to — e.g. `'fiscal'`, `'tax'`. */
293
+ readonly scope: string;
294
+ /** Human-readable label used in error messages (e.g. `'Q1 2025'`). */
295
+ readonly label: string;
296
+ /** Optional sub-type inside the scope (e.g. `'VAT'`, `'TDS'`, `'GST'`). */
297
+ readonly subType?: string;
298
+ /** Optional external reference (filed return number, statement ID, …). */
299
+ readonly externalRef?: string;
300
+ }
301
+ /**
302
+ * Context given to a lock resolver. All fields except `entryDate` are
303
+ * pass-throughs from the `createLockPlugin` boilerplate — resolvers don't
304
+ * need to worry about where the date came from (payload vs persisted doc).
305
+ */
306
+ interface LockResolverContext {
307
+ /** The effective date the entry will be posted on. */
308
+ readonly entryDate: Date;
309
+ /** The resolved multi-tenant scope value, or `undefined` if unscoped. */
310
+ readonly orgValue: unknown;
311
+ /** The mongoose session if the operation is running inside a transaction. */
312
+ readonly session: ClientSession | null;
313
+ /** The raw entry payload (for resolvers that need extra context). */
314
+ readonly data: Record<string, unknown>;
315
+ /** The full upstream repository context (advanced use). */
316
+ readonly repositoryContext: RepositoryContext;
317
+ }
318
+ /**
319
+ * Resolver signature. Return `null` if the entry is allowed, or a
320
+ * `LockHit` to block it with a 409 `PERIOD_LOCKED_{SCOPE}` error.
321
+ */
322
+ type LockResolver = (ctx: LockResolverContext) => Promise<LockHit | null>;
323
+ /**
324
+ * Predicate used to narrow a lock to specific accounts. Called per
325
+ * journal item; if *any* item's account matches, the entry is subject
326
+ * to the lock. Receives the populated account document (lean).
327
+ */
328
+ type LockAccountSelector = (account: Record<string, unknown>) => boolean;
329
+ /**
330
+ * Options accepted by `createLockPlugin`. All fields are type-safe —
331
+ * the factory handles date/org resolution, session propagation, and
332
+ * the `_ledgerInternal` skip path automatically.
333
+ */
334
+ interface CreateLockPluginOptions {
335
+ /**
336
+ * Scope identifier — short, lowercase, stable. Used in the plugin name
337
+ * (`accounting:lock:{scope}`) and the error code
338
+ * (`PERIOD_LOCKED_{SCOPE}`).
339
+ */
340
+ scope: string;
341
+ /**
342
+ * Resolver that decides whether a given entry is locked. Call one of
343
+ * the builtin resolvers (`periodResolver`, `watermarkResolver`) or
344
+ * implement your own.
345
+ */
346
+ resolve: LockResolver;
347
+ /**
348
+ * Optional account-level narrowing. When provided, the lock only
349
+ * fires if at least one of the entry's journal items touches an
350
+ * account that matches the predicate.
351
+ *
352
+ * When this is set, the plugin must be able to load account docs, so
353
+ * `AccountModel` becomes required.
354
+ */
355
+ accountSelector?: LockAccountSelector;
356
+ /** Required when `accountSelector` is set — used to hydrate accounts. */
357
+ AccountModel?: Model<unknown>;
358
+ /**
359
+ * Journal-entry model — required for partial updates (we need to look
360
+ * up the persisted `date` and `orgField` when the payload omits them).
361
+ */
362
+ JournalEntryModel?: Model<unknown>;
363
+ /** Multi-tenant scope field name (e.g. `'organizationId'`). */
364
+ orgField?: string;
365
+ }
366
+ //#endregion
367
+ //#region src/plugins/lock/create-lock-plugin.d.ts
368
+ declare function createLockPlugin(options: CreateLockPluginOptions): {
369
+ name: string;
370
+ apply(repo: RepositoryInstance): void;
371
+ };
372
+ //#endregion
373
+ //#region src/plugins/lock/period-resolver.d.ts
374
+ interface PeriodResolverOptions {
375
+ /** Scope identifier, passed through to the resulting `LockHit`. */
376
+ scope: string;
377
+ /** Mongoose model holding closed-period rows. */
378
+ PeriodModel: Model<unknown>;
379
+ /** Field name for the start of the window. Default: `'startDate'`. */
380
+ startField?: string;
381
+ /** Field name for the end of the window. Default: `'endDate'`. */
382
+ endField?: string;
383
+ /**
384
+ * Field name that indicates "this window is closed". Default:
385
+ * `'closed'` (matches FiscalPeriod model).
386
+ */
387
+ closedField?: string;
388
+ /** Value of the closed field that counts as closed. Default: `true`. */
389
+ closedValue?: unknown;
390
+ /** Field name to use as the display label in errors. Default: `'name'`. */
391
+ labelField?: string;
392
+ /** Field name to surface as `LockHit.subType`. Default: undefined. */
393
+ subTypeField?: string;
394
+ /** Field name to surface as `LockHit.externalRef`. Default: undefined. */
395
+ externalRefField?: string;
396
+ /** Multi-tenant scope field on the period doc. */
397
+ orgField?: string;
398
+ /**
399
+ * Optional additional query fragment merged into the lookup. Receives
400
+ * the resolver context so callers can derive dynamic filters from the
401
+ * entry payload (e.g. pick `taxType` from `data`).
402
+ */
403
+ extraQuery?: (ctx: LockResolverContext) => Record<string, unknown> | undefined;
404
+ }
405
+ declare function periodResolver(options: PeriodResolverOptions): LockResolver;
406
+ //#endregion
407
+ //#region src/plugins/lock/presets.d.ts
408
+ interface FiscalLockPluginOptions {
409
+ FiscalPeriodModel: Model<unknown>;
410
+ JournalEntryModel?: Model<unknown>;
411
+ orgField?: string;
412
+ }
413
+ declare function fiscalLockPlugin(options: FiscalLockPluginOptions): {
414
+ name: string;
415
+ apply(repo: _classytic_mongokit0.RepositoryInstance): void;
416
+ };
417
+ interface TaxLockPluginOptions {
418
+ /**
419
+ * Tax-period model. Expected shape (fields are overridable via the
420
+ * lower-level `createLockPlugin` if your schema differs):
421
+ *
422
+ * {
423
+ * periodStart: Date,
424
+ * periodEnd: Date,
425
+ * status: 'open' | 'filed' | 'amended',
426
+ * jurisdiction?: string,
427
+ * taxType?: string,
428
+ * returnRef?: string,
429
+ * [orgField]?: unknown,
430
+ * }
431
+ *
432
+ * `status !== 'open'` counts as locked.
433
+ */
434
+ TaxPeriodModel: Model<unknown>;
435
+ AccountModel: Model<unknown>;
436
+ JournalEntryModel?: Model<unknown>;
437
+ orgField?: string;
438
+ /**
439
+ * Predicate deciding which accounts participate in tax returns.
440
+ * Defaults to `acc => acc.taxMetadata != null` — matches the
441
+ * convention used by country packs that populate `taxMetadata` on
442
+ * tax-payable / tax-recoverable account types.
443
+ */
444
+ isTaxAffecting?: LockAccountSelector;
445
+ /**
446
+ * Derive `{ jurisdiction, taxType }` from the entry payload so each
447
+ * post can look up the right row. Default: use `data.jurisdiction`
448
+ * and `data.taxType` directly when present.
449
+ */
450
+ deriveFilter?: (data: Record<string, unknown>) => {
451
+ jurisdiction?: string;
452
+ taxType?: string;
453
+ } | undefined;
454
+ }
455
+ declare function taxLockPlugin(options: TaxLockPluginOptions): {
456
+ name: string;
457
+ apply(repo: _classytic_mongokit0.RepositoryInstance): void;
458
+ };
459
+ interface DailyLockPluginOptions {
460
+ /**
461
+ * Return the `lastClosedDate` for the given org/branch — everything
462
+ * on or before this date is frozen. Return `null` if the branch
463
+ * has never been closed.
464
+ */
465
+ getLastClosedDate: (orgValue: unknown, session: ClientSession | null) => Promise<Date | null> | Date | null;
466
+ JournalEntryModel?: Model<unknown>;
467
+ orgField?: string;
468
+ }
469
+ declare function dailyLockPlugin(options: DailyLockPluginOptions): {
470
+ name: string;
471
+ apply(repo: _classytic_mongokit0.RepositoryInstance): void;
472
+ };
473
+ //#endregion
474
+ //#region src/plugins/lock/watermark-resolver.d.ts
475
+ interface WatermarkResolverOptions {
476
+ /** Scope identifier, passed through to the resulting `LockHit`. */
477
+ scope: string;
478
+ /**
479
+ * Resolve the current watermark for the given org. Return `null` to
480
+ * mean "no lock in place". The session is forwarded so the lookup
481
+ * can participate in the caller's transaction.
482
+ */
483
+ getWatermark: (orgValue: unknown, session: ClientSession | null) => Promise<Date | null> | Date | null;
484
+ /**
485
+ * Optional label override. Defaults to `"through {ISO date}"`.
486
+ */
487
+ formatLabel?: (watermark: Date, ctx: LockResolverContext) => string;
488
+ }
489
+ declare function watermarkResolver(options: WatermarkResolverOptions): LockResolver;
490
+ //#endregion
491
+ //#region src/utils/tax-hooks.d.ts
492
+ interface TaxLineInput {
493
+ account: unknown;
494
+ amount: number;
495
+ side: 'debit' | 'credit';
496
+ taxCode?: string;
497
+ extraFields?: Record<string, unknown>;
498
+ }
499
+ interface GeneratedTaxLine {
500
+ account: unknown;
501
+ debit: number;
502
+ credit: number;
503
+ label?: string;
504
+ taxDetails?: Array<{
505
+ taxCode: string;
506
+ taxName?: string;
507
+ }>;
508
+ }
509
+ interface TaxLineGenerator {
510
+ generateTaxLines(input: TaxLineInput): GeneratedTaxLine[];
511
+ }
512
+ //#endregion
513
+ export { JournalEntryRepository as A, SeedResult as B, DoubleEntryPluginOptions as C, AccountRepository as D, creditLimitPlugin as E, PostOptions as F, ReconciliationRepository as I, ReverseOptions as L, JournalRepository as M, MatchInput as N, BulkCreateInput as O, OpenItem as P, ReverseResult as R, fxRealizationPlugin as S, CreditLimitPluginOptions as T, LockResolver as _, DailyLockPluginOptions as a, idempotencyPlugin as b, dailyLockPlugin as c, PeriodResolverOptions as d, periodResolver as f, LockHit as g, LockAccountSelector as h, watermarkResolver as i, JournalItemRef as j, BulkCreateResult as k, fiscalLockPlugin as l, CreateLockPluginOptions as m, TaxLineInput as n, FiscalLockPluginOptions as o, createLockPlugin as p, WatermarkResolverOptions as r, TaxLockPluginOptions as s, TaxLineGenerator as t, taxLockPlugin as u, LockResolverContext as v, doubleEntryPlugin as w, FxRealizationPluginOptions as x, IdempotencyPluginOptions as y, SeedOptions as z };
@@ -1,5 +1,5 @@
1
1
  import { c as DateRange } from "./core-BkGjuVZj.mjs";
2
- import { t as CountryPack } from "./index-GmfEFxVn.mjs";
2
+ import { t as CountryPack } from "./index-BthGypsI.mjs";
3
3
  import { ClientSession, Model } from "mongoose";
4
4
 
5
5
  //#region src/utils/logger.d.ts
@@ -403,6 +403,79 @@ declare function generateIncomeStatement(opts: IncomeStatementOptions, params: {
403
403
  filters?: Record<string, unknown>;
404
404
  }): Promise<IncomeStatementReport>;
405
405
  //#endregion
406
+ //#region src/reports/partner-ledger.d.ts
407
+ interface PartnerLedgerOptions {
408
+ AccountModel: Model<unknown>;
409
+ JournalEntryModel: Model<unknown>;
410
+ orgField?: string;
411
+ }
412
+ interface PartnerLedgerParams {
413
+ organizationId?: unknown;
414
+ /**
415
+ * The control account being ledgered — typically `2111 A/P`
416
+ * (supplier statement) or `1141 A/R` (customer statement).
417
+ */
418
+ controlAccountId: unknown;
419
+ /**
420
+ * Field name on each journal item that holds the partner reference.
421
+ * Default: `'partnerId'`. Whatever you declared in
422
+ * `schemaOptions.journalEntry.extraItemFields`.
423
+ */
424
+ partnerField?: string;
425
+ /**
426
+ * The specific partner whose statement we're generating. Required —
427
+ * to get all partners use `generateAgedBalance` instead.
428
+ */
429
+ partnerId: unknown;
430
+ startDate: Date;
431
+ endDate: Date;
432
+ /**
433
+ * If true, include items already matched (settled) inside the period.
434
+ * Default: true — statements show settled activity in the period.
435
+ */
436
+ includeMatched?: boolean;
437
+ /** Custom aged buckets for the open-items summary. */
438
+ buckets?: AgedBucketConfig[];
439
+ }
440
+ interface PartnerLedgerLine {
441
+ date: Date;
442
+ entry: unknown;
443
+ itemIndex: number;
444
+ referenceNumber?: string;
445
+ label?: string;
446
+ debit: number;
447
+ credit: number;
448
+ /** Running balance (debit - credit, signed) including this row. */
449
+ balance: number;
450
+ matchingNumber: string | null;
451
+ maturityDate?: Date | null;
452
+ /** Days past `maturityDate` as of `endDate`; null if no maturity set. */
453
+ daysPastDue: number | null;
454
+ isMatched: boolean;
455
+ }
456
+ interface PartnerLedgerReport {
457
+ metadata: {
458
+ generatedAt: string;
459
+ partnerId: unknown;
460
+ controlAccount: {
461
+ id: unknown;
462
+ name?: string;
463
+ code?: string;
464
+ };
465
+ period: {
466
+ startDate: string;
467
+ endDate: string;
468
+ };
469
+ };
470
+ openingBalance: number;
471
+ closingBalance: number;
472
+ openItemsTotal: number;
473
+ matchedTotal: number;
474
+ lines: PartnerLedgerLine[];
475
+ agedBuckets: Record<string, number>;
476
+ }
477
+ declare function generatePartnerLedger(opts: PartnerLedgerOptions, params: PartnerLedgerParams): Promise<PartnerLedgerReport>;
478
+ //#endregion
406
479
  //#region src/utils/revaluation.d.ts
407
480
  /**
408
481
  * Foreign Exchange Revaluation Utilities
@@ -539,4 +612,4 @@ declare function generateTrialBalance(opts: TrialBalanceOptions, params: {
539
612
  filters?: Record<string, unknown>;
540
613
  }): Promise<TrialBalanceReport>;
541
614
  //#endregion
542
- export { AgedBucketConfig as $, BudgetVsActualReport as A, IncomeStatementReport as B, DimensionBreakdownReport as C, generateCashFlow as D, CashFlowOptions as E, BalanceSheetReport as F, TaxReport as G, ReportAccount as H, CashFlowReport as I, TrialBalanceRow as J, TaxReturnSummary as K, CashFlowSection as L, generateBudgetVsActual as M, BalanceSheetOptions as N, BudgetVsActualOptions as O, generateBalanceSheet as P, AgedBalanceRow as Q, GeneralLedgerAccount as R, DimensionBreakdownParams as S, generateDimensionBreakdown as T, ReportCategory as U, LedgerEntry as V, ReportGroup as W, AgedBalanceParams as X, AgedBalanceOptions as Y, AgedBalanceReport as Z, FiscalCloseResult as _, RevaluationReport as a, reopenFiscalPeriod as b, RevaluationRate as c, computeRevaluation as d, DEFAULT_BUCKETS as et, IncomeStatementOptions as f, FiscalCloseOptions as g, generateGeneralLedger as h, RevaluationParams as i, BudgetVsActualRow as j, BudgetVsActualParams as k, RevaluationResult as l, GeneralLedgerOptions as m, generateTrialBalance as n, Logger as nt, generateRevaluation as o, generateIncomeStatement as p, TrialBalanceReport as q, RevaluationOptions as r, defaultLogger as rt, AccountForeignBalance as s, TrialBalanceOptions as t, generateAgedBalance as tt, buildRevaluationEntry as u, FiscalReopenResult as v, DimensionBreakdownRow as w, DimensionBreakdownOptions as x, closeFiscalPeriod as y, GeneralLedgerReport as z };
615
+ export { TrialBalanceRow as $, generateDimensionBreakdown as A, BalanceSheetReport as B, FiscalReopenResult as C, DimensionBreakdownParams as D, DimensionBreakdownOptions as E, BudgetVsActualReport as F, IncomeStatementReport as G, CashFlowSection as H, BudgetVsActualRow as I, ReportCategory as J, LedgerEntry as K, generateBudgetVsActual as L, generateCashFlow as M, BudgetVsActualOptions as N, DimensionBreakdownReport as O, BudgetVsActualParams as P, TrialBalanceReport as Q, BalanceSheetOptions as R, FiscalCloseResult as S, reopenFiscalPeriod as T, GeneralLedgerAccount as U, CashFlowReport as V, GeneralLedgerReport as W, TaxReport as X, ReportGroup as Y, TaxReturnSummary as Z, IncomeStatementOptions as _, RevaluationReport as a, DEFAULT_BUCKETS as at, generateGeneralLedger as b, RevaluationRate as c, defaultLogger as ct, computeRevaluation as d, AgedBalanceOptions as et, PartnerLedgerLine as f, generatePartnerLedger as g, PartnerLedgerReport as h, RevaluationParams as i, AgedBucketConfig as it, CashFlowOptions as j, DimensionBreakdownRow as k, RevaluationResult as l, PartnerLedgerParams as m, generateTrialBalance as n, AgedBalanceReport as nt, generateRevaluation as o, generateAgedBalance as ot, PartnerLedgerOptions as p, ReportAccount as q, RevaluationOptions as r, AgedBalanceRow as rt, AccountForeignBalance as s, Logger as st, TrialBalanceOptions as t, AgedBalanceParams as tt, buildRevaluationEntry as u, generateIncomeStatement as v, closeFiscalPeriod as w, FiscalCloseOptions as x, GeneralLedgerOptions as y, generateBalanceSheet as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/ledger",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Production-grade double-entry accounting engine for MongoDB — schemas, reports, tax, multi-tenant",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -78,7 +78,7 @@
78
78
  "url": "git+https://github.com/classytic/accounting.git"
79
79
  },
80
80
  "peerDependencies": {
81
- "@classytic/mongokit": ">=3.5.3",
81
+ "@classytic/mongokit": ">=3.5.5",
82
82
  "mongoose": ">=9.4.1"
83
83
  },
84
84
  "engines": {
@@ -96,12 +96,13 @@
96
96
  "format": "biome format src/ --write",
97
97
  "check": "biome ci src/ --diagnostic-level=error",
98
98
  "knip": "knip",
99
- "prepublishOnly": "npm run check && npm run build && npm run typecheck && npm test",
100
- "release": "npm run check && npm run build && npm run typecheck && npm test && npm publish --access public"
99
+ "smoke": "node scripts/smoke.mjs",
100
+ "prepublishOnly": "npm run check && npm run build && npm run typecheck && npm test && npm run smoke",
101
+ "release": "npm run check && npm run build && npm run typecheck && npm test && npm run smoke && npm publish --access public"
101
102
  },
102
103
  "devDependencies": {
103
104
  "@biomejs/biome": "^2.4.10",
104
- "@classytic/mongokit": "^3.5.3",
105
+ "@classytic/mongokit": "^3.5.5",
105
106
  "@types/node": "^22.0.0",
106
107
  "@vitest/coverage-v8": "^3.2.4",
107
108
  "knip": "^6.3.0",