UNPKG

@accounter/server

Version:

203 lines (181 loc) • 8.61 kB
import { GraphQLError } from 'graphql'; import { mergeChargesExecutor } from '@modules/charges/helpers/merge-charges.hepler.js'; import { ChargesProvider } from '@modules/charges/providers/charges.provider.js'; import type { IGetChargesByIdsResult } from '@modules/charges/types.js'; import { TransactionsProvider } from '@modules/transactions/providers/transactions.provider.js'; import type { IGetTransactionsByChargeIdsResult } from '@modules/transactions/types.js'; import { DEFAULT_FINANCIAL_ENTITY_ID } from '@shared/constants'; import { CornJobsProvider } from '../providers/corn-jobs.provider.js'; import type { CornJobsModule } from '../types.js'; const WIDE_DATE_DIFF_MILLISECONDS = 2_592_000_000; // 30 days const ACCEPTABLE_DATE_DIFF_MILLISECONDS = 86_400_000; // 1 days export const cornJobsResolvers: CornJobsModule.Resolvers = { Mutation: { mergeChargesByTransactionReference: async (_, __, { injector }) => { try { const candidates = (await injector.get(CornJobsProvider).getReferenceMergeCandidates({ ownerId: DEFAULT_FINANCIAL_ENTITY_ID, })) as IGetTransactionsByChargeIdsResult[]; const chargeIds = new Set<string>(candidates.map(candidate => candidate.charge_id!)); const charges = await injector .get(ChargesProvider) .getChargeByIdLoader.loadMany(Array.from(chargeIds)) .then(res => res.filter(charge => charge && 'id' in charge) as IGetChargesByIdsResult[]); const referenceMap = new Map< string, { transaction: IGetTransactionsByChargeIdsResult; charge: IGetChargesByIdsResult }[] >(); candidates.map(candidate => { const reference = candidate.source_reference; if (!reference) { throw new GraphQLError('reference is missing'); } const charge = charges.find(charge => charge.id === candidate.charge_id); if (!charge) { throw new GraphQLError(`Charge is missing for transaction ID=${candidate.id}`); } if (!referenceMap.has(reference)) { referenceMap.set(reference, []); } referenceMap.get(reference)?.push({ transaction: candidate, charge }); }); const mergableMathces: Record<string, string[]> = {}; Array.from(referenceMap.values()).map(candidates => { const candidatesIDs = new Set<string>( candidates.map(candidate => candidate.transaction.id!), ); for (const { transaction, charge } of candidates) { if (!transaction.id || !candidatesIDs.has(transaction.id) || !transaction.event_date) { continue; } candidatesIDs.delete(transaction.id); // filter candidates const fromDate = transaction.event_date.getTime() - ACCEPTABLE_DATE_DIFF_MILLISECONDS; const toDate = transaction.event_date.getTime() + ACCEPTABLE_DATE_DIFF_MILLISECONDS; const widelyFromDate = transaction.event_date.getTime() - WIDE_DATE_DIFF_MILLISECONDS; const widelyToDate = transaction.event_date.getTime() + WIDE_DATE_DIFF_MILLISECONDS; const matches = candidates.filter(({ transaction: internatTransaction }) => { if (!candidatesIDs.has(internatTransaction.id)) { return false; } const isWithinTimeRange = internatTransaction.event_date.getTime() >= fromDate && internatTransaction.event_date.getTime() <= toDate; // check if details match const isWidelyInTimeRange = internatTransaction.event_date.getTime() >= widelyFromDate && internatTransaction.event_date.getTime() <= widelyToDate; const areDetailsSufficient = isWidelyInTimeRange && internatTransaction.source_details && internatTransaction.source_details.length >= 5 && transaction.source_details && transaction.source_details.length >= 5; const isMatchingDetails = areDetailsSufficient && (internatTransaction.source_details?.includes(transaction.source_details!) || internatTransaction.source_details?.includes(transaction.source_details!)); return isWithinTimeRange || isMatchingDetails; }); if (matches.length === 0) { return; } matches.map(match => candidatesIDs.delete(match.transaction.id!)); matches.push({ transaction, charge }); // preparation for merge: rearrange based on charge const chargesWithTransactions = new Map< string, { transactions: IGetTransactionsByChargeIdsResult[]; charge: IGetChargesByIdsResult } >(); matches.map(({ transaction, charge }) => { if (!chargesWithTransactions.has(charge.id)) { chargesWithTransactions.set(charge.id, { transactions: [], charge }); } chargesWithTransactions.get(charge.id)?.transactions.push(transaction); }); if (chargesWithTransactions.size === 1) { return; } // figure which charge will be the main for merge const chargeMatches = Array.from(chargesWithTransactions.values()); const mainCandidates = chargeMatches.filter(({ charge, transactions }) => { const isFee = !transactions.some(transaction => !transaction.is_fee); const isUnlinked = !!charge.user_description?.includes('unlinked from charge'); return !isFee && !isUnlinked; }); if (mainCandidates.length === 0) { const main = chargeMatches.shift(); logMatch(main!, chargeMatches); mergableMathces[main!.charge.id] = chargeMatches.map(match => match.charge.id); return; } if (mainCandidates.length === 1) { const main = mainCandidates[0]; const chargesToMerge = chargeMatches.filter( match => match.charge.id !== main.charge.id, ); logMatch(main, chargesToMerge); mergableMathces[main.charge.id] = chargesToMerge.map(match => match.charge.id); return; } const main = chargeMatches.shift(); logMatch(main!, chargeMatches); console.log('not sure what to do now'); } }); // execute merges const chargeMergePromises = Object.entries(mergableMathces).map( async ([baseChargeID, chargeIdsToMerge]) => { await mergeChargesExecutor(chargeIdsToMerge, baseChargeID, injector); console.log(`Merged into charge ID=${baseChargeID} successfully`); }, ); await Promise.all(chargeMergePromises); return { success: true, charges: Object.keys(mergableMathces) .map(id => charges.find(charge => charge.id === id)) .filter(charge => charge) as IGetChargesByIdsResult[], }; } catch (e) { return { success: false, errors: [(e as Error)?.message ?? 'Unknown error'], }; } }, flagForeignFeeTransactions: async (_, __, { injector }) => { try { const updatedTransactionsId = await injector .get(CornJobsProvider) .flagForeignFeeTransactions({ ownerId: DEFAULT_FINANCIAL_ENTITY_ID }); const res = await injector .get(TransactionsProvider) .getTransactionByIdLoader.loadMany(updatedTransactionsId.map(({ id }) => id)); return { success: true, transactions: res.filter( transaction => transaction, ) as IGetTransactionsByChargeIdsResult[], }; } catch (e) { return { success: false, errors: [(e as Error)?.message ?? 'Unknown error'], }; } }, }, }; function logMatch( main: { transactions: IGetTransactionsByChargeIdsResult[]; charge: IGetChargesByIdsResult }, others: { transactions: IGetTransactionsByChargeIdsResult[]; charge: IGetChargesByIdsResult }[], ) { console.log('\n\n\n'); for (const { charge, transactions } of [main, ...others]) { console.log(charge.user_description); for (const transaction of transactions) { console.log(' ', transaction.source_description); } } }