@accounter/server 0.0.9-alpha-20251231123714-6cdd9de71b4672d74ece5d34c438d162987b2c93 → 0.0.9-alpha-20251231171312-67d2850c6eacafb65d42d987f3d49dca3c0f8bdd
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.
- package/CHANGELOG.md +27 -5
- package/README.md +66 -3
- package/dist/green-invoice-graphql/src/mesh-artifacts/index.d.ts +1 -1
- package/dist/green-invoice-graphql/src/mesh-artifacts/index.js +2 -2
- package/dist/green-invoice-graphql/src/mesh-artifacts/index.js.map +1 -1
- package/dist/server/scripts/seed-admin-context.js +20 -25
- package/dist/server/scripts/seed-admin-context.js.map +1 -1
- package/dist/server/src/__tests__/db-bootstrap.test.js +7 -2
- package/dist/server/src/__tests__/db-bootstrap.test.js.map +1 -1
- package/dist/server/src/__tests__/factories/business.d.ts +1 -1
- package/dist/server/src/__tests__/factories/financial-account.d.ts +1 -1
- package/dist/server/src/__tests__/factories/index.test.js +1 -0
- package/dist/server/src/__tests__/factories/index.test.js.map +1 -1
- package/dist/server/src/__tests__/factories/tax-category.d.ts +1 -1
- package/dist/server/src/__tests__/factories/tax-category.js +1 -1
- package/dist/server/src/__tests__/factories/tax-category.js.map +1 -1
- package/dist/server/src/__tests__/factories/tax-category.test.js +8 -6
- package/dist/server/src/__tests__/factories/tax-category.test.js.map +1 -1
- package/dist/server/src/__tests__/helpers/fixture-loader.d.ts +1 -1
- package/dist/server/src/__tests__/helpers/fixture-loader.js +25 -52
- package/dist/server/src/__tests__/helpers/fixture-loader.js.map +1 -1
- package/dist/server/src/__tests__/helpers/migration-verification.d.ts +1 -1
- package/dist/server/src/__tests__/helpers/migration-verification.js.map +1 -1
- package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js +4 -4
- package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js.map +1 -1
- package/dist/server/src/__tests__/helpers/seed-helpers.d.ts +9 -9
- package/dist/server/src/__tests__/helpers/seed-helpers.js +57 -54
- package/dist/server/src/__tests__/helpers/seed-helpers.js.map +1 -1
- package/dist/server/src/__tests__/seed-admin-context.integration.test.js +2 -1
- package/dist/server/src/__tests__/seed-admin-context.integration.test.js.map +1 -1
- package/dist/server/src/demo-fixtures/__tests__/deterministic-uuid.test.js +3 -2
- package/dist/server/src/demo-fixtures/__tests__/deterministic-uuid.test.js.map +1 -1
- package/dist/server/src/demo-fixtures/__tests__/seed-and-validate.test.d.ts +1 -0
- package/dist/server/src/demo-fixtures/__tests__/seed-and-validate.test.js +69 -0
- package/dist/server/src/demo-fixtures/__tests__/seed-and-validate.test.js.map +1 -0
- package/dist/server/src/demo-fixtures/__tests__/use-case-registry.test.d.ts +1 -0
- package/dist/server/src/demo-fixtures/__tests__/use-case-registry.test.js +26 -0
- package/dist/server/src/demo-fixtures/__tests__/use-case-registry.test.js.map +1 -0
- package/dist/server/src/demo-fixtures/helpers/admin-context.d.ts +10 -0
- package/dist/server/src/demo-fixtures/helpers/admin-context.js +40 -0
- package/dist/server/src/demo-fixtures/helpers/admin-context.js.map +1 -0
- package/dist/server/src/demo-fixtures/helpers/placeholder.d.ts +45 -0
- package/dist/server/src/demo-fixtures/helpers/placeholder.js +50 -0
- package/dist/server/src/demo-fixtures/helpers/placeholder.js.map +1 -0
- package/dist/server/src/demo-fixtures/helpers/seed-exchange-rates.d.ts +21 -0
- package/dist/server/src/demo-fixtures/helpers/seed-exchange-rates.js +26 -0
- package/dist/server/src/demo-fixtures/helpers/seed-exchange-rates.js.map +1 -0
- package/dist/server/src/demo-fixtures/helpers/seed-vat.d.ts +20 -0
- package/dist/server/src/demo-fixtures/helpers/seed-vat.js +30 -0
- package/dist/server/src/demo-fixtures/helpers/seed-vat.js.map +1 -0
- package/dist/server/src/demo-fixtures/use-cases/equity/shareholder-dividend.d.ts +9 -0
- package/dist/server/src/demo-fixtures/use-cases/equity/shareholder-dividend.js +86 -0
- package/dist/server/src/demo-fixtures/use-cases/equity/shareholder-dividend.js.map +1 -0
- package/dist/server/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.d.ts +12 -0
- package/dist/server/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.js +375 -0
- package/dist/server/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.js.map +1 -0
- package/dist/server/src/demo-fixtures/use-cases/income/client-payment-with-refund.d.ts +10 -0
- package/dist/server/src/demo-fixtures/use-cases/income/client-payment-with-refund.js +113 -0
- package/dist/server/src/demo-fixtures/use-cases/income/client-payment-with-refund.js.map +1 -0
- package/dist/server/src/demo-fixtures/use-cases/index.d.ts +41 -0
- package/dist/server/src/demo-fixtures/use-cases/index.js +50 -0
- package/dist/server/src/demo-fixtures/use-cases/index.js.map +1 -0
- package/dist/server/src/demo-fixtures/validate-demo-data.d.ts +1 -0
- package/dist/server/src/demo-fixtures/validate-demo-data.js +117 -0
- package/dist/server/src/demo-fixtures/validate-demo-data.js.map +1 -0
- package/dist/server/src/demo-fixtures/validators/ledger-validators.d.ts +349 -0
- package/dist/server/src/demo-fixtures/validators/ledger-validators.js +602 -0
- package/dist/server/src/demo-fixtures/validators/ledger-validators.js.map +1 -0
- package/dist/server/src/demo-fixtures/validators/ledger-validators.test.d.ts +1 -0
- package/dist/server/src/demo-fixtures/validators/ledger-validators.test.js +247 -0
- package/dist/server/src/demo-fixtures/validators/ledger-validators.test.js.map +1 -0
- package/dist/server/src/demo-fixtures/validators/types.d.ts +69 -0
- package/dist/server/src/demo-fixtures/validators/types.js +8 -0
- package/dist/server/src/demo-fixtures/validators/types.js.map +1 -0
- package/dist/server/src/fixtures/fixture-spec.d.ts +146 -0
- package/dist/server/src/fixtures/fixture-spec.js +2 -0
- package/dist/server/src/fixtures/fixture-spec.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js +4 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js.map +1 -1
- package/dist/server/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.js +4 -3
- package/dist/server/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.js.map +1 -1
- package/dist/server/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.js +5 -3
- package/dist/server/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.js.map +1 -1
- package/dist/server/src/shared/constants.d.ts +1 -0
- package/dist/server/src/shared/constants.js +1 -0
- package/dist/server/src/shared/constants.js.map +1 -1
- package/dist/server/src/shared/helpers/misc.js +2 -2
- package/dist/server/src/shared/helpers/misc.js.map +1 -1
- package/docs/demo-staging-guide.md +611 -0
- package/package.json +5 -2
- package/scripts/seed-admin-context.ts +22 -33
- package/src/__tests__/db-bootstrap.test.ts +9 -2
- package/src/__tests__/factories/business.ts +1 -1
- package/src/__tests__/factories/financial-account.ts +1 -1
- package/src/__tests__/factories/index.test.ts +1 -0
- package/src/__tests__/factories/tax-category.test.ts +8 -6
- package/src/__tests__/factories/tax-category.ts +2 -2
- package/src/__tests__/helpers/fixture-loader.ts +26 -61
- package/src/__tests__/helpers/migration-verification.ts +2 -2
- package/src/__tests__/helpers/seed-helpers.business.test.ts +4 -4
- package/src/__tests__/helpers/seed-helpers.ts +66 -75
- package/src/__tests__/seed-admin-context.integration.test.ts +2 -1
- package/src/demo-fixtures/__tests__/deterministic-uuid.test.ts +3 -2
- package/src/demo-fixtures/__tests__/seed-and-validate.test.ts +96 -0
- package/src/demo-fixtures/__tests__/use-case-registry.test.ts +27 -0
- package/src/demo-fixtures/helpers/admin-context.ts +59 -0
- package/src/demo-fixtures/helpers/placeholder.ts +50 -0
- package/src/demo-fixtures/helpers/seed-exchange-rates.ts +29 -0
- package/src/demo-fixtures/helpers/seed-vat.ts +35 -0
- package/src/demo-fixtures/use-cases/equity/shareholder-dividend.ts +88 -0
- package/src/demo-fixtures/use-cases/expenses/monthly-expense-foreign-currency.ts +377 -0
- package/src/demo-fixtures/use-cases/income/client-payment-with-refund.ts +115 -0
- package/src/demo-fixtures/use-cases/index.ts +52 -0
- package/src/demo-fixtures/validate-demo-data.ts +153 -0
- package/src/demo-fixtures/validators/README.md +190 -0
- package/src/demo-fixtures/validators/ledger-validators.test.ts +298 -0
- package/src/demo-fixtures/validators/ledger-validators.ts +711 -0
- package/src/demo-fixtures/validators/types.ts +83 -0
- package/src/fixtures/fixture-spec.ts +158 -0
- package/src/modules/charges-matcher/__tests__/single-match-integration.test.ts +6 -0
- package/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.ts +4 -3
- package/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.ts +6 -3
- package/src/shared/constants.ts +2 -0
- 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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|