@classytic/ledger 0.1.3 → 0.2.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/{account.repository-1C2sZvB2.d.mts → account.repository-C7gwFLfM.d.mts} +3 -3
- package/dist/{account.repository-1C2sZvB2.d.mts.map → account.repository-C7gwFLfM.d.mts.map} +1 -1
- package/dist/{account.repository-Crf5DGO4.mjs → account.repository-kDKwDt0I.mjs} +4 -6
- package/dist/{account.repository-Crf5DGO4.mjs.map → account.repository-kDKwDt0I.mjs.map} +1 -1
- package/dist/{categories-BNJBd4ze.mjs → categories-CclX7Q94.mjs} +2 -2
- package/dist/{categories-BNJBd4ze.mjs.map → categories-CclX7Q94.mjs.map} +1 -1
- package/dist/constants/index.d.mts +1 -1
- package/dist/constants/index.mjs +4 -5
- package/dist/{core-Cx0baosR.d.mts → core-8Xfnpn6g.d.mts} +1 -1
- package/dist/{core-Cx0baosR.d.mts.map → core-8Xfnpn6g.d.mts.map} +1 -1
- package/dist/country/index.d.mts +2 -105
- package/dist/country/index.mjs +1 -1
- package/dist/country/index.mjs.map +1 -1
- package/dist/{currencies-Bkn3FNkC.d.mts → currencies-4WAbFRlw.d.mts} +2 -2
- package/dist/{currencies-Bkn3FNkC.d.mts.map → currencies-4WAbFRlw.d.mts.map} +1 -1
- package/dist/{currencies-BBk3NwXn.mjs → currencies-W8kQAkm0.mjs} +2 -2
- package/dist/{currencies-BBk3NwXn.mjs.map → currencies-W8kQAkm0.mjs.map} +1 -1
- package/dist/{engine-Cd73EOT6.d.mts → engine-BzBMpWuy.d.mts} +31 -5
- package/dist/engine-BzBMpWuy.d.mts.map +1 -0
- package/dist/{errors-CeqRahE-.mjs → errors-B7yC-Jfw.mjs} +2 -2
- package/dist/{errors-CeqRahE-.mjs.map → errors-B7yC-Jfw.mjs.map} +1 -1
- package/dist/exports/index.d.mts +2 -2
- package/dist/exports/index.mjs +2 -3
- package/dist/{universal-CMfrZ2hG.mjs → exports-I5Xkq-9_.mjs} +2 -7
- package/dist/exports-I5Xkq-9_.mjs.map +1 -0
- package/dist/{fiscal-close-CNOwv_ud.mjs → fiscal-close-L631E3De.mjs} +22 -17
- package/dist/fiscal-close-L631E3De.mjs.map +1 -0
- package/dist/{fiscal-close-CzUzpnMg.d.mts → fiscal-close-dNlzB37y.d.mts} +4 -4
- package/dist/{fiscal-close-CzUzpnMg.d.mts.map → fiscal-close-dNlzB37y.d.mts.map} +1 -1
- package/dist/{fiscal-period.schema-CbALaaKl.mjs → fiscal-period.schema-BQ5wsAq3.mjs} +52 -8
- package/dist/fiscal-period.schema-BQ5wsAq3.mjs.map +1 -0
- package/dist/{fiscal-period.schema-DI2scngu.d.mts → fiscal-period.schema-BRdKAjrr.d.mts} +2 -2
- package/dist/{fiscal-period.schema-DI2scngu.d.mts.map → fiscal-period.schema-BRdKAjrr.d.mts.map} +1 -1
- package/dist/{idempotency.plugin-BESs9YPD.d.mts → idempotency.plugin-CPxPt4vX.d.mts} +1 -1
- package/dist/{idempotency.plugin-BESs9YPD.d.mts.map → idempotency.plugin-CPxPt4vX.d.mts.map} +1 -1
- package/dist/{idempotency.plugin-C6r8RI8d.mjs → idempotency.plugin-v9NQ_ta-.mjs} +3 -6
- package/dist/{idempotency.plugin-C6r8RI8d.mjs.map → idempotency.plugin-v9NQ_ta-.mjs.map} +1 -1
- package/dist/{universal-x33ZJODp.d.mts → index-BPukb3L8.d.mts} +1 -1
- package/dist/index-BPukb3L8.d.mts.map +1 -0
- package/dist/index-ZnSiqHYV.d.mts +105 -0
- package/dist/index-ZnSiqHYV.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +11 -12
- package/dist/index.mjs.map +1 -1
- package/dist/{journals-CI3Wb4EF.mjs → journals-oH-FK3g8.mjs} +2 -2
- package/dist/{journals-CI3Wb4EF.mjs.map → journals-oH-FK3g8.mjs.map} +1 -1
- package/dist/{logger-Cv6VVc4r.d.mts → logger-UbTdBb1x.d.mts} +1 -1
- package/dist/{logger-Cv6VVc4r.d.mts.map → logger-UbTdBb1x.d.mts.map} +1 -1
- package/dist/money.mjs +1 -1
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +2 -3
- package/dist/reports/index.d.mts +1 -1
- package/dist/reports/index.mjs +2 -3
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +2 -3
- package/dist/schemas/index.d.mts +1 -1
- package/dist/schemas/index.mjs +2 -3
- package/dist/{session-Dh0s6zG4.mjs → session-Ba8E3Ufa.mjs} +3 -6
- package/dist/{session-Dh0s6zG4.mjs.map → session-Ba8E3Ufa.mjs.map} +1 -1
- package/package.json +3 -3
- package/dist/country/index.d.mts.map +0 -1
- package/dist/engine-Cd73EOT6.d.mts.map +0 -1
- package/dist/fiscal-close-CNOwv_ud.mjs.map +0 -1
- package/dist/fiscal-period.schema-CbALaaKl.mjs.map +0 -1
- package/dist/universal-CMfrZ2hG.mjs.map +0 -1
- package/dist/universal-x33ZJODp.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-
|
|
1
|
+
{"version":3,"file":"session-Ba8E3Ufa.mjs","names":[],"sources":["../src/utils/tenant-guard.ts","../src/utils/logger.ts","../src/utils/session.ts"],"sourcesContent":["import { Errors } from './errors.js';\r\n\r\n/**\r\n * Multi-tenant scope guard.\r\n *\r\n * Throws when orgField is configured (multi-tenant mode active) but\r\n * organizationId is missing — preventing unscoped cross-tenant queries.\r\n */\r\nexport function requireOrgScope(orgField: string | undefined, organizationId: unknown): void {\r\n if (orgField && !organizationId) {\r\n throw Errors.validation(\r\n 'organizationId is required when multi-tenant mode is configured (orgField: \"' +\r\n orgField +\r\n '\"). Refusing to run unscoped query.',\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Build org-scoped query filter.\r\n * Returns an object like `{ business: orgId }` or `{}`.\r\n */\r\nexport function orgFilter(orgField: string | undefined, organizationId: unknown): Record<string, unknown> {\r\n if (!orgField) return {};\r\n // requireOrgScope should be called first; this is a convenience builder\r\n return organizationId ? { [orgField]: organizationId } : {};\r\n}\r\n","/**\r\n * Minimal logger interface for the accounting package.\r\n * Defaults to console. App layer can inject a real logger (Winston/Pino).\r\n */\r\nexport interface Logger {\r\n warn(message: string, meta?: Record<string, unknown>): void;\r\n error(message: string, meta?: Record<string, unknown>): void;\r\n info(message: string, meta?: Record<string, unknown>): void;\r\n}\r\n\r\n/** Default console-based implementation */\r\nexport const defaultLogger: Logger = {\r\n warn: (msg, meta) => console.warn(`[accounting] ${msg}`, meta ?? ''),\r\n error: (msg, meta) => console.error(`[accounting] ${msg}`, meta ?? ''),\r\n info: (msg, meta) => console.info(`[accounting] ${msg}`, meta ?? ''),\r\n};\r\n","/**\r\n * Shared session/transaction helpers.\r\n *\r\n * Provides a safe acquire/finalize pattern that:\r\n * - Detects standalone topology (no transactions) before starting one\r\n * - Manages commit/abort lifecycle with proper cleanup\r\n * - Always calls endSession() even when abort fails\r\n *\r\n * NOTE: session.startTransaction() is synchronous and does NOT throw on\r\n * standalone MongoDB — the \"Transaction numbers are only allowed on a replica\r\n * set member or mongos\" error only surfaces at the first database operation.\r\n * We detect standalone topology proactively via the driver's topology\r\n * description to avoid this trap.\r\n */\r\n\r\nimport type { ClientSession, Connection } from 'mongoose';\r\nimport type { Logger } from './logger.js';\r\nimport { defaultLogger } from './logger.js';\r\n\r\nexport interface SessionResult {\r\n session: ClientSession | null;\r\n ownSession: boolean;\r\n}\r\n\r\n/**\r\n * Acquire a session: uses external if provided, otherwise creates an internal one.\r\n * Returns { session, ownSession } so callers can commit/abort/end appropriately.\r\n *\r\n * When transactions are unavailable (no replica set / standalone), returns\r\n * session=null and the function runs without transactional safety.\r\n */\r\nexport async function acquireSession(\r\n db: Connection,\r\n externalSession: ClientSession | undefined | null,\r\n logger: Logger = defaultLogger,\r\n): Promise<SessionResult> {\r\n if (externalSession) {\r\n return { session: externalSession, ownSession: false };\r\n }\r\n\r\n try {\r\n const session = await db.startSession();\r\n\r\n // Detect standalone topology before starting a transaction.\r\n try {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const client = (db as any).getClient?.() ?? (db as any).client;\r\n const topologyType = client?.topology?.description?.type;\r\n if (topologyType === 'Single') {\r\n session.endSession();\r\n logger.warn(\r\n 'Transactions unavailable (standalone MongoDB). Operation is not atomic.',\r\n );\r\n return { session: null, ownSession: false };\r\n }\r\n } catch {\r\n // Topology detection failed — proceed optimistically\r\n }\r\n\r\n try {\r\n session.startTransaction();\r\n return { session, ownSession: true };\r\n } catch (err) {\r\n // startTransaction failed for unexpected reasons — clean up and fall back\r\n session.endSession();\r\n logger.warn(\r\n 'Transactions unavailable (no replica set). Operation is not atomic.',\r\n { error: (err as Error).message },\r\n );\r\n return { session: null, ownSession: false };\r\n }\r\n } catch {\r\n // startSession itself failed — run without session\r\n return { session: null, ownSession: false };\r\n }\r\n}\r\n\r\n/**\r\n * Finalize an owned session: commit or abort, then always end.\r\n */\r\nexport async function finalizeSession(\r\n session: ClientSession | null,\r\n ownSession: boolean,\r\n success: boolean,\r\n): Promise<void> {\r\n if (!ownSession || !session) return;\r\n\r\n try {\r\n if (success && session.inTransaction()) {\r\n await session.commitTransaction();\r\n } else if (!success && session.inTransaction()) {\r\n try {\r\n await session.abortTransaction();\r\n } catch {\r\n // Swallow abort errors — the original error is more important\r\n }\r\n }\r\n } finally {\r\n session.endSession();\r\n }\r\n}\r\n"],"mappings":";;;;;;;;AAQA,SAAgB,gBAAgB,UAA8B,gBAA+B;AAC3F,KAAI,YAAY,CAAC,eACf,OAAM,OAAO,WACX,kFACA,WACA,uCACD;;;;;ACHL,MAAa,gBAAwB;CACnC,OAAO,KAAK,SAAS,QAAQ,KAAK,gBAAgB,OAAO,QAAQ,GAAG;CACpE,QAAQ,KAAK,SAAS,QAAQ,MAAM,gBAAgB,OAAO,QAAQ,GAAG;CACtE,OAAO,KAAK,SAAS,QAAQ,KAAK,gBAAgB,OAAO,QAAQ,GAAG;CACrE;;;;;;;;;;ACgBD,eAAsB,eACpB,IACA,iBACA,SAAiB,eACO;AACxB,KAAI,gBACF,QAAO;EAAE,SAAS;EAAiB,YAAY;EAAO;AAGxD,KAAI;EACF,MAAM,UAAU,MAAM,GAAG,cAAc;AAGvC,MAAI;AAIF,QAFgB,GAAW,aAAa,IAAK,GAAW,SAC3B,UAAU,aAAa,SAC/B,UAAU;AAC7B,YAAQ,YAAY;AACpB,WAAO,KACL,0EACD;AACD,WAAO;KAAE,SAAS;KAAM,YAAY;KAAO;;UAEvC;AAIR,MAAI;AACF,WAAQ,kBAAkB;AAC1B,UAAO;IAAE;IAAS,YAAY;IAAM;WAC7B,KAAK;AAEZ,WAAQ,YAAY;AACpB,UAAO,KACL,uEACA,EAAE,OAAQ,IAAc,SAAS,CAClC;AACD,UAAO;IAAE,SAAS;IAAM,YAAY;IAAO;;SAEvC;AAEN,SAAO;GAAE,SAAS;GAAM,YAAY;GAAO;;;;;;AAO/C,eAAsB,gBACpB,SACA,YACA,SACe;AACf,KAAI,CAAC,cAAc,CAAC,QAAS;AAE7B,KAAI;AACF,MAAI,WAAW,QAAQ,eAAe,CACpC,OAAM,QAAQ,mBAAmB;WACxB,CAAC,WAAW,QAAQ,eAAe,CAC5C,KAAI;AACF,SAAM,QAAQ,kBAAkB;UAC1B;WAIF;AACR,UAAQ,YAAY"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/ledger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Production-grade double-entry accounting engine for MongoDB — schemas, reports, tax, multi-tenant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -109,8 +109,8 @@
|
|
|
109
109
|
"@vitest/coverage-v8": "^3.2.4",
|
|
110
110
|
"mongodb-memory-server": "^10.2.3",
|
|
111
111
|
"mongoose": "^9.3.1",
|
|
112
|
-
"tsdown": "^0.
|
|
112
|
+
"tsdown": "^0.21.5",
|
|
113
113
|
"typescript": "^5.7.0",
|
|
114
114
|
"vitest": "^3.0.0"
|
|
115
115
|
}
|
|
116
|
-
}
|
|
116
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/country/index.ts"],"mappings":";;;UAgBiB,OAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA;EAAA,SACA,QAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA;AAAA;AAAA,UAGM,gBAAA;EAAA,UACL,MAAA;AAAA;AAAA,UAKK,aAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,WAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA,IAAa,IAAA,EAAM,MAAA;EAAA,SACnB,OAAA;AAAA;AAAA,UAGM,iBAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA,EAAO,QAAA,CAAS,MAAA,kBAAwB,aAAA;EACjD,SAAA,CAAU,SAAA,EAAW,MAAA,2BAAiC,UAAA,GAAa,MAAA,4BAAkC,MAAA;EACrG,SAAA,CAAU,UAAA,EAAY,MAAA,4BAAkC,MAAA;AAAA;AAAA,UAKzC,WAAA;EAPU;EAAA,SAShB,IAAA;EARY;EAAA,SAUZ,IAAA;EAV4F;EAAA,SAY5F,eAAA;EAX+C;;;;EAAA,SAiB/C,YAAA,WAAuB,WAAA;EAnBhB;EAAA,SAsBP,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,OAAA;EAtBM;EAAA,SAyBxC,gBAAA,EAAkB,gBAAA;EAxBN;EAAA,SA2BZ,OAAA;EA3B0D;EAAA,SA8B1D,SAAA,GAAY,iBAAA;EA9BgF;EAAA,SAmC5F,oBAAA;EAlCa;EAAA,SAoCb,uBAAA;EApC+C;EAAA,SAsC/C,aAAA;EAtCqD;EAAA,SAwCrD,YAAA;IAAA,SACE,MAAA;IAAA,SACA,WAAA;IAAA,SACA,MAAA;IAAA,SACA,OAAA;IAAA,SACA,QAAA;EAAA;EAtBgB;EA4B3B,sBAAA,aAAmC,WAAA;EAAA;EAGnC,cAAA,CAAe,IAAA,WAAe,WAAA;EASQ;EANtC,kBAAA,CAAmB,IAAA;EASwB;EAN3C,gBAAA,CAAiB,IAAA;EArDR;EAwDT,oBAAA,CAAqB,MAAA,WAAiB,OAAA;EApD7B;EAuDT,mBAAA,aAAgC,WAAA;AAAA;AAAA,UAKjB,gBAAA;EACf,IAAA;EACA,IAAA;EACA,eAAA;EACA,YAAA,WAAuB,WAAA;EACvB,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,OAAA;EAClC,gBAAA,EAAkB,gBAAA;EAClB,OAAA;EACA,SAAA,GAAY,iBAAA;EACZ,oBAAA;EACA,uBAAA;EACA,aAAA;EACA,YAAA;IAAA,SACW,MAAA;IAAA,SACA,WAAA;IAAA,SACA,MAAA;IAAA,SACA,OAAA;IAAA,SACA,QAAA;EAAA;AAAA;;;;iBAOG,iBAAA,CAAkB,KAAA,EAAO,gBAAA,GAAmB,WAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"engine-Cd73EOT6.d.mts","names":[],"sources":["../src/types/engine.ts"],"mappings":";;;;;UAaiB,iBAAA;EAUa;EAR5B,QAAA;EAYc;EAVd,MAAA;AAAA;;UAMe,aAAA;EAMK;EAJpB,OAAA;EAEA;EAAA,WAAA,GAAc,MAAA;EAEd;EAAA,YAAA,GAAe,KAAA;IAAQ,MAAA,EAAQ,MAAA;IAAwB,OAAA,GAAU,MAAA;EAAA;AAAA;;UAIlD,oBAAA,SAA6B,aAAA;EAA7B;EAEf,aAAA;;EAEA,UAAA;EAJ4C;EAM5C,eAAA,GAAkB,MAAA;AAAA;;UAMH,WAAA;EANS;EAQxB,UAAA;AAAA;;UAMe,gBAAA;EANf;EAQA,SAAA;EAFe;EAIf,YAAA;;EAEA,eAAA;AAAA;;UAMe,sBAAA;EANA;EAQf,OAAA,EAAS,WAAA;EAFM;EAIf,QAAA;;EAEA,WAAA,GAAc,iBAAA;EAAA;EAEd,oBAAA;EAQQ;EANR,oBAAA;EAU6B;EAR7B,uBAAA;EAVA;EAYA,MAAA,GAAS,MAAA;EAVT;EAYA,KAAA,GAAQ,WAAA;EAVM;EAYd,WAAA;EARA;EAUA,UAAA,GAAa,gBAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fiscal-close-CNOwv_ud.mjs","names":[],"sources":["../src/utils/date-range.ts","../src/utils/filter-builder.ts","../src/reports/trial-balance.ts","../src/utils/account-helpers.ts","../src/reports/balance-sheet.ts","../src/reports/income-statement.ts","../src/reports/general-ledger.ts","../src/reports/cash-flow.ts","../src/reports/fiscal-close.ts"],"sourcesContent":["/**\r\n * Date Range Utility — Compute period boundaries for reports.\r\n */\r\n\r\nimport type { DateOption, DateRange, QuarterValue, CustomDateRange } from '../types/core.js';\r\n\r\n/**\r\n * Compute start/end dates from a date option + value.\r\n *\r\n * Examples:\r\n * getDateRange('month', '2025-03') → Mar 1 – Mar 31\r\n * getDateRange('quarter', { quarter: 2, year: 2025 }) → Apr 1 – Jun 30\r\n * getDateRange('year', 2025) → Jan 1 – Dec 31\r\n * getDateRange('custom', { startDate, endDate })\r\n */\r\nexport function getDateRange(option: DateOption, value: unknown): DateRange {\r\n switch (option) {\r\n case 'month': {\r\n // Parse 'YYYY-MM' strings explicitly to avoid UTC-vs-local timezone shift\r\n let year: number;\r\n let month: number;\r\n const strVal = String(value);\r\n const match = strVal.match(/^(\\d{4})-(\\d{1,2})$/);\r\n if (match) {\r\n year = parseInt(match[1], 10);\r\n month = parseInt(match[2], 10) - 1; // 0-indexed\r\n } else {\r\n const date = new Date(value as string | number | Date);\r\n year = date.getFullYear();\r\n month = date.getMonth();\r\n }\r\n const startDate = new Date(year, month, 1);\r\n const endDate = new Date(year, month + 1, 0, 23, 59, 59, 999);\r\n return { startDate, endDate };\r\n }\r\n\r\n case 'quarter': {\r\n const { quarter, year } = value as QuarterValue;\r\n const startMonth = (quarter - 1) * 3;\r\n const startDate = new Date(year, startMonth, 1);\r\n const endDate = new Date(year, startMonth + 3, 0, 23, 59, 59, 999);\r\n return { startDate, endDate };\r\n }\r\n\r\n case 'year': {\r\n const year = typeof value === 'number' ? value : parseInt(String(value), 10);\r\n const startDate = new Date(year, 0, 1);\r\n const endDate = new Date(year, 11, 31, 23, 59, 59, 999);\r\n return { startDate, endDate };\r\n }\r\n\r\n case 'custom': {\r\n const { startDate, endDate } = value as CustomDateRange;\r\n const end = new Date(endDate);\r\n // Normalize end date to end-of-day if time is midnight (00:00:00)\r\n if (end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 && end.getMilliseconds() === 0) {\r\n end.setHours(23, 59, 59, 999);\r\n }\r\n return {\r\n startDate: new Date(startDate),\r\n endDate: end,\r\n };\r\n }\r\n\r\n default: {\r\n // Default: current month\r\n const now = new Date();\r\n return {\r\n startDate: new Date(now.getFullYear(), now.getMonth(), 1),\r\n endDate: new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999),\r\n };\r\n }\r\n }\r\n}\r\n\r\n/** Get fiscal year start date for a given date and fiscal start month */\r\nexport function getFiscalYearStart(date: Date, fiscalStartMonth = 1): Date {\r\n const month = fiscalStartMonth - 1; // 0-indexed\r\n const year = date.getMonth() < month ? date.getFullYear() - 1 : date.getFullYear();\r\n return new Date(year, month, 1);\r\n}\r\n","/**\r\n * Filter Builder — Sanitizes user-supplied dimension filters for aggregation pipelines.\r\n *\r\n * Prevents injection of dangerous MongoDB operators while allowing\r\n * standard equality and comparison filters on custom dimension fields.\r\n */\r\n\r\nconst BLOCKED_OPERATORS = new Set([\r\n '$where', '$expr', '$function', '$accumulator',\r\n '$merge', '$out', '$unionWith',\r\n]);\r\n\r\n/**\r\n * Build a sanitized filter object from user-supplied dimension filters.\r\n * Blocks dangerous operators ($where, $expr, $function, etc.).\r\n *\r\n * @param filters - Key-value filters (e.g. { 'journalItems.departmentId': 'dept-1' })\r\n * @returns Sanitized filter object safe for $match stages\r\n * @throws Error if a blocked operator is used\r\n */\r\nexport function buildItemFilters(filters?: Record<string, unknown>): Record<string, unknown> {\r\n if (!filters || Object.keys(filters).length === 0) return {};\r\n\r\n const result: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(filters)) {\r\n // Block operators at top level\r\n if (key.startsWith('$')) {\r\n throw new Error(`Filter key \"${key}\" is not allowed. Use field names, not operators.`);\r\n }\r\n\r\n // Check nested values for blocked operators\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n for (const opKey of Object.keys(value as Record<string, unknown>)) {\r\n if (BLOCKED_OPERATORS.has(opKey)) {\r\n throw new Error(`Filter operator \"${opKey}\" is not allowed.`);\r\n }\r\n }\r\n }\r\n\r\n result[key] = value;\r\n }\r\n\r\n return result;\r\n}\r\n","/**\r\n * Trial Balance Report\r\n *\r\n * Three-column trial balance: Initial + Current Period + Ending Balance.\r\n * Pure aggregation pipeline — no cached balances.\r\n */\r\n\r\nimport type { Model, PipelineStage } from 'mongoose';\r\nimport type { AccountType, CategoryKey } from '../types/core.js';\r\nimport type { TrialBalanceRow, TrialBalanceReport } from '../types/report.js';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport { getDateRange, getFiscalYearStart } from '../utils/date-range.js';\r\nimport { computeEndingBalance } from '../utils/account-helpers.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface TrialBalanceOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n fiscalYearStartMonth?: number;\r\n}\r\n\r\nexport async function generateTrialBalance(\r\n opts: TrialBalanceOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n accountId?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<TrialBalanceReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth = 1 } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const fiscalYearStart = getFiscalYearStart(startDate, fiscalYearStartMonth);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch all active accounts\r\n const accountQuery: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) accountQuery[orgField] = params.organizationId;\r\n\r\n const allAccounts = await AccountModel.find(accountQuery).lean() as Array<Record<string, unknown>>;\r\n\r\n // Split by statement type\r\n const bsIds: unknown[] = [];\r\n const isIds: unknown[] = [];\r\n\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at || at.isGroup) continue;\r\n\r\n if (at.category.startsWith('Balance Sheet')) bsIds.push(acc._id);\r\n else if (at.category.startsWith('Income Statement')) isIds.push(acc._id);\r\n }\r\n\r\n const baseMatch: Record<string, unknown> = { state: 'posted' };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n const accountFilter = params.accountId ? { 'journalItems.account': params.accountId } : {};\r\n\r\n // Build pipelines\r\n const buildPipeline = (ids: unknown[], dateFrom: Date, dateTo: Date): PipelineStage[] => [\r\n { $match: { ...baseMatch, date: { $gte: dateFrom, $lt: dateTo } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: ids }, ...accountFilter, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ];\r\n\r\n // BS initial: all history before startDate\r\n // IS initial: fiscal year start → startDate\r\n // Current: startDate → endDate\r\n const [bsInitial, isInitial, current] = await Promise.all([\r\n bsIds.length ? JournalEntryModel.aggregate(buildPipeline(bsIds, new Date(0), startDate)) : [],\r\n isIds.length ? JournalEntryModel.aggregate(buildPipeline(isIds, fiscalYearStart, startDate)) : [],\r\n JournalEntryModel.aggregate(buildPipeline([...bsIds, ...isIds], startDate, new Date(endDate.getTime() + 1))),\r\n ]);\r\n\r\n // Merge\r\n const map = new Map<string, { iD: number; iC: number; cD: number; cC: number }>();\r\n\r\n for (const r of [...bsInitial, ...isInitial]) {\r\n const key = String(r._id);\r\n map.set(key, { iD: r.d, iC: r.c, cD: 0, cC: 0 });\r\n }\r\n for (const r of current) {\r\n const key = String(r._id);\r\n const existing = map.get(key) ?? { iD: 0, iC: 0, cD: 0, cC: 0 };\r\n existing.cD = r.d;\r\n existing.cC = r.c;\r\n map.set(key, existing);\r\n }\r\n\r\n // Build rows\r\n const accountLookup = new Map(allAccounts.map(a => [String(a._id), a]));\r\n\r\n const rows: TrialBalanceRow[] = [];\r\n for (const [id, bal] of map) {\r\n const acc = accountLookup.get(id);\r\n const totalD = bal.iD + bal.cD;\r\n const totalC = bal.iC + bal.cC;\r\n const net = totalD - totalC;\r\n\r\n rows.push({\r\n account: acc ?? id,\r\n initial: { debit: bal.iD, credit: bal.iC },\r\n current: { debit: bal.cD, credit: bal.cC },\r\n ending: net >= 0 ? { debit: net, credit: 0 } : { debit: 0, credit: Math.abs(net) },\r\n });\r\n }\r\n\r\n return { rows, period: { startDate, endDate } };\r\n}\r\n","/**\r\n * Account Helper Utilities\r\n */\r\n\r\nimport type { AccountType, TotalAccountOp, CategoryKey } from '../types/core.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\n\r\n/**\r\n * Check if an account type is a virtual tax sub-account.\r\n * Returns true if the account's parent has `isVirtualTotal: true`.\r\n * Works for any country pack — no code format assumptions.\r\n */\r\nexport function isVirtualTaxAccount(accountType: AccountType, accountMap: Map<string, AccountType>): boolean {\r\n if (!accountType.parentCode) return false;\r\n const parent = accountMap.get(accountType.parentCode);\r\n return parent?.isVirtualTotal === true;\r\n}\r\n\r\n/** Check if an account type is a balance sheet account */\r\nexport function isBalanceSheetAccountType(accountType: AccountType): boolean {\r\n const { category } = accountType;\r\n return category.endsWith('-Asset') || category.endsWith('-Liability') || category.endsWith('-Equity');\r\n}\r\n\r\n/** Check if an account type is an income statement account */\r\nexport function isIncomeStatementAccountType(accountType: AccountType): boolean {\r\n const { category } = accountType;\r\n return category.endsWith('-Income') || category.endsWith('-Expense');\r\n}\r\n\r\n/**\r\n * Calculate a total from sub-accounts using the totalAccountTypes formula.\r\n * @param formula - Array of { account, operation } instructions\r\n * @param balanceMap - Map of account code → balance\r\n */\r\nexport function calculateTotal(\r\n formula: readonly TotalAccountOp[],\r\n balanceMap: Map<string, number>,\r\n): number {\r\n let total = 0;\r\n for (const item of formula) {\r\n const balance = balanceMap.get(item.account) ?? 0;\r\n total += item.operation === '+' ? balance : -balance;\r\n }\r\n return total;\r\n}\r\n\r\n/**\r\n * Compute the ending balance for an account given its debits and credits.\r\n * Uses the account's main type to determine normal balance direction.\r\n *\r\n * Assets & Expenses: debit - credit\r\n * Liabilities, Equity & Income: credit - debit\r\n */\r\nexport function computeEndingBalance(\r\n category: CategoryKey,\r\n totalDebit: number,\r\n totalCredit: number,\r\n): number {\r\n const mainType = extractMainType(category);\r\n if (mainType === 'Asset' || mainType === 'Expense') {\r\n return totalDebit - totalCredit;\r\n }\r\n return totalCredit - totalDebit;\r\n}\r\n\r\n/**\r\n * Build a lookup map from an array of account types.\r\n */\r\nexport function buildAccountTypeMap(accountTypes: readonly AccountType[]): Map<string, AccountType> {\r\n const map = new Map<string, AccountType>();\r\n for (const at of accountTypes) {\r\n map.set(at.code, at);\r\n }\r\n return map;\r\n}\r\n","/**\r\n * Balance Sheet Report\r\n *\r\n * Assets = Liabilities + Equity\r\n * Net income injected into retained earnings for the current fiscal year.\r\n */\r\n\r\nimport type { Model, PipelineStage } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { BalanceSheetReport, ReportCategory, ReportGroup, ReportAccount } from '../types/report.js';\r\nimport { getDateRange, getFiscalYearStart } from '../utils/date-range.js';\r\nimport { computeEndingBalance, calculateTotal, isVirtualTaxAccount, buildAccountTypeMap } from '../utils/account-helpers.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\nimport type { CategoryKey } from '../types/core.js';\r\n\r\nexport interface BalanceSheetOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n fiscalYearStartMonth?: number;\r\n /** Display code for prior retained earnings (default: '3660') */\r\n retainedEarningsCode?: string;\r\n /** Display code for current year net income (default: '3680') */\r\n currentYearEarningsCode?: string;\r\n}\r\n\r\nexport async function generateBalanceSheet(\r\n opts: BalanceSheetOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n businessName?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<BalanceSheetReport> {\r\n const {\r\n AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth = 1,\r\n retainedEarningsCode = country.retainedEarningsCode ?? '3660',\r\n currentYearEarningsCode = country.currentYearEarningsCode ?? '3680',\r\n } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const fiscalYearStart = getFiscalYearStart(endDate, fiscalYearStartMonth);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch accounts\r\n const q: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) q[orgField] = params.organizationId;\r\n const allAccounts = await AccountModel.find(q).lean() as Array<Record<string, unknown>>;\r\n\r\n // Balance sheet account IDs\r\n const bsIds = allAccounts\r\n .filter(a => {\r\n const at = country.getAccountType(a.accountTypeCode as string);\r\n return at && !at.isGroup && at.category.startsWith('Balance Sheet');\r\n })\r\n .map(a => a._id);\r\n\r\n // Income statement account IDs (for net income calculation)\r\n const isIds = allAccounts\r\n .filter(a => {\r\n const at = country.getAccountType(a.accountTypeCode as string);\r\n return at && !at.isGroup && !at.isTotal && at.category.startsWith('Income Statement');\r\n })\r\n .map(a => a._id);\r\n\r\n const baseMatch: Record<string, unknown> = { state: 'posted' };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n // Run pipelines in parallel\r\n const [bsResults, netIncomeResults, priorRetainedResults] = await Promise.all([\r\n // Balance sheet balances (all time up to endDate)\r\n JournalEntryModel.aggregate([\r\n { $match: { ...baseMatch, date: { $lte: endDate } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: bsIds }, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Promise<Array<{ _id: unknown; d: number; c: number }>>,\r\n\r\n // Net income (fiscal year start → endDate)\r\n JournalEntryModel.aggregate([\r\n { $match: { ...baseMatch, date: { $gte: fiscalYearStart, $lte: endDate } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds }, ...itemFilters } },\r\n { $group: { _id: null, d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Promise<Array<{ _id: unknown; d: number; c: number }>>,\r\n\r\n // Prior retained earnings (all income statement before fiscal year)\r\n JournalEntryModel.aggregate([\r\n { $match: { ...baseMatch, date: { $lt: fiscalYearStart } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds }, ...itemFilters } },\r\n { $group: { _id: null, d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Promise<Array<{ _id: unknown; d: number; c: number }>>,\r\n ]);\r\n\r\n const netIncome = netIncomeResults.length > 0 ? netIncomeResults[0].c - netIncomeResults[0].d : 0;\r\n const priorRetained = priorRetainedResults.length > 0 ? priorRetainedResults[0].c - priorRetainedResults[0].d : 0;\r\n\r\n // Build categories\r\n const accountMap = new Map(allAccounts.map(a => [String(a._id), a]));\r\n const accountTypeMap = buildAccountTypeMap(country.accountTypes);\r\n const balanceMap = new Map<string, number>();\r\n\r\n const labels = country.reportLabels ?? {};\r\n const assets: ReportCategory = { name: labels.assets ?? 'Assets', total: 0, groups: [] };\r\n const liabilities: ReportCategory = { name: labels.liabilities ?? 'Liabilities', total: 0, groups: [] };\r\n const equity: ReportCategory = { name: labels.equity ?? 'Equity', total: 0, groups: [] };\r\n\r\n const groupsMap: Record<string, Record<string, ReportGroup>> = {\r\n Asset: {}, Liability: {}, Equity: {},\r\n };\r\n\r\n for (const r of bsResults) {\r\n const acc = accountMap.get(String(r._id));\r\n if (!acc) continue;\r\n\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at) continue;\r\n\r\n const mainType = extractMainType(at.category) ?? 'Asset';\r\n const balance = computeEndingBalance(at.category as CategoryKey, r.d, r.c);\r\n balanceMap.set(at.code, balance);\r\n\r\n const parentAt = at.parentCode ? country.getAccountType(at.parentCode) : undefined;\r\n const groupName = parentAt?.name ?? at.name;\r\n\r\n if (!(groupName in groupsMap[mainType])) {\r\n groupsMap[mainType][groupName] = { name: groupName, total: 0, accounts: [] };\r\n }\r\n\r\n const group = groupsMap[mainType][groupName];\r\n\r\n // Skip virtual tax sub-accounts from display but include in calculation\r\n if (!isVirtualTaxAccount(at, accountTypeMap)) {\r\n group.accounts.push({\r\n id: acc._id,\r\n name: (acc.name as string) ?? at.name,\r\n code: (acc.accountNumber as string) ?? at.code,\r\n balance,\r\n isTotal: at.isTotal,\r\n isVirtualTotal: at.isVirtualTotal,\r\n });\r\n }\r\n\r\n if (!at.isTotal) {\r\n group.total += balance;\r\n }\r\n }\r\n\r\n // Add retained earnings to equity\r\n const reGroup: ReportGroup = {\r\n name: 'Retained Earnings',\r\n total: priorRetained + netIncome,\r\n accounts: [\r\n { id: 'prior-retained', name: 'Previous Years Retained Earnings', code: retainedEarningsCode, balance: priorRetained },\r\n { id: 'current-year', name: `Current Year Net Income (${endDate.getFullYear()})`, code: currentYearEarningsCode, balance: netIncome, isCalculated: true },\r\n ],\r\n };\r\n\r\n if (!(reGroup.name in groupsMap.Equity)) {\r\n groupsMap.Equity[reGroup.name] = reGroup;\r\n } else {\r\n groupsMap.Equity[reGroup.name].accounts.push(...reGroup.accounts);\r\n groupsMap.Equity[reGroup.name].total += reGroup.total;\r\n }\r\n\r\n // Convert groups maps to arrays\r\n assets.groups = Object.values(groupsMap.Asset);\r\n liabilities.groups = Object.values(groupsMap.Liability);\r\n equity.groups = Object.values(groupsMap.Equity);\r\n\r\n // Sum totals\r\n assets.total = assets.groups.reduce((s, g) => s + g.total, 0);\r\n liabilities.total = liabilities.groups.reduce((s, g) => s + g.total, 0);\r\n equity.total = equity.groups.reduce((s, g) => s + g.total, 0);\r\n\r\n const liabilitiesAndEquity = liabilities.total + equity.total;\r\n\r\n return {\r\n metadata: {\r\n businessName: params.businessName,\r\n generatedAt: new Date().toISOString(),\r\n asOfDate: endDate.toISOString().split('T')[0],\r\n displayDate: `As of ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}`,\r\n },\r\n assets,\r\n liabilities,\r\n equity,\r\n summary: {\r\n totalAssets: assets.total,\r\n totalLiabilities: liabilities.total,\r\n totalEquity: equity.total,\r\n liabilitiesAndEquity,\r\n difference: assets.total - liabilitiesAndEquity,\r\n isBalanced: assets.total === liabilitiesAndEquity,\r\n },\r\n };\r\n}\r\n","/**\r\n * Income Statement (Profit & Loss) Report\r\n *\r\n * Revenue - COGS = Gross Profit\r\n * Gross Profit - Operating Expenses = Operating Income\r\n * Operating Income ± Other = Net Income\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { IncomeStatementReport, ReportCategory, ReportGroup } from '../types/report.js';\r\nimport { getDateRange } from '../utils/date-range.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface IncomeStatementOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n}\r\n\r\nexport async function generateIncomeStatement(\r\n opts: IncomeStatementOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n businessName?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<IncomeStatementReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch accounts\r\n const q: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) q[orgField] = params.organizationId;\r\n const allAccounts = await AccountModel.find(q).lean() as Array<Record<string, unknown>>;\r\n\r\n // Income statement posting accounts only\r\n const isAccounts = allAccounts.filter(a => {\r\n const at = country.getAccountType(a.accountTypeCode as string);\r\n return at && !at.isGroup && !at.isTotal && at.category.startsWith('Income Statement');\r\n });\r\n const isIds = isAccounts.map(a => a._id);\r\n\r\n const baseMatch: Record<string, unknown> = {\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n const results = await JournalEntryModel.aggregate([\r\n { $match: baseMatch },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds }, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Array<{ _id: unknown; d: number; c: number }>;\r\n\r\n const accountMap = new Map(allAccounts.map(a => [String(a._id), a]));\r\n\r\n // Organize into revenue and expenses\r\n const revenueGroups: Record<string, ReportGroup> = {};\r\n const expenseGroups: Record<string, ReportGroup> = {};\r\n\r\n for (const r of results) {\r\n const acc = accountMap.get(String(r._id));\r\n if (!acc) continue;\r\n\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at) continue;\r\n\r\n const mainType = extractMainType(at.category);\r\n const netAmount = mainType === 'Income' ? r.c - r.d : r.d - r.c;\r\n if (netAmount === 0) continue;\r\n\r\n const parentAt = at.parentCode ? country.getAccountType(at.parentCode) : undefined;\r\n const groupName = parentAt?.name ?? at.name;\r\n\r\n const groups = mainType === 'Income' ? revenueGroups : expenseGroups;\r\n\r\n if (!(groupName in groups)) {\r\n groups[groupName] = { name: groupName, total: 0, accounts: [] };\r\n }\r\n\r\n groups[groupName].accounts.push({\r\n id: acc._id,\r\n name: (acc.name as string) ?? at.name,\r\n code: (acc.accountNumber as string) ?? at.code,\r\n balance: netAmount,\r\n });\r\n groups[groupName].total += netAmount;\r\n }\r\n\r\n const labels = country.reportLabels ?? {};\r\n const revenue: ReportCategory = {\r\n name: labels.revenue ?? 'Revenue',\r\n total: Object.values(revenueGroups).reduce((s, g) => s + g.total, 0),\r\n groups: Object.values(revenueGroups),\r\n };\r\n\r\n const expenses: ReportCategory = {\r\n name: labels.expenses ?? 'Expenses',\r\n total: Object.values(expenseGroups).reduce((s, g) => s + g.total, 0),\r\n groups: Object.values(expenseGroups),\r\n };\r\n\r\n // Calculate COGS — use pack-declared group code, fall back to common names\r\n const cogsCode = country.cogsGroupCode;\r\n const isCogs = (name: string) =>\r\n cogsCode\r\n ? name === cogsCode\r\n : name === 'Cost of Sales' || name === 'Cost of Goods Sold';\r\n\r\n const cogsGroup = expenses.groups.find(g => isCogs(g.name));\r\n const costOfSales = cogsGroup?.total ?? 0;\r\n const grossProfit = revenue.total - costOfSales;\r\n const operatingExpenses = expenses.groups\r\n .filter(g => !isCogs(g.name))\r\n .reduce((s, g) => s + g.total, 0);\r\n const operatingIncome = grossProfit - operatingExpenses;\r\n const netIncome = revenue.total - expenses.total;\r\n\r\n const periodDisplay =\r\n params.dateOption === 'year'\r\n ? `For the year ended ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}`\r\n : `${startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}`;\r\n\r\n return {\r\n metadata: {\r\n businessName: params.businessName,\r\n generatedAt: new Date().toISOString(),\r\n periodStart: startDate.toISOString().split('T')[0],\r\n periodEnd: endDate.toISOString().split('T')[0],\r\n displayPeriod: periodDisplay,\r\n },\r\n revenue,\r\n costOfSales,\r\n grossProfit,\r\n expenses,\r\n operatingIncome,\r\n netIncome,\r\n };\r\n}\r\n","/**\r\n * General Ledger Report\r\n *\r\n * Shows every posted entry for selected accounts with running balances.\r\n * Uses batched queries (3 max) instead of per-account loops.\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { GeneralLedgerReport, GeneralLedgerAccount, LedgerEntry } from '../types/report.js';\r\nimport type { AccountType } from '../types/core.js';\r\nimport type { CategoryKey } from '../types/core.js';\r\nimport { getDateRange, getFiscalYearStart } from '../utils/date-range.js';\r\nimport { computeEndingBalance } from '../utils/account-helpers.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface GeneralLedgerOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n fiscalYearStartMonth?: number;\r\n}\r\n\r\nexport async function generateGeneralLedger(\r\n opts: GeneralLedgerOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n accountId?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<GeneralLedgerReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth = 1 } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const fiscalYearStart = getFiscalYearStart(startDate, fiscalYearStartMonth);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Get target accounts\r\n const acctQuery: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) acctQuery[orgField] = params.organizationId;\r\n if (params.accountId) acctQuery._id = params.accountId;\r\n\r\n const allAccounts = await AccountModel.find(acctQuery).lean() as Array<Record<string, unknown>>;\r\n\r\n // Filter to postable accounts (no groups, no totals)\r\n const filtered: Array<{ acc: Record<string, unknown>; at: AccountType }> = [];\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at || at.isGroup || at.isTotal) continue;\r\n filtered.push({ acc, at });\r\n }\r\n\r\n if (filtered.length === 0) {\r\n return { accounts: [], period: { startDate, endDate } };\r\n }\r\n\r\n // Separate BS vs IS account IDs (different opening-balance date ranges)\r\n const bsAccountIds: unknown[] = [];\r\n const isAccountIds: unknown[] = [];\r\n const allAccountIds: unknown[] = [];\r\n\r\n for (const { acc, at } of filtered) {\r\n allAccountIds.push(acc._id);\r\n if (at.category.startsWith('Balance Sheet')) {\r\n bsAccountIds.push(acc._id);\r\n } else {\r\n isAccountIds.push(acc._id);\r\n }\r\n }\r\n\r\n // Org scope helper\r\n const orgScope: Record<string, unknown> = {};\r\n if (orgField && params.organizationId) orgScope[orgField] = params.organizationId;\r\n\r\n // ── Batch queries (3 max, run in parallel) ──────────────────────────────────\r\n\r\n const openingBalancePipeline = (\r\n accountIds: unknown[],\r\n dateFilter: Record<string, unknown>,\r\n ) =>\r\n accountIds.length > 0\r\n ? JournalEntryModel.aggregate([\r\n { $match: { state: 'posted', date: dateFilter, ...orgScope } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: accountIds }, ...itemFilters } },\r\n {\r\n $group: {\r\n _id: '$journalItems.account',\r\n d: { $sum: '$journalItems.debit' },\r\n c: { $sum: '$journalItems.credit' },\r\n },\r\n },\r\n ])\r\n : Promise.resolve([]);\r\n\r\n const [bsOpenResults, isOpenResults, periodEntries] = await Promise.all([\r\n // BS opening: all posted entries before startDate\r\n openingBalancePipeline(bsAccountIds, { $lt: startDate }),\r\n // IS opening: posted entries from fiscal year start to before startDate\r\n openingBalancePipeline(isAccountIds, { $gte: fiscalYearStart, $lt: startDate }),\r\n // Period entries: all posted entries for any target account in the period\r\n JournalEntryModel.find({\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n 'journalItems.account': { $in: allAccountIds },\r\n ...orgScope,\r\n ...itemFilters,\r\n })\r\n .select('date referenceNumber label journalItems')\r\n .sort({ date: 1 })\r\n .lean() as Promise<Array<Record<string, unknown>>>,\r\n ]);\r\n\r\n // ── Build lookup maps ───────────────────────────────────────────────────────\r\n\r\n // Opening balance by account ID\r\n const openBalMap = new Map<string, { d: number; c: number }>();\r\n for (const r of [...(bsOpenResults as Array<{ _id: unknown; d: number; c: number }>),\r\n ...(isOpenResults as Array<{ _id: unknown; d: number; c: number }>)]) {\r\n openBalMap.set(String(r._id), { d: r.d, c: r.c });\r\n }\r\n\r\n // ── Pre-index period entries by account ID (O(entries × items) once) ────────\r\n\r\n const entryItemsByAccount = new Map<string, Array<{\r\n date: Date; referenceNumber: string; label: string; debit: number; credit: number;\r\n }>>();\r\n\r\n for (const entry of periodEntries) {\r\n const items = (entry.journalItems as Array<Record<string, unknown>>) ?? [];\r\n for (const item of items) {\r\n const accId = String(item.account);\r\n const debit = (item.debit as number) ?? 0;\r\n const credit = (item.credit as number) ?? 0;\r\n\r\n let list = entryItemsByAccount.get(accId);\r\n if (!list) {\r\n list = [];\r\n entryItemsByAccount.set(accId, list);\r\n }\r\n list.push({\r\n date: entry.date as Date,\r\n referenceNumber: (entry.referenceNumber as string) ?? '',\r\n label: (entry.label as string) ?? '',\r\n debit,\r\n credit,\r\n });\r\n }\r\n }\r\n\r\n // ── Assemble per-account results (O(accounts + total items)) ──────────────\r\n\r\n const glAccounts: GeneralLedgerAccount[] = [];\r\n\r\n for (const { acc, at } of filtered) {\r\n const accIdStr = String(acc._id);\r\n const openData = openBalMap.get(accIdStr);\r\n const openingBalance = openData\r\n ? computeEndingBalance(at.category as CategoryKey, openData.d, openData.c)\r\n : 0;\r\n\r\n let runningBalance = openingBalance;\r\n const entries: LedgerEntry[] = [];\r\n const mainType = extractMainType(at.category as CategoryKey);\r\n\r\n const accountItems = entryItemsByAccount.get(accIdStr) ?? [];\r\n for (const item of accountItems) {\r\n const delta =\r\n mainType === 'Asset' || mainType === 'Expense'\r\n ? item.debit - item.credit\r\n : item.credit - item.debit;\r\n\r\n runningBalance += delta;\r\n\r\n entries.push({\r\n date: item.date,\r\n referenceNumber: item.referenceNumber,\r\n label: item.label,\r\n debit: item.debit,\r\n credit: item.credit,\r\n runningBalance,\r\n });\r\n }\r\n\r\n glAccounts.push({\r\n account: acc,\r\n openingBalance,\r\n entries,\r\n closingBalance: runningBalance,\r\n });\r\n }\r\n\r\n return { accounts: glAccounts, period: { startDate, endDate } };\r\n}\r\n","/**\r\n * Cash Flow Statement\r\n *\r\n * Groups transactions by cashFlowCategory from account type definitions:\r\n * Operating, Investing, Financing.\r\n * Uses aggregation pipeline — no in-memory processing.\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { CashFlowCategory, CategoryKey } from '../types/core.js';\r\nimport type { CashFlowReport } from '../types/report.js';\r\nimport { getDateRange } from '../utils/date-range.js';\r\nimport { computeEndingBalance } from '../utils/account-helpers.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface CashFlowOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n}\r\n\r\nexport async function generateCashFlow(\r\n opts: CashFlowOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n businessName?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<CashFlowReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch accounts\r\n const q: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) q[orgField] = params.organizationId;\r\n const allAccounts = await AccountModel.find(q).lean() as Array<Record<string, unknown>>;\r\n\r\n // Build maps: accountId -> metadata, accountId -> raw account doc\r\n const accountCfMap = new Map<string, { category: CategoryKey; cfCategory: CashFlowCategory }>();\r\n const accountMap = new Map(allAccounts.map(a => [String(a._id), a]));\r\n const cfAccountIds: unknown[] = [];\r\n\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at || at.isGroup || at.isTotal) continue;\r\n\r\n const cf = at.cashFlowCategory;\r\n if (!cf) continue;\r\n\r\n // Normalize case: 'operating' → 'Operating'\r\n const normalized = (cf.charAt(0).toUpperCase() + cf.slice(1)) as CashFlowCategory;\r\n accountCfMap.set(String(acc._id), { category: at.category, cfCategory: normalized });\r\n cfAccountIds.push(acc._id);\r\n }\r\n\r\n // Aggregate journal items for accounts with cashFlowCategory\r\n const baseMatch: Record<string, unknown> = {\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n const results = cfAccountIds.length > 0\r\n ? await JournalEntryModel.aggregate([\r\n { $match: baseMatch },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: cfAccountIds }, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Array<{ _id: unknown; d: number; c: number }>\r\n : [];\r\n\r\n // Accumulate by category\r\n const flows: Record<CashFlowCategory, { total: number; accounts: Array<{ name: string; code: string; amount: number }> }> = {\r\n Operating: { total: 0, accounts: [] },\r\n Investing: { total: 0, accounts: [] },\r\n Financing: { total: 0, accounts: [] },\r\n };\r\n\r\n for (const r of results) {\r\n const accIdStr = String(r._id);\r\n const meta = accountCfMap.get(accIdStr);\r\n if (!meta) continue;\r\n\r\n // Net cash flow: for assets/expenses, debit increases (cash out), credit decreases (cash in)\r\n // For liabilities/equity/income, credit increases, debit decreases\r\n const amount = computeEndingBalance(meta.category, r.d, r.c);\r\n const acc = accountMap.get(accIdStr);\r\n const at = country.getAccountType(acc?.accountTypeCode as string);\r\n\r\n flows[meta.cfCategory].accounts.push({\r\n name: (acc?.name as string) ?? at?.name ?? '',\r\n code: (acc?.accountNumber as string) ?? at?.code ?? '',\r\n amount,\r\n });\r\n flows[meta.cfCategory].total += amount;\r\n }\r\n\r\n const netCashFlow = flows.Operating.total + flows.Investing.total + flows.Financing.total;\r\n\r\n const periodDisplay = `${startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}`;\r\n\r\n return {\r\n metadata: {\r\n businessName: params.businessName,\r\n generatedAt: new Date().toISOString(),\r\n periodStart: startDate.toISOString().split('T')[0],\r\n periodEnd: endDate.toISOString().split('T')[0],\r\n displayPeriod: periodDisplay,\r\n },\r\n operating: flows.Operating,\r\n investing: flows.Investing,\r\n financing: flows.Financing,\r\n netCashFlow,\r\n };\r\n}\r\n","/**\r\n * Fiscal Year Closing & Reopening\r\n *\r\n * Close: zeroes income/expense accounts, transfers net income to retained\r\n * earnings via a YEAR_END journal entry, marks the period closed.\r\n *\r\n * Reopen: validates no later period is closed, deletes the closing entry,\r\n * marks the period open again with an audit trail.\r\n *\r\n * Session management: creates an internal transaction by default.\r\n * Pass an external session to join a caller-managed transaction instead.\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { Errors } from '../utils/errors.js';\r\nimport type { Logger } from '../utils/logger.js';\r\nimport { defaultLogger } from '../utils/logger.js';\r\nimport { acquireSession, finalizeSession } from '../utils/session.js';\r\n\r\nexport interface FiscalCloseOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n FiscalPeriodModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n retainedEarningsCode?: string;\r\n logger?: Logger;\r\n}\r\n\r\nexport interface FiscalCloseResult {\r\n periodId: unknown;\r\n netIncome: number;\r\n closingEntryId: unknown | null;\r\n accountsClosed: number;\r\n closedAt: Date;\r\n}\r\n\r\nexport async function closeFiscalPeriod(\r\n opts: FiscalCloseOptions,\r\n params: {\r\n periodId: unknown;\r\n organizationId?: unknown;\r\n closedBy?: string;\r\n session?: ClientSession;\r\n },\r\n): Promise<FiscalCloseResult> {\r\n const {\r\n AccountModel,\r\n JournalEntryModel,\r\n FiscalPeriodModel,\r\n country,\r\n orgField,\r\n retainedEarningsCode = country.retainedEarningsCode ?? '3660',\r\n logger = defaultLogger,\r\n } = opts;\r\n const { periodId, organizationId, closedBy } = params;\r\n requireOrgScope(orgField, organizationId);\r\n\r\n const { session, ownSession } = await acquireSession(\r\n AccountModel.db,\r\n params.session,\r\n logger,\r\n );\r\n let success = false;\r\n\r\n try {\r\n const queryOpts = session ? { session } : {};\r\n\r\n // 1. Fetch and validate the fiscal period (org-scoped)\r\n const periodQuery: Record<string, unknown> = { _id: periodId };\r\n if (orgField && organizationId) periodQuery[orgField] = organizationId;\r\n const period = await FiscalPeriodModel.findOne(periodQuery, null, queryOpts).lean() as Record<string, unknown> | null;\r\n if (!period) throw Errors.notFound('Fiscal period not found');\r\n if (period.closed) throw Errors.fiscal('Fiscal period is already closed');\r\n\r\n const startDate = period.startDate as Date;\r\n const endDate = period.endDate as Date;\r\n\r\n // 2. Find all income statement accounts for this org\r\n const accountQuery: Record<string, unknown> = { active: true };\r\n if (orgField && organizationId) accountQuery[orgField] = organizationId;\r\n const allAccounts = await AccountModel.find(accountQuery, null, queryOpts).lean() as Array<Record<string, unknown>>;\r\n\r\n const isAccounts: Array<{ id: unknown; code: string; isIncome: boolean }> = [];\r\n let retainedEarningsId: unknown = null;\r\n\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at) continue;\r\n\r\n if (acc.accountTypeCode === retainedEarningsCode) {\r\n retainedEarningsId = acc._id;\r\n }\r\n\r\n if (at.isGroup || at.isTotal) continue;\r\n if (at.category.startsWith('Income Statement')) {\r\n isAccounts.push({\r\n id: acc._id,\r\n code: at.code,\r\n isIncome: at.category === 'Income Statement-Income',\r\n });\r\n }\r\n }\r\n\r\n if (!retainedEarningsId) {\r\n throw Errors.fiscal(\r\n `Retained earnings account (code: ${retainedEarningsCode}) not found. ` +\r\n 'Create this account before closing the fiscal period.',\r\n );\r\n }\r\n\r\n // 3. Aggregate balances for all IS accounts in the period\r\n const baseMatch: Record<string, unknown> = {\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n };\r\n if (orgField && organizationId) baseMatch[orgField] = organizationId;\r\n\r\n const isIds = isAccounts.map(a => a.id);\r\n const balances = isIds.length > 0\r\n ? await JournalEntryModel.aggregate([\r\n { $match: baseMatch },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds } } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ], queryOpts) as Array<{ _id: unknown; d: number; c: number }>\r\n : [];\r\n\r\n // 4. Build closing journal entry items\r\n const closingItems: Array<{ account: unknown; debit: number; credit: number; label: string }> = [];\r\n let netIncome = 0;\r\n\r\n const balMap = new Map(balances.map(b => [String(b._id), b]));\r\n\r\n for (const acc of isAccounts) {\r\n const bal = balMap.get(String(acc.id));\r\n if (!bal) continue;\r\n\r\n const net = bal.c - bal.d;\r\n if (net === 0) continue;\r\n\r\n closingItems.push({\r\n account: acc.id,\r\n debit: net > 0 ? net : 0,\r\n credit: net < 0 ? Math.abs(net) : 0,\r\n label: `Close ${acc.code}`,\r\n });\r\n\r\n netIncome += net;\r\n }\r\n\r\n let closingEntryId: unknown = null;\r\n\r\n if (closingItems.length > 0) {\r\n closingItems.push({\r\n account: retainedEarningsId,\r\n debit: netIncome < 0 ? Math.abs(netIncome) : 0,\r\n credit: netIncome > 0 ? netIncome : 0,\r\n label: 'Transfer net income to retained earnings',\r\n });\r\n\r\n const totalDebit = closingItems.reduce((s, i) => s + i.debit, 0);\r\n const totalCredit = closingItems.reduce((s, i) => s + i.credit, 0);\r\n\r\n const closingEntryData: Record<string, unknown> = {\r\n journalType: 'YEAR_END',\r\n state: 'posted',\r\n date: endDate,\r\n label: `Fiscal year closing – ${(period.name as string) ?? 'Period'}`,\r\n journalItems: closingItems,\r\n totalDebit,\r\n totalCredit,\r\n };\r\n if (orgField && organizationId) closingEntryData[orgField] = organizationId;\r\n\r\n const [closingEntry] = await JournalEntryModel.create([closingEntryData], queryOpts);\r\n closingEntryId = closingEntry._id;\r\n }\r\n\r\n // 5. Mark the period as closed (org-scoped)\r\n const closedAt = new Date();\r\n await FiscalPeriodModel.findOneAndUpdate(\r\n periodQuery,\r\n { closed: true, closedAt, closedBy: closedBy ?? null, closingEntryId },\r\n queryOpts,\r\n );\r\n\r\n const result: FiscalCloseResult = {\r\n periodId,\r\n netIncome,\r\n closingEntryId,\r\n accountsClosed: closingItems.length - (closingItems.length > 0 ? 1 : 0),\r\n closedAt,\r\n };\r\n\r\n success = true;\r\n return result;\r\n } finally {\r\n await finalizeSession(session, ownSession, success);\r\n }\r\n}\r\n\r\n// ── Reopen ────────────────────────────────────────────────────────────────\r\n\r\nexport interface FiscalReopenResult {\r\n periodId: unknown;\r\n deletedEntryId: unknown | null;\r\n reopenedAt: Date;\r\n}\r\n\r\nexport async function reopenFiscalPeriod(\r\n opts: Pick<FiscalCloseOptions, 'JournalEntryModel' | 'FiscalPeriodModel'> & {\r\n orgField?: string;\r\n logger?: Logger;\r\n /** Any model on the same connection — used to start sessions */\r\n AccountModel?: Model<unknown>;\r\n },\r\n params: {\r\n periodId: unknown;\r\n organizationId?: unknown;\r\n reopenedBy?: string;\r\n session?: ClientSession;\r\n },\r\n): Promise<FiscalReopenResult> {\r\n const { JournalEntryModel, FiscalPeriodModel, orgField, logger = defaultLogger } = opts;\r\n const { periodId, organizationId, reopenedBy } = params;\r\n requireOrgScope(orgField, organizationId);\r\n\r\n // Use any available model's db connection for session creation\r\n const db = (opts.AccountModel ?? FiscalPeriodModel).db;\r\n const { session, ownSession } = await acquireSession(db, params.session, logger);\r\n let success = false;\r\n\r\n try {\r\n const queryOpts = session ? { session } : {};\r\n\r\n // 1. Fetch and validate the period (org-scoped)\r\n const periodQuery: Record<string, unknown> = { _id: periodId };\r\n if (orgField && organizationId) periodQuery[orgField] = organizationId;\r\n const period = await FiscalPeriodModel.findOne(periodQuery, null, queryOpts).lean() as Record<string, unknown> | null;\r\n if (!period) throw Errors.notFound('Fiscal period not found');\r\n if (!period.closed) throw Errors.fiscal('Fiscal period is not closed');\r\n\r\n // 2. Block if a later period is already closed (prevents cascade corruption)\r\n const laterQuery: Record<string, unknown> = {\r\n closed: true,\r\n startDate: { $gt: period.endDate },\r\n };\r\n if (orgField && organizationId) laterQuery[orgField] = organizationId;\r\n\r\n const laterClosed = await FiscalPeriodModel.findOne(laterQuery, null, queryOpts).lean();\r\n if (laterClosed) {\r\n throw Errors.fiscal(\r\n 'Cannot reopen: a later fiscal period is already closed. Reopen later periods first.',\r\n );\r\n }\r\n\r\n // 3. Delete the closing journal entry (if one was created)\r\n const closingEntryId = period.closingEntryId ?? null;\r\n if (closingEntryId) {\r\n await JournalEntryModel.findByIdAndDelete(closingEntryId, queryOpts);\r\n }\r\n\r\n // 4. Mark the period as reopened (org-scoped)\r\n const reopenedAt = new Date();\r\n await FiscalPeriodModel.findOneAndUpdate(\r\n periodQuery,\r\n {\r\n closed: false,\r\n closedAt: null,\r\n closedBy: null,\r\n closingEntryId: null,\r\n reopenedAt,\r\n reopenedBy: reopenedBy ?? null,\r\n },\r\n queryOpts,\r\n );\r\n\r\n const result: FiscalReopenResult = {\r\n periodId,\r\n deletedEntryId: closingEntryId,\r\n reopenedAt,\r\n };\r\n\r\n success = true;\r\n return result;\r\n } finally {\r\n await finalizeSession(session, ownSession, success);\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fiscal-period.schema-CbALaaKl.mjs","names":[],"sources":["../src/schemas/account.schema.ts","../src/schemas/journal-entry.schema.ts","../src/schemas/fiscal-period.schema.ts"],"sourcesContent":["/**\r\n * Account Schema Factory\r\n *\r\n * Creates a Mongoose schema for Chart of Accounts that is:\r\n * - Multi-tenant aware (adds org field + compound indexes when configured)\r\n * - Validates accountTypeCode against the country pack\r\n * - Supports accountNumber (unique per org) and name (user-facing display)\r\n * - Lean: no cached balances — always computed from journal entries\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, SchemaOptions } from '../types/engine.js';\r\n\r\nexport function createAccountSchema(\r\n config: AccountingEngineConfig,\r\n options: SchemaOptions = {},\r\n) {\r\n const { multiTenant, country } = config;\r\n const { indexes = true, extraFields = {}, extraIndexes = [] } = options;\r\n\r\n // ── Base fields ──────────────────────────────────────────────────────────\r\n\r\n const fields: Record<string, unknown> = {\r\n accountTypeCode: {\r\n type: String,\r\n required: true,\r\n validate: {\r\n validator: (code: string) => country.isValidAccountType(code),\r\n message: (props: { value: string }) =>\r\n `\"${props.value}\" is not a valid account type code for ${country.name}.`,\r\n },\r\n },\r\n accountNumber: {\r\n type: String,\r\n required: true,\r\n trim: true,\r\n },\r\n name: {\r\n type: String,\r\n required: true,\r\n trim: true,\r\n },\r\n active: { type: Boolean, default: true },\r\n isCashAccount: { type: Boolean, default: false },\r\n ...extraFields,\r\n };\r\n\r\n // ── Multi-tenant field ───────────────────────────────────────────────────\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // ── Schema ───────────────────────────────────────────────────────────────\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n // ── Pre-validate: auto-default accountNumber and name ──────────────────\r\n\r\n schema.pre('validate', function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n if (!doc.accountNumber && doc.accountTypeCode) {\r\n doc.accountNumber = doc.accountTypeCode;\r\n }\r\n if (!doc.name && doc.accountTypeCode) {\r\n const at = country.getAccountType(doc.accountTypeCode);\r\n doc.name = at?.name ?? doc.accountTypeCode;\r\n }\r\n });\r\n\r\n // ── Indexes ──────────────────────────────────────────────────────────────\r\n\r\n if (indexes) {\r\n if (multiTenant) {\r\n const org = multiTenant.orgField;\r\n schema.index({ [org]: 1, active: 1 });\r\n // accountNumber is the unique identity per org\r\n schema.index({ [org]: 1, accountNumber: 1 }, { unique: true });\r\n // accountTypeCode is non-unique — multiple accounts can share a classification\r\n schema.index({ [org]: 1, accountTypeCode: 1 });\r\n } else {\r\n schema.index({ active: 1 });\r\n schema.index({ accountNumber: 1 }, { unique: true });\r\n schema.index({ accountTypeCode: 1 });\r\n }\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n return schema;\r\n}\r\n","/**\r\n * Journal Entry Schema Factory\r\n *\r\n * Creates a Mongoose schema for double-entry journal entries.\r\n * - Multi-tenant aware\r\n * - Embedded journal items with account refs\r\n * - State machine: draft → posted, draft → archived\r\n * - Auto-generated reference numbers\r\n * - Double-entry validation on post\r\n * - Optimized indexes for high-load reporting\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, JournalSchemaOptions } from '../types/engine.js';\r\nimport { getJournalTypeCodes, JOURNAL_CODES } from '../constants/journals.js';\r\n\r\nexport function createJournalEntrySchema(\r\n config: AccountingEngineConfig,\r\n accountModelName: string,\r\n options: JournalSchemaOptions = {},\r\n) {\r\n const { multiTenant } = config;\r\n const {\r\n indexes = true,\r\n autoReference = true,\r\n textSearch = true,\r\n extraFields = {},\r\n extraIndexes = [],\r\n extraItemFields = {},\r\n } = options;\r\n\r\n // ── Tax Detail (audit reference only) ────────────────────────────────────\r\n\r\n const TaxDetailSchema = new mongoose.Schema(\r\n {\r\n taxCode: { type: String },\r\n taxName: { type: String },\r\n },\r\n { _id: false },\r\n );\r\n\r\n // ── Journal Item ─────────────────────────────────────────────────────────\r\n\r\n const amountValidator = {\r\n validator: (v: number) => Number.isInteger(v) && v >= 0,\r\n message: '{PATH} must be a non-negative integer (cents), got {VALUE}',\r\n };\r\n\r\n const JournalItemSchema = new mongoose.Schema(\r\n {\r\n account: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: accountModelName,\r\n required: true,\r\n },\r\n label: { type: String },\r\n date: { type: Date },\r\n debit: { type: Number, default: 0, min: 0, validate: amountValidator },\r\n credit: { type: Number, default: 0, min: 0, validate: amountValidator },\r\n taxDetails: { type: [TaxDetailSchema], default: [] },\r\n ...extraItemFields,\r\n },\r\n { _id: false },\r\n );\r\n\r\n // ── Main fields ──────────────────────────────────────────────────────────\r\n\r\n const fields: Record<string, unknown> = {\r\n journalType: {\r\n type: String,\r\n enum: getJournalTypeCodes(),\r\n default: JOURNAL_CODES['MISC'],\r\n required: true,\r\n },\r\n referenceNumber: { type: String },\r\n label: { type: String },\r\n date: {\r\n type: Date,\r\n default: Date.now,\r\n required: function (this: { state?: string }) {\r\n return this.state !== 'draft';\r\n },\r\n },\r\n journalItems: { type: [JournalItemSchema], default: [] },\r\n totalDebit: { type: Number, required: true, min: 0, validate: { validator: Number.isInteger, message: 'totalDebit must be an integer (cents)' } },\r\n totalCredit: { type: Number, required: true, min: 0, validate: { validator: Number.isInteger, message: 'totalCredit must be an integer (cents)' } },\r\n state: {\r\n type: String,\r\n enum: ['draft', 'posted', 'archived'],\r\n default: 'draft',\r\n required: true,\r\n },\r\n stateChangedAt: { type: Date, default: Date.now },\r\n reversed: { type: Boolean, default: false },\r\n reversedBy: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: 'JournalEntry',\r\n default: null,\r\n },\r\n reversalOf: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: 'JournalEntry',\r\n default: null,\r\n },\r\n ...extraFields,\r\n };\r\n\r\n // ── Audit fields (conditional) ─────────────────────────────────────────\r\n\r\n if (config.audit?.trackActor) {\r\n fields.createdBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.postedBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.reversedByUser = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n }\r\n\r\n // ── Approval fields (conditional) ──────────────────────────────────────\r\n\r\n if (config.strictness?.requireApproval || config.audit?.trackActor) {\r\n fields.approvedBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.approvedAt = { type: Date, default: null };\r\n }\r\n\r\n // ── Idempotency key (conditional) ──────────────────────────────────────\r\n\r\n if (config.idempotency) {\r\n fields.idempotencyKey = { type: String, default: null };\r\n }\r\n\r\n // ── Multi-tenant field ───────────────────────────────────────────────────\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // ── Schema ───────────────────────────────────────────────────────────────\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n // ── Pre-validate: double-entry enforcement ───────────────────────────────\r\n\r\n schema.pre('validate', function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n\r\n // Propagate entry date to items without a date\r\n for (const item of doc.journalItems) {\r\n if (!item.date) item.date = doc.date;\r\n }\r\n\r\n // Each line must be debit OR credit (not both), and posted entries cannot have zero-value lines\r\n for (let i = 0; i < doc.journalItems.length; i++) {\r\n const d = doc.journalItems[i].debit || 0;\r\n const c = doc.journalItems[i].credit || 0;\r\n if (d > 0 && c > 0) {\r\n throw new Error(\r\n `Journal item at index ${i}: cannot have both debit (${d}) and credit (${c}) greater than zero`,\r\n );\r\n }\r\n if (doc.state === 'posted' && d === 0 && c === 0) {\r\n throw new Error(\r\n `Journal item at index ${i}: posted entries cannot have zero-value lines (both debit and credit are 0)`,\r\n );\r\n }\r\n }\r\n\r\n // Calculate totals\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const totalDebit = doc.journalItems.reduce((s: number, i: any) => s + (i.debit || 0), 0);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const totalCredit = doc.journalItems.reduce((s: number, i: any) => s + (i.credit || 0), 0);\r\n\r\n // Enforce minimum items and balance for posted entries\r\n if (doc.state === 'posted') {\r\n if (doc.journalItems.length < 2) {\r\n throw new Error('Posted entries must have at least 2 journal items');\r\n }\r\n if (totalDebit !== totalCredit) {\r\n throw new Error('Total debit must equal total credit for posted entries');\r\n }\r\n }\r\n\r\n doc.totalDebit = totalDebit;\r\n doc.totalCredit = totalCredit;\r\n });\r\n\r\n // ── Pre-save: auto-generate reference number ─────────────────────────────\r\n\r\n if (autoReference) {\r\n // Helper: compute next reference number from DB\r\n // Uses aggregation pipeline to extract & sort the numeric suffix,\r\n // avoiding lexicographic sort issues beyond sequence 9999.\r\n const generateReferenceNumber = async (doc: Record<string, unknown>, Model: mongoose.Model<unknown>, session: unknown) => {\r\n const jt = (doc.journalType as string) || 'MISC';\r\n const d = new Date(doc.date as string | number | Date);\r\n const year = d.getFullYear();\r\n const month = String(d.getMonth() + 1).padStart(2, '0');\r\n const prefix = `${jt}/${year}/${month}/`;\r\n\r\n // Build match filter\r\n const matchFilter: Record<string, unknown> = {\r\n referenceNumber: { $regex: `^${prefix.replace(/\\//g, '\\\\/')}` },\r\n };\r\n\r\n // Add org field to query for multi-tenant\r\n if (multiTenant) {\r\n matchFilter[multiTenant.orgField] = doc[multiTenant.orgField];\r\n }\r\n\r\n // Extract numeric suffix via $split and sort numerically\r\n const pipeline: mongoose.PipelineStage[] = [\r\n { $match: matchFilter },\r\n {\r\n $addFields: {\r\n _refSeq: {\r\n $toInt: {\r\n $arrayElemAt: [{ $split: ['$referenceNumber', '/'] }, -1],\r\n },\r\n },\r\n },\r\n },\r\n { $sort: { _refSeq: -1 as const } },\r\n { $limit: 1 },\r\n { $project: { _refSeq: 1 } },\r\n ];\r\n\r\n const results = await Model.aggregate(pipeline)\r\n .session(session as mongoose.mongo.ClientSession | null);\r\n\r\n let seq = 1;\r\n if (results.length > 0 && typeof results[0]._refSeq === 'number') {\r\n seq = results[0]._refSeq + 1;\r\n }\r\n\r\n return `${prefix}${String(seq).padStart(4, '0')}`;\r\n };\r\n\r\n schema.pre('save', async function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n\r\n if (doc.isModified('journalType')) {\r\n doc.referenceNumber = undefined;\r\n }\r\n\r\n if (!doc.referenceNumber) {\r\n const session = doc.$session?.() ?? null;\r\n const Model = doc.constructor as mongoose.Model<unknown>;\r\n doc.referenceNumber = await generateReferenceNumber(doc, Model, session);\r\n }\r\n });\r\n\r\n // Retry on duplicate key error (race condition between concurrent inserts)\r\n const MAX_REF_RETRIES = 3;\r\n schema.post('save', async function (error: Error, doc: unknown, next: (err?: Error) => void) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const mongoError = error as any;\r\n // 11000 = MongoDB duplicate key error\r\n if (mongoError.code === 11000 && mongoError.keyPattern?.referenceNumber) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const entry = doc as any;\r\n const retryCount: number = entry.__refRetries ?? 0;\r\n if (retryCount >= MAX_REF_RETRIES) {\r\n next(new Error(\r\n `Failed to generate unique reference number after ${MAX_REF_RETRIES} retries. ` +\r\n 'Too many concurrent inserts for this period.',\r\n ));\r\n return;\r\n }\r\n entry.__refRetries = retryCount + 1;\r\n const session = entry.$session?.() ?? null;\r\n const Model = entry.constructor as mongoose.Model<unknown>;\r\n entry.referenceNumber = await generateReferenceNumber(entry, Model, session);\r\n try {\r\n await entry.save({ session });\r\n next();\r\n } catch (retryError) {\r\n next(retryError as Error);\r\n }\r\n } else {\r\n next(error);\r\n }\r\n });\r\n }\r\n\r\n // ── Indexes ──────────────────────────────────────────────────────────────\r\n\r\n if (indexes) {\r\n const org = multiTenant?.orgField;\r\n\r\n // Partial filter: unique constraint only applies to docs with a string\r\n // referenceNumber — allows multiple entries without a ref when autoReference is off.\r\n const refPartial = { partialFilterExpression: { referenceNumber: { $exists: true, $type: 'string' } } };\r\n\r\n if (org) {\r\n schema.index({ [org]: 1, referenceNumber: 1 }, { unique: true, ...refPartial });\r\n schema.index({ [org]: 1, state: 1, date: 1 });\r\n schema.index({ [org]: 1, date: -1 });\r\n schema.index({ [org]: 1, journalType: 1 });\r\n schema.index({ 'journalItems.account': 1, state: 1 });\r\n schema.index({ [org]: 1, 'journalItems.account': 1, date: 1, state: 1 });\r\n } else {\r\n schema.index({ referenceNumber: 1 }, { unique: true, ...refPartial });\r\n schema.index({ state: 1, date: 1 });\r\n schema.index({ date: -1 });\r\n schema.index({ journalType: 1 });\r\n schema.index({ 'journalItems.account': 1, state: 1 });\r\n }\r\n\r\n schema.index({ reversed: 1 });\r\n\r\n // Idempotency key: unique sparse index (only when enabled)\r\n if (config.idempotency) {\r\n const idempotencyIdx: Record<string, 1 | -1> = {};\r\n if (org) idempotencyIdx[org] = 1;\r\n idempotencyIdx.idempotencyKey = 1;\r\n schema.index(idempotencyIdx, {\r\n unique: true,\r\n partialFilterExpression: { idempotencyKey: { $exists: true, $ne: null } },\r\n });\r\n }\r\n }\r\n\r\n if (textSearch) {\r\n schema.index(\r\n { referenceNumber: 'text', label: 'text' },\r\n { weights: { referenceNumber: 10, label: 5 }, name: 'journal_text_idx' },\r\n );\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n return schema;\r\n}\r\n","/**\r\n * Fiscal Period Schema Factory\r\n *\r\n * Creates a Mongoose schema for tracking fiscal periods (months, quarters, years).\r\n * Supports closing periods to lock entries.\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, SchemaOptions } from '../types/engine.js';\r\n\r\nexport function createFiscalPeriodSchema(\r\n config: AccountingEngineConfig,\r\n options: SchemaOptions = {},\r\n) {\r\n const { multiTenant } = config;\r\n const { indexes = true, extraFields = {}, extraIndexes = [] } = options;\r\n\r\n const fields: Record<string, unknown> = {\r\n name: { type: String, required: true },\r\n startDate: { type: Date, required: true },\r\n endDate: { type: Date, required: true },\r\n closed: { type: Boolean, default: false },\r\n closedAt: { type: Date, default: null },\r\n closedBy: { type: String, default: null },\r\n closingEntryId: { type: mongoose.Schema.Types.ObjectId, default: null },\r\n reopenedAt: { type: Date, default: null },\r\n reopenedBy: { type: String, default: null },\r\n ...extraFields,\r\n };\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n if (indexes) {\r\n if (multiTenant) {\r\n const org = multiTenant.orgField;\r\n schema.index({ [org]: 1, startDate: 1, endDate: 1 }, { unique: true });\r\n schema.index({ [org]: 1, closed: 1 });\r\n } else {\r\n schema.index({ startDate: 1, endDate: 1 }, { unique: true });\r\n schema.index({ closed: 1 });\r\n }\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n // ── Overlap guard: prevent overlapping date ranges within a tenant ─────\r\n schema.pre('validate', async function () {\r\n const doc = this as mongoose.Document & { startDate: Date; endDate: Date; [key: string]: unknown };\r\n if (!doc.startDate || !doc.endDate) return;\r\n\r\n // A period overlaps if: existing.startDate < this.endDate AND existing.endDate > this.startDate\r\n const overlapQuery: Record<string, unknown> = {\r\n _id: { $ne: doc._id },\r\n startDate: { $lt: doc.endDate },\r\n endDate: { $gt: doc.startDate },\r\n };\r\n\r\n if (multiTenant) {\r\n overlapQuery[multiTenant.orgField] = doc[multiTenant.orgField];\r\n }\r\n\r\n const overlap = await doc.collection.findOne(overlapQuery);\r\n if (overlap) {\r\n const msg = `Fiscal period overlaps with existing period \"${overlap.name}\" (${new Date(overlap.startDate).toISOString().split('T')[0]} – ${new Date(overlap.endDate).toISOString().split('T')[0]}).`;\r\n doc.invalidate('startDate', msg, doc.startDate, 'overlap');\r\n }\r\n });\r\n\r\n return schema;\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"universal-CMfrZ2hG.mjs","names":[],"sources":["../src/exports/csv-serializer.ts","../src/exports/field-map.ts","../src/exports/flatten-journal.ts","../src/exports/field-maps/quickbooks.ts","../src/exports/field-maps/universal.ts"],"sourcesContent":["/**\n * CSV Serializer — RFC 4180 compliant CSV string builder.\n *\n * Pure function. No I/O, no side effects.\n *\n * @module @classytic/ledger/exports\n */\n\nimport type { CsvOptions } from './types.js';\n\nconst NEEDS_QUOTING = /[\",\\r\\n]/;\n\n/** Escape a single CSV cell value per RFC 4180. */\nexport function escapeCell(value: string): string {\n if (NEEDS_QUOTING.test(value)) {\n return '\"' + value.replace(/\"/g, '\"\"') + '\"';\n }\n return value;\n}\n\n/** Serialize a 2D array of strings into a CSV string. */\nexport function serializeCsv(\n rows: readonly (readonly string[])[],\n options: CsvOptions = {},\n): string {\n const { delimiter = ',', lineTerminator = '\\r\\n' } = options;\n\n return rows\n .map(row => row.map(escapeCell).join(delimiter))\n .join(lineTerminator);\n}\n\n/** Build a CSV string with optional header row. */\nexport function buildCsv(\n headers: readonly string[],\n dataRows: readonly (readonly string[])[],\n options: CsvOptions = {},\n): string {\n const { includeHeaders = true } = options;\n const allRows = includeHeaders ? [headers, ...dataRows] : [...dataRows];\n return serializeCsv(allRows, options);\n}\n","/**\n * Field Map Application — Applies an ExportFieldMap to data rows.\n *\n * @module @classytic/ledger/exports\n */\n\nimport type { ExportFieldMap, CsvOptions } from './types.js';\nimport { buildCsv } from './csv-serializer.js';\n\n/** Extract headers from a field map. */\nexport function getHeaders<TRow>(fieldMap: ExportFieldMap<TRow>): string[] {\n return fieldMap.fields.map(f => f.header);\n}\n\n/** Apply a field map to a single row, producing an array of cell strings. */\nexport function extractRow<TRow>(fieldMap: ExportFieldMap<TRow>, row: TRow): string[] {\n return fieldMap.fields.map(f => f.extract(row));\n}\n\n/** Apply a field map to an array of rows, producing a 2D string array. */\nexport function extractAllRows<TRow>(\n fieldMap: ExportFieldMap<TRow>,\n rows: readonly TRow[],\n): string[][] {\n return rows.map(row => extractRow(fieldMap, row));\n}\n\n/** One-shot: map + serialize to CSV string. */\nexport function exportToCsv<TRow>(\n fieldMap: ExportFieldMap<TRow>,\n rows: readonly TRow[],\n options?: CsvOptions,\n): string {\n const headers = getHeaders(fieldMap);\n const dataRows = extractAllRows(fieldMap, rows);\n return buildCsv(headers, dataRows, options);\n}\n","/**\n * Journal Entry Flattener — Denormalizes journal entries into flat rows.\n *\n * Each journal item becomes one row with entry-level fields repeated.\n * Monetary values stay as integer cents (matching DB storage).\n *\n * @module @classytic/ledger/exports\n */\n\nimport type {\n PopulatedJournalEntry,\n PopulatedAccount,\n FlatJournalRow,\n} from './types.js';\n\nfunction toDate(value: Date | string | undefined | null): Date {\n if (!value) return new Date(0);\n if (value instanceof Date) return value;\n return new Date(value);\n}\n\nfunction resolveAccount(\n account: PopulatedAccount | string | null | undefined,\n): { id: string; name: string; typeCode: string } {\n if (!account) return { id: '', name: '', typeCode: '' };\n if (typeof account === 'string') return { id: account, name: '', typeCode: '' };\n return {\n id: String(account._id ?? ''),\n name: account.name ?? '',\n typeCode: account.accountTypeCode ?? '',\n };\n}\n\n/** Flatten a single journal entry into one FlatJournalRow per journal item. */\nexport function flattenJournalEntry(entry: PopulatedJournalEntry): FlatJournalRow[] {\n const entryDate = toDate(entry.date);\n const items = entry.journalItems ?? [];\n const itemCount = items.length;\n\n // Known core item keys — everything else is an extra dimension field\n const KNOWN_ITEM_KEYS = new Set(['account', 'label', 'date', 'debit', 'credit', 'taxDetails']);\n\n return items.map((item, index) => {\n const acct = resolveAccount(item.account);\n const firstTax = item.taxDetails?.[0];\n\n // Collect extra item fields (dimensions like departmentId, projectId, etc.)\n const extraItemFields: Record<string, unknown> = {};\n for (const key of Object.keys(item)) {\n if (!KNOWN_ITEM_KEYS.has(key)) {\n extraItemFields[key] = (item as Record<string, unknown>)[key];\n }\n }\n\n return {\n entryId: String(entry._id ?? ''),\n journalType: entry.journalType ?? '',\n referenceNumber: entry.referenceNumber ?? '',\n entryLabel: entry.label ?? '',\n entryDate,\n state: entry.state,\n reversed: entry.reversed ?? false,\n totalDebit: entry.totalDebit ?? 0,\n totalCredit: entry.totalCredit ?? 0,\n\n accountId: acct.id,\n accountName: acct.name,\n accountTypeCode: acct.typeCode,\n itemLabel: item.label ?? '',\n itemDate: item.date ? toDate(item.date) : entryDate,\n debit: item.debit ?? 0,\n credit: item.credit ?? 0,\n taxCode: firstTax?.taxCode ?? '',\n taxName: firstTax?.taxName ?? '',\n\n itemIndex: index,\n itemCount,\n ...extraItemFields,\n };\n });\n}\n\n/** Flatten multiple journal entries into a single flat row array. */\nexport function flattenJournalEntries(\n entries: readonly PopulatedJournalEntry[],\n): FlatJournalRow[] {\n const rows: FlatJournalRow[] = [];\n for (const entry of entries) {\n rows.push(...flattenJournalEntry(entry));\n }\n return rows;\n}\n","/**\n * QuickBooks General Journal Import Field Map\n *\n * Produces CSV compatible with QuickBooks Desktop and Online\n * \"Import General Journal Entries\" feature.\n *\n * @module @classytic/ledger/exports\n */\n\nimport type { ExportFieldMap, FlatJournalRow } from '../types.js';\nimport { Money } from '../../money.js';\n\nfunction formatQbDate(date: Date): string {\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n const y = date.getFullYear();\n return `${m}/${d}/${y}`;\n}\n\n/** Convert integer cents to dollar string, blank for zero. */\nfunction amountOrBlank(cents: number): string {\n if (cents === 0) return '';\n return Money.formatPlain(cents);\n}\n\nexport const quickbooksFieldMap: ExportFieldMap<FlatJournalRow> = {\n name: 'QuickBooks General Journal',\n target: 'quickbooks',\n fields: [\n { header: 'Date', extract: (row) => formatQbDate(row.entryDate) },\n { header: 'Transaction Type', extract: () => 'General Journal' },\n { header: 'Num', extract: (row) => row.referenceNumber },\n { header: 'Name', extract: () => '' },\n { header: 'Memo/Description', extract: (row) => row.itemLabel || row.entryLabel },\n { header: 'Account', extract: (row) => row.accountName || row.accountTypeCode || row.accountId },\n { header: 'Debit', extract: (row) => amountOrBlank(row.debit) },\n { header: 'Credit', extract: (row) => amountOrBlank(row.credit) },\n { header: 'Class', extract: () => '' },\n ],\n};\n","/**\n * Universal Export Field Map\n *\n * Comprehensive CSV export with all available fields.\n * Useful for data portability, auditing, or spreadsheet import.\n *\n * @module @classytic/ledger/exports\n */\n\nimport type { ExportFieldMap, FlatJournalRow } from '../types.js';\nimport { Money } from '../../money.js';\n\nfunction formatIsoDate(date: Date): string {\n return date.toISOString().split('T')[0];\n}\n\n/** Convert integer cents to dollar string (e.g. 10050 → \"100.50\"). */\nfunction centsToDisplay(cents: number): string {\n return Money.formatPlain(cents);\n}\n\nexport const universalFieldMap: ExportFieldMap<FlatJournalRow> = {\n name: 'Universal Journal Export',\n target: 'universal',\n fields: [\n { header: 'Entry ID', extract: (row) => row.entryId },\n { header: 'Date', extract: (row) => formatIsoDate(row.entryDate) },\n { header: 'Journal Type', extract: (row) => row.journalType },\n { header: 'Reference Number', extract: (row) => row.referenceNumber },\n { header: 'Entry Description', extract: (row) => row.entryLabel },\n { header: 'State', extract: (row) => row.state },\n { header: 'Reversed', extract: (row) => row.reversed ? 'Yes' : 'No' },\n { header: 'Account Code', extract: (row) => row.accountTypeCode },\n { header: 'Account Name', extract: (row) => row.accountName },\n { header: 'Line Description', extract: (row) => row.itemLabel },\n { header: 'Debit', extract: (row) => centsToDisplay(row.debit) },\n { header: 'Credit', extract: (row) => centsToDisplay(row.credit) },\n { header: 'Tax Code', extract: (row) => row.taxCode },\n { header: 'Tax Name', extract: (row) => row.taxName },\n { header: 'Entry Total Debit', extract: (row) => centsToDisplay(row.totalDebit) },\n { header: 'Entry Total Credit',extract: (row) => centsToDisplay(row.totalCredit) },\n { header: 'Line', extract: (row) => String(row.itemIndex + 1) },\n { header: 'Line Count', extract: (row) => String(row.itemCount) },\n ],\n};\n"],"mappings":";;;AAUA,MAAM,gBAAgB;;AAGtB,SAAgB,WAAW,OAAuB;AAChD,KAAI,cAAc,KAAK,MAAM,CAC3B,QAAO,OAAM,MAAM,QAAQ,MAAM,OAAK,GAAG;AAE3C,QAAO;;;AAIT,SAAgB,aACd,MACA,UAAsB,EAAE,EAChB;CACR,MAAM,EAAE,YAAY,KAAK,iBAAiB,WAAW;AAErD,QAAO,KACJ,KAAI,QAAO,IAAI,IAAI,WAAW,CAAC,KAAK,UAAU,CAAC,CAC/C,KAAK,eAAe;;;AAIzB,SAAgB,SACd,SACA,UACA,UAAsB,EAAE,EAChB;CACR,MAAM,EAAE,iBAAiB,SAAS;AAElC,QAAO,aADS,iBAAiB,CAAC,SAAS,GAAG,SAAS,GAAG,CAAC,GAAG,SAAS,EAC1C,QAAQ;;;;;;AC9BvC,SAAgB,WAAiB,UAA0C;AACzE,QAAO,SAAS,OAAO,KAAI,MAAK,EAAE,OAAO;;;AAI3C,SAAgB,WAAiB,UAAgC,KAAqB;AACpF,QAAO,SAAS,OAAO,KAAI,MAAK,EAAE,QAAQ,IAAI,CAAC;;;AAIjD,SAAgB,eACd,UACA,MACY;AACZ,QAAO,KAAK,KAAI,QAAO,WAAW,UAAU,IAAI,CAAC;;;AAInD,SAAgB,YACd,UACA,MACA,SACQ;AAGR,QAAO,SAFS,WAAW,SAAS,EACnB,eAAe,UAAU,KAAK,EACZ,QAAQ;;;;;ACpB7C,SAAS,OAAO,OAA+C;AAC7D,KAAI,CAAC,MAAO,wBAAO,IAAI,KAAK,EAAE;AAC9B,KAAI,iBAAiB,KAAM,QAAO;AAClC,QAAO,IAAI,KAAK,MAAM;;AAGxB,SAAS,eACP,SACgD;AAChD,KAAI,CAAC,QAAS,QAAO;EAAE,IAAI;EAAI,MAAM;EAAI,UAAU;EAAI;AACvD,KAAI,OAAO,YAAY,SAAU,QAAO;EAAE,IAAI;EAAS,MAAM;EAAI,UAAU;EAAI;AAC/E,QAAO;EACL,IAAI,OAAO,QAAQ,OAAO,GAAG;EAC7B,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ,mBAAmB;EACtC;;;AAIH,SAAgB,oBAAoB,OAAgD;CAClF,MAAM,YAAY,OAAO,MAAM,KAAK;CACpC,MAAM,QAAQ,MAAM,gBAAgB,EAAE;CACtC,MAAM,YAAY,MAAM;CAGxB,MAAM,kBAAkB,IAAI,IAAI;EAAC;EAAW;EAAS;EAAQ;EAAS;EAAU;EAAa,CAAC;AAE9F,QAAO,MAAM,KAAK,MAAM,UAAU;EAChC,MAAM,OAAO,eAAe,KAAK,QAAQ;EACzC,MAAM,WAAW,KAAK,aAAa;EAGnC,MAAM,kBAA2C,EAAE;AACnD,OAAK,MAAM,OAAO,OAAO,KAAK,KAAK,CACjC,KAAI,CAAC,gBAAgB,IAAI,IAAI,CAC3B,iBAAgB,OAAQ,KAAiC;AAI7D,SAAO;GACL,SAAS,OAAO,MAAM,OAAO,GAAG;GAChC,aAAa,MAAM,eAAe;GAClC,iBAAiB,MAAM,mBAAmB;GAC1C,YAAY,MAAM,SAAS;GAC3B;GACA,OAAO,MAAM;GACb,UAAU,MAAM,YAAY;GAC5B,YAAY,MAAM,cAAc;GAChC,aAAa,MAAM,eAAe;GAElC,WAAW,KAAK;GAChB,aAAa,KAAK;GAClB,iBAAiB,KAAK;GACtB,WAAW,KAAK,SAAS;GACzB,UAAU,KAAK,OAAO,OAAO,KAAK,KAAK,GAAG;GAC1C,OAAO,KAAK,SAAS;GACrB,QAAQ,KAAK,UAAU;GACvB,SAAS,UAAU,WAAW;GAC9B,SAAS,UAAU,WAAW;GAE9B,WAAW;GACX;GACA,GAAG;GACJ;GACD;;;AAIJ,SAAgB,sBACd,SACkB;CAClB,MAAM,OAAyB,EAAE;AACjC,MAAK,MAAM,SAAS,QAClB,MAAK,KAAK,GAAG,oBAAoB,MAAM,CAAC;AAE1C,QAAO;;;;;AC9ET,SAAS,aAAa,MAAoB;AAIxC,QAAO,GAHG,OAAO,KAAK,UAAU,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI,CAG1C,GAFF,OAAO,KAAK,SAAS,CAAC,CAAC,SAAS,GAAG,IAAI,CAEhC,GADP,KAAK,aAAa;;;AAK9B,SAAS,cAAc,OAAuB;AAC5C,KAAI,UAAU,EAAG,QAAO;AACxB,QAAO,MAAM,YAAY,MAAM;;AAGjC,MAAa,qBAAqD;CAChE,MAAM;CACN,QAAQ;CACR,QAAQ;EACN;GAAE,QAAQ;GAAoB,UAAU,QAAQ,aAAa,IAAI,UAAU;GAAE;EAC7E;GAAE,QAAQ;GAAoB,eAAe;GAAmB;EAChE;GAAE,QAAQ;GAAoB,UAAU,QAAQ,IAAI;GAAiB;EACrE;GAAE,QAAQ;GAAoB,eAAe;GAAI;EACjD;GAAE,QAAQ;GAAoB,UAAU,QAAQ,IAAI,aAAa,IAAI;GAAY;EACjF;GAAE,QAAQ;GAAoB,UAAU,QAAQ,IAAI,eAAe,IAAI,mBAAmB,IAAI;GAAW;EACzG;GAAE,QAAQ;GAAoB,UAAU,QAAQ,cAAc,IAAI,MAAM;GAAE;EAC1E;GAAE,QAAQ;GAAoB,UAAU,QAAQ,cAAc,IAAI,OAAO;GAAE;EAC3E;GAAE,QAAQ;GAAoB,eAAe;GAAI;EAClD;CACF;;;;AC3BD,SAAS,cAAc,MAAoB;AACzC,QAAO,KAAK,aAAa,CAAC,MAAM,IAAI,CAAC;;;AAIvC,SAAS,eAAe,OAAuB;AAC7C,QAAO,MAAM,YAAY,MAAM;;AAGjC,MAAa,oBAAoD;CAC/D,MAAM;CACN,QAAQ;CACR,QAAQ;EACN;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAS;EAC9D;GAAE,QAAQ;GAAqB,UAAU,QAAQ,cAAc,IAAI,UAAU;GAAE;EAC/E;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAa;EAClE;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAiB;EACtE;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAY;EACjE;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAO;EAC5D;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI,WAAW,QAAQ;GAAM;EAC9E;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAiB;EACtE;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAa;EAClE;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAW;EAChE;GAAE,QAAQ;GAAqB,UAAU,QAAQ,eAAe,IAAI,MAAM;GAAE;EAC5E;GAAE,QAAQ;GAAqB,UAAU,QAAQ,eAAe,IAAI,OAAO;GAAE;EAC7E;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAS;EAC9D;GAAE,QAAQ;GAAqB,UAAU,QAAQ,IAAI;GAAS;EAC9D;GAAE,QAAQ;GAAqB,UAAU,QAAQ,eAAe,IAAI,WAAW;GAAE;EACjF;GAAE,QAAQ;GAAqB,UAAU,QAAQ,eAAe,IAAI,YAAY;GAAE;EAClF;GAAE,QAAQ;GAAqB,UAAU,QAAQ,OAAO,IAAI,YAAY,EAAE;GAAE;EAC5E;GAAE,QAAQ;GAAqB,UAAU,QAAQ,OAAO,IAAI,UAAU;GAAE;EACzE;CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"universal-x33ZJODp.d.mts","names":[],"sources":["../src/exports/types.ts","../src/exports/csv-serializer.ts","../src/exports/field-map.ts","../src/exports/flatten-journal.ts","../src/exports/field-maps/quickbooks.ts","../src/exports/field-maps/universal.ts"],"mappings":";;AAeA;;;;;;;;;;;UAAiB,gBAAA;EACf,GAAA;EACA,eAAA;EACA,IAAA;EACA,MAAA;EACA,aAAA;AAAA;;;;UAMe,oBAAA;EACf,OAAA,EAAS,gBAAA;EACT,KAAA;EACA,IAAA,GAAO,IAAA;EACP,KAAA;EACA,MAAA;EACA,UAAA,GAAa,KAAA;IAAQ,OAAA;IAAkB,OAAA;EAAA;EAE3B;EAAA,CAAX,GAAA;AAAA;;;;;UAOc,qBAAA;EACf,GAAA;EACA,WAAA;EACA,eAAA;EACA,KAAA;EACA,IAAA,EAAM,IAAA;EACN,YAAA,EAAc,oBAAA;EACd,UAAA;EACA,WAAA;EACA,KAAA;EACA,QAAA;EACA,SAAA,GAAY,IAAA;EACZ,SAAA,GAAY,IAAA;EAAA,CACX,GAAA;AAAA;;;;;UASc,cAAA;EACf,OAAA;EACA,WAAA;EACA,eAAA;EACA,UAAA;EACA,SAAA,EAAW,IAAA;EACX,KAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EAEA,SAAA;EACA,WAAA;EACA,eAAA;EACA,SAAA;EACA,QAAA,EAAU,IAAA;EACV,KAAA;EACA,MAAA;EACA,OAAA;EACA,OAAA;EAEA,SAAA;EACA,SAAA;EARA;EAAA,CAWC,GAAA;AAAA;;;;;UASc,WAAA,QAAmB,cAAA;EAAA,SACzB,MAAA;EAAA,SACA,OAAA,GAAU,GAAA,EAAK,IAAA;AAAA;;AAF1B;;UAQiB,cAAA,QAAsB,cAAA;EAAA,SAC5B,IAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA,WAAiB,WAAA,CAAY,IAAA;AAAA;AAAA,UAKvB,UAAA;EACf,SAAA;EACA,cAAA;EACA,cAAA;AAAA;;;;iBCvGc,UAAA,CAAW,KAAA;;iBAQX,YAAA,CACd,IAAA,kCACA,OAAA,GAAS,UAAA;ADGX;AAAA,iBCOgB,QAAA,CACd,OAAA,qBACA,QAAA,kCACA,OAAA,GAAS,UAAA;;;;iBC1BK,UAAA,MAAA,CAAiB,QAAA,EAAU,cAAA,CAAe,IAAA;;iBAK1C,UAAA,MAAA,CAAiB,QAAA,EAAU,cAAA,CAAe,IAAA,GAAO,GAAA,EAAK,IAAA;;iBAKtD,cAAA,MAAA,CACd,QAAA,EAAU,cAAA,CAAe,IAAA,GACzB,IAAA,WAAe,IAAA;AFIjB;AAAA,iBEEgB,WAAA,MAAA,CACd,QAAA,EAAU,cAAA,CAAe,IAAA,GACzB,IAAA,WAAe,IAAA,IACf,OAAA,GAAU,UAAA;;;;iBCGI,mBAAA,CAAoB,KAAA,EAAO,qBAAA,GAAwB,cAAA;;iBAiDnD,qBAAA,CACd,OAAA,WAAkB,qBAAA,KACjB,cAAA;;;cC5DU,kBAAA,EAAoB,cAAA,CAAe,cAAA;;;cCJnC,iBAAA,EAAmB,cAAA,CAAe,cAAA"}
|