@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.
- package/CHANGELOG.md +47 -7
- package/dist/green-invoice-graphql/src/mesh-artifacts/index.d.ts +7 -7
- package/dist/server/src/__generated__/types.d.ts +77 -0
- package/dist/server/src/__generated__/types.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__generated__/types.d.ts +68 -0
- package/dist/server/src/modules/charges-matcher/__generated__/types.js +7 -0
- package/dist/server/src/modules/charges-matcher/__generated__/types.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/amount-confidence.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/amount-confidence.test.js +218 -0
- package/dist/server/src/modules/charges-matcher/__tests__/amount-confidence.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js +645 -0
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.js +530 -0
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/business-confidence.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/business-confidence.test.js +143 -0
- package/dist/server/src/modules/charges-matcher/__tests__/business-confidence.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/candidate-filter.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/candidate-filter.test.js +186 -0
- package/dist/server/src/modules/charges-matcher/__tests__/candidate-filter.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.js +301 -0
- package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/currency-confidence.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/currency-confidence.test.js +127 -0
- package/dist/server/src/modules/charges-matcher/__tests__/currency-confidence.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.js +246 -0
- package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-aggregator.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-aggregator.test.js +475 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-aggregator.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.js +287 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-business.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-business.test.js +151 -0
- package/dist/server/src/modules/charges-matcher/__tests__/document-business.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.js +550 -0
- package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/overall-confidence.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/overall-confidence.test.js +410 -0
- package/dist/server/src/modules/charges-matcher/__tests__/overall-confidence.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js +504 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.js +483 -0
- package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.d.ts +46 -0
- package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.js +143 -0
- package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/test-infrastructure.spec.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/test-infrastructure.spec.js +137 -0
- package/dist/server/src/modules/charges-matcher/__tests__/test-infrastructure.spec.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/transaction-aggregator.test.d.ts +1 -0
- package/dist/server/src/modules/charges-matcher/__tests__/transaction-aggregator.test.js +415 -0
- package/dist/server/src/modules/charges-matcher/__tests__/transaction-aggregator.test.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/amount-confidence.helper.d.ts +7 -0
- package/dist/server/src/modules/charges-matcher/helpers/amount-confidence.helper.js +70 -0
- package/dist/server/src/modules/charges-matcher/helpers/amount-confidence.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/business-confidence.helper.d.ts +7 -0
- package/dist/server/src/modules/charges-matcher/helpers/business-confidence.helper.js +19 -0
- package/dist/server/src/modules/charges-matcher/helpers/business-confidence.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/candidate-filter.helper.d.ts +24 -0
- package/dist/server/src/modules/charges-matcher/helpers/candidate-filter.helper.js +45 -0
- package/dist/server/src/modules/charges-matcher/helpers/candidate-filter.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.d.ts +33 -0
- package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.js +65 -0
- package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/currency-confidence.helper.d.ts +7 -0
- package/dist/server/src/modules/charges-matcher/helpers/currency-confidence.helper.js +18 -0
- package/dist/server/src/modules/charges-matcher/helpers/currency-confidence.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.d.ts +7 -0
- package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.js +35 -0
- package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.d.ts +49 -0
- package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.js +58 -0
- package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/document-business.helper.d.ts +13 -0
- package/dist/server/src/modules/charges-matcher/helpers/document-business.helper.js +37 -0
- package/dist/server/src/modules/charges-matcher/helpers/document-business.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/helpers/overall-confidence.helper.d.ts +42 -0
- package/dist/server/src/modules/charges-matcher/helpers/overall-confidence.helper.js +77 -0
- package/dist/server/src/modules/charges-matcher/helpers/overall-confidence.helper.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/index.d.ts +3 -0
- package/dist/server/src/modules/charges-matcher/index.js +15 -0
- package/dist/server/src/modules/charges-matcher/index.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.d.ts +48 -0
- package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js +133 -0
- package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.d.ts +38 -0
- package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js +248 -0
- package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/providers/document-aggregator.d.ts +61 -0
- package/dist/server/src/modules/charges-matcher/providers/document-aggregator.js +153 -0
- package/dist/server/src/modules/charges-matcher/providers/document-aggregator.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.d.ts +25 -0
- package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.js +114 -0
- package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/providers/single-match.provider.d.ts +39 -0
- package/dist/server/src/modules/charges-matcher/providers/single-match.provider.js +189 -0
- package/dist/server/src/modules/charges-matcher/providers/single-match.provider.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/providers/transaction-aggregator.d.ts +54 -0
- package/dist/server/src/modules/charges-matcher/providers/transaction-aggregator.js +93 -0
- package/dist/server/src/modules/charges-matcher/providers/transaction-aggregator.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.d.ts +2 -0
- package/dist/server/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.js +22 -0
- package/dist/server/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.d.ts +2 -0
- package/dist/server/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.js +24 -0
- package/dist/server/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/resolvers/index.d.ts +2 -0
- package/dist/server/src/modules/charges-matcher/resolvers/index.js +11 -0
- package/dist/server/src/modules/charges-matcher/resolvers/index.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.d.ts +2 -0
- package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.js +47 -0
- package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.js.map +1 -0
- package/dist/server/src/modules/charges-matcher/types.d.ts +179 -0
- package/dist/server/src/modules/charges-matcher/types.js +14 -0
- package/dist/server/src/modules/charges-matcher/types.js.map +1 -0
- package/dist/server/src/modules/documents/resolvers/document-suggestions.resolver.js +2 -2
- package/dist/server/src/modules/documents/resolvers/document-suggestions.resolver.js.map +1 -1
- package/dist/server/src/modules/green-invoice/helpers/contract-to-draft.helper.js +1 -1
- package/dist/server/src/modules/green-invoice/helpers/contract-to-draft.helper.js.map +1 -1
- package/dist/server/src/modules/green-invoice/helpers/green-invoice.helper.d.ts +1 -1
- package/dist/server/src/modules/green-invoice/helpers/green-invoice.helper.js +1 -1
- package/dist/server/src/modules/green-invoice/helpers/green-invoice.helper.js.map +1 -1
- package/dist/server/src/modules-app.js +2 -0
- package/dist/server/src/modules-app.js.map +1 -1
- package/dist/server/src/shared/types/index.d.ts +1 -1
- package/package.json +4 -4
- package/src/__generated__/types.ts +87 -0
- package/src/modules/charges-matcher/README.md +279 -0
- package/src/modules/charges-matcher/__generated__/types.ts +71 -0
- package/src/modules/charges-matcher/__tests__/amount-confidence.test.ts +260 -0
- package/src/modules/charges-matcher/__tests__/auto-match-integration.test.ts +714 -0
- package/src/modules/charges-matcher/__tests__/auto-match.test.ts +621 -0
- package/src/modules/charges-matcher/__tests__/business-confidence.test.ts +177 -0
- package/src/modules/charges-matcher/__tests__/candidate-filter.test.ts +238 -0
- package/src/modules/charges-matcher/__tests__/charge-validator.test.ts +374 -0
- package/src/modules/charges-matcher/__tests__/currency-confidence.test.ts +164 -0
- package/src/modules/charges-matcher/__tests__/date-confidence.test.ts +291 -0
- package/src/modules/charges-matcher/__tests__/document-aggregator.test.ts +614 -0
- package/src/modules/charges-matcher/__tests__/document-amount.test.ts +352 -0
- package/src/modules/charges-matcher/__tests__/document-business.test.ts +192 -0
- package/src/modules/charges-matcher/__tests__/match-scorer.test.ts +659 -0
- package/src/modules/charges-matcher/__tests__/overall-confidence.test.ts +502 -0
- package/src/modules/charges-matcher/__tests__/single-match-integration.test.ts +556 -0
- package/src/modules/charges-matcher/__tests__/single-match.test.ts +608 -0
- package/src/modules/charges-matcher/__tests__/test-helpers.ts +174 -0
- package/src/modules/charges-matcher/__tests__/test-infrastructure.spec.ts +177 -0
- package/src/modules/charges-matcher/__tests__/transaction-aggregator.test.ts +547 -0
- package/src/modules/charges-matcher/documentation/README.md +331 -0
- package/src/modules/charges-matcher/documentation/SPEC.md +1503 -0
- package/src/modules/charges-matcher/documentation/TODO.md +799 -0
- package/src/modules/charges-matcher/helpers/amount-confidence.helper.ts +88 -0
- package/src/modules/charges-matcher/helpers/business-confidence.helper.ts +23 -0
- package/src/modules/charges-matcher/helpers/candidate-filter.helper.ts +56 -0
- package/src/modules/charges-matcher/helpers/charge-validator.helper.ts +100 -0
- package/src/modules/charges-matcher/helpers/currency-confidence.helper.ts +22 -0
- package/src/modules/charges-matcher/helpers/date-confidence.helper.ts +41 -0
- package/src/modules/charges-matcher/helpers/document-amount.helper.ts +77 -0
- package/src/modules/charges-matcher/helpers/document-business.helper.ts +54 -0
- package/src/modules/charges-matcher/helpers/overall-confidence.helper.ts +90 -0
- package/src/modules/charges-matcher/index.ts +17 -0
- package/src/modules/charges-matcher/providers/auto-match.provider.ts +176 -0
- package/src/modules/charges-matcher/providers/charges-matcher.provider.ts +322 -0
- package/src/modules/charges-matcher/providers/document-aggregator.ts +211 -0
- package/src/modules/charges-matcher/providers/match-scorer.provider.ts +154 -0
- package/src/modules/charges-matcher/providers/single-match.provider.ts +252 -0
- package/src/modules/charges-matcher/providers/transaction-aggregator.ts +131 -0
- package/src/modules/charges-matcher/resolvers/auto-match-charges.resolver.ts +23 -0
- package/src/modules/charges-matcher/resolvers/find-charge-matches.resolver.ts +25 -0
- package/src/modules/charges-matcher/resolvers/index.ts +12 -0
- package/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.ts +47 -0
- package/src/modules/charges-matcher/types.ts +200 -0
- package/src/modules/documents/resolvers/document-suggestions.resolver.ts +2 -2
- package/src/modules/green-invoice/helpers/contract-to-draft.helper.ts +1 -1
- package/src/modules/green-invoice/helpers/green-invoice.helper.ts +1 -1
- package/src/modules-app.ts +2 -0
- 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:
|
|
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';
|
package/src/modules-app.ts
CHANGED
|
@@ -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,
|