@accounter/server
Version:
Accounter GraphQL server
171 lines • 9.68 kB
JavaScript
import { GraphQLError } from 'graphql';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { mergeChargesExecutor } from '../../charges/helpers/merge-charges.helper.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { CornJobsProvider } from '../providers/corn-jobs.provider.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 = {
Mutation: {
mergeChargesByTransactionReference: async (_, __, { injector }) => {
try {
const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const candidates = await injector.get(CornJobsProvider).getReferenceMergeCandidates({
ownerId,
});
const chargeIds = new Set(candidates.map(candidate => candidate.charge_id));
const charges = await injector
.get(ChargesProvider)
.getChargeByIdLoader.loadMany(Array.from(chargeIds))
.then(res => res.filter(charge => charge && !(charge instanceof Error)));
const referenceMap = new Map();
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 = {};
Array.from(referenceMap.values()).map(candidates => {
const candidatesIDs = new Set(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_description &&
internatTransaction.source_description.length >= 5 &&
transaction.source_description &&
transaction.source_description.length >= 5;
const isMatchingDetails = areDetailsSufficient &&
(internatTransaction.source_description?.includes(transaction.source_description) ||
internatTransaction.source_description?.includes(transaction.source_description));
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();
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),
};
}
catch (e) {
if (e instanceof GraphQLError) {
throw e;
}
return {
success: false,
errors: [e?.message ?? 'Unknown error'],
};
}
},
flagForeignFeeTransactions: async (_, __, { injector }) => {
try {
const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const res = await injector.get(CornJobsProvider).flagForeignFeeTransactions({ ownerId });
const updatedTransactionsIds = res.map(({ id }) => id);
return {
success: true,
transactions: updatedTransactionsIds,
};
}
catch (e) {
return {
success: false,
errors: [e?.message ?? 'Unknown error'],
};
}
},
calculateCreditcardTransactionsDebitDate: async (_, __, { injector }) => {
try {
const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
await injector.get(CornJobsProvider).calculateCreditcardDebitDate({ ownerId });
return true;
}
catch (e) {
console.error(e);
throw new GraphQLError('Failed to calculate creditcard transactions debit date');
}
},
},
};
function logMatch(main, others) {
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);
}
}
}
//# sourceMappingURL=corn-jobs.resolver.js.map