@classytic/ledger 0.8.0 → 0.9.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 +2 -0
- package/dist/bridges/index.mjs +1 -0
- package/dist/constants/index.d.mts +1 -1
- package/dist/constants/index.mjs +2 -2
- package/dist/country/index.d.mts +1 -1
- package/dist/errors-BI5k4iak.mjs +121 -0
- package/dist/events/index.d.mts +2 -0
- package/dist/events/index.mjs +2 -0
- package/dist/exports/index.d.mts +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/{fx-realization.plugin-DDVK-oYO.mjs → fx-realization.plugin-Bxlb8cIx.mjs} +2 -2
- package/dist/{index-RNZsX0Yo.d.mts → index-08IpHhrU.d.mts} +1 -1
- package/dist/index-dqkjgpII.d.mts +104 -0
- package/dist/index.d.mts +227 -65
- package/dist/index.mjs +385 -99
- package/dist/{journals-Dd4A9TN3.d.mts → journals-DUpWwFt1.d.mts} +1 -1
- package/dist/outbox-store-DQbL-KYT.mjs +132 -0
- package/dist/outbox-store-UYC4eZpI.d.mts +249 -0
- package/dist/{partner-ledger-D9H5hegI.mjs → partner-ledger-BoebloHk.mjs} +2 -2
- 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/sync/index.d.mts +1 -1
- package/dist/sync/index.mjs +1 -1
- package/dist/{sync-CnuVf441.d.mts → sync-JvchM3FO.d.mts} +1 -1
- package/dist/{trial-balance-DTj-c21f.d.mts → trial-balance-DyNm5bFu.d.mts} +2 -2
- package/package.json +14 -4
- package/dist/errors-CSDQPNyt.mjs +0 -33
- /package/dist/{categories-BkKdv16V.mjs → categories-FJlrvzcl.mjs} +0 -0
- /package/dist/{core-MpgjCqK0.d.mts → core-DwjkrRkJ.d.mts} +0 -0
- /package/dist/{currencies-CsuBGfgs.mjs → currencies-Jo5oaM_4.mjs} +0 -0
- /package/dist/{exports-B3whucXe.mjs → exports-C30yRapf.mjs} +0 -0
- /package/dist/{index-BSsvrf3m.d.mts → index-Db0n_6Z8.d.mts} +0 -0
- /package/dist/{index-bCEeSzdO.d.mts → index-J-XIbXH-.d.mts} +0 -0
- /package/dist/{opening-balance-DPXmAIzN.mjs → opening-balance-1cixYh6Y.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as LEDGER_EVENTS, n as InProcessLedgerBus, r as createEvent, t as OutboxOwnershipError } from "./outbox-store-DQbL-KYT.mjs";
|
|
2
|
+
import { a as IdempotencyConflictError, i as Errors, n as ConcurrencyError, o as ImmutableViolationError, r as DuplicateReferenceError, s as classifyDuplicateKey, t as AccountingError } from "./errors-BI5k4iak.mjs";
|
|
3
|
+
import { a as JOURNAL_CODES, c as getCustomJournalTypes, d as isValidJournalType, f as registerJournalType, i as isValidCurrency, l as getJournalType, n as getCurrency, o as JOURNAL_TYPES, r as getMinorUnit, s as _freezeJournalTypes, t as CURRENCIES, u as getJournalTypeCodes } from "./currencies-Jo5oaM_4.mjs";
|
|
2
4
|
import { Money, add, allocate, format, formatPlain, fromDecimal, multiply, parseCents, percentage, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal } from "./money.mjs";
|
|
3
|
-
import { n as
|
|
4
|
-
import {
|
|
5
|
-
import { c as
|
|
6
|
-
import {
|
|
7
|
-
import { t as buildOpeningBalanceEntry } from "./opening-balance-DPXmAIzN.mjs";
|
|
5
|
+
import { C as isVirtualTaxAccount, E as requireOrgScope, S as computeEndingBalance, T as generateAgedBalance, _ as buildItemFilters, a as finalizeSession, b as buildAccountTypeMap, c as generateRevaluation, d as generateIncomeStatement, f as generateGeneralLedger, g as generateBalanceSheet, h as generateBudgetVsActual, i as acquireSession, l as buildRevaluationEntry, m as generateCashFlow, n as closeFiscalPeriod, o as defaultLogger, p as generateDimensionBreakdown, r as reopenFiscalPeriod, s as generateTrialBalance, t as generatePartnerLedger, u as computeRevaluation, v as getDateRange, w as DEFAULT_BUCKETS, x as calculateTotal, y as getFiscalYearStart } from "./partner-ledger-BoebloHk.mjs";
|
|
6
|
+
import { c as getNormalBalance, d as isValidCategory, l as isBalanceSheet, n as CATEGORY_KEYS, t as CATEGORIES, u as isIncomeStatement } from "./categories-FJlrvzcl.mjs";
|
|
7
|
+
import { a as watermarkResolver, c as idempotencyPlugin, i as fiscalLockPlugin, l as doubleEntryPlugin, n as creditLimitPlugin, o as periodResolver, r as dailyLockPlugin, s as createLockPlugin, t as fxRealizationPlugin } from "./fx-realization.plugin-Bxlb8cIx.mjs";
|
|
8
|
+
import { t as buildOpeningBalanceEntry } from "./opening-balance-1cixYh6Y.mjs";
|
|
8
9
|
import { defineCountryPack } from "./country/index.mjs";
|
|
9
|
-
import { a as exportToCsv, i as quickbooksFieldMap, r as universalFieldMap, t as flattenJournalEntries } from "./exports-
|
|
10
|
-
import { QueryParser, Repository } from "@classytic/mongokit";
|
|
10
|
+
import { a as exportToCsv, i as quickbooksFieldMap, r as universalFieldMap, t as flattenJournalEntries } from "./exports-C30yRapf.mjs";
|
|
11
|
+
import { QueryParser, Repository, getNextSequence, multiTenantPlugin } from "@classytic/mongokit";
|
|
11
12
|
import mongoose, { Schema } from "mongoose";
|
|
13
|
+
//#region src/plugins/immutable-guard.plugin.ts
|
|
14
|
+
/**
|
|
15
|
+
* Returns a mongokit plugin function. Install only when
|
|
16
|
+
* `config.strictness.immutable === true`.
|
|
17
|
+
*/
|
|
18
|
+
function immutableGuardPlugin(options) {
|
|
19
|
+
const { JournalEntryModel, orgField } = options;
|
|
20
|
+
return (repo) => {
|
|
21
|
+
repo.on("before:update", async (ctx) => {
|
|
22
|
+
if (ctx._ledgerInternal) return;
|
|
23
|
+
const id = ctx.id;
|
|
24
|
+
if (!id) return;
|
|
25
|
+
const query = { _id: id };
|
|
26
|
+
if (orgField && ctx.query && orgField in ctx.query) query[orgField] = ctx.query[orgField];
|
|
27
|
+
if ((await JournalEntryModel.findOne(query).select({ state: 1 }).lean())?.state === "posted") throw new ImmutableViolationError(id);
|
|
28
|
+
});
|
|
29
|
+
repo.on("before:delete", async (ctx) => {
|
|
30
|
+
if (ctx._ledgerInternal) return;
|
|
31
|
+
const id = ctx.id;
|
|
32
|
+
if (!id) return;
|
|
33
|
+
const query = { _id: id };
|
|
34
|
+
if (orgField && ctx.query && orgField in ctx.query) query[orgField] = ctx.query[orgField];
|
|
35
|
+
if ((await JournalEntryModel.findOne(query).select({ state: 1 }).lean())?.state === "posted") throw new ImmutableViolationError(id);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
12
40
|
//#region src/schemas/currency-field.ts
|
|
13
41
|
/**
|
|
14
42
|
* Build the Mongoose currency field definition.
|
|
@@ -580,7 +608,10 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
|
|
|
580
608
|
ref: multiTenant.orgRef,
|
|
581
609
|
required: true
|
|
582
610
|
};
|
|
583
|
-
const schema = new mongoose.Schema(fields, {
|
|
611
|
+
const schema = new mongoose.Schema(fields, {
|
|
612
|
+
timestamps: true,
|
|
613
|
+
optimisticConcurrency: true
|
|
614
|
+
});
|
|
584
615
|
schema.pre("validate", function() {
|
|
585
616
|
for (const item of this.journalItems) if (!item.date) item.date = this.date;
|
|
586
617
|
for (let i = 0; i < this.journalItems.length; i++) {
|
|
@@ -598,56 +629,25 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
|
|
|
598
629
|
this.totalDebit = totalDebit;
|
|
599
630
|
this.totalCredit = totalCredit;
|
|
600
631
|
});
|
|
601
|
-
if (autoReference) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const results = await Model.aggregate(pipeline).session(session);
|
|
616
|
-
let seq = 1;
|
|
617
|
-
if (results.length > 0 && typeof results[0]._refSeq === "number") seq = results[0]._refSeq + 1;
|
|
618
|
-
return `${prefix}${String(seq).padStart(4, "0")}`;
|
|
619
|
-
};
|
|
620
|
-
schema.pre("save", async function() {
|
|
621
|
-
if (this.isModified("journalType")) this.referenceNumber = void 0;
|
|
622
|
-
if (!this.referenceNumber) {
|
|
623
|
-
const session = this.$session?.() ?? null;
|
|
624
|
-
const Model = this.constructor;
|
|
625
|
-
this.referenceNumber = await generateReferenceNumber(this, Model, session);
|
|
632
|
+
if (autoReference) schema.pre("save", async function() {
|
|
633
|
+
if (this.isModified("journalType")) this.referenceNumber = void 0;
|
|
634
|
+
if (!this.referenceNumber) {
|
|
635
|
+
const session = this.$session?.() ?? null;
|
|
636
|
+
const connection = this.constructor.db;
|
|
637
|
+
const journalType = this.journalType || "MISC";
|
|
638
|
+
const date = this.date ? new Date(this.date) : /* @__PURE__ */ new Date();
|
|
639
|
+
const year = date.getFullYear();
|
|
640
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
641
|
+
let orgScope = "global";
|
|
642
|
+
if (multiTenant) {
|
|
643
|
+
const raw = this.get(multiTenant.orgField);
|
|
644
|
+
if (raw != null) orgScope = typeof raw.toHexString === "function" ? raw.toHexString() : String(raw);
|
|
645
|
+
else orgScope = "unscoped";
|
|
626
646
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (mongoError.code === 11e3 && mongoError.keyPattern?.referenceNumber) {
|
|
632
|
-
const entry = doc;
|
|
633
|
-
const retryCount = entry.__refRetries ?? 0;
|
|
634
|
-
if (retryCount >= MAX_REF_RETRIES) {
|
|
635
|
-
next(/* @__PURE__ */ new Error(`Failed to generate unique reference number after ${MAX_REF_RETRIES} retries. Too many concurrent inserts for this period.`));
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
entry.__refRetries = retryCount + 1;
|
|
639
|
-
const session = entry.$session?.() ?? null;
|
|
640
|
-
const Model = entry.constructor;
|
|
641
|
-
entry.referenceNumber = await generateReferenceNumber(entry, Model, session);
|
|
642
|
-
try {
|
|
643
|
-
await entry.save({ session });
|
|
644
|
-
next();
|
|
645
|
-
} catch (retryError) {
|
|
646
|
-
next(retryError);
|
|
647
|
-
}
|
|
648
|
-
} else next(error);
|
|
649
|
-
});
|
|
650
|
-
}
|
|
647
|
+
const seq = await getNextSequence(`ledger:${orgScope}:${journalType}:${year}-${month}`, 1, connection, session ?? void 0);
|
|
648
|
+
this.referenceNumber = `${journalType}/${year}/${month}/${String(seq).padStart(4, "0")}`;
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
651
|
if (indexes) {
|
|
652
652
|
const org = multiTenant?.orgField;
|
|
653
653
|
const refPartial = { partialFilterExpression: { referenceNumber: {
|
|
@@ -713,10 +713,13 @@ function createJournalEntrySchema(config, accountModelName, options = {}) {
|
|
|
713
713
|
idempotencyIdx.idempotencyKey = 1;
|
|
714
714
|
schema.index(idempotencyIdx, {
|
|
715
715
|
unique: true,
|
|
716
|
-
partialFilterExpression: { idempotencyKey: {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
716
|
+
partialFilterExpression: { idempotencyKey: { $type: "string" } }
|
|
717
|
+
});
|
|
718
|
+
const ttlSeconds = typeof config.idempotencyTtlSeconds === "number" && config.idempotencyTtlSeconds > 0 ? config.idempotencyTtlSeconds : 86400;
|
|
719
|
+
schema.index({ createdAt: 1 }, {
|
|
720
|
+
name: "idempotency_ttl_idx",
|
|
721
|
+
expireAfterSeconds: ttlSeconds,
|
|
722
|
+
partialFilterExpression: { idempotencyKey: { $type: "string" } }
|
|
720
723
|
});
|
|
721
724
|
}
|
|
722
725
|
}
|
|
@@ -915,6 +918,15 @@ function createModels(connection, config) {
|
|
|
915
918
|
}
|
|
916
919
|
//#endregion
|
|
917
920
|
//#region src/repositories/account.repository.ts
|
|
921
|
+
async function safePublish$3(events, outboxStore, type, payload, ctx) {
|
|
922
|
+
const event = createEvent(type, payload, ctx);
|
|
923
|
+
if (outboxStore) try {
|
|
924
|
+
await outboxStore.save(event, { session: ctx?.session ?? void 0 });
|
|
925
|
+
} catch {}
|
|
926
|
+
if (events) try {
|
|
927
|
+
await events.publish(event);
|
|
928
|
+
} catch {}
|
|
929
|
+
}
|
|
918
930
|
/**
|
|
919
931
|
* Wire seedAccounts, bulkCreate and posting-account validation
|
|
920
932
|
* onto an existing mongokit Repository.
|
|
@@ -923,7 +935,9 @@ function createModels(connection, config) {
|
|
|
923
935
|
* @param country - The CountryPack for account type lookups
|
|
924
936
|
* @param orgField - The multi-tenant field name (e.g. 'business')
|
|
925
937
|
*/
|
|
926
|
-
function wireAccountMethods(repository, country, orgField) {
|
|
938
|
+
function wireAccountMethods(repository, country, orgField, integrations = {}) {
|
|
939
|
+
const events = integrations.events;
|
|
940
|
+
const outboxStore = integrations.outboxStore;
|
|
927
941
|
repository.on("before:create", (ctx) => {
|
|
928
942
|
const code = ctx.data?.accountTypeCode;
|
|
929
943
|
if (code && !country.isPostingAccount(code)) throw Errors.validation(`Cannot create account with type "${code}" — it is a structural group or calculated total, not a posting account.`);
|
|
@@ -954,8 +968,9 @@ function wireAccountMethods(repository, country, orgField) {
|
|
|
954
968
|
created: 0,
|
|
955
969
|
skipped: existingNumbers.size
|
|
956
970
|
};
|
|
971
|
+
let result;
|
|
957
972
|
try {
|
|
958
|
-
|
|
973
|
+
result = {
|
|
959
974
|
created: (await repository.createMany(toCreate, {
|
|
960
975
|
session: options.session ?? void 0,
|
|
961
976
|
ordered: false
|
|
@@ -966,13 +981,21 @@ function wireAccountMethods(repository, country, orgField) {
|
|
|
966
981
|
const bulkError = err;
|
|
967
982
|
if (bulkError.code === 11e3 || bulkError.writeErrors) {
|
|
968
983
|
const insertedDocs = bulkError.insertedDocs ?? [];
|
|
969
|
-
|
|
984
|
+
result = {
|
|
970
985
|
created: insertedDocs.length,
|
|
971
986
|
skipped: existingNumbers.size + (toCreate.length - insertedDocs.length)
|
|
972
987
|
};
|
|
973
|
-
}
|
|
974
|
-
throw err;
|
|
988
|
+
} else throw err;
|
|
975
989
|
}
|
|
990
|
+
await safePublish$3(events, outboxStore, LEDGER_EVENTS.ACCOUNT_SEEDED, {
|
|
991
|
+
created: result.created,
|
|
992
|
+
skipped: result.skipped,
|
|
993
|
+
organizationId: orgId
|
|
994
|
+
}, {
|
|
995
|
+
organizationId: orgId,
|
|
996
|
+
session: options.session ?? null
|
|
997
|
+
});
|
|
998
|
+
return result;
|
|
976
999
|
};
|
|
977
1000
|
/**
|
|
978
1001
|
* Bulk create accounts with validation and skip-if-exists logic.
|
|
@@ -1090,13 +1113,20 @@ function wireAccountMethods(repository, country, orgField) {
|
|
|
1090
1113
|
} else throw err;
|
|
1091
1114
|
}
|
|
1092
1115
|
}
|
|
1116
|
+
const summary = {
|
|
1117
|
+
total: accounts.length,
|
|
1118
|
+
created: results.created.length,
|
|
1119
|
+
skipped: results.skipped.length,
|
|
1120
|
+
errors: results.errors.length
|
|
1121
|
+
};
|
|
1122
|
+
await safePublish$3(events, outboxStore, LEDGER_EVENTS.ACCOUNT_BULK_CREATED, {
|
|
1123
|
+
created: summary.created,
|
|
1124
|
+
skipped: summary.skipped,
|
|
1125
|
+
errors: summary.errors,
|
|
1126
|
+
organizationId: orgId
|
|
1127
|
+
}, { organizationId: orgId });
|
|
1093
1128
|
return {
|
|
1094
|
-
summary
|
|
1095
|
-
total: accounts.length,
|
|
1096
|
-
created: results.created.length,
|
|
1097
|
-
skipped: results.skipped.length,
|
|
1098
|
-
errors: results.errors.length
|
|
1099
|
-
},
|
|
1129
|
+
summary,
|
|
1100
1130
|
...results
|
|
1101
1131
|
};
|
|
1102
1132
|
};
|
|
@@ -1113,6 +1143,15 @@ function wireAccountMethods(repository, country, orgField) {
|
|
|
1113
1143
|
}
|
|
1114
1144
|
//#endregion
|
|
1115
1145
|
//#region src/repositories/journal.repository.ts
|
|
1146
|
+
async function safePublish$2(events, outboxStore, type, payload, ctx) {
|
|
1147
|
+
const event = createEvent(type, payload, ctx);
|
|
1148
|
+
if (outboxStore) try {
|
|
1149
|
+
await outboxStore.save(event);
|
|
1150
|
+
} catch {}
|
|
1151
|
+
if (events) try {
|
|
1152
|
+
await events.publish(event);
|
|
1153
|
+
} catch {}
|
|
1154
|
+
}
|
|
1116
1155
|
/**
|
|
1117
1156
|
* Lean default set used when a country pack doesn't provide
|
|
1118
1157
|
* `journalTemplates`. Covers the Stripe/QuickBooks/Xero baseline.
|
|
@@ -1154,9 +1193,11 @@ const DEFAULT_TEMPLATES = [
|
|
|
1154
1193
|
sequencePrefix: "JE"
|
|
1155
1194
|
}
|
|
1156
1195
|
];
|
|
1157
|
-
function wireJournalMethods(repository, country, orgField) {
|
|
1196
|
+
function wireJournalMethods(repository, country, orgField, integrations = {}) {
|
|
1158
1197
|
const create = repository.create.bind(repository);
|
|
1159
1198
|
const exists = repository.exists.bind(repository);
|
|
1199
|
+
const events = integrations.events;
|
|
1200
|
+
const outboxStore = integrations.outboxStore;
|
|
1160
1201
|
repository.seedDefaults = async (orgId) => {
|
|
1161
1202
|
requireOrgScope(orgField, orgId);
|
|
1162
1203
|
const templates = country.journalTemplates ?? DEFAULT_TEMPLATES;
|
|
@@ -1182,6 +1223,11 @@ function wireJournalMethods(repository, country, orgField) {
|
|
|
1182
1223
|
await create(data);
|
|
1183
1224
|
created += 1;
|
|
1184
1225
|
}
|
|
1226
|
+
await safePublish$2(events, outboxStore, LEDGER_EVENTS.JOURNAL_SEEDED, {
|
|
1227
|
+
created,
|
|
1228
|
+
skipped,
|
|
1229
|
+
organizationId: orgId
|
|
1230
|
+
}, { organizationId: orgId });
|
|
1185
1231
|
return {
|
|
1186
1232
|
created,
|
|
1187
1233
|
skipped
|
|
@@ -1211,6 +1257,25 @@ function wireJournalMethods(repository, country, orgField) {
|
|
|
1211
1257
|
}
|
|
1212
1258
|
//#endregion
|
|
1213
1259
|
//#region src/repositories/journal-entry.repository.ts
|
|
1260
|
+
/**
|
|
1261
|
+
* Publish a domain event. When an outbox store is provided, first persist
|
|
1262
|
+
* the event inside the caller's session (so outbox + ledger write commit
|
|
1263
|
+
* atomically), then fire-and-forget publish to the transport. Without an
|
|
1264
|
+
* outbox, publish-only, still fire-and-forget — transport errors never
|
|
1265
|
+
* propagate into ledger mutations.
|
|
1266
|
+
*
|
|
1267
|
+
* Tracks PACKAGE_RULES §16 (host-composed transactional outbox) and §14
|
|
1268
|
+
* (domain verbs publish via injected transport).
|
|
1269
|
+
*/
|
|
1270
|
+
async function safePublish$1(events, outboxStore, type, payload, ctx, meta) {
|
|
1271
|
+
const event = createEvent(type, payload, ctx, meta);
|
|
1272
|
+
if (outboxStore) try {
|
|
1273
|
+
await outboxStore.save(event, { session: ctx?.session ?? void 0 });
|
|
1274
|
+
} catch {}
|
|
1275
|
+
if (events) try {
|
|
1276
|
+
await events.publish(event);
|
|
1277
|
+
} catch {}
|
|
1278
|
+
}
|
|
1214
1279
|
/** Keys that are either handled explicitly or must not be copied */
|
|
1215
1280
|
const ITEM_CORE_KEYS = new Set([
|
|
1216
1281
|
"account",
|
|
@@ -1233,11 +1298,58 @@ const ITEM_CORE_KEYS = new Set([
|
|
|
1233
1298
|
* @param orgField - The multi-tenant field name (e.g. 'business')
|
|
1234
1299
|
* @param strictness - Strictness rules (immutable, requireActor, requireApproval)
|
|
1235
1300
|
*/
|
|
1236
|
-
function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, strictness) {
|
|
1301
|
+
function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, strictness, integrations = {}) {
|
|
1302
|
+
const events = integrations.events;
|
|
1303
|
+
const outboxStore = integrations.outboxStore;
|
|
1237
1304
|
const getByQuery = repository.getByQuery.bind(repository);
|
|
1238
|
-
const
|
|
1305
|
+
const baseCreate = repository.create.bind(repository);
|
|
1239
1306
|
const update = repository.update.bind(repository);
|
|
1240
1307
|
const withTransaction = repository.withTransaction.bind(repository);
|
|
1308
|
+
const raceSafeCreate = async (data, options) => {
|
|
1309
|
+
const input = data;
|
|
1310
|
+
const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : void 0;
|
|
1311
|
+
const orgValue = orgField ? input[orgField] : void 0;
|
|
1312
|
+
if (idempotencyKey) {
|
|
1313
|
+
const prequery = { idempotencyKey };
|
|
1314
|
+
if (orgField && orgValue != null) prequery[orgField] = orgValue;
|
|
1315
|
+
const existing = await getByQuery(prequery, {
|
|
1316
|
+
lean: false,
|
|
1317
|
+
throwOnNotFound: false,
|
|
1318
|
+
...options?.session ? { session: options.session } : {}
|
|
1319
|
+
});
|
|
1320
|
+
if (existing) return existing;
|
|
1321
|
+
}
|
|
1322
|
+
try {
|
|
1323
|
+
return await baseCreate(data, options);
|
|
1324
|
+
} catch (err) {
|
|
1325
|
+
if (err instanceof IdempotencyConflictError && err.existingId) {
|
|
1326
|
+
const winner = await getByQuery({ _id: err.existingId }, {
|
|
1327
|
+
lean: false,
|
|
1328
|
+
throwOnNotFound: false,
|
|
1329
|
+
...options?.session ? { session: options.session } : {}
|
|
1330
|
+
});
|
|
1331
|
+
if (winner) return winner;
|
|
1332
|
+
throw err;
|
|
1333
|
+
}
|
|
1334
|
+
const dup = classifyDuplicateKey(err);
|
|
1335
|
+
if (!dup) throw err;
|
|
1336
|
+
if (dup.keyPattern?.referenceNumber) throw new DuplicateReferenceError(String(input.referenceNumber ?? ""));
|
|
1337
|
+
if (dup.keyPattern?.idempotencyKey && idempotencyKey) {
|
|
1338
|
+
const winnerQuery = { idempotencyKey };
|
|
1339
|
+
if (orgField && orgValue != null) winnerQuery[orgField] = orgValue;
|
|
1340
|
+
const winner = await getByQuery(winnerQuery, {
|
|
1341
|
+
lean: false,
|
|
1342
|
+
throwOnNotFound: false,
|
|
1343
|
+
...options?.session ? { session: options.session } : {}
|
|
1344
|
+
});
|
|
1345
|
+
if (winner) return winner;
|
|
1346
|
+
throw new IdempotencyConflictError(idempotencyKey, null);
|
|
1347
|
+
}
|
|
1348
|
+
throw Errors.conflict(`Journal entry write violated unique index ${dup.indexName}.`);
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
repository.create = raceSafeCreate.bind(repository);
|
|
1352
|
+
const create = raceSafeCreate;
|
|
1241
1353
|
const RESERVED_TOPLEVEL = new Set([
|
|
1242
1354
|
"_id",
|
|
1243
1355
|
"__v",
|
|
@@ -1347,7 +1459,23 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
|
|
|
1347
1459
|
_ledgerInternal: "post",
|
|
1348
1460
|
...options.session ? { session: options.session } : {}
|
|
1349
1461
|
};
|
|
1350
|
-
|
|
1462
|
+
const final = await update(entry._id, patch, updateOptions) ?? entry;
|
|
1463
|
+
await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_POSTED, {
|
|
1464
|
+
entryId: final._id,
|
|
1465
|
+
referenceNumber: final.referenceNumber,
|
|
1466
|
+
postedBy: options.actorId,
|
|
1467
|
+
totalDebit,
|
|
1468
|
+
totalCredit,
|
|
1469
|
+
organizationId: orgId
|
|
1470
|
+
}, {
|
|
1471
|
+
actorId: options.actorId,
|
|
1472
|
+
organizationId: orgId,
|
|
1473
|
+
session: options.session ?? null
|
|
1474
|
+
}, {
|
|
1475
|
+
resource: "journal-entry",
|
|
1476
|
+
resourceId: String(final._id)
|
|
1477
|
+
});
|
|
1478
|
+
return final;
|
|
1351
1479
|
};
|
|
1352
1480
|
/**
|
|
1353
1481
|
* Unpost an entry (posted → draft).
|
|
@@ -1366,10 +1494,23 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
|
|
|
1366
1494
|
_ledgerInternal: "unpost",
|
|
1367
1495
|
...options.session ? { session: options.session } : {}
|
|
1368
1496
|
};
|
|
1369
|
-
|
|
1497
|
+
const final = await update(entry._id, {
|
|
1370
1498
|
state: "draft",
|
|
1371
1499
|
stateChangedAt: /* @__PURE__ */ new Date()
|
|
1372
1500
|
}, updateOptions) ?? entry;
|
|
1501
|
+
await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_UNPOSTED, {
|
|
1502
|
+
entryId: final._id,
|
|
1503
|
+
unpostedBy: options.actorId,
|
|
1504
|
+
organizationId: orgId
|
|
1505
|
+
}, {
|
|
1506
|
+
actorId: options.actorId,
|
|
1507
|
+
organizationId: orgId,
|
|
1508
|
+
session: options.session ?? null
|
|
1509
|
+
}, {
|
|
1510
|
+
resource: "journal-entry",
|
|
1511
|
+
resourceId: String(final._id)
|
|
1512
|
+
});
|
|
1513
|
+
return final;
|
|
1373
1514
|
};
|
|
1374
1515
|
/**
|
|
1375
1516
|
* Archive a draft entry (draft → archived).
|
|
@@ -1386,10 +1527,23 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
|
|
|
1386
1527
|
_ledgerInternal: "archive",
|
|
1387
1528
|
...options.session ? { session: options.session } : {}
|
|
1388
1529
|
};
|
|
1389
|
-
|
|
1530
|
+
const final = await update(entry._id, {
|
|
1390
1531
|
state: "archived",
|
|
1391
1532
|
stateChangedAt: /* @__PURE__ */ new Date()
|
|
1392
1533
|
}, updateOptions) ?? entry;
|
|
1534
|
+
await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_ARCHIVED, {
|
|
1535
|
+
entryId: final._id,
|
|
1536
|
+
archivedBy: options.actorId,
|
|
1537
|
+
organizationId: orgId
|
|
1538
|
+
}, {
|
|
1539
|
+
actorId: options.actorId,
|
|
1540
|
+
organizationId: orgId,
|
|
1541
|
+
session: options.session ?? null
|
|
1542
|
+
}, {
|
|
1543
|
+
resource: "journal-entry",
|
|
1544
|
+
resourceId: String(final._id)
|
|
1545
|
+
});
|
|
1546
|
+
return final;
|
|
1393
1547
|
};
|
|
1394
1548
|
/**
|
|
1395
1549
|
* Duplicate an entry as a new draft.
|
|
@@ -1420,7 +1574,21 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
|
|
|
1420
1574
|
})
|
|
1421
1575
|
};
|
|
1422
1576
|
copyExtraTopLevel(entry, duplicateData);
|
|
1423
|
-
|
|
1577
|
+
const duplicated = await create(duplicateData, options.session ? { session: options.session } : {});
|
|
1578
|
+
const dup = duplicated;
|
|
1579
|
+
await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_DUPLICATED, {
|
|
1580
|
+
sourceEntryId: entry._id,
|
|
1581
|
+
duplicateEntryId: dup._id,
|
|
1582
|
+
organizationId: orgId
|
|
1583
|
+
}, {
|
|
1584
|
+
actorId: void 0,
|
|
1585
|
+
organizationId: orgId,
|
|
1586
|
+
session: options.session ?? null
|
|
1587
|
+
}, {
|
|
1588
|
+
resource: "journal-entry",
|
|
1589
|
+
resourceId: String(dup._id)
|
|
1590
|
+
});
|
|
1591
|
+
return duplicated;
|
|
1424
1592
|
};
|
|
1425
1593
|
/**
|
|
1426
1594
|
* Reverse a posted entry by creating a mirror entry with flipped debits/credits.
|
|
@@ -1483,8 +1651,23 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
|
|
|
1483
1651
|
_ledgerInternal: "reverseMark",
|
|
1484
1652
|
...session ? { session } : {}
|
|
1485
1653
|
};
|
|
1654
|
+
const original = await update(entry._id, markPatch, markOptions) ?? entry;
|
|
1655
|
+
await safePublish$1(events, outboxStore, LEDGER_EVENTS.ENTRY_REVERSED, {
|
|
1656
|
+
originalEntryId: original._id,
|
|
1657
|
+
reversalEntryId: reversalEntry._id,
|
|
1658
|
+
reversalDate: reversalData.date ?? /* @__PURE__ */ new Date(),
|
|
1659
|
+
reversedBy: options.actorId,
|
|
1660
|
+
organizationId: orgId
|
|
1661
|
+
}, {
|
|
1662
|
+
actorId: options.actorId,
|
|
1663
|
+
organizationId: orgId,
|
|
1664
|
+
session: session ?? null
|
|
1665
|
+
}, {
|
|
1666
|
+
resource: "journal-entry",
|
|
1667
|
+
resourceId: String(original._id)
|
|
1668
|
+
});
|
|
1486
1669
|
return {
|
|
1487
|
-
original
|
|
1670
|
+
original,
|
|
1488
1671
|
reversal: reversalEntry
|
|
1489
1672
|
};
|
|
1490
1673
|
};
|
|
@@ -1512,6 +1695,15 @@ function wireJournalEntryMethods(repository, _JournalEntryModel, orgField, stric
|
|
|
1512
1695
|
}
|
|
1513
1696
|
//#endregion
|
|
1514
1697
|
//#region src/repositories/reconciliation.repository.ts
|
|
1698
|
+
async function safePublish(events, outboxStore, type, payload, ctx) {
|
|
1699
|
+
const event = createEvent(type, payload, ctx);
|
|
1700
|
+
if (outboxStore) try {
|
|
1701
|
+
await outboxStore.save(event, { session: ctx?.session ?? void 0 });
|
|
1702
|
+
} catch {}
|
|
1703
|
+
if (events) try {
|
|
1704
|
+
await events.publish(event);
|
|
1705
|
+
} catch {}
|
|
1706
|
+
}
|
|
1515
1707
|
/**
|
|
1516
1708
|
* Default matching-number generator — atomic counter stored in the
|
|
1517
1709
|
* reconciliation collection. Uses a dedicated sentinel document keyed
|
|
@@ -1543,10 +1735,13 @@ async function nextMatchingNumber(ReconciliationModel, orgField, orgId, session)
|
|
|
1543
1735
|
}).lean())?.seq ?? 1;
|
|
1544
1736
|
return `RECN-${String(seq).padStart(6, "0")}`;
|
|
1545
1737
|
}
|
|
1546
|
-
function wireReconciliationMethods(repository, ReconciliationModel, JournalEntryModel, orgField) {
|
|
1738
|
+
function wireReconciliationMethods(repository, ReconciliationModel, JournalEntryModel, orgField, integrations = {}) {
|
|
1547
1739
|
const create = repository.create.bind(repository);
|
|
1548
1740
|
const deleteById = repository.delete.bind(repository);
|
|
1549
1741
|
const repoInstance = repository;
|
|
1742
|
+
const events = integrations.events;
|
|
1743
|
+
const outboxStore = integrations.outboxStore;
|
|
1744
|
+
const notification = integrations.bridges?.notification;
|
|
1550
1745
|
const emitHook = repoInstance.emitAsync.bind(repoInstance);
|
|
1551
1746
|
repository.match = async (input) => {
|
|
1552
1747
|
const { account, items, note, reconciledBy, organizationId, session = null } = input;
|
|
@@ -1634,6 +1829,29 @@ function wireReconciliationMethods(repository, ReconciliationModel, JournalEntry
|
|
|
1634
1829
|
...hookCtx,
|
|
1635
1830
|
reconciliation: record
|
|
1636
1831
|
});
|
|
1832
|
+
await safePublish(events, outboxStore, LEDGER_EVENTS.RECONCILIATION_MATCHED, {
|
|
1833
|
+
matchingNumber,
|
|
1834
|
+
account,
|
|
1835
|
+
itemCount: itemSnapshots.length,
|
|
1836
|
+
debitTotal,
|
|
1837
|
+
creditTotal,
|
|
1838
|
+
isFullReconcile,
|
|
1839
|
+
currency: sharedCurrency,
|
|
1840
|
+
organizationId
|
|
1841
|
+
}, {
|
|
1842
|
+
organizationId,
|
|
1843
|
+
session
|
|
1844
|
+
});
|
|
1845
|
+
if (!isFullReconcile && notification?.onReconciliationMismatch) try {
|
|
1846
|
+
await notification.onReconciliationMismatch({
|
|
1847
|
+
matchingNumber,
|
|
1848
|
+
account,
|
|
1849
|
+
debitTotal,
|
|
1850
|
+
creditTotal,
|
|
1851
|
+
difference,
|
|
1852
|
+
currency: sharedCurrency
|
|
1853
|
+
}, { organizationId });
|
|
1854
|
+
} catch {}
|
|
1637
1855
|
return record;
|
|
1638
1856
|
};
|
|
1639
1857
|
repository.unmatch = async (input) => {
|
|
@@ -1660,6 +1878,14 @@ function wireReconciliationMethods(repository, ReconciliationModel, JournalEntry
|
|
|
1660
1878
|
const result = await deleteById(String(existing._id));
|
|
1661
1879
|
if (!result.success) throw Errors.notFound("Failed to delete reconciliation record");
|
|
1662
1880
|
await emitHook("after:unmatch", unmatchCtx);
|
|
1881
|
+
await safePublish(events, outboxStore, LEDGER_EVENTS.RECONCILIATION_UNMATCHED, {
|
|
1882
|
+
matchingNumber,
|
|
1883
|
+
itemCount: items.length,
|
|
1884
|
+
organizationId
|
|
1885
|
+
}, {
|
|
1886
|
+
organizationId,
|
|
1887
|
+
session
|
|
1888
|
+
});
|
|
1663
1889
|
return result;
|
|
1664
1890
|
};
|
|
1665
1891
|
repository.getOpenItems = async (params) => {
|
|
@@ -1731,29 +1957,39 @@ function wireReconciliationMethods(repository, ReconciliationModel, JournalEntry
|
|
|
1731
1957
|
* Matches the flow/promo pattern: engine owns the repositories with all
|
|
1732
1958
|
* plugins (double-entry, fiscal-lock, idempotency) pre-wired.
|
|
1733
1959
|
*
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1960
|
+
* 0.9.0 additions:
|
|
1961
|
+
* - Optional `multiTenantPlugin` adoption (config.multiTenant.plugin)
|
|
1962
|
+
* - Optional `EventTransport` threaded to every wireXxxMethods call
|
|
1963
|
+
* - Optional `LedgerBridges` threaded through so domain verbs can resolve
|
|
1964
|
+
* external sources or send notifications without importing siblings.
|
|
1736
1965
|
*/
|
|
1737
1966
|
/**
|
|
1738
1967
|
* Build all ledger repositories with plugins + domain methods pre-wired.
|
|
1739
|
-
*
|
|
1740
|
-
* - `accounts` — has seedAccounts(), bulkCreate()
|
|
1741
|
-
* - `journalEntries` — has post(), unpost(), reverse(), duplicate() + double-entry + fiscal-lock (+ idempotency if enabled)
|
|
1742
|
-
* - `fiscalPeriods` — plain CRUD
|
|
1743
|
-
* - `budgets` — plain CRUD
|
|
1744
|
-
* - `reconciliations` — has reconcile(), unreconcile(), getUnreconciled()
|
|
1745
1968
|
*/
|
|
1746
|
-
function createRepositories(models, config, plugins = {}, pagination = {}) {
|
|
1969
|
+
function createRepositories(models, config, plugins = {}, pagination = {}, integrations = {}) {
|
|
1747
1970
|
const orgField = config.multiTenant?.orgField;
|
|
1748
1971
|
const strictness = config.strictness;
|
|
1749
1972
|
const country = config.country;
|
|
1973
|
+
const { events, bridges, outboxStore } = integrations;
|
|
1974
|
+
const tenantPlugins = [];
|
|
1975
|
+
if (orgField && config.multiTenant?.plugin) tenantPlugins.push(multiTenantPlugin({
|
|
1976
|
+
tenantField: orgField,
|
|
1977
|
+
contextKey: "organizationId",
|
|
1978
|
+
required: config.multiTenant.required ?? false,
|
|
1979
|
+
fieldType: config.tenantFieldType ?? "string"
|
|
1980
|
+
}));
|
|
1750
1981
|
const accountPagination = pagination.account ?? {};
|
|
1751
1982
|
const jePagination = pagination.journalEntry ?? {};
|
|
1752
1983
|
const fpPagination = pagination.fiscalPeriod ?? {};
|
|
1753
1984
|
const budgetPagination = pagination.budget ?? {};
|
|
1754
1985
|
const reconPagination = pagination.reconciliation ?? {};
|
|
1755
|
-
const accounts = wireAccountMethods(new Repository(models.Account, plugins.account ?? [], accountPagination), country, orgField
|
|
1986
|
+
const accounts = wireAccountMethods(new Repository(models.Account, [...tenantPlugins, ...plugins.account ?? []], accountPagination), country, orgField, {
|
|
1987
|
+
events,
|
|
1988
|
+
bridges,
|
|
1989
|
+
outboxStore
|
|
1990
|
+
});
|
|
1756
1991
|
const jePlugins = [
|
|
1992
|
+
...tenantPlugins,
|
|
1757
1993
|
...plugins.journalEntry ?? [],
|
|
1758
1994
|
doubleEntryPlugin({
|
|
1759
1995
|
JournalEntryModel: models.JournalEntry,
|
|
@@ -1770,10 +2006,22 @@ function createRepositories(models, config, plugins = {}, pagination = {}) {
|
|
|
1770
2006
|
JournalEntryModel: models.JournalEntry,
|
|
1771
2007
|
orgField
|
|
1772
2008
|
}));
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2009
|
+
if (strictness?.immutable) jePlugins.push(immutableGuardPlugin({
|
|
2010
|
+
JournalEntryModel: models.JournalEntry,
|
|
2011
|
+
orgField
|
|
2012
|
+
}));
|
|
2013
|
+
const journalEntries = wireJournalEntryMethods(new Repository(models.JournalEntry, jePlugins, jePagination), models.JournalEntry, orgField, strictness, {
|
|
2014
|
+
events,
|
|
2015
|
+
bridges,
|
|
2016
|
+
outboxStore
|
|
2017
|
+
});
|
|
2018
|
+
const fiscalPeriods = new Repository(models.FiscalPeriod, [...tenantPlugins, ...plugins.fiscalPeriod ?? []], fpPagination);
|
|
2019
|
+
const budgets = new Repository(models.Budget, [...tenantPlugins, ...plugins.budget ?? []], budgetPagination);
|
|
2020
|
+
const reconciliations = wireReconciliationMethods(new Repository(models.Reconciliation, [...tenantPlugins, ...plugins.reconciliation ?? []], reconPagination), models.Reconciliation, models.JournalEntry, orgField, {
|
|
2021
|
+
events,
|
|
2022
|
+
bridges,
|
|
2023
|
+
outboxStore
|
|
2024
|
+
});
|
|
1777
2025
|
const journalPagination = pagination.journal ?? {};
|
|
1778
2026
|
return {
|
|
1779
2027
|
accounts,
|
|
@@ -1781,7 +2029,11 @@ function createRepositories(models, config, plugins = {}, pagination = {}) {
|
|
|
1781
2029
|
fiscalPeriods,
|
|
1782
2030
|
budgets,
|
|
1783
2031
|
reconciliations,
|
|
1784
|
-
journals: wireJournalMethods(new Repository(models.Journal, plugins.journal ?? [], journalPagination), country, orgField
|
|
2032
|
+
journals: wireJournalMethods(new Repository(models.Journal, [...tenantPlugins, ...plugins.journal ?? []], journalPagination), country, orgField, {
|
|
2033
|
+
events,
|
|
2034
|
+
bridges,
|
|
2035
|
+
outboxStore
|
|
2036
|
+
})
|
|
1785
2037
|
};
|
|
1786
2038
|
}
|
|
1787
2039
|
//#endregion
|
|
@@ -2363,14 +2615,48 @@ var AccountingEngine = class {
|
|
|
2363
2615
|
repositories;
|
|
2364
2616
|
record;
|
|
2365
2617
|
introspect;
|
|
2618
|
+
/**
|
|
2619
|
+
* Event transport — structurally matches `@classytic/arc`'s `EventTransport`.
|
|
2620
|
+
* When the host does not inject one, the engine instantiates
|
|
2621
|
+
* `InProcessLedgerBus` (suitable for single-instance deployments only).
|
|
2622
|
+
* Subscribe with glob patterns: `ledger:entry.*`, `ledger:reconciliation.*`, `*`.
|
|
2623
|
+
*/
|
|
2624
|
+
events;
|
|
2625
|
+
/**
|
|
2626
|
+
* Host-provided bridges. Empty object when none supplied. Callers should
|
|
2627
|
+
* optional-chain every method (`engine.bridges.source?.resolve?.(...)`).
|
|
2628
|
+
*/
|
|
2629
|
+
bridges;
|
|
2630
|
+
/**
|
|
2631
|
+
* Host-provided outbox store for durable event delivery (0.9.0). When
|
|
2632
|
+
* present, every domain event is persisted to the outbox in the same
|
|
2633
|
+
* mongoose session as the ledger write before the transport publish.
|
|
2634
|
+
* Undefined when the host opts out of durable delivery.
|
|
2635
|
+
*/
|
|
2636
|
+
outboxStore;
|
|
2366
2637
|
_reports;
|
|
2367
2638
|
constructor(config) {
|
|
2368
2639
|
if (!config.mongoose) throw new Error("createAccountingEngine: `mongoose` connection is required. Pass `mongoose: mongoose.connection` in config.");
|
|
2369
2640
|
this.config = config;
|
|
2370
2641
|
this.country = config.country;
|
|
2371
2642
|
this.currency = config.currency;
|
|
2643
|
+
this.events = config.eventTransport ?? new InProcessLedgerBus();
|
|
2644
|
+
this.bridges = config.bridges ?? {};
|
|
2645
|
+
this.outboxStore = config.outboxStore;
|
|
2372
2646
|
this.models = createModels(config.mongoose, config);
|
|
2373
|
-
this.repositories = createRepositories(this.models, config, config.plugins ?? {}, config.pagination ?? {}
|
|
2647
|
+
this.repositories = createRepositories(this.models, config, config.plugins ?? {}, config.pagination ?? {}, {
|
|
2648
|
+
events: this.events,
|
|
2649
|
+
bridges: this.bridges,
|
|
2650
|
+
outboxStore: this.outboxStore
|
|
2651
|
+
});
|
|
2652
|
+
if (config.syncIndexes) Promise.all([
|
|
2653
|
+
this.models.Account.syncIndexes().catch(() => void 0),
|
|
2654
|
+
this.models.JournalEntry.syncIndexes().catch(() => void 0),
|
|
2655
|
+
this.models.FiscalPeriod.syncIndexes().catch(() => void 0),
|
|
2656
|
+
this.models.Budget.syncIndexes().catch(() => void 0),
|
|
2657
|
+
this.models.Reconciliation.syncIndexes().catch(() => void 0),
|
|
2658
|
+
this.models.Journal.syncIndexes().catch(() => void 0)
|
|
2659
|
+
]);
|
|
2374
2660
|
this.record = buildRecordAPI({
|
|
2375
2661
|
models: this.models,
|
|
2376
2662
|
repositories: this.repositories,
|
|
@@ -2595,4 +2881,4 @@ function buildDimensionIndexes(dimensions, orgField) {
|
|
|
2595
2881
|
});
|
|
2596
2882
|
}
|
|
2597
2883
|
//#endregion
|
|
2598
|
-
export { AccountingEngine, AccountingError, CATEGORIES, CATEGORY_KEYS, CURRENCIES, DEFAULT_BUCKETS, Errors, JOURNAL_CODES, JOURNAL_TYPES, Money, acquireSession, add, allocate, buildAccountTypeMap, buildDimensionFields, buildDimensionIndexes, buildItemFilters, buildRevaluationEntry, calculateTotal, closeFiscalPeriod, computeEndingBalance, computeRevaluation, createAccountingEngine, 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, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, isVirtualTaxAccount, multiply, parseCents, percentage, periodResolver, quickbooksFieldMap, registerJournalType, reopenFiscalPeriod, resolveModelNames, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal, universalFieldMap, watermarkResolver };
|
|
2884
|
+
export { AccountingEngine, AccountingError, CATEGORIES, CATEGORY_KEYS, CURRENCIES, ConcurrencyError, DEFAULT_BUCKETS, DuplicateReferenceError, Errors, IdempotencyConflictError, ImmutableViolationError, InProcessLedgerBus, JOURNAL_CODES, JOURNAL_TYPES, LEDGER_EVENTS, Money, OutboxOwnershipError, acquireSession, add, allocate, buildAccountTypeMap, buildDimensionFields, buildDimensionIndexes, buildItemFilters, buildRevaluationEntry, calculateTotal, classifyDuplicateKey, closeFiscalPeriod, computeEndingBalance, computeRevaluation, createAccountingEngine, createEvent, createLockPlugin, createModels, createRepositories, creditLimitPlugin, dailyLockPlugin, defaultLogger, defineCountryPack, doubleEntryPlugin, exportToCsv, finalizeSession, fiscalLockPlugin, flattenJournalEntries, format, formatPlain, fromDecimal, fxRealizationPlugin, generateAgedBalance, generateBalanceSheet, generateBudgetVsActual, generateCashFlow, generateDimensionBreakdown, generateGeneralLedger, generateIncomeStatement, generatePartnerLedger, generateRevaluation, generateTrialBalance, getCurrency, getCustomJournalTypes, getDateRange, getFiscalYearStart, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, idempotencyPlugin, immutableGuardPlugin, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, isVirtualTaxAccount, multiply, parseCents, percentage, periodResolver, quickbooksFieldMap, registerJournalType, reopenFiscalPeriod, resolveModelNames, splitTaxExclusive, splitTaxInclusive, subtract, toDecimal, universalFieldMap, watermarkResolver };
|