@accounter/server 0.0.8-alpha-20251102200443-d7162b8ce1dfc629b8b454df17dcec9ed005a052 → 0.0.8-alpha-20251102213150-c9d936f545d5351df0dc5326c2623266f1ad1f46
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 +1 -1
- 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-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-app.ts +2 -0
- package/src/shared/types/index.ts +1 -1
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
aggregateTransactions,
|
|
4
|
+
type Transaction,
|
|
5
|
+
} from '../providers/transaction-aggregator.js';
|
|
6
|
+
|
|
7
|
+
describe('Transaction Aggregator', () => {
|
|
8
|
+
// Helper to create test transactions
|
|
9
|
+
const createTransaction = (overrides: Partial<Transaction> = {}): Transaction => ({
|
|
10
|
+
id: 'txn-' + Math.random().toString(36).substr(2, 9),
|
|
11
|
+
charge_id: 'charge-123',
|
|
12
|
+
amount: "100",
|
|
13
|
+
currency: 'USD',
|
|
14
|
+
business_id: null,
|
|
15
|
+
event_date: new Date('2024-01-15'),
|
|
16
|
+
debit_date: null,
|
|
17
|
+
source_description: 'Test transaction',
|
|
18
|
+
is_fee: false,
|
|
19
|
+
debit_timestamp: null,
|
|
20
|
+
...overrides,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Single Transaction', () => {
|
|
24
|
+
it('should return single transaction as-is', () => {
|
|
25
|
+
const transaction = createTransaction({
|
|
26
|
+
amount: "150.5",
|
|
27
|
+
currency: 'USD',
|
|
28
|
+
business_id: 'business-1',
|
|
29
|
+
event_date: new Date('2024-01-15'),
|
|
30
|
+
source_description: 'Payment from client',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = aggregateTransactions([transaction]);
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
amount: 150.5,
|
|
37
|
+
currency: 'USD',
|
|
38
|
+
businessId: 'business-1',
|
|
39
|
+
date: new Date('2024-01-15'),
|
|
40
|
+
debitDate: null,
|
|
41
|
+
description: 'Payment from client',
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle transaction with null business_id', () => {
|
|
46
|
+
const transaction = createTransaction({
|
|
47
|
+
amount: "200",
|
|
48
|
+
business_id: null,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = aggregateTransactions([transaction]);
|
|
52
|
+
|
|
53
|
+
expect(result.businessId).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle transaction with null description', () => {
|
|
57
|
+
const transaction = createTransaction({
|
|
58
|
+
source_description: null,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = aggregateTransactions([transaction]);
|
|
62
|
+
|
|
63
|
+
expect(result.description).toBe('');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle transaction with empty description', () => {
|
|
67
|
+
const transaction = createTransaction({
|
|
68
|
+
source_description: ' ',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const result = aggregateTransactions([transaction]);
|
|
72
|
+
|
|
73
|
+
expect(result.description).toBe('');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('Multiple Transactions - Same Currency', () => {
|
|
78
|
+
it('should sum amounts correctly', () => {
|
|
79
|
+
const transactions = [
|
|
80
|
+
createTransaction({ amount: "100", currency: 'USD' }),
|
|
81
|
+
createTransaction({ amount: "200", currency: 'USD' }),
|
|
82
|
+
createTransaction({ amount: "50.5", currency: 'USD' }),
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const result = aggregateTransactions(transactions);
|
|
86
|
+
|
|
87
|
+
expect(result.amount).toBe(350.5);
|
|
88
|
+
expect(result.currency).toBe('USD');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle negative amounts (debits)', () => {
|
|
92
|
+
const transactions = [
|
|
93
|
+
createTransaction({ amount: "-100", currency: 'ILS' }),
|
|
94
|
+
createTransaction({ amount: "-50", currency: 'ILS' }),
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const result = aggregateTransactions(transactions);
|
|
98
|
+
|
|
99
|
+
expect(result.amount).toBe(-150);
|
|
100
|
+
expect(result.currency).toBe('ILS');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should handle mixed positive and negative amounts', () => {
|
|
104
|
+
const transactions = [
|
|
105
|
+
createTransaction({ amount: "100", currency: 'EUR' }),
|
|
106
|
+
createTransaction({ amount: "-30", currency: 'EUR' }),
|
|
107
|
+
createTransaction({ amount: "50", currency: 'EUR' }),
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const result = aggregateTransactions(transactions);
|
|
111
|
+
|
|
112
|
+
expect(result.amount).toBe(120);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('Fee Transactions', () => {
|
|
117
|
+
it('should exclude transactions where is_fee is true', () => {
|
|
118
|
+
const transactions = [
|
|
119
|
+
createTransaction({ amount: "100", currency: 'USD', is_fee: false }),
|
|
120
|
+
createTransaction({ amount: "5", currency: 'USD', is_fee: true }), // Fee - should be excluded
|
|
121
|
+
createTransaction({ amount: "200", currency: 'USD', is_fee: false }),
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const result = aggregateTransactions(transactions);
|
|
125
|
+
|
|
126
|
+
expect(result.amount).toBe(300); // 100 + 200, fee excluded
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should exclude transactions where is_fee is null but treated as false', () => {
|
|
130
|
+
const transactions = [
|
|
131
|
+
createTransaction({ amount: "100", currency: 'USD', is_fee: null }),
|
|
132
|
+
createTransaction({ amount: "50", currency: 'USD', is_fee: false }),
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const result = aggregateTransactions(transactions);
|
|
136
|
+
|
|
137
|
+
expect(result.amount).toBe(150); // Both included (null treated as false)
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should throw error when all transactions are fees', () => {
|
|
141
|
+
const transactions = [
|
|
142
|
+
createTransaction({ amount: "5", is_fee: true }),
|
|
143
|
+
createTransaction({ amount: "3", is_fee: true }),
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
expect(() => aggregateTransactions(transactions)).toThrow(
|
|
147
|
+
'Cannot aggregate transactions: all transactions are marked as fees',
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should not include fee descriptions in concatenation', () => {
|
|
152
|
+
const transactions = [
|
|
153
|
+
createTransaction({
|
|
154
|
+
amount: "100",
|
|
155
|
+
source_description: 'Main transaction',
|
|
156
|
+
is_fee: false,
|
|
157
|
+
}),
|
|
158
|
+
createTransaction({ amount: "5", source_description: 'Bank fee', is_fee: true }),
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const result = aggregateTransactions(transactions);
|
|
162
|
+
|
|
163
|
+
expect(result.description).toBe('Main transaction');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Currency Validation', () => {
|
|
168
|
+
it('should throw error with multiple currencies', () => {
|
|
169
|
+
const transactions = [
|
|
170
|
+
createTransaction({ amount: "100", currency: 'USD' }),
|
|
171
|
+
createTransaction({ amount: "200", currency: 'EUR' }),
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
expect(() => aggregateTransactions(transactions)).toThrow(
|
|
175
|
+
'Cannot aggregate transactions: multiple currencies found (USD, EUR)',
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should throw error with three different currencies', () => {
|
|
180
|
+
const transactions = [
|
|
181
|
+
createTransaction({ amount: "100", currency: 'USD' }),
|
|
182
|
+
createTransaction({ amount: "200", currency: 'EUR' }),
|
|
183
|
+
createTransaction({ amount: "300", currency: 'GBP' }),
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
expect(() => aggregateTransactions(transactions)).toThrow(/multiple currencies found/);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should not throw error when all have same currency', () => {
|
|
190
|
+
const transactions = [
|
|
191
|
+
createTransaction({ amount: "100", currency: 'ILS' }),
|
|
192
|
+
createTransaction({ amount: "200", currency: 'ILS' }),
|
|
193
|
+
createTransaction({ amount: "300", currency: 'ILS' }),
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
expect(() => aggregateTransactions(transactions)).not.toThrow();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should ignore currency of fee transactions in validation', () => {
|
|
200
|
+
const transactions = [
|
|
201
|
+
createTransaction({ amount: "100", currency: 'USD', is_fee: false }),
|
|
202
|
+
createTransaction({ amount: "5", currency: 'EUR', is_fee: true }), // Different currency but is fee
|
|
203
|
+
createTransaction({ amount: "200", currency: 'USD', is_fee: false }),
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
const result = aggregateTransactions(transactions);
|
|
207
|
+
|
|
208
|
+
expect(result.currency).toBe('USD');
|
|
209
|
+
expect(result.amount).toBe(300);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('Business ID Validation', () => {
|
|
214
|
+
it('should throw error with multiple different business IDs', () => {
|
|
215
|
+
const transactions = [
|
|
216
|
+
createTransaction({ amount: "100", business_id: 'business-1' }),
|
|
217
|
+
createTransaction({ amount: "200", business_id: 'business-2' }),
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
expect(() => aggregateTransactions(transactions)).toThrow(
|
|
221
|
+
'Cannot aggregate transactions: multiple business IDs found (business-1, business-2)',
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should throw error with three different business IDs', () => {
|
|
226
|
+
const transactions = [
|
|
227
|
+
createTransaction({ amount: "100", business_id: 'business-1' }),
|
|
228
|
+
createTransaction({ amount: "200", business_id: 'business-2' }),
|
|
229
|
+
createTransaction({ amount: "300", business_id: 'business-3' }),
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
expect(() => aggregateTransactions(transactions)).toThrow(/multiple business IDs found/);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should return null when all business IDs are null', () => {
|
|
236
|
+
const transactions = [
|
|
237
|
+
createTransaction({ amount: "100", business_id: null }),
|
|
238
|
+
createTransaction({ amount: "200", business_id: null }),
|
|
239
|
+
createTransaction({ amount: "300", business_id: null }),
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
const result = aggregateTransactions(transactions);
|
|
243
|
+
|
|
244
|
+
expect(result.businessId).toBeNull();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should return single non-null business ID', () => {
|
|
248
|
+
const transactions = [
|
|
249
|
+
createTransaction({ amount: "100", business_id: 'business-1' }),
|
|
250
|
+
createTransaction({ amount: "200", business_id: null }),
|
|
251
|
+
createTransaction({ amount: "300", business_id: null }),
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const result = aggregateTransactions(transactions);
|
|
255
|
+
|
|
256
|
+
expect(result.businessId).toBe('business-1');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should handle multiple nulls and one business ID', () => {
|
|
260
|
+
const transactions = [
|
|
261
|
+
createTransaction({ amount: "100", business_id: null }),
|
|
262
|
+
createTransaction({ amount: "200", business_id: 'business-xyz' }),
|
|
263
|
+
createTransaction({ amount: "300", business_id: null }),
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
const result = aggregateTransactions(transactions);
|
|
267
|
+
|
|
268
|
+
expect(result.businessId).toBe('business-xyz');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should not throw when all have same business ID', () => {
|
|
272
|
+
const transactions = [
|
|
273
|
+
createTransaction({ amount: "100", business_id: 'business-1' }),
|
|
274
|
+
createTransaction({ amount: "200", business_id: 'business-1' }),
|
|
275
|
+
createTransaction({ amount: "300", business_id: 'business-1' }),
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
const result = aggregateTransactions(transactions);
|
|
279
|
+
|
|
280
|
+
expect(result.businessId).toBe('business-1');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should ignore business ID of fee transactions in validation', () => {
|
|
284
|
+
const transactions = [
|
|
285
|
+
createTransaction({ amount: "100", business_id: 'business-1', is_fee: false }),
|
|
286
|
+
createTransaction({ amount: "5", business_id: 'business-2', is_fee: true }), // Different business but is fee
|
|
287
|
+
createTransaction({ amount: "200", business_id: 'business-1', is_fee: false }),
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
const result = aggregateTransactions(transactions);
|
|
291
|
+
|
|
292
|
+
expect(result.businessId).toBe('business-1');
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('Date Selection', () => {
|
|
297
|
+
it('should select earliest event_date', () => {
|
|
298
|
+
const transactions = [
|
|
299
|
+
createTransaction({ event_date: new Date('2024-03-15') }),
|
|
300
|
+
createTransaction({ event_date: new Date('2024-01-10') }), // Earliest
|
|
301
|
+
createTransaction({ event_date: new Date('2024-02-20') }),
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
const result = aggregateTransactions(transactions);
|
|
305
|
+
|
|
306
|
+
expect(result.date).toEqual(new Date('2024-01-10'));
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should select earliest from same month', () => {
|
|
310
|
+
const transactions = [
|
|
311
|
+
createTransaction({ event_date: new Date('2024-01-20') }),
|
|
312
|
+
createTransaction({ event_date: new Date('2024-01-15') }), // Earliest
|
|
313
|
+
createTransaction({ event_date: new Date('2024-01-25') }),
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
const result = aggregateTransactions(transactions);
|
|
317
|
+
|
|
318
|
+
expect(result.date).toEqual(new Date('2024-01-15'));
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should handle dates spanning years', () => {
|
|
322
|
+
const transactions = [
|
|
323
|
+
createTransaction({ event_date: new Date('2024-01-15') }),
|
|
324
|
+
createTransaction({ event_date: new Date('2023-12-31') }), // Earliest (previous year)
|
|
325
|
+
createTransaction({ event_date: new Date('2024-02-01') }),
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
const result = aggregateTransactions(transactions);
|
|
329
|
+
|
|
330
|
+
expect(result.date).toEqual(new Date('2023-12-31'));
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should handle same date for all transactions', () => {
|
|
334
|
+
const transactions = [
|
|
335
|
+
createTransaction({ event_date: new Date('2024-01-15') }),
|
|
336
|
+
createTransaction({ event_date: new Date('2024-01-15') }),
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
const result = aggregateTransactions(transactions);
|
|
340
|
+
|
|
341
|
+
expect(result.date).toEqual(new Date('2024-01-15'));
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe('Description Concatenation', () => {
|
|
346
|
+
it('should concatenate descriptions with line breaks', () => {
|
|
347
|
+
const transactions = [
|
|
348
|
+
createTransaction({ source_description: 'First transaction' }),
|
|
349
|
+
createTransaction({ source_description: 'Second transaction' }),
|
|
350
|
+
createTransaction({ source_description: 'Third transaction' }),
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
const result = aggregateTransactions(transactions);
|
|
354
|
+
|
|
355
|
+
expect(result.description).toBe('First transaction\nSecond transaction\nThird transaction');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should filter out null descriptions', () => {
|
|
359
|
+
const transactions = [
|
|
360
|
+
createTransaction({ source_description: 'First' }),
|
|
361
|
+
createTransaction({ source_description: null }),
|
|
362
|
+
createTransaction({ source_description: 'Third' }),
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
const result = aggregateTransactions(transactions);
|
|
366
|
+
|
|
367
|
+
expect(result.description).toBe('First\nThird');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('should filter out empty string descriptions', () => {
|
|
371
|
+
const transactions = [
|
|
372
|
+
createTransaction({ source_description: 'First' }),
|
|
373
|
+
createTransaction({ source_description: ' ' }),
|
|
374
|
+
createTransaction({ source_description: 'Third' }),
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
const result = aggregateTransactions(transactions);
|
|
378
|
+
|
|
379
|
+
expect(result.description).toBe('First\nThird');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should return empty string when all descriptions are null', () => {
|
|
383
|
+
const transactions = [
|
|
384
|
+
createTransaction({ source_description: null }),
|
|
385
|
+
createTransaction({ source_description: null }),
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
const result = aggregateTransactions(transactions);
|
|
389
|
+
|
|
390
|
+
expect(result.description).toBe('');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should preserve order of descriptions', () => {
|
|
394
|
+
const transactions = [
|
|
395
|
+
createTransaction({ source_description: 'Alpha' }),
|
|
396
|
+
createTransaction({ source_description: 'Beta' }),
|
|
397
|
+
createTransaction({ source_description: 'Gamma' }),
|
|
398
|
+
];
|
|
399
|
+
|
|
400
|
+
const result = aggregateTransactions(transactions);
|
|
401
|
+
|
|
402
|
+
expect(result.description).toBe('Alpha\nBeta\nGamma');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should handle special characters in descriptions', () => {
|
|
406
|
+
const transactions = [
|
|
407
|
+
createTransaction({ source_description: 'Payment: $100 USD' }),
|
|
408
|
+
createTransaction({ source_description: 'Ref #12345 (invoice)' }),
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
const result = aggregateTransactions(transactions);
|
|
412
|
+
|
|
413
|
+
expect(result.description).toBe('Payment: $100 USD\nRef #12345 (invoice)');
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
describe('Input Validation', () => {
|
|
418
|
+
it('should throw error for empty array', () => {
|
|
419
|
+
expect(() => aggregateTransactions([])).toThrow(
|
|
420
|
+
'Cannot aggregate transactions: array is empty',
|
|
421
|
+
);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should throw error for null input', () => {
|
|
425
|
+
expect(() => aggregateTransactions(null as any)).toThrow(
|
|
426
|
+
'Cannot aggregate transactions: array is empty',
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should throw error for undefined input', () => {
|
|
431
|
+
expect(() => aggregateTransactions(undefined as any)).toThrow(
|
|
432
|
+
'Cannot aggregate transactions: array is empty',
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('Complex Scenarios', () => {
|
|
438
|
+
it('should handle real-world scenario with fees, nulls, and mixed data', () => {
|
|
439
|
+
const transactions = [
|
|
440
|
+
createTransaction({
|
|
441
|
+
amount: "1000",
|
|
442
|
+
currency: 'USD',
|
|
443
|
+
business_id: 'client-abc',
|
|
444
|
+
event_date: new Date('2024-01-15'),
|
|
445
|
+
source_description: 'Invoice #123 payment',
|
|
446
|
+
is_fee: false,
|
|
447
|
+
}),
|
|
448
|
+
createTransaction({
|
|
449
|
+
amount: "2.5",
|
|
450
|
+
currency: 'USD',
|
|
451
|
+
business_id: 'bank-xyz',
|
|
452
|
+
event_date: new Date('2024-01-15'),
|
|
453
|
+
source_description: 'Wire transfer fee',
|
|
454
|
+
is_fee: true, // Should be excluded
|
|
455
|
+
}),
|
|
456
|
+
createTransaction({
|
|
457
|
+
amount: "500",
|
|
458
|
+
currency: 'USD',
|
|
459
|
+
business_id: 'client-abc',
|
|
460
|
+
event_date: new Date('2024-01-10'), // Earlier date
|
|
461
|
+
source_description: 'Partial payment',
|
|
462
|
+
is_fee: false,
|
|
463
|
+
}),
|
|
464
|
+
createTransaction({
|
|
465
|
+
amount: "200",
|
|
466
|
+
currency: 'USD',
|
|
467
|
+
business_id: null, // Null business
|
|
468
|
+
event_date: new Date('2024-01-20'),
|
|
469
|
+
source_description: null, // Null description
|
|
470
|
+
is_fee: false,
|
|
471
|
+
}),
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
const result = aggregateTransactions(transactions);
|
|
475
|
+
|
|
476
|
+
expect(result.amount).toBe(1700); // 1000 + 500 + 200 (fee excluded)
|
|
477
|
+
expect(result.currency).toBe('USD');
|
|
478
|
+
expect(result.businessId).toBe('client-abc'); // One non-null, one null
|
|
479
|
+
expect(result.date).toEqual(new Date('2024-01-10')); // Earliest
|
|
480
|
+
expect(result.description).toBe('Invoice #123 payment\nPartial payment'); // Fee desc excluded, null excluded
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should handle cryptocurrency transactions', () => {
|
|
484
|
+
const transactions = [
|
|
485
|
+
createTransaction({ amount: "0.5", currency: 'ETH' }),
|
|
486
|
+
createTransaction({ amount: "0.3", currency: 'ETH' }),
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
const result = aggregateTransactions(transactions);
|
|
490
|
+
|
|
491
|
+
expect(result.amount).toBe(0.8);
|
|
492
|
+
expect(result.currency).toBe('ETH');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should handle large number of transactions', () => {
|
|
496
|
+
const transactions = Array.from({ length: 100 }, (_, i) =>
|
|
497
|
+
createTransaction({
|
|
498
|
+
amount: "10",
|
|
499
|
+
currency: 'USD',
|
|
500
|
+
event_date: new Date(`2024-01-${(i % 28) + 1}`),
|
|
501
|
+
}),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const result = aggregateTransactions(transactions);
|
|
505
|
+
|
|
506
|
+
expect(result.amount).toBe(1000); // 100 × 10
|
|
507
|
+
expect(result.currency).toBe('USD');
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe('Error Messages', () => {
|
|
512
|
+
it('should provide descriptive error for mixed currencies', () => {
|
|
513
|
+
const transactions = [
|
|
514
|
+
createTransaction({ currency: 'USD' }),
|
|
515
|
+
createTransaction({ currency: 'EUR' }),
|
|
516
|
+
createTransaction({ currency: 'GBP' }),
|
|
517
|
+
];
|
|
518
|
+
|
|
519
|
+
expect(() => aggregateTransactions(transactions)).toThrow(
|
|
520
|
+
/multiple currencies found \(USD, EUR, GBP\)/,
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should provide descriptive error for multiple business IDs', () => {
|
|
525
|
+
const transactions = [
|
|
526
|
+
createTransaction({ business_id: 'business-a' }),
|
|
527
|
+
createTransaction({ business_id: 'business-b' }),
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
expect(() => aggregateTransactions(transactions)).toThrow(
|
|
531
|
+
/multiple business IDs found \(business-a, business-b\)/,
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should provide descriptive error for all fees', () => {
|
|
536
|
+
const transactions = [createTransaction({ is_fee: true })];
|
|
537
|
+
|
|
538
|
+
expect(() => aggregateTransactions(transactions)).toThrow(
|
|
539
|
+
'all transactions are marked as fees',
|
|
540
|
+
);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('should provide descriptive error for empty array', () => {
|
|
544
|
+
expect(() => aggregateTransactions([])).toThrow('array is empty');
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
});
|