@classytic/ledger 0.1.5 → 0.3.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 (70) hide show
  1. package/README.md +161 -64
  2. package/dist/{account.repository-Crf5DGO4.mjs → account.repository-BpkSd6q3.mjs} +190 -41
  3. package/dist/{categories-BNJBd4ze.mjs → categories-CclX7Q94.mjs} +0 -2
  4. package/dist/constants/index.d.mts +1 -1
  5. package/dist/constants/index.mjs +4 -5
  6. package/dist/{core-Cx0baosR.d.mts → core-8Xfnpn6g.d.mts} +1 -2
  7. package/dist/country/index.d.mts +2 -105
  8. package/dist/country/index.mjs +0 -2
  9. package/dist/{currencies-Bkn3FNkC.d.mts → currencies-4WAbFRlw.d.mts} +2 -3
  10. package/dist/{currencies-BBk3NwXn.mjs → currencies-W8kQAkm0.mjs} +0 -2
  11. package/dist/{idempotency.plugin-C6r8RI8d.mjs → date-lock.plugin-eYAJ9h_u.mjs} +50 -13
  12. package/dist/{engine-Cd73EOT6.d.mts → engine-Cn-9yerQ.d.mts} +38 -8
  13. package/dist/{errors-CeqRahE-.mjs → errors-B7yC-Jfw.mjs} +0 -2
  14. package/dist/exports/index.d.mts +2 -2
  15. package/dist/exports/index.mjs +2 -3
  16. package/dist/{universal-CMfrZ2hG.mjs → exports-I5Xkq-9_.mjs} +0 -7
  17. package/dist/{fiscal-close-DuXDgVvb.mjs → fiscal-close-B6LhQ10f.mjs} +742 -32
  18. package/dist/fiscal-period.schema-BMnlI9H5.d.mts +103 -0
  19. package/dist/{idempotency.plugin-BESs9YPD.d.mts → idempotency.plugin-B_CNsInz.d.mts} +19 -17
  20. package/dist/{universal-x33ZJODp.d.mts → index-BPukb3L8.d.mts} +1 -2
  21. package/dist/index-CxZqRaOU.d.mts +119 -0
  22. package/dist/index.d.mts +251 -29
  23. package/dist/index.mjs +124 -27
  24. package/dist/{journals-CI3Wb4EF.mjs → journals-oH-FK3g8.mjs} +0 -2
  25. package/dist/{logger-Cv6VVc4r.d.mts → logger-CbHWZl7v.d.mts} +1 -2
  26. package/dist/money.d.mts +1 -2
  27. package/dist/money.mjs +3 -3
  28. package/dist/plugins/index.d.mts +38 -2
  29. package/dist/plugins/index.mjs +57 -3
  30. package/dist/reconciliation.repository-CW4-8q90.d.mts +135 -0
  31. package/dist/{fiscal-period.schema-CbALaaKl.mjs → reconciliation.schema-BuetvZTd.mjs} +218 -30
  32. package/dist/reports/index.d.mts +2 -2
  33. package/dist/reports/index.mjs +2 -3
  34. package/dist/repositories/index.d.mts +2 -2
  35. package/dist/repositories/index.mjs +2 -3
  36. package/dist/revaluation-D9x0NE8w.d.mts +530 -0
  37. package/dist/schemas/index.d.mts +71 -2
  38. package/dist/schemas/index.mjs +2 -3
  39. package/dist/tenant-guard-Fm6AID_6.mjs +13 -0
  40. package/docs/reports.md +1 -1
  41. package/package.json +3 -3
  42. package/dist/account.repository-1C2sZvB2.d.mts +0 -29
  43. package/dist/account.repository-1C2sZvB2.d.mts.map +0 -1
  44. package/dist/account.repository-Crf5DGO4.mjs.map +0 -1
  45. package/dist/categories-BNJBd4ze.mjs.map +0 -1
  46. package/dist/core-Cx0baosR.d.mts.map +0 -1
  47. package/dist/country/index.d.mts.map +0 -1
  48. package/dist/country/index.mjs.map +0 -1
  49. package/dist/currencies-BBk3NwXn.mjs.map +0 -1
  50. package/dist/currencies-Bkn3FNkC.d.mts.map +0 -1
  51. package/dist/engine-Cd73EOT6.d.mts.map +0 -1
  52. package/dist/errors-CeqRahE-.mjs.map +0 -1
  53. package/dist/fiscal-close-CzUzpnMg.d.mts +0 -270
  54. package/dist/fiscal-close-CzUzpnMg.d.mts.map +0 -1
  55. package/dist/fiscal-close-DuXDgVvb.mjs.map +0 -1
  56. package/dist/fiscal-period.schema-CbALaaKl.mjs.map +0 -1
  57. package/dist/fiscal-period.schema-DI2scngu.d.mts +0 -38
  58. package/dist/fiscal-period.schema-DI2scngu.d.mts.map +0 -1
  59. package/dist/idempotency.plugin-BESs9YPD.d.mts.map +0 -1
  60. package/dist/idempotency.plugin-C6r8RI8d.mjs.map +0 -1
  61. package/dist/index.d.mts.map +0 -1
  62. package/dist/index.mjs.map +0 -1
  63. package/dist/journals-CI3Wb4EF.mjs.map +0 -1
  64. package/dist/logger-Cv6VVc4r.d.mts.map +0 -1
  65. package/dist/money.d.mts.map +0 -1
  66. package/dist/money.mjs.map +0 -1
  67. package/dist/session-Dh0s6zG4.mjs +0 -87
  68. package/dist/session-Dh0s6zG4.mjs.map +0 -1
  69. package/dist/universal-CMfrZ2hG.mjs.map +0 -1
  70. package/dist/universal-x33ZJODp.d.mts.map +0 -1
@@ -1 +0,0 @@
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":";;;;;AAiCA,MAAM,iBAAiB,IAAI,IAAI;CAAC;CAAW;CAAS;CAAU;CAAS;CAAQ;CAAc;CAAO;CAAK,CAAC;;;;;;;;;AA+B1G,SAAgB,wBAEd,YACA,mBACA,UACA,YACM;;;;;AAKN,YAAW,OAAO,eAAgB,IAAa,OAAiB,UAAuB,EAAE,EAAE;AACzF,MAAI,YAAY,gBAAgB,CAAC,QAAQ,QACvC,OAAM,OAAO,WAAW,2CAA2C;AAErE,kBAAgB,UAAU,MAAM;EAChC,MAAM,QAAiC,EAAE,KAAK,IAAI;AAClD,MAAI,YAAY,SAAS,KAAM,OAAM,YAAY;EAEjD,MAAM,QAAS,MAAM,kBAAkB,QAAQ,MAAM,CAClD,SAAS,uBAAuB,CAChC,QAAQ,QAAQ,WAAW,KAAK;AAEnC,MAAI,CAAC,MACH,OAAM,OAAO,SAAS,kBAAkB;AAI1C,MAAI,MAAM,kBAAkB,MAAM,UAAU,SAC1C,QAAO;AAGT,MAAI,MAAM,UAAU,QAClB,OAAM,OAAO,WAAW,mCAAmC;AAI7D,MAAI,YAAY,iBACd;OAAI,CAAC,MAAM,cAAc,CAAC,MAAM,WAC9B,OAAM,OAAO,WAAW,sFAAsF;;AAKlH,MAAI,CAAC,MAAM,gBAAgB,MAAM,aAAa,SAAS,EACrD,OAAM,OAAO,WAAW,mDAAmD;EAI7E,MAAM,UAAU,MAAM,aAAa,QAAQ,MAAmB,CAAC,EAAE,WAAW,EAAE,YAAY,GAAG;AAC7F,MAAI,QAAQ,SAAS,EACnB,OAAM,OAAO,WAAW,GAAG,QAAQ,OAAO,6BAA6B;AAIzE,MAAI,YAAY,SAAS,MAAM;GAC7B,MAAM,cAAc,MAAM,aAAa,QAAQ,MAAmB;IAChE,MAAM,OAAO,EAAE;AACf,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,WAAO,OAAO,KAAK,UAAU,KAAK,OAAO,MAAM;KAC/C;AACF,OAAI,YAAY,SAAS,EACvB,OAAM,OAAO,WACX,GAAG,YAAY,OAAO,uDACvB;;EAKL,MAAM,SAAS,MAAM,aAAa,QAAQ,OAAoB,EAAE,SAAS,OAAO,MAAM,EAAE,UAAU,OAAO,EAAE;AAC3G,MAAI,OAAO,SAAS,EAClB,OAAM,OAAO,WAAW,GAAG,OAAO,OAAO,6CAA6C;EAIxF,MAAM,UAAU,MAAM,aAAa,QAAQ,OAAoB,EAAE,SAAS,KAAK,MAAM,EAAE,UAAU,KAAK,EAAE;AACxG,MAAI,QAAQ,SAAS,EACnB,OAAM,OAAO,WACX,GAAG,QAAQ,OAAO,uFACnB;EAIH,MAAM,aAAa,MAAM,aAAa,QAAQ,GAAW,MAAmB,KAAK,EAAE,SAAS,IAAI,EAAE;EAClG,MAAM,cAAc,MAAM,aAAa,QAAQ,GAAW,MAAmB,KAAK,EAAE,UAAU,IAAI,EAAE;AACpG,MAAI,eAAe,YACjB,OAAM,OAAO,WACX,iCAAiC,WAAW,YAAY,cACzD;AAGH,QAAM,QAAQ;AACd,QAAM,iCAAiB,IAAI,MAAM;AACjC,MAAI,QAAQ,QACV,OAAM,WAAW,QAAQ;AAE3B,QAAM,MAAM,KAAK,EAAE,SAAS,QAAQ,SAAS,CAAC;AAE9C,SAAO;;;;;;;AAQT,YAAW,SAAS,eAAgB,IAAa,OAAiB,UAAuB,EAAE,EAAE;AAC3F,MAAI,YAAY,UACd,OAAM,OAAO,UAAU,8EAA8E;AAEvG,MAAI,YAAY,gBAAgB,CAAC,QAAQ,QACvC,OAAM,OAAO,WAAW,6CAA6C;AAEvE,kBAAgB,UAAU,MAAM;EAChC,MAAM,QAAiC,EAAE,KAAK,IAAI;AAClD,MAAI,YAAY,SAAS,KAAM,OAAM,YAAY;EAEjD,MAAM,QAAS,MAAM,kBAAkB,QAAQ,MAAM,CAClD,QAAQ,QAAQ,WAAW,KAAK;AAEnC,MAAI,CAAC,MACH,OAAM,OAAO,SAAS,kBAAkB;AAE1C,MAAI,MAAM,UAAU,SAClB,OAAM,OAAO,WAAW,sCAAsC;AAGhE,QAAM,QAAQ;AACd,QAAM,iCAAiB,IAAI,MAAM;AAEjC,MAAI,MAAM,UAAU;AAClB,SAAM,WAAW;AACjB,SAAM,aAAa;;AAErB,QAAM,MAAM,KAAK,EAAE,SAAS,QAAQ,SAAS,CAAC;AAE9C,SAAO;;;;;;;AAQT,YAAW,UAAU,eAAgB,IAAa,OAAiB,UAAuB,EAAE,EAAE;AAC5F,MAAI,YAAY,gBAAgB,CAAC,QAAQ,QACvC,OAAM,OAAO,WAAW,8CAA8C;AAExE,kBAAgB,UAAU,MAAM;EAChC,MAAM,QAAiC,EAAE,KAAK,IAAI;AAClD,MAAI,YAAY,SAAS,KAAM,OAAM,YAAY;EAEjD,MAAM,QAAS,MAAM,kBAAkB,QAAQ,MAAM,CAClD,QAAQ,QAAQ,WAAW,KAAK;AAEnC,MAAI,CAAC,MACH,OAAM,OAAO,SAAS,kBAAkB;AAE1C,MAAI,MAAM,UAAU,QAClB,OAAM,OAAO,WAAW,qCAAqC;AAG/D,QAAM,QAAQ;AACd,QAAM,iCAAiB,IAAI,MAAM;AACjC,QAAM,MAAM,KAAK,EAAE,SAAS,QAAQ,SAAS,CAAC;AAE9C,SAAO;;;;;;AAOT,YAAW,YAAY,eAAgB,IAAa,OAAiB,UAAuB,EAAE,EAAE;AAC9F,kBAAgB,UAAU,MAAM;EAChC,MAAM,QAAiC,EAAE,KAAK,IAAI;AAClD,MAAI,YAAY,SAAS,KAAM,OAAM,YAAY;EAEjD,MAAM,QAAS,MAAM,kBAAkB,QAAQ,MAAM,CAClD,QAAQ,QAAQ,WAAW,KAAK;AAEnC,MAAI,CAAC,MACH,OAAM,OAAO,SAAS,kBAAkB;EAG1C,MAAM,gBAAyC;GAC7C,aAAa,MAAM;GACnB,OAAO;GACP,sBAAM,IAAI,MAAM;GAChB,OAAO,MAAM,QAAQ,WAAW,MAAM,UAAU;GAChD,cAAc,MAAM,aAAa,KAAK,SAA+B;IACnE,MAAM,YAAY,OAAO,KAAK,YAAY,YAAY,KAAK,YAAY,OAClE,KAAK,QAAoC,MAC1C,KAAK;IAGT,MAAM,QAAiC,EAAE;AACzC,SAAK,MAAM,OAAO,OAAO,KAAK,KAAK,CACjC,KAAI,CAAC,eAAe,IAAI,IAAI,CAAE,OAAM,OAAO,KAAK;AAGlD,WAAO;KACL,GAAG;KACH,SAAS;KACT,OAAO,KAAK,SAAS;KACrB,QAAQ,KAAK,UAAU;KACvB,OAAO,KAAK;KACZ,sBAAM,IAAI,MAAM;KAChB,YAAY,KAAK,cAAc,EAAE;KAClC;KACD;GACH;AAGD,MAAI,YAAY,MAAM,aAAa,KACjC,eAAc,YAAY,MAAM;AAIlC,SADmB,MAAM,WAAW,OAAO,eAAe,QAAQ,UAAU,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE,CAAC;;;;;;;;;;;;;AAehH,YAAW,UAAU,eAAgB,IAAa,OAAiB,UAA0B,EAAE,EAAE;AAC/F,MAAI,YAAY,gBAAgB,CAAC,QAAQ,QACvC,OAAM,OAAO,WAAW,8CAA8C;AAExE,kBAAgB,UAAU,MAAM;EAChC,MAAM,EAAE,SAAS,eAAe,MAAM,eACpC,kBAAkB,IAClB,QAAQ,QACT;EACD,IAAI,UAAU;AAEd,MAAI;GACF,MAAM,QAAiC,EAAE,KAAK,IAAI;AAClD,OAAI,YAAY,SAAS,KAAM,OAAM,YAAY;GAEjD,MAAM,QAAS,MAAM,kBAAkB,QAAQ,MAAM,CAClD,SAAS,uBAAuB,CAChC,QAAQ,WAAW,KAAK;AAE3B,OAAI,CAAC,MACH,OAAM,OAAO,SAAS,kBAAkB;AAE1C,OAAI,MAAM,UAAU,SAClB,OAAM,OAAO,WAAW,sCAAsC;AAEhE,OAAI,MAAM,SACR,OAAM,OAAO,WAAW,kCAAkC;GAI5D,MAAM,gBAAgB,MAAM,aAAa,KAAK,SAA+B;IAC3E,MAAM,YAAY,OAAO,KAAK,YAAY,YAAY,KAAK,YAAY,OAClE,KAAK,QAAoC,MAC1C,KAAK;IAGT,MAAM,QAAiC,EAAE;AACzC,SAAK,MAAM,OAAO,OAAO,KAAK,KAAK,CACjC,KAAI,CAAC,eAAe,IAAI,IAAI,CAAE,OAAM,OAAO,KAAK;AAGlD,WAAO;KACL,GAAG;KACH,SAAS;KACT,OAAO,KAAK,UAAU;KACtB,QAAQ,KAAK,SAAS;KACtB,OAAO,KAAK,QAAQ,aAAa,KAAK,UAAU;KAChD,MAAM,KAAK;KACX,YAAY,KAAK,cAAc,EAAE;KAClC;KACD;GAEF,MAAM,aAAa,cAAc,QAAQ,GAAW,MAAyB,IAAI,EAAE,OAAO,EAAE;GAC5F,MAAM,cAAc,cAAc,QAAQ,GAAW,MAA0B,IAAI,EAAE,QAAQ,EAAE;GAG/F,MAAM,eAAwC;IAC5C,aAAa,MAAM,eAAe;IAClC,OAAO;IACP,MAAM,QAAQ,gCAAgB,IAAI,MAAM;IACxC,OAAO,eAAe,MAAM,mBAAmB,MAAM;IACrD,cAAc;IACd;IACA;IACA,YAAY,MAAM;IAClB,gCAAgB,IAAI,MAAM;IAC3B;AAGD,OAAI,YAAY,MAAM,aAAa,KACjC,cAAa,YAAY,MAAM;AAIjC,OAAI,QAAQ,QACV,cAAa,WAAW,QAAQ;GAIlC,MAAM,gBAAgB,MAAM,WAAW,OACrC,cACA,UAAU,EAAE,SAAS,GAAG,EAAE,CAC3B;AAGD,SAAM,WAAW;AACjB,SAAM,aAAa,cAAc;AACjC,OAAI,QAAQ,QACV,OAAM,iBAAiB,QAAQ;AAEjC,SAAM,MAAM,KAAK,EAAE,SAAS,CAAC;AAE7B,aAAU;AACV,UAAO;IAAE,UAAU;IAAO,UAAU;IAAe;YAC3C;AACR,SAAM,gBAAgB,SAAS,YAAY,QAAQ;;;;;;;;;;;;;;;;AC9WzD,SAAgB,mBAEd,YACA,cACA,SACA,UACM;AAEN,YAAW,GAAG,kBAAkB,QAAiC;EAE/D,MAAM,OADO,IAAI,MACE;AACnB,MAAI,QAAQ,CAAC,QAAQ,iBAAiB,KAAK,CACzC,OAAM,OAAO,WACX,oCAAoC,KAAK,0EAC1C;GAEH;;;;AAKF,YAAW,eAAe,eAAgB,OAAgB,UAAuB,EAAE,EAAE;AACnF,kBAAgB,UAAU,MAAM;EAChC,MAAM,eAAe,QAAQ,wBAAwB;EACrD,MAAM,SAAkC,EAAE;AAC1C,MAAI,YAAY,SAAS,KAAM,QAAO,YAAY;EAElD,MAAM,WAAW,MAAM,aAAa,KAAK,OAAO,CAAC,OAAO,gBAAgB,CAAC,MAAM;EAC/E,MAAM,kBAAkB,IAAI,IAAI,SAAS,KAAI,MAAK,EAAE,cAAc,CAAC;EAEnE,MAAM,WAAW,aACd,QAAO,OAAM,CAAC,gBAAgB,IAAI,GAAG,KAAK,CAAC,CAC3C,KAAI,OAAM;GACT,MAAM,MAA+B;IACnC,iBAAiB,GAAG;IACpB,eAAe,GAAG;IAClB,MAAM,GAAG;IACV;AACD,OAAI,YAAY,SAAS,KAAM,KAAI,YAAY;AAC/C,UAAO;IACP;AAEJ,MAAI,SAAS,WAAW,EAAG,QAAO;GAAE,SAAS;GAAG,SAAS,gBAAgB;GAAM;AAE/E,MAAI;AAOF,UAAO;IAAE,UAJQ,MAAM,aAAa,WAAW,UAAU;KACvD,SAAS,QAAQ,WAAW;KAC5B,SAAS;KACV,CAAC,EACyB;IAAQ,SAAS,gBAAgB;IAAM;WAC3D,KAAc;GAErB,MAAM,YAAY;AAClB,OAAI,UAAU,SAAS,QAAS,UAAU,aAAa;IAErD,MAAM,eAAe,UAAU,gBAAgB,EAAE;AACjD,WAAO;KACL,SAAS,aAAa;KACtB,SAAS,gBAAgB,QAAQ,SAAS,SAAS,aAAa;KACjE;;AAEH,SAAM;;;;;;;;;;AAWV,YAAW,aAAa,eACtB,UACA,OACA;AACA,kBAAgB,UAAU,MAAM;EAChC,MAAM,UAIF;GAAE,SAAS,EAAE;GAAE,SAAS,EAAE;GAAE,QAAQ,EAAE;GAAE;EAG5C,MAAM,gBAAiJ,EAAE;AAEzJ,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GACxC,MAAM,EAAE,iBAAiB,eAAe,MAAM,SAAS,MAAM,gBAAgB,UAAU,SAAS;AAEhG,OAAI,CAAC,iBAAiB;AACpB,YAAQ,OAAO,KAAK;KAAE,OAAO;KAAG,QAAQ;KAA+B,CAAC;AACxE;;GAGF,MAAM,KAAK,QAAQ,eAAe,gBAAgB;AAClD,OAAI,CAAC,IAAI;AACP,YAAQ,OAAO,KAAK;KAAE,OAAO;KAAG;KAAiB,QAAQ;KAA6B,CAAC;AACvF;;AAGF,OAAI,CAAC,QAAQ,iBAAiB,gBAAgB,EAAE;AAC9C,YAAQ,OAAO,KAAK;KAClB,OAAO;KACP;KACA,QAAQ,0BAA2B,GAA0C,UAAU,UAAU,QAAQ;KAC1G,CAAC;AACF;;GAGF,MAAM,iBAAiB,iBAAiB;GACxC,MAAM,eAAe,QAAS,GAA0C,QAAkB;AAC1F,iBAAc,KAAK;IAAE,OAAO;IAAG;IAAiB,eAAe;IAAgB,MAAM;IAAc,QAAQ,QAAQ,OAAO;IAAE,eAAe,QAAQ,cAAc;IAAE,CAAC;;AAGtK,MAAI,cAAc,WAAW,EAC3B,QAAO;GACL,SAAS;IAAE,OAAO,SAAS;IAAQ,SAAS;IAAG,SAAS,QAAQ,QAAQ;IAAQ,QAAQ,QAAQ,OAAO;IAAQ;GAC/G,GAAG;GACJ;EAKH,MAAM,eAAwC,EAAE,eAAe,EAAE,KAD1C,cAAc,KAAI,MAAK,EAAE,cAAc,EACwB,EAAE;AACxF,MAAI,YAAY,SAAS,KAAM,cAAa,YAAY;EAExD,MAAM,eAAe,MAAM,aAAa,KAAK,aAAa,CACvD,OAAO,gBAAgB,CACvB,MAAM;EACT,MAAM,kBAAkB,IAAI,IAAI,aAAa,KAAI,MAAK,EAAE,cAAwB,CAAC;EAGjF,MAAM,WAA4I,EAAE;AACpJ,OAAK,MAAM,QAAQ,cACjB,KAAI,gBAAgB,IAAI,KAAK,cAAc,CACzC,SAAQ,QAAQ,KAAK;GAAE,OAAO,KAAK;GAAO,iBAAiB,KAAK;GAAiB,QAAQ;GAAkB,CAAC;MAE5G,UAAS,KAAK,KAAK;AAIvB,MAAI,SAAS,SAAS,GAAG;GACvB,MAAM,OAAO,SAAS,KAAI,SAAQ;IAChC,MAAM,MAA+B;KACnC,iBAAiB,KAAK;KACtB,eAAe,KAAK;KACpB,MAAM,KAAK;KACX,QAAQ,KAAK;KACb,eAAe,KAAK;KACrB;AACD,QAAI,YAAY,SAAS,KAAM,KAAI,YAAY;AAC/C,WAAO;KACP;AAEF,OAAI;IAEF,MAAM,WAAW,MAAM,aAAa,WAAW,MAAM,EAAE,SAAS,OAAO,CAAC;AACxE,YAAQ,UAAU,SAAS,KAAK,MAAM,SAAS;KAC7C,iBAAiB,KAAK;KACtB,QAAQ,KAAK;KACb,eAAe,KAAK;KACpB,KAAM,SAAS,KAA4C;KAC5D,EAAE;YACI,KAAc;IAErB,MAAM,YAAY;AAClB,QAAI,UAAU,SAAS,QAAS,UAAU,aAAa;KAErD,MAAM,eAAe,UAAU,gBAAgB,EAAE;KACjD,MAAM,kBAAkB,IAAI,IAC1B,aAAa,KAAK,MAA+B,EAAE,cAAwB,CAC5E;AACD,UAAK,MAAM,QAAQ,SACjB,KAAI,gBAAgB,IAAI,KAAK,cAAc,EAAE;MAC3C,MAAM,OAAO,aAAa,MACvB,MAA+B,EAAE,kBAAkB,KAAK,cAC1D;AACD,cAAQ,QAAQ,KAAK;OACnB,iBAAiB,KAAK;OACtB,QAAQ,KAAK;OACb,eAAe,KAAK;OACpB,KAAK,MAAM;OACZ,CAAC;WAEF,SAAQ,QAAQ,KAAK;MACnB,OAAO,KAAK;MACZ,iBAAiB,KAAK;MACtB,QAAQ;MACT,CAAC;UAIN,OAAM;;;AAKZ,SAAO;GACL,SAAS;IACP,OAAO,SAAS;IAChB,SAAS,QAAQ,QAAQ;IACzB,SAAS,QAAQ,QAAQ;IACzB,QAAQ,QAAQ,OAAO;IACxB;GACD,GAAG;GACJ"}
@@ -1 +0,0 @@
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 +0,0 @@
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 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/country/index.ts"],"mappings":";;;UAgBiB,OAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA;EAAA,SACA,QAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA;AAAA;AAAA,UAGM,gBAAA;EAAA,UACL,MAAA;AAAA;AAAA,UAKK,aAAA;EAAA,SACN,IAAA;EAAA,SACA,IAAA;EAAA,SACA,WAAA;EAAA,SACA,IAAA;EAAA,SACA,SAAA,IAAa,IAAA,EAAM,MAAA;EAAA,SACnB,OAAA;AAAA;AAAA,UAGM,iBAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA,EAAO,QAAA,CAAS,MAAA,kBAAwB,aAAA;EACjD,SAAA,CAAU,SAAA,EAAW,MAAA,2BAAiC,UAAA,GAAa,MAAA,4BAAkC,MAAA;EACrG,SAAA,CAAU,UAAA,EAAY,MAAA,4BAAkC,MAAA;AAAA;AAAA,UAKzC,WAAA;EAPU;EAAA,SAShB,IAAA;EARY;EAAA,SAUZ,IAAA;EAV4F;EAAA,SAY5F,eAAA;EAX+C;;;;EAAA,SAiB/C,YAAA,WAAuB,WAAA;EAnBhB;EAAA,SAsBP,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,OAAA;EAtBM;EAAA,SAyBxC,gBAAA,EAAkB,gBAAA;EAxBN;EAAA,SA2BZ,OAAA;EA3B0D;EAAA,SA8B1D,SAAA,GAAY,iBAAA;EA9BgF;EAAA,SAmC5F,oBAAA;EAlCa;EAAA,SAoCb,uBAAA;EApC+C;EAAA,SAsC/C,aAAA;EAtCqD;EAAA,SAwCrD,YAAA;IAAA,SACE,MAAA;IAAA,SACA,WAAA;IAAA,SACA,MAAA;IAAA,SACA,OAAA;IAAA,SACA,QAAA;EAAA;EAtBgB;EA4B3B,sBAAA,aAAmC,WAAA;EAAA;EAGnC,cAAA,CAAe,IAAA,WAAe,WAAA;EASQ;EANtC,kBAAA,CAAmB,IAAA;EASwB;EAN3C,gBAAA,CAAiB,IAAA;EArDR;EAwDT,oBAAA,CAAqB,MAAA,WAAiB,OAAA;EApD7B;EAuDT,mBAAA,aAAgC,WAAA;AAAA;AAAA,UAKjB,gBAAA;EACf,IAAA;EACA,IAAA;EACA,eAAA;EACA,YAAA,WAAuB,WAAA;EACvB,QAAA,EAAU,QAAA,CAAS,MAAA,SAAe,OAAA;EAClC,gBAAA,EAAkB,gBAAA;EAClB,OAAA;EACA,SAAA,GAAY,iBAAA;EACZ,oBAAA;EACA,uBAAA;EACA,aAAA;EACA,YAAA;IAAA,SACW,MAAA;IAAA,SACA,WAAA;IAAA,SACA,MAAA;IAAA,SACA,OAAA;IAAA,SACA,QAAA;EAAA;AAAA;;;;iBAOG,iBAAA,CAAkB,KAAA,EAAO,gBAAA,GAAmB,WAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"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 +0,0 @@
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 +0,0 @@
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 +0,0 @@
1
- {"version":3,"file":"engine-Cd73EOT6.d.mts","names":[],"sources":["../src/types/engine.ts"],"mappings":";;;;;UAaiB,iBAAA;EAUa;EAR5B,QAAA;EAYc;EAVd,MAAA;AAAA;;UAMe,aAAA;EAMK;EAJpB,OAAA;EAEA;EAAA,WAAA,GAAc,MAAA;EAEd;EAAA,YAAA,GAAe,KAAA;IAAQ,MAAA,EAAQ,MAAA;IAAwB,OAAA,GAAU,MAAA;EAAA;AAAA;;UAIlD,oBAAA,SAA6B,aAAA;EAA7B;EAEf,aAAA;;EAEA,UAAA;EAJ4C;EAM5C,eAAA,GAAkB,MAAA;AAAA;;UAMH,WAAA;EANS;EAQxB,UAAA;AAAA;;UAMe,gBAAA;EANf;EAQA,SAAA;EAFe;EAIf,YAAA;;EAEA,eAAA;AAAA;;UAMe,sBAAA;EANA;EAQf,OAAA,EAAS,WAAA;EAFM;EAIf,QAAA;;EAEA,WAAA,GAAc,iBAAA;EAAA;EAEd,oBAAA;EAQQ;EANR,oBAAA;EAU6B;EAR7B,uBAAA;EAVA;EAYA,MAAA,GAAS,MAAA;EAVT;EAYA,KAAA,GAAQ,WAAA;EAVM;EAYd,WAAA;EARA;EAUA,UAAA,GAAa,gBAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"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,270 +0,0 @@
1
- import { c as DateRange } from "./core-Cx0baosR.mjs";
2
- import { CountryPack } from "./country/index.mjs";
3
- import { t as Logger } from "./logger-Cv6VVc4r.mjs";
4
- import { ClientSession, Model } from "mongoose";
5
-
6
- //#region src/types/report.d.ts
7
- interface ReportMetadata {
8
- businessName?: string;
9
- generatedAt: string;
10
- }
11
- interface ReportAccount {
12
- id: unknown;
13
- name: string;
14
- code: string;
15
- balance: number;
16
- isTotal?: boolean;
17
- isVirtualTotal?: boolean;
18
- isCalculated?: boolean;
19
- }
20
- interface ReportGroup {
21
- name: string;
22
- total: number;
23
- accounts: ReportAccount[];
24
- }
25
- interface ReportCategory {
26
- name: string;
27
- total: number;
28
- groups: ReportGroup[];
29
- }
30
- interface TrialBalanceRow {
31
- account: unknown;
32
- initial: {
33
- debit: number;
34
- credit: number;
35
- };
36
- current: {
37
- debit: number;
38
- credit: number;
39
- };
40
- ending: {
41
- debit: number;
42
- credit: number;
43
- };
44
- }
45
- interface TrialBalanceReport {
46
- rows: TrialBalanceRow[];
47
- period: DateRange;
48
- }
49
- interface BalanceSheetReport {
50
- metadata: ReportMetadata & {
51
- asOfDate: string;
52
- displayDate: string;
53
- };
54
- assets: ReportCategory;
55
- liabilities: ReportCategory;
56
- equity: ReportCategory;
57
- summary: {
58
- totalAssets: number;
59
- totalLiabilities: number;
60
- totalEquity: number;
61
- liabilitiesAndEquity: number;
62
- difference: number;
63
- isBalanced: boolean;
64
- };
65
- }
66
- interface IncomeStatementReport {
67
- metadata: ReportMetadata & {
68
- periodStart: string;
69
- periodEnd: string;
70
- displayPeriod: string;
71
- };
72
- revenue: ReportCategory;
73
- costOfSales: number;
74
- grossProfit: number;
75
- expenses: ReportCategory;
76
- operatingIncome: number;
77
- netIncome: number;
78
- }
79
- interface LedgerEntry {
80
- date: Date;
81
- referenceNumber: string;
82
- label: string;
83
- debit: number;
84
- credit: number;
85
- runningBalance: number;
86
- }
87
- interface GeneralLedgerAccount {
88
- account: unknown;
89
- openingBalance: number;
90
- entries: LedgerEntry[];
91
- closingBalance: number;
92
- }
93
- interface GeneralLedgerReport {
94
- accounts: GeneralLedgerAccount[];
95
- period: DateRange;
96
- }
97
- interface CashFlowSection {
98
- total: number;
99
- accounts: Array<{
100
- name: string;
101
- code: string;
102
- amount: number;
103
- }>;
104
- }
105
- interface CashFlowReport {
106
- metadata: ReportMetadata & {
107
- periodStart: string;
108
- periodEnd: string;
109
- displayPeriod: string;
110
- };
111
- operating: CashFlowSection;
112
- investing: CashFlowSection;
113
- financing: CashFlowSection;
114
- netCashFlow: number;
115
- }
116
- interface TaxAccountBalance {
117
- code: string;
118
- name: string;
119
- balance: number;
120
- taxMetadata?: unknown;
121
- }
122
- interface TaxReturnSummary {
123
- totalSales: number;
124
- gstHstCollected: number;
125
- inputTaxCredits: number;
126
- netTax: number;
127
- finalAmount: number;
128
- isRefund: boolean;
129
- refundAmount: number;
130
- paymentAmount: number;
131
- }
132
- interface TaxReport {
133
- period: {
134
- startDate: string;
135
- endDate: string;
136
- province: string;
137
- };
138
- accountBalances: {
139
- collected: Record<string, TaxAccountBalance>;
140
- itc: Record<string, TaxAccountBalance>;
141
- instalments: Record<string, TaxAccountBalance>;
142
- };
143
- craLines: Record<string | number, number>;
144
- summary: TaxReturnSummary;
145
- calculatedAt: string;
146
- }
147
- //#endregion
148
- //#region src/reports/trial-balance.d.ts
149
- interface TrialBalanceOptions {
150
- AccountModel: Model<unknown>;
151
- JournalEntryModel: Model<unknown>;
152
- country: CountryPack;
153
- orgField?: string;
154
- fiscalYearStartMonth?: number;
155
- }
156
- declare function generateTrialBalance(opts: TrialBalanceOptions, params: {
157
- organizationId?: unknown;
158
- dateOption: 'month' | 'quarter' | 'year' | 'custom';
159
- dateValue: unknown;
160
- accountId?: string;
161
- filters?: Record<string, unknown>;
162
- }): Promise<TrialBalanceReport>;
163
- //#endregion
164
- //#region src/reports/balance-sheet.d.ts
165
- interface BalanceSheetOptions {
166
- AccountModel: Model<unknown>;
167
- JournalEntryModel: Model<unknown>;
168
- country: CountryPack;
169
- orgField?: string;
170
- fiscalYearStartMonth?: number;
171
- /** Display code for prior retained earnings (default: '3660') */
172
- retainedEarningsCode?: string;
173
- /** Display code for current year net income (default: '3680') */
174
- currentYearEarningsCode?: string;
175
- }
176
- declare function generateBalanceSheet(opts: BalanceSheetOptions, params: {
177
- organizationId?: unknown;
178
- dateOption: 'month' | 'quarter' | 'year' | 'custom';
179
- dateValue: unknown;
180
- businessName?: string;
181
- filters?: Record<string, unknown>;
182
- }): Promise<BalanceSheetReport>;
183
- //#endregion
184
- //#region src/reports/income-statement.d.ts
185
- interface IncomeStatementOptions {
186
- AccountModel: Model<unknown>;
187
- JournalEntryModel: Model<unknown>;
188
- country: CountryPack;
189
- orgField?: string;
190
- }
191
- declare function generateIncomeStatement(opts: IncomeStatementOptions, params: {
192
- organizationId?: unknown;
193
- dateOption: 'month' | 'quarter' | 'year' | 'custom';
194
- dateValue: unknown;
195
- businessName?: string;
196
- filters?: Record<string, unknown>;
197
- }): Promise<IncomeStatementReport>;
198
- //#endregion
199
- //#region src/reports/general-ledger.d.ts
200
- interface GeneralLedgerOptions {
201
- AccountModel: Model<unknown>;
202
- JournalEntryModel: Model<unknown>;
203
- country: CountryPack;
204
- orgField?: string;
205
- fiscalYearStartMonth?: number;
206
- }
207
- declare function generateGeneralLedger(opts: GeneralLedgerOptions, params: {
208
- organizationId?: unknown;
209
- dateOption: 'month' | 'quarter' | 'year' | 'custom';
210
- dateValue: unknown;
211
- accountId?: string;
212
- filters?: Record<string, unknown>;
213
- }): Promise<GeneralLedgerReport>;
214
- //#endregion
215
- //#region src/reports/cash-flow.d.ts
216
- interface CashFlowOptions {
217
- AccountModel: Model<unknown>;
218
- JournalEntryModel: Model<unknown>;
219
- country: CountryPack;
220
- orgField?: string;
221
- }
222
- declare function generateCashFlow(opts: CashFlowOptions, params: {
223
- organizationId?: unknown;
224
- dateOption: 'month' | 'quarter' | 'year' | 'custom';
225
- dateValue: unknown;
226
- businessName?: string;
227
- filters?: Record<string, unknown>;
228
- }): Promise<CashFlowReport>;
229
- //#endregion
230
- //#region src/reports/fiscal-close.d.ts
231
- interface FiscalCloseOptions {
232
- AccountModel: Model<unknown>;
233
- JournalEntryModel: Model<unknown>;
234
- FiscalPeriodModel: Model<unknown>;
235
- country: CountryPack;
236
- orgField?: string;
237
- retainedEarningsCode?: string;
238
- logger?: Logger;
239
- }
240
- interface FiscalCloseResult {
241
- periodId: unknown;
242
- netIncome: number;
243
- closingEntryId: unknown | null;
244
- accountsClosed: number;
245
- closedAt: Date;
246
- }
247
- declare function closeFiscalPeriod(opts: FiscalCloseOptions, params: {
248
- periodId: unknown;
249
- organizationId?: unknown;
250
- closedBy?: string;
251
- session?: ClientSession;
252
- }): Promise<FiscalCloseResult>;
253
- interface FiscalReopenResult {
254
- periodId: unknown;
255
- deletedEntryId: unknown | null;
256
- reopenedAt: Date;
257
- }
258
- declare function reopenFiscalPeriod(opts: Pick<FiscalCloseOptions, 'JournalEntryModel' | 'FiscalPeriodModel'> & {
259
- orgField?: string;
260
- logger?: Logger; /** Any model on the same connection — used to start sessions */
261
- AccountModel?: Model<unknown>;
262
- }, params: {
263
- periodId: unknown;
264
- organizationId?: unknown;
265
- reopenedBy?: string;
266
- session?: ClientSession;
267
- }): Promise<FiscalReopenResult>;
268
- //#endregion
269
- export { ReportAccount as C, TaxReturnSummary as D, TaxReport as E, TrialBalanceReport as O, LedgerEntry as S, ReportGroup as T, CashFlowReport as _, reopenFiscalPeriod as a, GeneralLedgerReport as b, GeneralLedgerOptions as c, generateIncomeStatement as d, BalanceSheetOptions as f, BalanceSheetReport as g, generateTrialBalance as h, closeFiscalPeriod as i, TrialBalanceRow as k, generateGeneralLedger as l, TrialBalanceOptions as m, FiscalCloseResult as n, CashFlowOptions as o, generateBalanceSheet as p, FiscalReopenResult as r, generateCashFlow as s, FiscalCloseOptions as t, IncomeStatementOptions as u, CashFlowSection as v, ReportCategory as w, IncomeStatementReport as x, GeneralLedgerAccount as y };
270
- //# sourceMappingURL=fiscal-close-CzUzpnMg.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fiscal-close-CzUzpnMg.d.mts","names":[],"sources":["../src/types/report.ts","../src/reports/trial-balance.ts","../src/reports/balance-sheet.ts","../src/reports/income-statement.ts","../src/reports/general-ledger.ts","../src/reports/cash-flow.ts","../src/reports/fiscal-close.ts"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fiscal-close-DuXDgVvb.mjs","names":[],"sources":["../src/utils/date-range.ts","../src/utils/filter-builder.ts","../src/reports/trial-balance.ts","../src/utils/account-helpers.ts","../src/reports/balance-sheet.ts","../src/reports/income-statement.ts","../src/reports/general-ledger.ts","../src/reports/cash-flow.ts","../src/reports/fiscal-close.ts"],"sourcesContent":["/**\r\n * Date Range Utility — Compute period boundaries for reports.\r\n */\r\n\r\nimport type { DateOption, DateRange, QuarterValue, CustomDateRange } from '../types/core.js';\r\n\r\n/**\r\n * Compute start/end dates from a date option + value.\r\n *\r\n * Examples:\r\n * getDateRange('month', '2025-03') → Mar 1 – Mar 31\r\n * getDateRange('quarter', { quarter: 2, year: 2025 }) → Apr 1 – Jun 30\r\n * getDateRange('year', 2025) → Jan 1 – Dec 31\r\n * getDateRange('custom', { startDate, endDate })\r\n */\r\nexport function getDateRange(option: DateOption, value: unknown): DateRange {\r\n switch (option) {\r\n case 'month': {\r\n // Parse 'YYYY-MM' strings explicitly to avoid UTC-vs-local timezone shift\r\n let year: number;\r\n let month: number;\r\n const strVal = String(value);\r\n const match = strVal.match(/^(\\d{4})-(\\d{1,2})$/);\r\n if (match) {\r\n year = parseInt(match[1], 10);\r\n month = parseInt(match[2], 10) - 1; // 0-indexed\r\n } else {\r\n const date = new Date(value as string | number | Date);\r\n year = date.getFullYear();\r\n month = date.getMonth();\r\n }\r\n const startDate = new Date(year, month, 1);\r\n const endDate = new Date(year, month + 1, 0, 23, 59, 59, 999);\r\n return { startDate, endDate };\r\n }\r\n\r\n case 'quarter': {\r\n const { quarter, year } = value as QuarterValue;\r\n const startMonth = (quarter - 1) * 3;\r\n const startDate = new Date(year, startMonth, 1);\r\n const endDate = new Date(year, startMonth + 3, 0, 23, 59, 59, 999);\r\n return { startDate, endDate };\r\n }\r\n\r\n case 'year': {\r\n const year = typeof value === 'number' ? value : parseInt(String(value), 10);\r\n const startDate = new Date(year, 0, 1);\r\n const endDate = new Date(year, 11, 31, 23, 59, 59, 999);\r\n return { startDate, endDate };\r\n }\r\n\r\n case 'custom': {\r\n const { startDate, endDate } = value as CustomDateRange;\r\n const end = new Date(endDate);\r\n // Normalize end date to end-of-day if time is midnight (00:00:00)\r\n if (end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 && end.getMilliseconds() === 0) {\r\n end.setHours(23, 59, 59, 999);\r\n }\r\n return {\r\n startDate: new Date(startDate),\r\n endDate: end,\r\n };\r\n }\r\n\r\n default: {\r\n // Default: current month\r\n const now = new Date();\r\n return {\r\n startDate: new Date(now.getFullYear(), now.getMonth(), 1),\r\n endDate: new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999),\r\n };\r\n }\r\n }\r\n}\r\n\r\n/** Get fiscal year start date for a given date and fiscal start month */\r\nexport function getFiscalYearStart(date: Date, fiscalStartMonth = 1): Date {\r\n const month = fiscalStartMonth - 1; // 0-indexed\r\n const year = date.getMonth() < month ? date.getFullYear() - 1 : date.getFullYear();\r\n return new Date(year, month, 1);\r\n}\r\n","/**\r\n * Filter Builder — Sanitizes user-supplied dimension filters for aggregation pipelines.\r\n *\r\n * Prevents injection of dangerous MongoDB operators while allowing\r\n * standard equality and comparison filters on custom dimension fields.\r\n */\r\n\r\nconst BLOCKED_OPERATORS = new Set([\r\n '$where', '$expr', '$function', '$accumulator',\r\n '$merge', '$out', '$unionWith',\r\n]);\r\n\r\n/**\r\n * Build a sanitized filter object from user-supplied dimension filters.\r\n * Blocks dangerous operators ($where, $expr, $function, etc.).\r\n *\r\n * @param filters - Key-value filters (e.g. { 'journalItems.departmentId': 'dept-1' })\r\n * @returns Sanitized filter object safe for $match stages\r\n * @throws Error if a blocked operator is used\r\n */\r\nexport function buildItemFilters(filters?: Record<string, unknown>): Record<string, unknown> {\r\n if (!filters || Object.keys(filters).length === 0) return {};\r\n\r\n const result: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(filters)) {\r\n // Block operators at top level\r\n if (key.startsWith('$')) {\r\n throw new Error(`Filter key \"${key}\" is not allowed. Use field names, not operators.`);\r\n }\r\n\r\n // Check nested values for blocked operators\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n for (const opKey of Object.keys(value as Record<string, unknown>)) {\r\n if (BLOCKED_OPERATORS.has(opKey)) {\r\n throw new Error(`Filter operator \"${opKey}\" is not allowed.`);\r\n }\r\n }\r\n }\r\n\r\n result[key] = value;\r\n }\r\n\r\n return result;\r\n}\r\n","/**\r\n * Trial Balance Report\r\n *\r\n * Three-column trial balance: Initial + Current Period + Ending Balance.\r\n * Pure aggregation pipeline — no cached balances.\r\n */\r\n\r\nimport type { Model, PipelineStage } from 'mongoose';\r\nimport type { AccountType, CategoryKey } from '../types/core.js';\r\nimport type { TrialBalanceRow, TrialBalanceReport } from '../types/report.js';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport { getDateRange, getFiscalYearStart } from '../utils/date-range.js';\r\nimport { computeEndingBalance } from '../utils/account-helpers.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface TrialBalanceOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n fiscalYearStartMonth?: number;\r\n}\r\n\r\nexport async function generateTrialBalance(\r\n opts: TrialBalanceOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n accountId?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<TrialBalanceReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth = 1 } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const fiscalYearStart = getFiscalYearStart(startDate, fiscalYearStartMonth);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch all active accounts\r\n const accountQuery: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) accountQuery[orgField] = params.organizationId;\r\n\r\n const allAccounts = await AccountModel.find(accountQuery).lean() as Array<Record<string, unknown>>;\r\n\r\n // Split by statement type\r\n const bsIds: unknown[] = [];\r\n const isIds: unknown[] = [];\r\n\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at || at.isGroup) continue;\r\n\r\n if (at.category.startsWith('Balance Sheet')) bsIds.push(acc._id);\r\n else if (at.category.startsWith('Income Statement')) isIds.push(acc._id);\r\n }\r\n\r\n const baseMatch: Record<string, unknown> = { state: 'posted' };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n const accountFilter = params.accountId ? { 'journalItems.account': params.accountId } : {};\r\n\r\n // Build pipelines\r\n const buildPipeline = (ids: unknown[], dateFrom: Date, dateTo: Date): PipelineStage[] => [\r\n { $match: { ...baseMatch, date: { $gte: dateFrom, $lt: dateTo } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: ids }, ...accountFilter, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ];\r\n\r\n // BS initial: all history before startDate\r\n // IS initial: fiscal year start → startDate\r\n // Current: startDate → endDate\r\n const [bsInitial, isInitial, current] = await Promise.all([\r\n bsIds.length ? JournalEntryModel.aggregate(buildPipeline(bsIds, new Date(0), startDate)) : [],\r\n isIds.length ? JournalEntryModel.aggregate(buildPipeline(isIds, fiscalYearStart, startDate)) : [],\r\n JournalEntryModel.aggregate(buildPipeline([...bsIds, ...isIds], startDate, new Date(endDate.getTime() + 1))),\r\n ]);\r\n\r\n // Merge\r\n const map = new Map<string, { iD: number; iC: number; cD: number; cC: number }>();\r\n\r\n for (const r of [...bsInitial, ...isInitial]) {\r\n const key = String(r._id);\r\n map.set(key, { iD: r.d, iC: r.c, cD: 0, cC: 0 });\r\n }\r\n for (const r of current) {\r\n const key = String(r._id);\r\n const existing = map.get(key) ?? { iD: 0, iC: 0, cD: 0, cC: 0 };\r\n existing.cD = r.d;\r\n existing.cC = r.c;\r\n map.set(key, existing);\r\n }\r\n\r\n // Build rows\r\n const accountLookup = new Map(allAccounts.map(a => [String(a._id), a]));\r\n\r\n const rows: TrialBalanceRow[] = [];\r\n for (const [id, bal] of map) {\r\n const acc = accountLookup.get(id);\r\n const totalD = bal.iD + bal.cD;\r\n const totalC = bal.iC + bal.cC;\r\n const net = totalD - totalC;\r\n\r\n rows.push({\r\n account: acc ?? id,\r\n initial: { debit: bal.iD, credit: bal.iC },\r\n current: { debit: bal.cD, credit: bal.cC },\r\n ending: net >= 0 ? { debit: net, credit: 0 } : { debit: 0, credit: Math.abs(net) },\r\n });\r\n }\r\n\r\n return { rows, period: { startDate, endDate } };\r\n}\r\n","/**\r\n * Account Helper Utilities\r\n */\r\n\r\nimport type { AccountType, TotalAccountOp, CategoryKey } from '../types/core.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\n\r\n/**\r\n * Check if an account type is a virtual tax sub-account.\r\n * Returns true if the account's parent has `isVirtualTotal: true`.\r\n * Works for any country pack — no code format assumptions.\r\n */\r\nexport function isVirtualTaxAccount(accountType: AccountType, accountMap: Map<string, AccountType>): boolean {\r\n if (!accountType.parentCode) return false;\r\n const parent = accountMap.get(accountType.parentCode);\r\n return parent?.isVirtualTotal === true;\r\n}\r\n\r\n/** Check if an account type is a balance sheet account */\r\nexport function isBalanceSheetAccountType(accountType: AccountType): boolean {\r\n const { category } = accountType;\r\n return category.endsWith('-Asset') || category.endsWith('-Liability') || category.endsWith('-Equity');\r\n}\r\n\r\n/** Check if an account type is an income statement account */\r\nexport function isIncomeStatementAccountType(accountType: AccountType): boolean {\r\n const { category } = accountType;\r\n return category.endsWith('-Income') || category.endsWith('-Expense');\r\n}\r\n\r\n/**\r\n * Calculate a total from sub-accounts using the totalAccountTypes formula.\r\n * @param formula - Array of { account, operation } instructions\r\n * @param balanceMap - Map of account code → balance\r\n */\r\nexport function calculateTotal(\r\n formula: readonly TotalAccountOp[],\r\n balanceMap: Map<string, number>,\r\n): number {\r\n let total = 0;\r\n for (const item of formula) {\r\n const balance = balanceMap.get(item.account) ?? 0;\r\n total += item.operation === '+' ? balance : -balance;\r\n }\r\n return total;\r\n}\r\n\r\n/**\r\n * Compute the ending balance for an account given its debits and credits.\r\n * Uses the account's main type to determine normal balance direction.\r\n *\r\n * Assets & Expenses: debit - credit\r\n * Liabilities, Equity & Income: credit - debit\r\n */\r\nexport function computeEndingBalance(\r\n category: CategoryKey,\r\n totalDebit: number,\r\n totalCredit: number,\r\n): number {\r\n const mainType = extractMainType(category);\r\n if (mainType === 'Asset' || mainType === 'Expense') {\r\n return totalDebit - totalCredit;\r\n }\r\n return totalCredit - totalDebit;\r\n}\r\n\r\n/**\r\n * Build a lookup map from an array of account types.\r\n */\r\nexport function buildAccountTypeMap(accountTypes: readonly AccountType[]): Map<string, AccountType> {\r\n const map = new Map<string, AccountType>();\r\n for (const at of accountTypes) {\r\n map.set(at.code, at);\r\n }\r\n return map;\r\n}\r\n","/**\r\n * Balance Sheet Report\r\n *\r\n * Assets = Liabilities + Equity\r\n * Net income injected into retained earnings for the current fiscal year.\r\n */\r\n\r\nimport type { Model, PipelineStage } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { BalanceSheetReport, ReportCategory, ReportGroup, ReportAccount } from '../types/report.js';\r\nimport { getDateRange, getFiscalYearStart } from '../utils/date-range.js';\r\nimport { computeEndingBalance, calculateTotal, isVirtualTaxAccount, buildAccountTypeMap } from '../utils/account-helpers.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\nimport type { CategoryKey } from '../types/core.js';\r\n\r\nexport interface BalanceSheetOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n fiscalYearStartMonth?: number;\r\n /** Display code for prior retained earnings (default: '3660') */\r\n retainedEarningsCode?: string;\r\n /** Display code for current year net income (default: '3680') */\r\n currentYearEarningsCode?: string;\r\n}\r\n\r\nexport async function generateBalanceSheet(\r\n opts: BalanceSheetOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n businessName?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<BalanceSheetReport> {\r\n const {\r\n AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth = 1,\r\n retainedEarningsCode = country.retainedEarningsCode ?? '3660',\r\n currentYearEarningsCode = country.currentYearEarningsCode ?? '3680',\r\n } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const fiscalYearStart = getFiscalYearStart(endDate, fiscalYearStartMonth);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch accounts\r\n const q: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) q[orgField] = params.organizationId;\r\n const allAccounts = await AccountModel.find(q).lean() as Array<Record<string, unknown>>;\r\n\r\n // Balance sheet account IDs\r\n const bsIds = allAccounts\r\n .filter(a => {\r\n const at = country.getAccountType(a.accountTypeCode as string);\r\n return at && !at.isGroup && at.category.startsWith('Balance Sheet');\r\n })\r\n .map(a => a._id);\r\n\r\n // Income statement account IDs (for net income calculation)\r\n const isIds = allAccounts\r\n .filter(a => {\r\n const at = country.getAccountType(a.accountTypeCode as string);\r\n return at && !at.isGroup && !at.isTotal && at.category.startsWith('Income Statement');\r\n })\r\n .map(a => a._id);\r\n\r\n const baseMatch: Record<string, unknown> = { state: 'posted' };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n // Run pipelines in parallel\r\n const [bsResults, netIncomeResults, priorRetainedResults] = await Promise.all([\r\n // Balance sheet balances (all time up to endDate)\r\n JournalEntryModel.aggregate([\r\n { $match: { ...baseMatch, date: { $lte: endDate } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: bsIds }, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Promise<Array<{ _id: unknown; d: number; c: number }>>,\r\n\r\n // Net income (fiscal year start → endDate)\r\n JournalEntryModel.aggregate([\r\n { $match: { ...baseMatch, date: { $gte: fiscalYearStart, $lte: endDate } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds }, ...itemFilters } },\r\n { $group: { _id: null, d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Promise<Array<{ _id: unknown; d: number; c: number }>>,\r\n\r\n // Prior retained earnings (all income statement before fiscal year)\r\n JournalEntryModel.aggregate([\r\n { $match: { ...baseMatch, date: { $lt: fiscalYearStart } } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds }, ...itemFilters } },\r\n { $group: { _id: null, d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Promise<Array<{ _id: unknown; d: number; c: number }>>,\r\n ]);\r\n\r\n const netIncome = netIncomeResults.length > 0 ? netIncomeResults[0].c - netIncomeResults[0].d : 0;\r\n const priorRetained = priorRetainedResults.length > 0 ? priorRetainedResults[0].c - priorRetainedResults[0].d : 0;\r\n\r\n // Build categories\r\n const accountMap = new Map(allAccounts.map(a => [String(a._id), a]));\r\n const accountTypeMap = buildAccountTypeMap(country.accountTypes);\r\n const balanceMap = new Map<string, number>();\r\n\r\n const labels = country.reportLabels ?? {};\r\n const assets: ReportCategory = { name: labels.assets ?? 'Assets', total: 0, groups: [] };\r\n const liabilities: ReportCategory = { name: labels.liabilities ?? 'Liabilities', total: 0, groups: [] };\r\n const equity: ReportCategory = { name: labels.equity ?? 'Equity', total: 0, groups: [] };\r\n\r\n const groupsMap: Record<string, Record<string, ReportGroup>> = {\r\n Asset: {}, Liability: {}, Equity: {},\r\n };\r\n\r\n for (const r of bsResults) {\r\n const acc = accountMap.get(String(r._id));\r\n if (!acc) continue;\r\n\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at) continue;\r\n\r\n const mainType = extractMainType(at.category) ?? 'Asset';\r\n const balance = computeEndingBalance(at.category as CategoryKey, r.d, r.c);\r\n balanceMap.set(at.code, balance);\r\n\r\n const parentAt = at.parentCode ? country.getAccountType(at.parentCode) : undefined;\r\n const groupName = parentAt?.name ?? at.name;\r\n\r\n if (!(groupName in groupsMap[mainType])) {\r\n groupsMap[mainType][groupName] = { name: groupName, total: 0, accounts: [] };\r\n }\r\n\r\n const group = groupsMap[mainType][groupName];\r\n\r\n // Skip virtual tax sub-accounts from display but include in calculation\r\n if (!isVirtualTaxAccount(at, accountTypeMap)) {\r\n group.accounts.push({\r\n id: acc._id,\r\n name: (acc.name as string) ?? at.name,\r\n code: (acc.accountNumber as string) ?? at.code,\r\n balance,\r\n isTotal: at.isTotal,\r\n isVirtualTotal: at.isVirtualTotal,\r\n });\r\n }\r\n\r\n if (!at.isTotal) {\r\n group.total += balance;\r\n }\r\n }\r\n\r\n // Add retained earnings to equity\r\n const reGroup: ReportGroup = {\r\n name: 'Retained Earnings',\r\n total: priorRetained + netIncome,\r\n accounts: [\r\n { id: 'prior-retained', name: 'Previous Years Retained Earnings', code: retainedEarningsCode, balance: priorRetained },\r\n { id: 'current-year', name: `Current Year Net Income (${endDate.getFullYear()})`, code: currentYearEarningsCode, balance: netIncome, isCalculated: true },\r\n ],\r\n };\r\n\r\n if (!(reGroup.name in groupsMap.Equity)) {\r\n groupsMap.Equity[reGroup.name] = reGroup;\r\n } else {\r\n groupsMap.Equity[reGroup.name].accounts.push(...reGroup.accounts);\r\n groupsMap.Equity[reGroup.name].total += reGroup.total;\r\n }\r\n\r\n // Convert groups maps to arrays, filtering out zero-balance accounts and empty groups\r\n const pruneGroups = (groups: Record<string, ReportGroup>) =>\r\n Object.values(groups)\r\n .map(g => ({\r\n ...g,\r\n accounts: g.accounts.filter(a => a.balance !== 0 || a.isTotal || a.isCalculated),\r\n }))\r\n .filter(g => g.accounts.length > 0 || g.total !== 0);\r\n\r\n assets.groups = pruneGroups(groupsMap.Asset);\r\n liabilities.groups = pruneGroups(groupsMap.Liability);\r\n equity.groups = Object.values(groupsMap.Equity); // Keep equity as-is (retained earnings always shown)\r\n\r\n // Sum totals\r\n assets.total = assets.groups.reduce((s, g) => s + g.total, 0);\r\n liabilities.total = liabilities.groups.reduce((s, g) => s + g.total, 0);\r\n equity.total = equity.groups.reduce((s, g) => s + g.total, 0);\r\n\r\n const liabilitiesAndEquity = liabilities.total + equity.total;\r\n\r\n return {\r\n metadata: {\r\n businessName: params.businessName,\r\n generatedAt: new Date().toISOString(),\r\n asOfDate: endDate.toISOString().split('T')[0],\r\n displayDate: `As of ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}`,\r\n },\r\n assets,\r\n liabilities,\r\n equity,\r\n summary: {\r\n totalAssets: assets.total,\r\n totalLiabilities: liabilities.total,\r\n totalEquity: equity.total,\r\n liabilitiesAndEquity,\r\n difference: assets.total - liabilitiesAndEquity,\r\n isBalanced: assets.total === liabilitiesAndEquity,\r\n },\r\n };\r\n}\r\n","/**\r\n * Income Statement (Profit & Loss) Report\r\n *\r\n * Revenue - COGS = Gross Profit\r\n * Gross Profit - Operating Expenses = Operating Income\r\n * Operating Income ± Other = Net Income\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { IncomeStatementReport, ReportCategory, ReportGroup } from '../types/report.js';\r\nimport { getDateRange } from '../utils/date-range.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface IncomeStatementOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n}\r\n\r\nexport async function generateIncomeStatement(\r\n opts: IncomeStatementOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n businessName?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<IncomeStatementReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch accounts\r\n const q: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) q[orgField] = params.organizationId;\r\n const allAccounts = await AccountModel.find(q).lean() as Array<Record<string, unknown>>;\r\n\r\n // Income statement posting accounts only\r\n const isAccounts = allAccounts.filter(a => {\r\n const at = country.getAccountType(a.accountTypeCode as string);\r\n return at && !at.isGroup && !at.isTotal && at.category.startsWith('Income Statement');\r\n });\r\n const isIds = isAccounts.map(a => a._id);\r\n\r\n const baseMatch: Record<string, unknown> = {\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n const results = await JournalEntryModel.aggregate([\r\n { $match: baseMatch },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds }, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Array<{ _id: unknown; d: number; c: number }>;\r\n\r\n const accountMap = new Map(allAccounts.map(a => [String(a._id), a]));\r\n\r\n // Organize into revenue and expenses\r\n const revenueGroups: Record<string, ReportGroup> = {};\r\n const expenseGroups: Record<string, ReportGroup> = {};\r\n\r\n // Resolve the top-level IS group (Revenue, Cost of Sales, Operating Expenses)\r\n // by walking up the parent chain until hitting a group-label account type.\r\n const resolveGroupName = (at: { parentCode: string | null; name: string }) => {\r\n let current = at.parentCode ? country.getAccountType(at.parentCode) : undefined;\r\n while (current) {\r\n if (current.isGroup) return current.name;\r\n current = current.parentCode ? country.getAccountType(current.parentCode) : undefined;\r\n }\r\n return at.name;\r\n };\r\n\r\n for (const r of results) {\r\n const acc = accountMap.get(String(r._id));\r\n if (!acc) continue;\r\n\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at) continue;\r\n\r\n const mainType = extractMainType(at.category);\r\n const netAmount = mainType === 'Income' ? r.c - r.d : r.d - r.c;\r\n if (netAmount === 0) continue;\r\n\r\n const groupName = resolveGroupName(at);\r\n\r\n const groups = mainType === 'Income' ? revenueGroups : expenseGroups;\r\n\r\n if (!(groupName in groups)) {\r\n groups[groupName] = { name: groupName, total: 0, accounts: [] };\r\n }\r\n\r\n groups[groupName].accounts.push({\r\n id: acc._id,\r\n name: (acc.name as string) ?? at.name,\r\n code: (acc.accountNumber as string) ?? at.code,\r\n balance: netAmount,\r\n });\r\n groups[groupName].total += netAmount;\r\n }\r\n\r\n const labels = country.reportLabels ?? {};\r\n const revenue: ReportCategory = {\r\n name: labels.revenue ?? 'Revenue',\r\n total: Object.values(revenueGroups).reduce((s, g) => s + g.total, 0),\r\n groups: Object.values(revenueGroups),\r\n };\r\n\r\n const expenses: ReportCategory = {\r\n name: labels.expenses ?? 'Expenses',\r\n total: Object.values(expenseGroups).reduce((s, g) => s + g.total, 0),\r\n groups: Object.values(expenseGroups),\r\n };\r\n\r\n // Calculate COGS — use pack-declared group code, fall back to common names\r\n const cogsCode = country.cogsGroupCode;\r\n const isCogs = (name: string) =>\r\n cogsCode\r\n ? name === cogsCode\r\n : name === 'Cost of Sales' || name === 'Cost of Goods Sold';\r\n\r\n const cogsGroup = expenses.groups.find(g => isCogs(g.name));\r\n const costOfSales = cogsGroup?.total ?? 0;\r\n const grossProfit = revenue.total - costOfSales;\r\n const operatingExpenses = expenses.groups\r\n .filter(g => !isCogs(g.name))\r\n .reduce((s, g) => s + g.total, 0);\r\n const operatingIncome = grossProfit - operatingExpenses;\r\n const netIncome = revenue.total - expenses.total;\r\n\r\n const periodDisplay =\r\n params.dateOption === 'year'\r\n ? `For the year ended ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}`\r\n : `${startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}`;\r\n\r\n return {\r\n metadata: {\r\n businessName: params.businessName,\r\n generatedAt: new Date().toISOString(),\r\n periodStart: startDate.toISOString().split('T')[0],\r\n periodEnd: endDate.toISOString().split('T')[0],\r\n displayPeriod: periodDisplay,\r\n },\r\n revenue,\r\n costOfSales,\r\n grossProfit,\r\n expenses,\r\n operatingIncome,\r\n netIncome,\r\n };\r\n}\r\n","/**\r\n * General Ledger Report\r\n *\r\n * Shows every posted entry for selected accounts with running balances.\r\n * Uses batched queries (3 max) instead of per-account loops.\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { GeneralLedgerReport, GeneralLedgerAccount, LedgerEntry } from '../types/report.js';\r\nimport type { AccountType } from '../types/core.js';\r\nimport type { CategoryKey } from '../types/core.js';\r\nimport { getDateRange, getFiscalYearStart } from '../utils/date-range.js';\r\nimport { computeEndingBalance } from '../utils/account-helpers.js';\r\nimport { extractMainType } from '../constants/categories.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface GeneralLedgerOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n fiscalYearStartMonth?: number;\r\n}\r\n\r\nexport async function generateGeneralLedger(\r\n opts: GeneralLedgerOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n accountId?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<GeneralLedgerReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth = 1 } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const fiscalYearStart = getFiscalYearStart(startDate, fiscalYearStartMonth);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Get target accounts\r\n const acctQuery: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) acctQuery[orgField] = params.organizationId;\r\n if (params.accountId) acctQuery._id = params.accountId;\r\n\r\n const allAccounts = await AccountModel.find(acctQuery).lean() as Array<Record<string, unknown>>;\r\n\r\n // Filter to postable accounts (no groups, no totals)\r\n const filtered: Array<{ acc: Record<string, unknown>; at: AccountType }> = [];\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at || at.isGroup || at.isTotal) continue;\r\n filtered.push({ acc, at });\r\n }\r\n\r\n if (filtered.length === 0) {\r\n return { accounts: [], period: { startDate, endDate } };\r\n }\r\n\r\n // Separate BS vs IS account IDs (different opening-balance date ranges)\r\n const bsAccountIds: unknown[] = [];\r\n const isAccountIds: unknown[] = [];\r\n const allAccountIds: unknown[] = [];\r\n\r\n for (const { acc, at } of filtered) {\r\n allAccountIds.push(acc._id);\r\n if (at.category.startsWith('Balance Sheet')) {\r\n bsAccountIds.push(acc._id);\r\n } else {\r\n isAccountIds.push(acc._id);\r\n }\r\n }\r\n\r\n // Org scope helper\r\n const orgScope: Record<string, unknown> = {};\r\n if (orgField && params.organizationId) orgScope[orgField] = params.organizationId;\r\n\r\n // ── Batch queries (3 max, run in parallel) ──────────────────────────────────\r\n\r\n const openingBalancePipeline = (\r\n accountIds: unknown[],\r\n dateFilter: Record<string, unknown>,\r\n ) =>\r\n accountIds.length > 0\r\n ? JournalEntryModel.aggregate([\r\n { $match: { state: 'posted', date: dateFilter, ...orgScope } },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: accountIds }, ...itemFilters } },\r\n {\r\n $group: {\r\n _id: '$journalItems.account',\r\n d: { $sum: '$journalItems.debit' },\r\n c: { $sum: '$journalItems.credit' },\r\n },\r\n },\r\n ])\r\n : Promise.resolve([]);\r\n\r\n const [bsOpenResults, isOpenResults, periodEntries] = await Promise.all([\r\n // BS opening: all posted entries before startDate\r\n openingBalancePipeline(bsAccountIds, { $lt: startDate }),\r\n // IS opening: posted entries from fiscal year start to before startDate\r\n openingBalancePipeline(isAccountIds, { $gte: fiscalYearStart, $lt: startDate }),\r\n // Period entries: all posted entries for any target account in the period\r\n JournalEntryModel.find({\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n 'journalItems.account': { $in: allAccountIds },\r\n ...orgScope,\r\n ...itemFilters,\r\n })\r\n .select('date referenceNumber label journalItems')\r\n .sort({ date: 1 })\r\n .lean() as Promise<Array<Record<string, unknown>>>,\r\n ]);\r\n\r\n // ── Build lookup maps ───────────────────────────────────────────────────────\r\n\r\n // Opening balance by account ID\r\n const openBalMap = new Map<string, { d: number; c: number }>();\r\n for (const r of [...(bsOpenResults as Array<{ _id: unknown; d: number; c: number }>),\r\n ...(isOpenResults as Array<{ _id: unknown; d: number; c: number }>)]) {\r\n openBalMap.set(String(r._id), { d: r.d, c: r.c });\r\n }\r\n\r\n // ── Pre-index period entries by account ID (O(entries × items) once) ────────\r\n\r\n const entryItemsByAccount = new Map<string, Array<{\r\n date: Date; referenceNumber: string; label: string; debit: number; credit: number;\r\n }>>();\r\n\r\n for (const entry of periodEntries) {\r\n const items = (entry.journalItems as Array<Record<string, unknown>>) ?? [];\r\n for (const item of items) {\r\n const accId = String(item.account);\r\n const debit = (item.debit as number) ?? 0;\r\n const credit = (item.credit as number) ?? 0;\r\n\r\n let list = entryItemsByAccount.get(accId);\r\n if (!list) {\r\n list = [];\r\n entryItemsByAccount.set(accId, list);\r\n }\r\n list.push({\r\n date: entry.date as Date,\r\n referenceNumber: (entry.referenceNumber as string) ?? '',\r\n label: (entry.label as string) ?? '',\r\n debit,\r\n credit,\r\n });\r\n }\r\n }\r\n\r\n // ── Assemble per-account results (O(accounts + total items)) ──────────────\r\n\r\n const glAccounts: GeneralLedgerAccount[] = [];\r\n\r\n for (const { acc, at } of filtered) {\r\n const accIdStr = String(acc._id);\r\n const openData = openBalMap.get(accIdStr);\r\n const openingBalance = openData\r\n ? computeEndingBalance(at.category as CategoryKey, openData.d, openData.c)\r\n : 0;\r\n\r\n let runningBalance = openingBalance;\r\n const entries: LedgerEntry[] = [];\r\n const mainType = extractMainType(at.category as CategoryKey);\r\n\r\n const accountItems = entryItemsByAccount.get(accIdStr) ?? [];\r\n for (const item of accountItems) {\r\n const delta =\r\n mainType === 'Asset' || mainType === 'Expense'\r\n ? item.debit - item.credit\r\n : item.credit - item.debit;\r\n\r\n runningBalance += delta;\r\n\r\n entries.push({\r\n date: item.date,\r\n referenceNumber: item.referenceNumber,\r\n label: item.label,\r\n debit: item.debit,\r\n credit: item.credit,\r\n runningBalance,\r\n });\r\n }\r\n\r\n glAccounts.push({\r\n account: acc,\r\n openingBalance,\r\n entries,\r\n closingBalance: runningBalance,\r\n });\r\n }\r\n\r\n return { accounts: glAccounts, period: { startDate, endDate } };\r\n}\r\n","/**\r\n * Cash Flow Statement\r\n *\r\n * Groups transactions by cashFlowCategory from account type definitions:\r\n * Operating, Investing, Financing.\r\n * Uses aggregation pipeline — no in-memory processing.\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport type { CashFlowCategory, CategoryKey } from '../types/core.js';\r\nimport type { CashFlowReport } from '../types/report.js';\r\nimport { getDateRange } from '../utils/date-range.js';\r\nimport { computeEndingBalance } from '../utils/account-helpers.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { buildItemFilters } from '../utils/filter-builder.js';\r\n\r\nexport interface CashFlowOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n}\r\n\r\nexport async function generateCashFlow(\r\n opts: CashFlowOptions,\r\n params: {\r\n organizationId?: unknown;\r\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\r\n dateValue: unknown;\r\n businessName?: string;\r\n filters?: Record<string, unknown>;\r\n },\r\n): Promise<CashFlowReport> {\r\n const { AccountModel, JournalEntryModel, country, orgField } = opts;\r\n requireOrgScope(orgField, params.organizationId);\r\n const { startDate, endDate } = getDateRange(params.dateOption, params.dateValue);\r\n const itemFilters = buildItemFilters(params.filters);\r\n\r\n // Fetch accounts\r\n const q: Record<string, unknown> = { active: true };\r\n if (orgField && params.organizationId) q[orgField] = params.organizationId;\r\n const allAccounts = await AccountModel.find(q).lean() as Array<Record<string, unknown>>;\r\n\r\n // Build maps: accountId -> metadata, accountId -> raw account doc\r\n const accountCfMap = new Map<string, { category: CategoryKey; cfCategory: CashFlowCategory }>();\r\n const accountMap = new Map(allAccounts.map(a => [String(a._id), a]));\r\n const cfAccountIds: unknown[] = [];\r\n\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at || at.isGroup || at.isTotal) continue;\r\n\r\n const cf = at.cashFlowCategory;\r\n if (!cf) continue;\r\n\r\n // Normalize case: 'operating' → 'Operating'\r\n const normalized = (cf.charAt(0).toUpperCase() + cf.slice(1)) as CashFlowCategory;\r\n accountCfMap.set(String(acc._id), { category: at.category, cfCategory: normalized });\r\n cfAccountIds.push(acc._id);\r\n }\r\n\r\n // Aggregate journal items for accounts with cashFlowCategory\r\n const baseMatch: Record<string, unknown> = {\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n };\r\n if (orgField && params.organizationId) baseMatch[orgField] = params.organizationId;\r\n\r\n const results = cfAccountIds.length > 0\r\n ? await JournalEntryModel.aggregate([\r\n { $match: baseMatch },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: cfAccountIds }, ...itemFilters } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ]) as Array<{ _id: unknown; d: number; c: number }>\r\n : [];\r\n\r\n // Accumulate by category\r\n const flows: Record<CashFlowCategory, { total: number; accounts: Array<{ name: string; code: string; amount: number }> }> = {\r\n Operating: { total: 0, accounts: [] },\r\n Investing: { total: 0, accounts: [] },\r\n Financing: { total: 0, accounts: [] },\r\n };\r\n\r\n for (const r of results) {\r\n const accIdStr = String(r._id);\r\n const meta = accountCfMap.get(accIdStr);\r\n if (!meta) continue;\r\n\r\n // Net cash flow: for assets/expenses, debit increases (cash out), credit decreases (cash in)\r\n // For liabilities/equity/income, credit increases, debit decreases\r\n const amount = computeEndingBalance(meta.category, r.d, r.c);\r\n const acc = accountMap.get(accIdStr);\r\n const at = country.getAccountType(acc?.accountTypeCode as string);\r\n\r\n flows[meta.cfCategory].accounts.push({\r\n name: (acc?.name as string) ?? at?.name ?? '',\r\n code: (acc?.accountNumber as string) ?? at?.code ?? '',\r\n amount,\r\n });\r\n flows[meta.cfCategory].total += amount;\r\n }\r\n\r\n const netCashFlow = flows.Operating.total + flows.Investing.total + flows.Financing.total;\r\n\r\n const periodDisplay = `${startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – ${endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}`;\r\n\r\n return {\r\n metadata: {\r\n businessName: params.businessName,\r\n generatedAt: new Date().toISOString(),\r\n periodStart: startDate.toISOString().split('T')[0],\r\n periodEnd: endDate.toISOString().split('T')[0],\r\n displayPeriod: periodDisplay,\r\n },\r\n operating: flows.Operating,\r\n investing: flows.Investing,\r\n financing: flows.Financing,\r\n netCashFlow,\r\n };\r\n}\r\n","/**\r\n * Fiscal Year Closing & Reopening\r\n *\r\n * Close: zeroes income/expense accounts, transfers net income to retained\r\n * earnings via a YEAR_END journal entry, marks the period closed.\r\n *\r\n * Reopen: validates no later period is closed, deletes the closing entry,\r\n * marks the period open again with an audit trail.\r\n *\r\n * Session management: creates an internal transaction by default.\r\n * Pass an external session to join a caller-managed transaction instead.\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport type { CountryPack } from '../country/index.js';\r\nimport { requireOrgScope } from '../utils/tenant-guard.js';\r\nimport { Errors } from '../utils/errors.js';\r\nimport type { Logger } from '../utils/logger.js';\r\nimport { defaultLogger } from '../utils/logger.js';\r\nimport { acquireSession, finalizeSession } from '../utils/session.js';\r\n\r\nexport interface FiscalCloseOptions {\r\n AccountModel: Model<unknown>;\r\n JournalEntryModel: Model<unknown>;\r\n FiscalPeriodModel: Model<unknown>;\r\n country: CountryPack;\r\n orgField?: string;\r\n retainedEarningsCode?: string;\r\n logger?: Logger;\r\n}\r\n\r\nexport interface FiscalCloseResult {\r\n periodId: unknown;\r\n netIncome: number;\r\n closingEntryId: unknown | null;\r\n accountsClosed: number;\r\n closedAt: Date;\r\n}\r\n\r\nexport async function closeFiscalPeriod(\r\n opts: FiscalCloseOptions,\r\n params: {\r\n periodId: unknown;\r\n organizationId?: unknown;\r\n closedBy?: string;\r\n session?: ClientSession;\r\n },\r\n): Promise<FiscalCloseResult> {\r\n const {\r\n AccountModel,\r\n JournalEntryModel,\r\n FiscalPeriodModel,\r\n country,\r\n orgField,\r\n retainedEarningsCode = country.retainedEarningsCode ?? '3660',\r\n logger = defaultLogger,\r\n } = opts;\r\n const { periodId, organizationId, closedBy } = params;\r\n requireOrgScope(orgField, organizationId);\r\n\r\n const { session, ownSession } = await acquireSession(\r\n AccountModel.db,\r\n params.session,\r\n logger,\r\n );\r\n let success = false;\r\n\r\n try {\r\n const queryOpts = session ? { session } : {};\r\n\r\n // 1. Fetch and validate the fiscal period (org-scoped)\r\n const periodQuery: Record<string, unknown> = { _id: periodId };\r\n if (orgField && organizationId) periodQuery[orgField] = organizationId;\r\n const period = await FiscalPeriodModel.findOne(periodQuery, null, queryOpts).lean() as Record<string, unknown> | null;\r\n if (!period) throw Errors.notFound('Fiscal period not found');\r\n if (period.closed) throw Errors.fiscal('Fiscal period is already closed');\r\n\r\n const startDate = period.startDate as Date;\r\n const endDate = period.endDate as Date;\r\n\r\n // 2. Find all income statement accounts for this org\r\n const accountQuery: Record<string, unknown> = { active: true };\r\n if (orgField && organizationId) accountQuery[orgField] = organizationId;\r\n const allAccounts = await AccountModel.find(accountQuery, null, queryOpts).lean() as Array<Record<string, unknown>>;\r\n\r\n const isAccounts: Array<{ id: unknown; code: string; isIncome: boolean }> = [];\r\n let retainedEarningsId: unknown = null;\r\n\r\n for (const acc of allAccounts) {\r\n const at = country.getAccountType(acc.accountTypeCode as string);\r\n if (!at) continue;\r\n\r\n if (acc.accountTypeCode === retainedEarningsCode) {\r\n retainedEarningsId = acc._id;\r\n }\r\n\r\n if (at.isGroup || at.isTotal) continue;\r\n if (at.category.startsWith('Income Statement')) {\r\n isAccounts.push({\r\n id: acc._id,\r\n code: at.code,\r\n isIncome: at.category === 'Income Statement-Income',\r\n });\r\n }\r\n }\r\n\r\n if (!retainedEarningsId) {\r\n throw Errors.fiscal(\r\n `Retained earnings account (code: ${retainedEarningsCode}) not found. ` +\r\n 'Create this account before closing the fiscal period.',\r\n );\r\n }\r\n\r\n // 3. Aggregate balances for all IS accounts in the period\r\n const baseMatch: Record<string, unknown> = {\r\n state: 'posted',\r\n date: { $gte: startDate, $lte: endDate },\r\n };\r\n if (orgField && organizationId) baseMatch[orgField] = organizationId;\r\n\r\n const isIds = isAccounts.map(a => a.id);\r\n const balances = isIds.length > 0\r\n ? await JournalEntryModel.aggregate([\r\n { $match: baseMatch },\r\n { $unwind: '$journalItems' },\r\n { $match: { 'journalItems.account': { $in: isIds } } },\r\n { $group: { _id: '$journalItems.account', d: { $sum: '$journalItems.debit' }, c: { $sum: '$journalItems.credit' } } },\r\n ], queryOpts) as Array<{ _id: unknown; d: number; c: number }>\r\n : [];\r\n\r\n // 4. Build closing journal entry items\r\n const closingItems: Array<{ account: unknown; debit: number; credit: number; label: string }> = [];\r\n let netIncome = 0;\r\n\r\n const balMap = new Map(balances.map(b => [String(b._id), b]));\r\n\r\n for (const acc of isAccounts) {\r\n const bal = balMap.get(String(acc.id));\r\n if (!bal) continue;\r\n\r\n const net = bal.c - bal.d;\r\n if (net === 0) continue;\r\n\r\n closingItems.push({\r\n account: acc.id,\r\n debit: net > 0 ? net : 0,\r\n credit: net < 0 ? Math.abs(net) : 0,\r\n label: `Close ${acc.code}`,\r\n });\r\n\r\n netIncome += net;\r\n }\r\n\r\n let closingEntryId: unknown = null;\r\n\r\n if (closingItems.length > 0) {\r\n closingItems.push({\r\n account: retainedEarningsId,\r\n debit: netIncome < 0 ? Math.abs(netIncome) : 0,\r\n credit: netIncome > 0 ? netIncome : 0,\r\n label: 'Transfer net income to retained earnings',\r\n });\r\n\r\n const totalDebit = closingItems.reduce((s, i) => s + i.debit, 0);\r\n const totalCredit = closingItems.reduce((s, i) => s + i.credit, 0);\r\n\r\n const closingEntryData: Record<string, unknown> = {\r\n journalType: 'YEAR_END',\r\n state: 'posted',\r\n date: endDate,\r\n label: `Fiscal year closing – ${(period.name as string) ?? 'Period'}`,\r\n journalItems: closingItems,\r\n totalDebit,\r\n totalCredit,\r\n };\r\n if (orgField && organizationId) closingEntryData[orgField] = organizationId;\r\n\r\n const [closingEntry] = await JournalEntryModel.create([closingEntryData], queryOpts);\r\n closingEntryId = closingEntry._id;\r\n }\r\n\r\n // 5. Mark the period as closed (org-scoped)\r\n const closedAt = new Date();\r\n await FiscalPeriodModel.findOneAndUpdate(\r\n periodQuery,\r\n { closed: true, closedAt, closedBy: closedBy ?? null, closingEntryId },\r\n queryOpts,\r\n );\r\n\r\n const result: FiscalCloseResult = {\r\n periodId,\r\n netIncome,\r\n closingEntryId,\r\n accountsClosed: closingItems.length - (closingItems.length > 0 ? 1 : 0),\r\n closedAt,\r\n };\r\n\r\n success = true;\r\n return result;\r\n } finally {\r\n await finalizeSession(session, ownSession, success);\r\n }\r\n}\r\n\r\n// ── Reopen ────────────────────────────────────────────────────────────────\r\n\r\nexport interface FiscalReopenResult {\r\n periodId: unknown;\r\n deletedEntryId: unknown | null;\r\n reopenedAt: Date;\r\n}\r\n\r\nexport async function reopenFiscalPeriod(\r\n opts: Pick<FiscalCloseOptions, 'JournalEntryModel' | 'FiscalPeriodModel'> & {\r\n orgField?: string;\r\n logger?: Logger;\r\n /** Any model on the same connection — used to start sessions */\r\n AccountModel?: Model<unknown>;\r\n },\r\n params: {\r\n periodId: unknown;\r\n organizationId?: unknown;\r\n reopenedBy?: string;\r\n session?: ClientSession;\r\n },\r\n): Promise<FiscalReopenResult> {\r\n const { JournalEntryModel, FiscalPeriodModel, orgField, logger = defaultLogger } = opts;\r\n const { periodId, organizationId, reopenedBy } = params;\r\n requireOrgScope(orgField, organizationId);\r\n\r\n // Use any available model's db connection for session creation\r\n const db = (opts.AccountModel ?? FiscalPeriodModel).db;\r\n const { session, ownSession } = await acquireSession(db, params.session, logger);\r\n let success = false;\r\n\r\n try {\r\n const queryOpts = session ? { session } : {};\r\n\r\n // 1. Fetch and validate the period (org-scoped)\r\n const periodQuery: Record<string, unknown> = { _id: periodId };\r\n if (orgField && organizationId) periodQuery[orgField] = organizationId;\r\n const period = await FiscalPeriodModel.findOne(periodQuery, null, queryOpts).lean() as Record<string, unknown> | null;\r\n if (!period) throw Errors.notFound('Fiscal period not found');\r\n if (!period.closed) throw Errors.fiscal('Fiscal period is not closed');\r\n\r\n // 2. Block if a later period is already closed (prevents cascade corruption)\r\n const laterQuery: Record<string, unknown> = {\r\n closed: true,\r\n startDate: { $gt: period.endDate },\r\n };\r\n if (orgField && organizationId) laterQuery[orgField] = organizationId;\r\n\r\n const laterClosed = await FiscalPeriodModel.findOne(laterQuery, null, queryOpts).lean();\r\n if (laterClosed) {\r\n throw Errors.fiscal(\r\n 'Cannot reopen: a later fiscal period is already closed. Reopen later periods first.',\r\n );\r\n }\r\n\r\n // 3. Delete the closing journal entry (if one was created)\r\n const closingEntryId = period.closingEntryId ?? null;\r\n if (closingEntryId) {\r\n await JournalEntryModel.findByIdAndDelete(closingEntryId, queryOpts);\r\n }\r\n\r\n // 4. Mark the period as reopened (org-scoped)\r\n const reopenedAt = new Date();\r\n await FiscalPeriodModel.findOneAndUpdate(\r\n periodQuery,\r\n {\r\n closed: false,\r\n closedAt: null,\r\n closedBy: null,\r\n closingEntryId: null,\r\n reopenedAt,\r\n reopenedBy: reopenedBy ?? null,\r\n },\r\n queryOpts,\r\n );\r\n\r\n const result: FiscalReopenResult = {\r\n periodId,\r\n deletedEntryId: closingEntryId,\r\n reopenedAt,\r\n };\r\n\r\n success = true;\r\n return result;\r\n } finally {\r\n await finalizeSession(session, ownSession, success);\r\n }\r\n}\r\n"],"mappings":"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"}