@accounter/server 0.0.8-alpha-20251102200443-d7162b8ce1dfc629b8b454df17dcec9ed005a052 → 0.0.8-alpha-20251103003648-f6467c8cb9c739ec4439c260bccc7325f6a761ae

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 (185) hide show
  1. package/CHANGELOG.md +47 -7
  2. package/dist/green-invoice-graphql/src/mesh-artifacts/index.d.ts +7 -7
  3. package/dist/server/src/__generated__/types.d.ts +77 -0
  4. package/dist/server/src/__generated__/types.js.map +1 -1
  5. package/dist/server/src/modules/charges-matcher/__generated__/types.d.ts +68 -0
  6. package/dist/server/src/modules/charges-matcher/__generated__/types.js +7 -0
  7. package/dist/server/src/modules/charges-matcher/__generated__/types.js.map +1 -0
  8. package/dist/server/src/modules/charges-matcher/__tests__/amount-confidence.test.d.ts +1 -0
  9. package/dist/server/src/modules/charges-matcher/__tests__/amount-confidence.test.js +218 -0
  10. package/dist/server/src/modules/charges-matcher/__tests__/amount-confidence.test.js.map +1 -0
  11. package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.d.ts +1 -0
  12. package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js +645 -0
  13. package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js.map +1 -0
  14. package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.d.ts +1 -0
  15. package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.js +530 -0
  16. package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.js.map +1 -0
  17. package/dist/server/src/modules/charges-matcher/__tests__/business-confidence.test.d.ts +1 -0
  18. package/dist/server/src/modules/charges-matcher/__tests__/business-confidence.test.js +143 -0
  19. package/dist/server/src/modules/charges-matcher/__tests__/business-confidence.test.js.map +1 -0
  20. package/dist/server/src/modules/charges-matcher/__tests__/candidate-filter.test.d.ts +1 -0
  21. package/dist/server/src/modules/charges-matcher/__tests__/candidate-filter.test.js +186 -0
  22. package/dist/server/src/modules/charges-matcher/__tests__/candidate-filter.test.js.map +1 -0
  23. package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.d.ts +1 -0
  24. package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.js +301 -0
  25. package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.js.map +1 -0
  26. package/dist/server/src/modules/charges-matcher/__tests__/currency-confidence.test.d.ts +1 -0
  27. package/dist/server/src/modules/charges-matcher/__tests__/currency-confidence.test.js +127 -0
  28. package/dist/server/src/modules/charges-matcher/__tests__/currency-confidence.test.js.map +1 -0
  29. package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.d.ts +1 -0
  30. package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.js +246 -0
  31. package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.js.map +1 -0
  32. package/dist/server/src/modules/charges-matcher/__tests__/document-aggregator.test.d.ts +1 -0
  33. package/dist/server/src/modules/charges-matcher/__tests__/document-aggregator.test.js +475 -0
  34. package/dist/server/src/modules/charges-matcher/__tests__/document-aggregator.test.js.map +1 -0
  35. package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.d.ts +1 -0
  36. package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.js +287 -0
  37. package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.js.map +1 -0
  38. package/dist/server/src/modules/charges-matcher/__tests__/document-business.test.d.ts +1 -0
  39. package/dist/server/src/modules/charges-matcher/__tests__/document-business.test.js +151 -0
  40. package/dist/server/src/modules/charges-matcher/__tests__/document-business.test.js.map +1 -0
  41. package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.d.ts +1 -0
  42. package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.js +550 -0
  43. package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.js.map +1 -0
  44. package/dist/server/src/modules/charges-matcher/__tests__/overall-confidence.test.d.ts +1 -0
  45. package/dist/server/src/modules/charges-matcher/__tests__/overall-confidence.test.js +410 -0
  46. package/dist/server/src/modules/charges-matcher/__tests__/overall-confidence.test.js.map +1 -0
  47. package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.d.ts +1 -0
  48. package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js +504 -0
  49. package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js.map +1 -0
  50. package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.d.ts +1 -0
  51. package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.js +483 -0
  52. package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.js.map +1 -0
  53. package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.d.ts +46 -0
  54. package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.js +143 -0
  55. package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.js.map +1 -0
  56. package/dist/server/src/modules/charges-matcher/__tests__/test-infrastructure.spec.d.ts +1 -0
  57. package/dist/server/src/modules/charges-matcher/__tests__/test-infrastructure.spec.js +137 -0
  58. package/dist/server/src/modules/charges-matcher/__tests__/test-infrastructure.spec.js.map +1 -0
  59. package/dist/server/src/modules/charges-matcher/__tests__/transaction-aggregator.test.d.ts +1 -0
  60. package/dist/server/src/modules/charges-matcher/__tests__/transaction-aggregator.test.js +415 -0
  61. package/dist/server/src/modules/charges-matcher/__tests__/transaction-aggregator.test.js.map +1 -0
  62. package/dist/server/src/modules/charges-matcher/helpers/amount-confidence.helper.d.ts +7 -0
  63. package/dist/server/src/modules/charges-matcher/helpers/amount-confidence.helper.js +70 -0
  64. package/dist/server/src/modules/charges-matcher/helpers/amount-confidence.helper.js.map +1 -0
  65. package/dist/server/src/modules/charges-matcher/helpers/business-confidence.helper.d.ts +7 -0
  66. package/dist/server/src/modules/charges-matcher/helpers/business-confidence.helper.js +19 -0
  67. package/dist/server/src/modules/charges-matcher/helpers/business-confidence.helper.js.map +1 -0
  68. package/dist/server/src/modules/charges-matcher/helpers/candidate-filter.helper.d.ts +24 -0
  69. package/dist/server/src/modules/charges-matcher/helpers/candidate-filter.helper.js +45 -0
  70. package/dist/server/src/modules/charges-matcher/helpers/candidate-filter.helper.js.map +1 -0
  71. package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.d.ts +33 -0
  72. package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.js +65 -0
  73. package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.js.map +1 -0
  74. package/dist/server/src/modules/charges-matcher/helpers/currency-confidence.helper.d.ts +7 -0
  75. package/dist/server/src/modules/charges-matcher/helpers/currency-confidence.helper.js +18 -0
  76. package/dist/server/src/modules/charges-matcher/helpers/currency-confidence.helper.js.map +1 -0
  77. package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.d.ts +7 -0
  78. package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.js +35 -0
  79. package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.js.map +1 -0
  80. package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.d.ts +49 -0
  81. package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.js +58 -0
  82. package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.js.map +1 -0
  83. package/dist/server/src/modules/charges-matcher/helpers/document-business.helper.d.ts +13 -0
  84. package/dist/server/src/modules/charges-matcher/helpers/document-business.helper.js +37 -0
  85. package/dist/server/src/modules/charges-matcher/helpers/document-business.helper.js.map +1 -0
  86. package/dist/server/src/modules/charges-matcher/helpers/overall-confidence.helper.d.ts +42 -0
  87. package/dist/server/src/modules/charges-matcher/helpers/overall-confidence.helper.js +77 -0
  88. package/dist/server/src/modules/charges-matcher/helpers/overall-confidence.helper.js.map +1 -0
  89. package/dist/server/src/modules/charges-matcher/index.d.ts +3 -0
  90. package/dist/server/src/modules/charges-matcher/index.js +15 -0
  91. package/dist/server/src/modules/charges-matcher/index.js.map +1 -0
  92. package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.d.ts +48 -0
  93. package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js +133 -0
  94. package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js.map +1 -0
  95. package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.d.ts +38 -0
  96. package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js +248 -0
  97. package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js.map +1 -0
  98. package/dist/server/src/modules/charges-matcher/providers/document-aggregator.d.ts +61 -0
  99. package/dist/server/src/modules/charges-matcher/providers/document-aggregator.js +153 -0
  100. package/dist/server/src/modules/charges-matcher/providers/document-aggregator.js.map +1 -0
  101. package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.d.ts +25 -0
  102. package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.js +114 -0
  103. package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.js.map +1 -0
  104. package/dist/server/src/modules/charges-matcher/providers/single-match.provider.d.ts +39 -0
  105. package/dist/server/src/modules/charges-matcher/providers/single-match.provider.js +189 -0
  106. package/dist/server/src/modules/charges-matcher/providers/single-match.provider.js.map +1 -0
  107. package/dist/server/src/modules/charges-matcher/providers/transaction-aggregator.d.ts +54 -0
  108. package/dist/server/src/modules/charges-matcher/providers/transaction-aggregator.js +93 -0
  109. package/dist/server/src/modules/charges-matcher/providers/transaction-aggregator.js.map +1 -0
  110. package/dist/server/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.d.ts +2 -0
  111. package/dist/server/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.js +22 -0
  112. package/dist/server/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.js.map +1 -0
  113. package/dist/server/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.d.ts +2 -0
  114. package/dist/server/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.js +24 -0
  115. package/dist/server/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.js.map +1 -0
  116. package/dist/server/src/modules/charges-matcher/resolvers/index.d.ts +2 -0
  117. package/dist/server/src/modules/charges-matcher/resolvers/index.js +11 -0
  118. package/dist/server/src/modules/charges-matcher/resolvers/index.js.map +1 -0
  119. package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.d.ts +2 -0
  120. package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.js +47 -0
  121. package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.js.map +1 -0
  122. package/dist/server/src/modules/charges-matcher/types.d.ts +179 -0
  123. package/dist/server/src/modules/charges-matcher/types.js +14 -0
  124. package/dist/server/src/modules/charges-matcher/types.js.map +1 -0
  125. package/dist/server/src/modules/documents/resolvers/document-suggestions.resolver.js +2 -2
  126. package/dist/server/src/modules/documents/resolvers/document-suggestions.resolver.js.map +1 -1
  127. package/dist/server/src/modules/green-invoice/helpers/contract-to-draft.helper.js +1 -1
  128. package/dist/server/src/modules/green-invoice/helpers/contract-to-draft.helper.js.map +1 -1
  129. package/dist/server/src/modules/green-invoice/helpers/green-invoice.helper.d.ts +1 -1
  130. package/dist/server/src/modules/green-invoice/helpers/green-invoice.helper.js +1 -1
  131. package/dist/server/src/modules/green-invoice/helpers/green-invoice.helper.js.map +1 -1
  132. package/dist/server/src/modules-app.js +2 -0
  133. package/dist/server/src/modules-app.js.map +1 -1
  134. package/dist/server/src/shared/types/index.d.ts +1 -1
  135. package/package.json +4 -4
  136. package/src/__generated__/types.ts +87 -0
  137. package/src/modules/charges-matcher/README.md +279 -0
  138. package/src/modules/charges-matcher/__generated__/types.ts +71 -0
  139. package/src/modules/charges-matcher/__tests__/amount-confidence.test.ts +260 -0
  140. package/src/modules/charges-matcher/__tests__/auto-match-integration.test.ts +714 -0
  141. package/src/modules/charges-matcher/__tests__/auto-match.test.ts +621 -0
  142. package/src/modules/charges-matcher/__tests__/business-confidence.test.ts +177 -0
  143. package/src/modules/charges-matcher/__tests__/candidate-filter.test.ts +238 -0
  144. package/src/modules/charges-matcher/__tests__/charge-validator.test.ts +374 -0
  145. package/src/modules/charges-matcher/__tests__/currency-confidence.test.ts +164 -0
  146. package/src/modules/charges-matcher/__tests__/date-confidence.test.ts +291 -0
  147. package/src/modules/charges-matcher/__tests__/document-aggregator.test.ts +614 -0
  148. package/src/modules/charges-matcher/__tests__/document-amount.test.ts +352 -0
  149. package/src/modules/charges-matcher/__tests__/document-business.test.ts +192 -0
  150. package/src/modules/charges-matcher/__tests__/match-scorer.test.ts +659 -0
  151. package/src/modules/charges-matcher/__tests__/overall-confidence.test.ts +502 -0
  152. package/src/modules/charges-matcher/__tests__/single-match-integration.test.ts +556 -0
  153. package/src/modules/charges-matcher/__tests__/single-match.test.ts +608 -0
  154. package/src/modules/charges-matcher/__tests__/test-helpers.ts +174 -0
  155. package/src/modules/charges-matcher/__tests__/test-infrastructure.spec.ts +177 -0
  156. package/src/modules/charges-matcher/__tests__/transaction-aggregator.test.ts +547 -0
  157. package/src/modules/charges-matcher/documentation/README.md +331 -0
  158. package/src/modules/charges-matcher/documentation/SPEC.md +1503 -0
  159. package/src/modules/charges-matcher/documentation/TODO.md +799 -0
  160. package/src/modules/charges-matcher/helpers/amount-confidence.helper.ts +88 -0
  161. package/src/modules/charges-matcher/helpers/business-confidence.helper.ts +23 -0
  162. package/src/modules/charges-matcher/helpers/candidate-filter.helper.ts +56 -0
  163. package/src/modules/charges-matcher/helpers/charge-validator.helper.ts +100 -0
  164. package/src/modules/charges-matcher/helpers/currency-confidence.helper.ts +22 -0
  165. package/src/modules/charges-matcher/helpers/date-confidence.helper.ts +41 -0
  166. package/src/modules/charges-matcher/helpers/document-amount.helper.ts +77 -0
  167. package/src/modules/charges-matcher/helpers/document-business.helper.ts +54 -0
  168. package/src/modules/charges-matcher/helpers/overall-confidence.helper.ts +90 -0
  169. package/src/modules/charges-matcher/index.ts +17 -0
  170. package/src/modules/charges-matcher/providers/auto-match.provider.ts +176 -0
  171. package/src/modules/charges-matcher/providers/charges-matcher.provider.ts +322 -0
  172. package/src/modules/charges-matcher/providers/document-aggregator.ts +211 -0
  173. package/src/modules/charges-matcher/providers/match-scorer.provider.ts +154 -0
  174. package/src/modules/charges-matcher/providers/single-match.provider.ts +252 -0
  175. package/src/modules/charges-matcher/providers/transaction-aggregator.ts +131 -0
  176. package/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.ts +23 -0
  177. package/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.ts +25 -0
  178. package/src/modules/charges-matcher/resolvers/index.ts +12 -0
  179. package/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.ts +47 -0
  180. package/src/modules/charges-matcher/types.ts +200 -0
  181. package/src/modules/documents/resolvers/document-suggestions.resolver.ts +2 -2
  182. package/src/modules/green-invoice/helpers/contract-to-draft.helper.ts +1 -1
  183. package/src/modules/green-invoice/helpers/green-invoice.helper.ts +1 -1
  184. package/src/modules-app.ts +2 -0
  185. package/src/shared/types/index.ts +1 -1
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Auto-Match Provider
3
+ *
4
+ * Implements the core auto-match logic for processing all unmatched charges
5
+ * and automatically merging charges with high-confidence matches (≥0.95).
6
+ */
7
+ import { findMatches } from './single-match.provider.js';
8
+ /**
9
+ * Process a single unmatched charge and find best match
10
+ *
11
+ * This function uses findMatches() from single-match provider with NO date window
12
+ * restriction, then filters for high-confidence matches (≥0.95 threshold).
13
+ *
14
+ * @param sourceCharge - Unmatched charge to process (must have only transactions OR only documents)
15
+ * @param allCandidates - All candidate charges to search (complementary type to source)
16
+ * @param userId - Current user ID for business extraction
17
+ * @returns Processing result with match status
18
+ *
19
+ * @throws Error if sourceCharge is already matched (has both transactions and documents)
20
+ * @throws Error if sourceCharge has no transactions or documents
21
+ * @throws Error if any validation fails (propagated from findMatches)
22
+ */
23
+ export function processChargeForAutoMatch(sourceCharge, allCandidates, userId) {
24
+ const AUTO_MATCH_THRESHOLD = 0.95;
25
+ // Prepare source charge for findMatches
26
+ const hasTransactions = sourceCharge.transactions && sourceCharge.transactions.length > 0;
27
+ const hasDocuments = sourceCharge.documents && sourceCharge.documents.length > 0;
28
+ let sourceForMatching;
29
+ let candidatesForMatching;
30
+ if (hasTransactions && !hasDocuments) {
31
+ // Transaction charge - convert to TransactionCharge format
32
+ sourceForMatching = {
33
+ chargeId: sourceCharge.chargeId,
34
+ transactions: sourceCharge.transactions,
35
+ };
36
+ // Filter candidates to only document charges
37
+ candidatesForMatching = allCandidates
38
+ .filter(c => c.documents && c.documents.length > 0 && (!c.transactions || c.transactions.length === 0))
39
+ .map(c => ({
40
+ chargeId: c.chargeId,
41
+ documents: c.documents,
42
+ }));
43
+ }
44
+ else if (hasDocuments && !hasTransactions) {
45
+ // Document charge - convert to DocumentCharge format
46
+ sourceForMatching = {
47
+ chargeId: sourceCharge.chargeId,
48
+ documents: sourceCharge.documents,
49
+ };
50
+ // Filter candidates to only transaction charges
51
+ candidatesForMatching = allCandidates
52
+ .filter(c => c.transactions && c.transactions.length > 0 && (!c.documents || c.documents.length === 0))
53
+ .map(c => ({
54
+ chargeId: c.chargeId,
55
+ transactions: c.transactions,
56
+ }));
57
+ }
58
+ else {
59
+ // Invalid charge state
60
+ if (hasTransactions && hasDocuments) {
61
+ throw new Error(`Charge ${sourceCharge.chargeId} is already matched (has both transactions and documents)`);
62
+ }
63
+ throw new Error(`Charge ${sourceCharge.chargeId} has no transactions or documents`);
64
+ }
65
+ // Find all matches with no date window restriction
66
+ const allMatches = findMatches(sourceForMatching, candidatesForMatching, userId, {
67
+ dateWindowMonths: undefined, // No date restriction for auto-match
68
+ maxMatches: undefined, // Get all matches, we'll filter by threshold
69
+ });
70
+ // Filter for high-confidence matches (≥0.95)
71
+ const highConfidenceMatches = allMatches.filter(match => match.confidenceScore >= AUTO_MATCH_THRESHOLD);
72
+ // Determine result based on number of high-confidence matches
73
+ if (highConfidenceMatches.length === 0) {
74
+ return {
75
+ match: null,
76
+ status: 'no-match',
77
+ reason: allMatches.length > 0
78
+ ? `Best match has confidence ${allMatches[0].confidenceScore.toFixed(2)}, below threshold ${AUTO_MATCH_THRESHOLD}`
79
+ : 'No candidates found',
80
+ };
81
+ }
82
+ if (highConfidenceMatches.length === 1) {
83
+ return {
84
+ match: highConfidenceMatches[0],
85
+ status: 'matched',
86
+ reason: `Single high-confidence match found (${highConfidenceMatches[0].confidenceScore.toFixed(2)})`,
87
+ };
88
+ }
89
+ // Multiple high-confidence matches - ambiguous
90
+ return {
91
+ match: null,
92
+ status: 'skipped',
93
+ reason: `${highConfidenceMatches.length} high-confidence matches found (ambiguous)`,
94
+ };
95
+ }
96
+ /**
97
+ * Determine merge direction for two charges
98
+ *
99
+ * The merge direction follows these rules:
100
+ * 1. If one charge is matched (has both transactions and documents), keep the matched one
101
+ * 2. If both are unmatched, keep the one with transactions (transaction charge is the "anchor")
102
+ * 3. If neither has transactions, keep the first one (arbitrary but consistent)
103
+ *
104
+ * @param charge1 - First charge
105
+ * @param charge2 - Second charge
106
+ * @returns [source, target] tuple where source will be merged INTO target (source is deleted)
107
+ */
108
+ export function determineMergeDirection(charge1, charge2) {
109
+ const charge1HasTransactions = charge1.transactions && charge1.transactions.length > 0;
110
+ const charge1HasDocuments = charge1.documents && charge1.documents.length > 0;
111
+ const charge2HasTransactions = charge2.transactions && charge2.transactions.length > 0;
112
+ const charge2HasDocuments = charge2.documents && charge2.documents.length > 0;
113
+ const charge1IsMatched = charge1HasTransactions && charge1HasDocuments;
114
+ const charge2IsMatched = charge2HasTransactions && charge2HasDocuments;
115
+ // Rule 1: If one is matched, keep the matched one
116
+ if (charge1IsMatched && !charge2IsMatched) {
117
+ return [charge2, charge1]; // Merge charge2 INTO charge1 (keep charge1)
118
+ }
119
+ if (charge2IsMatched && !charge1IsMatched) {
120
+ return [charge1, charge2]; // Merge charge1 INTO charge2 (keep charge2)
121
+ }
122
+ // Rule 2: Both unmatched - keep the one with transactions
123
+ if (charge1HasTransactions && !charge2HasTransactions) {
124
+ return [charge2, charge1]; // Merge charge2 INTO charge1 (keep transaction charge)
125
+ }
126
+ if (charge2HasTransactions && !charge1HasTransactions) {
127
+ return [charge1, charge2]; // Merge charge1 INTO charge2 (keep transaction charge)
128
+ }
129
+ // Rule 3: Neither has transactions (both are document-only) or both have transactions
130
+ // Keep charge1 (arbitrary but consistent)
131
+ return [charge2, charge1]; // Merge charge2 INTO charge1 (keep charge1)
132
+ }
133
+ //# sourceMappingURL=auto-match.provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-match.provider.js","sourceRoot":"","sources":["../../../../../../src/modules/charges-matcher/providers/auto-match.provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Charges Matcher Provider
3
+ *
4
+ * Provides database-integrated charge matching functionality using the Injector pattern.
5
+ * Integrates with existing modules: charges, transactions, and documents.
6
+ */
7
+ import { type AutoMatchChargesResult, type ChargeMatchesResult } from '../types.js';
8
+ /**
9
+ * Charges Matcher Provider
10
+ *
11
+ * Provides high-level charge matching operations with database integration.
12
+ * Uses the Injector pattern to access existing providers from other modules.
13
+ */
14
+ export declare class ChargesMatcherProvider {
15
+ /**
16
+ * Find potential matches for an unmatched charge
17
+ *
18
+ * @param chargeId - ID of the unmatched charge to find matches for
19
+ * @param injector - GraphQL modules injector for provider access
20
+ * @returns Top 5 matches ordered by confidence score
21
+ * @throws Error if charge not found
22
+ * @throws Error if charge is already matched
23
+ * @throws Error if charge data is invalid
24
+ */
25
+ findMatchesForCharge(chargeId: string, context: GraphQLModules.AppContext): Promise<ChargeMatchesResult>;
26
+ /**
27
+ * Auto-match all unmatched charges
28
+ *
29
+ * Automatically merges charges that have a single high-confidence match (≥0.95).
30
+ * Skips charges with multiple high-confidence matches (ambiguous).
31
+ * Processes all unmatched charges and returns a summary of actions taken.
32
+ *
33
+ * @param injector - GraphQL modules injector for provider access
34
+ * @param context - GraphQL context with user information
35
+ * @returns Summary of matches made, skipped charges, and errors
36
+ */
37
+ autoMatchCharges(context: GraphQLModules.AppContext): Promise<AutoMatchChargesResult>;
38
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Charges Matcher Provider
3
+ *
4
+ * Provides database-integrated charge matching functionality using the Injector pattern.
5
+ * Integrates with existing modules: charges, transactions, and documents.
6
+ */
7
+ import { __decorate } from "tslib";
8
+ import { Injectable, Scope } from 'graphql-modules';
9
+ import { mergeChargesExecutor } from '../../charges/helpers/merge-charges.hepler.js';
10
+ import { ChargesProvider } from '../../charges/providers/charges.provider.js';
11
+ import { DocumentsProvider } from '../../documents/providers/documents.provider.js';
12
+ import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
13
+ import { dateToTimelessDateString } from '../../../shared/helpers/index.js';
14
+ import { validateChargeIsUnmatched } from '../helpers/charge-validator.helper.js';
15
+ import { ChargeType, } from '../types.js';
16
+ import { determineMergeDirection, processChargeForAutoMatch } from './auto-match.provider.js';
17
+ import { aggregateDocuments } from './document-aggregator.js';
18
+ import { findMatches } from './single-match.provider.js';
19
+ import { aggregateTransactions } from './transaction-aggregator.js';
20
+ /**
21
+ * Charges Matcher Provider
22
+ *
23
+ * Provides high-level charge matching operations with database integration.
24
+ * Uses the Injector pattern to access existing providers from other modules.
25
+ */
26
+ let ChargesMatcherProvider = class ChargesMatcherProvider {
27
+ /**
28
+ * Find potential matches for an unmatched charge
29
+ *
30
+ * @param chargeId - ID of the unmatched charge to find matches for
31
+ * @param injector - GraphQL modules injector for provider access
32
+ * @returns Top 5 matches ordered by confidence score
33
+ * @throws Error if charge not found
34
+ * @throws Error if charge is already matched
35
+ * @throws Error if charge data is invalid
36
+ */
37
+ async findMatchesForCharge(chargeId, context) {
38
+ const { adminContext: { defaultAdminBusinessId: adminBusinessId }, injector, } = context;
39
+ // Get current user ID from context
40
+ if (!adminBusinessId) {
41
+ throw new Error('Admin business not found in context');
42
+ }
43
+ // Get providers from injector
44
+ const chargesProvider = injector.get(ChargesProvider);
45
+ const transactionsProvider = injector.get(TransactionsProvider);
46
+ const documentsProvider = injector.get(DocumentsProvider);
47
+ // Step 1: Load source charge data
48
+ const sourceCharge = await chargesProvider.getChargeByIdLoader.load(chargeId);
49
+ if (sourceCharge instanceof Error) {
50
+ throw new Error(`Source charge not found: ${chargeId}`);
51
+ }
52
+ // Step 2: Load transactions and documents for source charge
53
+ const sourceTransactions = (await transactionsProvider.transactionsByChargeIDLoader.load(chargeId));
54
+ const sourceDocuments = (await documentsProvider.getDocumentsByChargeIdLoader.load(chargeId));
55
+ // Step 3: Validate source charge is unmatched
56
+ const sourceChargeWithData = {
57
+ ...sourceCharge,
58
+ transactions: sourceTransactions,
59
+ documents: sourceDocuments,
60
+ };
61
+ validateChargeIsUnmatched(sourceChargeWithData);
62
+ // Step 4: Determine reference date and date window from source charge
63
+ let referenceDate;
64
+ const hasTransactions = sourceTransactions && sourceTransactions.length > 0;
65
+ if (hasTransactions) {
66
+ // Use earliest transaction event_date
67
+ const aggregated = aggregateTransactions(sourceTransactions);
68
+ referenceDate = aggregated.date;
69
+ }
70
+ else {
71
+ // Use latest document date
72
+ const aggregated = aggregateDocuments(sourceDocuments, adminBusinessId);
73
+ referenceDate = aggregated.date;
74
+ }
75
+ // Step 5: Load candidate charges from database
76
+ // Use 12-month window centered on reference date
77
+ const windowStart = new Date(referenceDate);
78
+ windowStart.setMonth(windowStart.getMonth() - 12);
79
+ const windowEnd = new Date(referenceDate);
80
+ windowEnd.setMonth(windowEnd.getMonth() + 12);
81
+ const candidateCharges = await chargesProvider.getChargesByFilters({
82
+ ownerIds: [adminBusinessId],
83
+ fromAnyDate: dateToTimelessDateString(windowStart),
84
+ toAnyDate: dateToTimelessDateString(windowEnd),
85
+ });
86
+ // Step 6: Load transactions and documents for all candidate charges
87
+ const candidateChargesWithData = [];
88
+ for (const candidate of candidateCharges) {
89
+ // Skip the source charge itself
90
+ if (candidate.id === chargeId) {
91
+ continue;
92
+ }
93
+ const candidateTransactions = (await transactionsProvider.transactionsByChargeIDLoader.load(candidate.id));
94
+ const candidateDocuments = (await documentsProvider.getDocumentsByChargeIdLoader.load(candidate.id));
95
+ const hasTxs = candidateTransactions && candidateTransactions.length > 0;
96
+ const hasDocs = candidateDocuments && candidateDocuments.length > 0;
97
+ // Only include unmatched charges (not both types)
98
+ if (hasTxs && !hasDocs) {
99
+ candidateChargesWithData.push({
100
+ chargeId: candidate.id,
101
+ transactions: candidateTransactions,
102
+ });
103
+ }
104
+ else if (hasDocs && !hasTxs) {
105
+ candidateChargesWithData.push({
106
+ chargeId: candidate.id,
107
+ documents: candidateDocuments,
108
+ });
109
+ }
110
+ // Skip matched charges (have both) and empty charges (have neither)
111
+ }
112
+ // Step 7: Build source charge object for findMatches
113
+ let sourceChargeData;
114
+ if (hasTransactions) {
115
+ sourceChargeData = {
116
+ chargeId,
117
+ transactions: sourceTransactions,
118
+ };
119
+ }
120
+ else {
121
+ sourceChargeData = {
122
+ chargeId,
123
+ documents: sourceDocuments,
124
+ };
125
+ }
126
+ // Step 8: Call core findMatches function
127
+ const matches = findMatches(sourceChargeData, candidateChargesWithData, adminBusinessId, {
128
+ maxMatches: 5,
129
+ dateWindowMonths: 12,
130
+ });
131
+ // Step 9: Format and return result
132
+ return {
133
+ matches: matches.map(match => ({
134
+ chargeId: match.chargeId,
135
+ confidenceScore: match.confidenceScore,
136
+ })),
137
+ };
138
+ }
139
+ /**
140
+ * Auto-match all unmatched charges
141
+ *
142
+ * Automatically merges charges that have a single high-confidence match (≥0.95).
143
+ * Skips charges with multiple high-confidence matches (ambiguous).
144
+ * Processes all unmatched charges and returns a summary of actions taken.
145
+ *
146
+ * @param injector - GraphQL modules injector for provider access
147
+ * @param context - GraphQL context with user information
148
+ * @returns Summary of matches made, skipped charges, and errors
149
+ */
150
+ async autoMatchCharges(context) {
151
+ const { adminContext: { defaultAdminBusinessId: adminBusinessId }, injector, } = context;
152
+ // Get current user ID from context
153
+ if (!adminBusinessId) {
154
+ throw new Error('Admin business not found in context');
155
+ }
156
+ // Get providers from injector
157
+ const chargesProvider = injector.get(ChargesProvider);
158
+ const transactionsProvider = injector.get(TransactionsProvider);
159
+ const documentsProvider = injector.get(DocumentsProvider);
160
+ // Step 1: Load all charges for this user
161
+ const allCharges = await chargesProvider.getChargesByFilters({
162
+ ownerIds: [adminBusinessId],
163
+ });
164
+ // Step 2: Load transactions and documents for all charges
165
+ const chargesWithData = [];
166
+ const mergedChargeIds = new Set(); // Track merged charges to exclude from processing
167
+ for (const charge of allCharges) {
168
+ const transactions = (await transactionsProvider.transactionsByChargeIDLoader.load(charge.id));
169
+ const documents = (await documentsProvider.getDocumentsByChargeIdLoader.load(charge.id));
170
+ chargesWithData.push({
171
+ chargeId: charge.id,
172
+ ownerId: charge.owner_id ?? adminBusinessId,
173
+ type: ChargeType.TRANSACTION_ONLY, // Will be determined by processChargeForAutoMatch
174
+ transactions: transactions || [],
175
+ documents: documents || [],
176
+ });
177
+ }
178
+ // Step 3: Filter to get only unmatched charges
179
+ const unmatchedCharges = chargesWithData.filter(charge => {
180
+ const hasTx = charge.transactions && charge.transactions.length > 0;
181
+ const hasDocs = charge.documents && charge.documents.length > 0;
182
+ return (hasTx && !hasDocs) || (!hasTx && hasDocs);
183
+ });
184
+ // Step 4: Process each unmatched charge
185
+ const result = {
186
+ totalMatches: 0,
187
+ mergedCharges: [],
188
+ skippedCharges: [],
189
+ errors: [],
190
+ };
191
+ for (const sourceCharge of unmatchedCharges) {
192
+ // Skip if this charge was already merged in this run
193
+ if (mergedChargeIds.has(sourceCharge.chargeId)) {
194
+ continue;
195
+ }
196
+ try {
197
+ // Get candidates (exclude already merged charges)
198
+ const candidates = chargesWithData.filter(c => c.chargeId !== sourceCharge.chargeId && !mergedChargeIds.has(c.chargeId));
199
+ // Process this charge for auto-match
200
+ const processResult = processChargeForAutoMatch(sourceCharge, candidates, adminBusinessId);
201
+ if (processResult.status === 'matched' && processResult.match) {
202
+ // Found a single high-confidence match - execute merge
203
+ const matchedChargeId = processResult.match.chargeId;
204
+ const matchedCharge = chargesWithData.find(c => c.chargeId === matchedChargeId);
205
+ if (!matchedCharge) {
206
+ result.errors.push(`Matched charge ${matchedChargeId} not found in charge pool for ${sourceCharge.chargeId}`);
207
+ continue;
208
+ }
209
+ // Determine merge direction
210
+ const [sourceToMerge, targetToKeep] = determineMergeDirection(sourceCharge, matchedCharge);
211
+ try {
212
+ // Execute merge via existing merge functionality
213
+ await mergeChargesExecutor([sourceToMerge.chargeId], targetToKeep.chargeId, injector);
214
+ // Track successful merge
215
+ result.totalMatches++;
216
+ result.mergedCharges.push({
217
+ chargeId: sourceToMerge.chargeId,
218
+ confidenceScore: processResult.match.confidenceScore,
219
+ });
220
+ // Mark both charges as processed (merged away charge and kept charge)
221
+ mergedChargeIds.add(sourceToMerge.chargeId);
222
+ mergedChargeIds.add(targetToKeep.chargeId); // Don't process the kept charge again
223
+ }
224
+ catch (mergeError) {
225
+ result.errors.push(`Failed to merge ${sourceToMerge.chargeId} into ${targetToKeep.chargeId}: ${mergeError instanceof Error ? mergeError.message : String(mergeError)}`);
226
+ }
227
+ }
228
+ else if (processResult.status === 'skipped') {
229
+ // Multiple high-confidence matches - ambiguous
230
+ result.skippedCharges.push(sourceCharge.chargeId);
231
+ }
232
+ // status === 'no-match': do nothing, silently skip
233
+ }
234
+ catch (error) {
235
+ // Capture error but continue processing other charges
236
+ result.errors.push(`Error processing charge ${sourceCharge.chargeId}: ${error instanceof Error ? error.message : String(error)}`);
237
+ }
238
+ }
239
+ return result;
240
+ }
241
+ };
242
+ ChargesMatcherProvider = __decorate([
243
+ Injectable({
244
+ scope: Scope.Operation,
245
+ })
246
+ ], ChargesMatcherProvider);
247
+ export { ChargesMatcherProvider };
248
+ //# sourceMappingURL=charges-matcher.provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charges-matcher.provider.js","sourceRoot":"","sources":["../../../../../../src/modules/charges-matcher/providers/charges-matcher.provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Document Aggregator
3
+ *
4
+ * Aggregates multiple documents from a single charge into a unified representation
5
+ * for matching purposes. Handles type priority filtering, business extraction,
6
+ * amount normalization, currency validation, date selection, and description concatenation.
7
+ */
8
+ import { currency } from '../../documents/types.js';
9
+ import { type DocumentType } from '../helpers/document-amount.helper.js';
10
+ import { AggregatedDocument } from '../types.js';
11
+ /**
12
+ * Minimal document interface for aggregation
13
+ * Based on database schema from accounter_schema.documents
14
+ * Note: Documents use charge_id for the FK
15
+ */
16
+ export interface Document {
17
+ id: string;
18
+ charge_id: string | null;
19
+ creditor_id: string | null;
20
+ debtor_id: string | null;
21
+ currency_code: currency | null;
22
+ date: Date | null;
23
+ total_amount: number | null;
24
+ type: DocumentType;
25
+ serial_number: string | null;
26
+ image_url: string | null;
27
+ file_url: string | null;
28
+ }
29
+ /**
30
+ * Aggregate multiple documents into a single representation
31
+ *
32
+ * Per specification (section 4.2):
33
+ * 1. Filter by type priority: if both invoices AND receipts exist, use only invoices
34
+ * 2. Extract business ID from each document (creditor_id/debtor_id)
35
+ * 3. Normalize each amount based on business role and document type
36
+ * 4. Validate non-empty array
37
+ * 5. Check for mixed currencies → throw error
38
+ * 6. Check for multiple non-null business IDs → throw error
39
+ * 7. Sum all normalized amounts
40
+ * 8. Select latest date
41
+ * 9. Concatenate serial_number or file names with line breaks
42
+ * 10. Determine document type for result (use first after filtering)
43
+ *
44
+ * @param documents - Array of documents from a charge (use charge_id field for FK)
45
+ * @param adminBusinessId - Current admin business UUID for business extraction
46
+ * @returns Aggregated document data
47
+ *
48
+ * @throws {Error} If documents array is empty
49
+ * @throws {Error} If multiple different currencies exist
50
+ * @throws {Error} If multiple different non-null business IDs exist
51
+ * @throws {Error} If business extraction fails (propagates from extractDocumentBusiness)
52
+ * @throws {Error} If no valid date found in any document
53
+ *
54
+ * @example
55
+ * const aggregated = aggregateDocuments([
56
+ * { total_amount: 100, currency_code: 'USD', creditor_id: 'b1', debtor_id: 'u1', type: 'INVOICE', ... },
57
+ * { total_amount: 50, currency_code: 'USD', creditor_id: 'b1', debtor_id: 'u1', type: 'INVOICE', ... }
58
+ * ], 'u1');
59
+ * // Returns aggregated with normalized amounts summed
60
+ */
61
+ export declare function aggregateDocuments(documents: Document[], adminBusinessId: string): Omit<AggregatedDocument, 'businessIsCreditor'>;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Document Aggregator
3
+ *
4
+ * Aggregates multiple documents from a single charge into a unified representation
5
+ * for matching purposes. Handles type priority filtering, business extraction,
6
+ * amount normalization, currency validation, date selection, and description concatenation.
7
+ */
8
+ import { normalizeDocumentAmount } from '../helpers/document-amount.helper.js';
9
+ import { extractDocumentBusiness } from '../helpers/document-business.helper.js';
10
+ /**
11
+ * Accounting document types (used for type priority filtering)
12
+ */
13
+ const INVOICE_TYPES = ['INVOICE', 'CREDIT_INVOICE'];
14
+ const RECEIPT_TYPES = ['RECEIPT', 'INVOICE_RECEIPT'];
15
+ /**
16
+ * Check if document type is an invoice or credit invoice
17
+ */
18
+ function isInvoiceType(type) {
19
+ return INVOICE_TYPES.includes(type);
20
+ }
21
+ /**
22
+ * Check if document type is a receipt or invoice-receipt
23
+ */
24
+ function isReceiptType(type) {
25
+ return RECEIPT_TYPES.includes(type);
26
+ }
27
+ /**
28
+ * Aggregate multiple documents into a single representation
29
+ *
30
+ * Per specification (section 4.2):
31
+ * 1. Filter by type priority: if both invoices AND receipts exist, use only invoices
32
+ * 2. Extract business ID from each document (creditor_id/debtor_id)
33
+ * 3. Normalize each amount based on business role and document type
34
+ * 4. Validate non-empty array
35
+ * 5. Check for mixed currencies → throw error
36
+ * 6. Check for multiple non-null business IDs → throw error
37
+ * 7. Sum all normalized amounts
38
+ * 8. Select latest date
39
+ * 9. Concatenate serial_number or file names with line breaks
40
+ * 10. Determine document type for result (use first after filtering)
41
+ *
42
+ * @param documents - Array of documents from a charge (use charge_id field for FK)
43
+ * @param adminBusinessId - Current admin business UUID for business extraction
44
+ * @returns Aggregated document data
45
+ *
46
+ * @throws {Error} If documents array is empty
47
+ * @throws {Error} If multiple different currencies exist
48
+ * @throws {Error} If multiple different non-null business IDs exist
49
+ * @throws {Error} If business extraction fails (propagates from extractDocumentBusiness)
50
+ * @throws {Error} If no valid date found in any document
51
+ *
52
+ * @example
53
+ * const aggregated = aggregateDocuments([
54
+ * { total_amount: 100, currency_code: 'USD', creditor_id: 'b1', debtor_id: 'u1', type: 'INVOICE', ... },
55
+ * { total_amount: 50, currency_code: 'USD', creditor_id: 'b1', debtor_id: 'u1', type: 'INVOICE', ... }
56
+ * ], 'u1');
57
+ * // Returns aggregated with normalized amounts summed
58
+ */
59
+ export function aggregateDocuments(documents, adminBusinessId) {
60
+ // Validate non-empty input
61
+ if (!documents || documents.length === 0) {
62
+ throw new Error('Cannot aggregate documents: array is empty');
63
+ }
64
+ // Apply type priority filtering
65
+ const hasInvoices = documents.some(d => isInvoiceType(d.type));
66
+ const hasReceipts = documents.some(d => isReceiptType(d.type));
67
+ let filteredDocuments = documents;
68
+ if (hasInvoices && hasReceipts) {
69
+ // If both invoices and receipts exist, use only invoices
70
+ filteredDocuments = documents.filter(d => isInvoiceType(d.type));
71
+ }
72
+ // Validate we still have documents after filtering
73
+ if (filteredDocuments.length === 0) {
74
+ throw new Error('Cannot aggregate documents: no valid documents after type priority filtering');
75
+ }
76
+ // Extract business info and normalize amounts for each document
77
+ const processedDocuments = filteredDocuments.map(doc => {
78
+ const businessInfo = extractDocumentBusiness(doc.creditor_id, doc.debtor_id, adminBusinessId);
79
+ const normalizedAmount = normalizeDocumentAmount(doc.total_amount ?? 0, businessInfo.isBusinessCreditor, doc.type);
80
+ return {
81
+ document: doc,
82
+ businessInfo,
83
+ normalizedAmount,
84
+ };
85
+ });
86
+ // Validate single currency
87
+ const currencies = new Set(processedDocuments
88
+ .map(p => p.document.currency_code)
89
+ .filter((code) => code !== null && code !== undefined));
90
+ if (currencies.size === 0) {
91
+ throw new Error('Cannot aggregate documents: all documents have null currency_code');
92
+ }
93
+ if (currencies.size > 1) {
94
+ throw new Error(`Cannot aggregate documents: multiple currencies found (${Array.from(currencies).join(', ')})`);
95
+ }
96
+ // Validate single non-null business ID
97
+ const businessIds = processedDocuments
98
+ .map(p => p.businessInfo.businessId)
99
+ .filter((id) => id !== null && id !== undefined);
100
+ const uniqueBusinessIds = new Set(businessIds);
101
+ if (uniqueBusinessIds.size > 1) {
102
+ throw new Error(`Cannot aggregate documents: multiple business IDs found (${Array.from(uniqueBusinessIds).join(', ')})`);
103
+ }
104
+ // Sum normalized amounts
105
+ const totalAmount = processedDocuments.reduce((sum, p) => sum + p.normalizedAmount, 0);
106
+ // Get common currency (safe since we validated single currency)
107
+ const currency = Array.from(currencies)[0];
108
+ // Get business ID (single non-null or null if all null)
109
+ const businessId = uniqueBusinessIds.size === 1 ? Array.from(uniqueBusinessIds)[0] : null;
110
+ // Get latest date
111
+ const dates = filteredDocuments
112
+ .map(d => d.date)
113
+ .filter((date) => date !== null && date !== undefined);
114
+ if (dates.length === 0) {
115
+ throw new Error('Cannot aggregate documents: all documents have null date');
116
+ }
117
+ const latestDate = dates.reduce((latest, d) => {
118
+ return d > latest ? d : latest;
119
+ }, dates[0]);
120
+ // Concatenate descriptions (serial numbers, file names, or IDs)
121
+ const descriptions = filteredDocuments
122
+ .map(d => {
123
+ // Prefer serial_number, fallback to file_url or image_url name, or document ID
124
+ if (d.serial_number && d.serial_number.trim() !== '') {
125
+ return d.serial_number.trim();
126
+ }
127
+ if (d.file_url) {
128
+ // Extract filename from URL
129
+ const fileName = d.file_url.split('/').pop() || d.file_url;
130
+ return fileName;
131
+ }
132
+ if (d.image_url) {
133
+ // Extract filename from URL
134
+ const fileName = d.image_url.split('/').pop() || d.image_url;
135
+ return fileName;
136
+ }
137
+ // Fallback to document ID
138
+ return `Doc-${d.id.substring(0, 8)}`;
139
+ })
140
+ .filter(desc => desc !== null && desc !== undefined && desc.trim() !== '');
141
+ const description = descriptions.length > 0 ? descriptions.join('\n') : '';
142
+ // Determine document type (use first document after filtering)
143
+ const type = filteredDocuments[0].type;
144
+ return {
145
+ amount: totalAmount,
146
+ currency,
147
+ businessId,
148
+ date: latestDate,
149
+ type,
150
+ description,
151
+ };
152
+ }
153
+ //# sourceMappingURL=document-aggregator.js.map