@classytic/ledger 0.3.0 → 0.4.1
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 +82 -20
- package/dist/constants/index.d.mts +2 -2
- package/dist/constants/index.mjs +3 -3
- package/dist/{date-lock.plugin-eYAJ9h_u.mjs → date-lock.plugin-DL6pe24p.mjs} +2 -2
- package/dist/{engine-Cn-9yerQ.d.mts → engine-scgOvxHJ.d.mts} +30 -2
- package/dist/exports/index.d.mts +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/{exports-I5Xkq-9_.mjs → exports-DoGQQtMQ.mjs} +96 -75
- package/dist/{fiscal-close-B6LhQ10f.mjs → fiscal-close-B2_7WMTe.mjs} +748 -751
- package/dist/{index-BPukb3L8.d.mts → index-J-XIbXH-.d.mts} +7 -7
- package/dist/index.d.mts +239 -87
- package/dist/index.mjs +149 -12
- package/dist/{fiscal-period.schema-BMnlI9H5.d.mts → journal-entry.schema-JqrfbvB4.d.mts} +12 -12
- package/dist/{journals-oH-FK3g8.mjs → journals-BfwnCFam.mjs} +27 -4
- package/dist/{currencies-4WAbFRlw.d.mts → journals-DTipb_rz.d.mts} +16 -7
- package/dist/money.mjs +2 -2
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +1 -1
- package/dist/{reconciliation.repository-CW4-8q90.d.mts → reconciliation.repository-D-D_ITL-.d.mts} +14 -14
- package/dist/{account.repository-BpkSd6q3.mjs → reconciliation.repository-fPwFKvrk.mjs} +255 -255
- package/dist/{reconciliation.schema-BuetvZTd.mjs → reconciliation.schema-BA1lPv4t.mjs} +174 -173
- package/dist/reports/index.d.mts +1 -1
- package/dist/reports/index.mjs +1 -1
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/schemas/index.d.mts +6 -6
- package/dist/schemas/index.mjs +1 -1
- package/dist/{tenant-guard-Fm6AID_6.mjs → tenant-guard-r17Se3Bb.mjs} +1 -1
- package/dist/{revaluation-D9x0NE8w.d.mts → trial-balance-DcQ0xj_4.d.mts} +124 -124
- package/docs/schemas.md +2 -2
- package/package.json +14 -6
- /package/dist/{categories-CclX7Q94.mjs → categories-DWogBUgQ.mjs} +0 -0
- /package/dist/{errors-B7yC-Jfw.mjs → errors-B_dyYZc_.mjs} +0 -0
- /package/dist/{idempotency.plugin-B_CNsInz.d.mts → idempotency.plugin-zU-GKJ0-.d.mts} +0 -0
- /package/dist/{logger-CbHWZl7v.d.mts → logger-UbTdBb1x.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @classytic/ledger
|
|
2
2
|
|
|
3
|
-
Embeddable double-entry accounting engine for MongoDB. Integer-cents arithmetic, plugin-based, country-agnostic.
|
|
3
|
+
Embeddable double-entry accounting engine for MongoDB. Integer-cents arithmetic, plugin-based, country-agnostic. Extensible journal types, multi-tenant isolation at every layer.
|
|
4
4
|
|
|
5
5
|
Build QuickBooks, Xero, or TaxCycle-grade apps — the engine handles the accounting, you handle the UX.
|
|
6
6
|
|
|
@@ -15,23 +15,49 @@ npm install @classytic/ledger-bd # Bangladesh (BFRS, VAT/TDS, Mushak)
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
+
import mongoose from 'mongoose';
|
|
18
19
|
import { createAccountingEngine } from '@classytic/ledger';
|
|
19
20
|
import { canadaPack } from '@classytic/ledger-ca';
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
// The engine owns the models — matches flow/promo pattern
|
|
23
|
+
const engine = createAccountingEngine({
|
|
24
|
+
mongoose: mongoose.connection,
|
|
22
25
|
country: canadaPack,
|
|
23
26
|
currency: 'CAD',
|
|
24
|
-
multiTenant: { orgField: '
|
|
27
|
+
multiTenant: { orgField: 'organizationId', orgRef: 'Organization' },
|
|
25
28
|
});
|
|
26
29
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
30
|
+
// Models, repositories, and reports are auto-created
|
|
31
|
+
await engine.repositories.accounts.seedAccounts(orgId);
|
|
32
|
+
const entry = await engine.repositories.journalEntries.post(entryId, orgId);
|
|
33
|
+
const bs = await engine.reports.balanceSheet({ organizationId: orgId, dateOption: 'year', dateValue: 2025 });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Engine-owned models (recommended)
|
|
37
|
+
|
|
38
|
+
Pass `mongoose: connection` in config and the engine creates and wires everything:
|
|
39
|
+
|
|
40
|
+
| Property | What it gives you |
|
|
41
|
+
|----------|-------------------|
|
|
42
|
+
| `engine.models.Account` / `JournalEntry` / `FiscalPeriod` / `Budget` / `Reconciliation` | Mongoose models, ready to query |
|
|
43
|
+
| `engine.repositories.accounts.seedAccounts()` / `bulkCreate()` | Account repo with domain methods |
|
|
44
|
+
| `engine.repositories.journalEntries.post()` / `reverse()` / `unpost()` / `duplicate()` | JE repo with plugins pre-wired (double-entry, fiscal-lock, idempotency) |
|
|
45
|
+
| `engine.repositories.fiscalPeriods` / `budgets` | Plain CRUD repos |
|
|
46
|
+
| `engine.repositories.reconciliations.reconcile()` / `unreconcile()` / `getUnreconciled()` | Reconciliation repo with domain methods |
|
|
47
|
+
| `engine.reports.trialBalance()` / `balanceSheet()` / etc. | All 10 reports, bound to the owned models |
|
|
48
|
+
|
|
49
|
+
This pattern unblocks framework auto-discovery (Arc `loadResources`, Fastify plugins, etc.) because resources can be defined at module top-level without factory wrappers.
|
|
50
|
+
|
|
51
|
+
### Low-level (manual schema/model setup)
|
|
31
52
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
If you need custom naming or don't want the engine to own models, omit `mongoose` from config:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const engine = createAccountingEngine({ country: canadaPack, currency: 'CAD' });
|
|
57
|
+
const Account = mongoose.model('GLAccount', engine.createAccountSchema());
|
|
58
|
+
const JournalEntry = mongoose.model('GLEntry', engine.createJournalEntrySchema('GLAccount'));
|
|
59
|
+
const accountRepo = engine.wireAccountRepository(new Repository(Account), Account);
|
|
60
|
+
const reports = engine.createReports({ Account, JournalEntry });
|
|
35
61
|
```
|
|
36
62
|
|
|
37
63
|
## Core Features
|
|
@@ -41,8 +67,9 @@ const bs = await reports.balanceSheet({ organizationId, dateOption: 'year', date
|
|
|
41
67
|
- Integer-cents storage — zero floating-point drift
|
|
42
68
|
- Draft → Posted → Reversed state machine
|
|
43
69
|
- Configurable immutability (corrections only via reversal)
|
|
44
|
-
- Multi-tenant isolation at every layer
|
|
70
|
+
- Multi-tenant isolation at every layer (reports, schemas, repositories)
|
|
45
71
|
- Country packs for localized charts of accounts and tax codes
|
|
72
|
+
- Extensible journal type registry — add domain-specific types (POS, E-Commerce, Payroll) at startup
|
|
46
73
|
|
|
47
74
|
**10 Reports**
|
|
48
75
|
- Trial Balance (3-column: initial + period + ending)
|
|
@@ -155,7 +182,36 @@ taxHookPlugin({
|
|
|
155
182
|
| `@classytic/ledger/repositories` | Repository wiring |
|
|
156
183
|
| `@classytic/ledger/exports` | CSV export + QuickBooks field maps |
|
|
157
184
|
| `@classytic/ledger/country` | `defineCountryPack`, `CountryPack` interface |
|
|
158
|
-
| `@classytic/ledger/constants` | Categories, journal types, currencies |
|
|
185
|
+
| `@classytic/ledger/constants` | Categories, journal types (+ registry), currencies |
|
|
186
|
+
|
|
187
|
+
## Extensible Journal Types
|
|
188
|
+
|
|
189
|
+
The 15 built-in journal types (SALES, PURCHASES, GENERAL, PAYROLL, etc.) cover standard accounting. For domain-specific needs, register custom types **before** schema creation:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { registerJournalType, getJournalTypeCodes, isValidJournalType } from '@classytic/ledger';
|
|
193
|
+
|
|
194
|
+
// Register at startup, before createJournalEntrySchema()
|
|
195
|
+
registerJournalType('POS_SALES', {
|
|
196
|
+
code: 'POS_SALES',
|
|
197
|
+
name: 'POS Sales Journal',
|
|
198
|
+
description: 'Daily aggregated point-of-sale transactions',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
registerJournalType('ECOM_SALES', {
|
|
202
|
+
code: 'ECOM_SALES',
|
|
203
|
+
name: 'E-Commerce Sales Journal',
|
|
204
|
+
description: 'Per-order online transactions',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Custom types pass Mongoose enum validation, appear in all lookups
|
|
208
|
+
isValidJournalType('POS_SALES'); // true
|
|
209
|
+
getJournalTypeCodes(); // [...15 built-in, 'POS_SALES', 'ECOM_SALES']
|
|
210
|
+
|
|
211
|
+
// Reference numbers use the custom type prefix: POS_SALES/2025/03/0001
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The registry freezes when `createJournalEntrySchema()` is called. Late registration throws. Built-in types cannot be overridden.
|
|
159
215
|
|
|
160
216
|
## Country Packs
|
|
161
217
|
|
|
@@ -180,25 +236,31 @@ Available packs: `@classytic/ledger-ca` (Canada), `@classytic/ledger-bd` (Bangla
|
|
|
180
236
|
|
|
181
237
|
## Testing
|
|
182
238
|
|
|
183
|
-
949 tests covering unit, integration, and end-to-end scenarios:
|
|
184
|
-
|
|
185
239
|
```bash
|
|
186
|
-
npm test
|
|
187
|
-
npx vitest run tests/e2e/
|
|
240
|
+
npm test # run all
|
|
241
|
+
npx vitest run tests/e2e/ # e2e scenarios only
|
|
242
|
+
npx vitest run tests/scenarios/ # integration scenarios
|
|
243
|
+
npx vitest run tests/hardening/ # edge cases & invariants
|
|
188
244
|
```
|
|
189
245
|
|
|
190
|
-
|
|
246
|
+
Test suites cover:
|
|
191
247
|
- Canadian small business full-year lifecycle
|
|
192
248
|
- Multi-currency trading with FX revaluation
|
|
193
|
-
-
|
|
249
|
+
- Multi-tenant report isolation (org A cannot see org B)
|
|
250
|
+
- Posting pipeline → Trial Balance → Income Statement → Balance Sheet
|
|
251
|
+
- Reversal & correction workflows with audit trail
|
|
252
|
+
- Custom journal type registry → schema → posting pipeline
|
|
253
|
+
- Double-entry conservation law (debit = credit across all entries)
|
|
254
|
+
- Money arithmetic hardening (overflow, penny-leak, float traps)
|
|
255
|
+
- Public API surface & subpath export verification
|
|
194
256
|
- O-Level / A-Level / university textbook accounting problems
|
|
195
257
|
|
|
196
258
|
## Requirements
|
|
197
259
|
|
|
198
260
|
- Node.js >= 22
|
|
199
261
|
- MongoDB (replica set recommended for transactions)
|
|
200
|
-
- Mongoose >= 9
|
|
201
|
-
- @classytic/mongokit >= 3
|
|
262
|
+
- Mongoose >= 9.4.1
|
|
263
|
+
- @classytic/mongokit >= 3.5.3
|
|
202
264
|
|
|
203
265
|
## License
|
|
204
266
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType };
|
|
1
|
+
import { S as isValidCategory, _ as getCategoryMainType, a as getJournalTypeCodes, b as isBalanceSheet, c as CURRENCIES, d as isValidCurrency, f as CATEGORIES, g as extractStatementType, h as extractMainType, i as getJournalType, l as getCurrency, m as categoryKey, n as JOURNAL_TYPES, o as isValidJournalType, p as CATEGORY_KEYS, r as getCustomJournalTypes, s as registerJournalType, t as JOURNAL_CODES, u as getMinorUnit, v as getCategoryStatementType, x as isIncomeStatement, y as getNormalBalance } from "../journals-DTipb_rz.mjs";
|
|
2
|
+
export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getCustomJournalTypes, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, registerJournalType };
|
package/dist/constants/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as extractStatementType, c as getNormalBalance, d as isValidCategory, i as extractMainType, l as isBalanceSheet, n as CATEGORY_KEYS, o as getCategoryMainType, r as categoryKey, s as getCategoryStatementType, t as CATEGORIES, u as isIncomeStatement } from "../categories-
|
|
1
|
+
import { a as getJournalType, c as registerJournalType, i as getCustomJournalTypes, n as JOURNAL_TYPES, o as getJournalTypeCodes, s as isValidJournalType, t as JOURNAL_CODES } from "../journals-BfwnCFam.mjs";
|
|
2
|
+
import { a as extractStatementType, c as getNormalBalance, d as isValidCategory, i as extractMainType, l as isBalanceSheet, n as CATEGORY_KEYS, o as getCategoryMainType, r as categoryKey, s as getCategoryStatementType, t as CATEGORIES, u as isIncomeStatement } from "../categories-DWogBUgQ.mjs";
|
|
3
3
|
import { i as isValidCurrency, n as getCurrency, r as getMinorUnit, t as CURRENCIES } from "../currencies-W8kQAkm0.mjs";
|
|
4
|
-
export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType };
|
|
4
|
+
export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getCustomJournalTypes, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType, registerJournalType };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as Errors } from "./errors-
|
|
1
|
+
import { n as Errors } from "./errors-B_dyYZc_.mjs";
|
|
2
2
|
//#region src/plugins/double-entry.plugin.ts
|
|
3
3
|
function doubleEntryPlugin(options = {}) {
|
|
4
4
|
const { onlyOnPost = true, JournalEntryModel, AccountModel, orgField } = options;
|
|
@@ -36,7 +36,7 @@ function doubleEntryPlugin(options = {}) {
|
|
|
36
36
|
const accountIds = items.map((i) => i.account).filter((a) => a != null && a !== "");
|
|
37
37
|
if (accountIds.length === 0) throw Errors.validation("Posted entry has items with missing accounts.");
|
|
38
38
|
const selectFields = orgField ? `_id ${orgField}` : "_id";
|
|
39
|
-
const accounts = await AccountModel
|
|
39
|
+
const accounts = await AccountModel?.find({ _id: { $in: accountIds } }).select(selectFields).session(context.session ?? null).lean();
|
|
40
40
|
const foundIds = new Set(accounts.map((a) => String(a._id)));
|
|
41
41
|
const missingCount = accountIds.filter((id) => !foundIds.has(String(id))).length;
|
|
42
42
|
if (missingCount > 0) throw Errors.validation(`${missingCount} item(s) reference non-existent accounts.`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as CountryPack } from "./index-CxZqRaOU.mjs";
|
|
2
|
-
import { t as Logger } from "./logger-
|
|
2
|
+
import { t as Logger } from "./logger-UbTdBb1x.mjs";
|
|
3
|
+
import { Connection } from "mongoose";
|
|
3
4
|
|
|
4
5
|
//#region src/types/engine.d.ts
|
|
5
6
|
/** Multi-tenant configuration */
|
|
@@ -68,8 +69,35 @@ interface MultiCurrencyConfig {
|
|
|
68
69
|
/** Allowed foreign currency codes. If omitted, any ISO 4217 code is accepted. */
|
|
69
70
|
currencies?: readonly string[];
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Override default model names. Useful when you want to avoid collisions
|
|
74
|
+
* with existing models or use custom naming conventions.
|
|
75
|
+
*/
|
|
76
|
+
interface ModelNames {
|
|
77
|
+
account?: string;
|
|
78
|
+
journalEntry?: string;
|
|
79
|
+
fiscalPeriod?: string;
|
|
80
|
+
budget?: string;
|
|
81
|
+
reconciliation?: string;
|
|
82
|
+
}
|
|
71
83
|
/** Main engine configuration */
|
|
72
84
|
interface AccountingEngineConfig {
|
|
85
|
+
/**
|
|
86
|
+
* Mongoose connection. Required for `engine.models` and `engine.repositories`
|
|
87
|
+
* to be auto-populated. If omitted, you must use the low-level schema
|
|
88
|
+
* factories (`engine.createAccountSchema()`) and register models yourself.
|
|
89
|
+
*/
|
|
90
|
+
mongoose?: Connection;
|
|
91
|
+
/** Override default model names (e.g. 'Account' → 'GLAccount') */
|
|
92
|
+
modelNames?: ModelNames;
|
|
93
|
+
/** Extra fields / indexes per model */
|
|
94
|
+
schemaOptions?: {
|
|
95
|
+
account?: SchemaOptions;
|
|
96
|
+
journalEntry?: JournalSchemaOptions;
|
|
97
|
+
fiscalPeriod?: SchemaOptions;
|
|
98
|
+
budget?: SchemaOptions;
|
|
99
|
+
reconciliation?: SchemaOptions;
|
|
100
|
+
};
|
|
73
101
|
/** Country pack providing account types, tax codes, and templates */
|
|
74
102
|
country: CountryPack;
|
|
75
103
|
/** Default ISO 4217 currency code — the functional/base currency (e.g., 'CAD', 'BDT') */
|
|
@@ -99,4 +127,4 @@ interface AccountingEngineConfig {
|
|
|
99
127
|
strictness?: StrictnessConfig;
|
|
100
128
|
}
|
|
101
129
|
//#endregion
|
|
102
|
-
export {
|
|
130
|
+
export { MultiCurrencyConfig as a, StrictnessConfig as c, ModelNames as i, AuditConfig as n, MultiTenantConfig as o, JournalSchemaOptions as r, SchemaOptions as s, AccountingEngineConfig as t };
|
package/dist/exports/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as PopulatedJournalEntry, a as exportToCsv, c as getHeaders, d as serializeCsv, f as CsvOptions, g as PopulatedAccount, h as FlatJournalRow, i as
|
|
1
|
+
import { _ as PopulatedJournalEntry, a as exportToCsv, c as getHeaders, d as serializeCsv, f as CsvOptions, g as PopulatedAccount, h as FlatJournalRow, i as quickbooksFieldMap, l as buildCsv, m as ExportFieldMap, n as flattenJournalEntry, o as extractAllRows, p as ExportField, r as universalFieldMap, s as extractRow, t as flattenJournalEntries, u as escapeCell, v as PopulatedJournalItem } from "../index-J-XIbXH-.mjs";
|
|
2
2
|
export { CsvOptions, ExportField, ExportFieldMap, FlatJournalRow, PopulatedAccount, PopulatedJournalEntry, PopulatedJournalItem, buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };
|
package/dist/exports/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as exportToCsv, c as getHeaders, d as serializeCsv, i as
|
|
1
|
+
import { a as exportToCsv, c as getHeaders, d as serializeCsv, i as quickbooksFieldMap, l as buildCsv, n as flattenJournalEntry, o as extractAllRows, r as universalFieldMap, s as extractRow, t as flattenJournalEntries, u as escapeCell } from "../exports-DoGQQtMQ.mjs";
|
|
2
2
|
export { buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };
|
|
@@ -3,7 +3,7 @@ import { Money } from "./money.mjs";
|
|
|
3
3
|
const NEEDS_QUOTING = /[",\r\n]/;
|
|
4
4
|
/** Escape a single CSV cell value per RFC 4180. */
|
|
5
5
|
function escapeCell(value) {
|
|
6
|
-
if (NEEDS_QUOTING.test(value)) return "
|
|
6
|
+
if (NEEDS_QUOTING.test(value)) return `"${value.replace(/"/g, "\"\"")}"`;
|
|
7
7
|
return value;
|
|
8
8
|
}
|
|
9
9
|
/** Serialize a 2D array of strings into a CSV string. */
|
|
@@ -18,6 +18,11 @@ function buildCsv(headers, dataRows, options = {}) {
|
|
|
18
18
|
}
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/exports/field-map.ts
|
|
21
|
+
/**
|
|
22
|
+
* Field Map Application — Applies an ExportFieldMap to data rows.
|
|
23
|
+
*
|
|
24
|
+
* @module @classytic/ledger/exports
|
|
25
|
+
*/
|
|
21
26
|
/** Extract headers from a field map. */
|
|
22
27
|
function getHeaders(fieldMap) {
|
|
23
28
|
return fieldMap.fields.map((f) => f.header);
|
|
@@ -35,80 +40,15 @@ function exportToCsv(fieldMap, rows, options) {
|
|
|
35
40
|
return buildCsv(getHeaders(fieldMap), extractAllRows(fieldMap, rows), options);
|
|
36
41
|
}
|
|
37
42
|
//#endregion
|
|
38
|
-
//#region src/exports/flatten-journal.ts
|
|
39
|
-
function toDate(value) {
|
|
40
|
-
if (!value) return /* @__PURE__ */ new Date(0);
|
|
41
|
-
if (value instanceof Date) return value;
|
|
42
|
-
return new Date(value);
|
|
43
|
-
}
|
|
44
|
-
function resolveAccount(account) {
|
|
45
|
-
if (!account) return {
|
|
46
|
-
id: "",
|
|
47
|
-
name: "",
|
|
48
|
-
typeCode: ""
|
|
49
|
-
};
|
|
50
|
-
if (typeof account === "string") return {
|
|
51
|
-
id: account,
|
|
52
|
-
name: "",
|
|
53
|
-
typeCode: ""
|
|
54
|
-
};
|
|
55
|
-
return {
|
|
56
|
-
id: String(account._id ?? ""),
|
|
57
|
-
name: account.name ?? "",
|
|
58
|
-
typeCode: account.accountTypeCode ?? ""
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/** Flatten a single journal entry into one FlatJournalRow per journal item. */
|
|
62
|
-
function flattenJournalEntry(entry) {
|
|
63
|
-
const entryDate = toDate(entry.date);
|
|
64
|
-
const items = entry.journalItems ?? [];
|
|
65
|
-
const itemCount = items.length;
|
|
66
|
-
const KNOWN_ITEM_KEYS = new Set([
|
|
67
|
-
"account",
|
|
68
|
-
"label",
|
|
69
|
-
"date",
|
|
70
|
-
"debit",
|
|
71
|
-
"credit",
|
|
72
|
-
"taxDetails"
|
|
73
|
-
]);
|
|
74
|
-
return items.map((item, index) => {
|
|
75
|
-
const acct = resolveAccount(item.account);
|
|
76
|
-
const firstTax = item.taxDetails?.[0];
|
|
77
|
-
const extraItemFields = {};
|
|
78
|
-
for (const key of Object.keys(item)) if (!KNOWN_ITEM_KEYS.has(key)) extraItemFields[key] = item[key];
|
|
79
|
-
return {
|
|
80
|
-
entryId: String(entry._id ?? ""),
|
|
81
|
-
journalType: entry.journalType ?? "",
|
|
82
|
-
referenceNumber: entry.referenceNumber ?? "",
|
|
83
|
-
entryLabel: entry.label ?? "",
|
|
84
|
-
entryDate,
|
|
85
|
-
state: entry.state,
|
|
86
|
-
reversed: entry.reversed ?? false,
|
|
87
|
-
totalDebit: entry.totalDebit ?? 0,
|
|
88
|
-
totalCredit: entry.totalCredit ?? 0,
|
|
89
|
-
accountId: acct.id,
|
|
90
|
-
accountName: acct.name,
|
|
91
|
-
accountTypeCode: acct.typeCode,
|
|
92
|
-
itemLabel: item.label ?? "",
|
|
93
|
-
itemDate: item.date ? toDate(item.date) : entryDate,
|
|
94
|
-
debit: item.debit ?? 0,
|
|
95
|
-
credit: item.credit ?? 0,
|
|
96
|
-
taxCode: firstTax?.taxCode ?? "",
|
|
97
|
-
taxName: firstTax?.taxName ?? "",
|
|
98
|
-
itemIndex: index,
|
|
99
|
-
itemCount,
|
|
100
|
-
...extraItemFields
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
/** Flatten multiple journal entries into a single flat row array. */
|
|
105
|
-
function flattenJournalEntries(entries) {
|
|
106
|
-
const rows = [];
|
|
107
|
-
for (const entry of entries) rows.push(...flattenJournalEntry(entry));
|
|
108
|
-
return rows;
|
|
109
|
-
}
|
|
110
|
-
//#endregion
|
|
111
43
|
//#region src/exports/field-maps/quickbooks.ts
|
|
44
|
+
/**
|
|
45
|
+
* QuickBooks General Journal Import Field Map
|
|
46
|
+
*
|
|
47
|
+
* Produces CSV compatible with QuickBooks Desktop and Online
|
|
48
|
+
* "Import General Journal Entries" feature.
|
|
49
|
+
*
|
|
50
|
+
* @module @classytic/ledger/exports
|
|
51
|
+
*/
|
|
112
52
|
function formatQbDate(date) {
|
|
113
53
|
return `${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}/${date.getFullYear()}`;
|
|
114
54
|
}
|
|
@@ -161,6 +101,14 @@ const quickbooksFieldMap = {
|
|
|
161
101
|
};
|
|
162
102
|
//#endregion
|
|
163
103
|
//#region src/exports/field-maps/universal.ts
|
|
104
|
+
/**
|
|
105
|
+
* Universal Export Field Map
|
|
106
|
+
*
|
|
107
|
+
* Comprehensive CSV export with all available fields.
|
|
108
|
+
* Useful for data portability, auditing, or spreadsheet import.
|
|
109
|
+
*
|
|
110
|
+
* @module @classytic/ledger/exports
|
|
111
|
+
*/
|
|
164
112
|
function formatIsoDate(date) {
|
|
165
113
|
return date.toISOString().split("T")[0];
|
|
166
114
|
}
|
|
@@ -247,4 +195,77 @@ const universalFieldMap = {
|
|
|
247
195
|
]
|
|
248
196
|
};
|
|
249
197
|
//#endregion
|
|
250
|
-
|
|
198
|
+
//#region src/exports/flatten-journal.ts
|
|
199
|
+
function toDate(value) {
|
|
200
|
+
if (!value) return /* @__PURE__ */ new Date(0);
|
|
201
|
+
if (value instanceof Date) return value;
|
|
202
|
+
return new Date(value);
|
|
203
|
+
}
|
|
204
|
+
function resolveAccount(account) {
|
|
205
|
+
if (!account) return {
|
|
206
|
+
id: "",
|
|
207
|
+
name: "",
|
|
208
|
+
typeCode: ""
|
|
209
|
+
};
|
|
210
|
+
if (typeof account === "string") return {
|
|
211
|
+
id: account,
|
|
212
|
+
name: "",
|
|
213
|
+
typeCode: ""
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
id: String(account._id ?? ""),
|
|
217
|
+
name: account.name ?? "",
|
|
218
|
+
typeCode: account.accountTypeCode ?? ""
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/** Flatten a single journal entry into one FlatJournalRow per journal item. */
|
|
222
|
+
function flattenJournalEntry(entry) {
|
|
223
|
+
const entryDate = toDate(entry.date);
|
|
224
|
+
const items = entry.journalItems ?? [];
|
|
225
|
+
const itemCount = items.length;
|
|
226
|
+
const KNOWN_ITEM_KEYS = new Set([
|
|
227
|
+
"account",
|
|
228
|
+
"label",
|
|
229
|
+
"date",
|
|
230
|
+
"debit",
|
|
231
|
+
"credit",
|
|
232
|
+
"taxDetails"
|
|
233
|
+
]);
|
|
234
|
+
return items.map((item, index) => {
|
|
235
|
+
const acct = resolveAccount(item.account);
|
|
236
|
+
const firstTax = item.taxDetails?.[0];
|
|
237
|
+
const extraItemFields = {};
|
|
238
|
+
for (const key of Object.keys(item)) if (!KNOWN_ITEM_KEYS.has(key)) extraItemFields[key] = item[key];
|
|
239
|
+
return {
|
|
240
|
+
entryId: String(entry._id ?? ""),
|
|
241
|
+
journalType: entry.journalType ?? "",
|
|
242
|
+
referenceNumber: entry.referenceNumber ?? "",
|
|
243
|
+
entryLabel: entry.label ?? "",
|
|
244
|
+
entryDate,
|
|
245
|
+
state: entry.state,
|
|
246
|
+
reversed: entry.reversed ?? false,
|
|
247
|
+
totalDebit: entry.totalDebit ?? 0,
|
|
248
|
+
totalCredit: entry.totalCredit ?? 0,
|
|
249
|
+
accountId: acct.id,
|
|
250
|
+
accountName: acct.name,
|
|
251
|
+
accountTypeCode: acct.typeCode,
|
|
252
|
+
itemLabel: item.label ?? "",
|
|
253
|
+
itemDate: item.date ? toDate(item.date) : entryDate,
|
|
254
|
+
debit: item.debit ?? 0,
|
|
255
|
+
credit: item.credit ?? 0,
|
|
256
|
+
taxCode: firstTax?.taxCode ?? "",
|
|
257
|
+
taxName: firstTax?.taxName ?? "",
|
|
258
|
+
itemIndex: index,
|
|
259
|
+
itemCount,
|
|
260
|
+
...extraItemFields
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/** Flatten multiple journal entries into a single flat row array. */
|
|
265
|
+
function flattenJournalEntries(entries) {
|
|
266
|
+
const rows = [];
|
|
267
|
+
for (const entry of entries) rows.push(...flattenJournalEntry(entry));
|
|
268
|
+
return rows;
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
export { exportToCsv as a, getHeaders as c, serializeCsv as d, quickbooksFieldMap as i, buildCsv as l, flattenJournalEntry as n, extractAllRows as o, universalFieldMap as r, extractRow as s, flattenJournalEntries as t, escapeCell as u };
|