@accounter/server 0.0.9-alpha-20251231123714-6cdd9de71b4672d74ece5d34c438d162987b2c93 → 0.0.9-alpha-20251231163357-33d4c33fec5e21dad2e04d1f293f6248d2550588

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 (124) hide show
  1. package/CHANGELOG.md +27 -5
  2. package/README.md +66 -3
  3. package/dist/server/scripts/seed-admin-context.js +20 -25
  4. package/dist/server/scripts/seed-admin-context.js.map +1 -1
  5. package/dist/server/src/__tests__/db-bootstrap.test.js +7 -2
  6. package/dist/server/src/__tests__/db-bootstrap.test.js.map +1 -1
  7. package/dist/server/src/__tests__/factories/business.d.ts +1 -1
  8. package/dist/server/src/__tests__/factories/financial-account.d.ts +1 -1
  9. package/dist/server/src/__tests__/factories/index.test.js +1 -0
  10. package/dist/server/src/__tests__/factories/index.test.js.map +1 -1
  11. package/dist/server/src/__tests__/factories/tax-category.d.ts +1 -1
  12. package/dist/server/src/__tests__/factories/tax-category.js +1 -1
  13. package/dist/server/src/__tests__/factories/tax-category.js.map +1 -1
  14. package/dist/server/src/__tests__/factories/tax-category.test.js +8 -6
  15. package/dist/server/src/__tests__/factories/tax-category.test.js.map +1 -1
  16. package/dist/server/src/__tests__/helpers/fixture-loader.d.ts +1 -1
  17. package/dist/server/src/__tests__/helpers/fixture-loader.js +25 -52
  18. package/dist/server/src/__tests__/helpers/fixture-loader.js.map +1 -1
  19. package/dist/server/src/__tests__/helpers/migration-verification.d.ts +1 -1
  20. package/dist/server/src/__tests__/helpers/migration-verification.js.map +1 -1
  21. package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js +4 -4
  22. package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js.map +1 -1
  23. package/dist/server/src/__tests__/helpers/seed-helpers.d.ts +9 -9
  24. package/dist/server/src/__tests__/helpers/seed-helpers.js +57 -54
  25. package/dist/server/src/__tests__/helpers/seed-helpers.js.map +1 -1
  26. package/dist/server/src/__tests__/seed-admin-context.integration.test.js +2 -1
  27. package/dist/server/src/__tests__/seed-admin-context.integration.test.js.map +1 -1
  28. package/dist/server/src/demo-fixtures/__tests__/deterministic-uuid.test.js +3 -2
  29. package/dist/server/src/demo-fixtures/__tests__/deterministic-uuid.test.js.map +1 -1
  30. package/dist/server/src/demo-fixtures/__tests__/seed-and-validate.test.d.ts +1 -0
  31. package/dist/server/src/demo-fixtures/__tests__/seed-and-validate.test.js +69 -0
  32. package/dist/server/src/demo-fixtures/__tests__/seed-and-validate.test.js.map +1 -0
  33. package/dist/server/src/demo-fixtures/__tests__/use-case-registry.test.d.ts +1 -0
  34. package/dist/server/src/demo-fixtures/__tests__/use-case-registry.test.js +26 -0
  35. package/dist/server/src/demo-fixtures/__tests__/use-case-registry.test.js.map +1 -0
  36. package/dist/server/src/demo-fixtures/helpers/admin-context.d.ts +10 -0
  37. package/dist/server/src/demo-fixtures/helpers/admin-context.js +40 -0
  38. package/dist/server/src/demo-fixtures/helpers/admin-context.js.map +1 -0
  39. package/dist/server/src/demo-fixtures/helpers/placeholder.d.ts +45 -0
  40. package/dist/server/src/demo-fixtures/helpers/placeholder.js +50 -0
  41. package/dist/server/src/demo-fixtures/helpers/placeholder.js.map +1 -0
  42. package/dist/server/src/demo-fixtures/helpers/seed-exchange-rates.d.ts +21 -0
  43. package/dist/server/src/demo-fixtures/helpers/seed-exchange-rates.js +26 -0
  44. package/dist/server/src/demo-fixtures/helpers/seed-exchange-rates.js.map +1 -0
  45. package/dist/server/src/demo-fixtures/helpers/seed-vat.d.ts +20 -0
  46. package/dist/server/src/demo-fixtures/helpers/seed-vat.js +30 -0
  47. package/dist/server/src/demo-fixtures/helpers/seed-vat.js.map +1 -0
  48. package/dist/server/src/demo-fixtures/use-cases/equity/shareholder-dividend.d.ts +9 -0
  49. package/dist/server/src/demo-fixtures/use-cases/equity/shareholder-dividend.js +86 -0
  50. package/dist/server/src/demo-fixtures/use-cases/equity/shareholder-dividend.js.map +1 -0
  51. package/dist/server/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.d.ts +12 -0
  52. package/dist/server/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.js +375 -0
  53. package/dist/server/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.js.map +1 -0
  54. package/dist/server/src/demo-fixtures/use-cases/income/client-payment-with-refund.d.ts +10 -0
  55. package/dist/server/src/demo-fixtures/use-cases/income/client-payment-with-refund.js +113 -0
  56. package/dist/server/src/demo-fixtures/use-cases/income/client-payment-with-refund.js.map +1 -0
  57. package/dist/server/src/demo-fixtures/use-cases/index.d.ts +41 -0
  58. package/dist/server/src/demo-fixtures/use-cases/index.js +50 -0
  59. package/dist/server/src/demo-fixtures/use-cases/index.js.map +1 -0
  60. package/dist/server/src/demo-fixtures/validate-demo-data.d.ts +1 -0
  61. package/dist/server/src/demo-fixtures/validate-demo-data.js +117 -0
  62. package/dist/server/src/demo-fixtures/validate-demo-data.js.map +1 -0
  63. package/dist/server/src/demo-fixtures/validators/ledger-validators.d.ts +349 -0
  64. package/dist/server/src/demo-fixtures/validators/ledger-validators.js +602 -0
  65. package/dist/server/src/demo-fixtures/validators/ledger-validators.js.map +1 -0
  66. package/dist/server/src/demo-fixtures/validators/ledger-validators.test.d.ts +1 -0
  67. package/dist/server/src/demo-fixtures/validators/ledger-validators.test.js +247 -0
  68. package/dist/server/src/demo-fixtures/validators/ledger-validators.test.js.map +1 -0
  69. package/dist/server/src/demo-fixtures/validators/types.d.ts +69 -0
  70. package/dist/server/src/demo-fixtures/validators/types.js +8 -0
  71. package/dist/server/src/demo-fixtures/validators/types.js.map +1 -0
  72. package/dist/server/src/fixtures/fixture-spec.d.ts +146 -0
  73. package/dist/server/src/fixtures/fixture-spec.js +2 -0
  74. package/dist/server/src/fixtures/fixture-spec.js.map +1 -0
  75. package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js +4 -0
  76. package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js.map +1 -1
  77. package/dist/server/src/modules/deel/resolvers/deel.resolvers.js +0 -3
  78. package/dist/server/src/modules/deel/resolvers/deel.resolvers.js.map +1 -1
  79. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.js +4 -3
  80. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.js.map +1 -1
  81. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.js +5 -3
  82. package/dist/server/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.js.map +1 -1
  83. package/dist/server/src/shared/constants.d.ts +1 -0
  84. package/dist/server/src/shared/constants.js +1 -0
  85. package/dist/server/src/shared/constants.js.map +1 -1
  86. package/dist/server/src/shared/helpers/misc.js +2 -2
  87. package/dist/server/src/shared/helpers/misc.js.map +1 -1
  88. package/docs/demo-staging-guide.md +611 -0
  89. package/package.json +5 -2
  90. package/scripts/seed-admin-context.ts +22 -33
  91. package/src/__tests__/db-bootstrap.test.ts +9 -2
  92. package/src/__tests__/factories/business.ts +1 -1
  93. package/src/__tests__/factories/financial-account.ts +1 -1
  94. package/src/__tests__/factories/index.test.ts +1 -0
  95. package/src/__tests__/factories/tax-category.test.ts +8 -6
  96. package/src/__tests__/factories/tax-category.ts +2 -2
  97. package/src/__tests__/helpers/fixture-loader.ts +26 -61
  98. package/src/__tests__/helpers/migration-verification.ts +2 -2
  99. package/src/__tests__/helpers/seed-helpers.business.test.ts +4 -4
  100. package/src/__tests__/helpers/seed-helpers.ts +66 -75
  101. package/src/__tests__/seed-admin-context.integration.test.ts +2 -1
  102. package/src/demo-fixtures/__tests__/deterministic-uuid.test.ts +3 -2
  103. package/src/demo-fixtures/__tests__/seed-and-validate.test.ts +96 -0
  104. package/src/demo-fixtures/__tests__/use-case-registry.test.ts +27 -0
  105. package/src/demo-fixtures/helpers/admin-context.ts +59 -0
  106. package/src/demo-fixtures/helpers/placeholder.ts +50 -0
  107. package/src/demo-fixtures/helpers/seed-exchange-rates.ts +29 -0
  108. package/src/demo-fixtures/helpers/seed-vat.ts +35 -0
  109. package/src/demo-fixtures/use-cases/equity/shareholder-dividend.ts +88 -0
  110. package/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.ts +377 -0
  111. package/src/demo-fixtures/use-cases/income/client-payment-with-refund.ts +115 -0
  112. package/src/demo-fixtures/use-cases/index.ts +52 -0
  113. package/src/demo-fixtures/validate-demo-data.ts +153 -0
  114. package/src/demo-fixtures/validators/README.md +190 -0
  115. package/src/demo-fixtures/validators/ledger-validators.test.ts +298 -0
  116. package/src/demo-fixtures/validators/ledger-validators.ts +711 -0
  117. package/src/demo-fixtures/validators/types.ts +83 -0
  118. package/src/fixtures/fixture-spec.ts +158 -0
  119. package/src/modules/charges-matcher/__tests__/single-match-integration.test.ts +6 -0
  120. package/src/modules/deel/resolvers/deel.resolvers.ts +0 -3
  121. package/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.ts +4 -3
  122. package/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.ts +6 -3
  123. package/src/shared/constants.ts +2 -0
  124. package/src/shared/helpers/misc.ts +2 -3
@@ -0,0 +1,602 @@
1
+ /**
2
+ * Parse a numeric amount from a string, handling null/undefined values
3
+ *
4
+ * PostgreSQL returns numeric values as strings when queried via pg driver.
5
+ * This utility safely converts them to numbers for arithmetic operations.
6
+ *
7
+ * @param value - The string value to parse (may be null or undefined)
8
+ * @returns The parsed number, or 0 if value is null/undefined/invalid
9
+ *
10
+ * @example
11
+ * parseAmount('100.50') // 100.50
12
+ * parseAmount(null) // 0
13
+ * parseAmount('invalid') // 0
14
+ */
15
+ export function parseAmount(value) {
16
+ if (value == null) {
17
+ return 0;
18
+ }
19
+ const parsed = parseFloat(value);
20
+ if (Number.isNaN(parsed)) {
21
+ return 0;
22
+ }
23
+ return parsed;
24
+ }
25
+ /**
26
+ * Check if two numeric values are balanced within a tolerance
27
+ *
28
+ * Uses tolerance-based comparison to handle floating-point arithmetic
29
+ * imprecision. Two values are considered balanced if their absolute
30
+ * difference is less than or equal to the tolerance.
31
+ *
32
+ * @param a - First value to compare
33
+ * @param b - Second value to compare
34
+ * @param tolerance - Maximum acceptable difference (default: 0.005)
35
+ * @returns True if values are balanced within tolerance, false otherwise
36
+ *
37
+ * @example
38
+ * isBalanced(100, 100) // true
39
+ * isBalanced(100, 100.004, 0.005) // true
40
+ * isBalanced(100, 102, 0.005) // false
41
+ */
42
+ export function isBalanced(a, b, tolerance = 0.005) {
43
+ return Math.abs(a - b) <= tolerance;
44
+ }
45
+ /**
46
+ * Validate per-record internal balance (FR1)
47
+ *
48
+ * Ensures each ledger record is internally balanced according to double-entry
49
+ * bookkeeping principles: total debits must equal total credits within tolerance.
50
+ * Also detects empty records where all amounts are zero.
51
+ *
52
+ * This is a fundamental validation that must pass for every record individually
53
+ * before aggregate-level validations can be meaningful.
54
+ *
55
+ * @param records - Array of ledger records to validate
56
+ * @param context - Validation context containing use-case ID and tolerance
57
+ * @returns Array of error messages (empty if all records are valid)
58
+ *
59
+ * Functional Requirement: FR1 - Per-Record Internal Balance
60
+ * - Rule: (debit_local_amount1 + debit_local_amount2) == (credit_local_amount1 + credit_local_amount2)
61
+ * - Tolerance: Specified in context (typically ±0.005 for accounting rounding)
62
+ * - Also implements FR10: Empty Ledger Detection
63
+ *
64
+ * @example
65
+ * const errors = validateRecordInternalBalance(records, {
66
+ * useCaseId: 'monthly-expense',
67
+ * defaultCurrency: 'ILS',
68
+ * tolerance: 0.005
69
+ * });
70
+ * // Returns: [] if valid, or error messages like:
71
+ * // ["monthly-expense - Record 0 (uuid-123): internal imbalance (debit=100.00, credit=99.98)"]
72
+ */
73
+ export function validateRecordInternalBalance(records, context) {
74
+ const errors = [];
75
+ records.map((record, index) => {
76
+ const totalDebit = parseAmount(record.debit_local_amount1) + parseAmount(record.debit_local_amount2);
77
+ const totalCredit = parseAmount(record.credit_local_amount1) + parseAmount(record.credit_local_amount2);
78
+ // FR10: Empty record detection
79
+ if (totalDebit === 0 && totalCredit === 0) {
80
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): empty record (all amounts zero)`);
81
+ return; // Skip balance check for empty records
82
+ }
83
+ // FR1: Internal balance check
84
+ if (!isBalanced(totalDebit, totalCredit, context.tolerance)) {
85
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): internal imbalance ` +
86
+ `(debit=${totalDebit.toFixed(2)}, credit=${totalCredit.toFixed(2)})`);
87
+ }
88
+ });
89
+ return errors;
90
+ }
91
+ /**
92
+ * Validate aggregate balance across all records (FR2)
93
+ *
94
+ * Validates that the sum of all debits equals the sum of all credits across
95
+ * all ledger records for a use-case. This is the second level of validation
96
+ * (after per-record balance) and ensures the entire ledger set balances.
97
+ *
98
+ * This refactors and enhances the existing aggregate balance validation logic
99
+ * that was previously only applied to a single use-case. The new implementation
100
+ * applies to all use-cases with expectations.
101
+ *
102
+ * @param records - Array of ledger records to validate
103
+ * @param context - Validation context containing use-case ID and tolerance
104
+ * @returns Array of error messages (empty if aggregate is balanced)
105
+ *
106
+ * Functional Requirement: FR2 - Aggregate Balance Validation
107
+ * - Rule: Σ(all debits) == Σ(all credits)
108
+ * - Tolerance: Specified in context (typically ±0.005)
109
+ * - Enhancement: Now applies to ALL use-cases, not just first one
110
+ *
111
+ * @example
112
+ * const errors = validateAggregateBalance(records, {
113
+ * useCaseId: 'monthly-expense',
114
+ * defaultCurrency: 'ILS',
115
+ * tolerance: 0.005
116
+ * });
117
+ * // Returns: [] if valid, or error like:
118
+ * // ["monthly-expense: aggregate ledger not balanced (debit 1000.00, credit 999.50)"]
119
+ */
120
+ export function validateAggregateBalance(records, context) {
121
+ const errors = [];
122
+ const totalDebit = records.reduce((sum, rec) => {
123
+ return sum + parseAmount(rec.debit_local_amount1) + parseAmount(rec.debit_local_amount2);
124
+ }, 0);
125
+ const totalCredit = records.reduce((sum, rec) => {
126
+ return sum + parseAmount(rec.credit_local_amount1) + parseAmount(rec.credit_local_amount2);
127
+ }, 0);
128
+ if (!isBalanced(totalDebit, totalCredit, context.tolerance)) {
129
+ errors.push(`${context.useCaseId}: aggregate ledger not balanced ` +
130
+ `(debit ${totalDebit.toFixed(2)}, credit ${totalCredit.toFixed(2)})`);
131
+ }
132
+ return errors;
133
+ }
134
+ /**
135
+ * Validate entity-level balance (FR3)
136
+ *
137
+ * Validates that each financial entity's net position across all ledger records
138
+ * balances to zero (or within tolerance). This is the third level of validation
139
+ * in the hierarchy:
140
+ * 1. Per-record balance (FR1) - each record internally balanced
141
+ * 2. Aggregate balance (FR2) - all records collectively balanced
142
+ * 3. Entity balance (FR3) - each entity's position balanced across records
143
+ *
144
+ * In double-entry bookkeeping, every entity that appears in the ledger should
145
+ * have a net zero position when considering all transactions. If an entity has
146
+ * debits totaling $500 across various records, it should also have credits
147
+ * totaling $500 across those same or other records.
148
+ *
149
+ * @param records - Array of ledger records to validate
150
+ * @param context - Validation context containing use-case ID and tolerance
151
+ * @returns Array of error messages (empty if all entities are balanced)
152
+ *
153
+ * Functional Requirement: FR3 - Entity-Level Balance Validation
154
+ * - Rule: For each entity, Σ(debits) - Σ(credits) ≈ 0
155
+ * - Tolerance: Specified in context (typically ±0.005)
156
+ * - Tracks: debit/credit amounts across all 4 entity fields per record
157
+ *
158
+ * Implementation:
159
+ * - Accumulates debits and credits per entity across all records
160
+ * - Calculates net balance (totalDebit - totalCredit) for each entity
161
+ * - Validates net balance is within tolerance of zero
162
+ *
163
+ * @example
164
+ * const errors = validateEntityBalance(records, {
165
+ * useCaseId: 'monthly-expense',
166
+ * defaultCurrency: 'ILS',
167
+ * tolerance: 0.005
168
+ * });
169
+ * // Returns: [] if valid, or errors like:
170
+ * // ["monthly-expense: Entity entity-123 unbalanced (net=50.00, debit=150.00, credit=100.00, records=3)"]
171
+ */
172
+ export function validateEntityBalance(records, context) {
173
+ const errors = [];
174
+ const entityBalances = new Map();
175
+ /**
176
+ * Helper function to accumulate entity balance
177
+ * Adds debit/credit amounts to an entity's running totals
178
+ */
179
+ const addToEntity = (entityId, debit, credit) => {
180
+ if (!entityId)
181
+ return;
182
+ const current = entityBalances.get(entityId) || {
183
+ entityId,
184
+ totalDebit: 0,
185
+ totalCredit: 0,
186
+ netBalance: 0,
187
+ recordCount: 0,
188
+ };
189
+ current.totalDebit += debit;
190
+ current.totalCredit += credit;
191
+ current.netBalance = current.totalDebit - current.totalCredit;
192
+ current.recordCount += 1;
193
+ entityBalances.set(entityId, current);
194
+ };
195
+ // Accumulate balances for all entities across all records
196
+ records.map(record => {
197
+ addToEntity(record.debit_entity1, parseAmount(record.debit_local_amount1), 0);
198
+ addToEntity(record.debit_entity2, parseAmount(record.debit_local_amount2), 0);
199
+ addToEntity(record.credit_entity1, 0, parseAmount(record.credit_local_amount1));
200
+ addToEntity(record.credit_entity2, 0, parseAmount(record.credit_local_amount2));
201
+ });
202
+ // Validate each entity balances to zero (within tolerance)
203
+ Array.from(entityBalances.values()).map(balance => {
204
+ if (!isBalanced(balance.netBalance, 0, context.tolerance)) {
205
+ errors.push(`${context.useCaseId}: Entity ${balance.entityId} unbalanced ` +
206
+ `(net=${balance.netBalance.toFixed(2)}, debit=${balance.totalDebit.toFixed(2)}, ` +
207
+ `credit=${balance.totalCredit.toFixed(2)}, records=${balance.recordCount})`);
208
+ }
209
+ });
210
+ return errors;
211
+ }
212
+ /**
213
+ * Validate ledger record count (FR8)
214
+ *
215
+ * Validates that the actual number of ledger records matches the expected count
216
+ * specified in the use-case expectations. This ensures data completeness and
217
+ * detects cases where records may be missing or extra records were created.
218
+ *
219
+ * This enhances the existing record count validation by applying it to all
220
+ * use-cases systematically rather than ad-hoc checks.
221
+ *
222
+ * @param records - Array of ledger records to validate
223
+ * @param expectedCount - Expected number of ledger records for this use-case
224
+ * @param context - Validation context containing use-case ID
225
+ * @returns Array of error messages (empty if count matches)
226
+ *
227
+ * Functional Requirement: FR8 - Record Count Validation
228
+ * - Rule: Actual record count must match expected count exactly
229
+ * - Enhancement: Future support for minimum count validation for cases
230
+ * where ledger generation may create additional balancing entries
231
+ *
232
+ * @example
233
+ * const errors = validateRecordCount(records, 24, {
234
+ * useCaseId: 'monthly-expense',
235
+ * defaultCurrency: 'ILS',
236
+ * tolerance: 0.005
237
+ * });
238
+ * // Returns: [] if count matches, or error like:
239
+ * // ["monthly-expense: ledger record count mismatch (expected 24, got 23)"]
240
+ */
241
+ export function validateRecordCount(records, expectedCount, context) {
242
+ const errors = [];
243
+ if (records.length !== expectedCount) {
244
+ errors.push(`${context.useCaseId}: ledger record count mismatch ` +
245
+ `(expected ${expectedCount}, got ${records.length})`);
246
+ }
247
+ return errors;
248
+ }
249
+ /**
250
+ * Validate all amounts are positive (FR5)
251
+ *
252
+ * Ensures data integrity by validating that all amount fields contain
253
+ * non-negative values. Negative amounts are not allowed in the ledger
254
+ * system as they violate accounting principles where debits and credits
255
+ * must always be positive or zero.
256
+ *
257
+ * This validation checks all 8 amount fields per record:
258
+ * - Local amounts: debit_local_amount1/2, credit_local_amount1/2
259
+ * - Foreign amounts: debit_foreign_amount1/2, credit_foreign_amount1/2
260
+ *
261
+ * @param records - Array of ledger records to validate
262
+ * @param context - Validation context containing use-case ID
263
+ * @returns Array of error messages (empty if all amounts are non-negative)
264
+ *
265
+ * Functional Requirement: FR5 - Positive Amount Validation
266
+ * - Checks all amount fields for negative values
267
+ * - Reports specific field and value for any negative amounts found
268
+ *
269
+ * @example
270
+ * const errors = validatePositiveAmounts(records, {
271
+ * useCaseId: 'monthly-expense',
272
+ * defaultCurrency: 'ILS',
273
+ * tolerance: 0.005
274
+ * });
275
+ * // Returns: [] if valid, or errors like:
276
+ * // ["monthly-expense - Record 0 (uuid-123): negative amount in debit_local_amount1 (-100.00)"]
277
+ */
278
+ export function validatePositiveAmounts(records, context) {
279
+ const errors = [];
280
+ const amountFields = [
281
+ 'debit_local_amount1',
282
+ 'debit_local_amount2',
283
+ 'credit_local_amount1',
284
+ 'credit_local_amount2',
285
+ 'debit_foreign_amount1',
286
+ 'debit_foreign_amount2',
287
+ 'credit_foreign_amount1',
288
+ 'credit_foreign_amount2',
289
+ ];
290
+ records.map((record, index) => {
291
+ amountFields.map(field => {
292
+ const value = parseAmount(record[field]);
293
+ if (value < 0) {
294
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
295
+ `negative amount in ${field} (${value.toFixed(2)})`);
296
+ }
297
+ });
298
+ });
299
+ return errors;
300
+ }
301
+ /**
302
+ * Validate dates (FR7)
303
+ *
304
+ * Ensures all ledger records have valid invoice_date and value_date fields
305
+ * within acceptable ranges. Proper date validation is critical for:
306
+ * - Financial reporting accuracy
307
+ * - Tax compliance and audit trails
308
+ * - Chronological transaction ordering
309
+ * - Preventing data entry errors
310
+ *
311
+ * Validation rules:
312
+ * - Both invoice_date and value_date must be present (not null)
313
+ * - Both dates must be valid Date objects (not NaN)
314
+ * - Both dates must fall within the range 2020-01-01 to 2030-12-31
315
+ *
316
+ * @param records - Array of ledger records to validate
317
+ * @param context - Validation context containing use-case ID
318
+ * @returns Array of error messages (empty if all dates are valid)
319
+ *
320
+ * Functional Requirement: FR7 - Date Validation
321
+ * - Checks for missing dates (null values)
322
+ * - Checks for invalid dates (parse errors)
323
+ * - Checks for dates outside reasonable business range
324
+ *
325
+ * @example
326
+ * const errors = validateDates(records, {
327
+ * useCaseId: 'monthly-expense',
328
+ * defaultCurrency: 'ILS',
329
+ * tolerance: 0.005
330
+ * });
331
+ * // Returns: [] if valid, or errors like:
332
+ * // ["monthly-expense - Record 0 (uuid-123): missing invoice_date"]
333
+ * // ["monthly-expense - Record 1 (uuid-456): invoice_date out of range (1999-01-01T00:00:00.000Z)"]
334
+ */
335
+ export function validateDates(records, context) {
336
+ const errors = [];
337
+ const minDate = new Date('2020-01-01');
338
+ const maxDate = new Date('2030-12-31');
339
+ records.map((record, index) => {
340
+ // Check invoice_date
341
+ if (record.invoice_date) {
342
+ const invoiceDate = new Date(record.invoice_date);
343
+ if (Number.isNaN(invoiceDate.getTime())) {
344
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): invalid invoice_date`);
345
+ }
346
+ else if (invoiceDate < minDate || invoiceDate > maxDate) {
347
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
348
+ `invoice_date out of range (${invoiceDate.toISOString()})`);
349
+ }
350
+ }
351
+ else {
352
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): missing invoice_date`);
353
+ }
354
+ // Check value_date
355
+ if (record.value_date) {
356
+ const valueDate = new Date(record.value_date);
357
+ if (Number.isNaN(valueDate.getTime())) {
358
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): invalid value_date`);
359
+ }
360
+ else if (valueDate < minDate || valueDate > maxDate) {
361
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
362
+ `value_date out of range (${valueDate.toISOString()})`);
363
+ }
364
+ }
365
+ else {
366
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): missing value_date`);
367
+ }
368
+ });
369
+ return errors;
370
+ }
371
+ /**
372
+ * Validate foreign currency handling (FR6)
373
+ *
374
+ * Ensures proper handling of foreign currency transactions by validating:
375
+ * 1. Currency field consistency with foreign amount fields
376
+ * 2. Presence of foreign amounts when currency is not the default (ILS)
377
+ * 3. Absence of foreign amounts when currency is the default (ILS)
378
+ * 4. Reasonableness of implied exchange rates between local and foreign amounts
379
+ *
380
+ * Foreign currency validation is critical for:
381
+ * - Accurate financial reporting in multi-currency environments
382
+ * - Compliance with international accounting standards
383
+ * - Detection of data entry errors in currency conversion
384
+ * - Prevention of fraudulent or suspicious exchange rate manipulation
385
+ *
386
+ * Exchange rate validation:
387
+ * - Implied rate = local_amount / foreign_amount
388
+ * - Rate must be between 0.1 and 10.0 to be considered reasonable
389
+ * - Rates outside this range likely indicate data entry errors
390
+ *
391
+ * @param records - Array of ledger records to validate
392
+ * @param context - Validation context containing use-case ID and default currency
393
+ * @returns Array of error messages (empty if all currency handling is valid)
394
+ *
395
+ * Functional Requirement: FR6 - Foreign Currency Validation
396
+ * - Validates currency field matches foreign amount presence/absence
397
+ * - Checks exchange rate consistency within reasonable bounds
398
+ * - Applies to all 4 amount pairs (debit1, debit2, credit1, credit2)
399
+ *
400
+ * @example
401
+ * const errors = validateForeignCurrency(records, {
402
+ * useCaseId: 'monthly-expense',
403
+ * defaultCurrency: 'ILS',
404
+ * tolerance: 0.005
405
+ * });
406
+ * // Returns: [] if valid, or errors like:
407
+ * // ["monthly-expense - Record 0 (uuid-123): foreign currency (USD) but no foreign amounts"]
408
+ * // ["monthly-expense - Record 1 (uuid-456): local currency (ILS) but has foreign amounts"]
409
+ * // ["monthly-expense - Record 2 (uuid-789): suspicious exchange rate in debit1 (rate=15.2000)"]
410
+ */
411
+ export function validateForeignCurrency(records, context) {
412
+ const errors = [];
413
+ records.map((record, index) => {
414
+ const isForeignCurrency = record.currency !== context.defaultCurrency;
415
+ const hasForeignAmounts = record.debit_foreign_amount1 !== null ||
416
+ record.debit_foreign_amount2 !== null ||
417
+ record.credit_foreign_amount1 !== null ||
418
+ record.credit_foreign_amount2 !== null;
419
+ // Validate currency field consistency with foreign amounts
420
+ if (isForeignCurrency && !hasForeignAmounts) {
421
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
422
+ `foreign currency (${record.currency}) but no foreign amounts`);
423
+ }
424
+ if (!isForeignCurrency && hasForeignAmounts) {
425
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
426
+ `local currency (${record.currency}) but has foreign amounts`);
427
+ }
428
+ // Validate exchange rate consistency for foreign currency records
429
+ if (isForeignCurrency) {
430
+ /**
431
+ * Helper function to check exchange rate reasonableness
432
+ * @param localAmount - Local currency amount (e.g., ILS)
433
+ * @param foreignAmount - Foreign currency amount (e.g., USD)
434
+ * @param field - Field identifier for error reporting
435
+ */
436
+ const checkExchangeRate = (localAmount, foreignAmount, field) => {
437
+ // Skip if either amount is null or zero
438
+ if (!localAmount || !foreignAmount)
439
+ return;
440
+ const local = parseAmount(localAmount);
441
+ const foreign = parseAmount(foreignAmount);
442
+ if (foreign === 0)
443
+ return; // Avoid division by zero
444
+ const impliedRate = local / foreign;
445
+ // Check if rate is reasonable (between 0.1 and 10.0)
446
+ // This catches obvious data entry errors like:
447
+ // - Swapped local/foreign amounts (rate would be inverted)
448
+ // - Missing decimal points (e.g., 350 instead of 3.50)
449
+ // - Completely incorrect amounts
450
+ if (impliedRate < 0.1 || impliedRate > 10.0) {
451
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
452
+ `suspicious exchange rate in ${field} (rate=${impliedRate.toFixed(4)})`);
453
+ }
454
+ };
455
+ // Check exchange rates for all 4 amount pairs
456
+ checkExchangeRate(record.debit_local_amount1, record.debit_foreign_amount1, 'debit1');
457
+ checkExchangeRate(record.debit_local_amount2, record.debit_foreign_amount2, 'debit2');
458
+ checkExchangeRate(record.credit_local_amount1, record.credit_foreign_amount1, 'credit1');
459
+ checkExchangeRate(record.credit_local_amount2, record.credit_foreign_amount2, 'credit2');
460
+ }
461
+ });
462
+ return errors;
463
+ }
464
+ /**
465
+ * Validate no orphaned amounts (FR4)
466
+ *
467
+ * Ensures data integrity by validating that every non-zero amount field has
468
+ * a corresponding entity reference. This prevents "orphaned" amounts that
469
+ * cannot be attributed to any financial entity.
470
+ *
471
+ * An "orphaned amount" is a ledger entry where an amount value exists but
472
+ * its corresponding entity field is null. This violates double-entry
473
+ * bookkeeping principles where every amount must be associated with an entity.
474
+ *
475
+ * Rules enforced:
476
+ * - Primary fields (entity1): If amount > 0, entity must be present
477
+ * - Secondary fields (entity2): If entity is null, amount must also be null
478
+ *
479
+ * @param records - Array of ledger records to validate
480
+ * @param context - Validation context containing use-case ID
481
+ * @returns Array of error messages (empty if no orphaned amounts found)
482
+ *
483
+ * Functional Requirement: FR4 - Orphaned Amount Detection
484
+ * - Checks all 4 amount/entity pairs per record
485
+ * - Detects amounts without entities
486
+ * - Detects secondary fields that should be null
487
+ *
488
+ * @example
489
+ * const errors = validateNoOrphanedAmounts(records, {
490
+ * useCaseId: 'monthly-expense',
491
+ * defaultCurrency: 'ILS',
492
+ * tolerance: 0.005
493
+ * });
494
+ * // Returns: [] if valid, or errors like:
495
+ * // ["monthly-expense - Record 0 (uuid-123): orphaned amount in debit_local_amount1/debit_entity1 (100.00 without entity)"]
496
+ */
497
+ export function validateNoOrphanedAmounts(records, context) {
498
+ const errors = [];
499
+ records.map((record, index) => {
500
+ const checks = [
501
+ // Debit entity 1 (primary - always required to have entity if amount > 0)
502
+ {
503
+ amount: parseAmount(record.debit_local_amount1),
504
+ entity: record.debit_entity1,
505
+ field: 'debit_local_amount1/debit_entity1',
506
+ },
507
+ // Debit entity 2 (secondary - both should be null or both populated)
508
+ {
509
+ amount: parseAmount(record.debit_local_amount2),
510
+ entity: record.debit_entity2,
511
+ field: 'debit_local_amount2/debit_entity2',
512
+ },
513
+ // Credit entity 1 (primary)
514
+ {
515
+ amount: parseAmount(record.credit_local_amount1),
516
+ entity: record.credit_entity1,
517
+ field: 'credit_local_amount1/credit_entity1',
518
+ },
519
+ // Credit entity 2 (secondary)
520
+ {
521
+ amount: parseAmount(record.credit_local_amount2),
522
+ entity: record.credit_entity2,
523
+ field: 'credit_local_amount2/credit_entity2',
524
+ },
525
+ ];
526
+ checks.map(({ amount, entity, field }) => {
527
+ if (amount > 0 && !entity) {
528
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
529
+ `orphaned amount in ${field} (${amount.toFixed(2)} without entity)`);
530
+ }
531
+ // Secondary fields: if entity is null, amount should also be null
532
+ if (field.includes('2') &&
533
+ !entity &&
534
+ record[field.split('/')[0]] !== null) {
535
+ errors.push(`${context.useCaseId} - Record ${index} (${record.id}): ` +
536
+ `${field} should be null when entity is null`);
537
+ }
538
+ });
539
+ });
540
+ return errors;
541
+ }
542
+ /**
543
+ * Master validation function - runs all validators (FR1-FR10)
544
+ *
545
+ * Orchestrates comprehensive ledger validation by executing all individual
546
+ * validation functions in a logical sequence. This is the main entry point
547
+ * for validating a complete set of ledger records for a use-case.
548
+ *
549
+ * Validation hierarchy:
550
+ * 1. Per-record validation (FR1, FR10) - Each record is internally balanced
551
+ * 2. Aggregate validation (FR2) - Total debits equal total credits
552
+ * 3. Entity validation (FR3) - Each entity's position balances
553
+ * 4. Data integrity (FR4, FR5) - No orphaned amounts, all amounts positive
554
+ * 5. Business rules (FR6, FR7) - Foreign currency and date validation
555
+ * 6. Structural validation (FR8) - Record count matches expectations
556
+ *
557
+ * This function implements NFR2 (Error Reporting): Collects ALL errors before
558
+ * failing (no fail-fast), allowing comprehensive error discovery in a single run.
559
+ *
560
+ * @param records - Array of ledger records to validate
561
+ * @param expectedRecordCount - Expected number of ledger records for this use-case
562
+ * @param context - Validation context containing use-case ID, currency, and tolerance
563
+ * @returns Array of all error messages from all validators (empty if fully valid)
564
+ *
565
+ * Functional Requirements Implemented:
566
+ * - FR1: Per-Record Internal Balance
567
+ * - FR2: Aggregate Balance Validation
568
+ * - FR3: Entity-Level Balance Validation
569
+ * - FR4: Orphaned Amount Detection
570
+ * - FR5: Positive Amount Validation
571
+ * - FR6: Foreign Currency Validation
572
+ * - FR7: Date Validation
573
+ * - FR8: Record Count Validation
574
+ * - FR10: Empty Ledger Detection (within FR1)
575
+ *
576
+ * @example
577
+ * const errors = validateLedgerRecords(records, 24, {
578
+ * useCaseId: 'monthly-expense',
579
+ * defaultCurrency: 'ILS',
580
+ * tolerance: 0.005
581
+ * });
582
+ * if (errors.length > 0) {
583
+ * console.error('Validation failed:', errors);
584
+ * } else {
585
+ * console.log('All validations passed');
586
+ * }
587
+ */
588
+ export function validateLedgerRecords(records, expectedRecordCount, context) {
589
+ const allErrors = [];
590
+ // Run all validators in logical order
591
+ // Each validator adds its errors to the aggregate array
592
+ allErrors.push(...validateRecordInternalBalance(records, context));
593
+ allErrors.push(...validateAggregateBalance(records, context));
594
+ allErrors.push(...validateEntityBalance(records, context));
595
+ allErrors.push(...validateNoOrphanedAmounts(records, context));
596
+ allErrors.push(...validatePositiveAmounts(records, context));
597
+ allErrors.push(...validateForeignCurrency(records, context));
598
+ allErrors.push(...validateDates(records, context));
599
+ allErrors.push(...validateRecordCount(records, expectedRecordCount, context));
600
+ return allErrors;
601
+ }
602
+ //# sourceMappingURL=ledger-validators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-validators.js","sourceRoot":"","sources":["../../../../../src/demo-fixtures/validators/ledger-validators.ts"],"names":[],"mappings":"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"}