@accounter/server 0.0.8-alpha-20251104081410-ad15de052a742b1353c38e22ebbcae3e9e0b4d90 → 0.0.8-alpha-20251104115901-2a484be84dde31c9a4be251038269ba4823b6dc9

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 (35) hide show
  1. package/CHANGELOG.md +27 -5
  2. package/dist/green-invoice-graphql/src/mesh-artifacts/index.d.ts +5 -5
  3. package/dist/green-invoice-graphql/src/mesh-artifacts/sources/GreenInvoice/schemaWithAnnotations.js +3 -3
  4. package/dist/green-invoice-graphql/src/mesh-artifacts/sources/GreenInvoice/schemaWithAnnotations.js.map +1 -1
  5. package/dist/green-invoice-graphql/src/mesh-artifacts/sources/GreenInvoice/types.d.ts +3 -3
  6. package/dist/server/src/__generated__/types.d.ts +11 -4
  7. package/dist/server/src/__generated__/types.js.map +1 -1
  8. package/dist/server/src/modules/app-providers/green-invoice-client.d.ts +1 -1
  9. package/dist/server/src/modules/charges-matcher/__generated__/types.d.ts +3 -1
  10. package/dist/server/src/modules/charges-matcher/__generated__/types.js.map +1 -1
  11. package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js +0 -1
  12. package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js.map +1 -1
  13. package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.d.ts +2 -2
  14. package/dist/server/src/modules/charges-matcher/__tests__/test-helpers.js.map +1 -1
  15. package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js +10 -1
  16. package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js.map +1 -1
  17. package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js +22 -10
  18. package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js.map +1 -1
  19. package/dist/server/src/modules/charges-matcher/resolvers/index.js +4 -0
  20. package/dist/server/src/modules/charges-matcher/resolvers/index.js.map +1 -1
  21. package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.js +1 -0
  22. package/dist/server/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.js.map +1 -1
  23. package/dist/server/src/modules/charges-matcher/types.d.ts +5 -3
  24. package/dist/server/src/modules/charges-matcher/types.js +1 -1
  25. package/dist/server/src/modules/charges-matcher/types.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/__generated__/types.ts +7 -4
  28. package/src/modules/charges-matcher/__generated__/types.ts +3 -1
  29. package/src/modules/charges-matcher/__tests__/auto-match-integration.test.ts +0 -1
  30. package/src/modules/charges-matcher/__tests__/test-helpers.ts +3 -3
  31. package/src/modules/charges-matcher/providers/auto-match.provider.ts +11 -1
  32. package/src/modules/charges-matcher/providers/charges-matcher.provider.ts +63 -46
  33. package/src/modules/charges-matcher/resolvers/index.ts +5 -0
  34. package/src/modules/charges-matcher/typeDefs/charges-matcher.graphql.ts +1 -0
  35. package/src/modules/charges-matcher/types.ts +6 -4
@@ -5,7 +5,7 @@ export namespace ChargesMatcherModule {
5
5
  Query: 'findChargeMatches';
6
6
  Mutation: 'autoMatchCharges';
7
7
  ChargeMatchesResult: 'matches';
8
- ChargeMatch: 'chargeId' | 'confidenceScore';
8
+ ChargeMatch: 'chargeId' | 'charge' | 'confidenceScore';
9
9
  AutoMatchChargesResult: 'totalMatches' | 'mergedCharges' | 'skippedCharges' | 'errors';
10
10
  MergedCharge: 'chargeId' | 'confidenceScore';
11
11
  };
@@ -16,6 +16,7 @@ export namespace ChargesMatcherModule {
16
16
  export type Mutation = Pick<Types.Mutation, DefinedFields['Mutation']>;
17
17
  export type AutoMatchChargesResult = Pick<Types.AutoMatchChargesResult, DefinedFields['AutoMatchChargesResult']>;
18
18
  export type ChargeMatch = Pick<Types.ChargeMatch, DefinedFields['ChargeMatch']>;
19
+ export type Charge = Types.Charge;
19
20
  export type MergedCharge = Pick<Types.MergedCharge, DefinedFields['MergedCharge']>;
20
21
 
21
22
  export type QueryResolvers = Pick<Types.QueryResolvers, DefinedFields['Query']>;
@@ -53,6 +54,7 @@ export namespace ChargesMatcherModule {
53
54
  ChargeMatch?: {
54
55
  '*'?: gm.Middleware[];
55
56
  chargeId?: gm.Middleware[];
57
+ charge?: gm.Middleware[];
56
58
  confidenceScore?: gm.Middleware[];
57
59
  };
58
60
  AutoMatchChargesResult?: {
@@ -40,7 +40,6 @@ type Injector = {
40
40
 
41
41
  // Test constants
42
42
  const ADMIN_BUSINESS_ID = 'user-123';
43
- const BUSINESS_A = 'business-a';
44
43
 
45
44
  // Helper to create charge
46
45
  function createCharge(id: string, ownerId: string) {
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  Transaction,
3
3
  Document,
4
- ChargeMatch,
4
+ ChargeMatchProto,
5
5
  AggregatedTransaction,
6
6
  AggregatedDocument,
7
7
  ConfidenceScores,
@@ -125,8 +125,8 @@ export function createMockConfidenceScores(
125
125
  /**
126
126
  * Factory for creating mock charge matches
127
127
  */
128
- export function createMockChargeMatch(overrides: Partial<ChargeMatch> = {}): ChargeMatch {
129
- const defaultMatch: ChargeMatch = {
128
+ export function createMockChargeMatch(overrides: Partial<ChargeMatchProto> = {}): ChargeMatchProto {
129
+ const defaultMatch: ChargeMatchProto = {
130
130
  chargeId: '00000000-0000-0000-0000-000000000099',
131
131
  confidenceScore: 0.95,
132
132
  };
@@ -146,8 +146,10 @@ export function determineMergeDirection(
146
146
  charge1: ChargeWithData,
147
147
  charge2: ChargeWithData,
148
148
  ): [ChargeWithData, ChargeWithData] {
149
+ const charge1HasDescription = charge1.description && charge1.description.length > 0;
149
150
  const charge1HasTransactions = charge1.transactions && charge1.transactions.length > 0;
150
151
  const charge1HasDocuments = charge1.documents && charge1.documents.length > 0;
152
+ const charge2HasDescription = charge2.description && charge2.description.length > 0;
151
153
  const charge2HasTransactions = charge2.transactions && charge2.transactions.length > 0;
152
154
  const charge2HasDocuments = charge2.documents && charge2.documents.length > 0;
153
155
 
@@ -162,7 +164,15 @@ export function determineMergeDirection(
162
164
  return [charge1, charge2]; // Merge charge1 INTO charge2 (keep charge2)
163
165
  }
164
166
 
165
- // Rule 2: Both unmatched - keep the one with transactions
167
+ // Rule 2: Both unmatched - keep the one with description
168
+ if (charge1HasDescription && !charge2HasDescription) {
169
+ return [charge2, charge1]; // Merge charge2 INTO charge1 (keep description charge)
170
+ }
171
+ if (charge2HasDescription && !charge1HasDescription) {
172
+ return [charge1, charge2]; // Merge charge1 INTO charge2 (keep description charge)
173
+ }
174
+
175
+ // Rule 3: Neither has description or both have description - keep the one with transactions
166
176
  if (charge1HasTransactions && !charge2HasTransactions) {
167
177
  return [charge2, charge1]; // Merge charge2 INTO charge1 (keep transaction charge)
168
178
  }
@@ -5,6 +5,7 @@
5
5
  * Integrates with existing modules: charges, transactions, and documents.
6
6
  */
7
7
 
8
+ import { subYears } from 'date-fns';
8
9
  import { Injectable, Scope } from 'graphql-modules';
9
10
  import { mergeChargesExecutor } from '@modules/charges/helpers/merge-charges.hepler.js';
10
11
  import { ChargesProvider } from '@modules/charges/providers/charges.provider.js';
@@ -116,36 +117,42 @@ export class ChargesMatcherProvider {
116
117
  // Step 6: Load transactions and documents for all candidate charges
117
118
  const candidateChargesWithData: Array<TransactionCharge | DocumentCharge> = [];
118
119
 
119
- for (const candidate of candidateCharges) {
120
- // Skip the source charge itself
121
- if (candidate.id === chargeId) {
122
- continue;
123
- }
120
+ await Promise.all(
121
+ candidateCharges.map(async candidate => {
122
+ // Skip the source charge itself
123
+ if (candidate.id === chargeId) {
124
+ return;
125
+ }
124
126
 
125
- const candidateTransactions = (await transactionsProvider.transactionsByChargeIDLoader.load(
126
- candidate.id,
127
- )) as Transaction[];
128
- const candidateDocuments = (await documentsProvider.getDocumentsByChargeIdLoader.load(
129
- candidate.id,
130
- )) as Document[];
131
-
132
- const hasTxs = candidateTransactions && candidateTransactions.length > 0;
133
- const hasDocs = candidateDocuments && candidateDocuments.length > 0;
134
-
135
- // Only include unmatched charges (not both types)
136
- if (hasTxs && !hasDocs) {
137
- candidateChargesWithData.push({
138
- chargeId: candidate.id,
139
- transactions: candidateTransactions,
140
- });
141
- } else if (hasDocs && !hasTxs) {
142
- candidateChargesWithData.push({
143
- chargeId: candidate.id,
144
- documents: candidateDocuments,
145
- });
146
- }
147
- // Skip matched charges (have both) and empty charges (have neither)
148
- }
127
+ const candidateTransactionsPromise = transactionsProvider.transactionsByChargeIDLoader.load(
128
+ candidate.id,
129
+ ) as Promise<Transaction[]>;
130
+ const candidateDocumentsPromise = documentsProvider.getDocumentsByChargeIdLoader.load(
131
+ candidate.id,
132
+ ) as Promise<Document[]>;
133
+ const [candidateTransactions, candidateDocuments] = await Promise.all([
134
+ candidateTransactionsPromise,
135
+ candidateDocumentsPromise,
136
+ ]);
137
+
138
+ const hasTxs = candidateTransactions && candidateTransactions.length > 0;
139
+ const hasDocs = candidateDocuments && candidateDocuments.length > 0;
140
+
141
+ // Only include unmatched charges (not both types)
142
+ if (hasTxs && !hasDocs) {
143
+ candidateChargesWithData.push({
144
+ chargeId: candidate.id,
145
+ transactions: candidateTransactions,
146
+ });
147
+ } else if (hasDocs && !hasTxs) {
148
+ candidateChargesWithData.push({
149
+ chargeId: candidate.id,
150
+ documents: candidateDocuments,
151
+ });
152
+ }
153
+ // Skip matched charges (have both) and empty charges (have neither)
154
+ }),
155
+ );
149
156
 
150
157
  // Step 7: Build source charge object for findMatches
151
158
  let sourceChargeData: TransactionCharge | DocumentCharge;
@@ -208,30 +215,40 @@ export class ChargesMatcherProvider {
208
215
  const documentsProvider = injector.get(DocumentsProvider);
209
216
 
210
217
  // Step 1: Load all charges for this user
218
+ const prevYear = dateToTimelessDateString(subYears(new Date(), 1));
211
219
  const allCharges = await chargesProvider.getChargesByFilters({
212
220
  ownerIds: [adminBusinessId],
221
+ fromAnyDate: prevYear,
213
222
  });
214
223
 
215
224
  // Step 2: Load transactions and documents for all charges
216
225
  const chargesWithData: ChargeWithData[] = [];
217
226
  const mergedChargeIds = new Set<string>(); // Track merged charges to exclude from processing
218
227
 
219
- for (const charge of allCharges) {
220
- const transactions = (await transactionsProvider.transactionsByChargeIDLoader.load(
221
- charge.id,
222
- )) as Transaction[];
223
- const documents = (await documentsProvider.getDocumentsByChargeIdLoader.load(
224
- charge.id,
225
- )) as Document[];
226
-
227
- chargesWithData.push({
228
- chargeId: charge.id,
229
- ownerId: charge.owner_id ?? adminBusinessId,
230
- type: ChargeType.TRANSACTION_ONLY, // Will be determined by processChargeForAutoMatch
231
- transactions: transactions || [],
232
- documents: documents || [],
233
- });
234
- }
228
+ await Promise.all(
229
+ allCharges.map(async charge => {
230
+ const transactionsPromise = transactionsProvider.transactionsByChargeIDLoader.load(
231
+ charge.id,
232
+ ) as Promise<Transaction[]>;
233
+ const documentsPromise = documentsProvider.getDocumentsByChargeIdLoader.load(
234
+ charge.id,
235
+ ) as Promise<Document[]>;
236
+
237
+ const [transactions, documents] = await Promise.all([
238
+ transactionsPromise,
239
+ documentsPromise,
240
+ ]);
241
+
242
+ chargesWithData.push({
243
+ chargeId: charge.id,
244
+ ownerId: charge.owner_id ?? adminBusinessId,
245
+ type: ChargeType.TRANSACTION_ONLY, // Will be determined by processChargeForAutoMatch
246
+ description: charge.user_description ?? undefined,
247
+ transactions: transactions || [],
248
+ documents: documents || [],
249
+ });
250
+ }),
251
+ );
235
252
 
236
253
  // Step 3: Filter to get only unmatched charges
237
254
  const unmatchedCharges = chargesWithData.filter(charge => {
@@ -288,7 +305,7 @@ export class ChargesMatcherProvider {
288
305
  // Track successful merge
289
306
  result.totalMatches++;
290
307
  result.mergedCharges.push({
291
- chargeId: sourceToMerge.chargeId,
308
+ chargeId: targetToKeep.chargeId,
292
309
  confidenceScore: processResult.match.confidenceScore,
293
310
  });
294
311
 
@@ -1,3 +1,4 @@
1
+ import { ChargesProvider } from '@modules/charges/providers/charges.provider.js';
1
2
  import type { ChargesMatcherModule } from '../types.js';
2
3
  import { autoMatchChargesResolver } from './auto-match-charges.resolver.js';
3
4
  import { findChargeMatchesResolver } from './find-charge-matches.resolver.js';
@@ -9,4 +10,8 @@ export const chargesMatcherResolvers: ChargesMatcherModule.Resolvers = {
9
10
  Mutation: {
10
11
  ...autoMatchChargesResolver.Mutation,
11
12
  },
13
+ ChargeMatch: {
14
+ charge: async ({ chargeId }, _args, { injector }) =>
15
+ injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId),
16
+ },
12
17
  };
@@ -21,6 +21,7 @@ export default gql`
21
21
  type ChargeMatch {
22
22
  " UUID of the matched charge "
23
23
  chargeId: UUID!
24
+ charge: Charge!
24
25
  " Confidence score between 0.00 and 1.00 "
25
26
  confidenceScore: Float!
26
27
  }
@@ -1,8 +1,6 @@
1
1
  import type { currency, document_type, IGetAllDocumentsResult } from '@modules/documents/types.js';
2
2
  import type { IGetTransactionsByIdsResult } from '@modules/transactions/types.js';
3
3
 
4
- export * from './__generated__/types.js';
5
-
6
4
  /**
7
5
  * Re-export shared types from other modules
8
6
  */
@@ -26,7 +24,7 @@ export type Document = IGetAllDocumentsResult;
26
24
  /**
27
25
  * Represents a single charge match with its confidence score
28
26
  */
29
- export interface ChargeMatch {
27
+ export interface ChargeMatchProto {
30
28
  /** UUID of the matched charge */
31
29
  chargeId: string;
32
30
  /** Confidence score between 0.00 and 1.00 (two decimal precision) */
@@ -38,7 +36,7 @@ export interface ChargeMatch {
38
36
  */
39
37
  export interface ChargeMatchesResult {
40
38
  /** Array of up to 5 matches, ordered by confidence score (highest first) */
41
- matches: ChargeMatch[];
39
+ matches: ChargeMatchProto[];
42
40
  }
43
41
 
44
42
  /**
@@ -149,6 +147,8 @@ export interface ChargeWithData {
149
147
  ownerId: string;
150
148
  /** Charge classification */
151
149
  type: ChargeType;
150
+ /** Charge description */
151
+ description?: string;
152
152
  /** Associated transactions (if any) */
153
153
  transactions: Transaction[];
154
154
  /** Associated documents (if any) */
@@ -198,3 +198,5 @@ export interface DocumentCharge {
198
198
  /** Array of documents in the charge */
199
199
  documents: Document[];
200
200
  }
201
+
202
+ export * from './__generated__/types.js';