@classytic/ledger 0.1.5 → 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.
Files changed (66) hide show
  1. package/dist/{account.repository-1C2sZvB2.d.mts → account.repository-C7gwFLfM.d.mts} +3 -3
  2. package/dist/{account.repository-1C2sZvB2.d.mts.map → account.repository-C7gwFLfM.d.mts.map} +1 -1
  3. package/dist/{account.repository-Crf5DGO4.mjs → account.repository-kDKwDt0I.mjs} +4 -6
  4. package/dist/{account.repository-Crf5DGO4.mjs.map → account.repository-kDKwDt0I.mjs.map} +1 -1
  5. package/dist/{categories-BNJBd4ze.mjs → categories-CclX7Q94.mjs} +2 -2
  6. package/dist/{categories-BNJBd4ze.mjs.map → categories-CclX7Q94.mjs.map} +1 -1
  7. package/dist/constants/index.d.mts +1 -1
  8. package/dist/constants/index.mjs +4 -5
  9. package/dist/{core-Cx0baosR.d.mts → core-8Xfnpn6g.d.mts} +1 -1
  10. package/dist/{core-Cx0baosR.d.mts.map → core-8Xfnpn6g.d.mts.map} +1 -1
  11. package/dist/country/index.d.mts +2 -105
  12. package/dist/country/index.mjs +1 -1
  13. package/dist/country/index.mjs.map +1 -1
  14. package/dist/{currencies-Bkn3FNkC.d.mts → currencies-4WAbFRlw.d.mts} +2 -2
  15. package/dist/{currencies-Bkn3FNkC.d.mts.map → currencies-4WAbFRlw.d.mts.map} +1 -1
  16. package/dist/{currencies-BBk3NwXn.mjs → currencies-W8kQAkm0.mjs} +2 -2
  17. package/dist/{currencies-BBk3NwXn.mjs.map → currencies-W8kQAkm0.mjs.map} +1 -1
  18. package/dist/{engine-Cd73EOT6.d.mts → engine-BzBMpWuy.d.mts} +31 -5
  19. package/dist/engine-BzBMpWuy.d.mts.map +1 -0
  20. package/dist/{errors-CeqRahE-.mjs → errors-B7yC-Jfw.mjs} +2 -2
  21. package/dist/{errors-CeqRahE-.mjs.map → errors-B7yC-Jfw.mjs.map} +1 -1
  22. package/dist/exports/index.d.mts +2 -2
  23. package/dist/exports/index.mjs +2 -3
  24. package/dist/{universal-CMfrZ2hG.mjs → exports-I5Xkq-9_.mjs} +2 -7
  25. package/dist/exports-I5Xkq-9_.mjs.map +1 -0
  26. package/dist/{fiscal-close-DuXDgVvb.mjs → fiscal-close-L631E3De.mjs} +8 -15
  27. package/dist/fiscal-close-L631E3De.mjs.map +1 -0
  28. package/dist/{fiscal-close-CzUzpnMg.d.mts → fiscal-close-dNlzB37y.d.mts} +4 -4
  29. package/dist/{fiscal-close-CzUzpnMg.d.mts.map → fiscal-close-dNlzB37y.d.mts.map} +1 -1
  30. package/dist/{fiscal-period.schema-CbALaaKl.mjs → fiscal-period.schema-BQ5wsAq3.mjs} +52 -8
  31. package/dist/fiscal-period.schema-BQ5wsAq3.mjs.map +1 -0
  32. package/dist/{fiscal-period.schema-DI2scngu.d.mts → fiscal-period.schema-BRdKAjrr.d.mts} +2 -2
  33. package/dist/{fiscal-period.schema-DI2scngu.d.mts.map → fiscal-period.schema-BRdKAjrr.d.mts.map} +1 -1
  34. package/dist/{idempotency.plugin-BESs9YPD.d.mts → idempotency.plugin-CPxPt4vX.d.mts} +1 -1
  35. package/dist/{idempotency.plugin-BESs9YPD.d.mts.map → idempotency.plugin-CPxPt4vX.d.mts.map} +1 -1
  36. package/dist/{idempotency.plugin-C6r8RI8d.mjs → idempotency.plugin-v9NQ_ta-.mjs} +3 -6
  37. package/dist/{idempotency.plugin-C6r8RI8d.mjs.map → idempotency.plugin-v9NQ_ta-.mjs.map} +1 -1
  38. package/dist/{universal-x33ZJODp.d.mts → index-BPukb3L8.d.mts} +1 -1
  39. package/dist/index-BPukb3L8.d.mts.map +1 -0
  40. package/dist/index-ZnSiqHYV.d.mts +105 -0
  41. package/dist/index-ZnSiqHYV.d.mts.map +1 -0
  42. package/dist/index.d.mts +11 -11
  43. package/dist/index.mjs +11 -12
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/{journals-CI3Wb4EF.mjs → journals-oH-FK3g8.mjs} +2 -2
  46. package/dist/{journals-CI3Wb4EF.mjs.map → journals-oH-FK3g8.mjs.map} +1 -1
  47. package/dist/{logger-Cv6VVc4r.d.mts → logger-UbTdBb1x.d.mts} +1 -1
  48. package/dist/{logger-Cv6VVc4r.d.mts.map → logger-UbTdBb1x.d.mts.map} +1 -1
  49. package/dist/money.mjs +1 -1
  50. package/dist/plugins/index.d.mts +1 -1
  51. package/dist/plugins/index.mjs +2 -3
  52. package/dist/reports/index.d.mts +1 -1
  53. package/dist/reports/index.mjs +2 -3
  54. package/dist/repositories/index.d.mts +1 -1
  55. package/dist/repositories/index.mjs +2 -3
  56. package/dist/schemas/index.d.mts +1 -1
  57. package/dist/schemas/index.mjs +2 -3
  58. package/dist/{session-Dh0s6zG4.mjs → session-Ba8E3Ufa.mjs} +3 -6
  59. package/dist/{session-Dh0s6zG4.mjs.map → session-Ba8E3Ufa.mjs.map} +1 -1
  60. package/package.json +2 -2
  61. package/dist/country/index.d.mts.map +0 -1
  62. package/dist/engine-Cd73EOT6.d.mts.map +0 -1
  63. package/dist/fiscal-close-DuXDgVvb.mjs.map +0 -1
  64. package/dist/fiscal-period.schema-CbALaaKl.mjs.map +0 -1
  65. package/dist/universal-CMfrZ2hG.mjs.map +0 -1
  66. package/dist/universal-x33ZJODp.d.mts.map +0 -1
@@ -1,5 +1,5 @@
1
- import { CountryPack } from "./country/index.mjs";
2
- import { o as StrictnessConfig } from "./engine-Cd73EOT6.mjs";
1
+ import { t as CountryPack } from "./index-ZnSiqHYV.mjs";
2
+ import { s as StrictnessConfig } from "./engine-BzBMpWuy.mjs";
3
3
  import { Model } from "mongoose";
4
4
 
5
5
  //#region src/repositories/journal-entry.repository.d.ts
@@ -26,4 +26,4 @@ declare function wireJournalEntryMethods(repository: any, JournalEntryModel: Mod
26
26
  declare function wireAccountMethods(repository: any, AccountModel: Model<unknown>, country: CountryPack, orgField?: string): void;
27
27
  //#endregion
28
28
  export { wireJournalEntryMethods as n, wireAccountMethods as t };
29
- //# sourceMappingURL=account.repository-1C2sZvB2.d.mts.map
29
+ //# sourceMappingURL=account.repository-C7gwFLfM.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"account.repository-1C2sZvB2.d.mts","names":[],"sources":["../src/repositories/journal-entry.repository.ts","../src/repositories/account.repository.ts"],"mappings":";;;;;;;;;;;;;iBAgEgB,uBAAA,CAEd,UAAA,OACA,iBAAA,EAAmB,KAAA,WACnB,QAAA,WACA,UAAA,GAAa,gBAAA;;;;;;;;;;;;iBC3CC,kBAAA,CAEd,UAAA,OACA,YAAA,EAAc,KAAA,WACd,OAAA,EAAS,WAAA,EACT,QAAA"}
1
+ {"version":3,"file":"account.repository-C7gwFLfM.d.mts","names":[],"sources":["../src/repositories/journal-entry.repository.ts","../src/repositories/account.repository.ts"],"mappings":";;;;;;;;;;;;;iBAgEgB,uBAAA,CAEd,UAAA,OACA,iBAAA,EAAmB,KAAA,WACnB,QAAA,WACA,UAAA,GAAa,gBAAA;;;;;;;;;;;;iBC3CC,kBAAA,CAEd,UAAA,OACA,YAAA,EAAc,KAAA,WACd,OAAA,EAAS,WAAA,EACT,QAAA"}
@@ -1,6 +1,5 @@
1
- import { n as Errors } from "./errors-CeqRahE-.mjs";
2
- import { i as requireOrgScope, n as finalizeSession, t as acquireSession } from "./session-Dh0s6zG4.mjs";
3
-
1
+ import { n as Errors } from "./errors-B7yC-Jfw.mjs";
2
+ import { i as requireOrgScope, n as finalizeSession, t as acquireSession } from "./session-Ba8E3Ufa.mjs";
4
3
  //#region src/repositories/journal-entry.repository.ts
5
4
  /** Keys that are either handled explicitly or must not be copied */
6
5
  const ITEM_CORE_KEYS = new Set([
@@ -203,7 +202,6 @@ function wireJournalEntryMethods(repository, JournalEntryModel, orgField, strict
203
202
  }
204
203
  };
205
204
  }
206
-
207
205
  //#endregion
208
206
  //#region src/repositories/account.repository.ts
209
207
  /**
@@ -387,7 +385,7 @@ function wireAccountMethods(repository, AccountModel, country, orgField) {
387
385
  };
388
386
  };
389
387
  }
390
-
391
388
  //#endregion
392
389
  export { wireJournalEntryMethods as n, wireAccountMethods as t };
393
- //# sourceMappingURL=account.repository-Crf5DGO4.mjs.map
390
+
391
+ //# sourceMappingURL=account.repository-kDKwDt0I.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"account.repository-Crf5DGO4.mjs","names":[],"sources":["../src/repositories/journal-entry.repository.ts","../src/repositories/account.repository.ts"],"sourcesContent":["/**\r\n * Journal Entry Repository Factory\r\n *\r\n * Creates a mongokit Repository with post/reverse domain logic baked in.\r\n * Used by AccountingEngine.createJournalEntryRepository().\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport type { StrictnessConfig } from '../types/engine.js';\r\nimport { Errors } from '../utils/errors.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { acquireSession, finalizeSession } from '../utils/session.js';\r\n\r\ninterface PostOptions {\r\n session?: ClientSession | null;\r\n /** Actor performing this operation (required when strictness.requireActor is enabled) */\r\n actorId?: unknown;\r\n}\r\n\r\ninterface JournalItem {\r\n account?: unknown;\r\n debit?: number;\r\n credit?: number;\r\n}\r\n\r\ninterface JournalItemWithLabel extends JournalItem {\r\n label?: string;\r\n date?: Date;\r\n taxDetails?: unknown[];\r\n [key: string]: unknown;\r\n}\r\n\r\n/** Keys that are either handled explicitly or must not be copied */\r\nconst ITEM_CORE_KEYS = new Set(['account', 'debit', 'credit', 'label', 'date', 'taxDetails', '_id', 'id']);\r\n\r\ninterface JournalEntryDoc {\r\n _id: unknown;\r\n state: string;\r\n stateChangedAt?: Date;\r\n journalType?: string;\r\n referenceNumber?: string;\r\n label?: string;\r\n date?: Date;\r\n reversed?: boolean;\r\n reversedBy?: unknown;\r\n reversalOf?: unknown;\r\n journalItems: JournalItemWithLabel[];\r\n save(options?: { session?: ClientSession | null }): Promise<this>;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface ReverseOptions extends PostOptions {\r\n /** Date for the reversal entry (defaults to now) */\r\n reversalDate?: Date;\r\n}\r\n\r\n/**\r\n * Wire post/reverse onto an existing mongokit Repository.\r\n *\r\n * @param repository - A mongokit Repository instance (already created)\r\n * @param JournalEntryModel - The Mongoose model for journal entries\r\n * @param orgField - The multi-tenant field name (e.g. 'business')\r\n * @param strictness - Strictness rules (immutable, requireActor, requireApproval)\r\n */\r\nexport function wireJournalEntryMethods(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n repository: any,\r\n JournalEntryModel: Model<unknown>,\r\n orgField?: string,\r\n strictness?: StrictnessConfig,\r\n): void {\r\n /**\r\n * Post an entry (draft → posted).\r\n * Validates items, balance, and accounts before changing state.\r\n */\r\n repository.post = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for post operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .populate('journalItems.account')\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n\r\n // Idempotency: if already posted with same idempotency key, return as-is\r\n if (entry.idempotencyKey && entry.state === 'posted') {\r\n return entry;\r\n }\r\n\r\n if (entry.state !== 'draft') {\r\n throw Errors.validation('Only draft entries can be posted');\r\n }\r\n\r\n // Approval requirement — both approvedBy and approvedAt must be set\r\n if (strictness?.requireApproval) {\r\n if (!entry.approvedBy || !entry.approvedAt) {\r\n throw Errors.validation('Entry must be approved before posting. Both approvedBy and approvedAt are required.');\r\n }\r\n }\r\n\r\n // Must have >= 2 items\r\n if (!entry.journalItems || entry.journalItems.length < 2) {\r\n throw Errors.validation('Journal entry must have at least 2 items to post');\r\n }\r\n\r\n // Every item must have a valid account\r\n const missing = entry.journalItems.filter((i: JournalItem) => !i.account || i.account === '');\r\n if (missing.length > 0) {\r\n throw Errors.validation(`${missing.length} item(s) missing an account`);\r\n }\r\n\r\n // Verify all populated accounts belong to the same org (multi-tenant integrity)\r\n if (orgField && orgId != null) {\r\n const crossTenant = entry.journalItems.filter((i: JournalItem) => {\r\n const acct = i.account as Record<string, unknown> | null;\r\n if (!acct || typeof acct !== 'object') return false;\r\n return String(acct[orgField]) !== String(orgId);\r\n });\r\n if (crossTenant.length > 0) {\r\n throw Errors.validation(\r\n `${crossTenant.length} item(s) reference accounts from another organization`,\r\n );\r\n }\r\n }\r\n\r\n // Every item must have debit or credit > 0\r\n const zeroed = entry.journalItems.filter((i: JournalItem) => (i.debit || 0) === 0 && (i.credit || 0) === 0);\r\n if (zeroed.length > 0) {\r\n throw Errors.validation(`${zeroed.length} item(s) have both debit and credit as zero`);\r\n }\r\n\r\n // Each line must be debit OR credit, not both\r\n const bothSet = entry.journalItems.filter((i: JournalItem) => (i.debit || 0) > 0 && (i.credit || 0) > 0);\r\n if (bothSet.length > 0) {\r\n throw Errors.validation(\r\n `${bothSet.length} item(s) have both debit and credit set — each line must be debit OR credit, not both`,\r\n );\r\n }\r\n\r\n // Must be balanced — integer cents, exact comparison\r\n const totalDebit = entry.journalItems.reduce((s: number, i: JournalItem) => s + (i.debit || 0), 0);\r\n const totalCredit = entry.journalItems.reduce((s: number, i: JournalItem) => s + (i.credit || 0), 0);\r\n if (totalDebit !== totalCredit) {\r\n throw Errors.validation(\r\n `Entry is not balanced. Debit: ${totalDebit}, Credit: ${totalCredit}`,\r\n );\r\n }\r\n\r\n entry.state = 'posted';\r\n entry.stateChangedAt = new Date();\r\n if (options.actorId) {\r\n entry.postedBy = options.actorId;\r\n }\r\n await entry.save({ session: options.session });\r\n\r\n return entry;\r\n };\r\n\r\n /**\r\n * Unpost an entry (posted → draft).\r\n * Resets state to draft so the entry can be edited and re-posted.\r\n * Also clears the reversed flag if set, allowing full re-editing.\r\n */\r\n repository.unpost = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n if (strictness?.immutable) {\r\n throw Errors.immutable('Unpost is disabled in strict mode. Use reverse() to correct posted entries.');\r\n }\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for unpost operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n if (entry.state !== 'posted') {\r\n throw Errors.validation('Only posted entries can be unposted');\r\n }\r\n\r\n entry.state = 'draft';\r\n entry.stateChangedAt = new Date();\r\n // Clear reversal flags so the entry is fully editable as a draft\r\n if (entry.reversed) {\r\n entry.reversed = false;\r\n entry.reversedBy = undefined;\r\n }\r\n await entry.save({ session: options.session });\r\n\r\n return entry;\r\n };\r\n\r\n /**\r\n * Archive a draft entry (draft → archived).\r\n * Used to discard unneeded drafts without deleting them, preserving audit trail.\r\n * Only draft entries can be archived. Posted entries must be reversed instead.\r\n */\r\n repository.archive = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for archive operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n if (entry.state !== 'draft') {\r\n throw Errors.validation('Only draft entries can be archived');\r\n }\r\n\r\n entry.state = 'archived';\r\n entry.stateChangedAt = new Date();\r\n await entry.save({ session: options.session });\r\n\r\n return entry;\r\n };\r\n\r\n /**\r\n * Duplicate an entry as a new draft.\r\n * Copies journal items, journal type, and label. Assigns today's date.\r\n */\r\n repository.duplicate = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n\r\n const duplicateData: Record<string, unknown> = {\r\n journalType: entry.journalType,\r\n state: 'draft',\r\n date: new Date(),\r\n label: entry.label ? `Copy of ${entry.label}` : 'Duplicated entry',\r\n journalItems: entry.journalItems.map((item: JournalItemWithLabel) => {\r\n const accountId = typeof item.account === 'object' && item.account !== null\r\n ? (item.account as Record<string, unknown>)._id\r\n : item.account;\r\n\r\n // Preserve dimension/extra fields (departmentId, projectId, locationId, etc.)\r\n const extra: Record<string, unknown> = {};\r\n for (const key of Object.keys(item)) {\r\n if (!ITEM_CORE_KEYS.has(key)) extra[key] = item[key];\r\n }\r\n\r\n return {\r\n ...extra,\r\n account: accountId,\r\n debit: item.debit ?? 0,\r\n credit: item.credit ?? 0,\r\n label: item.label,\r\n date: new Date(),\r\n taxDetails: item.taxDetails ?? [],\r\n };\r\n }),\r\n };\r\n\r\n // Carry over org field\r\n if (orgField && entry[orgField] != null) {\r\n duplicateData[orgField] = entry[orgField];\r\n }\r\n\r\n const duplicated = await repository.create(duplicateData, options.session ? { session: options.session } : {});\r\n return duplicated;\r\n };\r\n\r\n /**\r\n * Reverse a posted entry by creating a mirror entry with flipped debits/credits.\r\n * Marks the original as reversed and links both entries bidirectionally.\r\n *\r\n * Atomic: creates an internal transaction by default. Pass an external session\r\n * to join a caller-managed transaction instead. On standalone MongoDB (no\r\n * replica set), falls back to non-atomic execution with a warning.\r\n *\r\n * Routes the reversal through repository.create() so all plugins (fiscal-lock,\r\n * double-entry) enforce policy on the reversal entry.\r\n */\r\n repository.reverse = async function (id: unknown, orgId?: unknown, options: ReverseOptions = {}) {\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for reverse operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const { session, ownSession } = await acquireSession(\r\n JournalEntryModel.db,\r\n options.session,\r\n );\r\n let success = false;\r\n\r\n try {\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .populate('journalItems.account')\r\n .session(session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n if (entry.state !== 'posted') {\r\n throw Errors.validation('Only posted entries can be reversed');\r\n }\r\n if (entry.reversed) {\r\n throw Errors.validation('Entry has already been reversed');\r\n }\r\n\r\n // Build reversal items — swap debit ↔ credit for each line\r\n const reversalItems = entry.journalItems.map((item: JournalItemWithLabel) => {\r\n const accountId = typeof item.account === 'object' && item.account !== null\r\n ? (item.account as Record<string, unknown>)._id\r\n : item.account;\r\n\r\n // Preserve dimension/extra fields (departmentId, projectId, locationId, etc.)\r\n const extra: Record<string, unknown> = {};\r\n for (const key of Object.keys(item)) {\r\n if (!ITEM_CORE_KEYS.has(key)) extra[key] = item[key];\r\n }\r\n\r\n return {\r\n ...extra,\r\n account: accountId,\r\n debit: item.credit ?? 0,\r\n credit: item.debit ?? 0,\r\n label: item.label ? `Reversal: ${item.label}` : undefined,\r\n date: item.date,\r\n taxDetails: item.taxDetails ?? [],\r\n };\r\n });\r\n\r\n const totalDebit = reversalItems.reduce((s: number, i: { debit: number }) => s + i.debit, 0);\r\n const totalCredit = reversalItems.reduce((s: number, i: { credit: number }) => s + i.credit, 0);\r\n\r\n // Build reversal entry data\r\n const reversalData: Record<string, unknown> = {\r\n journalType: entry.journalType ?? 'MISC',\r\n state: 'posted',\r\n date: options.reversalDate ?? new Date(),\r\n label: `Reversal of ${entry.referenceNumber ?? entry._id}`,\r\n journalItems: reversalItems,\r\n totalDebit,\r\n totalCredit,\r\n reversalOf: entry._id,\r\n stateChangedAt: new Date(),\r\n };\r\n\r\n // Carry over org field\r\n if (orgField && entry[orgField] != null) {\r\n reversalData[orgField] = entry[orgField];\r\n }\r\n\r\n // Stamp actor on reversal entry\r\n if (options.actorId) {\r\n reversalData.postedBy = options.actorId;\r\n }\r\n\r\n // Create reversal via repository so plugins (fiscal-lock, double-entry) run\r\n const reversalEntry = await repository.create(\r\n reversalData,\r\n session ? { session } : {},\r\n );\r\n\r\n // Mark original as reversed (bidirectional link)\r\n entry.reversed = true;\r\n entry.reversedBy = reversalEntry._id;\r\n if (options.actorId) {\r\n entry.reversedByUser = options.actorId;\r\n }\r\n await entry.save({ session });\r\n\r\n success = true;\r\n return { original: entry, reversal: reversalEntry };\r\n } finally {\r\n await finalizeSession(session, ownSession, success);\r\n }\r\n };\r\n}\r\n","/**\r\n * Account Repository Factory\r\n *\r\n * Creates a mongokit Repository with seedAccounts/bulkCreate and\r\n * posting-account validation baked in.\r\n * Used by AccountingEngine.createAccountRepository().\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport { Errors } from '../utils/errors.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\n\r\ninterface SeedOptions {\r\n session?: ClientSession | null;\r\n}\r\n\r\n/**\r\n * Wire seedAccounts, bulkCreate and posting-account validation\r\n * onto an existing mongokit Repository.\r\n *\r\n * @param repository - A mongokit Repository instance (already created)\r\n * @param AccountModel - The Mongoose model for accounts\r\n * @param country - The CountryPack for account type lookups\r\n * @param orgField - The multi-tenant field name (e.g. 'business')\r\n */\r\nexport function wireAccountMethods(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n repository: any,\r\n AccountModel: Model<unknown>,\r\n country: CountryPack,\r\n orgField?: string,\r\n): void {\r\n // Validate posting accounts on create\r\n repository.on('before:create', (ctx: Record<string, unknown>) => {\r\n const data = ctx.data as Record<string, unknown> | undefined;\r\n const code = data?.accountTypeCode as string | undefined;\r\n if (code && !country.isPostingAccount(code)) {\r\n throw Errors.validation(\r\n `Cannot create account with type \"${code}\" — it is a structural group or calculated total, not a posting account.`,\r\n );\r\n }\r\n });\r\n\r\n /**\r\n * Seed standard posting accounts for an organization.\r\n */\r\n repository.seedAccounts = async function (orgId: unknown, options: SeedOptions = {}) {\r\n requireOrgScope(orgField, orgId);\r\n const postingTypes = country.getPostingAccountTypes();\r\n const filter: Record<string, unknown> = {};\r\n if (orgField && orgId != null) filter[orgField] = orgId;\r\n\r\n const existing = await AccountModel.find(filter).select('accountNumber').lean() as unknown as Array<{ accountNumber: string }>;\r\n const existingNumbers = new Set(existing.map(a => a.accountNumber));\r\n\r\n const toCreate = postingTypes\r\n .filter(at => !existingNumbers.has(at.code))\r\n .map(at => {\r\n const doc: Record<string, unknown> = {\r\n accountTypeCode: at.code,\r\n accountNumber: at.code,\r\n name: at.name,\r\n };\r\n if (orgField && orgId != null) doc[orgField] = orgId;\r\n return doc;\r\n });\r\n\r\n if (toCreate.length === 0) return { created: 0, skipped: existingNumbers.size };\r\n\r\n try {\r\n // ordered: false ensures a dup-key on one doc doesn't abort the rest\r\n // (handles concurrent seed calls hitting the unique accountNumber index)\r\n const inserted = await AccountModel.insertMany(toCreate, {\r\n session: options.session ?? undefined,\r\n ordered: false,\r\n });\r\n return { created: inserted.length, skipped: existingNumbers.size };\r\n } catch (err: unknown) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const bulkError = err as any;\r\n if (bulkError.code === 11000 || bulkError.writeErrors) {\r\n // Partial success: some docs inserted, some hit dup-key from concurrent caller\r\n const insertedDocs = bulkError.insertedDocs ?? [];\r\n return {\r\n created: insertedDocs.length,\r\n skipped: existingNumbers.size + (toCreate.length - insertedDocs.length),\r\n };\r\n }\r\n throw err;\r\n }\r\n };\r\n\r\n /**\r\n * Bulk create accounts with validation and skip-if-exists logic.\r\n *\r\n * Uses a single batch query to check existing accounts (instead of N+1),\r\n * and ordered: false on insertMany to handle concurrent race conditions\r\n * gracefully (duplicate key errors on individual docs don't abort the batch).\r\n */\r\n repository.bulkCreate = async function (\r\n accounts: Array<{ accountTypeCode?: string; accountNumber?: string; name?: string; active?: boolean; isCashAccount?: boolean }>,\r\n orgId: unknown,\r\n ) {\r\n requireOrgScope(orgField, orgId);\r\n const results: {\r\n created: Array<Record<string, unknown>>;\r\n skipped: Array<Record<string, unknown>>;\r\n errors: Array<Record<string, unknown>>;\r\n } = { created: [], skipped: [], errors: [] };\r\n\r\n // Validate all accounts first (no DB calls)\r\n const validAccounts: Array<{ index: number; accountTypeCode: string; accountNumber: string; name: string; active: boolean; isCashAccount: boolean }> = [];\r\n\r\n for (let i = 0; i < accounts.length; i++) {\r\n const { accountTypeCode, accountNumber, name, active = true, isCashAccount = false } = accounts[i];\r\n\r\n if (!accountTypeCode) {\r\n results.errors.push({ index: i, reason: 'accountTypeCode is required' });\r\n continue;\r\n }\r\n\r\n const at = country.getAccountType(accountTypeCode);\r\n if (!at) {\r\n results.errors.push({ index: i, accountTypeCode, reason: 'Invalid account type code' });\r\n continue;\r\n }\r\n\r\n if (!country.isPostingAccount(accountTypeCode)) {\r\n results.errors.push({\r\n index: i,\r\n accountTypeCode,\r\n reason: `Not a posting account (${(at as unknown as Record<string, unknown>).isGroup ? 'group' : 'total'})`,\r\n });\r\n continue;\r\n }\r\n\r\n const resolvedNumber = accountNumber ?? accountTypeCode;\r\n const resolvedName = name ?? (at as unknown as Record<string, unknown>).name as string ?? accountTypeCode;\r\n validAccounts.push({ index: i, accountTypeCode, accountNumber: resolvedNumber, name: resolvedName, active: Boolean(active), isCashAccount: Boolean(isCashAccount) });\r\n }\r\n\r\n if (validAccounts.length === 0) {\r\n return {\r\n summary: { total: accounts.length, created: 0, skipped: results.skipped.length, errors: results.errors.length },\r\n ...results,\r\n };\r\n }\r\n\r\n // Single batch query to find all existing accounts by accountNumber for this org\r\n const numbersToCheck = validAccounts.map(a => a.accountNumber);\r\n const existsFilter: Record<string, unknown> = { accountNumber: { $in: numbersToCheck } };\r\n if (orgField && orgId != null) existsFilter[orgField] = orgId;\r\n\r\n const existingDocs = await AccountModel.find(existsFilter)\r\n .select('accountNumber')\r\n .lean() as Array<Record<string, unknown>>;\r\n const existingNumbers = new Set(existingDocs.map(d => d.accountNumber as string));\r\n\r\n // Partition into create vs skip\r\n const toCreate: Array<{ index: number; accountTypeCode: string; accountNumber: string; name: string; active: boolean; isCashAccount: boolean }> = [];\r\n for (const item of validAccounts) {\r\n if (existingNumbers.has(item.accountNumber)) {\r\n results.skipped.push({ index: item.index, accountTypeCode: item.accountTypeCode, reason: 'Already exists' });\r\n } else {\r\n toCreate.push(item);\r\n }\r\n }\r\n\r\n if (toCreate.length > 0) {\r\n const docs = toCreate.map(item => {\r\n const doc: Record<string, unknown> = {\r\n accountTypeCode: item.accountTypeCode,\r\n accountNumber: item.accountNumber,\r\n name: item.name,\r\n active: item.active,\r\n isCashAccount: item.isCashAccount,\r\n };\r\n if (orgField && orgId != null) doc[orgField] = orgId;\r\n return doc;\r\n });\r\n\r\n try {\r\n // ordered: false ensures a dup-key on one doc doesn't abort the rest\r\n const inserted = await AccountModel.insertMany(docs, { ordered: false });\r\n results.created = toCreate.map((item, idx) => ({\r\n accountTypeCode: item.accountTypeCode,\r\n active: item.active,\r\n isCashAccount: item.isCashAccount,\r\n _id: (inserted[idx] as unknown as Record<string, unknown>)._id,\r\n }));\r\n } catch (err: unknown) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const bulkError = err as any;\r\n if (bulkError.code === 11000 || bulkError.writeErrors) {\r\n // Partial success: some docs inserted, some hit dup-key from concurrent caller\r\n const insertedDocs = bulkError.insertedDocs ?? [];\r\n const insertedNumbers = new Set(\r\n insertedDocs.map((d: Record<string, unknown>) => d.accountNumber as string),\r\n );\r\n for (const item of toCreate) {\r\n if (insertedNumbers.has(item.accountNumber)) {\r\n const iDoc = insertedDocs.find(\r\n (d: Record<string, unknown>) => d.accountNumber === item.accountNumber,\r\n );\r\n results.created.push({\r\n accountTypeCode: item.accountTypeCode,\r\n active: item.active,\r\n isCashAccount: item.isCashAccount,\r\n _id: iDoc?._id,\r\n });\r\n } else {\r\n results.skipped.push({\r\n index: item.index,\r\n accountTypeCode: item.accountTypeCode,\r\n reason: 'Already exists (concurrent insert)',\r\n });\r\n }\r\n }\r\n } else {\r\n throw err;\r\n }\r\n }\r\n }\r\n\r\n return {\r\n summary: {\r\n total: accounts.length,\r\n created: results.created.length,\r\n skipped: results.skipped.length,\r\n errors: results.errors.length,\r\n },\r\n ...results,\r\n };\r\n };\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"account.repository-kDKwDt0I.mjs","names":[],"sources":["../src/repositories/journal-entry.repository.ts","../src/repositories/account.repository.ts"],"sourcesContent":["/**\r\n * Journal Entry Repository Factory\r\n *\r\n * Creates a mongokit Repository with post/reverse domain logic baked in.\r\n * Used by AccountingEngine.createJournalEntryRepository().\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport type { StrictnessConfig } from '../types/engine.js';\r\nimport { Errors } from '../utils/errors.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { acquireSession, finalizeSession } from '../utils/session.js';\r\n\r\ninterface PostOptions {\r\n session?: ClientSession | null;\r\n /** Actor performing this operation (required when strictness.requireActor is enabled) */\r\n actorId?: unknown;\r\n}\r\n\r\ninterface JournalItem {\r\n account?: unknown;\r\n debit?: number;\r\n credit?: number;\r\n}\r\n\r\ninterface JournalItemWithLabel extends JournalItem {\r\n label?: string;\r\n date?: Date;\r\n taxDetails?: unknown[];\r\n [key: string]: unknown;\r\n}\r\n\r\n/** Keys that are either handled explicitly or must not be copied */\r\nconst ITEM_CORE_KEYS = new Set(['account', 'debit', 'credit', 'label', 'date', 'taxDetails', '_id', 'id']);\r\n\r\ninterface JournalEntryDoc {\r\n _id: unknown;\r\n state: string;\r\n stateChangedAt?: Date;\r\n journalType?: string;\r\n referenceNumber?: string;\r\n label?: string;\r\n date?: Date;\r\n reversed?: boolean;\r\n reversedBy?: unknown;\r\n reversalOf?: unknown;\r\n journalItems: JournalItemWithLabel[];\r\n save(options?: { session?: ClientSession | null }): Promise<this>;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface ReverseOptions extends PostOptions {\r\n /** Date for the reversal entry (defaults to now) */\r\n reversalDate?: Date;\r\n}\r\n\r\n/**\r\n * Wire post/reverse onto an existing mongokit Repository.\r\n *\r\n * @param repository - A mongokit Repository instance (already created)\r\n * @param JournalEntryModel - The Mongoose model for journal entries\r\n * @param orgField - The multi-tenant field name (e.g. 'business')\r\n * @param strictness - Strictness rules (immutable, requireActor, requireApproval)\r\n */\r\nexport function wireJournalEntryMethods(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n repository: any,\r\n JournalEntryModel: Model<unknown>,\r\n orgField?: string,\r\n strictness?: StrictnessConfig,\r\n): void {\r\n /**\r\n * Post an entry (draft → posted).\r\n * Validates items, balance, and accounts before changing state.\r\n */\r\n repository.post = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for post operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .populate('journalItems.account')\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n\r\n // Idempotency: if already posted with same idempotency key, return as-is\r\n if (entry.idempotencyKey && entry.state === 'posted') {\r\n return entry;\r\n }\r\n\r\n if (entry.state !== 'draft') {\r\n throw Errors.validation('Only draft entries can be posted');\r\n }\r\n\r\n // Approval requirement — both approvedBy and approvedAt must be set\r\n if (strictness?.requireApproval) {\r\n if (!entry.approvedBy || !entry.approvedAt) {\r\n throw Errors.validation('Entry must be approved before posting. Both approvedBy and approvedAt are required.');\r\n }\r\n }\r\n\r\n // Must have >= 2 items\r\n if (!entry.journalItems || entry.journalItems.length < 2) {\r\n throw Errors.validation('Journal entry must have at least 2 items to post');\r\n }\r\n\r\n // Every item must have a valid account\r\n const missing = entry.journalItems.filter((i: JournalItem) => !i.account || i.account === '');\r\n if (missing.length > 0) {\r\n throw Errors.validation(`${missing.length} item(s) missing an account`);\r\n }\r\n\r\n // Verify all populated accounts belong to the same org (multi-tenant integrity)\r\n if (orgField && orgId != null) {\r\n const crossTenant = entry.journalItems.filter((i: JournalItem) => {\r\n const acct = i.account as Record<string, unknown> | null;\r\n if (!acct || typeof acct !== 'object') return false;\r\n return String(acct[orgField]) !== String(orgId);\r\n });\r\n if (crossTenant.length > 0) {\r\n throw Errors.validation(\r\n `${crossTenant.length} item(s) reference accounts from another organization`,\r\n );\r\n }\r\n }\r\n\r\n // Every item must have debit or credit > 0\r\n const zeroed = entry.journalItems.filter((i: JournalItem) => (i.debit || 0) === 0 && (i.credit || 0) === 0);\r\n if (zeroed.length > 0) {\r\n throw Errors.validation(`${zeroed.length} item(s) have both debit and credit as zero`);\r\n }\r\n\r\n // Each line must be debit OR credit, not both\r\n const bothSet = entry.journalItems.filter((i: JournalItem) => (i.debit || 0) > 0 && (i.credit || 0) > 0);\r\n if (bothSet.length > 0) {\r\n throw Errors.validation(\r\n `${bothSet.length} item(s) have both debit and credit set — each line must be debit OR credit, not both`,\r\n );\r\n }\r\n\r\n // Must be balanced — integer cents, exact comparison\r\n const totalDebit = entry.journalItems.reduce((s: number, i: JournalItem) => s + (i.debit || 0), 0);\r\n const totalCredit = entry.journalItems.reduce((s: number, i: JournalItem) => s + (i.credit || 0), 0);\r\n if (totalDebit !== totalCredit) {\r\n throw Errors.validation(\r\n `Entry is not balanced. Debit: ${totalDebit}, Credit: ${totalCredit}`,\r\n );\r\n }\r\n\r\n entry.state = 'posted';\r\n entry.stateChangedAt = new Date();\r\n if (options.actorId) {\r\n entry.postedBy = options.actorId;\r\n }\r\n await entry.save({ session: options.session });\r\n\r\n return entry;\r\n };\r\n\r\n /**\r\n * Unpost an entry (posted → draft).\r\n * Resets state to draft so the entry can be edited and re-posted.\r\n * Also clears the reversed flag if set, allowing full re-editing.\r\n */\r\n repository.unpost = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n if (strictness?.immutable) {\r\n throw Errors.immutable('Unpost is disabled in strict mode. Use reverse() to correct posted entries.');\r\n }\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for unpost operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n if (entry.state !== 'posted') {\r\n throw Errors.validation('Only posted entries can be unposted');\r\n }\r\n\r\n entry.state = 'draft';\r\n entry.stateChangedAt = new Date();\r\n // Clear reversal flags so the entry is fully editable as a draft\r\n if (entry.reversed) {\r\n entry.reversed = false;\r\n entry.reversedBy = undefined;\r\n }\r\n await entry.save({ session: options.session });\r\n\r\n return entry;\r\n };\r\n\r\n /**\r\n * Archive a draft entry (draft → archived).\r\n * Used to discard unneeded drafts without deleting them, preserving audit trail.\r\n * Only draft entries can be archived. Posted entries must be reversed instead.\r\n */\r\n repository.archive = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for archive operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n if (entry.state !== 'draft') {\r\n throw Errors.validation('Only draft entries can be archived');\r\n }\r\n\r\n entry.state = 'archived';\r\n entry.stateChangedAt = new Date();\r\n await entry.save({ session: options.session });\r\n\r\n return entry;\r\n };\r\n\r\n /**\r\n * Duplicate an entry as a new draft.\r\n * Copies journal items, journal type, and label. Assigns today's date.\r\n */\r\n repository.duplicate = async function (id: unknown, orgId?: unknown, options: PostOptions = {}) {\r\n requireOrgScope(orgField, orgId);\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .session(options.session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n\r\n const duplicateData: Record<string, unknown> = {\r\n journalType: entry.journalType,\r\n state: 'draft',\r\n date: new Date(),\r\n label: entry.label ? `Copy of ${entry.label}` : 'Duplicated entry',\r\n journalItems: entry.journalItems.map((item: JournalItemWithLabel) => {\r\n const accountId = typeof item.account === 'object' && item.account !== null\r\n ? (item.account as Record<string, unknown>)._id\r\n : item.account;\r\n\r\n // Preserve dimension/extra fields (departmentId, projectId, locationId, etc.)\r\n const extra: Record<string, unknown> = {};\r\n for (const key of Object.keys(item)) {\r\n if (!ITEM_CORE_KEYS.has(key)) extra[key] = item[key];\r\n }\r\n\r\n return {\r\n ...extra,\r\n account: accountId,\r\n debit: item.debit ?? 0,\r\n credit: item.credit ?? 0,\r\n label: item.label,\r\n date: new Date(),\r\n taxDetails: item.taxDetails ?? [],\r\n };\r\n }),\r\n };\r\n\r\n // Carry over org field\r\n if (orgField && entry[orgField] != null) {\r\n duplicateData[orgField] = entry[orgField];\r\n }\r\n\r\n const duplicated = await repository.create(duplicateData, options.session ? { session: options.session } : {});\r\n return duplicated;\r\n };\r\n\r\n /**\r\n * Reverse a posted entry by creating a mirror entry with flipped debits/credits.\r\n * Marks the original as reversed and links both entries bidirectionally.\r\n *\r\n * Atomic: creates an internal transaction by default. Pass an external session\r\n * to join a caller-managed transaction instead. On standalone MongoDB (no\r\n * replica set), falls back to non-atomic execution with a warning.\r\n *\r\n * Routes the reversal through repository.create() so all plugins (fiscal-lock,\r\n * double-entry) enforce policy on the reversal entry.\r\n */\r\n repository.reverse = async function (id: unknown, orgId?: unknown, options: ReverseOptions = {}) {\r\n if (strictness?.requireActor && !options.actorId) {\r\n throw Errors.validation('actorId is required for reverse operations.');\r\n }\r\n requireOrgScope(orgField, orgId);\r\n const { session, ownSession } = await acquireSession(\r\n JournalEntryModel.db,\r\n options.session,\r\n );\r\n let success = false;\r\n\r\n try {\r\n const query: Record<string, unknown> = { _id: id };\r\n if (orgField && orgId != null) query[orgField] = orgId;\r\n\r\n const entry = (await JournalEntryModel.findOne(query)\r\n .populate('journalItems.account')\r\n .session(session || null)) as JournalEntryDoc | null;\r\n\r\n if (!entry) {\r\n throw Errors.notFound('Entry not found');\r\n }\r\n if (entry.state !== 'posted') {\r\n throw Errors.validation('Only posted entries can be reversed');\r\n }\r\n if (entry.reversed) {\r\n throw Errors.validation('Entry has already been reversed');\r\n }\r\n\r\n // Build reversal items — swap debit ↔ credit for each line\r\n const reversalItems = entry.journalItems.map((item: JournalItemWithLabel) => {\r\n const accountId = typeof item.account === 'object' && item.account !== null\r\n ? (item.account as Record<string, unknown>)._id\r\n : item.account;\r\n\r\n // Preserve dimension/extra fields (departmentId, projectId, locationId, etc.)\r\n const extra: Record<string, unknown> = {};\r\n for (const key of Object.keys(item)) {\r\n if (!ITEM_CORE_KEYS.has(key)) extra[key] = item[key];\r\n }\r\n\r\n return {\r\n ...extra,\r\n account: accountId,\r\n debit: item.credit ?? 0,\r\n credit: item.debit ?? 0,\r\n label: item.label ? `Reversal: ${item.label}` : undefined,\r\n date: item.date,\r\n taxDetails: item.taxDetails ?? [],\r\n };\r\n });\r\n\r\n const totalDebit = reversalItems.reduce((s: number, i: { debit: number }) => s + i.debit, 0);\r\n const totalCredit = reversalItems.reduce((s: number, i: { credit: number }) => s + i.credit, 0);\r\n\r\n // Build reversal entry data\r\n const reversalData: Record<string, unknown> = {\r\n journalType: entry.journalType ?? 'MISC',\r\n state: 'posted',\r\n date: options.reversalDate ?? new Date(),\r\n label: `Reversal of ${entry.referenceNumber ?? entry._id}`,\r\n journalItems: reversalItems,\r\n totalDebit,\r\n totalCredit,\r\n reversalOf: entry._id,\r\n stateChangedAt: new Date(),\r\n };\r\n\r\n // Carry over org field\r\n if (orgField && entry[orgField] != null) {\r\n reversalData[orgField] = entry[orgField];\r\n }\r\n\r\n // Stamp actor on reversal entry\r\n if (options.actorId) {\r\n reversalData.postedBy = options.actorId;\r\n }\r\n\r\n // Create reversal via repository so plugins (fiscal-lock, double-entry) run\r\n const reversalEntry = await repository.create(\r\n reversalData,\r\n session ? { session } : {},\r\n );\r\n\r\n // Mark original as reversed (bidirectional link)\r\n entry.reversed = true;\r\n entry.reversedBy = reversalEntry._id;\r\n if (options.actorId) {\r\n entry.reversedByUser = options.actorId;\r\n }\r\n await entry.save({ session });\r\n\r\n success = true;\r\n return { original: entry, reversal: reversalEntry };\r\n } finally {\r\n await finalizeSession(session, ownSession, success);\r\n }\r\n };\r\n}\r\n","/**\r\n * Account Repository Factory\r\n *\r\n * Creates a mongokit Repository with seedAccounts/bulkCreate and\r\n * posting-account validation baked in.\r\n * Used by AccountingEngine.createAccountRepository().\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport { Errors } from '../utils/errors.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\n\r\ninterface SeedOptions {\r\n session?: ClientSession | null;\r\n}\r\n\r\n/**\r\n * Wire seedAccounts, bulkCreate and posting-account validation\r\n * onto an existing mongokit Repository.\r\n *\r\n * @param repository - A mongokit Repository instance (already created)\r\n * @param AccountModel - The Mongoose model for accounts\r\n * @param country - The CountryPack for account type lookups\r\n * @param orgField - The multi-tenant field name (e.g. 'business')\r\n */\r\nexport function wireAccountMethods(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n repository: any,\r\n AccountModel: Model<unknown>,\r\n country: CountryPack,\r\n orgField?: string,\r\n): void {\r\n // Validate posting accounts on create\r\n repository.on('before:create', (ctx: Record<string, unknown>) => {\r\n const data = ctx.data as Record<string, unknown> | undefined;\r\n const code = data?.accountTypeCode as string | undefined;\r\n if (code && !country.isPostingAccount(code)) {\r\n throw Errors.validation(\r\n `Cannot create account with type \"${code}\" — it is a structural group or calculated total, not a posting account.`,\r\n );\r\n }\r\n });\r\n\r\n /**\r\n * Seed standard posting accounts for an organization.\r\n */\r\n repository.seedAccounts = async function (orgId: unknown, options: SeedOptions = {}) {\r\n requireOrgScope(orgField, orgId);\r\n const postingTypes = country.getPostingAccountTypes();\r\n const filter: Record<string, unknown> = {};\r\n if (orgField && orgId != null) filter[orgField] = orgId;\r\n\r\n const existing = await AccountModel.find(filter).select('accountNumber').lean() as unknown as Array<{ accountNumber: string }>;\r\n const existingNumbers = new Set(existing.map(a => a.accountNumber));\r\n\r\n const toCreate = postingTypes\r\n .filter(at => !existingNumbers.has(at.code))\r\n .map(at => {\r\n const doc: Record<string, unknown> = {\r\n accountTypeCode: at.code,\r\n accountNumber: at.code,\r\n name: at.name,\r\n };\r\n if (orgField && orgId != null) doc[orgField] = orgId;\r\n return doc;\r\n });\r\n\r\n if (toCreate.length === 0) return { created: 0, skipped: existingNumbers.size };\r\n\r\n try {\r\n // ordered: false ensures a dup-key on one doc doesn't abort the rest\r\n // (handles concurrent seed calls hitting the unique accountNumber index)\r\n const inserted = await AccountModel.insertMany(toCreate, {\r\n session: options.session ?? undefined,\r\n ordered: false,\r\n });\r\n return { created: inserted.length, skipped: existingNumbers.size };\r\n } catch (err: unknown) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const bulkError = err as any;\r\n if (bulkError.code === 11000 || bulkError.writeErrors) {\r\n // Partial success: some docs inserted, some hit dup-key from concurrent caller\r\n const insertedDocs = bulkError.insertedDocs ?? [];\r\n return {\r\n created: insertedDocs.length,\r\n skipped: existingNumbers.size + (toCreate.length - insertedDocs.length),\r\n };\r\n }\r\n throw err;\r\n }\r\n };\r\n\r\n /**\r\n * Bulk create accounts with validation and skip-if-exists logic.\r\n *\r\n * Uses a single batch query to check existing accounts (instead of N+1),\r\n * and ordered: false on insertMany to handle concurrent race conditions\r\n * gracefully (duplicate key errors on individual docs don't abort the batch).\r\n */\r\n repository.bulkCreate = async function (\r\n accounts: Array<{ accountTypeCode?: string; accountNumber?: string; name?: string; active?: boolean; isCashAccount?: boolean }>,\r\n orgId: unknown,\r\n ) {\r\n requireOrgScope(orgField, orgId);\r\n const results: {\r\n created: Array<Record<string, unknown>>;\r\n skipped: Array<Record<string, unknown>>;\r\n errors: Array<Record<string, unknown>>;\r\n } = { created: [], skipped: [], errors: [] };\r\n\r\n // Validate all accounts first (no DB calls)\r\n const validAccounts: Array<{ index: number; accountTypeCode: string; accountNumber: string; name: string; active: boolean; isCashAccount: boolean }> = [];\r\n\r\n for (let i = 0; i < accounts.length; i++) {\r\n const { accountTypeCode, accountNumber, name, active = true, isCashAccount = false } = accounts[i];\r\n\r\n if (!accountTypeCode) {\r\n results.errors.push({ index: i, reason: 'accountTypeCode is required' });\r\n continue;\r\n }\r\n\r\n const at = country.getAccountType(accountTypeCode);\r\n if (!at) {\r\n results.errors.push({ index: i, accountTypeCode, reason: 'Invalid account type code' });\r\n continue;\r\n }\r\n\r\n if (!country.isPostingAccount(accountTypeCode)) {\r\n results.errors.push({\r\n index: i,\r\n accountTypeCode,\r\n reason: `Not a posting account (${(at as unknown as Record<string, unknown>).isGroup ? 'group' : 'total'})`,\r\n });\r\n continue;\r\n }\r\n\r\n const resolvedNumber = accountNumber ?? accountTypeCode;\r\n const resolvedName = name ?? (at as unknown as Record<string, unknown>).name as string ?? accountTypeCode;\r\n validAccounts.push({ index: i, accountTypeCode, accountNumber: resolvedNumber, name: resolvedName, active: Boolean(active), isCashAccount: Boolean(isCashAccount) });\r\n }\r\n\r\n if (validAccounts.length === 0) {\r\n return {\r\n summary: { total: accounts.length, created: 0, skipped: results.skipped.length, errors: results.errors.length },\r\n ...results,\r\n };\r\n }\r\n\r\n // Single batch query to find all existing accounts by accountNumber for this org\r\n const numbersToCheck = validAccounts.map(a => a.accountNumber);\r\n const existsFilter: Record<string, unknown> = { accountNumber: { $in: numbersToCheck } };\r\n if (orgField && orgId != null) existsFilter[orgField] = orgId;\r\n\r\n const existingDocs = await AccountModel.find(existsFilter)\r\n .select('accountNumber')\r\n .lean() as Array<Record<string, unknown>>;\r\n const existingNumbers = new Set(existingDocs.map(d => d.accountNumber as string));\r\n\r\n // Partition into create vs skip\r\n const toCreate: Array<{ index: number; accountTypeCode: string; accountNumber: string; name: string; active: boolean; isCashAccount: boolean }> = [];\r\n for (const item of validAccounts) {\r\n if (existingNumbers.has(item.accountNumber)) {\r\n results.skipped.push({ index: item.index, accountTypeCode: item.accountTypeCode, reason: 'Already exists' });\r\n } else {\r\n toCreate.push(item);\r\n }\r\n }\r\n\r\n if (toCreate.length > 0) {\r\n const docs = toCreate.map(item => {\r\n const doc: Record<string, unknown> = {\r\n accountTypeCode: item.accountTypeCode,\r\n accountNumber: item.accountNumber,\r\n name: item.name,\r\n active: item.active,\r\n isCashAccount: item.isCashAccount,\r\n };\r\n if (orgField && orgId != null) doc[orgField] = orgId;\r\n return doc;\r\n });\r\n\r\n try {\r\n // ordered: false ensures a dup-key on one doc doesn't abort the rest\r\n const inserted = await AccountModel.insertMany(docs, { ordered: false });\r\n results.created = toCreate.map((item, idx) => ({\r\n accountTypeCode: item.accountTypeCode,\r\n active: item.active,\r\n isCashAccount: item.isCashAccount,\r\n _id: (inserted[idx] as unknown as Record<string, unknown>)._id,\r\n }));\r\n } catch (err: unknown) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const bulkError = err as any;\r\n if (bulkError.code === 11000 || bulkError.writeErrors) {\r\n // Partial success: some docs inserted, some hit dup-key from concurrent caller\r\n const insertedDocs = bulkError.insertedDocs ?? [];\r\n const insertedNumbers = new Set(\r\n insertedDocs.map((d: Record<string, unknown>) => d.accountNumber as string),\r\n );\r\n for (const item of toCreate) {\r\n if (insertedNumbers.has(item.accountNumber)) {\r\n const iDoc = insertedDocs.find(\r\n (d: Record<string, unknown>) => d.accountNumber === item.accountNumber,\r\n );\r\n results.created.push({\r\n accountTypeCode: item.accountTypeCode,\r\n active: item.active,\r\n isCashAccount: item.isCashAccount,\r\n _id: iDoc?._id,\r\n });\r\n } else {\r\n results.skipped.push({\r\n index: item.index,\r\n accountTypeCode: item.accountTypeCode,\r\n reason: 'Already exists (concurrent insert)',\r\n });\r\n }\r\n }\r\n } else {\r\n throw err;\r\n }\r\n }\r\n }\r\n\r\n return {\r\n summary: {\r\n total: accounts.length,\r\n created: results.created.length,\r\n skipped: results.skipped.length,\r\n errors: results.errors.length,\r\n },\r\n ...results,\r\n };\r\n };\r\n}\r\n"],"mappings":"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"}
@@ -64,7 +64,7 @@ function extractStatementType(key) {
64
64
  const parts = key.split("-");
65
65
  return parts.length === 2 ? parts[0] : null;
66
66
  }
67
-
68
67
  //#endregion
69
68
  export { extractStatementType as a, getNormalBalance as c, isValidCategory as d, extractMainType as i, isBalanceSheet as l, CATEGORY_KEYS as n, getCategoryMainType as o, categoryKey as r, getCategoryStatementType as s, CATEGORIES as t, isIncomeStatement as u };
70
- //# sourceMappingURL=categories-BNJBd4ze.mjs.map
69
+
70
+ //# sourceMappingURL=categories-CclX7Q94.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"categories-BNJBd4ze.mjs","names":[],"sources":["../src/constants/categories.ts"],"sourcesContent":["/**\r\n * Account Categories — Single source of truth for account classification.\r\n */\r\n\r\nimport type { Category, CategoryKey, MainType, StatementType } from '../types/core.js';\r\n\r\n/** All valid categories */\r\nexport const CATEGORIES: Readonly<Record<CategoryKey, Category>> = Object.freeze({\r\n 'Balance Sheet-Asset': { name: 'Balance Sheet', mainType: 'Asset', statementType: 'Balance Sheet' },\r\n 'Balance Sheet-Liability': { name: 'Balance Sheet', mainType: 'Liability', statementType: 'Balance Sheet' },\r\n 'Balance Sheet-Equity': { name: 'Balance Sheet', mainType: 'Equity', statementType: 'Balance Sheet' },\r\n 'Income Statement-Income': { name: 'Income Statement', mainType: 'Income', statementType: 'Income Statement' },\r\n 'Income Statement-Expense': { name: 'Income Statement', mainType: 'Expense', statementType: 'Income Statement' },\r\n});\r\n\r\nexport const CATEGORY_KEYS = Object.keys(CATEGORIES) as CategoryKey[];\r\n\r\nexport function isValidCategory(key: string): key is CategoryKey {\r\n return key in CATEGORIES;\r\n}\r\n\r\nexport function getCategoryMainType(key: CategoryKey): MainType {\r\n return CATEGORIES[key].mainType;\r\n}\r\n\r\nexport function getCategoryStatementType(key: CategoryKey): StatementType {\r\n return CATEGORIES[key].statementType;\r\n}\r\n\r\nexport function isBalanceSheet(key: CategoryKey): boolean {\r\n return CATEGORIES[key].statementType === 'Balance Sheet';\r\n}\r\n\r\nexport function isIncomeStatement(key: CategoryKey): boolean {\r\n return CATEGORIES[key].statementType === 'Income Statement';\r\n}\r\n\r\n/**\r\n * Get the normal balance for a main type.\r\n * Assets & Expenses → debit. Liabilities, Equity & Income → credit.\r\n */\r\nexport function getNormalBalance(mainType: MainType): 'debit' | 'credit' {\r\n return mainType === 'Asset' || mainType === 'Expense' ? 'debit' : 'credit';\r\n}\r\n\r\n/** Build a category key from parts */\r\nexport function categoryKey(statement: StatementType, mainType: MainType): CategoryKey {\r\n return `${statement}-${mainType}` as CategoryKey;\r\n}\r\n\r\n/** Extract main type from a category key string */\r\nexport function extractMainType(key: string): MainType | null {\r\n const parts = key.split('-');\r\n return (parts.length === 2 ? parts[1] : null) as MainType | null;\r\n}\r\n\r\n/** Extract statement type from a category key string */\r\nexport function extractStatementType(key: string): StatementType | null {\r\n const parts = key.split('-');\r\n return (parts.length === 2 ? parts[0] : null) as StatementType | null;\r\n}\r\n"],"mappings":";;AAOA,MAAa,aAAsD,OAAO,OAAO;CAC/E,uBAAuB;EAAE,MAAM;EAAiB,UAAU;EAAS,eAAe;EAAiB;CACnG,2BAA2B;EAAE,MAAM;EAAiB,UAAU;EAAa,eAAe;EAAiB;CAC3G,wBAAwB;EAAE,MAAM;EAAiB,UAAU;EAAU,eAAe;EAAiB;CACrG,2BAA2B;EAAE,MAAM;EAAoB,UAAU;EAAU,eAAe;EAAoB;CAC9G,4BAA4B;EAAE,MAAM;EAAoB,UAAU;EAAW,eAAe;EAAoB;CACjH,CAAC;AAEF,MAAa,gBAAgB,OAAO,KAAK,WAAW;AAEpD,SAAgB,gBAAgB,KAAiC;AAC/D,QAAO,OAAO;;AAGhB,SAAgB,oBAAoB,KAA4B;AAC9D,QAAO,WAAW,KAAK;;AAGzB,SAAgB,yBAAyB,KAAiC;AACxE,QAAO,WAAW,KAAK;;AAGzB,SAAgB,eAAe,KAA2B;AACxD,QAAO,WAAW,KAAK,kBAAkB;;AAG3C,SAAgB,kBAAkB,KAA2B;AAC3D,QAAO,WAAW,KAAK,kBAAkB;;;;;;AAO3C,SAAgB,iBAAiB,UAAwC;AACvE,QAAO,aAAa,WAAW,aAAa,YAAY,UAAU;;;AAIpE,SAAgB,YAAY,WAA0B,UAAiC;AACrF,QAAO,GAAG,UAAU,GAAG;;;AAIzB,SAAgB,gBAAgB,KAA8B;CAC5D,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAQ,MAAM,WAAW,IAAI,MAAM,KAAK;;;AAI1C,SAAgB,qBAAqB,KAAmC;CACtE,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAQ,MAAM,WAAW,IAAI,MAAM,KAAK"}
1
+ {"version":3,"file":"categories-CclX7Q94.mjs","names":[],"sources":["../src/constants/categories.ts"],"sourcesContent":["/**\r\n * Account Categories — Single source of truth for account classification.\r\n */\r\n\r\nimport type { Category, CategoryKey, MainType, StatementType } from '../types/core.js';\r\n\r\n/** All valid categories */\r\nexport const CATEGORIES: Readonly<Record<CategoryKey, Category>> = Object.freeze({\r\n 'Balance Sheet-Asset': { name: 'Balance Sheet', mainType: 'Asset', statementType: 'Balance Sheet' },\r\n 'Balance Sheet-Liability': { name: 'Balance Sheet', mainType: 'Liability', statementType: 'Balance Sheet' },\r\n 'Balance Sheet-Equity': { name: 'Balance Sheet', mainType: 'Equity', statementType: 'Balance Sheet' },\r\n 'Income Statement-Income': { name: 'Income Statement', mainType: 'Income', statementType: 'Income Statement' },\r\n 'Income Statement-Expense': { name: 'Income Statement', mainType: 'Expense', statementType: 'Income Statement' },\r\n});\r\n\r\nexport const CATEGORY_KEYS = Object.keys(CATEGORIES) as CategoryKey[];\r\n\r\nexport function isValidCategory(key: string): key is CategoryKey {\r\n return key in CATEGORIES;\r\n}\r\n\r\nexport function getCategoryMainType(key: CategoryKey): MainType {\r\n return CATEGORIES[key].mainType;\r\n}\r\n\r\nexport function getCategoryStatementType(key: CategoryKey): StatementType {\r\n return CATEGORIES[key].statementType;\r\n}\r\n\r\nexport function isBalanceSheet(key: CategoryKey): boolean {\r\n return CATEGORIES[key].statementType === 'Balance Sheet';\r\n}\r\n\r\nexport function isIncomeStatement(key: CategoryKey): boolean {\r\n return CATEGORIES[key].statementType === 'Income Statement';\r\n}\r\n\r\n/**\r\n * Get the normal balance for a main type.\r\n * Assets & Expenses → debit. Liabilities, Equity & Income → credit.\r\n */\r\nexport function getNormalBalance(mainType: MainType): 'debit' | 'credit' {\r\n return mainType === 'Asset' || mainType === 'Expense' ? 'debit' : 'credit';\r\n}\r\n\r\n/** Build a category key from parts */\r\nexport function categoryKey(statement: StatementType, mainType: MainType): CategoryKey {\r\n return `${statement}-${mainType}` as CategoryKey;\r\n}\r\n\r\n/** Extract main type from a category key string */\r\nexport function extractMainType(key: string): MainType | null {\r\n const parts = key.split('-');\r\n return (parts.length === 2 ? parts[1] : null) as MainType | null;\r\n}\r\n\r\n/** Extract statement type from a category key string */\r\nexport function extractStatementType(key: string): StatementType | null {\r\n const parts = key.split('-');\r\n return (parts.length === 2 ? parts[0] : null) as StatementType | null;\r\n}\r\n"],"mappings":";;AAOA,MAAa,aAAsD,OAAO,OAAO;CAC/E,uBAAuB;EAAE,MAAM;EAAiB,UAAU;EAAS,eAAe;EAAiB;CACnG,2BAA2B;EAAE,MAAM;EAAiB,UAAU;EAAa,eAAe;EAAiB;CAC3G,wBAAwB;EAAE,MAAM;EAAiB,UAAU;EAAU,eAAe;EAAiB;CACrG,2BAA2B;EAAE,MAAM;EAAoB,UAAU;EAAU,eAAe;EAAoB;CAC9G,4BAA4B;EAAE,MAAM;EAAoB,UAAU;EAAW,eAAe;EAAoB;CACjH,CAAC;AAEF,MAAa,gBAAgB,OAAO,KAAK,WAAW;AAEpD,SAAgB,gBAAgB,KAAiC;AAC/D,QAAO,OAAO;;AAGhB,SAAgB,oBAAoB,KAA4B;AAC9D,QAAO,WAAW,KAAK;;AAGzB,SAAgB,yBAAyB,KAAiC;AACxE,QAAO,WAAW,KAAK;;AAGzB,SAAgB,eAAe,KAA2B;AACxD,QAAO,WAAW,KAAK,kBAAkB;;AAG3C,SAAgB,kBAAkB,KAA2B;AAC3D,QAAO,WAAW,KAAK,kBAAkB;;;;;;AAO3C,SAAgB,iBAAiB,UAAwC;AACvE,QAAO,aAAa,WAAW,aAAa,YAAY,UAAU;;;AAIpE,SAAgB,YAAY,WAA0B,UAAiC;AACrF,QAAO,GAAG,UAAU,GAAG;;;AAIzB,SAAgB,gBAAgB,KAA8B;CAC5D,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAQ,MAAM,WAAW,IAAI,MAAM,KAAK;;;AAI1C,SAAgB,qBAAqB,KAAmC;CACtE,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAQ,MAAM,WAAW,IAAI,MAAM,KAAK"}
@@ -1,2 +1,2 @@
1
- import { _ as getNormalBalance, a as JOURNAL_CODES, b as isValidCategory, c as getJournalTypeCodes, d as CATEGORY_KEYS, f as categoryKey, g as getCategoryStatementType, h as getCategoryMainType, i as isValidCurrency, l as isValidJournalType, m as extractStatementType, n as getCurrency, o as JOURNAL_TYPES, p as extractMainType, r as getMinorUnit, s as getJournalType, t as CURRENCIES, u as CATEGORIES, v as isBalanceSheet, y as isIncomeStatement } from "../currencies-Bkn3FNkC.mjs";
1
+ import { _ as getNormalBalance, a as JOURNAL_CODES, b as isValidCategory, c as getJournalTypeCodes, d as CATEGORY_KEYS, f as categoryKey, g as getCategoryStatementType, h as getCategoryMainType, i as isValidCurrency, l as isValidJournalType, m as extractStatementType, n as getCurrency, o as JOURNAL_TYPES, p as extractMainType, r as getMinorUnit, s as getJournalType, t as CURRENCIES, u as CATEGORIES, v as isBalanceSheet, y as isIncomeStatement } from "../currencies-4WAbFRlw.mjs";
2
2
  export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType };
@@ -1,5 +1,4 @@
1
- import { a as isValidJournalType, i as getJournalTypeCodes, n as JOURNAL_TYPES, r as getJournalType, t as JOURNAL_CODES } from "../journals-CI3Wb4EF.mjs";
2
- import { a as extractStatementType, c as getNormalBalance, d as isValidCategory, i as extractMainType, l as isBalanceSheet, n as CATEGORY_KEYS, o as getCategoryMainType, r as categoryKey, s as getCategoryStatementType, t as CATEGORIES, u as isIncomeStatement } from "../categories-BNJBd4ze.mjs";
3
- import { i as isValidCurrency, n as getCurrency, r as getMinorUnit, t as CURRENCIES } from "../currencies-BBk3NwXn.mjs";
4
-
5
- export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType };
1
+ import { a as isValidJournalType, i as getJournalTypeCodes, n as JOURNAL_TYPES, r as getJournalType, t as JOURNAL_CODES } from "../journals-oH-FK3g8.mjs";
2
+ import { a as extractStatementType, c as getNormalBalance, d as isValidCategory, i as extractMainType, l as isBalanceSheet, n as CATEGORY_KEYS, o as getCategoryMainType, r as categoryKey, s as getCategoryStatementType, t as CATEGORIES, u as isIncomeStatement } from "../categories-CclX7Q94.mjs";
3
+ import { i as isValidCurrency, n as getCurrency, r as getMinorUnit, t as CURRENCIES } from "../currencies-W8kQAkm0.mjs";
4
+ export { CATEGORIES, CATEGORY_KEYS, CURRENCIES, JOURNAL_CODES, JOURNAL_TYPES, categoryKey, extractMainType, extractStatementType, getCategoryMainType, getCategoryStatementType, getCurrency, getJournalType, getJournalTypeCodes, getMinorUnit, getNormalBalance, isBalanceSheet, isIncomeStatement, isValidCategory, isValidCurrency, isValidJournalType };
@@ -101,4 +101,4 @@ interface DateRange {
101
101
  }
102
102
  //#endregion
103
103
  export { TaxMetadata as _, Cents as a, DateRange as c, JournalType as d, MainType as f, TaxDetail as g, StatementType as h, CategoryKey as i, EntryState as l, ObjectId as m, CashFlowCategory as n, Currency as o, NormalBalance as p, Category as r, DateOption as s, AccountType as t, JournalItem as u, TotalAccountOp as v };
104
- //# sourceMappingURL=core-Cx0baosR.d.mts.map
104
+ //# sourceMappingURL=core-8Xfnpn6g.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"core-Cx0baosR.d.mts","names":[],"sources":["../src/types/core.ts"],"mappings":";;;AAgCA;AAAA,KAdY,KAAA;EAAA,SAA4B,OAAA;AAAA;;KAG5B,QAAA,GAAW,KAAA,CAAM,QAAA;AAoB7B;AAAA,KATY,aAAA;;KAGA,QAAA;;AAcZ;;;KARY,WAAA;;UAQK,QAAA;EAAA,SACN,IAAA,EAAM,aAAA;EAAA,SACN,QAAA,EAAU,QAAA;EAAA,SACV,aAAA,EAAe,aAAA;AAAA;;KAId,aAAA;;KAGA,gBAAA;;UAKK,cAAA;EAAA,SACN,OAAA;EAAA,SACA,SAAA;AAAA;;UAIM,WAAA;EAAA,SACN,OAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;EAAA,SACA,eAAA;AAAA;;;;AANX;UAaiB,WAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA,EAAU,WAAA;EAAA,SACV,WAAA;EAAA,SACA,UAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA;EAAA,SACA,OAAA;EAAA,SACA,iBAAA,YAA6B,cAAA;EAAA,SAC7B,gBAAA,GAAmB,gBAAA;EAAA,SACnB,WAAA,GAAc,WAAA;EAAA,SACd,UAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA;AAAA;;UAMM,WAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,WAAA;AAAA;;KAIC,UAAA;;UAGK,SAAA;EACf,OAAA;EACA,OAAA;AAAA;;;;;UAOe,WAAA;EACf,OAAA,EAAS,QAAA;EACT,KAAA;EACA,IAAA,GAAO,IAAA;EACP,KAAA;EACA,MAAA;EACA,UAAA,GAAa,SAAA;EAzBE;EAAA,CA2Bd,GAAA;AAAA;;UAMc,QAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA;EAAA,SACA,SAAA;AAAA;AAAA,KAKC,UAAA;AAAA,UAcK,SAAA;EACf,SAAA,EAAW,IAAA;EACX,OAAA,EAAS,IAAA;AAAA"}
1
+ {"version":3,"file":"core-8Xfnpn6g.d.mts","names":[],"sources":["../src/types/core.ts"],"mappings":";;;AAgCA;AAAA,KAdY,KAAA;EAAA,SAA4B,OAAA;AAAA;;KAG5B,QAAA,GAAW,KAAA,CAAM,QAAA;AAoB7B;AAAA,KATY,aAAA;;KAGA,QAAA;;AAcZ;;;KARY,WAAA;;UAQK,QAAA;EAAA,SACN,IAAA,EAAM,aAAA;EAAA,SACN,QAAA,EAAU,QAAA;EAAA,SACV,aAAA,EAAe,aAAA;AAAA;;KAId,aAAA;;KAGA,gBAAA;;UAKK,cAAA;EAAA,SACN,OAAA;EAAA,SACA,SAAA;AAAA;;UAIM,WAAA;EAAA,SACN,OAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;EAAA,SACA,eAAA;AAAA;;;;AANX;UAaiB,WAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA,EAAU,WAAA;EAAA,SACV,WAAA;EAAA,SACA,UAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA;EAAA,SACA,OAAA;EAAA,SACA,iBAAA,YAA6B,cAAA;EAAA,SAC7B,gBAAA,GAAmB,gBAAA;EAAA,SACnB,WAAA,GAAc,WAAA;EAAA,SACd,UAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA;AAAA;;UAMM,WAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,WAAA;AAAA;;KAIC,UAAA;;UAGK,SAAA;EACf,OAAA;EACA,OAAA;AAAA;;;;;UAOe,WAAA;EACf,OAAA,EAAS,QAAA;EACT,KAAA;EACA,IAAA,GAAO,IAAA;EACP,KAAA;EACA,MAAA;EACA,UAAA,GAAa,SAAA;EAzBE;EAAA,CA2Bd,GAAA;AAAA;;UAMc,QAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA;EAAA,SACA,SAAA;AAAA;AAAA,KAKC,UAAA;AAAA,UAcK,SAAA;EACf,SAAA,EAAW,IAAA;EACX,OAAA,EAAS,IAAA;AAAA"}
@@ -1,105 +1,2 @@
1
- import { t as AccountType } from "../core-Cx0baosR.mjs";
2
-
3
- //#region src/country/index.d.ts
4
- interface TaxCode {
5
- readonly code: string;
6
- readonly name: string;
7
- readonly taxType: string;
8
- readonly rate: number;
9
- readonly direction: 'collected' | 'recoverable' | 'paid';
10
- readonly province?: string;
11
- readonly reportLines?: readonly number[];
12
- readonly description: string;
13
- readonly active: boolean;
14
- }
15
- interface TaxCodesByRegion {
16
- readonly [region: string]: readonly string[];
17
- }
18
- interface TaxReportLine {
19
- readonly line: number | string;
20
- readonly name: string;
21
- readonly description: string;
22
- readonly type: 'input' | 'calculated' | 'manual';
23
- readonly calculate?: (data: Record<string | number, number>) => number;
24
- readonly section: string;
25
- }
26
- interface TaxReportTemplate {
27
- readonly name: string;
28
- readonly lines: Readonly<Record<string | number, TaxReportLine>>;
29
- calculate(inputData: Record<string | number, number>, manualData?: Record<string | number, number>): Record<string | number, number>;
30
- summarize(calculated: Record<string | number, number>): Record<string, unknown>;
31
- }
32
- interface CountryPack {
33
- /** ISO 3166-1 alpha-2 code (e.g., 'CA', 'US', 'GB') */
34
- readonly code: string;
35
- /** Country name */
36
- readonly name: string;
37
- /** Default currency code */
38
- readonly defaultCurrency: string;
39
- /**
40
- * Full chart of accounts template — flat array of account type definitions.
41
- * Includes both regular accounts and virtual tax sub-accounts.
42
- */
43
- readonly accountTypes: readonly AccountType[];
44
- /** Tax codes indexed by code string */
45
- readonly taxCodes: Readonly<Record<string, TaxCode>>;
46
- /** Tax codes grouped by region/province/state */
47
- readonly taxCodesByRegion: TaxCodesByRegion;
48
- /** Available regions (provinces/states) */
49
- readonly regions: readonly string[];
50
- /** Tax report template (e.g., CRA GST/HST return) */
51
- readonly taxReport?: TaxReportTemplate;
52
- /** Account code for prior retained earnings (e.g. '3660' CA, '3200' US) */
53
- readonly retainedEarningsCode?: string;
54
- /** Account code for current year net income (e.g. '3680' CA, '3210' US) */
55
- readonly currentYearEarningsCode?: string;
56
- /** Group label code used to identify Cost of Sales in the income statement */
57
- readonly cogsGroupCode?: string;
58
- /** Override default English report section names */
59
- readonly reportLabels?: {
60
- readonly assets?: string;
61
- readonly liabilities?: string;
62
- readonly equity?: string;
63
- readonly revenue?: string;
64
- readonly expenses?: string;
65
- };
66
- /** Get all account types that can be posted to (not groups, not totals) */
67
- getPostingAccountTypes(): readonly AccountType[];
68
- /** Get account type by code */
69
- getAccountType(code: string): AccountType | undefined;
70
- /** Validate an account type code exists */
71
- isValidAccountType(code: string): boolean;
72
- /** Check if an account type can receive postings */
73
- isPostingAccount(code: string): boolean;
74
- /** Get tax codes for a specific region */
75
- getTaxCodesForRegion(region: string): TaxCode[];
76
- /** Flatten hierarchical accounts (if needed) */
77
- flattenAccountTypes(): readonly AccountType[];
78
- }
79
- interface CountryPackInput {
80
- code: string;
81
- name: string;
82
- defaultCurrency: string;
83
- accountTypes: readonly AccountType[];
84
- taxCodes: Readonly<Record<string, TaxCode>>;
85
- taxCodesByRegion: TaxCodesByRegion;
86
- regions: readonly string[];
87
- taxReport?: TaxReportTemplate;
88
- retainedEarningsCode?: string;
89
- currentYearEarningsCode?: string;
90
- cogsGroupCode?: string;
91
- reportLabels?: {
92
- readonly assets?: string;
93
- readonly liabilities?: string;
94
- readonly equity?: string;
95
- readonly revenue?: string;
96
- readonly expenses?: string;
97
- };
98
- }
99
- /**
100
- * Factory to create a CountryPack with auto-generated helper methods.
101
- */
102
- declare function defineCountryPack(input: CountryPackInput): CountryPack;
103
- //#endregion
104
- export { CountryPack, CountryPackInput, TaxCode, TaxCodesByRegion, TaxReportLine, TaxReportTemplate, defineCountryPack };
105
- //# sourceMappingURL=index.d.mts.map
1
+ import { a as TaxReportLine, i as TaxCodesByRegion, n as CountryPackInput, o as TaxReportTemplate, r as TaxCode, s as defineCountryPack, t as CountryPack } from "../index-ZnSiqHYV.mjs";
2
+ export { CountryPack, CountryPackInput, TaxCode, TaxCodesByRegion, TaxReportLine, TaxReportTemplate, defineCountryPack };
@@ -21,7 +21,7 @@ function defineCountryPack(input) {
21
21
  flattenAccountTypes: () => input.accountTypes
22
22
  };
23
23
  }
24
-
25
24
  //#endregion
26
25
  export { defineCountryPack };
26
+
27
27
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/country/index.ts"],"sourcesContent":["/**\n * Country Pack Interface\n *\n * A country pack provides everything country-specific:\n * account types (chart of accounts template), tax codes,\n * and optionally a tax report generator.\n *\n * Example:\n * import { canadaPack } from '@classytic/ledger-ca';\n * const engine = createAccountingEngine({ country: canadaPack, currency: 'CAD' });\n */\n\nimport type { AccountType, CategoryKey } from '../types/core.js';\n\n// ─── Tax Code ────────────────────────────────────────────────────────────────\n\nexport interface TaxCode {\n readonly code: string;\n readonly name: string;\n readonly taxType: string;\n readonly rate: number;\n readonly direction: 'collected' | 'recoverable' | 'paid';\n readonly province?: string;\n readonly reportLines?: readonly number[];\n readonly description: string;\n readonly active: boolean;\n}\n\nexport interface TaxCodesByRegion {\n readonly [region: string]: readonly string[];\n}\n\n// ─── Tax Report Generator ────────────────────────────────────────────────────\n\nexport interface TaxReportLine {\n readonly line: number | string;\n readonly name: string;\n readonly description: string;\n readonly type: 'input' | 'calculated' | 'manual';\n readonly calculate?: (data: Record<string | number, number>) => number;\n readonly section: string;\n}\n\nexport interface TaxReportTemplate {\n readonly name: string;\n readonly lines: Readonly<Record<string | number, TaxReportLine>>;\n calculate(inputData: Record<string | number, number>, manualData?: Record<string | number, number>): Record<string | number, number>;\n summarize(calculated: Record<string | number, number>): Record<string, unknown>;\n}\n\n// ─── Country Pack ────────────────────────────────────────────────────────────\n\nexport interface CountryPack {\n /** ISO 3166-1 alpha-2 code (e.g., 'CA', 'US', 'GB') */\n readonly code: string;\n /** Country name */\n readonly name: string;\n /** Default currency code */\n readonly defaultCurrency: string;\n\n /**\n * Full chart of accounts template — flat array of account type definitions.\n * Includes both regular accounts and virtual tax sub-accounts.\n */\n readonly accountTypes: readonly AccountType[];\n\n /** Tax codes indexed by code string */\n readonly taxCodes: Readonly<Record<string, TaxCode>>;\n\n /** Tax codes grouped by region/province/state */\n readonly taxCodesByRegion: TaxCodesByRegion;\n\n /** Available regions (provinces/states) */\n readonly regions: readonly string[];\n\n /** Tax report template (e.g., CRA GST/HST return) */\n readonly taxReport?: TaxReportTemplate;\n\n // ── Country-specific report defaults ──\n\n /** Account code for prior retained earnings (e.g. '3660' CA, '3200' US) */\n readonly retainedEarningsCode?: string;\n /** Account code for current year net income (e.g. '3680' CA, '3210' US) */\n readonly currentYearEarningsCode?: string;\n /** Group label code used to identify Cost of Sales in the income statement */\n readonly cogsGroupCode?: string;\n /** Override default English report section names */\n readonly reportLabels?: {\n readonly assets?: string;\n readonly liabilities?: string;\n readonly equity?: string;\n readonly revenue?: string;\n readonly expenses?: string;\n };\n\n // ── Helpers ──\n\n /** Get all account types that can be posted to (not groups, not totals) */\n getPostingAccountTypes(): readonly AccountType[];\n\n /** Get account type by code */\n getAccountType(code: string): AccountType | undefined;\n\n /** Validate an account type code exists */\n isValidAccountType(code: string): boolean;\n\n /** Check if an account type can receive postings */\n isPostingAccount(code: string): boolean;\n\n /** Get tax codes for a specific region */\n getTaxCodesForRegion(region: string): TaxCode[];\n\n /** Flatten hierarchical accounts (if needed) */\n flattenAccountTypes(): readonly AccountType[];\n}\n\n// ─── Helper: Build a country pack from raw data ──────────────────────────────\n\nexport interface CountryPackInput {\n code: string;\n name: string;\n defaultCurrency: string;\n accountTypes: readonly AccountType[];\n taxCodes: Readonly<Record<string, TaxCode>>;\n taxCodesByRegion: TaxCodesByRegion;\n regions: readonly string[];\n taxReport?: TaxReportTemplate;\n retainedEarningsCode?: string;\n currentYearEarningsCode?: string;\n cogsGroupCode?: string;\n reportLabels?: {\n readonly assets?: string;\n readonly liabilities?: string;\n readonly equity?: string;\n readonly revenue?: string;\n readonly expenses?: string;\n };\n}\n\n/**\n * Factory to create a CountryPack with auto-generated helper methods.\n */\nexport function defineCountryPack(input: CountryPackInput): CountryPack {\n // Build lookup map once\n const accountMap = new Map<string, AccountType>();\n for (const at of input.accountTypes) {\n accountMap.set(at.code, at);\n }\n\n const postingTypes = input.accountTypes.filter(at => !at.isTotal && !at.isGroup);\n\n return {\n ...input,\n\n getPostingAccountTypes: () => postingTypes,\n\n getAccountType: (code: string) => accountMap.get(code),\n\n isValidAccountType: (code: string) => accountMap.has(code),\n\n isPostingAccount: (code: string) => {\n const at = accountMap.get(code);\n return at !== undefined && !at.isTotal && !at.isGroup;\n },\n\n getTaxCodesForRegion: (region: string) => {\n const codes = input.taxCodesByRegion[region] ?? [];\n return codes.map(c => input.taxCodes[c]).filter(Boolean) as TaxCode[];\n },\n\n flattenAccountTypes: () => input.accountTypes,\n };\n}\n"],"mappings":";;;;AA8IA,SAAgB,kBAAkB,OAAsC;CAEtE,MAAM,6BAAa,IAAI,KAA0B;AACjD,MAAK,MAAM,MAAM,MAAM,aACrB,YAAW,IAAI,GAAG,MAAM,GAAG;CAG7B,MAAM,eAAe,MAAM,aAAa,QAAO,OAAM,CAAC,GAAG,WAAW,CAAC,GAAG,QAAQ;AAEhF,QAAO;EACL,GAAG;EAEH,8BAA8B;EAE9B,iBAAiB,SAAiB,WAAW,IAAI,KAAK;EAEtD,qBAAqB,SAAiB,WAAW,IAAI,KAAK;EAE1D,mBAAmB,SAAiB;GAClC,MAAM,KAAK,WAAW,IAAI,KAAK;AAC/B,UAAO,OAAO,UAAa,CAAC,GAAG,WAAW,CAAC,GAAG;;EAGhD,uBAAuB,WAAmB;AAExC,WADc,MAAM,iBAAiB,WAAW,EAAE,EACrC,KAAI,MAAK,MAAM,SAAS,GAAG,CAAC,OAAO,QAAQ;;EAG1D,2BAA2B,MAAM;EAClC"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/country/index.ts"],"sourcesContent":["/**\n * Country Pack Interface\n *\n * A country pack provides everything country-specific:\n * account types (chart of accounts template), tax codes,\n * and optionally a tax report generator.\n *\n * Example:\n * import { canadaPack } from '@classytic/ledger-ca';\n * const engine = createAccountingEngine({ country: canadaPack, currency: 'CAD' });\n */\n\nimport type { AccountType, CategoryKey } from '../types/core.js';\n\n// ─── Tax Code ────────────────────────────────────────────────────────────────\n\nexport interface TaxCode {\n readonly code: string;\n readonly name: string;\n readonly taxType: string;\n readonly rate: number;\n readonly direction: 'collected' | 'recoverable' | 'paid';\n readonly province?: string;\n readonly reportLines?: readonly number[];\n readonly description: string;\n readonly active: boolean;\n}\n\nexport interface TaxCodesByRegion {\n readonly [region: string]: readonly string[];\n}\n\n// ─── Tax Report Generator ────────────────────────────────────────────────────\n\nexport interface TaxReportLine {\n readonly line: number | string;\n readonly name: string;\n readonly description: string;\n readonly type: 'input' | 'calculated' | 'manual';\n readonly calculate?: (data: Record<string | number, number>) => number;\n readonly section: string;\n}\n\nexport interface TaxReportTemplate {\n readonly name: string;\n readonly lines: Readonly<Record<string | number, TaxReportLine>>;\n calculate(inputData: Record<string | number, number>, manualData?: Record<string | number, number>): Record<string | number, number>;\n summarize(calculated: Record<string | number, number>): Record<string, unknown>;\n}\n\n// ─── Country Pack ────────────────────────────────────────────────────────────\n\nexport interface CountryPack {\n /** ISO 3166-1 alpha-2 code (e.g., 'CA', 'US', 'GB') */\n readonly code: string;\n /** Country name */\n readonly name: string;\n /** Default currency code */\n readonly defaultCurrency: string;\n\n /**\n * Full chart of accounts template — flat array of account type definitions.\n * Includes both regular accounts and virtual tax sub-accounts.\n */\n readonly accountTypes: readonly AccountType[];\n\n /** Tax codes indexed by code string */\n readonly taxCodes: Readonly<Record<string, TaxCode>>;\n\n /** Tax codes grouped by region/province/state */\n readonly taxCodesByRegion: TaxCodesByRegion;\n\n /** Available regions (provinces/states) */\n readonly regions: readonly string[];\n\n /** Tax report template (e.g., CRA GST/HST return) */\n readonly taxReport?: TaxReportTemplate;\n\n // ── Country-specific report defaults ──\n\n /** Account code for prior retained earnings (e.g. '3660' CA, '3200' US) */\n readonly retainedEarningsCode?: string;\n /** Account code for current year net income (e.g. '3680' CA, '3210' US) */\n readonly currentYearEarningsCode?: string;\n /** Group label code used to identify Cost of Sales in the income statement */\n readonly cogsGroupCode?: string;\n /** Override default English report section names */\n readonly reportLabels?: {\n readonly assets?: string;\n readonly liabilities?: string;\n readonly equity?: string;\n readonly revenue?: string;\n readonly expenses?: string;\n };\n\n // ── Helpers ──\n\n /** Get all account types that can be posted to (not groups, not totals) */\n getPostingAccountTypes(): readonly AccountType[];\n\n /** Get account type by code */\n getAccountType(code: string): AccountType | undefined;\n\n /** Validate an account type code exists */\n isValidAccountType(code: string): boolean;\n\n /** Check if an account type can receive postings */\n isPostingAccount(code: string): boolean;\n\n /** Get tax codes for a specific region */\n getTaxCodesForRegion(region: string): TaxCode[];\n\n /** Flatten hierarchical accounts (if needed) */\n flattenAccountTypes(): readonly AccountType[];\n}\n\n// ─── Helper: Build a country pack from raw data ──────────────────────────────\n\nexport interface CountryPackInput {\n code: string;\n name: string;\n defaultCurrency: string;\n accountTypes: readonly AccountType[];\n taxCodes: Readonly<Record<string, TaxCode>>;\n taxCodesByRegion: TaxCodesByRegion;\n regions: readonly string[];\n taxReport?: TaxReportTemplate;\n retainedEarningsCode?: string;\n currentYearEarningsCode?: string;\n cogsGroupCode?: string;\n reportLabels?: {\n readonly assets?: string;\n readonly liabilities?: string;\n readonly equity?: string;\n readonly revenue?: string;\n readonly expenses?: string;\n };\n}\n\n/**\n * Factory to create a CountryPack with auto-generated helper methods.\n */\nexport function defineCountryPack(input: CountryPackInput): CountryPack {\n // Build lookup map once\n const accountMap = new Map<string, AccountType>();\n for (const at of input.accountTypes) {\n accountMap.set(at.code, at);\n }\n\n const postingTypes = input.accountTypes.filter(at => !at.isTotal && !at.isGroup);\n\n return {\n ...input,\n\n getPostingAccountTypes: () => postingTypes,\n\n getAccountType: (code: string) => accountMap.get(code),\n\n isValidAccountType: (code: string) => accountMap.has(code),\n\n isPostingAccount: (code: string) => {\n const at = accountMap.get(code);\n return at !== undefined && !at.isTotal && !at.isGroup;\n },\n\n getTaxCodesForRegion: (region: string) => {\n const codes = input.taxCodesByRegion[region] ?? [];\n return codes.map(c => input.taxCodes[c]).filter(Boolean) as TaxCode[];\n },\n\n flattenAccountTypes: () => input.accountTypes,\n };\n}\n"],"mappings":";;;;AA8IA,SAAgB,kBAAkB,OAAsC;CAEtE,MAAM,6BAAa,IAAI,KAA0B;AACjD,MAAK,MAAM,MAAM,MAAM,aACrB,YAAW,IAAI,GAAG,MAAM,GAAG;CAG7B,MAAM,eAAe,MAAM,aAAa,QAAO,OAAM,CAAC,GAAG,WAAW,CAAC,GAAG,QAAQ;AAEhF,QAAO;EACL,GAAG;EAEH,8BAA8B;EAE9B,iBAAiB,SAAiB,WAAW,IAAI,KAAK;EAEtD,qBAAqB,SAAiB,WAAW,IAAI,KAAK;EAE1D,mBAAmB,SAAiB;GAClC,MAAM,KAAK,WAAW,IAAI,KAAK;AAC/B,UAAO,OAAO,KAAA,KAAa,CAAC,GAAG,WAAW,CAAC,GAAG;;EAGhD,uBAAuB,WAAmB;AAExC,WADc,MAAM,iBAAiB,WAAW,EAAE,EACrC,KAAI,MAAK,MAAM,SAAS,GAAG,CAAC,OAAO,QAAQ;;EAG1D,2BAA2B,MAAM;EAClC"}
@@ -1,4 +1,4 @@
1
- import { d as JournalType, f as MainType, h as StatementType, i as CategoryKey, o as Currency, r as Category } from "./core-Cx0baosR.mjs";
1
+ import { d as JournalType, f as MainType, h as StatementType, i as CategoryKey, o as Currency, r as Category } from "./core-8Xfnpn6g.mjs";
2
2
 
3
3
  //#region src/constants/categories.d.ts
4
4
  /** All valid categories */
@@ -35,4 +35,4 @@ declare function isValidCurrency(code: string): boolean;
35
35
  declare function getMinorUnit(code: string): number;
36
36
  //#endregion
37
37
  export { getNormalBalance as _, JOURNAL_CODES as a, isValidCategory as b, getJournalTypeCodes as c, CATEGORY_KEYS as d, categoryKey as f, getCategoryStatementType as g, getCategoryMainType as h, isValidCurrency as i, isValidJournalType as l, extractStatementType as m, getCurrency as n, JOURNAL_TYPES as o, extractMainType as p, getMinorUnit as r, getJournalType as s, CURRENCIES as t, CATEGORIES as u, isBalanceSheet as v, isIncomeStatement as y };
38
- //# sourceMappingURL=currencies-Bkn3FNkC.d.mts.map
38
+ //# sourceMappingURL=currencies-4WAbFRlw.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"currencies-Bkn3FNkC.d.mts","names":[],"sources":["../src/constants/categories.ts","../src/constants/journals.ts","../src/constants/currencies.ts"],"mappings":";;;;cAOa,UAAA,EAAY,QAAA,CAAS,MAAA,CAAO,WAAA,EAAa,QAAA;AAAA,cAQzC,aAAA,EAA2C,WAAA;AAAA,iBAExC,eAAA,CAAgB,GAAA,WAAc,GAAA,IAAO,WAAA;AAAA,iBAIrC,mBAAA,CAAoB,GAAA,EAAK,WAAA,GAAc,QAAA;AAAA,iBAIvC,wBAAA,CAAyB,GAAA,EAAK,WAAA,GAAc,aAAA;AAAA,iBAI5C,cAAA,CAAe,GAAA,EAAK,WAAA;AAAA,iBAIpB,iBAAA,CAAkB,GAAA,EAAK,WAAA;;;;;iBAQvB,gBAAA,CAAiB,QAAA,EAAU,QAAA;;iBAK3B,WAAA,CAAY,SAAA,EAAW,aAAA,EAAe,QAAA,EAAU,QAAA,GAAW,WAAA;;iBAK3D,eAAA,CAAgB,GAAA,WAAc,QAAA;;iBAM9B,oBAAA,CAAqB,GAAA,WAAc,aAAA;;;cClDtC,aAAA,EAAe,QAAA,CAAS,MAAA,SAAe,WAAA;AAAA,cAkBvC,aAAA,EAAa,QAAA,CAAA,MAAA;AAAA,iBAIV,mBAAA,CAAA;AAAA,iBAIA,kBAAA,CAAmB,IAAA;AAAA,iBAInB,cAAA,CAAe,IAAA,WAAe,WAAA;;;cC/BjC,UAAA,EAAY,QAAA,CAAS,MAAA,SAAe,QAAA;AAAA,iBAcjC,WAAA,CAAY,IAAA,WAAe,QAAA;AAAA,iBAI3B,eAAA,CAAgB,IAAA;AAAA,iBAIhB,YAAA,CAAa,IAAA"}
1
+ {"version":3,"file":"currencies-4WAbFRlw.d.mts","names":[],"sources":["../src/constants/categories.ts","../src/constants/journals.ts","../src/constants/currencies.ts"],"mappings":";;;;cAOa,UAAA,EAAY,QAAA,CAAS,MAAA,CAAO,WAAA,EAAa,QAAA;AAAA,cAQzC,aAAA,EAA2C,WAAA;AAAA,iBAExC,eAAA,CAAgB,GAAA,WAAc,GAAA,IAAO,WAAA;AAAA,iBAIrC,mBAAA,CAAoB,GAAA,EAAK,WAAA,GAAc,QAAA;AAAA,iBAIvC,wBAAA,CAAyB,GAAA,EAAK,WAAA,GAAc,aAAA;AAAA,iBAI5C,cAAA,CAAe,GAAA,EAAK,WAAA;AAAA,iBAIpB,iBAAA,CAAkB,GAAA,EAAK,WAAA;;;;;iBAQvB,gBAAA,CAAiB,QAAA,EAAU,QAAA;;iBAK3B,WAAA,CAAY,SAAA,EAAW,aAAA,EAAe,QAAA,EAAU,QAAA,GAAW,WAAA;;iBAK3D,eAAA,CAAgB,GAAA,WAAc,QAAA;;iBAM9B,oBAAA,CAAqB,GAAA,WAAc,aAAA;;;cClDtC,aAAA,EAAe,QAAA,CAAS,MAAA,SAAe,WAAA;AAAA,cAkBvC,aAAA,EAAa,QAAA,CAAA,MAAA;AAAA,iBAIV,mBAAA,CAAA;AAAA,iBAIA,kBAAA,CAAmB,IAAA;AAAA,iBAInB,cAAA,CAAe,IAAA,WAAe,WAAA;;;cC/BjC,UAAA,EAAY,QAAA,CAAS,MAAA,SAAe,QAAA;AAAA,iBAcjC,WAAA,CAAY,IAAA,WAAe,QAAA;AAAA,iBAI3B,eAAA,CAAgB,IAAA;AAAA,iBAIhB,YAAA,CAAa,IAAA"}
@@ -76,7 +76,7 @@ function isValidCurrency(code) {
76
76
  function getMinorUnit(code) {
77
77
  return CURRENCIES[code]?.minorUnit ?? 2;
78
78
  }
79
-
80
79
  //#endregion
81
80
  export { isValidCurrency as i, getCurrency as n, getMinorUnit as r, CURRENCIES as t };
82
- //# sourceMappingURL=currencies-BBk3NwXn.mjs.map
81
+
82
+ //# sourceMappingURL=currencies-W8kQAkm0.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"currencies-BBk3NwXn.mjs","names":[],"sources":["../src/constants/currencies.ts"],"sourcesContent":["/**\r\n * ISO 4217 Currency Definitions\r\n */\r\n\r\nimport type { Currency } from '../types/core.js';\r\n\r\nexport const CURRENCIES: Readonly<Record<string, Currency>> = Object.freeze({\r\n CAD: { code: 'CAD', name: 'Canadian Dollar', symbol: '$', minorUnit: 2 },\r\n USD: { code: 'USD', name: 'US Dollar', symbol: '$', minorUnit: 2 },\r\n GBP: { code: 'GBP', name: 'British Pound', symbol: '£', minorUnit: 2 },\r\n EUR: { code: 'EUR', name: 'Euro', symbol: '€', minorUnit: 2 },\r\n JPY: { code: 'JPY', name: 'Japanese Yen', symbol: '¥', minorUnit: 0 },\r\n AUD: { code: 'AUD', name: 'Australian Dollar', symbol: '$', minorUnit: 2 },\r\n CHF: { code: 'CHF', name: 'Swiss Franc', symbol: 'CHF', minorUnit: 2 },\r\n INR: { code: 'INR', name: 'Indian Rupee', symbol: '₹', minorUnit: 2 },\r\n BDT: { code: 'BDT', name: 'Bangladeshi Taka', symbol: '৳', minorUnit: 2 },\r\n AED: { code: 'AED', name: 'UAE Dirham', symbol: 'د.إ', minorUnit: 2 },\r\n SAR: { code: 'SAR', name: 'Saudi Riyal', symbol: '﷼', minorUnit: 2 },\r\n});\r\n\r\nexport function getCurrency(code: string): Currency | null {\r\n return CURRENCIES[code] ?? null;\r\n}\r\n\r\nexport function isValidCurrency(code: string): boolean {\r\n return code in CURRENCIES;\r\n}\r\n\r\nexport function getMinorUnit(code: string): number {\r\n return CURRENCIES[code]?.minorUnit ?? 2;\r\n}\r\n"],"mappings":";AAMA,MAAa,aAAiD,OAAO,OAAO;CAC1E,KAAK;EAAE,MAAM;EAAO,MAAM;EAAmB,QAAQ;EAAK,WAAW;EAAG;CACxE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAa,QAAQ;EAAK,WAAW;EAAG;CAClE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAiB,QAAQ;EAAK,WAAW;EAAG;CACtE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAQ,QAAQ;EAAK,WAAW;EAAG;CAC7D,KAAK;EAAE,MAAM;EAAO,MAAM;EAAgB,QAAQ;EAAK,WAAW;EAAG;CACrE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAqB,QAAQ;EAAK,WAAW;EAAG;CAC1E,KAAK;EAAE,MAAM;EAAO,MAAM;EAAe,QAAQ;EAAO,WAAW;EAAG;CACtE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAgB,QAAQ;EAAK,WAAW;EAAG;CACrE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAoB,QAAQ;EAAK,WAAW;EAAG;CACzE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAc,QAAQ;EAAO,WAAW;EAAG;CACrE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAe,QAAQ;EAAK,WAAW;EAAG;CACrE,CAAC;AAEF,SAAgB,YAAY,MAA+B;AACzD,QAAO,WAAW,SAAS;;AAG7B,SAAgB,gBAAgB,MAAuB;AACrD,QAAO,QAAQ;;AAGjB,SAAgB,aAAa,MAAsB;AACjD,QAAO,WAAW,OAAO,aAAa"}
1
+ {"version":3,"file":"currencies-W8kQAkm0.mjs","names":[],"sources":["../src/constants/currencies.ts"],"sourcesContent":["/**\r\n * ISO 4217 Currency Definitions\r\n */\r\n\r\nimport type { Currency } from '../types/core.js';\r\n\r\nexport const CURRENCIES: Readonly<Record<string, Currency>> = Object.freeze({\r\n CAD: { code: 'CAD', name: 'Canadian Dollar', symbol: '$', minorUnit: 2 },\r\n USD: { code: 'USD', name: 'US Dollar', symbol: '$', minorUnit: 2 },\r\n GBP: { code: 'GBP', name: 'British Pound', symbol: '£', minorUnit: 2 },\r\n EUR: { code: 'EUR', name: 'Euro', symbol: '€', minorUnit: 2 },\r\n JPY: { code: 'JPY', name: 'Japanese Yen', symbol: '¥', minorUnit: 0 },\r\n AUD: { code: 'AUD', name: 'Australian Dollar', symbol: '$', minorUnit: 2 },\r\n CHF: { code: 'CHF', name: 'Swiss Franc', symbol: 'CHF', minorUnit: 2 },\r\n INR: { code: 'INR', name: 'Indian Rupee', symbol: '₹', minorUnit: 2 },\r\n BDT: { code: 'BDT', name: 'Bangladeshi Taka', symbol: '৳', minorUnit: 2 },\r\n AED: { code: 'AED', name: 'UAE Dirham', symbol: 'د.إ', minorUnit: 2 },\r\n SAR: { code: 'SAR', name: 'Saudi Riyal', symbol: '﷼', minorUnit: 2 },\r\n});\r\n\r\nexport function getCurrency(code: string): Currency | null {\r\n return CURRENCIES[code] ?? null;\r\n}\r\n\r\nexport function isValidCurrency(code: string): boolean {\r\n return code in CURRENCIES;\r\n}\r\n\r\nexport function getMinorUnit(code: string): number {\r\n return CURRENCIES[code]?.minorUnit ?? 2;\r\n}\r\n"],"mappings":";AAMA,MAAa,aAAiD,OAAO,OAAO;CAC1E,KAAK;EAAE,MAAM;EAAO,MAAM;EAAmB,QAAQ;EAAK,WAAW;EAAG;CACxE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAa,QAAQ;EAAK,WAAW;EAAG;CAClE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAiB,QAAQ;EAAK,WAAW;EAAG;CACtE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAQ,QAAQ;EAAK,WAAW;EAAG;CAC7D,KAAK;EAAE,MAAM;EAAO,MAAM;EAAgB,QAAQ;EAAK,WAAW;EAAG;CACrE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAqB,QAAQ;EAAK,WAAW;EAAG;CAC1E,KAAK;EAAE,MAAM;EAAO,MAAM;EAAe,QAAQ;EAAO,WAAW;EAAG;CACtE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAgB,QAAQ;EAAK,WAAW;EAAG;CACrE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAoB,QAAQ;EAAK,WAAW;EAAG;CACzE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAc,QAAQ;EAAO,WAAW;EAAG;CACrE,KAAK;EAAE,MAAM;EAAO,MAAM;EAAe,QAAQ;EAAK,WAAW;EAAG;CACrE,CAAC;AAEF,SAAgB,YAAY,MAA+B;AACzD,QAAO,WAAW,SAAS;;AAG7B,SAAgB,gBAAgB,MAAuB;AACrD,QAAO,QAAQ;;AAGjB,SAAgB,aAAa,MAAsB;AACjD,QAAO,WAAW,OAAO,aAAa"}
@@ -1,5 +1,5 @@
1
- import { CountryPack } from "./country/index.mjs";
2
- import { t as Logger } from "./logger-Cv6VVc4r.mjs";
1
+ import { t as CountryPack } from "./index-ZnSiqHYV.mjs";
2
+ import { t as Logger } from "./logger-UbTdBb1x.mjs";
3
3
 
4
4
  //#region src/types/engine.d.ts
5
5
  /** Multi-tenant configuration */
@@ -44,14 +44,40 @@ interface StrictnessConfig {
44
44
  /** If true, entries must have approvedBy/approvedAt set before posting */
45
45
  requireApproval?: boolean;
46
46
  }
47
+ /**
48
+ * Multi-currency configuration.
49
+ * When enabled, adds currency and exchange rate fields to journal items
50
+ * and a currency field to accounts. Allows recording transactions in
51
+ * foreign currencies while maintaining a base (functional) currency.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const engine = createAccountingEngine({
56
+ * country: canadaPack,
57
+ * currency: 'CAD', // base/functional currency
58
+ * multiCurrency: {
59
+ * enabled: true,
60
+ * currencies: ['USD', 'GBP', 'BDT'], // allowed foreign currencies
61
+ * },
62
+ * });
63
+ * ```
64
+ */
65
+ interface MultiCurrencyConfig {
66
+ /** Enable multi-currency fields on schemas */
67
+ enabled: boolean;
68
+ /** Allowed foreign currency codes. If omitted, any ISO 4217 code is accepted. */
69
+ currencies?: readonly string[];
70
+ }
47
71
  /** Main engine configuration */
48
72
  interface AccountingEngineConfig {
49
73
  /** Country pack providing account types, tax codes, and templates */
50
74
  country: CountryPack;
51
- /** Default ISO 4217 currency code (e.g., 'CAD', 'USD') */
75
+ /** Default ISO 4217 currency code — the functional/base currency (e.g., 'CAD', 'BDT') */
52
76
  currency: string;
53
77
  /** Multi-tenant configuration. Omit for single-tenant apps. */
54
78
  multiTenant?: MultiTenantConfig;
79
+ /** Multi-currency support. Omit for single-currency apps. */
80
+ multiCurrency?: MultiCurrencyConfig;
55
81
  /** Fiscal year start month (1-12, default: 1 = January) */
56
82
  fiscalYearStartMonth?: number;
57
83
  /** Display code for prior retained earnings on balance sheet (default: '3660') */
@@ -68,5 +94,5 @@ interface AccountingEngineConfig {
68
94
  strictness?: StrictnessConfig;
69
95
  }
70
96
  //#endregion
71
- export { SchemaOptions as a, MultiTenantConfig as i, AuditConfig as n, StrictnessConfig as o, JournalSchemaOptions as r, AccountingEngineConfig as t };
72
- //# sourceMappingURL=engine-Cd73EOT6.d.mts.map
97
+ export { MultiTenantConfig as a, MultiCurrencyConfig as i, AuditConfig as n, SchemaOptions as o, JournalSchemaOptions as r, StrictnessConfig as s, AccountingEngineConfig as t };
98
+ //# sourceMappingURL=engine-BzBMpWuy.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-BzBMpWuy.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;;;;;AAuBF;;;;;AAUA;;;;;;;;;UAViB,mBAAA;EAgCc;EA9B7B,OAAA;EAUS;EART,UAAA;AAAA;;UAMe,sBAAA;EAQC;EANhB,OAAA,EAAS,WAAA;EAUT;EARA,QAAA;EAYA;EAVA,WAAA,GAAc,iBAAA;EAYd;EAVA,aAAA,GAAgB,mBAAA;EAYhB;EAVA,oBAAA;EAYa;EAVb,oBAAA;EAU6B;EAR7B,uBAAA;;EAEA,MAAA,GAAS,MAAA;;EAET,KAAA,GAAQ,WAAA;;EAER,WAAA;;EAEA,UAAA,GAAa,gBAAA;AAAA"}
@@ -22,7 +22,7 @@ const Errors = {
22
22
  immutable: (msg) => new AccountingError(msg, 403, "IMMUTABLE_ENTRY"),
23
23
  fiscal: (msg) => new AccountingError(msg, 400, "FISCAL_ERROR")
24
24
  };
25
-
26
25
  //#endregion
27
26
  export { Errors as n, AccountingError as t };
28
- //# sourceMappingURL=errors-CeqRahE-.mjs.map
27
+
28
+ //# sourceMappingURL=errors-B7yC-Jfw.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors-CeqRahE-.mjs","names":[],"sources":["../src/utils/errors.ts"],"sourcesContent":["/**\r\n * Typed error for the accounting package.\r\n * Carries HTTP status + machine-readable code.\r\n * Replaces all ad-hoc `(error as ...).status = N` patterns.\r\n */\r\nexport class AccountingError extends Error {\r\n readonly status: number;\r\n readonly code: string;\r\n\r\n constructor(message: string, status = 400, code = 'ACCOUNTING_ERROR') {\r\n super(message);\r\n this.name = 'AccountingError';\r\n this.status = status;\r\n this.code = code;\r\n }\r\n}\r\n\r\n/** Convenience factory functions */\r\nexport const Errors = {\r\n validation: (msg: string) => new AccountingError(msg, 400, 'VALIDATION_ERROR'),\r\n notFound: (msg: string) => new AccountingError(msg, 404, 'NOT_FOUND'),\r\n conflict: (msg: string) => new AccountingError(msg, 409, 'CONFLICT'),\r\n immutable: (msg: string) => new AccountingError(msg, 403, 'IMMUTABLE_ENTRY'),\r\n fiscal: (msg: string) => new AccountingError(msg, 400, 'FISCAL_ERROR'),\r\n} as const;\r\n"],"mappings":";;;;;;AAKA,IAAa,kBAAb,cAAqC,MAAM;CACzC,AAAS;CACT,AAAS;CAET,YAAY,SAAiB,SAAS,KAAK,OAAO,oBAAoB;AACpE,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,OAAO;;;;AAKhB,MAAa,SAAS;CACpB,aAAa,QAAgB,IAAI,gBAAgB,KAAK,KAAK,mBAAmB;CAC9E,WAAW,QAAgB,IAAI,gBAAgB,KAAK,KAAK,YAAY;CACrE,WAAW,QAAgB,IAAI,gBAAgB,KAAK,KAAK,WAAW;CACpE,YAAY,QAAgB,IAAI,gBAAgB,KAAK,KAAK,kBAAkB;CAC5E,SAAS,QAAgB,IAAI,gBAAgB,KAAK,KAAK,eAAe;CACvE"}
1
+ {"version":3,"file":"errors-B7yC-Jfw.mjs","names":[],"sources":["../src/utils/errors.ts"],"sourcesContent":["/**\r\n * Typed error for the accounting package.\r\n * Carries HTTP status + machine-readable code.\r\n * Replaces all ad-hoc `(error as ...).status = N` patterns.\r\n */\r\nexport class AccountingError extends Error {\r\n readonly status: number;\r\n readonly code: string;\r\n\r\n constructor(message: string, status = 400, code = 'ACCOUNTING_ERROR') {\r\n super(message);\r\n this.name = 'AccountingError';\r\n this.status = status;\r\n this.code = code;\r\n }\r\n}\r\n\r\n/** Convenience factory functions */\r\nexport const Errors = {\r\n validation: (msg: string) => new AccountingError(msg, 400, 'VALIDATION_ERROR'),\r\n notFound: (msg: string) => new AccountingError(msg, 404, 'NOT_FOUND'),\r\n conflict: (msg: string) => new AccountingError(msg, 409, 'CONFLICT'),\r\n immutable: (msg: string) => new AccountingError(msg, 403, 'IMMUTABLE_ENTRY'),\r\n fiscal: (msg: string) => new AccountingError(msg, 400, 'FISCAL_ERROR'),\r\n} as const;\r\n"],"mappings":";;;;;;AAKA,IAAa,kBAAb,cAAqC,MAAM;CACzC;CACA;CAEA,YAAY,SAAiB,SAAS,KAAK,OAAO,oBAAoB;AACpE,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,OAAO;;;;AAKhB,MAAa,SAAS;CACpB,aAAa,QAAgB,IAAI,gBAAgB,KAAK,KAAK,mBAAmB;CAC9E,WAAW,QAAgB,IAAI,gBAAgB,KAAK,KAAK,YAAY;CACrE,WAAW,QAAgB,IAAI,gBAAgB,KAAK,KAAK,WAAW;CACpE,YAAY,QAAgB,IAAI,gBAAgB,KAAK,KAAK,kBAAkB;CAC5E,SAAS,QAAgB,IAAI,gBAAgB,KAAK,KAAK,eAAe;CACvE"}
@@ -1,2 +1,2 @@
1
- import { _ as PopulatedJournalEntry, a as exportToCsv, c as getHeaders, d as serializeCsv, f as CsvOptions, g as PopulatedAccount, h as FlatJournalRow, i as flattenJournalEntry, l as buildCsv, m as ExportFieldMap, n as quickbooksFieldMap, o as extractAllRows, p as ExportField, r as flattenJournalEntries, s as extractRow, t as universalFieldMap, u as escapeCell, v as PopulatedJournalItem } from "../universal-x33ZJODp.mjs";
2
- export { type CsvOptions, type ExportField, type ExportFieldMap, type FlatJournalRow, type PopulatedAccount, type PopulatedJournalEntry, type PopulatedJournalItem, buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };
1
+ import { _ as PopulatedJournalEntry, a as exportToCsv, c as getHeaders, d as serializeCsv, f as CsvOptions, g as PopulatedAccount, h as FlatJournalRow, i as flattenJournalEntry, l as buildCsv, m as ExportFieldMap, n as quickbooksFieldMap, o as extractAllRows, p as ExportField, r as flattenJournalEntries, s as extractRow, t as universalFieldMap, u as escapeCell, v as PopulatedJournalItem } from "../index-BPukb3L8.mjs";
2
+ export { CsvOptions, ExportField, ExportFieldMap, FlatJournalRow, PopulatedAccount, PopulatedJournalEntry, PopulatedJournalItem, buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };
@@ -1,3 +1,2 @@
1
- import { a as exportToCsv, c as getHeaders, d as serializeCsv, i as flattenJournalEntry, l as buildCsv, n as quickbooksFieldMap, o as extractAllRows, r as flattenJournalEntries, s as extractRow, t as universalFieldMap, u as escapeCell } from "../universal-CMfrZ2hG.mjs";
2
-
3
- export { buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };
1
+ import { a as exportToCsv, c as getHeaders, d as serializeCsv, i as flattenJournalEntry, l as buildCsv, n as quickbooksFieldMap, o as extractAllRows, r as flattenJournalEntries, s as extractRow, t as universalFieldMap, u as escapeCell } from "../exports-I5Xkq-9_.mjs";
2
+ export { buildCsv, escapeCell, exportToCsv, extractAllRows, extractRow, flattenJournalEntries, flattenJournalEntry, getHeaders, quickbooksFieldMap, serializeCsv, universalFieldMap };