@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,117 @@
1
+ /**
2
+ * Demo Data Validation Script
3
+ *
4
+ * Validates the integrity of seeded demo data with comprehensive ledger validation.
5
+ *
6
+ * Validation Checks:
7
+ * - Admin business entity exists
8
+ * - Charge count matches expected
9
+ * - Ledger records for each use-case:
10
+ * - Per-record internal balance (FR1)
11
+ * - Aggregate balance (FR2)
12
+ * - Entity-level balance (FR3)
13
+ * - No orphaned amounts (FR4)
14
+ * - Positive amounts only (FR5)
15
+ * - Foreign currency consistency (FR6)
16
+ * - Valid dates (FR7)
17
+ * - Record count matches (FR8)
18
+ *
19
+ * Exit codes:
20
+ * - 0: All validations passed
21
+ * - 1: Validation errors found or database connection failed
22
+ */
23
+ import { config } from 'dotenv';
24
+ import pg from 'pg';
25
+ import { getAllUseCases } from './use-cases/index.js';
26
+ import { validateLedgerRecords } from './validators/ledger-validators.js';
27
+ config({
28
+ path: '../../.env',
29
+ });
30
+ const DEFAULT_CURRENCY = 'ILS';
31
+ const BALANCE_TOLERANCE = 0.005;
32
+ /**
33
+ * Main validation function - connects to DB and runs all checks
34
+ */
35
+ async function validateDemoData() {
36
+ const client = new pg.Client({
37
+ user: process.env.POSTGRES_USER,
38
+ password: process.env.POSTGRES_PASSWORD,
39
+ host: process.env.POSTGRES_HOST,
40
+ port: parseInt(process.env.POSTGRES_PORT || '5432'),
41
+ database: process.env.POSTGRES_DB,
42
+ ssl: process.env.POSTGRES_SSL === '1',
43
+ });
44
+ const errors = [];
45
+ try {
46
+ await client.connect();
47
+ // 1. Admin business exists
48
+ const adminCheck = await client.query(`SELECT id FROM accounter_schema.financial_entities WHERE type = 'business' AND name = 'Accounter Admin Business'`);
49
+ if (adminCheck.rows.length === 0) {
50
+ errors.push('Admin business entity missing');
51
+ }
52
+ // 2. Use-case charge count reconciliation
53
+ const useCases = getAllUseCases();
54
+ const expectedChargeCount = useCases.reduce((sum, uc) => sum + uc.fixtures.charges.length, 0);
55
+ const actualChargeCount = await client.query(`SELECT COUNT(*) FROM accounter_schema.charges`);
56
+ if (parseInt(actualChargeCount.rows[0].count) !== expectedChargeCount) {
57
+ errors.push(`Charge count mismatch: expected ${expectedChargeCount}, got ${actualChargeCount.rows[0].count}`);
58
+ }
59
+ // 3. Comprehensive ledger validation for all use-cases with expectations (FR9)
60
+ const useCasesWithExpectations = useCases.filter(uc => uc.expectations);
61
+ console.log(`\nValidating ledger records for ${useCasesWithExpectations.length} use-case(s)...`);
62
+ let ledgerRecordsExist = false;
63
+ for (const useCase of useCasesWithExpectations) {
64
+ // Get all charges for this use-case
65
+ const chargeIds = useCase.fixtures.charges.map(c => c.id);
66
+ // Fetch all ledger records for these charges
67
+ const ledgerRecords = await client.query(`SELECT * FROM accounter_schema.ledger_records WHERE charge_id = ANY($1) ORDER BY created_at`, [chargeIds]);
68
+ if (ledgerRecords.rows.length === 0) {
69
+ // Ledger generation is separate from seeding - this is expected
70
+ console.log(` ⚠ ${useCase.id}: no ledger records (ledger generation not run)`);
71
+ continue;
72
+ }
73
+ ledgerRecordsExist = true;
74
+ // Create validation context
75
+ const context = {
76
+ useCaseId: useCase.id,
77
+ defaultCurrency: DEFAULT_CURRENCY,
78
+ tolerance: BALANCE_TOLERANCE,
79
+ };
80
+ // Run comprehensive validation
81
+ const validationErrors = validateLedgerRecords(ledgerRecords.rows, useCase.expectations.ledgerRecordCount, context);
82
+ errors.push(...validationErrors);
83
+ // Log progress
84
+ if (validationErrors.length === 0) {
85
+ console.log(` ✓ ${useCase.id} (${ledgerRecords.rows.length} records)`);
86
+ }
87
+ else {
88
+ console.log(` ✗ ${useCase.id} (${validationErrors.length} error(s))`);
89
+ }
90
+ }
91
+ if (!ledgerRecordsExist) {
92
+ console.log(' ℹ️ Ledger records not found. Run ledger generation to create and validate ledger entries.');
93
+ }
94
+ // 4. VAT row present (percentage stored as decimal 0.17 for 17%)
95
+ const vatCheck = await client.query(`SELECT 1 FROM accounter_schema.vat_value WHERE percentage = 0.17`);
96
+ if (vatCheck.rows.length === 0) {
97
+ errors.push('VAT default (17%) missing');
98
+ }
99
+ // Report errors or success
100
+ if (errors.length > 0) {
101
+ console.error('❌ Validation failed:');
102
+ for (const err of errors)
103
+ console.error(` - ${err}`);
104
+ process.exit(1);
105
+ }
106
+ console.log('✅ Demo data validation passed');
107
+ }
108
+ catch (error) {
109
+ console.error('❌ Validation error:', error);
110
+ process.exit(1);
111
+ }
112
+ finally {
113
+ await client.end();
114
+ }
115
+ }
116
+ validateDemoData();
117
+ //# sourceMappingURL=validate-demo-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-demo-data.js","sourceRoot":"","sources":["../../../../src/demo-fixtures/validate-demo-data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,349 @@
1
+ import type { LedgerRecord, ValidationContext } from './types.js';
2
+ /**
3
+ * Parse a numeric amount from a string, handling null/undefined values
4
+ *
5
+ * PostgreSQL returns numeric values as strings when queried via pg driver.
6
+ * This utility safely converts them to numbers for arithmetic operations.
7
+ *
8
+ * @param value - The string value to parse (may be null or undefined)
9
+ * @returns The parsed number, or 0 if value is null/undefined/invalid
10
+ *
11
+ * @example
12
+ * parseAmount('100.50') // 100.50
13
+ * parseAmount(null) // 0
14
+ * parseAmount('invalid') // 0
15
+ */
16
+ export declare function parseAmount(value: string | null | undefined): number;
17
+ /**
18
+ * Check if two numeric values are balanced within a tolerance
19
+ *
20
+ * Uses tolerance-based comparison to handle floating-point arithmetic
21
+ * imprecision. Two values are considered balanced if their absolute
22
+ * difference is less than or equal to the tolerance.
23
+ *
24
+ * @param a - First value to compare
25
+ * @param b - Second value to compare
26
+ * @param tolerance - Maximum acceptable difference (default: 0.005)
27
+ * @returns True if values are balanced within tolerance, false otherwise
28
+ *
29
+ * @example
30
+ * isBalanced(100, 100) // true
31
+ * isBalanced(100, 100.004, 0.005) // true
32
+ * isBalanced(100, 102, 0.005) // false
33
+ */
34
+ export declare function isBalanced(a: number, b: number, tolerance?: number): boolean;
35
+ /**
36
+ * Validate per-record internal balance (FR1)
37
+ *
38
+ * Ensures each ledger record is internally balanced according to double-entry
39
+ * bookkeeping principles: total debits must equal total credits within tolerance.
40
+ * Also detects empty records where all amounts are zero.
41
+ *
42
+ * This is a fundamental validation that must pass for every record individually
43
+ * before aggregate-level validations can be meaningful.
44
+ *
45
+ * @param records - Array of ledger records to validate
46
+ * @param context - Validation context containing use-case ID and tolerance
47
+ * @returns Array of error messages (empty if all records are valid)
48
+ *
49
+ * Functional Requirement: FR1 - Per-Record Internal Balance
50
+ * - Rule: (debit_local_amount1 + debit_local_amount2) == (credit_local_amount1 + credit_local_amount2)
51
+ * - Tolerance: Specified in context (typically ±0.005 for accounting rounding)
52
+ * - Also implements FR10: Empty Ledger Detection
53
+ *
54
+ * @example
55
+ * const errors = validateRecordInternalBalance(records, {
56
+ * useCaseId: 'monthly-expense',
57
+ * defaultCurrency: 'ILS',
58
+ * tolerance: 0.005
59
+ * });
60
+ * // Returns: [] if valid, or error messages like:
61
+ * // ["monthly-expense - Record 0 (uuid-123): internal imbalance (debit=100.00, credit=99.98)"]
62
+ */
63
+ export declare function validateRecordInternalBalance(records: LedgerRecord[], context: ValidationContext): string[];
64
+ /**
65
+ * Validate aggregate balance across all records (FR2)
66
+ *
67
+ * Validates that the sum of all debits equals the sum of all credits across
68
+ * all ledger records for a use-case. This is the second level of validation
69
+ * (after per-record balance) and ensures the entire ledger set balances.
70
+ *
71
+ * This refactors and enhances the existing aggregate balance validation logic
72
+ * that was previously only applied to a single use-case. The new implementation
73
+ * applies to all use-cases with expectations.
74
+ *
75
+ * @param records - Array of ledger records to validate
76
+ * @param context - Validation context containing use-case ID and tolerance
77
+ * @returns Array of error messages (empty if aggregate is balanced)
78
+ *
79
+ * Functional Requirement: FR2 - Aggregate Balance Validation
80
+ * - Rule: Σ(all debits) == Σ(all credits)
81
+ * - Tolerance: Specified in context (typically ±0.005)
82
+ * - Enhancement: Now applies to ALL use-cases, not just first one
83
+ *
84
+ * @example
85
+ * const errors = validateAggregateBalance(records, {
86
+ * useCaseId: 'monthly-expense',
87
+ * defaultCurrency: 'ILS',
88
+ * tolerance: 0.005
89
+ * });
90
+ * // Returns: [] if valid, or error like:
91
+ * // ["monthly-expense: aggregate ledger not balanced (debit 1000.00, credit 999.50)"]
92
+ */
93
+ export declare function validateAggregateBalance(records: LedgerRecord[], context: ValidationContext): string[];
94
+ /**
95
+ * Validate entity-level balance (FR3)
96
+ *
97
+ * Validates that each financial entity's net position across all ledger records
98
+ * balances to zero (or within tolerance). This is the third level of validation
99
+ * in the hierarchy:
100
+ * 1. Per-record balance (FR1) - each record internally balanced
101
+ * 2. Aggregate balance (FR2) - all records collectively balanced
102
+ * 3. Entity balance (FR3) - each entity's position balanced across records
103
+ *
104
+ * In double-entry bookkeeping, every entity that appears in the ledger should
105
+ * have a net zero position when considering all transactions. If an entity has
106
+ * debits totaling $500 across various records, it should also have credits
107
+ * totaling $500 across those same or other records.
108
+ *
109
+ * @param records - Array of ledger records to validate
110
+ * @param context - Validation context containing use-case ID and tolerance
111
+ * @returns Array of error messages (empty if all entities are balanced)
112
+ *
113
+ * Functional Requirement: FR3 - Entity-Level Balance Validation
114
+ * - Rule: For each entity, Σ(debits) - Σ(credits) ≈ 0
115
+ * - Tolerance: Specified in context (typically ±0.005)
116
+ * - Tracks: debit/credit amounts across all 4 entity fields per record
117
+ *
118
+ * Implementation:
119
+ * - Accumulates debits and credits per entity across all records
120
+ * - Calculates net balance (totalDebit - totalCredit) for each entity
121
+ * - Validates net balance is within tolerance of zero
122
+ *
123
+ * @example
124
+ * const errors = validateEntityBalance(records, {
125
+ * useCaseId: 'monthly-expense',
126
+ * defaultCurrency: 'ILS',
127
+ * tolerance: 0.005
128
+ * });
129
+ * // Returns: [] if valid, or errors like:
130
+ * // ["monthly-expense: Entity entity-123 unbalanced (net=50.00, debit=150.00, credit=100.00, records=3)"]
131
+ */
132
+ export declare function validateEntityBalance(records: LedgerRecord[], context: ValidationContext): string[];
133
+ /**
134
+ * Validate ledger record count (FR8)
135
+ *
136
+ * Validates that the actual number of ledger records matches the expected count
137
+ * specified in the use-case expectations. This ensures data completeness and
138
+ * detects cases where records may be missing or extra records were created.
139
+ *
140
+ * This enhances the existing record count validation by applying it to all
141
+ * use-cases systematically rather than ad-hoc checks.
142
+ *
143
+ * @param records - Array of ledger records to validate
144
+ * @param expectedCount - Expected number of ledger records for this use-case
145
+ * @param context - Validation context containing use-case ID
146
+ * @returns Array of error messages (empty if count matches)
147
+ *
148
+ * Functional Requirement: FR8 - Record Count Validation
149
+ * - Rule: Actual record count must match expected count exactly
150
+ * - Enhancement: Future support for minimum count validation for cases
151
+ * where ledger generation may create additional balancing entries
152
+ *
153
+ * @example
154
+ * const errors = validateRecordCount(records, 24, {
155
+ * useCaseId: 'monthly-expense',
156
+ * defaultCurrency: 'ILS',
157
+ * tolerance: 0.005
158
+ * });
159
+ * // Returns: [] if count matches, or error like:
160
+ * // ["monthly-expense: ledger record count mismatch (expected 24, got 23)"]
161
+ */
162
+ export declare function validateRecordCount(records: LedgerRecord[], expectedCount: number, context: ValidationContext): string[];
163
+ /**
164
+ * Validate all amounts are positive (FR5)
165
+ *
166
+ * Ensures data integrity by validating that all amount fields contain
167
+ * non-negative values. Negative amounts are not allowed in the ledger
168
+ * system as they violate accounting principles where debits and credits
169
+ * must always be positive or zero.
170
+ *
171
+ * This validation checks all 8 amount fields per record:
172
+ * - Local amounts: debit_local_amount1/2, credit_local_amount1/2
173
+ * - Foreign amounts: debit_foreign_amount1/2, credit_foreign_amount1/2
174
+ *
175
+ * @param records - Array of ledger records to validate
176
+ * @param context - Validation context containing use-case ID
177
+ * @returns Array of error messages (empty if all amounts are non-negative)
178
+ *
179
+ * Functional Requirement: FR5 - Positive Amount Validation
180
+ * - Checks all amount fields for negative values
181
+ * - Reports specific field and value for any negative amounts found
182
+ *
183
+ * @example
184
+ * const errors = validatePositiveAmounts(records, {
185
+ * useCaseId: 'monthly-expense',
186
+ * defaultCurrency: 'ILS',
187
+ * tolerance: 0.005
188
+ * });
189
+ * // Returns: [] if valid, or errors like:
190
+ * // ["monthly-expense - Record 0 (uuid-123): negative amount in debit_local_amount1 (-100.00)"]
191
+ */
192
+ export declare function validatePositiveAmounts(records: LedgerRecord[], context: ValidationContext): string[];
193
+ /**
194
+ * Validate dates (FR7)
195
+ *
196
+ * Ensures all ledger records have valid invoice_date and value_date fields
197
+ * within acceptable ranges. Proper date validation is critical for:
198
+ * - Financial reporting accuracy
199
+ * - Tax compliance and audit trails
200
+ * - Chronological transaction ordering
201
+ * - Preventing data entry errors
202
+ *
203
+ * Validation rules:
204
+ * - Both invoice_date and value_date must be present (not null)
205
+ * - Both dates must be valid Date objects (not NaN)
206
+ * - Both dates must fall within the range 2020-01-01 to 2030-12-31
207
+ *
208
+ * @param records - Array of ledger records to validate
209
+ * @param context - Validation context containing use-case ID
210
+ * @returns Array of error messages (empty if all dates are valid)
211
+ *
212
+ * Functional Requirement: FR7 - Date Validation
213
+ * - Checks for missing dates (null values)
214
+ * - Checks for invalid dates (parse errors)
215
+ * - Checks for dates outside reasonable business range
216
+ *
217
+ * @example
218
+ * const errors = validateDates(records, {
219
+ * useCaseId: 'monthly-expense',
220
+ * defaultCurrency: 'ILS',
221
+ * tolerance: 0.005
222
+ * });
223
+ * // Returns: [] if valid, or errors like:
224
+ * // ["monthly-expense - Record 0 (uuid-123): missing invoice_date"]
225
+ * // ["monthly-expense - Record 1 (uuid-456): invoice_date out of range (1999-01-01T00:00:00.000Z)"]
226
+ */
227
+ export declare function validateDates(records: LedgerRecord[], context: ValidationContext): string[];
228
+ /**
229
+ * Validate foreign currency handling (FR6)
230
+ *
231
+ * Ensures proper handling of foreign currency transactions by validating:
232
+ * 1. Currency field consistency with foreign amount fields
233
+ * 2. Presence of foreign amounts when currency is not the default (ILS)
234
+ * 3. Absence of foreign amounts when currency is the default (ILS)
235
+ * 4. Reasonableness of implied exchange rates between local and foreign amounts
236
+ *
237
+ * Foreign currency validation is critical for:
238
+ * - Accurate financial reporting in multi-currency environments
239
+ * - Compliance with international accounting standards
240
+ * - Detection of data entry errors in currency conversion
241
+ * - Prevention of fraudulent or suspicious exchange rate manipulation
242
+ *
243
+ * Exchange rate validation:
244
+ * - Implied rate = local_amount / foreign_amount
245
+ * - Rate must be between 0.1 and 10.0 to be considered reasonable
246
+ * - Rates outside this range likely indicate data entry errors
247
+ *
248
+ * @param records - Array of ledger records to validate
249
+ * @param context - Validation context containing use-case ID and default currency
250
+ * @returns Array of error messages (empty if all currency handling is valid)
251
+ *
252
+ * Functional Requirement: FR6 - Foreign Currency Validation
253
+ * - Validates currency field matches foreign amount presence/absence
254
+ * - Checks exchange rate consistency within reasonable bounds
255
+ * - Applies to all 4 amount pairs (debit1, debit2, credit1, credit2)
256
+ *
257
+ * @example
258
+ * const errors = validateForeignCurrency(records, {
259
+ * useCaseId: 'monthly-expense',
260
+ * defaultCurrency: 'ILS',
261
+ * tolerance: 0.005
262
+ * });
263
+ * // Returns: [] if valid, or errors like:
264
+ * // ["monthly-expense - Record 0 (uuid-123): foreign currency (USD) but no foreign amounts"]
265
+ * // ["monthly-expense - Record 1 (uuid-456): local currency (ILS) but has foreign amounts"]
266
+ * // ["monthly-expense - Record 2 (uuid-789): suspicious exchange rate in debit1 (rate=15.2000)"]
267
+ */
268
+ export declare function validateForeignCurrency(records: LedgerRecord[], context: ValidationContext): string[];
269
+ /**
270
+ * Validate no orphaned amounts (FR4)
271
+ *
272
+ * Ensures data integrity by validating that every non-zero amount field has
273
+ * a corresponding entity reference. This prevents "orphaned" amounts that
274
+ * cannot be attributed to any financial entity.
275
+ *
276
+ * An "orphaned amount" is a ledger entry where an amount value exists but
277
+ * its corresponding entity field is null. This violates double-entry
278
+ * bookkeeping principles where every amount must be associated with an entity.
279
+ *
280
+ * Rules enforced:
281
+ * - Primary fields (entity1): If amount > 0, entity must be present
282
+ * - Secondary fields (entity2): If entity is null, amount must also be null
283
+ *
284
+ * @param records - Array of ledger records to validate
285
+ * @param context - Validation context containing use-case ID
286
+ * @returns Array of error messages (empty if no orphaned amounts found)
287
+ *
288
+ * Functional Requirement: FR4 - Orphaned Amount Detection
289
+ * - Checks all 4 amount/entity pairs per record
290
+ * - Detects amounts without entities
291
+ * - Detects secondary fields that should be null
292
+ *
293
+ * @example
294
+ * const errors = validateNoOrphanedAmounts(records, {
295
+ * useCaseId: 'monthly-expense',
296
+ * defaultCurrency: 'ILS',
297
+ * tolerance: 0.005
298
+ * });
299
+ * // Returns: [] if valid, or errors like:
300
+ * // ["monthly-expense - Record 0 (uuid-123): orphaned amount in debit_local_amount1/debit_entity1 (100.00 without entity)"]
301
+ */
302
+ export declare function validateNoOrphanedAmounts(records: LedgerRecord[], context: ValidationContext): string[];
303
+ /**
304
+ * Master validation function - runs all validators (FR1-FR10)
305
+ *
306
+ * Orchestrates comprehensive ledger validation by executing all individual
307
+ * validation functions in a logical sequence. This is the main entry point
308
+ * for validating a complete set of ledger records for a use-case.
309
+ *
310
+ * Validation hierarchy:
311
+ * 1. Per-record validation (FR1, FR10) - Each record is internally balanced
312
+ * 2. Aggregate validation (FR2) - Total debits equal total credits
313
+ * 3. Entity validation (FR3) - Each entity's position balances
314
+ * 4. Data integrity (FR4, FR5) - No orphaned amounts, all amounts positive
315
+ * 5. Business rules (FR6, FR7) - Foreign currency and date validation
316
+ * 6. Structural validation (FR8) - Record count matches expectations
317
+ *
318
+ * This function implements NFR2 (Error Reporting): Collects ALL errors before
319
+ * failing (no fail-fast), allowing comprehensive error discovery in a single run.
320
+ *
321
+ * @param records - Array of ledger records to validate
322
+ * @param expectedRecordCount - Expected number of ledger records for this use-case
323
+ * @param context - Validation context containing use-case ID, currency, and tolerance
324
+ * @returns Array of all error messages from all validators (empty if fully valid)
325
+ *
326
+ * Functional Requirements Implemented:
327
+ * - FR1: Per-Record Internal Balance
328
+ * - FR2: Aggregate Balance Validation
329
+ * - FR3: Entity-Level Balance Validation
330
+ * - FR4: Orphaned Amount Detection
331
+ * - FR5: Positive Amount Validation
332
+ * - FR6: Foreign Currency Validation
333
+ * - FR7: Date Validation
334
+ * - FR8: Record Count Validation
335
+ * - FR10: Empty Ledger Detection (within FR1)
336
+ *
337
+ * @example
338
+ * const errors = validateLedgerRecords(records, 24, {
339
+ * useCaseId: 'monthly-expense',
340
+ * defaultCurrency: 'ILS',
341
+ * tolerance: 0.005
342
+ * });
343
+ * if (errors.length > 0) {
344
+ * console.error('Validation failed:', errors);
345
+ * } else {
346
+ * console.log('All validations passed');
347
+ * }
348
+ */
349
+ export declare function validateLedgerRecords(records: LedgerRecord[], expectedRecordCount: number, context: ValidationContext): string[];