@classytic/ledger 0.1.3

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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +108 -0
  3. package/dist/account.repository-1C2sZvB2.d.mts +29 -0
  4. package/dist/account.repository-1C2sZvB2.d.mts.map +1 -0
  5. package/dist/account.repository-Crf5DGO4.mjs +393 -0
  6. package/dist/account.repository-Crf5DGO4.mjs.map +1 -0
  7. package/dist/categories-BNJBd4ze.mjs +70 -0
  8. package/dist/categories-BNJBd4ze.mjs.map +1 -0
  9. package/dist/constants/index.d.mts +2 -0
  10. package/dist/constants/index.mjs +5 -0
  11. package/dist/core-Cx0baosR.d.mts +104 -0
  12. package/dist/core-Cx0baosR.d.mts.map +1 -0
  13. package/dist/country/index.d.mts +105 -0
  14. package/dist/country/index.d.mts.map +1 -0
  15. package/dist/country/index.mjs +27 -0
  16. package/dist/country/index.mjs.map +1 -0
  17. package/dist/currencies-BBk3NwXn.mjs +82 -0
  18. package/dist/currencies-BBk3NwXn.mjs.map +1 -0
  19. package/dist/currencies-Bkn3FNkC.d.mts +38 -0
  20. package/dist/currencies-Bkn3FNkC.d.mts.map +1 -0
  21. package/dist/engine-Cd73EOT6.d.mts +72 -0
  22. package/dist/engine-Cd73EOT6.d.mts.map +1 -0
  23. package/dist/errors-CeqRahE-.mjs +28 -0
  24. package/dist/errors-CeqRahE-.mjs.map +1 -0
  25. package/dist/exports/index.d.mts +2 -0
  26. package/dist/exports/index.mjs +3 -0
  27. package/dist/fiscal-close-CNOwv_ud.mjs +934 -0
  28. package/dist/fiscal-close-CNOwv_ud.mjs.map +1 -0
  29. package/dist/fiscal-close-CzUzpnMg.d.mts +270 -0
  30. package/dist/fiscal-close-CzUzpnMg.d.mts.map +1 -0
  31. package/dist/fiscal-period.schema-CbALaaKl.mjs +477 -0
  32. package/dist/fiscal-period.schema-CbALaaKl.mjs.map +1 -0
  33. package/dist/fiscal-period.schema-DI2scngu.d.mts +38 -0
  34. package/dist/fiscal-period.schema-DI2scngu.d.mts.map +1 -0
  35. package/dist/idempotency.plugin-BESs9YPD.d.mts +58 -0
  36. package/dist/idempotency.plugin-BESs9YPD.d.mts.map +1 -0
  37. package/dist/idempotency.plugin-C6r8RI8d.mjs +165 -0
  38. package/dist/idempotency.plugin-C6r8RI8d.mjs.map +1 -0
  39. package/dist/index.d.mts +308 -0
  40. package/dist/index.d.mts.map +1 -0
  41. package/dist/index.mjs +171 -0
  42. package/dist/index.mjs.map +1 -0
  43. package/dist/journals-CI3Wb4EF.mjs +92 -0
  44. package/dist/journals-CI3Wb4EF.mjs.map +1 -0
  45. package/dist/logger-Cv6VVc4r.d.mts +15 -0
  46. package/dist/logger-Cv6VVc4r.d.mts.map +1 -0
  47. package/dist/money.d.mts +129 -0
  48. package/dist/money.d.mts.map +1 -0
  49. package/dist/money.mjs +197 -0
  50. package/dist/money.mjs.map +1 -0
  51. package/dist/plugins/index.d.mts +2 -0
  52. package/dist/plugins/index.mjs +3 -0
  53. package/dist/reports/index.d.mts +2 -0
  54. package/dist/reports/index.mjs +3 -0
  55. package/dist/repositories/index.d.mts +2 -0
  56. package/dist/repositories/index.mjs +3 -0
  57. package/dist/schemas/index.d.mts +2 -0
  58. package/dist/schemas/index.mjs +3 -0
  59. package/dist/session-Dh0s6zG4.mjs +87 -0
  60. package/dist/session-Dh0s6zG4.mjs.map +1 -0
  61. package/dist/universal-CMfrZ2hG.mjs +257 -0
  62. package/dist/universal-CMfrZ2hG.mjs.map +1 -0
  63. package/dist/universal-x33ZJODp.d.mts +137 -0
  64. package/dist/universal-x33ZJODp.d.mts.map +1 -0
  65. package/docs/country-packs.md +117 -0
  66. package/docs/engine.md +147 -0
  67. package/docs/exports.md +81 -0
  68. package/docs/money.md +81 -0
  69. package/docs/plugins.md +136 -0
  70. package/docs/reports.md +154 -0
  71. package/docs/repositories.md +239 -0
  72. package/docs/schemas.md +146 -0
  73. package/docs/subledger-integration.md +287 -0
  74. package/package.json +116 -0
@@ -0,0 +1,477 @@
1
+ import { i as getJournalTypeCodes, t as JOURNAL_CODES } from "./journals-CI3Wb4EF.mjs";
2
+ import mongoose from "mongoose";
3
+
4
+ //#region src/schemas/account.schema.ts
5
+ /**
6
+ * Account Schema Factory
7
+ *
8
+ * Creates a Mongoose schema for Chart of Accounts that is:
9
+ * - Multi-tenant aware (adds org field + compound indexes when configured)
10
+ * - Validates accountTypeCode against the country pack
11
+ * - Supports accountNumber (unique per org) and name (user-facing display)
12
+ * - Lean: no cached balances — always computed from journal entries
13
+ */
14
+ function createAccountSchema(config, options = {}) {
15
+ const { multiTenant, country } = config;
16
+ const { indexes = true, extraFields = {}, extraIndexes = [] } = options;
17
+ const fields = {
18
+ accountTypeCode: {
19
+ type: String,
20
+ required: true,
21
+ validate: {
22
+ validator: (code) => country.isValidAccountType(code),
23
+ message: (props) => `"${props.value}" is not a valid account type code for ${country.name}.`
24
+ }
25
+ },
26
+ accountNumber: {
27
+ type: String,
28
+ required: true,
29
+ trim: true
30
+ },
31
+ name: {
32
+ type: String,
33
+ required: true,
34
+ trim: true
35
+ },
36
+ active: {
37
+ type: Boolean,
38
+ default: true
39
+ },
40
+ isCashAccount: {
41
+ type: Boolean,
42
+ default: false
43
+ },
44
+ ...extraFields
45
+ };
46
+ if (multiTenant) fields[multiTenant.orgField] = {
47
+ type: mongoose.Schema.Types.ObjectId,
48
+ ref: multiTenant.orgRef,
49
+ required: true
50
+ };
51
+ const schema = new mongoose.Schema(fields, { timestamps: true });
52
+ schema.pre("validate", function() {
53
+ const doc = this;
54
+ if (!doc.accountNumber && doc.accountTypeCode) doc.accountNumber = doc.accountTypeCode;
55
+ if (!doc.name && doc.accountTypeCode) doc.name = country.getAccountType(doc.accountTypeCode)?.name ?? doc.accountTypeCode;
56
+ });
57
+ if (indexes) if (multiTenant) {
58
+ const org = multiTenant.orgField;
59
+ schema.index({
60
+ [org]: 1,
61
+ active: 1
62
+ });
63
+ schema.index({
64
+ [org]: 1,
65
+ accountNumber: 1
66
+ }, { unique: true });
67
+ schema.index({
68
+ [org]: 1,
69
+ accountTypeCode: 1
70
+ });
71
+ } else {
72
+ schema.index({ active: 1 });
73
+ schema.index({ accountNumber: 1 }, { unique: true });
74
+ schema.index({ accountTypeCode: 1 });
75
+ }
76
+ for (const idx of extraIndexes) schema.index(idx.fields, idx.options);
77
+ return schema;
78
+ }
79
+
80
+ //#endregion
81
+ //#region src/schemas/journal-entry.schema.ts
82
+ /**
83
+ * Journal Entry Schema Factory
84
+ *
85
+ * Creates a Mongoose schema for double-entry journal entries.
86
+ * - Multi-tenant aware
87
+ * - Embedded journal items with account refs
88
+ * - State machine: draft → posted, draft → archived
89
+ * - Auto-generated reference numbers
90
+ * - Double-entry validation on post
91
+ * - Optimized indexes for high-load reporting
92
+ */
93
+ function createJournalEntrySchema(config, accountModelName, options = {}) {
94
+ const { multiTenant } = config;
95
+ const { indexes = true, autoReference = true, textSearch = true, extraFields = {}, extraIndexes = [], extraItemFields = {} } = options;
96
+ const TaxDetailSchema = new mongoose.Schema({
97
+ taxCode: { type: String },
98
+ taxName: { type: String }
99
+ }, { _id: false });
100
+ const amountValidator = {
101
+ validator: (v) => Number.isInteger(v) && v >= 0,
102
+ message: "{PATH} must be a non-negative integer (cents), got {VALUE}"
103
+ };
104
+ const JournalItemSchema = new mongoose.Schema({
105
+ account: {
106
+ type: mongoose.Schema.Types.ObjectId,
107
+ ref: accountModelName,
108
+ required: true
109
+ },
110
+ label: { type: String },
111
+ date: { type: Date },
112
+ debit: {
113
+ type: Number,
114
+ default: 0,
115
+ min: 0,
116
+ validate: amountValidator
117
+ },
118
+ credit: {
119
+ type: Number,
120
+ default: 0,
121
+ min: 0,
122
+ validate: amountValidator
123
+ },
124
+ taxDetails: {
125
+ type: [TaxDetailSchema],
126
+ default: []
127
+ },
128
+ ...extraItemFields
129
+ }, { _id: false });
130
+ const fields = {
131
+ journalType: {
132
+ type: String,
133
+ enum: getJournalTypeCodes(),
134
+ default: JOURNAL_CODES["MISC"],
135
+ required: true
136
+ },
137
+ referenceNumber: { type: String },
138
+ label: { type: String },
139
+ date: {
140
+ type: Date,
141
+ default: Date.now,
142
+ required: function() {
143
+ return this.state !== "draft";
144
+ }
145
+ },
146
+ journalItems: {
147
+ type: [JournalItemSchema],
148
+ default: []
149
+ },
150
+ totalDebit: {
151
+ type: Number,
152
+ required: true,
153
+ min: 0,
154
+ validate: {
155
+ validator: Number.isInteger,
156
+ message: "totalDebit must be an integer (cents)"
157
+ }
158
+ },
159
+ totalCredit: {
160
+ type: Number,
161
+ required: true,
162
+ min: 0,
163
+ validate: {
164
+ validator: Number.isInteger,
165
+ message: "totalCredit must be an integer (cents)"
166
+ }
167
+ },
168
+ state: {
169
+ type: String,
170
+ enum: [
171
+ "draft",
172
+ "posted",
173
+ "archived"
174
+ ],
175
+ default: "draft",
176
+ required: true
177
+ },
178
+ stateChangedAt: {
179
+ type: Date,
180
+ default: Date.now
181
+ },
182
+ reversed: {
183
+ type: Boolean,
184
+ default: false
185
+ },
186
+ reversedBy: {
187
+ type: mongoose.Schema.Types.ObjectId,
188
+ ref: "JournalEntry",
189
+ default: null
190
+ },
191
+ reversalOf: {
192
+ type: mongoose.Schema.Types.ObjectId,
193
+ ref: "JournalEntry",
194
+ default: null
195
+ },
196
+ ...extraFields
197
+ };
198
+ if (config.audit?.trackActor) {
199
+ fields.createdBy = {
200
+ type: mongoose.Schema.Types.ObjectId,
201
+ default: null
202
+ };
203
+ fields.postedBy = {
204
+ type: mongoose.Schema.Types.ObjectId,
205
+ default: null
206
+ };
207
+ fields.reversedByUser = {
208
+ type: mongoose.Schema.Types.ObjectId,
209
+ default: null
210
+ };
211
+ }
212
+ if (config.strictness?.requireApproval || config.audit?.trackActor) {
213
+ fields.approvedBy = {
214
+ type: mongoose.Schema.Types.ObjectId,
215
+ default: null
216
+ };
217
+ fields.approvedAt = {
218
+ type: Date,
219
+ default: null
220
+ };
221
+ }
222
+ if (config.idempotency) fields.idempotencyKey = {
223
+ type: String,
224
+ default: null
225
+ };
226
+ if (multiTenant) fields[multiTenant.orgField] = {
227
+ type: mongoose.Schema.Types.ObjectId,
228
+ ref: multiTenant.orgRef,
229
+ required: true
230
+ };
231
+ const schema = new mongoose.Schema(fields, { timestamps: true });
232
+ schema.pre("validate", function() {
233
+ const doc = this;
234
+ for (const item of doc.journalItems) if (!item.date) item.date = doc.date;
235
+ for (let i = 0; i < doc.journalItems.length; i++) {
236
+ const d = doc.journalItems[i].debit || 0;
237
+ const c = doc.journalItems[i].credit || 0;
238
+ if (d > 0 && c > 0) throw new Error(`Journal item at index ${i}: cannot have both debit (${d}) and credit (${c}) greater than zero`);
239
+ if (doc.state === "posted" && d === 0 && c === 0) throw new Error(`Journal item at index ${i}: posted entries cannot have zero-value lines (both debit and credit are 0)`);
240
+ }
241
+ const totalDebit = doc.journalItems.reduce((s, i) => s + (i.debit || 0), 0);
242
+ const totalCredit = doc.journalItems.reduce((s, i) => s + (i.credit || 0), 0);
243
+ if (doc.state === "posted") {
244
+ if (doc.journalItems.length < 2) throw new Error("Posted entries must have at least 2 journal items");
245
+ if (totalDebit !== totalCredit) throw new Error("Total debit must equal total credit for posted entries");
246
+ }
247
+ doc.totalDebit = totalDebit;
248
+ doc.totalCredit = totalCredit;
249
+ });
250
+ if (autoReference) {
251
+ const generateReferenceNumber = async (doc, Model, session) => {
252
+ const jt = doc.journalType || "MISC";
253
+ const d = new Date(doc.date);
254
+ const prefix = `${jt}/${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}/`;
255
+ const matchFilter = { referenceNumber: { $regex: `^${prefix.replace(/\//g, "\\/")}` } };
256
+ if (multiTenant) matchFilter[multiTenant.orgField] = doc[multiTenant.orgField];
257
+ const pipeline = [
258
+ { $match: matchFilter },
259
+ { $addFields: { _refSeq: { $toInt: { $arrayElemAt: [{ $split: ["$referenceNumber", "/"] }, -1] } } } },
260
+ { $sort: { _refSeq: -1 } },
261
+ { $limit: 1 },
262
+ { $project: { _refSeq: 1 } }
263
+ ];
264
+ const results = await Model.aggregate(pipeline).session(session);
265
+ let seq = 1;
266
+ if (results.length > 0 && typeof results[0]._refSeq === "number") seq = results[0]._refSeq + 1;
267
+ return `${prefix}${String(seq).padStart(4, "0")}`;
268
+ };
269
+ schema.pre("save", async function() {
270
+ const doc = this;
271
+ if (doc.isModified("journalType")) doc.referenceNumber = void 0;
272
+ if (!doc.referenceNumber) {
273
+ const session = doc.$session?.() ?? null;
274
+ const Model = doc.constructor;
275
+ doc.referenceNumber = await generateReferenceNumber(doc, Model, session);
276
+ }
277
+ });
278
+ const MAX_REF_RETRIES = 3;
279
+ schema.post("save", async function(error, doc, next) {
280
+ const mongoError = error;
281
+ if (mongoError.code === 11e3 && mongoError.keyPattern?.referenceNumber) {
282
+ const entry = doc;
283
+ const retryCount = entry.__refRetries ?? 0;
284
+ if (retryCount >= MAX_REF_RETRIES) {
285
+ next(/* @__PURE__ */ new Error(`Failed to generate unique reference number after ${MAX_REF_RETRIES} retries. Too many concurrent inserts for this period.`));
286
+ return;
287
+ }
288
+ entry.__refRetries = retryCount + 1;
289
+ const session = entry.$session?.() ?? null;
290
+ const Model = entry.constructor;
291
+ entry.referenceNumber = await generateReferenceNumber(entry, Model, session);
292
+ try {
293
+ await entry.save({ session });
294
+ next();
295
+ } catch (retryError) {
296
+ next(retryError);
297
+ }
298
+ } else next(error);
299
+ });
300
+ }
301
+ if (indexes) {
302
+ const org = multiTenant?.orgField;
303
+ const refPartial = { partialFilterExpression: { referenceNumber: {
304
+ $exists: true,
305
+ $type: "string"
306
+ } } };
307
+ if (org) {
308
+ schema.index({
309
+ [org]: 1,
310
+ referenceNumber: 1
311
+ }, {
312
+ unique: true,
313
+ ...refPartial
314
+ });
315
+ schema.index({
316
+ [org]: 1,
317
+ state: 1,
318
+ date: 1
319
+ });
320
+ schema.index({
321
+ [org]: 1,
322
+ date: -1
323
+ });
324
+ schema.index({
325
+ [org]: 1,
326
+ journalType: 1
327
+ });
328
+ schema.index({
329
+ "journalItems.account": 1,
330
+ state: 1
331
+ });
332
+ schema.index({
333
+ [org]: 1,
334
+ "journalItems.account": 1,
335
+ date: 1,
336
+ state: 1
337
+ });
338
+ } else {
339
+ schema.index({ referenceNumber: 1 }, {
340
+ unique: true,
341
+ ...refPartial
342
+ });
343
+ schema.index({
344
+ state: 1,
345
+ date: 1
346
+ });
347
+ schema.index({ date: -1 });
348
+ schema.index({ journalType: 1 });
349
+ schema.index({
350
+ "journalItems.account": 1,
351
+ state: 1
352
+ });
353
+ }
354
+ schema.index({ reversed: 1 });
355
+ if (config.idempotency) {
356
+ const idempotencyIdx = {};
357
+ if (org) idempotencyIdx[org] = 1;
358
+ idempotencyIdx.idempotencyKey = 1;
359
+ schema.index(idempotencyIdx, {
360
+ unique: true,
361
+ partialFilterExpression: { idempotencyKey: {
362
+ $exists: true,
363
+ $ne: null
364
+ } }
365
+ });
366
+ }
367
+ }
368
+ if (textSearch) schema.index({
369
+ referenceNumber: "text",
370
+ label: "text"
371
+ }, {
372
+ weights: {
373
+ referenceNumber: 10,
374
+ label: 5
375
+ },
376
+ name: "journal_text_idx"
377
+ });
378
+ for (const idx of extraIndexes) schema.index(idx.fields, idx.options);
379
+ return schema;
380
+ }
381
+
382
+ //#endregion
383
+ //#region src/schemas/fiscal-period.schema.ts
384
+ /**
385
+ * Fiscal Period Schema Factory
386
+ *
387
+ * Creates a Mongoose schema for tracking fiscal periods (months, quarters, years).
388
+ * Supports closing periods to lock entries.
389
+ */
390
+ function createFiscalPeriodSchema(config, options = {}) {
391
+ const { multiTenant } = config;
392
+ const { indexes = true, extraFields = {}, extraIndexes = [] } = options;
393
+ const fields = {
394
+ name: {
395
+ type: String,
396
+ required: true
397
+ },
398
+ startDate: {
399
+ type: Date,
400
+ required: true
401
+ },
402
+ endDate: {
403
+ type: Date,
404
+ required: true
405
+ },
406
+ closed: {
407
+ type: Boolean,
408
+ default: false
409
+ },
410
+ closedAt: {
411
+ type: Date,
412
+ default: null
413
+ },
414
+ closedBy: {
415
+ type: String,
416
+ default: null
417
+ },
418
+ closingEntryId: {
419
+ type: mongoose.Schema.Types.ObjectId,
420
+ default: null
421
+ },
422
+ reopenedAt: {
423
+ type: Date,
424
+ default: null
425
+ },
426
+ reopenedBy: {
427
+ type: String,
428
+ default: null
429
+ },
430
+ ...extraFields
431
+ };
432
+ if (multiTenant) fields[multiTenant.orgField] = {
433
+ type: mongoose.Schema.Types.ObjectId,
434
+ ref: multiTenant.orgRef,
435
+ required: true
436
+ };
437
+ const schema = new mongoose.Schema(fields, { timestamps: true });
438
+ if (indexes) if (multiTenant) {
439
+ const org = multiTenant.orgField;
440
+ schema.index({
441
+ [org]: 1,
442
+ startDate: 1,
443
+ endDate: 1
444
+ }, { unique: true });
445
+ schema.index({
446
+ [org]: 1,
447
+ closed: 1
448
+ });
449
+ } else {
450
+ schema.index({
451
+ startDate: 1,
452
+ endDate: 1
453
+ }, { unique: true });
454
+ schema.index({ closed: 1 });
455
+ }
456
+ for (const idx of extraIndexes) schema.index(idx.fields, idx.options);
457
+ schema.pre("validate", async function() {
458
+ const doc = this;
459
+ if (!doc.startDate || !doc.endDate) return;
460
+ const overlapQuery = {
461
+ _id: { $ne: doc._id },
462
+ startDate: { $lt: doc.endDate },
463
+ endDate: { $gt: doc.startDate }
464
+ };
465
+ if (multiTenant) overlapQuery[multiTenant.orgField] = doc[multiTenant.orgField];
466
+ const overlap = await doc.collection.findOne(overlapQuery);
467
+ if (overlap) {
468
+ 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]}).`;
469
+ doc.invalidate("startDate", msg, doc.startDate, "overlap");
470
+ }
471
+ });
472
+ return schema;
473
+ }
474
+
475
+ //#endregion
476
+ export { createJournalEntrySchema as n, createAccountSchema as r, createFiscalPeriodSchema as t };
477
+ //# sourceMappingURL=fiscal-period.schema-CbALaaKl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fiscal-period.schema-CbALaaKl.mjs","names":[],"sources":["../src/schemas/account.schema.ts","../src/schemas/journal-entry.schema.ts","../src/schemas/fiscal-period.schema.ts"],"sourcesContent":["/**\r\n * Account Schema Factory\r\n *\r\n * Creates a Mongoose schema for Chart of Accounts that is:\r\n * - Multi-tenant aware (adds org field + compound indexes when configured)\r\n * - Validates accountTypeCode against the country pack\r\n * - Supports accountNumber (unique per org) and name (user-facing display)\r\n * - Lean: no cached balances — always computed from journal entries\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, SchemaOptions } from '../types/engine.js';\r\n\r\nexport function createAccountSchema(\r\n config: AccountingEngineConfig,\r\n options: SchemaOptions = {},\r\n) {\r\n const { multiTenant, country } = config;\r\n const { indexes = true, extraFields = {}, extraIndexes = [] } = options;\r\n\r\n // ── Base fields ──────────────────────────────────────────────────────────\r\n\r\n const fields: Record<string, unknown> = {\r\n accountTypeCode: {\r\n type: String,\r\n required: true,\r\n validate: {\r\n validator: (code: string) => country.isValidAccountType(code),\r\n message: (props: { value: string }) =>\r\n `\"${props.value}\" is not a valid account type code for ${country.name}.`,\r\n },\r\n },\r\n accountNumber: {\r\n type: String,\r\n required: true,\r\n trim: true,\r\n },\r\n name: {\r\n type: String,\r\n required: true,\r\n trim: true,\r\n },\r\n active: { type: Boolean, default: true },\r\n isCashAccount: { type: Boolean, default: false },\r\n ...extraFields,\r\n };\r\n\r\n // ── Multi-tenant field ───────────────────────────────────────────────────\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // ── Schema ───────────────────────────────────────────────────────────────\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n // ── Pre-validate: auto-default accountNumber and name ──────────────────\r\n\r\n schema.pre('validate', function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n if (!doc.accountNumber && doc.accountTypeCode) {\r\n doc.accountNumber = doc.accountTypeCode;\r\n }\r\n if (!doc.name && doc.accountTypeCode) {\r\n const at = country.getAccountType(doc.accountTypeCode);\r\n doc.name = at?.name ?? doc.accountTypeCode;\r\n }\r\n });\r\n\r\n // ── Indexes ──────────────────────────────────────────────────────────────\r\n\r\n if (indexes) {\r\n if (multiTenant) {\r\n const org = multiTenant.orgField;\r\n schema.index({ [org]: 1, active: 1 });\r\n // accountNumber is the unique identity per org\r\n schema.index({ [org]: 1, accountNumber: 1 }, { unique: true });\r\n // accountTypeCode is non-unique — multiple accounts can share a classification\r\n schema.index({ [org]: 1, accountTypeCode: 1 });\r\n } else {\r\n schema.index({ active: 1 });\r\n schema.index({ accountNumber: 1 }, { unique: true });\r\n schema.index({ accountTypeCode: 1 });\r\n }\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n return schema;\r\n}\r\n","/**\r\n * Journal Entry Schema Factory\r\n *\r\n * Creates a Mongoose schema for double-entry journal entries.\r\n * - Multi-tenant aware\r\n * - Embedded journal items with account refs\r\n * - State machine: draft → posted, draft → archived\r\n * - Auto-generated reference numbers\r\n * - Double-entry validation on post\r\n * - Optimized indexes for high-load reporting\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, JournalSchemaOptions } from '../types/engine.js';\r\nimport { getJournalTypeCodes, JOURNAL_CODES } from '../constants/journals.js';\r\n\r\nexport function createJournalEntrySchema(\r\n config: AccountingEngineConfig,\r\n accountModelName: string,\r\n options: JournalSchemaOptions = {},\r\n) {\r\n const { multiTenant } = config;\r\n const {\r\n indexes = true,\r\n autoReference = true,\r\n textSearch = true,\r\n extraFields = {},\r\n extraIndexes = [],\r\n extraItemFields = {},\r\n } = options;\r\n\r\n // ── Tax Detail (audit reference only) ────────────────────────────────────\r\n\r\n const TaxDetailSchema = new mongoose.Schema(\r\n {\r\n taxCode: { type: String },\r\n taxName: { type: String },\r\n },\r\n { _id: false },\r\n );\r\n\r\n // ── Journal Item ─────────────────────────────────────────────────────────\r\n\r\n const amountValidator = {\r\n validator: (v: number) => Number.isInteger(v) && v >= 0,\r\n message: '{PATH} must be a non-negative integer (cents), got {VALUE}',\r\n };\r\n\r\n const JournalItemSchema = new mongoose.Schema(\r\n {\r\n account: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: accountModelName,\r\n required: true,\r\n },\r\n label: { type: String },\r\n date: { type: Date },\r\n debit: { type: Number, default: 0, min: 0, validate: amountValidator },\r\n credit: { type: Number, default: 0, min: 0, validate: amountValidator },\r\n taxDetails: { type: [TaxDetailSchema], default: [] },\r\n ...extraItemFields,\r\n },\r\n { _id: false },\r\n );\r\n\r\n // ── Main fields ──────────────────────────────────────────────────────────\r\n\r\n const fields: Record<string, unknown> = {\r\n journalType: {\r\n type: String,\r\n enum: getJournalTypeCodes(),\r\n default: JOURNAL_CODES['MISC'],\r\n required: true,\r\n },\r\n referenceNumber: { type: String },\r\n label: { type: String },\r\n date: {\r\n type: Date,\r\n default: Date.now,\r\n required: function (this: { state?: string }) {\r\n return this.state !== 'draft';\r\n },\r\n },\r\n journalItems: { type: [JournalItemSchema], default: [] },\r\n totalDebit: { type: Number, required: true, min: 0, validate: { validator: Number.isInteger, message: 'totalDebit must be an integer (cents)' } },\r\n totalCredit: { type: Number, required: true, min: 0, validate: { validator: Number.isInteger, message: 'totalCredit must be an integer (cents)' } },\r\n state: {\r\n type: String,\r\n enum: ['draft', 'posted', 'archived'],\r\n default: 'draft',\r\n required: true,\r\n },\r\n stateChangedAt: { type: Date, default: Date.now },\r\n reversed: { type: Boolean, default: false },\r\n reversedBy: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: 'JournalEntry',\r\n default: null,\r\n },\r\n reversalOf: {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: 'JournalEntry',\r\n default: null,\r\n },\r\n ...extraFields,\r\n };\r\n\r\n // ── Audit fields (conditional) ─────────────────────────────────────────\r\n\r\n if (config.audit?.trackActor) {\r\n fields.createdBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.postedBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.reversedByUser = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n }\r\n\r\n // ── Approval fields (conditional) ──────────────────────────────────────\r\n\r\n if (config.strictness?.requireApproval || config.audit?.trackActor) {\r\n fields.approvedBy = { type: mongoose.Schema.Types.ObjectId, default: null };\r\n fields.approvedAt = { type: Date, default: null };\r\n }\r\n\r\n // ── Idempotency key (conditional) ──────────────────────────────────────\r\n\r\n if (config.idempotency) {\r\n fields.idempotencyKey = { type: String, default: null };\r\n }\r\n\r\n // ── Multi-tenant field ───────────────────────────────────────────────────\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // ── Schema ───────────────────────────────────────────────────────────────\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n // ── Pre-validate: double-entry enforcement ───────────────────────────────\r\n\r\n schema.pre('validate', function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n\r\n // Propagate entry date to items without a date\r\n for (const item of doc.journalItems) {\r\n if (!item.date) item.date = doc.date;\r\n }\r\n\r\n // Each line must be debit OR credit (not both), and posted entries cannot have zero-value lines\r\n for (let i = 0; i < doc.journalItems.length; i++) {\r\n const d = doc.journalItems[i].debit || 0;\r\n const c = doc.journalItems[i].credit || 0;\r\n if (d > 0 && c > 0) {\r\n throw new Error(\r\n `Journal item at index ${i}: cannot have both debit (${d}) and credit (${c}) greater than zero`,\r\n );\r\n }\r\n if (doc.state === 'posted' && d === 0 && c === 0) {\r\n throw new Error(\r\n `Journal item at index ${i}: posted entries cannot have zero-value lines (both debit and credit are 0)`,\r\n );\r\n }\r\n }\r\n\r\n // Calculate totals\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const totalDebit = doc.journalItems.reduce((s: number, i: any) => s + (i.debit || 0), 0);\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const totalCredit = doc.journalItems.reduce((s: number, i: any) => s + (i.credit || 0), 0);\r\n\r\n // Enforce minimum items and balance for posted entries\r\n if (doc.state === 'posted') {\r\n if (doc.journalItems.length < 2) {\r\n throw new Error('Posted entries must have at least 2 journal items');\r\n }\r\n if (totalDebit !== totalCredit) {\r\n throw new Error('Total debit must equal total credit for posted entries');\r\n }\r\n }\r\n\r\n doc.totalDebit = totalDebit;\r\n doc.totalCredit = totalCredit;\r\n });\r\n\r\n // ── Pre-save: auto-generate reference number ─────────────────────────────\r\n\r\n if (autoReference) {\r\n // Helper: compute next reference number from DB\r\n // Uses aggregation pipeline to extract & sort the numeric suffix,\r\n // avoiding lexicographic sort issues beyond sequence 9999.\r\n const generateReferenceNumber = async (doc: Record<string, unknown>, Model: mongoose.Model<unknown>, session: unknown) => {\r\n const jt = (doc.journalType as string) || 'MISC';\r\n const d = new Date(doc.date as string | number | Date);\r\n const year = d.getFullYear();\r\n const month = String(d.getMonth() + 1).padStart(2, '0');\r\n const prefix = `${jt}/${year}/${month}/`;\r\n\r\n // Build match filter\r\n const matchFilter: Record<string, unknown> = {\r\n referenceNumber: { $regex: `^${prefix.replace(/\\//g, '\\\\/')}` },\r\n };\r\n\r\n // Add org field to query for multi-tenant\r\n if (multiTenant) {\r\n matchFilter[multiTenant.orgField] = doc[multiTenant.orgField];\r\n }\r\n\r\n // Extract numeric suffix via $split and sort numerically\r\n const pipeline: mongoose.PipelineStage[] = [\r\n { $match: matchFilter },\r\n {\r\n $addFields: {\r\n _refSeq: {\r\n $toInt: {\r\n $arrayElemAt: [{ $split: ['$referenceNumber', '/'] }, -1],\r\n },\r\n },\r\n },\r\n },\r\n { $sort: { _refSeq: -1 as const } },\r\n { $limit: 1 },\r\n { $project: { _refSeq: 1 } },\r\n ];\r\n\r\n const results = await Model.aggregate(pipeline)\r\n .session(session as mongoose.mongo.ClientSession | null);\r\n\r\n let seq = 1;\r\n if (results.length > 0 && typeof results[0]._refSeq === 'number') {\r\n seq = results[0]._refSeq + 1;\r\n }\r\n\r\n return `${prefix}${String(seq).padStart(4, '0')}`;\r\n };\r\n\r\n schema.pre('save', async function () {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const doc = this as any;\r\n\r\n if (doc.isModified('journalType')) {\r\n doc.referenceNumber = undefined;\r\n }\r\n\r\n if (!doc.referenceNumber) {\r\n const session = doc.$session?.() ?? null;\r\n const Model = doc.constructor as mongoose.Model<unknown>;\r\n doc.referenceNumber = await generateReferenceNumber(doc, Model, session);\r\n }\r\n });\r\n\r\n // Retry on duplicate key error (race condition between concurrent inserts)\r\n const MAX_REF_RETRIES = 3;\r\n schema.post('save', async function (error: Error, doc: unknown, next: (err?: Error) => void) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const mongoError = error as any;\r\n // 11000 = MongoDB duplicate key error\r\n if (mongoError.code === 11000 && mongoError.keyPattern?.referenceNumber) {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const entry = doc as any;\r\n const retryCount: number = entry.__refRetries ?? 0;\r\n if (retryCount >= MAX_REF_RETRIES) {\r\n next(new Error(\r\n `Failed to generate unique reference number after ${MAX_REF_RETRIES} retries. ` +\r\n 'Too many concurrent inserts for this period.',\r\n ));\r\n return;\r\n }\r\n entry.__refRetries = retryCount + 1;\r\n const session = entry.$session?.() ?? null;\r\n const Model = entry.constructor as mongoose.Model<unknown>;\r\n entry.referenceNumber = await generateReferenceNumber(entry, Model, session);\r\n try {\r\n await entry.save({ session });\r\n next();\r\n } catch (retryError) {\r\n next(retryError as Error);\r\n }\r\n } else {\r\n next(error);\r\n }\r\n });\r\n }\r\n\r\n // ── Indexes ──────────────────────────────────────────────────────────────\r\n\r\n if (indexes) {\r\n const org = multiTenant?.orgField;\r\n\r\n // Partial filter: unique constraint only applies to docs with a string\r\n // referenceNumber — allows multiple entries without a ref when autoReference is off.\r\n const refPartial = { partialFilterExpression: { referenceNumber: { $exists: true, $type: 'string' } } };\r\n\r\n if (org) {\r\n schema.index({ [org]: 1, referenceNumber: 1 }, { unique: true, ...refPartial });\r\n schema.index({ [org]: 1, state: 1, date: 1 });\r\n schema.index({ [org]: 1, date: -1 });\r\n schema.index({ [org]: 1, journalType: 1 });\r\n schema.index({ 'journalItems.account': 1, state: 1 });\r\n schema.index({ [org]: 1, 'journalItems.account': 1, date: 1, state: 1 });\r\n } else {\r\n schema.index({ referenceNumber: 1 }, { unique: true, ...refPartial });\r\n schema.index({ state: 1, date: 1 });\r\n schema.index({ date: -1 });\r\n schema.index({ journalType: 1 });\r\n schema.index({ 'journalItems.account': 1, state: 1 });\r\n }\r\n\r\n schema.index({ reversed: 1 });\r\n\r\n // Idempotency key: unique sparse index (only when enabled)\r\n if (config.idempotency) {\r\n const idempotencyIdx: Record<string, 1 | -1> = {};\r\n if (org) idempotencyIdx[org] = 1;\r\n idempotencyIdx.idempotencyKey = 1;\r\n schema.index(idempotencyIdx, {\r\n unique: true,\r\n partialFilterExpression: { idempotencyKey: { $exists: true, $ne: null } },\r\n });\r\n }\r\n }\r\n\r\n if (textSearch) {\r\n schema.index(\r\n { referenceNumber: 'text', label: 'text' },\r\n { weights: { referenceNumber: 10, label: 5 }, name: 'journal_text_idx' },\r\n );\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n return schema;\r\n}\r\n","/**\r\n * Fiscal Period Schema Factory\r\n *\r\n * Creates a Mongoose schema for tracking fiscal periods (months, quarters, years).\r\n * Supports closing periods to lock entries.\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { AccountingEngineConfig, SchemaOptions } from '../types/engine.js';\r\n\r\nexport function createFiscalPeriodSchema(\r\n config: AccountingEngineConfig,\r\n options: SchemaOptions = {},\r\n) {\r\n const { multiTenant } = config;\r\n const { indexes = true, extraFields = {}, extraIndexes = [] } = options;\r\n\r\n const fields: Record<string, unknown> = {\r\n name: { type: String, required: true },\r\n startDate: { type: Date, required: true },\r\n endDate: { type: Date, required: true },\r\n closed: { type: Boolean, default: false },\r\n closedAt: { type: Date, default: null },\r\n closedBy: { type: String, default: null },\r\n closingEntryId: { type: mongoose.Schema.Types.ObjectId, default: null },\r\n reopenedAt: { type: Date, default: null },\r\n reopenedBy: { type: String, default: null },\r\n ...extraFields,\r\n };\r\n\r\n if (multiTenant) {\r\n fields[multiTenant.orgField] = {\r\n type: mongoose.Schema.Types.ObjectId,\r\n ref: multiTenant.orgRef,\r\n required: true,\r\n };\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const schema = new mongoose.Schema(fields as any, { timestamps: true });\r\n\r\n if (indexes) {\r\n if (multiTenant) {\r\n const org = multiTenant.orgField;\r\n schema.index({ [org]: 1, startDate: 1, endDate: 1 }, { unique: true });\r\n schema.index({ [org]: 1, closed: 1 });\r\n } else {\r\n schema.index({ startDate: 1, endDate: 1 }, { unique: true });\r\n schema.index({ closed: 1 });\r\n }\r\n }\r\n\r\n for (const idx of extraIndexes) {\r\n schema.index(idx.fields, idx.options);\r\n }\r\n\r\n // ── Overlap guard: prevent overlapping date ranges within a tenant ─────\r\n schema.pre('validate', async function () {\r\n const doc = this as mongoose.Document & { startDate: Date; endDate: Date; [key: string]: unknown };\r\n if (!doc.startDate || !doc.endDate) return;\r\n\r\n // A period overlaps if: existing.startDate < this.endDate AND existing.endDate > this.startDate\r\n const overlapQuery: Record<string, unknown> = {\r\n _id: { $ne: doc._id },\r\n startDate: { $lt: doc.endDate },\r\n endDate: { $gt: doc.startDate },\r\n };\r\n\r\n if (multiTenant) {\r\n overlapQuery[multiTenant.orgField] = doc[multiTenant.orgField];\r\n }\r\n\r\n const overlap = await doc.collection.findOne(overlapQuery);\r\n if (overlap) {\r\n const msg = `Fiscal period overlaps with existing period \"${overlap.name}\" (${new Date(overlap.startDate).toISOString().split('T')[0]} – ${new Date(overlap.endDate).toISOString().split('T')[0]}).`;\r\n doc.invalidate('startDate', msg, doc.startDate, 'overlap');\r\n }\r\n });\r\n\r\n return schema;\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { a as SchemaOptions, r as JournalSchemaOptions, t as AccountingEngineConfig } from "./engine-Cd73EOT6.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-DI2scngu.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fiscal-period.schema-DI2scngu.d.mts","names":[],"sources":["../src/schemas/account.schema.ts","../src/schemas/journal-entry.schema.ts","../src/schemas/fiscal-period.schema.ts"],"mappings":";;;;iBAagB,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;;;;;;;;;;;iBCTpB,wBAAA,CACd,MAAA,EAAQ,sBAAA,EACR,OAAA,GAAS,aAAA,GAAkB,QAAA,CAAA,MAAA,MAAA,QAAA,CAAA,KAAA"}
@@ -0,0 +1,58 @@
1
+ import { Model } from "mongoose";
2
+
3
+ //#region src/plugins/double-entry.plugin.d.ts
4
+ /** Minimal interface matching @classytic/mongokit RepositoryInstance */
5
+ interface RepositoryInstance$2 {
6
+ on(event: string, listener: (data: unknown) => void | Promise<void>): unknown;
7
+ }
8
+ interface DoubleEntryPluginOptions {
9
+ /** Only enforce on posted entries (default: true) */
10
+ onlyOnPost?: boolean;
11
+ /** Mongoose model — required to validate partial updates that only set state */
12
+ JournalEntryModel?: Model<unknown>;
13
+ /** Account model — when provided, posted creates verify account existence + tenant scoping */
14
+ AccountModel?: Model<unknown>;
15
+ /** Multi-tenant org field name (e.g. 'business'). Required for tenant-account integrity checks. */
16
+ orgField?: string;
17
+ }
18
+ declare function doubleEntryPlugin(options?: DoubleEntryPluginOptions): {
19
+ name: string;
20
+ apply(repo: RepositoryInstance$2): void;
21
+ };
22
+ //#endregion
23
+ //#region src/plugins/fiscal-lock.plugin.d.ts
24
+ /** Minimal interface matching @classytic/mongokit RepositoryInstance */
25
+ interface RepositoryInstance$1 {
26
+ on(event: string, listener: (data: unknown) => void | Promise<void>): unknown;
27
+ }
28
+ interface FiscalLockPluginOptions {
29
+ /** Mongoose model for fiscal periods */
30
+ FiscalPeriodModel: Model<unknown>;
31
+ /** Mongoose model for journal entries — needed to look up persisted date on partial updates */
32
+ JournalEntryModel?: Model<unknown>;
33
+ /** Organization field name (for multi-tenant) */
34
+ orgField?: string;
35
+ }
36
+ declare function fiscalLockPlugin(options: FiscalLockPluginOptions): {
37
+ name: string;
38
+ apply(repo: RepositoryInstance$1): void;
39
+ };
40
+ //#endregion
41
+ //#region src/plugins/idempotency.plugin.d.ts
42
+ /** Minimal interface matching @classytic/mongokit RepositoryInstance */
43
+ interface RepositoryInstance {
44
+ on(event: string, listener: (data: unknown) => void | Promise<void>): unknown;
45
+ }
46
+ interface IdempotencyPluginOptions {
47
+ /** Mongoose model for journal entries */
48
+ JournalEntryModel: Model<unknown>;
49
+ /** Multi-tenant org field name */
50
+ orgField?: string;
51
+ }
52
+ declare function idempotencyPlugin(options: IdempotencyPluginOptions): {
53
+ name: string;
54
+ apply(repo: RepositoryInstance): void;
55
+ };
56
+ //#endregion
57
+ export { DoubleEntryPluginOptions as a, fiscalLockPlugin as i, idempotencyPlugin as n, doubleEntryPlugin as o, FiscalLockPluginOptions as r, IdempotencyPluginOptions as t };
58
+ //# sourceMappingURL=idempotency.plugin-BESs9YPD.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.plugin-BESs9YPD.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"}