@classytic/ledger 0.10.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridges/index.d.mts +1 -1
- package/dist/constants/index.d.mts +1 -1
- package/dist/constants/index.mjs +2 -2
- package/dist/{core-DwjkrRkJ.d.mts → core-B7uVjqGS.d.mts} +25 -0
- package/dist/country/index.d.mts +1 -1
- package/dist/events/index.d.mts +1 -1
- package/dist/exports/index.d.mts +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/{fx-realization.plugin-Dzqzi3u0.mjs → fx-realization.plugin-DY3pPxIi.mjs} +70 -1
- package/dist/{index-ClLwzNRF.d.mts → index-BFPFihTF.d.mts} +8 -0
- package/dist/{index-08IpHhrU.d.mts → index-Dd7HknPP.d.mts} +1 -1
- package/dist/index.d.mts +115 -19
- package/dist/index.mjs +340 -156
- package/dist/{journals-DUpWwFt1.d.mts → journals-CTrAuzdk.d.mts} +1 -1
- package/dist/{partner-ledger-BIkmQsAc.mjs → partner-ledger-B0eym6Ss.mjs} +868 -212
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +1 -1
- package/dist/reports/index.d.mts +1 -1
- package/dist/reports/index.mjs +1 -1
- package/dist/{trial-balance-DCG5lOoC.d.mts → trial-balance-UXV2PN6x.d.mts} +215 -75
- package/package.json +8 -20
- package/dist/opening-balance-1cixYh6Y.mjs +0 -60
- package/dist/sync/index.d.mts +0 -324
- package/dist/sync/index.mjs +0 -530
- package/dist/sync-JvchM3FO.d.mts +0 -152
- /package/dist/{categories-FJlrvzcl.mjs → categories-CclX7Q94.mjs} +0 -0
- /package/dist/{currencies-Jo5oaM_4.mjs → currencies-OuPHPyS2.mjs} +0 -0
- /package/dist/{exports-C30yRapf.mjs → exports-B3whucXe.mjs} +0 -0
- /package/dist/{index-Bl0gP9lD.d.mts → index-DygMrab0.d.mts} +0 -0
- /package/dist/{index-J-XIbXH-.d.mts → index-pRW5cZhF.d.mts} +0 -0
- /package/dist/{outbox-store-BcCiHMPw.d.mts → outbox-store-CPLeocPg.d.mts} +0 -0
package/dist/sync/index.mjs
DELETED
|
@@ -1,530 +0,0 @@
|
|
|
1
|
-
import { t as buildOpeningBalanceEntry } from "../opening-balance-1cixYh6Y.mjs";
|
|
2
|
-
//#region src/sync/ledger-bridge.ts
|
|
3
|
-
const isCustomerSide = (t) => t === "out_invoice" || t === "out_refund" || t === "receipt";
|
|
4
|
-
const isRefund = (t) => t === "out_refund" || t === "in_refund";
|
|
5
|
-
const JOURNAL_TYPE_MAP = {
|
|
6
|
-
out_invoice: "SALES",
|
|
7
|
-
in_invoice: "PURCHASES",
|
|
8
|
-
out_refund: "SALES",
|
|
9
|
-
in_refund: "PURCHASES",
|
|
10
|
-
receipt: "CASH_RECEIPTS"
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Create a LedgerBridge implementation backed by a @classytic/ledger engine.
|
|
14
|
-
*
|
|
15
|
-
* @param engine - The accounting engine (or any object matching the minimal
|
|
16
|
-
* shape: `{ record: { adjustment, payment }, repositories: { journalEntries: { reverse } } }`)
|
|
17
|
-
* @param config - Account mapping configuration
|
|
18
|
-
* @returns A LedgerBridge that the invoice engine can use
|
|
19
|
-
*/
|
|
20
|
-
function createLedgerBridge(engine, config) {
|
|
21
|
-
const { accounts } = config;
|
|
22
|
-
return {
|
|
23
|
-
async createJournalEntry(input) {
|
|
24
|
-
const customerSide = isCustomerSide(input.moveType);
|
|
25
|
-
const refund = isRefund(input.moveType);
|
|
26
|
-
const isReceipt = input.moveType === "receipt";
|
|
27
|
-
const balanceSheetAccount = customerSide ? isReceipt && config.receiptAccount ? config.receiptAccount : accounts.receivable : accounts.payable;
|
|
28
|
-
const incomeOrExpenseAccount = customerSide ? accounts.revenue : accounts.expense;
|
|
29
|
-
const taxAccount = customerSide ? accounts.taxPayable : accounts.taxReceivable;
|
|
30
|
-
const lines = [];
|
|
31
|
-
if (customerSide && !refund) {
|
|
32
|
-
lines.push({
|
|
33
|
-
account: balanceSheetAccount,
|
|
34
|
-
debit: input.totalAmount,
|
|
35
|
-
label: `Invoice ${input.invoiceId}`
|
|
36
|
-
});
|
|
37
|
-
for (const line of input.lines) lines.push({
|
|
38
|
-
account: incomeOrExpenseAccount,
|
|
39
|
-
credit: line.amount,
|
|
40
|
-
label: line.description
|
|
41
|
-
});
|
|
42
|
-
if (input.taxAmount > 0) lines.push({
|
|
43
|
-
account: taxAccount,
|
|
44
|
-
credit: input.taxAmount,
|
|
45
|
-
label: "Tax"
|
|
46
|
-
});
|
|
47
|
-
} else if (customerSide && refund) {
|
|
48
|
-
lines.push({
|
|
49
|
-
account: balanceSheetAccount,
|
|
50
|
-
credit: input.totalAmount,
|
|
51
|
-
label: `Credit Note ${input.invoiceId}`
|
|
52
|
-
});
|
|
53
|
-
for (const line of input.lines) lines.push({
|
|
54
|
-
account: incomeOrExpenseAccount,
|
|
55
|
-
debit: line.amount,
|
|
56
|
-
label: line.description
|
|
57
|
-
});
|
|
58
|
-
if (input.taxAmount > 0) lines.push({
|
|
59
|
-
account: taxAccount,
|
|
60
|
-
debit: input.taxAmount,
|
|
61
|
-
label: "Tax reversal"
|
|
62
|
-
});
|
|
63
|
-
} else if (!customerSide && !refund) {
|
|
64
|
-
for (const line of input.lines) lines.push({
|
|
65
|
-
account: incomeOrExpenseAccount,
|
|
66
|
-
debit: line.amount,
|
|
67
|
-
label: line.description
|
|
68
|
-
});
|
|
69
|
-
if (input.taxAmount > 0) lines.push({
|
|
70
|
-
account: taxAccount,
|
|
71
|
-
debit: input.taxAmount,
|
|
72
|
-
label: "Tax"
|
|
73
|
-
});
|
|
74
|
-
lines.push({
|
|
75
|
-
account: balanceSheetAccount,
|
|
76
|
-
credit: input.totalAmount,
|
|
77
|
-
label: `Bill ${input.invoiceId}`
|
|
78
|
-
});
|
|
79
|
-
} else {
|
|
80
|
-
lines.push({
|
|
81
|
-
account: balanceSheetAccount,
|
|
82
|
-
debit: input.totalAmount,
|
|
83
|
-
label: `Vendor Credit ${input.invoiceId}`
|
|
84
|
-
});
|
|
85
|
-
for (const line of input.lines) lines.push({
|
|
86
|
-
account: incomeOrExpenseAccount,
|
|
87
|
-
credit: line.amount,
|
|
88
|
-
label: line.description
|
|
89
|
-
});
|
|
90
|
-
if (input.taxAmount > 0) lines.push({
|
|
91
|
-
account: taxAccount,
|
|
92
|
-
credit: input.taxAmount,
|
|
93
|
-
label: "Tax reversal"
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
const label = input.notes ? `${input.moveType} ${input.invoiceId} — ${input.notes}` : `${input.moveType} ${input.invoiceId}`;
|
|
97
|
-
const result = await engine.record.adjustment(input.organizationId, {
|
|
98
|
-
date: input.date,
|
|
99
|
-
lines,
|
|
100
|
-
label,
|
|
101
|
-
journalType: JOURNAL_TYPE_MAP[input.moveType] ?? "GENERAL"
|
|
102
|
-
}, input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {});
|
|
103
|
-
return String(result._id);
|
|
104
|
-
},
|
|
105
|
-
async reverseJournalEntry(journalEntryId, _reason, ctx) {
|
|
106
|
-
const options = {};
|
|
107
|
-
if (ctx.actorId) options.actorId = ctx.actorId;
|
|
108
|
-
if (ctx.session) options.session = ctx.session;
|
|
109
|
-
const result = await engine.repositories.journalEntries.reverse(journalEntryId, ctx.organizationId, options);
|
|
110
|
-
return String(result.reversal._id);
|
|
111
|
-
},
|
|
112
|
-
async recordPayment(input) {
|
|
113
|
-
let fromAccount;
|
|
114
|
-
let toAccount;
|
|
115
|
-
if (config.resolvePaymentAccounts) {
|
|
116
|
-
const resolved = config.resolvePaymentAccounts(input);
|
|
117
|
-
fromAccount = resolved.receivableOrPayable;
|
|
118
|
-
toAccount = resolved.cash;
|
|
119
|
-
} else {
|
|
120
|
-
fromAccount = accounts.receivable;
|
|
121
|
-
toAccount = accounts.cash;
|
|
122
|
-
}
|
|
123
|
-
const result = await engine.record.payment(input.organizationId, {
|
|
124
|
-
date: input.date,
|
|
125
|
-
amount: input.amount,
|
|
126
|
-
fromReceivableAccount: fromAccount,
|
|
127
|
-
toCashAccount: toAccount,
|
|
128
|
-
label: `Payment ${input.paymentId} for ${input.invoiceId}`
|
|
129
|
-
}, { idempotencyKey: `payment:${input.paymentId}` });
|
|
130
|
-
return String(result._id);
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
//#endregion
|
|
135
|
-
//#region src/sync/mappers/bank-statement.ts
|
|
136
|
-
function bankStatementMapper(config) {
|
|
137
|
-
return {
|
|
138
|
-
externalId: (txn) => txn.externalId,
|
|
139
|
-
toJournalEntry: (txn, _ctx) => {
|
|
140
|
-
const cents = Number(txn.amount.amount);
|
|
141
|
-
const absCents = Math.abs(cents);
|
|
142
|
-
const isInflow = cents > 0;
|
|
143
|
-
const counterAccount = config.categorize?.(txn) ?? config.suspenseAccountId;
|
|
144
|
-
const label = [
|
|
145
|
-
config.labelPrefix ?? "Import",
|
|
146
|
-
txn.description,
|
|
147
|
-
txn.counterparty?.name
|
|
148
|
-
].filter(Boolean).join(" — ");
|
|
149
|
-
return {
|
|
150
|
-
date: txn.postedDate,
|
|
151
|
-
label,
|
|
152
|
-
referenceNumber: txn.externalId,
|
|
153
|
-
journalItems: isInflow ? [{
|
|
154
|
-
account: config.bankAccountId,
|
|
155
|
-
debit: absCents,
|
|
156
|
-
credit: 0
|
|
157
|
-
}, {
|
|
158
|
-
account: counterAccount,
|
|
159
|
-
debit: 0,
|
|
160
|
-
credit: absCents
|
|
161
|
-
}] : [{
|
|
162
|
-
account: counterAccount,
|
|
163
|
-
debit: absCents,
|
|
164
|
-
credit: 0
|
|
165
|
-
}, {
|
|
166
|
-
account: config.bankAccountId,
|
|
167
|
-
debit: 0,
|
|
168
|
-
credit: absCents
|
|
169
|
-
}],
|
|
170
|
-
extra: {
|
|
171
|
-
_importSource: txn.type ?? "bank-import",
|
|
172
|
-
_importCounterparty: txn.counterparty?.name,
|
|
173
|
-
_importReference: txn.reference
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
//#endregion
|
|
180
|
-
//#region src/sync/mappers/invoice.ts
|
|
181
|
-
function invoiceMapper(config) {
|
|
182
|
-
return {
|
|
183
|
-
externalId: (inv) => inv.externalId,
|
|
184
|
-
toJournalEntry: (inv) => {
|
|
185
|
-
const isSales = inv.type === "sales";
|
|
186
|
-
const total = Number(inv.total.amount);
|
|
187
|
-
const taxTotal = Number(inv.taxTotal.amount);
|
|
188
|
-
const items = [];
|
|
189
|
-
if (isSales) items.push({
|
|
190
|
-
account: config.receivablesAccountId,
|
|
191
|
-
debit: total,
|
|
192
|
-
credit: 0
|
|
193
|
-
});
|
|
194
|
-
else items.push({
|
|
195
|
-
account: config.payablesAccountId,
|
|
196
|
-
debit: 0,
|
|
197
|
-
credit: total
|
|
198
|
-
});
|
|
199
|
-
for (const line of inv.lines) {
|
|
200
|
-
const lineAmount = Number(line.amount.amount);
|
|
201
|
-
const account = (line.accountCode ? config.resolveAccountCode?.(line.accountCode) : void 0) ?? (isSales ? config.defaultRevenueAccountId : config.defaultExpenseAccountId);
|
|
202
|
-
if (isSales) items.push({
|
|
203
|
-
account,
|
|
204
|
-
debit: 0,
|
|
205
|
-
credit: lineAmount,
|
|
206
|
-
label: line.description
|
|
207
|
-
});
|
|
208
|
-
else items.push({
|
|
209
|
-
account,
|
|
210
|
-
debit: lineAmount,
|
|
211
|
-
credit: 0,
|
|
212
|
-
label: line.description
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
if (taxTotal > 0) {
|
|
216
|
-
const taxAccount = isSales ? config.taxLiabilityAccountId : config.taxReceivableAccountId;
|
|
217
|
-
if (taxAccount) if (isSales) items.push({
|
|
218
|
-
account: taxAccount,
|
|
219
|
-
debit: 0,
|
|
220
|
-
credit: taxTotal
|
|
221
|
-
});
|
|
222
|
-
else items.push({
|
|
223
|
-
account: taxAccount,
|
|
224
|
-
debit: taxTotal,
|
|
225
|
-
credit: 0
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
return {
|
|
229
|
-
date: inv.issueDate,
|
|
230
|
-
label: `${isSales ? "Sales" : "Purchase"} Invoice — ${inv.contact.name ?? inv.externalId}`,
|
|
231
|
-
referenceNumber: inv.externalId,
|
|
232
|
-
journalItems: items,
|
|
233
|
-
extra: {
|
|
234
|
-
_importSource: "invoice-import",
|
|
235
|
-
_importContactName: inv.contact.name,
|
|
236
|
-
_importContactRef: inv.contact.reference
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
//#endregion
|
|
243
|
-
//#region src/sync/mappers/journal-entry.ts
|
|
244
|
-
function journalEntryMapper(config) {
|
|
245
|
-
return {
|
|
246
|
-
externalId: (je) => je.externalId,
|
|
247
|
-
toJournalEntry: (je) => ({
|
|
248
|
-
date: je.date,
|
|
249
|
-
label: je.narration ?? `Imported JE ${je.externalId}`,
|
|
250
|
-
referenceNumber: je.externalId,
|
|
251
|
-
journalItems: je.lines.map((line) => ({
|
|
252
|
-
account: config.resolveAccountCode(line.accountCode),
|
|
253
|
-
debit: line.debit ? Number(line.debit.amount) : 0,
|
|
254
|
-
credit: line.credit ? Number(line.credit.amount) : 0,
|
|
255
|
-
label: line.description
|
|
256
|
-
})),
|
|
257
|
-
extra: { _importSource: "journal-entry-import" }
|
|
258
|
-
})
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
//#endregion
|
|
262
|
-
//#region src/sync/mappers/opening-balance.ts
|
|
263
|
-
function openingBalanceMapper(config) {
|
|
264
|
-
return {
|
|
265
|
-
externalId: (_tb) => {
|
|
266
|
-
return `opening-balance:${config.cutoverDate.toISOString().split("T")[0]}`;
|
|
267
|
-
},
|
|
268
|
-
toJournalEntry: (tb, _ctx) => {
|
|
269
|
-
const balances = [];
|
|
270
|
-
for (const line of tb.lines) {
|
|
271
|
-
const net = (line.debit ? Number(line.debit.amount) : 0) - (line.credit ? Number(line.credit.amount) : 0);
|
|
272
|
-
if (net === 0) continue;
|
|
273
|
-
const resolved = config.resolveAccountCode(line.accountCode);
|
|
274
|
-
if (!resolved) continue;
|
|
275
|
-
balances.push({
|
|
276
|
-
accountCode: String(resolved),
|
|
277
|
-
balance: net
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
if (balances.length === 0) return null;
|
|
281
|
-
return buildOpeningBalanceEntry({
|
|
282
|
-
cutoverDate: config.cutoverDate,
|
|
283
|
-
balances,
|
|
284
|
-
equityAccountCode: String(config.equityAccountId)
|
|
285
|
-
}).entry;
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
//#endregion
|
|
290
|
-
//#region src/sync/wire-export.ts
|
|
291
|
-
function wireExport(args) {
|
|
292
|
-
const batchSize = args.options?.batchSize ?? 100;
|
|
293
|
-
const onProgress = args.options?.onProgress;
|
|
294
|
-
return { async run() {
|
|
295
|
-
const start = performance.now();
|
|
296
|
-
let emitted = 0;
|
|
297
|
-
const errors = [];
|
|
298
|
-
const entries = await args.journalEntries.getAll(args.query);
|
|
299
|
-
let batch = [];
|
|
300
|
-
for (const entry of entries) {
|
|
301
|
-
try {
|
|
302
|
-
const out = args.sink.fromJournalEntry(entry);
|
|
303
|
-
batch.push(out);
|
|
304
|
-
} catch (err) {
|
|
305
|
-
const id = entry?._id;
|
|
306
|
-
errors.push({
|
|
307
|
-
entryId: id ? String(id) : void 0,
|
|
308
|
-
message: err.message
|
|
309
|
-
});
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (batch.length >= batchSize) {
|
|
313
|
-
const count = batch.length;
|
|
314
|
-
await args.sink.emit(batch);
|
|
315
|
-
batch = [];
|
|
316
|
-
emitted += count;
|
|
317
|
-
onProgress?.({ emitted });
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (batch.length > 0) {
|
|
321
|
-
const count = batch.length;
|
|
322
|
-
await args.sink.emit(batch);
|
|
323
|
-
batch = [];
|
|
324
|
-
emitted += count;
|
|
325
|
-
onProgress?.({ emitted });
|
|
326
|
-
}
|
|
327
|
-
if (args.sink.flush) await args.sink.flush();
|
|
328
|
-
return {
|
|
329
|
-
ok: errors.length === 0,
|
|
330
|
-
emitted,
|
|
331
|
-
errors,
|
|
332
|
-
durationMs: performance.now() - start
|
|
333
|
-
};
|
|
334
|
-
} };
|
|
335
|
-
}
|
|
336
|
-
//#endregion
|
|
337
|
-
//#region src/sync/wire-import.ts
|
|
338
|
-
function wireImport(args) {
|
|
339
|
-
const batchSize = args.options?.batchSize ?? 100;
|
|
340
|
-
const strict = args.options?.strict ?? false;
|
|
341
|
-
const dryRun = args.options?.dryRun ?? false;
|
|
342
|
-
const journalType = args.options?.journalType ?? "GENERAL";
|
|
343
|
-
const onProgress = args.options?.onProgress;
|
|
344
|
-
const useBulk = !dryRun && typeof args.journalEntries.createMany === "function";
|
|
345
|
-
return { async run() {
|
|
346
|
-
const start = performance.now();
|
|
347
|
-
const ctx = {
|
|
348
|
-
organizationId: args.context.organizationId,
|
|
349
|
-
importedAt: /* @__PURE__ */ new Date(),
|
|
350
|
-
importRunId: args.context.importRunId
|
|
351
|
-
};
|
|
352
|
-
let inserted = 0;
|
|
353
|
-
let skipped = 0;
|
|
354
|
-
let failed = 0;
|
|
355
|
-
const errors = [];
|
|
356
|
-
let processed = 0;
|
|
357
|
-
const batch = [];
|
|
358
|
-
for await (const raw of toAsyncIterable(args.source)) {
|
|
359
|
-
batch.push(raw);
|
|
360
|
-
if (batch.length >= batchSize) {
|
|
361
|
-
const result = await processBatch(batch.splice(0));
|
|
362
|
-
inserted += result.inserted;
|
|
363
|
-
skipped += result.skipped;
|
|
364
|
-
failed += result.failed;
|
|
365
|
-
errors.push(...result.errors);
|
|
366
|
-
processed += result.processed;
|
|
367
|
-
if (strict && result.failed > 0) break;
|
|
368
|
-
onProgress?.({ processed });
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (batch.length > 0) {
|
|
372
|
-
const result = await processBatch(batch);
|
|
373
|
-
inserted += result.inserted;
|
|
374
|
-
skipped += result.skipped;
|
|
375
|
-
failed += result.failed;
|
|
376
|
-
errors.push(...result.errors);
|
|
377
|
-
processed += result.processed;
|
|
378
|
-
onProgress?.({ processed });
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
ok: failed === 0,
|
|
382
|
-
inserted,
|
|
383
|
-
skipped,
|
|
384
|
-
failed,
|
|
385
|
-
errors,
|
|
386
|
-
durationMs: performance.now() - start
|
|
387
|
-
};
|
|
388
|
-
async function processBatch(records) {
|
|
389
|
-
let batchInserted = 0;
|
|
390
|
-
let batchSkipped = 0;
|
|
391
|
-
let batchFailed = 0;
|
|
392
|
-
const batchErrors = [];
|
|
393
|
-
const externalIds = [];
|
|
394
|
-
for (const raw of records) try {
|
|
395
|
-
externalIds.push(args.mapper.externalId(raw));
|
|
396
|
-
} catch (err) {
|
|
397
|
-
externalIds.push("");
|
|
398
|
-
batchErrors.push({
|
|
399
|
-
externalId: void 0,
|
|
400
|
-
message: `externalId() threw: ${err.message}`,
|
|
401
|
-
cause: err
|
|
402
|
-
});
|
|
403
|
-
batchFailed += 1;
|
|
404
|
-
}
|
|
405
|
-
const existingSet = /* @__PURE__ */ new Set();
|
|
406
|
-
const validIds = externalIds.filter((id) => id.length > 0);
|
|
407
|
-
if (validIds.length > 0 && args.findExisting) try {
|
|
408
|
-
const found = await args.findExisting(validIds, ctx.organizationId);
|
|
409
|
-
for (const id of found) existingSet.add(id);
|
|
410
|
-
} catch {}
|
|
411
|
-
const pendingDocs = [];
|
|
412
|
-
for (let i = 0; i < records.length; i++) {
|
|
413
|
-
const raw = records[i];
|
|
414
|
-
const externalId = externalIds[i];
|
|
415
|
-
if (!externalId) continue;
|
|
416
|
-
if (existingSet.has(externalId)) {
|
|
417
|
-
batchSkipped += 1;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
let inputs;
|
|
421
|
-
try {
|
|
422
|
-
inputs = args.mapper.toJournalEntry(raw, ctx);
|
|
423
|
-
} catch (err) {
|
|
424
|
-
batchErrors.push({
|
|
425
|
-
externalId,
|
|
426
|
-
message: `toJournalEntry() threw: ${err.message}`,
|
|
427
|
-
cause: err
|
|
428
|
-
});
|
|
429
|
-
batchFailed += 1;
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
if (inputs === null) {
|
|
433
|
-
batchSkipped += 1;
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
const inputArray = Array.isArray(inputs) ? inputs : [inputs];
|
|
437
|
-
for (const input of inputArray) {
|
|
438
|
-
if (dryRun) {
|
|
439
|
-
batchInserted += 1;
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
const doc = {
|
|
443
|
-
journalType: input.journalType ?? journalType,
|
|
444
|
-
journal: input.journal,
|
|
445
|
-
label: input.label ?? "Import",
|
|
446
|
-
date: input.date,
|
|
447
|
-
journalItems: input.journalItems.map((item) => ({
|
|
448
|
-
account: item.account,
|
|
449
|
-
debit: item.debit,
|
|
450
|
-
credit: item.credit,
|
|
451
|
-
label: item.label,
|
|
452
|
-
currency: item.currency,
|
|
453
|
-
exchangeRate: item.exchangeRate,
|
|
454
|
-
originalDebit: item.originalDebit,
|
|
455
|
-
originalCredit: item.originalCredit,
|
|
456
|
-
matchingNumber: item.matchingNumber,
|
|
457
|
-
maturityDate: item.maturityDate
|
|
458
|
-
})),
|
|
459
|
-
_externalId: externalId,
|
|
460
|
-
state: "posted",
|
|
461
|
-
...ctx.organizationId !== void 0 ? { organizationId: ctx.organizationId } : {},
|
|
462
|
-
...ctx.importRunId ? { _importRunId: ctx.importRunId } : {},
|
|
463
|
-
...input.extra
|
|
464
|
-
};
|
|
465
|
-
pendingDocs.push({
|
|
466
|
-
externalId,
|
|
467
|
-
doc
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
if (dryRun || pendingDocs.length === 0) return {
|
|
472
|
-
inserted: batchInserted,
|
|
473
|
-
skipped: batchSkipped,
|
|
474
|
-
failed: batchFailed,
|
|
475
|
-
errors: batchErrors,
|
|
476
|
-
processed: records.length
|
|
477
|
-
};
|
|
478
|
-
if (useBulk) try {
|
|
479
|
-
await args.journalEntries.createMany?.(pendingDocs.map((p) => p.doc));
|
|
480
|
-
batchInserted += pendingDocs.length;
|
|
481
|
-
} catch (_err) {
|
|
482
|
-
for (const { externalId, doc } of pendingDocs) try {
|
|
483
|
-
await args.journalEntries.create(doc);
|
|
484
|
-
batchInserted += 1;
|
|
485
|
-
} catch (innerErr) {
|
|
486
|
-
const innerMsg = innerErr.message;
|
|
487
|
-
if (innerMsg.includes("idempotency") || innerMsg.includes("duplicate")) batchSkipped += 1;
|
|
488
|
-
else {
|
|
489
|
-
batchErrors.push({
|
|
490
|
-
externalId,
|
|
491
|
-
message: innerMsg,
|
|
492
|
-
cause: innerErr
|
|
493
|
-
});
|
|
494
|
-
batchFailed += 1;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
else for (const { externalId, doc } of pendingDocs) try {
|
|
499
|
-
await args.journalEntries.create(doc);
|
|
500
|
-
batchInserted += 1;
|
|
501
|
-
} catch (err) {
|
|
502
|
-
const msg = err.message;
|
|
503
|
-
if (msg.includes("idempotency") || msg.includes("duplicate")) batchSkipped += 1;
|
|
504
|
-
else {
|
|
505
|
-
batchErrors.push({
|
|
506
|
-
externalId,
|
|
507
|
-
message: msg,
|
|
508
|
-
cause: err
|
|
509
|
-
});
|
|
510
|
-
batchFailed += 1;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
return {
|
|
514
|
-
inserted: batchInserted,
|
|
515
|
-
skipped: batchSkipped,
|
|
516
|
-
failed: batchFailed,
|
|
517
|
-
errors: batchErrors,
|
|
518
|
-
processed: records.length
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
} };
|
|
522
|
-
}
|
|
523
|
-
function toAsyncIterable(source) {
|
|
524
|
-
if (Symbol.asyncIterator in Object(source)) return source;
|
|
525
|
-
return { async *[Symbol.asyncIterator]() {
|
|
526
|
-
for (const item of source) yield item;
|
|
527
|
-
} };
|
|
528
|
-
}
|
|
529
|
-
//#endregion
|
|
530
|
-
export { bankStatementMapper, buildOpeningBalanceEntry, createLedgerBridge, invoiceMapper, journalEntryMapper, openingBalanceMapper, wireExport, wireImport };
|
package/dist/sync-JvchM3FO.d.mts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { a as Cents } from "./core-DwjkrRkJ.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/types/sync.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Maps a raw record (from any source) into a JournalEntry creation payload.
|
|
6
|
-
* The consumer passes this to wireImport. Reference implementations ship
|
|
7
|
-
* for every fin-io canonical shape: ofxBankMapper, camtBankMapper, etc.
|
|
8
|
-
*/
|
|
9
|
-
interface ImportMapper<TRaw> {
|
|
10
|
-
/**
|
|
11
|
-
* Transform one raw record into zero, one, or many JournalEntry inputs.
|
|
12
|
-
* Return null to skip a record (e.g. opening-balance entries).
|
|
13
|
-
*/
|
|
14
|
-
toJournalEntry(raw: TRaw, ctx: ImportContext): JournalEntryInput | JournalEntryInput[] | null;
|
|
15
|
-
/**
|
|
16
|
-
* Stable, source-assigned unique ID for the raw record. Used for
|
|
17
|
-
* idempotent re-imports — wireImport checks for an existing entry
|
|
18
|
-
* with this idempotencyKey before creating.
|
|
19
|
-
*/
|
|
20
|
-
externalId(raw: TRaw): string;
|
|
21
|
-
}
|
|
22
|
-
interface ImportContext {
|
|
23
|
-
organizationId: unknown;
|
|
24
|
-
/** When the import job started, for audit. */
|
|
25
|
-
importedAt: Date;
|
|
26
|
-
/** Optional run-scoped tag (e.g. 'monthly-bank-import-2026-04'). */
|
|
27
|
-
importRunId?: string;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Minimal JournalEntry creation payload. This is what the mapper produces
|
|
31
|
-
* and wireImport passes to `journalEntries.create()`. The shape matches
|
|
32
|
-
* the AccountingEngine's expected create input.
|
|
33
|
-
*/
|
|
34
|
-
interface JournalEntryInput {
|
|
35
|
-
journalType?: string;
|
|
36
|
-
journal?: unknown;
|
|
37
|
-
referenceNumber?: string;
|
|
38
|
-
label?: string;
|
|
39
|
-
date: Date;
|
|
40
|
-
journalItems: JournalItemInput[];
|
|
41
|
-
/** Extra fields injected into the entry doc (dimension fields, tags, etc.). */
|
|
42
|
-
extra?: Record<string, unknown>;
|
|
43
|
-
}
|
|
44
|
-
interface JournalItemInput {
|
|
45
|
-
account: unknown;
|
|
46
|
-
debit: Cents;
|
|
47
|
-
credit: Cents;
|
|
48
|
-
label?: string;
|
|
49
|
-
currency?: string;
|
|
50
|
-
exchangeRate?: number;
|
|
51
|
-
originalDebit?: Cents;
|
|
52
|
-
originalCredit?: Cents;
|
|
53
|
-
matchingNumber?: string;
|
|
54
|
-
maturityDate?: Date;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Result of an import run. Always returned — never thrown. Errors on
|
|
58
|
-
* individual records do NOT abort the run unless `strict: true`.
|
|
59
|
-
*/
|
|
60
|
-
interface ImportReport {
|
|
61
|
-
ok: boolean;
|
|
62
|
-
inserted: number;
|
|
63
|
-
skipped: number;
|
|
64
|
-
failed: number;
|
|
65
|
-
errors: ImportError[];
|
|
66
|
-
durationMs: number;
|
|
67
|
-
}
|
|
68
|
-
interface ImportError {
|
|
69
|
-
externalId?: string;
|
|
70
|
-
message: string;
|
|
71
|
-
cause?: unknown;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Maps a JournalEntry into an external format record. Used by wireExport.
|
|
75
|
-
*/
|
|
76
|
-
interface ExportSink<TOut> {
|
|
77
|
-
fromJournalEntry(entry: unknown): TOut;
|
|
78
|
-
emit(records: TOut[]): Promise<void>;
|
|
79
|
-
flush?(): Promise<void>;
|
|
80
|
-
}
|
|
81
|
-
interface ExportReport {
|
|
82
|
-
ok: boolean;
|
|
83
|
-
emitted: number;
|
|
84
|
-
errors: Array<{
|
|
85
|
-
entryId?: string;
|
|
86
|
-
message: string;
|
|
87
|
-
}>;
|
|
88
|
-
durationMs: number;
|
|
89
|
-
}
|
|
90
|
-
interface WireImportArgs<TRaw> {
|
|
91
|
-
/** The raw records to import. */
|
|
92
|
-
source: Iterable<TRaw> | AsyncIterable<TRaw>;
|
|
93
|
-
/** Maps raw → JournalEntry input. */
|
|
94
|
-
mapper: ImportMapper<TRaw>;
|
|
95
|
-
/** The ledger's JournalEntry repository (from createRepositories()). */
|
|
96
|
-
journalEntries: {
|
|
97
|
-
create(data: Record<string, unknown>): Promise<unknown>;
|
|
98
|
-
/**
|
|
99
|
-
* Bulk create journal entries. When provided, wireImport uses this for
|
|
100
|
-
* batch inserts instead of per-record create() — dramatically faster
|
|
101
|
-
* for large imports (single round-trip per batch instead of N).
|
|
102
|
-
*
|
|
103
|
-
* Falls back to sequential create() if not provided.
|
|
104
|
-
*/
|
|
105
|
-
createMany?(data: Record<string, unknown>[]): Promise<unknown[]>;
|
|
106
|
-
getAll(query: Record<string, unknown>): Promise<unknown[]>;
|
|
107
|
-
};
|
|
108
|
-
/** Organizational context. */
|
|
109
|
-
context: Pick<ImportContext, 'organizationId' | 'importRunId'>;
|
|
110
|
-
/**
|
|
111
|
-
* Optional callback that checks whether entries with the given
|
|
112
|
-
* referenceNumbers already exist. Returns the set of existing ones.
|
|
113
|
-
* If not provided, wireImport skips the pre-check and relies on
|
|
114
|
-
* create() errors (or the idempotency plugin) for dedup.
|
|
115
|
-
*
|
|
116
|
-
* Example using Mongoose Model directly:
|
|
117
|
-
* findExisting: async (refNums, orgId) => {
|
|
118
|
-
* const docs = await JournalEntry.find({
|
|
119
|
-
* organizationId: orgId,
|
|
120
|
-
* referenceNumber: { $in: refNums },
|
|
121
|
-
* }).select('referenceNumber').lean();
|
|
122
|
-
* return new Set(docs.map(d => d.referenceNumber));
|
|
123
|
-
* }
|
|
124
|
-
*/
|
|
125
|
-
findExisting?: (referenceNumbers: string[], organizationId: unknown) => Promise<Set<string>>;
|
|
126
|
-
options?: {
|
|
127
|
-
/** First error aborts the run. Default: false. */strict?: boolean; /** Entries per batch. Default: 100. */
|
|
128
|
-
batchSize?: number; /** Dry-run: do everything except persist. Default: false. */
|
|
129
|
-
dryRun?: boolean; /** Progress callback. */
|
|
130
|
-
onProgress?: (p: {
|
|
131
|
-
processed: number;
|
|
132
|
-
total?: number;
|
|
133
|
-
}) => void; /** Journal type for imported entries. Default: 'GENERAL'. */
|
|
134
|
-
journalType?: string;
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
interface WireExportArgs<TOut> {
|
|
138
|
-
/** Query to select entries for export. */
|
|
139
|
-
query: Record<string, unknown>;
|
|
140
|
-
sink: ExportSink<TOut>;
|
|
141
|
-
journalEntries: {
|
|
142
|
-
getAll(query: Record<string, unknown>): Promise<unknown[]>;
|
|
143
|
-
};
|
|
144
|
-
options?: {
|
|
145
|
-
batchSize?: number;
|
|
146
|
-
onProgress?: (p: {
|
|
147
|
-
emitted: number;
|
|
148
|
-
}) => void;
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
//#endregion
|
|
152
|
-
export { ImportMapper as a, JournalItemInput as c, ImportError as i, WireExportArgs as l, ExportSink as n, ImportReport as o, ImportContext as r, JournalEntryInput as s, ExportReport as t, WireImportArgs as u };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|