@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,131 @@
1
+ /**
2
+ * Transaction Aggregator
3
+ *
4
+ * Aggregates multiple transactions from a single charge into a unified representation
5
+ * for matching purposes. Handles fee exclusion, currency validation, business ID
6
+ * validation, amount summation, date selection, and description concatenation.
7
+ */
8
+
9
+ import type { currency } from '@modules/documents/types';
10
+ import type { AggregatedTransaction } from '../types.js';
11
+
12
+ /**
13
+ * Minimal transaction interface for aggregation
14
+ * Based on database schema from accounter_schema.transactions
15
+ */
16
+ export interface Transaction {
17
+ id: string; // UUID
18
+ charge_id: string; // UUID
19
+ amount: string; // numeric in DB
20
+ currency: currency; // Currency type
21
+ business_id: string | null; // UUID or null
22
+ event_date: Date;
23
+ debit_date: Date | null;
24
+ debit_timestamp: Date | null;
25
+ source_description: string | null;
26
+ is_fee: boolean | null;
27
+ }
28
+
29
+ /**
30
+ * Aggregate multiple transactions into a single representation
31
+ *
32
+ * Per specification (section 4.2):
33
+ * 1. Exclude transactions where is_fee = true
34
+ * 2. If multiple currencies exist: throw error
35
+ * 3. If multiple non-null business IDs exist: throw error
36
+ * 4. Amount: sum of all amounts
37
+ * 5. Currency: the common currency
38
+ * 6. Business ID: the single non-null business ID (or null if all null)
39
+ * 7. Date: earliest event_date
40
+ * 8. Description: concatenate all source_description values with line breaks
41
+ *
42
+ * @param transactions - Array of transactions from a charge
43
+ * @returns Aggregated transaction data
44
+ *
45
+ * @throws {Error} If transactions array is empty
46
+ * @throws {Error} If no non-fee transactions exist after filtering
47
+ * @throws {Error} If multiple different currencies exist
48
+ * @throws {Error} If multiple different non-null business IDs exist
49
+ *
50
+ * @example
51
+ * const aggregated = aggregateTransactions([
52
+ * { amount: 100, currency: 'USD', business_id: 'b1', event_date: new Date('2024-01-15'), ... },
53
+ * { amount: 50, currency: 'USD', business_id: 'b1', event_date: new Date('2024-01-20'), ... }
54
+ * ]);
55
+ * // Returns: { amount: 150, currency: 'USD', businessId: 'b1', date: Date('2024-01-15'), ... }
56
+ */
57
+ export function aggregateTransactions(transactions: Transaction[]): AggregatedTransaction {
58
+ // Validate non-empty input
59
+ if (!transactions || transactions.length === 0) {
60
+ throw new Error('Cannot aggregate transactions: array is empty');
61
+ }
62
+
63
+ // Filter out fee transactions
64
+ const nonFeeTransactions = transactions.filter(t => t.is_fee !== true);
65
+
66
+ // Validate we have transactions after filtering
67
+ if (nonFeeTransactions.length === 0) {
68
+ throw new Error('Cannot aggregate transactions: all transactions are marked as fees');
69
+ }
70
+
71
+ // Validate single currency
72
+ const currencies = new Set(nonFeeTransactions.map(t => t.currency));
73
+ if (currencies.size > 1) {
74
+ throw new Error(
75
+ `Cannot aggregate transactions: multiple currencies found (${Array.from(currencies).join(', ')})`,
76
+ );
77
+ }
78
+
79
+ // Validate single non-null business ID
80
+ const businessIds = nonFeeTransactions
81
+ .map(t => t.business_id)
82
+ .filter((id): id is string => id !== null && id !== undefined);
83
+
84
+ const uniqueBusinessIds = new Set(businessIds);
85
+ if (uniqueBusinessIds.size > 1) {
86
+ throw new Error(
87
+ `Cannot aggregate transactions: multiple business IDs found (${Array.from(uniqueBusinessIds).join(', ')})`,
88
+ );
89
+ }
90
+
91
+ // Sum amounts
92
+ const totalAmount = nonFeeTransactions.reduce((sum, t) => sum + Number(t.amount), 0);
93
+
94
+ // Get common currency (safe since we validated single currency)
95
+ const currency = nonFeeTransactions[0].currency;
96
+
97
+ // Get business ID (single non-null or null if all null)
98
+ const businessId = uniqueBusinessIds.size === 1 ? Array.from(uniqueBusinessIds)[0] : null;
99
+
100
+ // Get earliest event_date
101
+ const earliestDate = nonFeeTransactions.reduce((earliest, t) => {
102
+ return t.event_date < earliest ? t.event_date : earliest;
103
+ }, nonFeeTransactions[0].event_date);
104
+
105
+ // Get earliest debit_date
106
+ const earliestDebitDate = nonFeeTransactions.reduce(
107
+ (earliest, t) => {
108
+ const debitDate = t.debit_timestamp ?? t.debit_date;
109
+ if (debitDate === null) return earliest;
110
+ if (earliest === null) return debitDate;
111
+ return debitDate < earliest ? debitDate : earliest;
112
+ },
113
+ null as Date | null,
114
+ );
115
+
116
+ // Concatenate descriptions with line breaks, filtering out nulls
117
+ const descriptions = nonFeeTransactions
118
+ .map(t => t.source_description)
119
+ .filter((desc): desc is string => desc !== null && desc !== undefined && desc.trim() !== '');
120
+
121
+ const description = descriptions.length > 0 ? descriptions.join('\n') : '';
122
+
123
+ return {
124
+ amount: totalAmount,
125
+ currency,
126
+ businessId,
127
+ date: earliestDate,
128
+ description,
129
+ debitDate: earliestDebitDate,
130
+ };
131
+ }
@@ -0,0 +1,23 @@
1
+ import { GraphQLError } from 'graphql';
2
+ import { ChargesMatcherProvider } from '../providers/charges-matcher.provider.js';
3
+ import type { ChargesMatcherModule } from '../types.js';
4
+
5
+ export const autoMatchChargesResolver: ChargesMatcherModule.Resolvers = {
6
+ Mutation: {
7
+ autoMatchCharges: async (_, __, context) => {
8
+ try {
9
+ const result = await context.injector.get(ChargesMatcherProvider).autoMatchCharges(context);
10
+ return result;
11
+ } catch (e) {
12
+ if (e instanceof GraphQLError) {
13
+ throw e;
14
+ }
15
+ const message =
16
+ (e as Error)?.message ??
17
+ (e as { errors: Error[] })?.errors.map(e => e.message).toString() ??
18
+ 'Unknown error';
19
+ throw new GraphQLError(message);
20
+ }
21
+ },
22
+ },
23
+ };
@@ -0,0 +1,25 @@
1
+ import { GraphQLError } from 'graphql';
2
+ import { ChargesMatcherProvider } from '../providers/charges-matcher.provider.js';
3
+ import type { ChargesMatcherModule } from '../types.js';
4
+
5
+ export const findChargeMatchesResolver: ChargesMatcherModule.Resolvers = {
6
+ Query: {
7
+ findChargeMatches: async (_, { chargeId }, context) => {
8
+ try {
9
+ const result = await context.injector
10
+ .get(ChargesMatcherProvider)
11
+ .findMatchesForCharge(chargeId, context);
12
+ return result;
13
+ } catch (e) {
14
+ if (e instanceof GraphQLError) {
15
+ throw e;
16
+ }
17
+ const message =
18
+ (e as Error)?.message ??
19
+ (e as { errors: Error[] })?.errors.map(e => e.message).toString() ??
20
+ 'Unknown error';
21
+ throw new GraphQLError(message);
22
+ }
23
+ },
24
+ },
25
+ };
@@ -0,0 +1,12 @@
1
+ import type { ChargesMatcherModule } from '../types.js';
2
+ import { autoMatchChargesResolver } from './auto-match-charges.resolver.js';
3
+ import { findChargeMatchesResolver } from './find-charge-matches.resolver.js';
4
+
5
+ export const chargesMatcherResolvers: ChargesMatcherModule.Resolvers = {
6
+ Query: {
7
+ ...findChargeMatchesResolver.Query,
8
+ },
9
+ Mutation: {
10
+ ...autoMatchChargesResolver.Mutation,
11
+ },
12
+ };
@@ -0,0 +1,47 @@
1
+ import { gql } from 'graphql-modules';
2
+
3
+ export default gql`
4
+ extend type Query {
5
+ " Find potential matches for a single unmatched charge "
6
+ findChargeMatches(chargeId: UUID!): ChargeMatchesResult! @auth(role: ACCOUNTANT)
7
+ }
8
+
9
+ extend type Mutation {
10
+ " Automatically match all unmatched charges above the confidence threshold "
11
+ autoMatchCharges: AutoMatchChargesResult! @auth(role: ACCOUNTANT)
12
+ }
13
+
14
+ " Result of finding matches for a single charge "
15
+ type ChargeMatchesResult {
16
+ " Array of up to 5 matches, ordered by confidence score (highest first) "
17
+ matches: [ChargeMatch!]!
18
+ }
19
+
20
+ " A single charge match with its confidence score "
21
+ type ChargeMatch {
22
+ " UUID of the matched charge "
23
+ chargeId: UUID!
24
+ " Confidence score between 0.00 and 1.00 "
25
+ confidenceScore: Float!
26
+ }
27
+
28
+ " Result of the auto-match operation "
29
+ type AutoMatchChargesResult {
30
+ " Total number of charges that were successfully matched and merged "
31
+ totalMatches: Int!
32
+ " Array of charges that were merged, with their confidence scores "
33
+ mergedCharges: [MergedCharge!]!
34
+ " Array of charge UUIDs that had multiple high-confidence matches and were skipped "
35
+ skippedCharges: [UUID!]!
36
+ " Array of error messages encountered during the operation "
37
+ errors: [String!]!
38
+ }
39
+
40
+ " A charge that was successfully merged during auto-match "
41
+ type MergedCharge {
42
+ " UUID of the deleted/merged-away charge "
43
+ chargeId: UUID!
44
+ " Confidence score that triggered the merge "
45
+ confidenceScore: Float!
46
+ }
47
+ `;
@@ -0,0 +1,200 @@
1
+ import type { currency, document_type, IGetAllDocumentsResult } from '@modules/documents/types.js';
2
+ import type { IGetTransactionsByIdsResult } from '@modules/transactions/types.js';
3
+
4
+ export * from './__generated__/types.js';
5
+
6
+ /**
7
+ * Re-export shared types from other modules
8
+ */
9
+ export type {
10
+ currency as Currency,
11
+ document_type as DocumentType,
12
+ } from '@modules/documents/types.js';
13
+
14
+ /**
15
+ * Transaction interface matching the database schema
16
+ * Uses the complete type from transactions module
17
+ */
18
+ export type Transaction = IGetTransactionsByIdsResult;
19
+
20
+ /**
21
+ * Document interface matching the database schema
22
+ * Uses the complete type from documents module
23
+ */
24
+ export type Document = IGetAllDocumentsResult;
25
+
26
+ /**
27
+ * Represents a single charge match with its confidence score
28
+ */
29
+ export interface ChargeMatch {
30
+ /** UUID of the matched charge */
31
+ chargeId: string;
32
+ /** Confidence score between 0.00 and 1.00 (two decimal precision) */
33
+ confidenceScore: number;
34
+ }
35
+
36
+ /**
37
+ * Result of finding matches for a single charge
38
+ */
39
+ export interface ChargeMatchesResult {
40
+ /** Array of up to 5 matches, ordered by confidence score (highest first) */
41
+ matches: ChargeMatch[];
42
+ }
43
+
44
+ /**
45
+ * Represents a charge that was successfully merged during auto-match
46
+ */
47
+ export interface MergedCharge {
48
+ /** UUID of the deleted/merged-away charge */
49
+ chargeId: string;
50
+ /** Confidence score that triggered the merge (≥0.95) */
51
+ confidenceScore: number;
52
+ }
53
+
54
+ /**
55
+ * Result of the auto-match operation
56
+ */
57
+ export interface AutoMatchChargesResult {
58
+ /** Total number of charges that were successfully matched and merged */
59
+ totalMatches: number;
60
+ /** Array of charges that were merged, with their confidence scores */
61
+ mergedCharges: MergedCharge[];
62
+ /** Array of charge UUIDs that had multiple high-confidence matches and were skipped */
63
+ skippedCharges: string[];
64
+ /** Array of error messages encountered during the operation */
65
+ errors: string[];
66
+ }
67
+
68
+ /**
69
+ * Aggregated transaction data for matching purposes
70
+ */
71
+ export interface AggregatedTransaction {
72
+ /** Sum of all transaction amounts */
73
+ amount: number;
74
+ /** Common currency across all transactions */
75
+ currency: currency;
76
+ /** Single non-null business ID (or null if all are null) */
77
+ businessId: string | null;
78
+ /** Earliest event_date among transactions */
79
+ date: Date;
80
+ /** Earliest debit_date/debit_timestamp (for receipt matching) */
81
+ debitDate: Date | null;
82
+ /** Concatenated source_description values */
83
+ description: string;
84
+ }
85
+
86
+ /**
87
+ * Aggregated document data for matching purposes
88
+ */
89
+ export interface AggregatedDocument {
90
+ /** Sum of all normalized document amounts */
91
+ amount: number;
92
+ /** Common currency across all documents */
93
+ currency: currency;
94
+ /** Single non-null business ID (or null if all are null) */
95
+ businessId: string | null;
96
+ /** Latest document date */
97
+ date: Date;
98
+ /** Concatenated serial numbers and identifiers */
99
+ description: string;
100
+ /** Document type (for date matching logic) */
101
+ type: document_type;
102
+ /** Whether the business is on the creditor side */
103
+ businessIsCreditor: boolean;
104
+ }
105
+
106
+ /**
107
+ * Individual confidence scores for different matching factors
108
+ */
109
+ export interface ConfidenceScores {
110
+ /** Amount confidence (0.0 - 1.0) */
111
+ amount: number;
112
+ /** Currency confidence (0.0 - 1.0) */
113
+ currency: number;
114
+ /** Business confidence (0.0 - 1.0) */
115
+ business: number;
116
+ /** Date confidence (0.0 - 1.0) */
117
+ date: number;
118
+ }
119
+
120
+ /**
121
+ * Complete confidence calculation result
122
+ */
123
+ export interface ConfidenceResult {
124
+ /** Overall weighted confidence score */
125
+ overall: number;
126
+ /** Individual component scores */
127
+ scores: ConfidenceScores;
128
+ }
129
+
130
+ /**
131
+ * Charge classification for matching purposes
132
+ */
133
+ export enum ChargeType {
134
+ /** Charge has transactions but no accounting documents */
135
+ TRANSACTION_ONLY = 'TRANSACTION_ONLY',
136
+ /** Charge has accounting documents but no transactions */
137
+ DOCUMENT_ONLY = 'DOCUMENT_ONLY',
138
+ /** Charge has both transactions and accounting documents */
139
+ MATCHED = 'MATCHED',
140
+ }
141
+
142
+ /**
143
+ * Charge with its associated data for matching
144
+ */
145
+ export interface ChargeWithData {
146
+ /** Charge UUID */
147
+ chargeId: string;
148
+ /** Owner UUID */
149
+ ownerId: string;
150
+ /** Charge classification */
151
+ type: ChargeType;
152
+ /** Associated transactions (if any) */
153
+ transactions: Transaction[];
154
+ /** Associated documents (if any) */
155
+ documents: Document[];
156
+ }
157
+
158
+ /**
159
+ * Candidate charge for matching with aggregated data
160
+ */
161
+ export interface MatchCandidate {
162
+ /** Charge UUID */
163
+ chargeId: string;
164
+ /** Aggregated transaction data (if transaction charge) */
165
+ transactionData?: AggregatedTransaction;
166
+ /** Aggregated document data (if document charge) */
167
+ documentData?: AggregatedDocument;
168
+ }
169
+
170
+ /**
171
+ * Match score result with confidence and components
172
+ */
173
+ export interface MatchScore {
174
+ /** Charge ID being scored */
175
+ chargeId: string;
176
+ /** Overall confidence score (0.0 - 1.0) */
177
+ confidenceScore: number;
178
+ /** Individual confidence component scores */
179
+ components: ConfidenceScores;
180
+ }
181
+
182
+ /**
183
+ * Transaction charge for matching
184
+ */
185
+ export interface TransactionCharge {
186
+ /** Charge UUID */
187
+ chargeId: string;
188
+ /** Array of transactions in the charge */
189
+ transactions: Transaction[];
190
+ }
191
+
192
+ /**
193
+ * Document charge for matching
194
+ */
195
+ export interface DocumentCharge {
196
+ /** Charge UUID */
197
+ chargeId: string;
198
+ /** Array of documents in the charge */
199
+ documents: Document[];
200
+ }
@@ -42,13 +42,13 @@ const missingInfoSuggestions: Resolver<
42
42
  if (charge?.transactions_event_amount && charge?.transactions_currency) {
43
43
  // use transactions info, if exists
44
44
  response.amount = {
45
- amount: charge.transactions_event_amount,
45
+ amount: Number(charge.transactions_event_amount),
46
46
  currency: formatCurrency(charge.transactions_currency),
47
47
  };
48
48
  } else if (charge?.documents_event_amount && charge?.documents_currency) {
49
49
  // Use parallel documents (if exists) as documents_event_amount is based on invoices OR receipts
50
50
  response.amount = {
51
- amount: String(charge.documents_event_amount),
51
+ amount: charge.documents_event_amount,
52
52
  currency: formatCurrency(charge.documents_currency),
53
53
  };
54
54
  }
@@ -9,7 +9,7 @@ import { Currency } from '@shared/enums';
9
9
  import { NewDocumentInfo } from '@shared/gql-types';
10
10
  import { dateToTimelessDateString } from '@shared/helpers';
11
11
  import { TimelessDateString } from '@shared/types';
12
- import { getClientFromGreenInvoiceClient } from './green-invoice-clients.helper';
12
+ import { getClientFromGreenInvoiceClient } from './green-invoice-clients.helper.js';
13
13
 
14
14
  export const convertContractToDraft = async (
15
15
  contract: IGetContractsByIdsResult,
@@ -21,7 +21,7 @@ import type {
21
21
  import { CloudinaryProvider } from '@modules/app-providers/cloudinary.js';
22
22
  import { GreenInvoiceClientProvider } from '@modules/app-providers/green-invoice-client.js';
23
23
  import { ChargesProvider } from '@modules/charges/providers/charges.provider.js';
24
- import { CountryCode } from '@modules/countries/types';
24
+ import { CountryCode } from '@modules/countries/types.js';
25
25
  import { DocumentsProvider } from '@modules/documents/providers/documents.provider.js';
26
26
  import { IssuedDocumentsProvider } from '@modules/documents/providers/issued-documents.provider.js';
27
27
  import type { document_status, IInsertDocumentsParams } from '@modules/documents/types';
@@ -20,6 +20,7 @@ import { GmailServiceProvider } from './modules/app-providers/gmail-listener/gma
20
20
  import { PubsubServiceProvider } from './modules/app-providers/gmail-listener/pubsub-service.provider.js';
21
21
  import { GreenInvoiceClientProvider } from './modules/app-providers/green-invoice-client.js';
22
22
  import { businessTripsModule } from './modules/business-trips/index.js';
23
+ import { chargesMatcherModule } from './modules/charges-matcher/index.js';
23
24
  import { chargesModule } from './modules/charges/index.js';
24
25
  import { chartsModule } from './modules/charts/index.js';
25
26
  import { commonModule } from './modules/common/index.js';
@@ -61,6 +62,7 @@ export async function createGraphQLApp(env: Environment) {
61
62
  accountantApprovalModule,
62
63
  businessTripsModule,
63
64
  chargesModule,
65
+ chargesMatcherModule,
64
66
  cornJobsModule,
65
67
  depreciationModule,
66
68
  documentsModule,
@@ -32,7 +32,7 @@ export interface DocumentSuggestionsProto {
32
32
  ownerId?: string;
33
33
  counterpartyId?: string;
34
34
  amount?: {
35
- amount: string;
35
+ amount: number;
36
36
  currency: Currency;
37
37
  };
38
38
  isIncome?: boolean;