@classytic/ledger 0.2.0 → 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 (66) hide show
  1. package/README.md +161 -64
  2. package/dist/{account.repository-kDKwDt0I.mjs → account.repository-BpkSd6q3.mjs} +189 -38
  3. package/dist/categories-CclX7Q94.mjs +0 -2
  4. package/dist/core-8Xfnpn6g.d.mts +1 -2
  5. package/dist/country/index.d.mts +1 -1
  6. package/dist/country/index.mjs +0 -2
  7. package/dist/currencies-4WAbFRlw.d.mts +1 -2
  8. package/dist/currencies-W8kQAkm0.mjs +0 -2
  9. package/dist/{idempotency.plugin-v9NQ_ta-.mjs → date-lock.plugin-eYAJ9h_u.mjs} +49 -9
  10. package/dist/{engine-BzBMpWuy.d.mts → engine-Cn-9yerQ.d.mts} +11 -7
  11. package/dist/errors-B7yC-Jfw.mjs +0 -2
  12. package/dist/exports-I5Xkq-9_.mjs +0 -2
  13. package/dist/{fiscal-close-L631E3De.mjs → fiscal-close-B6LhQ10f.mjs} +737 -20
  14. package/dist/fiscal-period.schema-BMnlI9H5.d.mts +103 -0
  15. package/dist/{idempotency.plugin-CPxPt4vX.d.mts → idempotency.plugin-B_CNsInz.d.mts} +19 -17
  16. package/dist/index-BPukb3L8.d.mts +1 -2
  17. package/dist/{index-ZnSiqHYV.d.mts → index-CxZqRaOU.d.mts} +20 -6
  18. package/dist/index.d.mts +248 -26
  19. package/dist/index.mjs +119 -21
  20. package/dist/journals-oH-FK3g8.mjs +0 -2
  21. package/dist/{logger-UbTdBb1x.d.mts → logger-CbHWZl7v.d.mts} +1 -2
  22. package/dist/money.d.mts +1 -2
  23. package/dist/money.mjs +3 -3
  24. package/dist/plugins/index.d.mts +38 -2
  25. package/dist/plugins/index.mjs +57 -2
  26. package/dist/reconciliation.repository-CW4-8q90.d.mts +135 -0
  27. package/dist/{fiscal-period.schema-BQ5wsAq3.mjs → reconciliation.schema-BuetvZTd.mjs} +168 -24
  28. package/dist/reports/index.d.mts +2 -2
  29. package/dist/reports/index.mjs +2 -2
  30. package/dist/repositories/index.d.mts +2 -2
  31. package/dist/repositories/index.mjs +2 -2
  32. package/dist/revaluation-D9x0NE8w.d.mts +530 -0
  33. package/dist/schemas/index.d.mts +71 -2
  34. package/dist/schemas/index.mjs +2 -2
  35. package/dist/tenant-guard-Fm6AID_6.mjs +13 -0
  36. package/docs/reports.md +1 -1
  37. package/package.json +2 -2
  38. package/dist/account.repository-C7gwFLfM.d.mts +0 -29
  39. package/dist/account.repository-C7gwFLfM.d.mts.map +0 -1
  40. package/dist/account.repository-kDKwDt0I.mjs.map +0 -1
  41. package/dist/categories-CclX7Q94.mjs.map +0 -1
  42. package/dist/core-8Xfnpn6g.d.mts.map +0 -1
  43. package/dist/country/index.mjs.map +0 -1
  44. package/dist/currencies-4WAbFRlw.d.mts.map +0 -1
  45. package/dist/currencies-W8kQAkm0.mjs.map +0 -1
  46. package/dist/engine-BzBMpWuy.d.mts.map +0 -1
  47. package/dist/errors-B7yC-Jfw.mjs.map +0 -1
  48. package/dist/exports-I5Xkq-9_.mjs.map +0 -1
  49. package/dist/fiscal-close-L631E3De.mjs.map +0 -1
  50. package/dist/fiscal-close-dNlzB37y.d.mts +0 -270
  51. package/dist/fiscal-close-dNlzB37y.d.mts.map +0 -1
  52. package/dist/fiscal-period.schema-BQ5wsAq3.mjs.map +0 -1
  53. package/dist/fiscal-period.schema-BRdKAjrr.d.mts +0 -38
  54. package/dist/fiscal-period.schema-BRdKAjrr.d.mts.map +0 -1
  55. package/dist/idempotency.plugin-CPxPt4vX.d.mts.map +0 -1
  56. package/dist/idempotency.plugin-v9NQ_ta-.mjs.map +0 -1
  57. package/dist/index-BPukb3L8.d.mts.map +0 -1
  58. package/dist/index-ZnSiqHYV.d.mts.map +0 -1
  59. package/dist/index.d.mts.map +0 -1
  60. package/dist/index.mjs.map +0 -1
  61. package/dist/journals-oH-FK3g8.mjs.map +0 -1
  62. package/dist/logger-UbTdBb1x.d.mts.map +0 -1
  63. package/dist/money.d.mts.map +0 -1
  64. package/dist/money.mjs.map +0 -1
  65. package/dist/session-Ba8E3Ufa.mjs +0 -84
  66. package/dist/session-Ba8E3Ufa.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"fiscal-close-dNlzB37y.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-period.schema-BQ5wsAq3.mjs","names":[],"sources":["../src/schemas/currency-field.ts","../src/schemas/account.schema.ts","../src/schemas/journal-entry.schema.ts","../src/schemas/fiscal-period.schema.ts"],"sourcesContent":["/**\n * Shared Mongoose field definition for multi-currency support.\n * Used by both Account and JournalItem schemas when multiCurrency is enabled.\n */\n\nimport type { AccountingEngineConfig } from '../types/engine.js';\n\n/**\n * Build the Mongoose currency field definition.\n * Returns `null` if multi-currency is not enabled.\n */\nexport function buildCurrencyField(config: AccountingEngineConfig): Record<string, unknown> | null {\n if (!config.multiCurrency?.enabled) return null;\n\n const allowed = config.multiCurrency.currencies;\n return {\n type: String,\n default: null,\n ...(allowed?.length ? { enum: [null, config.currency, ...allowed] } : {}),\n };\n}\n","/**\r\n * Account Schema Factory\r\n *\r\n * Creates a Mongoose schema for Chart of Accounts that is:\r\n * - Multi-tenant aware (adds org field + compound indexes when configured)\r\n * - Validates accountTypeCode against the country pack\r\n * - Supports accountNumber (unique per org) and name (user-facing display)\r\n * - Lean: no cached balances — always computed from journal entries\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, SchemaOptions } from '../types/engine.js';\r\nimport { buildCurrencyField } from './currency-field.js';\r\n\r\nexport function createAccountSchema(\r\n config: AccountingEngineConfig,\r\n options: SchemaOptions = {},\r\n) {\r\n const { multiTenant, country } = config;\r\n const { indexes = true, extraFields = {}, extraIndexes = [] } = options;\r\n\r\n // ── Base fields ──────────────────────────────────────────────────────────\r\n\r\n const fields: Record<string, unknown> = {\r\n accountTypeCode: {\r\n type: String,\r\n required: true,\r\n validate: {\r\n validator: (code: string) => country.isValidAccountType(code),\r\n message: (props: { value: string }) =>\r\n `\"${props.value}\" is not a valid account type code for ${country.name}.`,\r\n },\r\n },\r\n accountNumber: {\r\n type: String,\r\n required: true,\r\n trim: true,\r\n },\r\n name: {\r\n type: String,\r\n required: true,\r\n trim: true,\r\n },\r\n active: { type: Boolean, default: true },\r\n isCashAccount: { type: Boolean, default: false },\r\n };\r\n\r\n // ── Multi-currency account field (opt-in) ──────────────────────────────\r\n const currencyField = buildCurrencyField(config);\r\n if (currencyField) fields.currency = currencyField;\r\n\r\n Object.assign(fields, extraFields);\r\n\r\n // ── Multi-tenant field ───────────────────────────────────────────────────\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // ── Schema ───────────────────────────────────────────────────────────────\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n // ── Pre-validate: auto-default accountNumber and name ──────────────────\r\n\r\n schema.pre('validate', function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n if (!doc.accountNumber && doc.accountTypeCode) {\r\n doc.accountNumber = doc.accountTypeCode;\r\n }\r\n if (!doc.name && doc.accountTypeCode) {\r\n const at = country.getAccountType(doc.accountTypeCode);\r\n doc.name = at?.name ?? doc.accountTypeCode;\r\n }\r\n });\r\n\r\n // ── Indexes ──────────────────────────────────────────────────────────────\r\n\r\n if (indexes) {\r\n if (multiTenant) {\r\n const org = multiTenant.orgField;\r\n schema.index({ [org]: 1, active: 1 });\r\n // accountNumber is the unique identity per org\r\n schema.index({ [org]: 1, accountNumber: 1 }, { unique: true });\r\n // accountTypeCode is non-unique — multiple accounts can share a classification\r\n schema.index({ [org]: 1, accountTypeCode: 1 });\r\n } else {\r\n schema.index({ active: 1 });\r\n schema.index({ accountNumber: 1 }, { unique: true });\r\n schema.index({ accountTypeCode: 1 });\r\n }\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n return schema;\r\n}\r\n","/**\r\n * Journal Entry Schema Factory\r\n *\r\n * Creates a Mongoose schema for double-entry journal entries.\r\n * - Multi-tenant aware\r\n * - Embedded journal items with account refs\r\n * - State machine: draft → posted, draft → archived\r\n * - Auto-generated reference numbers\r\n * - Double-entry validation on post\r\n * - Optimized indexes for high-load reporting\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, JournalSchemaOptions } from '../types/engine.js';\r\nimport { getJournalTypeCodes, JOURNAL_CODES } from '../constants/journals.js';\r\nimport { buildCurrencyField } from './currency-field.js';\r\n\r\nexport function createJournalEntrySchema(\r\n config: AccountingEngineConfig,\r\n accountModelName: string,\r\n options: JournalSchemaOptions = {},\r\n) {\r\n const { multiTenant } = config;\r\n const {\r\n indexes = true,\r\n autoReference = true,\r\n textSearch = true,\r\n extraFields = {},\r\n extraIndexes = [],\r\n extraItemFields = {},\r\n } = options;\r\n\r\n // ── Tax Detail (audit reference only) ────────────────────────────────────\r\n\r\n const TaxDetailSchema = new mongoose.Schema(\r\n {\r\n taxCode: { type: String },\r\n taxName: { type: String },\r\n },\r\n { _id: false },\r\n );\r\n\r\n // ── Journal Item ─────────────────────────────────────────────────────────\r\n\r\n const amountValidator = {\r\n validator: (v: number) => Number.isInteger(v) && v >= 0,\r\n message: '{PATH} must be a non-negative integer (cents), got {VALUE}',\r\n };\r\n\r\n // ── Multi-currency item fields (opt-in) ──────────────────────────────────\r\n const currencyItemFields: Record<string, unknown> = {};\r\n const currencyField = buildCurrencyField(config);\r\n if (currencyField) {\r\n currencyItemFields.currency = currencyField;\r\n currencyItemFields.exchangeRate = {\r\n type: Number, default: null,\r\n validate: { validator: (v: number | null) => v === null || v > 0, message: 'exchangeRate must be greater than zero when set, got {VALUE}' },\r\n };\r\n currencyItemFields.originalDebit = { type: Number, default: null, min: 0, validate: amountValidator };\r\n currencyItemFields.originalCredit = { type: Number, default: null, min: 0, validate: amountValidator };\r\n }\r\n\r\n const JournalItemSchema = new mongoose.Schema(\r\n {\r\n account: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: accountModelName,\r\n required: true,\r\n },\r\n label: { type: String },\r\n date: { type: Date },\r\n debit: { type: Number, default: 0, min: 0, validate: amountValidator },\r\n credit: { type: Number, default: 0, min: 0, validate: amountValidator },\r\n taxDetails: { type: [TaxDetailSchema], default: [] },\r\n ...currencyItemFields,\r\n ...extraItemFields,\r\n },\r\n { _id: false },\r\n );\r\n\r\n // ── Main fields ──────────────────────────────────────────────────────────\r\n\r\n const fields: Record<string, unknown> = {\r\n journalType: {\r\n type: String,\r\n enum: getJournalTypeCodes(),\r\n default: JOURNAL_CODES['MISC'],\r\n required: true,\r\n },\r\n referenceNumber: { type: String },\r\n label: { type: String },\r\n date: {\r\n type: Date,\r\n default: Date.now,\r\n required: function (this: { state?: string }) {\r\n return this.state !== 'draft';\r\n },\r\n },\r\n journalItems: { type: [JournalItemSchema], default: [] },\r\n totalDebit: { type: Number, required: true, min: 0, validate: { validator: Number.isInteger, message: 'totalDebit must be an integer (cents)' } },\r\n totalCredit: { type: Number, required: true, min: 0, validate: { validator: Number.isInteger, message: 'totalCredit must be an integer (cents)' } },\r\n state: {\r\n type: String,\r\n enum: ['draft', 'posted', 'archived'],\r\n default: 'draft',\r\n required: true,\r\n },\r\n stateChangedAt: { type: Date, default: Date.now },\r\n reversed: { type: Boolean, default: false },\r\n reversedBy: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: 'JournalEntry',\r\n default: null,\r\n },\r\n reversalOf: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: 'JournalEntry',\r\n default: null,\r\n },\r\n ...extraFields,\r\n };\r\n\r\n // ── Audit fields (conditional) ─────────────────────────────────────────\r\n\r\n if (config.audit?.trackActor) {\r\n fields.createdBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.postedBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.reversedByUser = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n }\r\n\r\n // ── Approval fields (conditional) ──────────────────────────────────────\r\n\r\n if (config.strictness?.requireApproval || config.audit?.trackActor) {\r\n fields.approvedBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.approvedAt = { type: Date, default: null };\r\n }\r\n\r\n // ── Idempotency key (conditional) ──────────────────────────────────────\r\n\r\n if (config.idempotency) {\r\n fields.idempotencyKey = { type: String, default: null };\r\n }\r\n\r\n // ── Multi-tenant field ───────────────────────────────────────────────────\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // ── Schema ───────────────────────────────────────────────────────────────\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n // ── Pre-validate: double-entry enforcement ───────────────────────────────\r\n\r\n schema.pre('validate', function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n\r\n // Propagate entry date to items without a date\r\n for (const item of doc.journalItems) {\r\n if (!item.date) item.date = doc.date;\r\n }\r\n\r\n // Each line must be debit OR credit (not both), and posted entries cannot have zero-value lines\r\n for (let i = 0; i < doc.journalItems.length; i++) {\r\n const d = doc.journalItems[i].debit || 0;\r\n const c = doc.journalItems[i].credit || 0;\r\n if (d > 0 && c > 0) {\r\n throw new Error(\r\n `Journal item at index ${i}: cannot have both debit (${d}) and credit (${c}) greater than zero`,\r\n );\r\n }\r\n if (doc.state === 'posted' && d === 0 && c === 0) {\r\n throw new Error(\r\n `Journal item at index ${i}: posted entries cannot have zero-value lines (both debit and credit are 0)`,\r\n );\r\n }\r\n }\r\n\r\n // Calculate totals\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const totalDebit = doc.journalItems.reduce((s: number, i: any) => s + (i.debit || 0), 0);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const totalCredit = doc.journalItems.reduce((s: number, i: any) => s + (i.credit || 0), 0);\r\n\r\n // Enforce minimum items and balance for posted entries\r\n if (doc.state === 'posted') {\r\n if (doc.journalItems.length < 2) {\r\n throw new Error('Posted entries must have at least 2 journal items');\r\n }\r\n if (totalDebit !== totalCredit) {\r\n throw new Error('Total debit must equal total credit for posted entries');\r\n }\r\n }\r\n\r\n doc.totalDebit = totalDebit;\r\n doc.totalCredit = totalCredit;\r\n });\r\n\r\n // ── Pre-save: auto-generate reference number ─────────────────────────────\r\n\r\n if (autoReference) {\r\n // Helper: compute next reference number from DB\r\n // Uses aggregation pipeline to extract & sort the numeric suffix,\r\n // avoiding lexicographic sort issues beyond sequence 9999.\r\n const generateReferenceNumber = async (doc: Record<string, unknown>, Model: mongoose.Model<unknown>, session: unknown) => {\r\n const jt = (doc.journalType as string) || 'MISC';\r\n const d = new Date(doc.date as string | number | Date);\r\n const year = d.getFullYear();\r\n const month = String(d.getMonth() + 1).padStart(2, '0');\r\n const prefix = `${jt}/${year}/${month}/`;\r\n\r\n // Build match filter\r\n const matchFilter: Record<string, unknown> = {\r\n referenceNumber: { $regex: `^${prefix.replace(/\\//g, '\\\\/')}` },\r\n };\r\n\r\n // Add org field to query for multi-tenant\r\n if (multiTenant) {\r\n matchFilter[multiTenant.orgField] = doc[multiTenant.orgField];\r\n }\r\n\r\n // Extract numeric suffix via $split and sort numerically\r\n const pipeline: mongoose.PipelineStage[] = [\r\n { $match: matchFilter },\r\n {\r\n $addFields: {\r\n _refSeq: {\r\n $toInt: {\r\n $arrayElemAt: [{ $split: ['$referenceNumber', '/'] }, -1],\r\n },\r\n },\r\n },\r\n },\r\n { $sort: { _refSeq: -1 as const } },\r\n { $limit: 1 },\r\n { $project: { _refSeq: 1 } },\r\n ];\r\n\r\n const results = await Model.aggregate(pipeline)\r\n .session(session as mongoose.mongo.ClientSession | null);\r\n\r\n let seq = 1;\r\n if (results.length > 0 && typeof results[0]._refSeq === 'number') {\r\n seq = results[0]._refSeq + 1;\r\n }\r\n\r\n return `${prefix}${String(seq).padStart(4, '0')}`;\r\n };\r\n\r\n schema.pre('save', async function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n\r\n if (doc.isModified('journalType')) {\r\n doc.referenceNumber = undefined;\r\n }\r\n\r\n if (!doc.referenceNumber) {\r\n const session = doc.$session?.() ?? null;\r\n const Model = doc.constructor as mongoose.Model<unknown>;\r\n doc.referenceNumber = await generateReferenceNumber(doc, Model, session);\r\n }\r\n });\r\n\r\n // Retry on duplicate key error (race condition between concurrent inserts)\r\n const MAX_REF_RETRIES = 3;\r\n schema.post('save', async function (error: Error, doc: unknown, next: (err?: Error) => void) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const mongoError = error as any;\r\n // 11000 = MongoDB duplicate key error\r\n if (mongoError.code === 11000 && mongoError.keyPattern?.referenceNumber) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const entry = doc as any;\r\n const retryCount: number = entry.__refRetries ?? 0;\r\n if (retryCount >= MAX_REF_RETRIES) {\r\n next(new Error(\r\n `Failed to generate unique reference number after ${MAX_REF_RETRIES} retries. ` +\r\n 'Too many concurrent inserts for this period.',\r\n ));\r\n return;\r\n }\r\n entry.__refRetries = retryCount + 1;\r\n const session = entry.$session?.() ?? null;\r\n const Model = entry.constructor as mongoose.Model<unknown>;\r\n entry.referenceNumber = await generateReferenceNumber(entry, Model, session);\r\n try {\r\n await entry.save({ session });\r\n next();\r\n } catch (retryError) {\r\n next(retryError as Error);\r\n }\r\n } else {\r\n next(error);\r\n }\r\n });\r\n }\r\n\r\n // ── Indexes ──────────────────────────────────────────────────────────────\r\n\r\n if (indexes) {\r\n const org = multiTenant?.orgField;\r\n\r\n // Partial filter: unique constraint only applies to docs with a string\r\n // referenceNumber — allows multiple entries without a ref when autoReference is off.\r\n const refPartial = { partialFilterExpression: { referenceNumber: { $exists: true, $type: 'string' } } };\r\n\r\n if (org) {\r\n schema.index({ [org]: 1, referenceNumber: 1 }, { unique: true, ...refPartial });\r\n schema.index({ [org]: 1, state: 1, date: 1 });\r\n schema.index({ [org]: 1, date: -1 });\r\n schema.index({ [org]: 1, journalType: 1 });\r\n schema.index({ 'journalItems.account': 1, state: 1 });\r\n schema.index({ [org]: 1, 'journalItems.account': 1, date: 1, state: 1 });\r\n } else {\r\n schema.index({ referenceNumber: 1 }, { unique: true, ...refPartial });\r\n schema.index({ state: 1, date: 1 });\r\n schema.index({ date: -1 });\r\n schema.index({ journalType: 1 });\r\n schema.index({ 'journalItems.account': 1, state: 1 });\r\n }\r\n\r\n schema.index({ reversed: 1 });\r\n\r\n // Idempotency key: unique sparse index (only when enabled)\r\n if (config.idempotency) {\r\n const idempotencyIdx: Record<string, 1 | -1> = {};\r\n if (org) idempotencyIdx[org] = 1;\r\n idempotencyIdx.idempotencyKey = 1;\r\n schema.index(idempotencyIdx, {\r\n unique: true,\r\n partialFilterExpression: { idempotencyKey: { $exists: true, $ne: null } },\r\n });\r\n }\r\n }\r\n\r\n if (textSearch) {\r\n schema.index(\r\n { referenceNumber: 'text', label: 'text' },\r\n { weights: { referenceNumber: 10, label: 5 }, name: 'journal_text_idx' },\r\n );\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n return schema;\r\n}\r\n","/**\r\n * Fiscal Period Schema Factory\r\n *\r\n * Creates a Mongoose schema for tracking fiscal periods (months, quarters, years).\r\n * Supports closing periods to lock entries.\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, SchemaOptions } from '../types/engine.js';\r\n\r\nexport function createFiscalPeriodSchema(\r\n config: AccountingEngineConfig,\r\n options: SchemaOptions = {},\r\n) {\r\n const { multiTenant } = config;\r\n const { indexes = true, extraFields = {}, extraIndexes = [] } = options;\r\n\r\n const fields: Record<string, unknown> = {\r\n name: { type: String, required: true },\r\n startDate: { type: Date, required: true },\r\n endDate: { type: Date, required: true },\r\n closed: { type: Boolean, default: false },\r\n closedAt: { type: Date, default: null },\r\n closedBy: { type: String, default: null },\r\n closingEntryId: { type: mongoose.Schema.Types.ObjectId, default: null },\r\n reopenedAt: { type: Date, default: null },\r\n reopenedBy: { type: String, default: null },\r\n ...extraFields,\r\n };\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n if (indexes) {\r\n if (multiTenant) {\r\n const org = multiTenant.orgField;\r\n schema.index({ [org]: 1, startDate: 1, endDate: 1 }, { unique: true });\r\n schema.index({ [org]: 1, closed: 1 });\r\n } else {\r\n schema.index({ startDate: 1, endDate: 1 }, { unique: true });\r\n schema.index({ closed: 1 });\r\n }\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n // ── Overlap guard: prevent overlapping date ranges within a tenant ─────\r\n schema.pre('validate', async function () {\r\n const doc = this as mongoose.Document & { startDate: Date; endDate: Date; [key: string]: unknown };\r\n if (!doc.startDate || !doc.endDate) return;\r\n\r\n // A period overlaps if: existing.startDate < this.endDate AND existing.endDate > this.startDate\r\n const overlapQuery: Record<string, unknown> = {\r\n _id: { $ne: doc._id },\r\n startDate: { $lt: doc.endDate },\r\n endDate: { $gt: doc.startDate },\r\n };\r\n\r\n if (multiTenant) {\r\n overlapQuery[multiTenant.orgField] = doc[multiTenant.orgField];\r\n }\r\n\r\n const overlap = await doc.collection.findOne(overlapQuery);\r\n if (overlap) {\r\n const msg = `Fiscal period overlaps with existing period \"${overlap.name}\" (${new Date(overlap.startDate).toISOString().split('T')[0]} – ${new Date(overlap.endDate).toISOString().split('T')[0]}).`;\r\n doc.invalidate('startDate', msg, doc.startDate, 'overlap');\r\n }\r\n });\r\n\r\n return schema;\r\n}\r\n"],"mappings":"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"}
@@ -1,38 +0,0 @@
1
- import { o as SchemaOptions, r as JournalSchemaOptions, t as AccountingEngineConfig } from "./engine-BzBMpWuy.mjs";
2
- import mongoose from "mongoose";
3
-
4
- //#region src/schemas/account.schema.d.ts
5
- declare function createAccountSchema(config: AccountingEngineConfig, options?: SchemaOptions): mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
6
- timestamps: true;
7
- }, any, any, unknown, {
8
- [x: string]: any;
9
- } & Required<{
10
- _id: unknown;
11
- }> & {
12
- __v: number;
13
- }>;
14
- //#endregion
15
- //#region src/schemas/journal-entry.schema.d.ts
16
- declare function createJournalEntrySchema(config: AccountingEngineConfig, accountModelName: string, options?: JournalSchemaOptions): mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
17
- timestamps: true;
18
- }, any, any, unknown, {
19
- [x: string]: any;
20
- } & Required<{
21
- _id: unknown;
22
- }> & {
23
- __v: number;
24
- }>;
25
- //#endregion
26
- //#region src/schemas/fiscal-period.schema.d.ts
27
- declare function createFiscalPeriodSchema(config: AccountingEngineConfig, options?: SchemaOptions): mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
28
- timestamps: true;
29
- }, any, any, unknown, {
30
- [x: string]: any;
31
- } & Required<{
32
- _id: unknown;
33
- }> & {
34
- __v: number;
35
- }>;
36
- //#endregion
37
- export { createJournalEntrySchema as n, createAccountSchema as r, createFiscalPeriodSchema as t };
38
- //# sourceMappingURL=fiscal-period.schema-BRdKAjrr.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fiscal-period.schema-BRdKAjrr.d.mts","names":[],"sources":["../src/schemas/account.schema.ts","../src/schemas/journal-entry.schema.ts","../src/schemas/fiscal-period.schema.ts"],"mappings":";;;;iBAcgB,mBAAA,CACd,MAAA,EAAQ,sBAAA,EACR,OAAA,GAAS,aAAA,GAAkB,QAAA,CAAA,MAAA,MAAA,QAAA,CAAA,KAAA;;;;;;;;;;;iBCCb,wBAAA,CACd,MAAA,EAAQ,sBAAA,EACR,gBAAA,UACA,OAAA,GAAS,oBAAA,GAAyB,QAAA,CAAA,MAAA,MAAA,QAAA,CAAA,KAAA;;;;;;;;;;;iBCVpB,wBAAA,CACd,MAAA,EAAQ,sBAAA,EACR,OAAA,GAAS,aAAA,GAAkB,QAAA,CAAA,MAAA,MAAA,QAAA,CAAA,KAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"idempotency.plugin-CPxPt4vX.d.mts","names":[],"sources":["../src/plugins/double-entry.plugin.ts","../src/plugins/fiscal-lock.plugin.ts","../src/plugins/idempotency.plugin.ts"],"mappings":";;;;UAaU,oBAAA;EACR,EAAA,CAAG,KAAA,UAAe,QAAA,GAAW,IAAA,qBAAyB,OAAA;AAAA;AAAA,UAGvC,wBAAA;EAAA;EAEf,UAAA;;EAEA,iBAAA,GAAoB,KAAA;EAFpB;EAIA,YAAA,GAAe,KAAA;EAFK;EAIpB,QAAA;AAAA;AAAA,iBAGc,iBAAA,CAAkB,OAAA,GAAS,wBAAA;;cAyC3B,oBAAA;AAAA;;;;UCxDN,oBAAA;EACR,EAAA,CAAG,KAAA,UAAe,QAAA,GAAW,IAAA,qBAAyB,OAAA;AAAA;AAAA,UAGvC,uBAAA;EDAA;ECEf,iBAAA,EAAmB,KAAA;;EAEnB,iBAAA,GAAoB,KAAA;EDFpB;ECIA,QAAA;AAAA;AAAA,iBAGc,gBAAA,CAAiB,OAAA,EAAS,uBAAA;;cAK1B,oBAAA;AAAA;;;;UCpBN,kBAAA;EACR,EAAA,CAAG,KAAA,UAAe,QAAA,GAAW,IAAA,qBAAyB,OAAA;AAAA;AAAA,UAGvC,wBAAA;EFDoD;EEGnE,iBAAA,EAAmB,KAAA;EFAJ;EEEf,QAAA;AAAA;AAAA,iBAGc,iBAAA,CAAkB,OAAA,EAAS,wBAAA;;cAK3B,kBAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"idempotency.plugin-v9NQ_ta-.mjs","names":[],"sources":["../src/plugins/double-entry.plugin.ts","../src/plugins/fiscal-lock.plugin.ts","../src/plugins/idempotency.plugin.ts"],"sourcesContent":["/**\r\n * Double-Entry Validation Plugin for @classytic/mongokit\r\n *\r\n * Ensures every journal entry posted via the repository satisfies:\r\n * sum(debits) === sum(credits)\r\n *\r\n * Plugs into the before:create and before:update hooks.\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport { Errors } from '../utils/errors.js';\r\n\r\n/** Minimal interface matching @classytic/mongokit RepositoryInstance */\r\ninterface RepositoryInstance {\r\n on(event: string, listener: (data: unknown) => void | Promise<void>): unknown;\r\n}\r\n\r\nexport interface DoubleEntryPluginOptions {\r\n /** Only enforce on posted entries (default: true) */\r\n onlyOnPost?: boolean;\r\n /** Mongoose model — required to validate partial updates that only set state */\r\n JournalEntryModel?: Model<unknown>;\r\n /** Account model — when provided, posted creates verify account existence + tenant scoping */\r\n AccountModel?: Model<unknown>;\r\n /** Multi-tenant org field name (e.g. 'business'). Required for tenant-account integrity checks. */\r\n orgField?: string;\r\n}\r\n\r\nexport function doubleEntryPlugin(options: DoubleEntryPluginOptions = {}) {\r\n const { onlyOnPost = true, JournalEntryModel, AccountModel, orgField } = options;\r\n\r\n function validateItems(\r\n items: Array<{ debit?: number; credit?: number }>,\r\n data: Record<string, unknown>,\r\n ): void {\r\n // Each line must be debit OR credit (not both), and cannot be zero-value\r\n for (let i = 0; i < items.length; i++) {\r\n const d = items[i].debit ?? 0;\r\n const c = items[i].credit ?? 0;\r\n if (d > 0 && c > 0) {\r\n throw Errors.validation(\r\n `Invalid journal item at index ${i}: a line cannot have both debit (${d}) and credit (${c}) greater than zero.`,\r\n );\r\n }\r\n if (d === 0 && c === 0) {\r\n throw Errors.validation(\r\n `Invalid journal item at index ${i}: a line cannot have both debit and credit equal to zero.`,\r\n );\r\n }\r\n }\r\n\r\n const totalDebit = items.reduce((s, i) => s + (i.debit ?? 0), 0);\r\n const totalCredit = items.reduce((s, i) => s + (i.credit ?? 0), 0);\r\n\r\n // Integer cents — exact comparison, no floating-point drift possible.\r\n if (totalDebit !== totalCredit) {\r\n throw Errors.validation(\r\n `Double-entry violation: debits (${totalDebit}) ≠ credits (${totalCredit}). ` +\r\n `Difference: ${Math.abs(totalDebit - totalCredit)}`,\r\n );\r\n }\r\n\r\n // Sync totals onto the data object\r\n data.totalDebit = totalDebit;\r\n data.totalCredit = totalCredit;\r\n }\r\n\r\n return {\r\n name: 'accounting:double-entry',\r\n apply(repo: RepositoryInstance) {\r\n const validate = async (context: Record<string, unknown>) => {\r\n const data = context.data as Record<string, unknown> | undefined;\r\n if (!data) return;\r\n\r\n // Skip draft entries if configured\r\n if (onlyOnPost && data.state !== 'posted') return;\r\n\r\n const items = data.journalItems as Array<{ debit?: number; credit?: number; account?: unknown }> | undefined;\r\n\r\n // Posted entries must have at least 2 journal items\r\n if (data.state === 'posted' && (!items || items.length < 2)) {\r\n throw Errors.validation(\r\n `Cannot post entry: at least 2 journal items required, got ${items?.length ?? 0}.`,\r\n );\r\n }\r\n\r\n if (!items || items.length === 0) return;\r\n\r\n validateItems(items, data);\r\n\r\n // Account existence + tenant-account integrity (fail-closed for posted creates)\r\n if (data.state === 'posted') {\r\n if (!AccountModel) {\r\n throw new Error(\r\n 'doubleEntryPlugin: AccountModel is required to validate posted entries. ' +\r\n 'Pass AccountModel in plugin options to enable account existence and tenant integrity checks.',\r\n );\r\n }\r\n await validateAccounts(items, data, context);\r\n }\r\n };\r\n\r\n /** Verify all journal item accounts exist and belong to the same org */\r\n const validateAccounts = async (\r\n items: Array<{ account?: unknown }>,\r\n data: Record<string, unknown>,\r\n context: Record<string, unknown>,\r\n ) => {\r\n const accountIds = items\r\n .map(i => i.account)\r\n .filter(a => a != null && a !== '');\r\n\r\n if (accountIds.length === 0) {\r\n throw Errors.validation('Posted entry has items with missing accounts.');\r\n }\r\n\r\n const selectFields = orgField ? `_id ${orgField}` : '_id';\r\n const accounts = await AccountModel!.find({ _id: { $in: accountIds } })\r\n .select(selectFields)\r\n .session((context.session as import('mongoose').ClientSession) ?? null)\r\n .lean() as Array<Record<string, unknown>>;\r\n\r\n // Check all accounts exist\r\n const foundIds = new Set(accounts.map(a => String(a._id)));\r\n const missingCount = accountIds.filter(id => !foundIds.has(String(id))).length;\r\n if (missingCount > 0) {\r\n throw Errors.validation(\r\n `${missingCount} item(s) reference non-existent accounts.`,\r\n );\r\n }\r\n\r\n // Check tenant scoping\r\n if (orgField && data[orgField] != null) {\r\n const dataOrg = String(data[orgField]);\r\n const crossTenant = accounts.filter(a => String(a[orgField]) !== dataOrg);\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\r\n const validateUpdate = async (context: Record<string, unknown>) => {\r\n const data = context.data as Record<string, unknown> | undefined;\r\n if (!data) return;\r\n\r\n // ── Immutability guard: block modifications to posted entries ──────\r\n // Allow: idempotent state re-set (state: 'posted')\r\n // Block: everything else — including reversed/reversedBy (only settable via\r\n // reverse() which uses entry.save() directly, bypassing this hook)\r\n if (JournalEntryModel) {\r\n const id = context.id;\r\n if (id) {\r\n // Check if target entry is already posted\r\n const target = await JournalEntryModel.findById(id)\r\n .select('state')\r\n .session((context.session as import('mongoose').ClientSession) ?? null)\r\n .lean() as Record<string, unknown> | null;\r\n\r\n if (target?.state === 'posted') {\r\n // Block any state transition away from 'posted' (immutable ledger)\r\n if (data.state !== undefined && data.state !== 'posted') {\r\n throw Errors.immutable(\r\n 'Cannot change state of a posted journal entry. Posted entries are immutable.',\r\n );\r\n }\r\n\r\n // Only allow idempotent state re-set on posted entries.\r\n // reversed/reversedBy are NOT allowed through repository.update() —\r\n // reverse() uses entry.save() directly to bypass the plugin, so any\r\n // attempt to set these flags through the generic update path is illegitimate.\r\n const allowedKeys = new Set(['state']);\r\n const dataKeys = Object.keys(data);\r\n const hasDisallowedKeys = dataKeys.some(k => !allowedKeys.has(k));\r\n\r\n if (hasDisallowedKeys) {\r\n throw Errors.immutable(\r\n 'Cannot modify a posted journal entry. Use reverse() to create a correcting entry instead.',\r\n );\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (onlyOnPost && data.state !== 'posted') return;\r\n\r\n const items = data.journalItems as Array<{ debit?: number; credit?: number }> | undefined;\r\n\r\n if (items !== undefined) {\r\n // Items present in payload — validate directly\r\n if (items.length < 2) {\r\n throw Errors.validation(\r\n `Cannot post entry: at least 2 journal items required, got ${items.length}.`,\r\n );\r\n }\r\n validateItems(items, data);\r\n\r\n // Account existence + tenant-account integrity (when AccountModel provided)\r\n if (AccountModel) {\r\n await validateAccounts(items as Array<{ account?: unknown }>, data, context);\r\n }\r\n return;\r\n }\r\n\r\n // state → posted but no journalItems in payload: fetch the persisted doc\r\n if (!JournalEntryModel) {\r\n throw new Error(\r\n 'doubleEntryPlugin: JournalEntryModel is required to validate partial updates that set state to \"posted\". ' +\r\n 'Pass JournalEntryModel in plugin options.',\r\n );\r\n }\r\n\r\n const id = context.id;\r\n if (!id) {\r\n throw new Error(\r\n 'doubleEntryPlugin: update context is missing \"id\". Cannot validate partial post without document ID.',\r\n );\r\n }\r\n\r\n const existing = await JournalEntryModel.findById(id)\r\n .select('journalItems')\r\n .session((context.session as import('mongoose').ClientSession) ?? null)\r\n .lean() as Record<string, unknown> | null;\r\n\r\n if (!existing) return; // will 404 downstream\r\n\r\n const persistedItems = existing.journalItems as Array<{ debit?: number; credit?: number; account?: unknown }> | undefined;\r\n if (!persistedItems || persistedItems.length < 2) {\r\n throw Errors.validation(\r\n `Cannot post entry: at least 2 journal items required, got ${persistedItems?.length ?? 0}.`,\r\n );\r\n }\r\n\r\n validateItems(persistedItems, data);\r\n\r\n // Account existence + tenant-account integrity (when AccountModel provided)\r\n if (AccountModel) {\r\n await validateAccounts(persistedItems, { ...data, ...existing }, context);\r\n }\r\n };\r\n\r\n repo.on('before:create', (payload: unknown) => validate(payload as Record<string, unknown>));\r\n repo.on('before:update', (payload: unknown) => validateUpdate(payload as Record<string, unknown>));\r\n },\r\n };\r\n}\r\n","/**\r\n * Fiscal Lock Plugin for @classytic/mongokit\r\n *\r\n * Prevents journal entries from being created or posted\r\n * in a closed fiscal period.\r\n *\r\n * Requires a FiscalPeriod model to check against.\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport { Errors } from '../utils/errors.js';\r\n\r\n/** Minimal interface matching @classytic/mongokit RepositoryInstance */\r\ninterface RepositoryInstance {\r\n on(event: string, listener: (data: unknown) => void | Promise<void>): unknown;\r\n}\r\n\r\nexport interface FiscalLockPluginOptions {\r\n /** Mongoose model for fiscal periods */\r\n FiscalPeriodModel: Model<unknown>;\r\n /** Mongoose model for journal entries — needed to look up persisted date on partial updates */\r\n JournalEntryModel?: Model<unknown>;\r\n /** Organization field name (for multi-tenant) */\r\n orgField?: string;\r\n}\r\n\r\nexport function fiscalLockPlugin(options: FiscalLockPluginOptions) {\r\n const { FiscalPeriodModel, JournalEntryModel, orgField } = options;\r\n\r\n return {\r\n name: 'accounting:fiscal-lock',\r\n apply(repo: RepositoryInstance) {\r\n const checkPeriod = async (context: Record<string, unknown>, isUpdate: boolean) => {\r\n const data = context.data as Record<string, unknown> | undefined;\r\n if (!data) return;\r\n\r\n // Only check when posting or creating posted entries\r\n if (data.state !== 'posted') return;\r\n\r\n const session = (context.session as ClientSession) ?? null;\r\n\r\n // Resolve the entry date (and org field from persisted doc if needed)\r\n let entryDate: Date | undefined;\r\n let persistedDoc: Record<string, unknown> | null = null;\r\n\r\n if (data.date) {\r\n entryDate = new Date(data.date as string | number | Date);\r\n } else if (!isUpdate) {\r\n // Create without explicit date — schema will default to now, so check against now\r\n entryDate = new Date();\r\n } else {\r\n // Partial update without date — fetch the persisted doc\r\n if (!context.id) {\r\n throw new Error(\r\n 'fiscalLockPlugin: update context is missing \"id\". Cannot validate fiscal lock without document ID.',\r\n );\r\n }\r\n if (!JournalEntryModel) {\r\n throw new Error(\r\n 'fiscalLockPlugin: JournalEntryModel is required to validate partial updates that set state to \"posted\". ' +\r\n 'Pass JournalEntryModel in plugin options.',\r\n );\r\n }\r\n const selectFields = orgField ? `date ${orgField}` : 'date';\r\n persistedDoc = await JournalEntryModel.findById(context.id)\r\n .select(selectFields)\r\n .session(session)\r\n .lean() as Record<string, unknown> | null;\r\n if (persistedDoc?.date) {\r\n entryDate = new Date(persistedDoc.date as string | number | Date);\r\n }\r\n }\r\n\r\n if (!entryDate) return; // No date to check against (new entry without date defaults to draft)\r\n\r\n // Build query\r\n const query: Record<string, unknown> = {\r\n startDate: { $lte: entryDate },\r\n endDate: { $gte: entryDate },\r\n closed: true,\r\n };\r\n\r\n // Multi-tenant scope — check payload, context, then persisted doc\r\n if (orgField) {\r\n let orgValue = data[orgField] ?? context[orgField];\r\n\r\n if (!orgValue && isUpdate) {\r\n // Org field not in payload or context — resolve from persisted doc\r\n if (persistedDoc) {\r\n orgValue = persistedDoc[orgField];\r\n } else if (context.id && JournalEntryModel) {\r\n const persisted = await JournalEntryModel.findById(context.id)\r\n .select(orgField)\r\n .session(session)\r\n .lean() as Record<string, unknown> | null;\r\n if (persisted) orgValue = persisted[orgField];\r\n }\r\n }\r\n\r\n if (!orgValue) {\r\n throw new Error(\r\n `fiscalLockPlugin: orgField \"${orgField}\" is configured but could not be resolved from ` +\r\n 'payload, context, or persisted document. Refusing to run unscoped fiscal period query.',\r\n );\r\n }\r\n\r\n query[orgField] = orgValue;\r\n }\r\n\r\n const closedPeriod = await FiscalPeriodModel.findOne(query).session(session).lean();\r\n\r\n if (closedPeriod) {\r\n const period = closedPeriod as Record<string, unknown>;\r\n throw Errors.fiscal(\r\n `Cannot post entry dated ${entryDate.toISOString().split('T')[0]}: ` +\r\n `fiscal period \"${period.name}\" is closed.`,\r\n );\r\n }\r\n };\r\n\r\n repo.on('before:create', (payload: unknown) => checkPeriod(payload as Record<string, unknown>, false));\r\n repo.on('before:update', (payload: unknown) => checkPeriod(payload as Record<string, unknown>, true));\r\n },\r\n };\r\n}\r\n","/**\r\n * Idempotency Plugin for @classytic/mongokit\r\n *\r\n * Prevents duplicate journal entries by checking for existing entries\r\n * with the same idempotency key before creation.\r\n */\r\n\r\nimport type { Model, ClientSession } from 'mongoose';\r\nimport { Errors } from '../utils/errors.js';\r\n\r\n/** Minimal interface matching @classytic/mongokit RepositoryInstance */\r\ninterface RepositoryInstance {\r\n on(event: string, listener: (data: unknown) => void | Promise<void>): unknown;\r\n}\r\n\r\nexport interface IdempotencyPluginOptions {\r\n /** Mongoose model for journal entries */\r\n JournalEntryModel: Model<unknown>;\r\n /** Multi-tenant org field name */\r\n orgField?: string;\r\n}\r\n\r\nexport function idempotencyPlugin(options: IdempotencyPluginOptions) {\r\n const { JournalEntryModel, orgField } = options;\r\n\r\n return {\r\n name: 'accounting:idempotency',\r\n apply(repo: RepositoryInstance) {\r\n repo.on('before:create', async (raw: unknown) => {\r\n const context = raw as Record<string, unknown>;\r\n const data = context.data as Record<string, unknown> | undefined;\r\n if (!data?.idempotencyKey) return;\r\n\r\n const query: Record<string, unknown> = {\r\n idempotencyKey: data.idempotencyKey,\r\n };\r\n if (orgField && data[orgField]) {\r\n query[orgField] = data[orgField];\r\n }\r\n\r\n const existing = await JournalEntryModel.findOne(query)\r\n .select('_id')\r\n .session((context.session as ClientSession) ?? null)\r\n .lean() as Record<string, unknown> | null;\r\n\r\n if (existing) {\r\n throw Errors.conflict(\r\n `Duplicate idempotency key: \"${data.idempotencyKey}\". Existing entry: ${existing._id}`,\r\n );\r\n }\r\n });\r\n },\r\n };\r\n}\r\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-BPukb3L8.d.mts","names":[],"sources":["../src/exports/types.ts","../src/exports/csv-serializer.ts","../src/exports/field-map.ts","../src/exports/flatten-journal.ts","../src/exports/field-maps/quickbooks.ts","../src/exports/field-maps/universal.ts"],"mappings":";;AAeA;;;;;;;;;;;UAAiB,gBAAA;EACf,GAAA;EACA,eAAA;EACA,IAAA;EACA,MAAA;EACA,aAAA;AAAA;;;;UAMe,oBAAA;EACf,OAAA,EAAS,gBAAA;EACT,KAAA;EACA,IAAA,GAAO,IAAA;EACP,KAAA;EACA,MAAA;EACA,UAAA,GAAa,KAAA;IAAQ,OAAA;IAAkB,OAAA;EAAA;EAE3B;EAAA,CAAX,GAAA;AAAA;;;;;UAOc,qBAAA;EACf,GAAA;EACA,WAAA;EACA,eAAA;EACA,KAAA;EACA,IAAA,EAAM,IAAA;EACN,YAAA,EAAc,oBAAA;EACd,UAAA;EACA,WAAA;EACA,KAAA;EACA,QAAA;EACA,SAAA,GAAY,IAAA;EACZ,SAAA,GAAY,IAAA;EAAA,CACX,GAAA;AAAA;;;;;UASc,cAAA;EACf,OAAA;EACA,WAAA;EACA,eAAA;EACA,UAAA;EACA,SAAA,EAAW,IAAA;EACX,KAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EAEA,SAAA;EACA,WAAA;EACA,eAAA;EACA,SAAA;EACA,QAAA,EAAU,IAAA;EACV,KAAA;EACA,MAAA;EACA,OAAA;EACA,OAAA;EAEA,SAAA;EACA,SAAA;EARA;EAAA,CAWC,GAAA;AAAA;;;;;UASc,WAAA,QAAmB,cAAA;EAAA,SACzB,MAAA;EAAA,SACA,OAAA,GAAU,GAAA,EAAK,IAAA;AAAA;;AAF1B;;UAQiB,cAAA,QAAsB,cAAA;EAAA,SAC5B,IAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA,WAAiB,WAAA,CAAY,IAAA;AAAA;AAAA,UAKvB,UAAA;EACf,SAAA;EACA,cAAA;EACA,cAAA;AAAA;;;;iBCvGc,UAAA,CAAW,KAAA;;iBAQX,YAAA,CACd,IAAA,kCACA,OAAA,GAAS,UAAA;ADGX;AAAA,iBCOgB,QAAA,CACd,OAAA,qBACA,QAAA,kCACA,OAAA,GAAS,UAAA;;;;iBC1BK,UAAA,MAAA,CAAiB,QAAA,EAAU,cAAA,CAAe,IAAA;;iBAK1C,UAAA,MAAA,CAAiB,QAAA,EAAU,cAAA,CAAe,IAAA,GAAO,GAAA,EAAK,IAAA;;iBAKtD,cAAA,MAAA,CACd,QAAA,EAAU,cAAA,CAAe,IAAA,GACzB,IAAA,WAAe,IAAA;AFIjB;AAAA,iBEEgB,WAAA,MAAA,CACd,QAAA,EAAU,cAAA,CAAe,IAAA,GACzB,IAAA,WAAe,IAAA,IACf,OAAA,GAAU,UAAA;;;;iBCGI,mBAAA,CAAoB,KAAA,EAAO,qBAAA,GAAwB,cAAA;;iBAiDnD,qBAAA,CACd,OAAA,WAAkB,qBAAA,KACjB,cAAA;;;cC5DU,kBAAA,EAAoB,cAAA,CAAe,cAAA;;;cCJnC,iBAAA,EAAmB,cAAA,CAAe,cAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-ZnSiqHYV.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.d.mts","names":[],"sources":["../src/engine.ts","../src/utils/date-range.ts","../src/utils/account-helpers.ts","../src/utils/errors.ts","../src/utils/session.ts","../src/utils/filter-builder.ts","../src/types/contracts.ts"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/engine.ts"],"sourcesContent":["/**\n * AccountingEngine — The main entry point for @classytic/ledger.\n *\n * Usage:\n * const accounting = createAccountingEngine({\n * country: canadaPack,\n * currency: 'CAD',\n * multiTenant: { orgField: 'business', orgRef: 'Business' },\n * });\n *\n * const AccountSchema = accounting.createAccountSchema();\n * const JournalEntrySchema = accounting.createJournalEntrySchema('Account');\n * const FiscalPeriodSchema = accounting.createFiscalPeriodSchema();\n *\n * // Register models\n * const Account = mongoose.model('Account', AccountSchema);\n * const JournalEntry = mongoose.model('JournalEntry', JournalEntrySchema);\n * const FiscalPeriod = mongoose.model('FiscalPeriod', FiscalPeriodSchema);\n *\n * // Reports\n * const reports = accounting.createReports({ Account, JournalEntry });\n * const bs = await reports.balanceSheet({ dateOption: 'year', dateValue: 2025, organizationId: '...' });\n */\n\nimport type { Model } from 'mongoose';\nimport type { AccountingEngineConfig, SchemaOptions, JournalSchemaOptions } from './types/engine.js';\nimport type { CountryPack } from './country/index.js';\nimport { createAccountSchema } from './schemas/account.schema.js';\nimport { createJournalEntrySchema } from './schemas/journal-entry.schema.js';\nimport { createFiscalPeriodSchema } from './schemas/fiscal-period.schema.js';\nimport { generateTrialBalance } from './reports/trial-balance.js';\nimport { generateBalanceSheet } from './reports/balance-sheet.js';\nimport { generateIncomeStatement } from './reports/income-statement.js';\nimport { generateGeneralLedger } from './reports/general-ledger.js';\nimport { generateCashFlow } from './reports/cash-flow.js';\nimport { Money } from './money.js';\nimport { wireJournalEntryMethods } from './repositories/journal-entry.repository.js';\nimport { wireAccountMethods } from './repositories/account.repository.js';\nimport { doubleEntryPlugin } from './plugins/double-entry.plugin.js';\nimport { fiscalLockPlugin } from './plugins/fiscal-lock.plugin.js';\nimport { idempotencyPlugin } from './plugins/idempotency.plugin.js';\n\nexport class AccountingEngine {\n readonly config: AccountingEngineConfig;\n readonly country: CountryPack;\n readonly currency: string;\n readonly money = Money;\n\n constructor(config: AccountingEngineConfig) {\n this.config = config;\n this.country = config.country;\n this.currency = config.currency;\n }\n\n // ── Schema Factories ───────────────────────────────────────────────────────\n\n createAccountSchema(options?: SchemaOptions) {\n return createAccountSchema(this.config, options);\n }\n\n createJournalEntrySchema(accountModelName: string, options?: JournalSchemaOptions) {\n return createJournalEntrySchema(this.config, accountModelName, options);\n }\n\n createFiscalPeriodSchema(options?: SchemaOptions) {\n return createFiscalPeriodSchema(this.config, options);\n }\n\n // ── Report Engine ──────────────────────────────────────────────────────────\n\n createReports(models: {\n Account: Model<unknown>;\n JournalEntry: Model<unknown>;\n }) {\n const { Account: AccountModel, JournalEntry: JournalEntryModel } = models;\n const { country, config } = this;\n const orgField = config.multiTenant?.orgField;\n const fiscalYearStartMonth = config.fiscalYearStartMonth ?? 1;\n const retainedEarningsCode = config.retainedEarningsCode;\n const currentYearEarningsCode = config.currentYearEarningsCode;\n\n return {\n trialBalance: (params: {\n organizationId?: unknown;\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\n dateValue: unknown;\n accountId?: string;\n filters?: Record<string, unknown>;\n }) =>\n generateTrialBalance(\n { AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth },\n params,\n ),\n\n balanceSheet: (params: {\n organizationId?: unknown;\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\n dateValue: unknown;\n businessName?: string;\n filters?: Record<string, unknown>;\n }) =>\n generateBalanceSheet(\n { AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth, retainedEarningsCode, currentYearEarningsCode },\n params,\n ),\n\n incomeStatement: (params: {\n organizationId?: unknown;\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\n dateValue: unknown;\n businessName?: string;\n filters?: Record<string, unknown>;\n }) =>\n generateIncomeStatement(\n { AccountModel, JournalEntryModel, country, orgField },\n params,\n ),\n\n generalLedger: (params: {\n organizationId?: unknown;\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\n dateValue: unknown;\n accountId?: string;\n filters?: Record<string, unknown>;\n }) =>\n generateGeneralLedger(\n { AccountModel, JournalEntryModel, country, orgField, fiscalYearStartMonth },\n params,\n ),\n\n cashFlow: (params: {\n organizationId?: unknown;\n dateOption: 'month' | 'quarter' | 'year' | 'custom';\n dateValue: unknown;\n businessName?: string;\n filters?: Record<string, unknown>;\n }) =>\n generateCashFlow(\n { AccountModel, JournalEntryModel, country, orgField },\n params,\n ),\n };\n }\n\n // ── Account Type Helpers ───────────────────────────────────────────────────\n\n /** Get all posting account types (accounts you can post transactions to) */\n getPostingAccountTypes() {\n return this.country.getPostingAccountTypes();\n }\n\n /** Validate an account type code */\n isValidAccountType(code: string) {\n return this.country.isValidAccountType(code);\n }\n\n /** Get account type definition by code */\n getAccountType(code: string) {\n return this.country.getAccountType(code);\n }\n\n /** Get tax codes for a region */\n getTaxCodesForRegion(region: string) {\n return this.country.getTaxCodesForRegion(region);\n }\n\n // ── Repository Factories ─────────────────────────────────────────────────\n\n /**\n * Create a fully-configured journal entry repository with secure plugin wiring.\n * This is the **recommended** way to set up journal entry repositories.\n *\n * Includes:\n * - Double-entry plugin with account existence + tenant integrity validation\n * - Fiscal lock plugin (when FiscalPeriodModel is provided)\n * - post(), unpost(), reverse(), and duplicate() domain methods\n *\n * @param createRepository - The `createRepository` function from @classytic/mongokit\n * @param models.JournalEntryModel - Mongoose model for journal entries\n * @param models.AccountModel - Mongoose model for accounts (required for secure posted-create validation)\n * @param models.FiscalPeriodModel - Mongoose model for fiscal periods (optional, enables fiscal lock)\n * @param additionalPlugins - Extra plugins to include (e.g. timestampPlugin)\n * @returns A wired repository with post(), unpost(), reverse(), duplicate(), and all plugins configured\n */\n createJournalEntryRepository(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n createRepository: (model: Model<unknown>, plugins: any[]) => any,\n models: {\n JournalEntryModel: Model<unknown>;\n AccountModel: Model<unknown>;\n FiscalPeriodModel?: Model<unknown>;\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n additionalPlugins: any[] = [],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): any {\n const orgField = this.config.multiTenant?.orgField;\n const { JournalEntryModel, AccountModel, FiscalPeriodModel } = models;\n\n const plugins = [\n ...additionalPlugins,\n doubleEntryPlugin({\n JournalEntryModel,\n AccountModel,\n orgField,\n }),\n ];\n\n if (FiscalPeriodModel) {\n plugins.push(\n fiscalLockPlugin({\n FiscalPeriodModel,\n JournalEntryModel,\n orgField,\n }),\n );\n }\n\n if (this.config.idempotency) {\n plugins.push(\n idempotencyPlugin({\n JournalEntryModel,\n orgField,\n }),\n );\n }\n\n const repository = createRepository(JournalEntryModel, plugins);\n wireJournalEntryMethods(repository, JournalEntryModel, orgField, this.config.strictness);\n return repository;\n }\n\n /**\n * Wire post/reverse domain methods onto a mongokit Repository\n * for journal entries. The repository must already be created via\n * `createRepository(Model, plugins)` from @classytic/mongokit.\n *\n * **Note:** Prefer `createJournalEntryRepository()` which guarantees\n * secure plugin wiring. This method only adds domain methods and does\n * not validate plugin configuration.\n *\n * @param repository - An existing mongokit Repository instance\n * @param JournalEntryModel - The Mongoose model for journal entries\n * @returns The same repository, now with `.post()` and `.reverse()`\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireJournalEntryRepository(repository: any, JournalEntryModel: Model<unknown>): any {\n const orgField = this.config.multiTenant?.orgField;\n wireJournalEntryMethods(repository, JournalEntryModel, orgField, this.config.strictness);\n return repository;\n }\n\n /**\n * Wire seedAccounts/bulkCreate and posting-account validation onto a\n * mongokit Repository for accounts. The repository must already be\n * created via `createRepository(Model, plugins)` from @classytic/mongokit.\n *\n * @param repository - An existing mongokit Repository instance\n * @param AccountModel - The Mongoose model for accounts\n * @returns The same repository, now with `.seedAccounts()` and `.bulkCreate()`\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireAccountRepository(repository: any, AccountModel: Model<unknown>): any {\n const orgField = this.config.multiTenant?.orgField;\n wireAccountMethods(repository, AccountModel, this.country, orgField);\n return repository;\n }\n}\n\n// ── Factory ────────────────────────────────────────────────────────────────\n\nexport function createAccountingEngine(config: AccountingEngineConfig): AccountingEngine {\n return new AccountingEngine(config);\n}\n"],"mappings":";;;;;;;;;;;;;AA0CA,IAAa,mBAAb,MAA8B;CAC5B;CACA;CACA;CACA,QAAiB;CAEjB,YAAY,QAAgC;AAC1C,OAAK,SAAS;AACd,OAAK,UAAU,OAAO;AACtB,OAAK,WAAW,OAAO;;CAKzB,oBAAoB,SAAyB;AAC3C,SAAO,oBAAoB,KAAK,QAAQ,QAAQ;;CAGlD,yBAAyB,kBAA0B,SAAgC;AACjF,SAAO,yBAAyB,KAAK,QAAQ,kBAAkB,QAAQ;;CAGzE,yBAAyB,SAAyB;AAChD,SAAO,yBAAyB,KAAK,QAAQ,QAAQ;;CAKvD,cAAc,QAGX;EACD,MAAM,EAAE,SAAS,cAAc,cAAc,sBAAsB;EACnE,MAAM,EAAE,SAAS,WAAW;EAC5B,MAAM,WAAW,OAAO,aAAa;EACrC,MAAM,uBAAuB,OAAO,wBAAwB;EAC5D,MAAM,uBAAuB,OAAO;EACpC,MAAM,0BAA0B,OAAO;AAEvC,SAAO;GACL,eAAe,WAOb,qBACE;IAAE;IAAc;IAAmB;IAAS;IAAU;IAAsB,EAC5E,OACD;GAEH,eAAe,WAOb,qBACE;IAAE;IAAc;IAAmB;IAAS;IAAU;IAAsB;IAAsB;IAAyB,EAC3H,OACD;GAEH,kBAAkB,WAOhB,wBACE;IAAE;IAAc;IAAmB;IAAS;IAAU,EACtD,OACD;GAEH,gBAAgB,WAOd,sBACE;IAAE;IAAc;IAAmB;IAAS;IAAU;IAAsB,EAC5E,OACD;GAEH,WAAW,WAOT,iBACE;IAAE;IAAc;IAAmB;IAAS;IAAU,EACtD,OACD;GACJ;;;CAMH,yBAAyB;AACvB,SAAO,KAAK,QAAQ,wBAAwB;;;CAI9C,mBAAmB,MAAc;AAC/B,SAAO,KAAK,QAAQ,mBAAmB,KAAK;;;CAI9C,eAAe,MAAc;AAC3B,SAAO,KAAK,QAAQ,eAAe,KAAK;;;CAI1C,qBAAqB,QAAgB;AACnC,SAAO,KAAK,QAAQ,qBAAqB,OAAO;;;;;;;;;;;;;;;;;;CAqBlD,6BAEE,kBACA,QAMA,oBAA2B,EAAE,EAExB;EACL,MAAM,WAAW,KAAK,OAAO,aAAa;EAC1C,MAAM,EAAE,mBAAmB,cAAc,sBAAsB;EAE/D,MAAM,UAAU,CACd,GAAG,mBACH,kBAAkB;GAChB;GACA;GACA;GACD,CAAC,CACH;AAED,MAAI,kBACF,SAAQ,KACN,iBAAiB;GACf;GACA;GACA;GACD,CAAC,CACH;AAGH,MAAI,KAAK,OAAO,YACd,SAAQ,KACN,kBAAkB;GAChB;GACA;GACD,CAAC,CACH;EAGH,MAAM,aAAa,iBAAiB,mBAAmB,QAAQ;AAC/D,0BAAwB,YAAY,mBAAmB,UAAU,KAAK,OAAO,WAAW;AACxF,SAAO;;;;;;;;;;;;;;;CAiBT,2BAA2B,YAAiB,mBAAwC;EAClF,MAAM,WAAW,KAAK,OAAO,aAAa;AAC1C,0BAAwB,YAAY,mBAAmB,UAAU,KAAK,OAAO,WAAW;AACxF,SAAO;;;;;;;;;;;CAaT,sBAAsB,YAAiB,cAAmC;EACxE,MAAM,WAAW,KAAK,OAAO,aAAa;AAC1C,qBAAmB,YAAY,cAAc,KAAK,SAAS,SAAS;AACpE,SAAO;;;AAMX,SAAgB,uBAAuB,QAAkD;AACvF,QAAO,IAAI,iBAAiB,OAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"journals-oH-FK3g8.mjs","names":[],"sources":["../src/constants/journals.ts"],"sourcesContent":["/**\r\n * Journal Types — Standard journal classifications.\r\n * Extensible: country packs can add custom journal types.\r\n */\r\n\r\nimport type { JournalType } from '../types/core.js';\r\n\r\nexport const JOURNAL_TYPES: Readonly<Record<string, JournalType>> = Object.freeze({\r\n SALES: { code: 'SALES', name: 'Sales Journal', description: 'Sales transactions and revenue' },\r\n PURCHASES: { code: 'PURCHASES', name: 'Purchases Journal', description: 'Purchase transactions and expenses' },\r\n CASH_RECEIPTS: { code: 'CASH_RECEIPTS', name: 'Cash Receipts Journal', description: 'Cash and bank deposits received' },\r\n CASH_PAYMENTS: { code: 'CASH_PAYMENTS', name: 'Cash Payments Journal', description: 'Cash and bank payments made' },\r\n PAYROLL: { code: 'PAYROLL', name: 'Payroll Journal', description: 'Employee wages, salaries, and related expenses' },\r\n GENERAL: { code: 'GENERAL', name: 'General Journal', description: 'Adjusting entries, corrections, and misc transactions' },\r\n INVENTORY: { code: 'INVENTORY', name: 'Inventory Journal', description: 'Inventory adjustments and movements' },\r\n FIXED_ASSETS: { code: 'FIXED_ASSETS', name: 'Fixed Assets Journal', description: 'Asset purchases, disposals, and depreciation' },\r\n BANK_RECONCILIATION:{ code: 'BANK_RECONCILIATION', name: 'Bank Reconciliation', description: 'Bank reconciliation adjustments' },\r\n DEPRECIATION: { code: 'DEPRECIATION', name: 'Depreciation Journal', description: 'Periodic depreciation expenses' },\r\n YEAR_END: { code: 'YEAR_END', name: 'Year-End Adjustments', description: 'Year-end closing and adjustment entries' },\r\n ACCOUNTS_RECEIVABLE:{ code: 'ACCOUNTS_RECEIVABLE', name: 'Accounts Receivable', description: 'Customer invoices and receivable transactions' },\r\n ACCOUNTS_PAYABLE: { code: 'ACCOUNTS_PAYABLE', name: 'Accounts Payable', description: 'Vendor bills and payable transactions' },\r\n TAX: { code: 'TAX', name: 'Tax Journal', description: 'GST/HST/PST and other tax-related entries' },\r\n MISC: { code: 'MISC', name: 'Miscellaneous', description: 'Transactions that don\\'t fit other categories' },\r\n});\r\n\r\nexport const JOURNAL_CODES = Object.freeze(\r\n Object.fromEntries(Object.keys(JOURNAL_TYPES).map(k => [k, k])) as Record<string, string>,\r\n);\r\n\r\nexport function getJournalTypeCodes(): string[] {\r\n return Object.keys(JOURNAL_TYPES);\r\n}\r\n\r\nexport function isValidJournalType(code: string): boolean {\r\n return code in JOURNAL_TYPES;\r\n}\r\n\r\nexport function getJournalType(code: string): JournalType | null {\r\n return JOURNAL_TYPES[code] ?? null;\r\n}\r\n"],"mappings":";AAOA,MAAa,gBAAuD,OAAO,OAAO;CAChF,OAAoB;EAAE,MAAM;EAAS,MAAM;EAAiB,aAAa;EAAkC;CAC3G,WAAoB;EAAE,MAAM;EAAa,MAAM;EAAqB,aAAa;EAAsC;CACvH,eAAoB;EAAE,MAAM;EAAiB,MAAM;EAAyB,aAAa;EAAmC;CAC5H,eAAoB;EAAE,MAAM;EAAiB,MAAM;EAAyB,aAAa;EAA+B;CACxH,SAAoB;EAAE,MAAM;EAAW,MAAM;EAAmB,aAAa;EAAkD;CAC/H,SAAoB;EAAE,MAAM;EAAW,MAAM;EAAmB,aAAa;EAAyD;CACtI,WAAoB;EAAE,MAAM;EAAa,MAAM;EAAqB,aAAa;EAAuC;CACxH,cAAoB;EAAE,MAAM;EAAgB,MAAM;EAAwB,aAAa;EAAgD;CACvI,qBAAoB;EAAE,MAAM;EAAuB,MAAM;EAAuB,aAAa;EAAmC;CAChI,cAAoB;EAAE,MAAM;EAAgB,MAAM;EAAwB,aAAa;EAAkC;CACzH,UAAoB;EAAE,MAAM;EAAY,MAAM;EAAwB,aAAa;EAA2C;CAC9H,qBAAoB;EAAE,MAAM;EAAuB,MAAM;EAAuB,aAAa;EAAiD;CAC9I,kBAAoB;EAAE,MAAM;EAAoB,MAAM;EAAoB,aAAa;EAAyC;CAChI,KAAoB;EAAE,MAAM;EAAO,MAAM;EAAe,aAAa;EAA6C;CAClH,MAAoB;EAAE,MAAM;EAAQ,MAAM;EAAiB,aAAa;EAAiD;CAC1H,CAAC;AAEF,MAAa,gBAAgB,OAAO,OAClC,OAAO,YAAY,OAAO,KAAK,cAAc,CAAC,KAAI,MAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAChE;AAED,SAAgB,sBAAgC;AAC9C,QAAO,OAAO,KAAK,cAAc;;AAGnC,SAAgB,mBAAmB,MAAuB;AACxD,QAAO,QAAQ;;AAGjB,SAAgB,eAAe,MAAkC;AAC/D,QAAO,cAAc,SAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"logger-UbTdBb1x.d.mts","names":[],"sources":["../src/utils/logger.ts"],"mappings":";;AAIA;;;UAAiB,MAAA;EACf,IAAA,CAAK,OAAA,UAAiB,IAAA,GAAO,MAAA;EAC7B,KAAA,CAAM,OAAA,UAAiB,IAAA,GAAO,MAAA;EAC9B,IAAA,CAAK,OAAA,UAAiB,IAAA,GAAO,MAAA;AAAA;;cAIlB,aAAA,EAAe,MAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"money.d.mts","names":[],"sources":["../src/money.ts"],"mappings":";;AA4BA;;;;;AAKA;;;;;AAMA;;;;;AAMA;;;;;AAKA;;;;iBAtBgB,KAAA,CAAM,MAAA;AA2BtB;AAAA,iBAtBgB,WAAA,CAAY,OAAA,UAAiB,SAAA;;iBAM7B,SAAA,CAAU,KAAA,UAAe,SAAA;;iBAMzB,GAAA,CAAI,CAAA,UAAW,CAAA;;iBAKf,QAAA,CAAS,CAAA,UAAW,CAAA;;iBAKpB,QAAA,CAAS,KAAA,UAAe,MAAA;AAgBxC;;;;AAAA,iBARgB,UAAA,CAAW,KAAA,UAAe,IAAA;;;;;iBAQ1B,iBAAA,CACd,eAAA,UACA,OAAA;EACG,IAAA;EAAc,GAAA;AAAA;;;;;iBAUH,iBAAA,CACd,eAAA,UACA,OAAA;EACG,IAAA;EAAc,GAAA;EAAa,KAAA;AAAA;;;;;AA0ChC;;;iBA5BgB,QAAA,CAAS,UAAA,UAAoB,MAAA;;iBA4B7B,MAAA,CAAO,CAAA,UAAW,CAAA;;iBAKlB,MAAA,CAAO,KAAA;;iBAKP,UAAA,CAAW,KAAA;AAA3B;AAAA,iBAKgB,UAAA,CAAW,KAAA;;iBAKX,GAAA,CAAI,KAAA;;iBAKJ,MAAA,CAAO,KAAA;;iBAKP,GAAA,CAAI,CAAA,UAAW,CAAA;;iBAKf,GAAA,CAAI,CAAA,UAAW,CAAA;AAf/B;;;;;AAAA,iBA0BgB,MAAA,CACd,KAAA,UACA,YAAA,WACA,MAAA,WACA,SAAA;;;;;iBAac,WAAA,CAAY,KAAA,UAAe,SAAA;;iBAO3B,OAAA,CAAQ,KAAA,YAAiB,KAAA;;;AAnCzC;;;iBA4CgB,UAAA,CAAW,KAAA,mBAAwB,SAAA;AAAA,cAWtC,KAAA;EAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"money.mjs","names":[],"sources":["../src/money.ts"],"sourcesContent":["/**\n * Money — Integer-cents arithmetic helpers for safe financial computation.\n *\n * Provides utilities that operate on **integer minor-unit values** (cents) to\n * avoid floating-point rounding errors in intermediate calculations.\n *\n * **DB storage contract:**\n * Journal entry `debit` / `credit` / `totalDebit` / `totalCredit` fields are\n * stored as **integer cents** (e.g. 10050 for $100.50). All report outputs,\n * aggregation results, and repository methods return integer cents.\n *\n * Use `Money.fromDecimal()` to convert user-facing dollar inputs to cents\n * at the HTTP/API boundary. Use `Money.toDecimal()` or `Money.formatPlain()`\n * to convert cents back to dollars for display or CSV export.\n *\n * Example workflow:\n * const cents = Money.fromDecimal(req.body.debit); // 100.50 → 10050\n * const taxCents = Money.percentage(cents, 5); // 5% → 502 (rounded)\n * const display = Money.formatPlain(taxCents); // 502 → \"5.02\"\n *\n * Inspired by Stripe's money handling — simple, correct, auditable.\n *\n * @module @classytic/ledger/money\n */\n\n// ─── Core Arithmetic ─────────────────────────────────────────────────────────\n\n/** Round a floating-point value to the nearest integer cent */\nexport function round(amount: number): number {\n return Math.round(amount);\n}\n\n/** Convert a decimal dollar amount to integer cents: 10.50 → 1050 */\nexport function fromDecimal(dollars: number, minorUnit = 2): number {\n const factor = 10 ** minorUnit;\n return Math.round(dollars * factor);\n}\n\n/** Convert integer cents to a decimal dollar amount: 1050 → 10.50 */\nexport function toDecimal(cents: number, minorUnit = 2): number {\n const factor = 10 ** minorUnit;\n return cents / factor;\n}\n\n/** Add two cent amounts */\nexport function add(a: number, b: number): number {\n return a + b;\n}\n\n/** Subtract b from a in cents */\nexport function subtract(a: number, b: number): number {\n return a - b;\n}\n\n/** Multiply cents by a factor, rounding to nearest cent */\nexport function multiply(cents: number, factor: number): number {\n return Math.round(cents * factor);\n}\n\n/**\n * Calculate a percentage of a cent amount.\n * percentage(10000, 5) → 500 (5% of $100.00 = $5.00)\n */\nexport function percentage(cents: number, rate: number): number {\n return Math.round((cents * rate) / 100);\n}\n\n/**\n * Calculate tax from a tax-inclusive amount.\n * splitTaxInclusive(10500, 0.05) → { base: 10000, tax: 500 }\n */\nexport function splitTaxInclusive(\n inclusiveAmount: number,\n taxRate: number,\n): { base: number; tax: number } {\n const base = Math.round(inclusiveAmount / (1 + taxRate));\n const tax = inclusiveAmount - base;\n return { base, tax };\n}\n\n/**\n * Calculate tax from a tax-exclusive amount.\n * splitTaxExclusive(10000, 0.05) → { base: 10000, tax: 500, total: 10500 }\n */\nexport function splitTaxExclusive(\n exclusiveAmount: number,\n taxRate: number,\n): { base: number; tax: number; total: number } {\n const tax = Math.round(exclusiveAmount * taxRate);\n return { base: exclusiveAmount, tax, total: exclusiveAmount + tax };\n}\n\n// ─── Allocation ──────────────────────────────────────────────────────────────\n\n/**\n * Allocate cents across ratios with zero remainder error.\n * Uses largest-remainder method (same as parliamentary seat allocation).\n *\n * allocate(1000, [1, 1, 1]) → [334, 333, 333] (sums to 1000 exactly)\n * allocate(10000, [50, 30, 20]) → [5000, 3000, 2000]\n */\nexport function allocate(totalCents: number, ratios: number[]): number[] {\n if (ratios.length === 0) throw new Error('Ratios must be non-empty');\n if (ratios.some(r => r < 0)) throw new Error('Ratios must be non-negative');\n\n const ratioSum = ratios.reduce((s, r) => s + r, 0);\n if (ratioSum === 0) throw new Error('Sum of ratios must be > 0');\n\n // Base allocation (floor)\n const allocations = ratios.map(r => Math.floor((totalCents * r) / ratioSum));\n let remainder = totalCents - allocations.reduce((s, a) => s + a, 0);\n\n // Distribute remainder by largest fractional part\n const fractions = ratios.map((r, i) => ({\n index: i,\n frac: ((totalCents * r) / ratioSum) - allocations[i],\n }));\n fractions.sort((a, b) => b.frac - a.frac);\n\n for (let i = 0; i < remainder; i++) {\n allocations[fractions[i].index]++;\n }\n\n return allocations;\n}\n\n// ─── Comparison ──────────────────────────────────────────────────────────────\n\n/** Are two cent amounts equal? */\nexport function equals(a: number, b: number): boolean {\n return a === b;\n}\n\n/** Is the amount zero? */\nexport function isZero(cents: number): boolean {\n return cents === 0;\n}\n\n/** Is the amount positive (> 0)? */\nexport function isPositive(cents: number): boolean {\n return cents > 0;\n}\n\n/** Is the amount negative (< 0)? */\nexport function isNegative(cents: number): boolean {\n return cents < 0;\n}\n\n/** Absolute value */\nexport function abs(cents: number): number {\n return Math.abs(cents);\n}\n\n/** Negate */\nexport function negate(cents: number): number {\n return -cents;\n}\n\n/** Min of two amounts */\nexport function min(a: number, b: number): number {\n return Math.min(a, b);\n}\n\n/** Max of two amounts */\nexport function max(a: number, b: number): number {\n return Math.max(a, b);\n}\n\n// ─── Formatting ──────────────────────────────────────────────────────────────\n\n/**\n * Format cents as a currency string.\n * format(10550, 'CAD') → \"$105.50\"\n * format(10550, 'CAD', 'en-CA') → \"$105.50\"\n */\nexport function format(\n cents: number,\n currencyCode = 'CAD',\n locale = 'en-CA',\n minorUnit = 2,\n): string {\n const dollars = toDecimal(cents, minorUnit);\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency: currencyCode,\n }).format(dollars);\n}\n\n/**\n * Format cents as a plain decimal string (no currency symbol).\n * formatPlain(10550) → \"105.50\"\n */\nexport function formatPlain(cents: number, minorUnit = 2): string {\n return toDecimal(cents, minorUnit).toFixed(minorUnit);\n}\n\n// ─── Validation ──────────────────────────────────────────────────────────────\n\n/** Is the value a valid integer cent amount? */\nexport function isValid(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value);\n}\n\n/**\n * Parse a string or number into cents.\n * parseCents(\"105.50\") → 10550\n * parseCents(105.50) → 10550\n */\nexport function parseCents(input: string | number, minorUnit = 2): number {\n if (typeof input === 'number') return fromDecimal(input, minorUnit);\n\n const cleaned = input.replace(/[$,\\s]/g, '');\n const parsed = parseFloat(cleaned);\n if (isNaN(parsed)) throw new Error(`Cannot parse \"${input}\" as money`);\n return fromDecimal(parsed, minorUnit);\n}\n\n// ─── Bundled Export ──────────────────────────────────────────────────────────\n\nexport const Money = {\n round,\n fromDecimal,\n toDecimal,\n add,\n subtract,\n multiply,\n percentage,\n splitTaxInclusive,\n splitTaxExclusive,\n allocate,\n equals,\n isZero,\n isPositive,\n isNegative,\n abs,\n negate,\n min,\n max,\n format,\n formatPlain,\n isValid,\n parseCents,\n} as const;\n"],"mappings":"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"}
@@ -1,84 +0,0 @@
1
- import { n as Errors } from "./errors-B7yC-Jfw.mjs";
2
- //#region src/utils/tenant-guard.ts
3
- /**
4
- * Multi-tenant scope guard.
5
- *
6
- * Throws when orgField is configured (multi-tenant mode active) but
7
- * organizationId is missing — preventing unscoped cross-tenant queries.
8
- */
9
- function requireOrgScope(orgField, organizationId) {
10
- if (orgField && !organizationId) throw Errors.validation("organizationId is required when multi-tenant mode is configured (orgField: \"" + orgField + "\"). Refusing to run unscoped query.");
11
- }
12
- //#endregion
13
- //#region src/utils/logger.ts
14
- /** Default console-based implementation */
15
- const defaultLogger = {
16
- warn: (msg, meta) => console.warn(`[accounting] ${msg}`, meta ?? ""),
17
- error: (msg, meta) => console.error(`[accounting] ${msg}`, meta ?? ""),
18
- info: (msg, meta) => console.info(`[accounting] ${msg}`, meta ?? "")
19
- };
20
- //#endregion
21
- //#region src/utils/session.ts
22
- /**
23
- * Acquire a session: uses external if provided, otherwise creates an internal one.
24
- * Returns { session, ownSession } so callers can commit/abort/end appropriately.
25
- *
26
- * When transactions are unavailable (no replica set / standalone), returns
27
- * session=null and the function runs without transactional safety.
28
- */
29
- async function acquireSession(db, externalSession, logger = defaultLogger) {
30
- if (externalSession) return {
31
- session: externalSession,
32
- ownSession: false
33
- };
34
- try {
35
- const session = await db.startSession();
36
- try {
37
- if ((db.getClient?.() ?? db.client)?.topology?.description?.type === "Single") {
38
- session.endSession();
39
- logger.warn("Transactions unavailable (standalone MongoDB). Operation is not atomic.");
40
- return {
41
- session: null,
42
- ownSession: false
43
- };
44
- }
45
- } catch {}
46
- try {
47
- session.startTransaction();
48
- return {
49
- session,
50
- ownSession: true
51
- };
52
- } catch (err) {
53
- session.endSession();
54
- logger.warn("Transactions unavailable (no replica set). Operation is not atomic.", { error: err.message });
55
- return {
56
- session: null,
57
- ownSession: false
58
- };
59
- }
60
- } catch {
61
- return {
62
- session: null,
63
- ownSession: false
64
- };
65
- }
66
- }
67
- /**
68
- * Finalize an owned session: commit or abort, then always end.
69
- */
70
- async function finalizeSession(session, ownSession, success) {
71
- if (!ownSession || !session) return;
72
- try {
73
- if (success && session.inTransaction()) await session.commitTransaction();
74
- else if (!success && session.inTransaction()) try {
75
- await session.abortTransaction();
76
- } catch {}
77
- } finally {
78
- session.endSession();
79
- }
80
- }
81
- //#endregion
82
- export { requireOrgScope as i, finalizeSession as n, defaultLogger as r, acquireSession as t };
83
-
84
- //# sourceMappingURL=session-Ba8E3Ufa.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session-Ba8E3Ufa.mjs","names":[],"sources":["../src/utils/tenant-guard.ts","../src/utils/logger.ts","../src/utils/session.ts"],"sourcesContent":["import { Errors } from './errors.js';\r\n\r\n/**\r\n * Multi-tenant scope guard.\r\n *\r\n * Throws when orgField is configured (multi-tenant mode active) but\r\n * organizationId is missing — preventing unscoped cross-tenant queries.\r\n */\r\nexport function requireOrgScope(orgField: string | undefined, organizationId: unknown): void {\r\n if (orgField && !organizationId) {\r\n throw Errors.validation(\r\n 'organizationId is required when multi-tenant mode is configured (orgField: \"' +\r\n orgField +\r\n '\"). Refusing to run unscoped query.',\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Build org-scoped query filter.\r\n * Returns an object like `{ business: orgId }` or `{}`.\r\n */\r\nexport function orgFilter(orgField: string | undefined, organizationId: unknown): Record<string, unknown> {\r\n if (!orgField) return {};\r\n // requireOrgScope should be called first; this is a convenience builder\r\n return organizationId ? { [orgField]: organizationId } : {};\r\n}\r\n","/**\r\n * Minimal logger interface for the accounting package.\r\n * Defaults to console. App layer can inject a real logger (Winston/Pino).\r\n */\r\nexport interface Logger {\r\n warn(message: string, meta?: Record<string, unknown>): void;\r\n error(message: string, meta?: Record<string, unknown>): void;\r\n info(message: string, meta?: Record<string, unknown>): void;\r\n}\r\n\r\n/** Default console-based implementation */\r\nexport const defaultLogger: Logger = {\r\n warn: (msg, meta) => console.warn(`[accounting] ${msg}`, meta ?? ''),\r\n error: (msg, meta) => console.error(`[accounting] ${msg}`, meta ?? ''),\r\n info: (msg, meta) => console.info(`[accounting] ${msg}`, meta ?? ''),\r\n};\r\n","/**\r\n * Shared session/transaction helpers.\r\n *\r\n * Provides a safe acquire/finalize pattern that:\r\n * - Detects standalone topology (no transactions) before starting one\r\n * - Manages commit/abort lifecycle with proper cleanup\r\n * - Always calls endSession() even when abort fails\r\n *\r\n * NOTE: session.startTransaction() is synchronous and does NOT throw on\r\n * standalone MongoDB — the \"Transaction numbers are only allowed on a replica\r\n * set member or mongos\" error only surfaces at the first database operation.\r\n * We detect standalone topology proactively via the driver's topology\r\n * description to avoid this trap.\r\n */\r\n\r\nimport type { ClientSession, Connection } from 'mongoose';\r\nimport type { Logger } from './logger.js';\r\nimport { defaultLogger } from './logger.js';\r\n\r\nexport interface SessionResult {\r\n session: ClientSession | null;\r\n ownSession: boolean;\r\n}\r\n\r\n/**\r\n * Acquire a session: uses external if provided, otherwise creates an internal one.\r\n * Returns { session, ownSession } so callers can commit/abort/end appropriately.\r\n *\r\n * When transactions are unavailable (no replica set / standalone), returns\r\n * session=null and the function runs without transactional safety.\r\n */\r\nexport async function acquireSession(\r\n db: Connection,\r\n externalSession: ClientSession | undefined | null,\r\n logger: Logger = defaultLogger,\r\n): Promise<SessionResult> {\r\n if (externalSession) {\r\n return { session: externalSession, ownSession: false };\r\n }\r\n\r\n try {\r\n const session = await db.startSession();\r\n\r\n // Detect standalone topology before starting a transaction.\r\n try {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const client = (db as any).getClient?.() ?? (db as any).client;\r\n const topologyType = client?.topology?.description?.type;\r\n if (topologyType === 'Single') {\r\n session.endSession();\r\n logger.warn(\r\n 'Transactions unavailable (standalone MongoDB). Operation is not atomic.',\r\n );\r\n return { session: null, ownSession: false };\r\n }\r\n } catch {\r\n // Topology detection failed — proceed optimistically\r\n }\r\n\r\n try {\r\n session.startTransaction();\r\n return { session, ownSession: true };\r\n } catch (err) {\r\n // startTransaction failed for unexpected reasons — clean up and fall back\r\n session.endSession();\r\n logger.warn(\r\n 'Transactions unavailable (no replica set). Operation is not atomic.',\r\n { error: (err as Error).message },\r\n );\r\n return { session: null, ownSession: false };\r\n }\r\n } catch {\r\n // startSession itself failed — run without session\r\n return { session: null, ownSession: false };\r\n }\r\n}\r\n\r\n/**\r\n * Finalize an owned session: commit or abort, then always end.\r\n */\r\nexport async function finalizeSession(\r\n session: ClientSession | null,\r\n ownSession: boolean,\r\n success: boolean,\r\n): Promise<void> {\r\n if (!ownSession || !session) return;\r\n\r\n try {\r\n if (success && session.inTransaction()) {\r\n await session.commitTransaction();\r\n } else if (!success && session.inTransaction()) {\r\n try {\r\n await session.abortTransaction();\r\n } catch {\r\n // Swallow abort errors — the original error is more important\r\n }\r\n }\r\n } finally {\r\n session.endSession();\r\n }\r\n}\r\n"],"mappings":";;;;;;;;AAQA,SAAgB,gBAAgB,UAA8B,gBAA+B;AAC3F,KAAI,YAAY,CAAC,eACf,OAAM,OAAO,WACX,kFACA,WACA,uCACD;;;;;ACHL,MAAa,gBAAwB;CACnC,OAAO,KAAK,SAAS,QAAQ,KAAK,gBAAgB,OAAO,QAAQ,GAAG;CACpE,QAAQ,KAAK,SAAS,QAAQ,MAAM,gBAAgB,OAAO,QAAQ,GAAG;CACtE,OAAO,KAAK,SAAS,QAAQ,KAAK,gBAAgB,OAAO,QAAQ,GAAG;CACrE;;;;;;;;;;ACgBD,eAAsB,eACpB,IACA,iBACA,SAAiB,eACO;AACxB,KAAI,gBACF,QAAO;EAAE,SAAS;EAAiB,YAAY;EAAO;AAGxD,KAAI;EACF,MAAM,UAAU,MAAM,GAAG,cAAc;AAGvC,MAAI;AAIF,QAFgB,GAAW,aAAa,IAAK,GAAW,SAC3B,UAAU,aAAa,SAC/B,UAAU;AAC7B,YAAQ,YAAY;AACpB,WAAO,KACL,0EACD;AACD,WAAO;KAAE,SAAS;KAAM,YAAY;KAAO;;UAEvC;AAIR,MAAI;AACF,WAAQ,kBAAkB;AAC1B,UAAO;IAAE;IAAS,YAAY;IAAM;WAC7B,KAAK;AAEZ,WAAQ,YAAY;AACpB,UAAO,KACL,uEACA,EAAE,OAAQ,IAAc,SAAS,CAClC;AACD,UAAO;IAAE,SAAS;IAAM,YAAY;IAAO;;SAEvC;AAEN,SAAO;GAAE,SAAS;GAAM,YAAY;GAAO;;;;;;AAO/C,eAAsB,gBACpB,SACA,YACA,SACe;AACf,KAAI,CAAC,cAAc,CAAC,QAAS;AAE7B,KAAI;AACF,MAAI,WAAW,QAAQ,eAAe,CACpC,OAAM,QAAQ,mBAAmB;WACxB,CAAC,WAAW,QAAQ,eAAe,CAC5C,KAAI;AACF,SAAM,QAAQ,kBAAkB;UAC1B;WAIF;AACR,UAAQ,YAAY"}