@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.
- package/README.md +2 -0
- package/dist/country/index.d.mts +2 -2
- package/dist/{errors-BmRjW38t.mjs → errors-CSDQPNyt.mjs} +1 -1
- package/dist/fx-realization.plugin-CgQFDGv2.mjs +459 -0
- package/dist/index-BthGypsI.d.mts +228 -0
- package/dist/index.d.mts +50 -105
- package/dist/index.mjs +644 -124
- package/dist/{fiscal-close-Dk3yRT9i.mjs → partner-ledger-D9H5hegI.mjs} +143 -6
- package/dist/plugins/index.d.mts +2 -24
- package/dist/plugins/index.mjs +2 -2
- package/dist/reports/index.d.mts +2 -2
- package/dist/reports/index.mjs +2 -2
- package/dist/tax-hooks-BnVenul5.d.mts +513 -0
- package/dist/{trial-balance-BZ7yOOFD.d.mts → trial-balance-s92GEvRR.d.mts} +75 -2
- package/package.json +6 -5
- package/dist/date-lock.plugin-B2Jy0ukX.mjs +0 -253
- package/dist/idempotency.plugin-CK7LHnBn.d.mts +0 -60
- package/dist/index-GmfEFxVn.d.mts +0 -119
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import { n as Errors } from "./errors-BmRjW38t.mjs";
|
|
2
|
-
//#region src/plugins/double-entry.plugin.ts
|
|
3
|
-
function doubleEntryPlugin(options = {}) {
|
|
4
|
-
const { onlyOnPost = true, JournalEntryModel, AccountModel, orgField } = options;
|
|
5
|
-
function validateItems(items, data) {
|
|
6
|
-
const lineErrors = [];
|
|
7
|
-
for (let i = 0; i < items.length; i++) {
|
|
8
|
-
const d = items[i].debit ?? 0;
|
|
9
|
-
const c = items[i].credit ?? 0;
|
|
10
|
-
if (d > 0 && c > 0) lineErrors.push({
|
|
11
|
-
path: `journalItems.${i}`,
|
|
12
|
-
issue: "line cannot have both debit and credit greater than zero",
|
|
13
|
-
value: {
|
|
14
|
-
debit: d,
|
|
15
|
-
credit: c
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
if (d === 0 && c === 0) lineErrors.push({
|
|
19
|
-
path: `journalItems.${i}`,
|
|
20
|
-
issue: "line cannot have both debit and credit equal to zero",
|
|
21
|
-
value: {
|
|
22
|
-
debit: 0,
|
|
23
|
-
credit: 0
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (lineErrors.length > 0) throw Errors.validation(`Invalid journal line(s): ${lineErrors.map((e) => `${e.path} — ${e.issue}`).join("; ")}`, lineErrors);
|
|
28
|
-
const totalDebit = items.reduce((s, i) => s + (i.debit ?? 0), 0);
|
|
29
|
-
const totalCredit = items.reduce((s, i) => s + (i.credit ?? 0), 0);
|
|
30
|
-
if (totalDebit !== totalCredit) throw Errors.validation(`Double-entry violation: debits (${totalDebit}) ≠ credits (${totalCredit}). Difference: ${Math.abs(totalDebit - totalCredit)}`, [{
|
|
31
|
-
path: "journalItems",
|
|
32
|
-
issue: "debits must equal credits",
|
|
33
|
-
value: {
|
|
34
|
-
totalDebit,
|
|
35
|
-
totalCredit,
|
|
36
|
-
difference: totalDebit - totalCredit
|
|
37
|
-
}
|
|
38
|
-
}]);
|
|
39
|
-
data.totalDebit = totalDebit;
|
|
40
|
-
data.totalCredit = totalCredit;
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
name: "accounting:double-entry",
|
|
44
|
-
apply(repo) {
|
|
45
|
-
const validate = async (context) => {
|
|
46
|
-
const data = context.data;
|
|
47
|
-
if (!data) return;
|
|
48
|
-
if (onlyOnPost && data.state !== "posted") return;
|
|
49
|
-
const items = data.journalItems;
|
|
50
|
-
if (data.state === "posted" && (!items || items.length < 2)) throw Errors.validation(`Cannot post entry: at least 2 journal items required, got ${items?.length ?? 0}.`);
|
|
51
|
-
if (!items || items.length === 0) return;
|
|
52
|
-
validateItems(items, data);
|
|
53
|
-
if (data.state === "posted") {
|
|
54
|
-
if (!AccountModel) throw new Error("doubleEntryPlugin: AccountModel is required to validate posted entries. Pass AccountModel in plugin options to enable account existence and tenant integrity checks.");
|
|
55
|
-
await validateAccounts(items, data, context);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
/** Verify all journal item accounts exist and belong to the same org */
|
|
59
|
-
const validateAccounts = async (items, data, context) => {
|
|
60
|
-
const missingIdxs = [];
|
|
61
|
-
items.forEach((item, idx) => {
|
|
62
|
-
if (item.account == null || item.account === "") missingIdxs.push(idx);
|
|
63
|
-
});
|
|
64
|
-
if (missingIdxs.length > 0) throw Errors.validation(`Posted entry has items with missing accounts at index(es): ${missingIdxs.join(", ")}.`, missingIdxs.map((i) => ({
|
|
65
|
-
path: `journalItems.${i}.account`,
|
|
66
|
-
issue: "account is required on posted entries"
|
|
67
|
-
})));
|
|
68
|
-
const accountIds = items.map((i) => i.account);
|
|
69
|
-
const selectFields = orgField ? `_id ${orgField}` : "_id";
|
|
70
|
-
const accounts = await AccountModel?.find({ _id: { $in: accountIds } }).select(selectFields).session(context.session ?? null).lean();
|
|
71
|
-
const foundIds = new Set(accounts.map((a) => String(a._id)));
|
|
72
|
-
const missingFieldErrors = [];
|
|
73
|
-
items.forEach((item, idx) => {
|
|
74
|
-
if (!foundIds.has(String(item.account))) missingFieldErrors.push({
|
|
75
|
-
path: `journalItems.${idx}.account`,
|
|
76
|
-
issue: "account does not exist",
|
|
77
|
-
value: item.account
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
if (missingFieldErrors.length > 0) throw Errors.validation(`${missingFieldErrors.length} item(s) reference non-existent accounts.`, missingFieldErrors);
|
|
81
|
-
if (orgField && data[orgField] != null) {
|
|
82
|
-
const dataOrg = String(data[orgField]);
|
|
83
|
-
const accountOrgById = new Map(accounts.map((a) => [String(a._id), String(a[orgField])]));
|
|
84
|
-
const crossTenantFieldErrors = [];
|
|
85
|
-
items.forEach((item, idx) => {
|
|
86
|
-
const acctOrg = accountOrgById.get(String(item.account));
|
|
87
|
-
if (acctOrg !== void 0 && acctOrg !== dataOrg) crossTenantFieldErrors.push({
|
|
88
|
-
path: `journalItems.${idx}.account`,
|
|
89
|
-
issue: "account belongs to another organization",
|
|
90
|
-
value: {
|
|
91
|
-
account: item.account,
|
|
92
|
-
expectedOrg: dataOrg,
|
|
93
|
-
actualOrg: acctOrg
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
if (crossTenantFieldErrors.length > 0) throw Errors.validation(`${crossTenantFieldErrors.length} item(s) reference accounts from another organization.`, crossTenantFieldErrors);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
const validateUpdate = async (context) => {
|
|
101
|
-
const data = context.data;
|
|
102
|
-
if (!data) return;
|
|
103
|
-
if (JournalEntryModel) {
|
|
104
|
-
const id = context.id;
|
|
105
|
-
if (id) {
|
|
106
|
-
if ((await JournalEntryModel.findById(id).select("state").session(context.session ?? null).lean())?.state === "posted") {
|
|
107
|
-
if (data.state !== void 0 && data.state !== "posted") throw Errors.immutable("Cannot change state of a posted journal entry. Posted entries are immutable.");
|
|
108
|
-
const allowedKeys = new Set(["state"]);
|
|
109
|
-
if (Object.keys(data).some((k) => !allowedKeys.has(k))) throw Errors.immutable("Cannot modify a posted journal entry. Use reverse() to create a correcting entry instead.");
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (onlyOnPost && data.state !== "posted") return;
|
|
114
|
-
const items = data.journalItems;
|
|
115
|
-
if (items !== void 0) {
|
|
116
|
-
if (items.length < 2) throw Errors.validation(`Cannot post entry: at least 2 journal items required, got ${items.length}.`);
|
|
117
|
-
validateItems(items, data);
|
|
118
|
-
if (AccountModel) await validateAccounts(items, data, context);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (!JournalEntryModel) throw new Error("doubleEntryPlugin: JournalEntryModel is required to validate partial updates that set state to \"posted\". Pass JournalEntryModel in plugin options.");
|
|
122
|
-
const id = context.id;
|
|
123
|
-
if (!id) throw new Error("doubleEntryPlugin: update context is missing \"id\". Cannot validate partial post without document ID.");
|
|
124
|
-
const existing = await JournalEntryModel.findById(id).select("journalItems").session(context.session ?? null).lean();
|
|
125
|
-
if (!existing) return;
|
|
126
|
-
const persistedItems = existing.journalItems;
|
|
127
|
-
if (!persistedItems || persistedItems.length < 2) throw Errors.validation(`Cannot post entry: at least 2 journal items required, got ${persistedItems?.length ?? 0}.`);
|
|
128
|
-
validateItems(persistedItems, data);
|
|
129
|
-
if (AccountModel) await validateAccounts(persistedItems, {
|
|
130
|
-
...data,
|
|
131
|
-
...existing
|
|
132
|
-
}, context);
|
|
133
|
-
};
|
|
134
|
-
repo.on("before:create", validate);
|
|
135
|
-
repo.on("before:update", validateUpdate);
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
//#endregion
|
|
140
|
-
//#region src/plugins/fiscal-lock.plugin.ts
|
|
141
|
-
function fiscalLockPlugin(options) {
|
|
142
|
-
const { FiscalPeriodModel, JournalEntryModel, orgField } = options;
|
|
143
|
-
return {
|
|
144
|
-
name: "accounting:fiscal-lock",
|
|
145
|
-
apply(repo) {
|
|
146
|
-
const checkPeriod = async (context, isUpdate) => {
|
|
147
|
-
const data = context.data;
|
|
148
|
-
if (!data) return;
|
|
149
|
-
if (data.state !== "posted") return;
|
|
150
|
-
const session = context.session ?? null;
|
|
151
|
-
let entryDate;
|
|
152
|
-
let persistedDoc = null;
|
|
153
|
-
if (data.date) entryDate = new Date(data.date);
|
|
154
|
-
else if (!isUpdate) entryDate = /* @__PURE__ */ new Date();
|
|
155
|
-
else {
|
|
156
|
-
if (!context.id) throw new Error("fiscalLockPlugin: update context is missing \"id\". Cannot validate fiscal lock without document ID.");
|
|
157
|
-
if (!JournalEntryModel) throw new Error("fiscalLockPlugin: JournalEntryModel is required to validate partial updates that set state to \"posted\". Pass JournalEntryModel in plugin options.");
|
|
158
|
-
const selectFields = orgField ? `date ${orgField}` : "date";
|
|
159
|
-
persistedDoc = await JournalEntryModel.findById(context.id).select(selectFields).session(session).lean();
|
|
160
|
-
if (persistedDoc?.date) entryDate = new Date(persistedDoc.date);
|
|
161
|
-
}
|
|
162
|
-
if (!entryDate) return;
|
|
163
|
-
const query = {
|
|
164
|
-
startDate: { $lte: entryDate },
|
|
165
|
-
endDate: { $gte: entryDate },
|
|
166
|
-
closed: true
|
|
167
|
-
};
|
|
168
|
-
if (orgField) {
|
|
169
|
-
let orgValue = data[orgField] ?? context[orgField];
|
|
170
|
-
if (!orgValue && isUpdate) {
|
|
171
|
-
if (persistedDoc) orgValue = persistedDoc[orgField];
|
|
172
|
-
else if (context.id && JournalEntryModel) {
|
|
173
|
-
const persisted = await JournalEntryModel.findById(context.id).select(orgField).session(session).lean();
|
|
174
|
-
if (persisted) orgValue = persisted[orgField];
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (!orgValue) throw new Error(`fiscalLockPlugin: orgField "${orgField}" is configured but could not be resolved from payload, context, or persisted document. Refusing to run unscoped fiscal period query.`);
|
|
178
|
-
query[orgField] = orgValue;
|
|
179
|
-
}
|
|
180
|
-
const closedPeriod = await FiscalPeriodModel.findOne(query).session(session).lean();
|
|
181
|
-
if (closedPeriod) {
|
|
182
|
-
const period = closedPeriod;
|
|
183
|
-
throw Errors.fiscal(`Cannot post entry dated ${entryDate.toISOString().split("T")[0]}: fiscal period "${period.name}" is closed.`);
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
repo.on("before:create", (ctx) => checkPeriod(ctx, false));
|
|
187
|
-
repo.on("before:update", (ctx) => checkPeriod(ctx, true));
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
//#endregion
|
|
192
|
-
//#region src/plugins/idempotency.plugin.ts
|
|
193
|
-
function idempotencyPlugin(options) {
|
|
194
|
-
const { JournalEntryModel, orgField } = options;
|
|
195
|
-
return {
|
|
196
|
-
name: "accounting:idempotency",
|
|
197
|
-
apply(repo) {
|
|
198
|
-
repo.on("before:create", async (context) => {
|
|
199
|
-
const data = context.data;
|
|
200
|
-
if (!data?.idempotencyKey) return;
|
|
201
|
-
const query = { idempotencyKey: data.idempotencyKey };
|
|
202
|
-
if (orgField && data[orgField]) query[orgField] = data[orgField];
|
|
203
|
-
const existing = await JournalEntryModel.findOne(query).select("_id").session(context.session ?? null).lean();
|
|
204
|
-
if (existing) throw Errors.conflict(`Duplicate idempotency key: "${data.idempotencyKey}". Existing entry: ${existing._id}`);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
//#endregion
|
|
210
|
-
//#region src/plugins/date-lock.plugin.ts
|
|
211
|
-
function dateLockPlugin(options) {
|
|
212
|
-
const { getLockDate, JournalEntryModel, orgField } = options;
|
|
213
|
-
return {
|
|
214
|
-
name: "accounting:date-lock",
|
|
215
|
-
apply(repo) {
|
|
216
|
-
const checkLock = async (context, isUpdate) => {
|
|
217
|
-
const data = context.data;
|
|
218
|
-
if (!data) return;
|
|
219
|
-
if (data.state !== "posted") return;
|
|
220
|
-
const session = context.session ?? null;
|
|
221
|
-
let entryDate;
|
|
222
|
-
let persistedDoc = null;
|
|
223
|
-
if (data.date) entryDate = new Date(data.date);
|
|
224
|
-
else if (!isUpdate) entryDate = /* @__PURE__ */ new Date();
|
|
225
|
-
else {
|
|
226
|
-
if (!context.id) throw new Error("dateLockPlugin: update context is missing \"id\". Cannot validate date lock without document ID.");
|
|
227
|
-
const selectFields = orgField ? `date ${orgField}` : "date";
|
|
228
|
-
persistedDoc = await JournalEntryModel.findById(context.id).select(selectFields).session(session).lean();
|
|
229
|
-
if (persistedDoc?.date) entryDate = new Date(persistedDoc.date);
|
|
230
|
-
}
|
|
231
|
-
if (!entryDate) return;
|
|
232
|
-
let orgValue;
|
|
233
|
-
if (orgField) {
|
|
234
|
-
orgValue = data[orgField] ?? context[orgField];
|
|
235
|
-
if (!orgValue && isUpdate) {
|
|
236
|
-
if (persistedDoc) orgValue = persistedDoc[orgField];
|
|
237
|
-
else if (context.id) {
|
|
238
|
-
const persisted = await JournalEntryModel.findById(context.id).select(orgField).session(session).lean();
|
|
239
|
-
if (persisted) orgValue = persisted[orgField];
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const lockDate = await getLockDate(orgValue, session ?? void 0);
|
|
244
|
-
if (!lockDate) return;
|
|
245
|
-
if (entryDate < lockDate) throw Errors.fiscal(`Cannot post entry dated ${entryDate.toISOString().split("T")[0]}: date is before lock date ${lockDate.toISOString().split("T")[0]}.`);
|
|
246
|
-
};
|
|
247
|
-
repo.on("before:create", (ctx) => checkLock(ctx, false));
|
|
248
|
-
repo.on("before:update", (ctx) => checkLock(ctx, true));
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
//#endregion
|
|
253
|
-
export { doubleEntryPlugin as i, idempotencyPlugin as n, fiscalLockPlugin as r, dateLockPlugin as t };
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { ClientSession, Model } from "mongoose";
|
|
2
|
-
import { RepositoryInstance } from "@classytic/mongokit";
|
|
3
|
-
|
|
4
|
-
//#region src/plugins/date-lock.plugin.d.ts
|
|
5
|
-
interface DateLockPluginOptions {
|
|
6
|
-
/** Async function to resolve the lock date for a given org. Return null for no lock. */
|
|
7
|
-
getLockDate: (orgId?: unknown, session?: ClientSession) => Promise<Date | null>;
|
|
8
|
-
/** Mongoose model for journal entries (needed for partial updates) */
|
|
9
|
-
JournalEntryModel: Model<unknown>;
|
|
10
|
-
/** Org field name */
|
|
11
|
-
orgField?: string;
|
|
12
|
-
}
|
|
13
|
-
declare function dateLockPlugin(options: DateLockPluginOptions): {
|
|
14
|
-
name: string;
|
|
15
|
-
apply(repo: RepositoryInstance): void;
|
|
16
|
-
};
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region src/plugins/double-entry.plugin.d.ts
|
|
19
|
-
interface DoubleEntryPluginOptions {
|
|
20
|
-
/** Only enforce on posted entries (default: true) */
|
|
21
|
-
onlyOnPost?: boolean;
|
|
22
|
-
/** Mongoose model — required to validate partial updates that only set state */
|
|
23
|
-
JournalEntryModel?: Model<unknown>;
|
|
24
|
-
/** Account model — when provided, posted creates verify account existence + tenant scoping */
|
|
25
|
-
AccountModel?: Model<unknown>;
|
|
26
|
-
/** Multi-tenant org field name (e.g. 'business'). Required for tenant-account integrity checks. */
|
|
27
|
-
orgField?: string;
|
|
28
|
-
}
|
|
29
|
-
declare function doubleEntryPlugin(options?: DoubleEntryPluginOptions): {
|
|
30
|
-
name: string;
|
|
31
|
-
apply(repo: RepositoryInstance): void;
|
|
32
|
-
};
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/plugins/fiscal-lock.plugin.d.ts
|
|
35
|
-
interface FiscalLockPluginOptions {
|
|
36
|
-
/** Mongoose model for fiscal periods */
|
|
37
|
-
FiscalPeriodModel: Model<unknown>;
|
|
38
|
-
/** Mongoose model for journal entries — needed to look up persisted date on partial updates */
|
|
39
|
-
JournalEntryModel?: Model<unknown>;
|
|
40
|
-
/** Organization field name (for multi-tenant) */
|
|
41
|
-
orgField?: string;
|
|
42
|
-
}
|
|
43
|
-
declare function fiscalLockPlugin(options: FiscalLockPluginOptions): {
|
|
44
|
-
name: string;
|
|
45
|
-
apply(repo: RepositoryInstance): void;
|
|
46
|
-
};
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/plugins/idempotency.plugin.d.ts
|
|
49
|
-
interface IdempotencyPluginOptions {
|
|
50
|
-
/** Mongoose model for journal entries */
|
|
51
|
-
JournalEntryModel: Model<unknown>;
|
|
52
|
-
/** Multi-tenant org field name */
|
|
53
|
-
orgField?: string;
|
|
54
|
-
}
|
|
55
|
-
declare function idempotencyPlugin(options: IdempotencyPluginOptions): {
|
|
56
|
-
name: string;
|
|
57
|
-
apply(repo: RepositoryInstance): void;
|
|
58
|
-
};
|
|
59
|
-
//#endregion
|
|
60
|
-
export { DoubleEntryPluginOptions as a, dateLockPlugin as c, fiscalLockPlugin as i, idempotencyPlugin as n, doubleEntryPlugin as o, FiscalLockPluginOptions as r, DateLockPluginOptions as s, IdempotencyPluginOptions as t };
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { t as AccountType } from "./core-BkGjuVZj.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/country/index.d.ts
|
|
4
|
-
interface TaxCode {
|
|
5
|
-
readonly code: string;
|
|
6
|
-
readonly name: string;
|
|
7
|
-
readonly taxType: string;
|
|
8
|
-
readonly rate: number;
|
|
9
|
-
readonly direction: 'collected' | 'recoverable' | 'paid';
|
|
10
|
-
readonly province?: string;
|
|
11
|
-
readonly reportLines?: readonly number[];
|
|
12
|
-
readonly description: string;
|
|
13
|
-
readonly active: boolean;
|
|
14
|
-
}
|
|
15
|
-
interface TaxCodesByRegion {
|
|
16
|
-
readonly [region: string]: readonly string[];
|
|
17
|
-
}
|
|
18
|
-
interface TaxReportLine {
|
|
19
|
-
readonly line: number | string;
|
|
20
|
-
readonly name: string;
|
|
21
|
-
readonly description: string;
|
|
22
|
-
readonly type: 'input' | 'calculated' | 'manual';
|
|
23
|
-
readonly calculate?: (data: Record<string | number, number>) => number;
|
|
24
|
-
readonly section: string;
|
|
25
|
-
}
|
|
26
|
-
interface TaxReportTemplate {
|
|
27
|
-
readonly name: string;
|
|
28
|
-
readonly lines: Readonly<Record<string | number, TaxReportLine>>;
|
|
29
|
-
calculate(inputData: Record<string | number, number>, manualData?: Record<string | number, number>): Record<string | number, number>;
|
|
30
|
-
summarize(calculated: Record<string | number, number>): Record<string, unknown>;
|
|
31
|
-
}
|
|
32
|
-
interface CountryPack {
|
|
33
|
-
/** ISO 3166-1 alpha-2 code (e.g., 'CA', 'US', 'GB') */
|
|
34
|
-
readonly code: string;
|
|
35
|
-
/** Country name */
|
|
36
|
-
readonly name: string;
|
|
37
|
-
/** Default currency code */
|
|
38
|
-
readonly defaultCurrency: string;
|
|
39
|
-
/**
|
|
40
|
-
* Full chart of accounts template — flat array of account type definitions.
|
|
41
|
-
* Includes both regular accounts and virtual tax sub-accounts.
|
|
42
|
-
*/
|
|
43
|
-
readonly accountTypes: readonly AccountType[];
|
|
44
|
-
/** Tax codes indexed by code string */
|
|
45
|
-
readonly taxCodes: Readonly<Record<string, TaxCode>>;
|
|
46
|
-
/** Tax codes grouped by region/province/state */
|
|
47
|
-
readonly taxCodesByRegion: TaxCodesByRegion;
|
|
48
|
-
/** Available regions (provinces/states) */
|
|
49
|
-
readonly regions: readonly string[];
|
|
50
|
-
/** Tax report template (e.g., CRA GST/HST return) */
|
|
51
|
-
readonly taxReport?: TaxReportTemplate;
|
|
52
|
-
/**
|
|
53
|
-
* The retained earnings account code — the account that holds accumulated
|
|
54
|
-
* retained earnings (e.g. '3600' CA, '3310' BD).
|
|
55
|
-
*
|
|
56
|
-
* On the balance sheet, this account is excluded from normal equity grouping
|
|
57
|
-
* and its balance is folded into the computed "Retained Earnings" section
|
|
58
|
-
* (opening RE = RE account balance + prior-year unclosed P&L).
|
|
59
|
-
*
|
|
60
|
-
* Inspired by Odoo's `equity_unaffected` account type.
|
|
61
|
-
*/
|
|
62
|
-
readonly retainedEarningsAccountCode?: string;
|
|
63
|
-
/**
|
|
64
|
-
* Display code for the "Previous Years Retained Earnings" line on the
|
|
65
|
-
* balance sheet (e.g. '3660' for CA GIFI). Defaults to retainedEarningsAccountCode.
|
|
66
|
-
*/
|
|
67
|
-
readonly retainedEarningsDisplayCode?: string;
|
|
68
|
-
/** Display code for current year net income line (e.g. '3680' CA, '3311' BD) */
|
|
69
|
-
readonly currentYearEarningsCode?: string;
|
|
70
|
-
/** Group label code used to identify Cost of Sales in the income statement */
|
|
71
|
-
readonly cogsGroupCode?: string;
|
|
72
|
-
/** Override default English report section names */
|
|
73
|
-
readonly reportLabels?: {
|
|
74
|
-
readonly assets?: string;
|
|
75
|
-
readonly liabilities?: string;
|
|
76
|
-
readonly equity?: string;
|
|
77
|
-
readonly revenue?: string;
|
|
78
|
-
readonly expenses?: string;
|
|
79
|
-
};
|
|
80
|
-
/** Get all account types that can be posted to (not groups, not totals) */
|
|
81
|
-
getPostingAccountTypes(): readonly AccountType[];
|
|
82
|
-
/** Get account type by code */
|
|
83
|
-
getAccountType(code: string): AccountType | undefined;
|
|
84
|
-
/** Validate an account type code exists */
|
|
85
|
-
isValidAccountType(code: string): boolean;
|
|
86
|
-
/** Check if an account type can receive postings */
|
|
87
|
-
isPostingAccount(code: string): boolean;
|
|
88
|
-
/** Get tax codes for a specific region */
|
|
89
|
-
getTaxCodesForRegion(region: string): TaxCode[];
|
|
90
|
-
/** Flatten hierarchical accounts (if needed) */
|
|
91
|
-
flattenAccountTypes(): readonly AccountType[];
|
|
92
|
-
}
|
|
93
|
-
interface CountryPackInput {
|
|
94
|
-
code: string;
|
|
95
|
-
name: string;
|
|
96
|
-
defaultCurrency: string;
|
|
97
|
-
accountTypes: readonly AccountType[];
|
|
98
|
-
taxCodes: Readonly<Record<string, TaxCode>>;
|
|
99
|
-
taxCodesByRegion: TaxCodesByRegion;
|
|
100
|
-
regions: readonly string[];
|
|
101
|
-
taxReport?: TaxReportTemplate;
|
|
102
|
-
retainedEarningsAccountCode?: string;
|
|
103
|
-
retainedEarningsDisplayCode?: string;
|
|
104
|
-
currentYearEarningsCode?: string;
|
|
105
|
-
cogsGroupCode?: string;
|
|
106
|
-
reportLabels?: {
|
|
107
|
-
readonly assets?: string;
|
|
108
|
-
readonly liabilities?: string;
|
|
109
|
-
readonly equity?: string;
|
|
110
|
-
readonly revenue?: string;
|
|
111
|
-
readonly expenses?: string;
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Factory to create a CountryPack with auto-generated helper methods.
|
|
116
|
-
*/
|
|
117
|
-
declare function defineCountryPack(input: CountryPackInput): CountryPack;
|
|
118
|
-
//#endregion
|
|
119
|
-
export { TaxReportLine as a, TaxCodesByRegion as i, CountryPackInput as n, TaxReportTemplate as o, TaxCode as r, defineCountryPack as s, CountryPack as t };
|