@accounter/server
Version:
Accounter GraphQL server
452 lines • 17.5 kB
JavaScript
import { GraphQLError } from 'graphql';
import { formatAmount } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { suggestionDataSchema } from '../../financial-entities/helpers/business-suggestion-data-schema.helper.js';
import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js';
import { FinancialEntitiesProvider } from '../../financial-entities/providers/financial-entities.provider.js';
import { TransactionsProvider } from '../providers/transactions.provider.js';
function sortPhrasesByPriority(a, b) {
return b[1].priority - a[1].priority;
}
const missingInfoSuggestions = async (transactionId, _, { injector }, __) => {
const adminContext = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const { poalimBusinessId, etherScanBusinessId, krakenBusinessId, etanaBusinessId, isracardBusinessId, otsarHahayalBusinessId, otsarHahayalCreditCardBusinessId, } = adminContext.financialAccounts;
const transaction = await injector
.get(TransactionsProvider)
.transactionByIdLoader.load(transactionId);
if (transaction.business_id) {
return null;
}
if (transaction.is_fee) {
if (transaction.source_description?.includes('Swift')) {
const { swiftBusinessId } = adminContext.financialAccounts;
if (!swiftBusinessId) {
throw new Error('Swift business is not set');
}
return {
business: swiftBusinessId,
};
}
switch (transaction.source_origin) {
case 'ETANA': {
if (!etanaBusinessId) {
throw new Error('Etana business is not set');
}
return {
business: etanaBusinessId,
};
}
case 'ETHERSCAN': {
if (!etherScanBusinessId) {
throw new Error('EtherScan business is not set');
}
return {
business: etherScanBusinessId,
};
}
case 'KRAKEN': {
if (!krakenBusinessId) {
throw new Error('Kraken business is not set');
}
return {
business: krakenBusinessId,
};
}
case 'POALIM': {
if (!poalimBusinessId) {
throw new Error('Poalim business is not set');
}
return {
business: poalimBusinessId,
};
}
case 'ISRACARD': {
if (!isracardBusinessId) {
throw new Error('Isracard business is not set');
}
return {
business: isracardBusinessId,
};
}
case 'OTSAR_HAHAYAL': {
if (!otsarHahayalBusinessId) {
throw new Error('Otsar Hahayal business is not set');
}
return {
business: otsarHahayalBusinessId,
};
}
case 'OTSAR_HAHAYAL_CREDIT_CARD': {
if (!otsarHahayalCreditCardBusinessId) {
throw new Error('Otsar Hahayal Credit Card business is not set');
}
return {
business: otsarHahayalCreditCardBusinessId,
};
}
}
}
if (transaction.business_id) {
const business = await injector
.get(BusinessesProvider)
.getBusinessByIdLoader.load(transaction.business_id);
if (business?.suggestion_data) {
return {
business: business.id,
};
}
}
const description = transaction.source_description?.trim() ?? '';
const businesses = await injector
.get(BusinessesProvider)
.getAllBusinesses()
.then(businesses => businesses.filter(business => business.suggestion_data));
const suggestions = {};
for (const business of businesses) {
const { data: suggestionData, success } = suggestionDataSchema.safeParse(business.suggestion_data);
if (!success)
continue;
if (!suggestionData.phrases)
continue;
for (const phrase of suggestionData.phrases) {
suggestions[phrase] = {
business: business.id,
priority: suggestionData.priority ?? 0,
};
}
}
for (const [phrase, suggestion] of Object.entries(suggestions).sort(sortPhrasesByPriority)) {
if (Array.isArray(phrase) && new RegExp(phrase.join('|')).test(description)) {
return suggestion;
}
if (description.includes(phrase)) {
return suggestion;
}
}
switch (transaction.source_origin) {
case 'ETANA': {
if (!etanaBusinessId) {
throw new Error('Etana business is not set');
}
if (transaction.is_fee || /\bfee\b/.test(description.toLowerCase())) {
return {
business: etanaBusinessId,
};
}
const amount = formatAmount(transaction.amount);
if (poalimBusinessId && amount < 0) {
return {
business: poalimBusinessId,
};
}
if (krakenBusinessId && amount > 0) {
return {
business: krakenBusinessId,
};
}
break;
}
case 'ETHERSCAN': {
if (!etherScanBusinessId) {
throw new Error('EtherScan business is not set');
}
if (transaction.is_fee || /\bfee\b/.test(description.toLowerCase())) {
return {
business: etherScanBusinessId,
};
}
const amount = formatAmount(transaction.amount);
if (krakenBusinessId && amount < 0) {
return {
business: krakenBusinessId,
};
}
if (amount > 0) {
return {
business: '73519067-c8fe-4073-aec6-608ff596f8a8', // name: 'The Graph Foundation'
};
}
break;
}
case 'KRAKEN': {
if (!krakenBusinessId) {
throw new Error('Kraken business is not set');
}
if (transaction.is_fee ||
/\bfee\b/.test(description.toLowerCase()) ||
/\btrade\b/.test(description.toLowerCase())) {
return {
business: krakenBusinessId,
};
}
const amount = formatAmount(transaction.amount);
if (etanaBusinessId && amount < 0) {
return {
business: etanaBusinessId,
};
}
if (etherScanBusinessId && amount > 0) {
return {
business: etherScanBusinessId,
};
}
break;
}
}
if (poalimBusinessId &&
(description.includes('ע\' העברת מט"ח') ||
(description.includes('העברת מט"ח') && Math.abs(formatAmount(transaction.amount)) < 400) ||
(description.includes('מטח') && Math.abs(formatAmount(transaction.amount)) < 400) ||
description.includes('F.C.COM') ||
description.includes('ע.מפעולות-ישיר') ||
description.includes('ריבית חובה') ||
description.includes('FEE'))) {
return {
business: poalimBusinessId,
};
}
if (description.includes('דותן שמחה') || description.includes('שמחה דותן')) {
return {
business: 'ca9d301f-f6db-40a8-a02e-7cf4b63fa2df', //name: 'Dotan Employee',
};
}
if (description.includes('גולדשטין אורי')) {
return {
business: '7843b805-3bb7-4d1c-9219-ff783100334b', //name: 'Uri Employee',
};
}
if (description.includes('גרדוש')) {
return {
business: 'f4b591f3-d817-4e3d-9ecb-35b38d2df7ef', //name: 'Gil Employee',
};
}
if (description.includes('תובל')) {
return {
business: '4420accf-da13-43b0-9aaa-3b94758598e4', //name: 'Tuval Employee',
};
}
if (description.includes('מנורה מבטחים פנס')) {
return {
business: 'af386033-a577-4c9a-880a-d49acd15141d', //name: 'מנורה פנסיה',
};
}
if (description.includes('פניקס אקסלנס')) {
return {
business: '1453f8d1-adae-4761-851b-83799290b8d1', //name: 'פניקס השתלמות',
};
}
if (description.includes('מיטב דש גמל ופנס')) {
return {
business: '340c3552-0a15-4e22-ba03-19ae9322859c', //name: 'איילון פנסיה',
};
}
if (description.includes('מגדל מקפת')) {
return {
business: 'fc2ea992-a2be-4f8a-a639-542a81276beb', //name: 'מגדל פנסיה',
};
}
if (description.includes('מגדל השתלמות')) {
return {
business: '2697dae8-cbd5-4669-8e80-d0964e5c077e', //name: 'מגדל השתלמות',
};
}
if (description.includes('ביטוח לאומי')) {
return {
business: '6d4b01dd-5a5e-4a43-8e40-e9dadfcc10fa', //name: 'Social Security Deductions',
};
}
if (description.includes('LANCE GLOBAL')) {
return {
business: '9c9f3979-08b1-453c-832a-d5eba2bba79a', //name: 'Lance Global Inc',
};
}
if ((description.includes('העברת מט"ח') &&
(description.includes('fbv') || description.includes('fv'))) ||
description.includes('kamil kisiela')) {
return {
business: 'f1749353-979b-46df-8931-93a3aafab1f4', //name: 'Jelly JS Kamil Kisiela',
};
}
if (description.includes('מס הכנסה')) {
const flag = description.includes('מס הכנסה ני');
return {
business: flag
? 'f1ade516-4999-4919-9d94-6b013221536d' //name: 'Tax Deductions',
: '9d3a8a88-6958-4119-b509-d50a7cdc0744', //name: 'Tax',
};
}
if (description.includes('המכס ומעמ-גביי תשלום') || description.includes('CUSTOM + V.A.T')) {
return {
business: 'c7fdf6f6-e075-44ee-b251-cbefea366826', //name: 'VAT',
};
}
if (description.includes('חניון')) {
return {
business: description.includes('אחוזות החוף')
? 'b51aeb4d-48ed-43e5-84a2-f12b6905fd51' //name: 'Ahuzot',
: 'ed7b54f1-f695-4bc2-b8af-887e3115bfcc', //name: 'Parking',
};
}
if (description.includes('deel')) {
return {
business: '8d34f668-7233-4ce3-9c9c-82550b0839ff', //name: 'Deel Germany GmbH',
};
}
if (description.includes('GITHUB')) {
const suggested = {
business: 'af23ab30-5cf9-4433-abe1-14ae70ab64d7', //name: 'GitHub, Inc',
};
return suggested;
}
if (formatAmount(transaction.amount) === -4329) {
return {
business: '8069311d-314e-4d1a-8f76-629757070ca0', //name: 'Avi Peretz',
};
}
if (description.includes('APPLE COM BILL/ITUNES.COM')) {
return {
business: '6346872a-708d-4910-9428-72019b053ea5', //name: 'Apple',
};
}
if (poalimBusinessId &&
(description.includes('ע\' העברת מט"ח') ||
(description.includes('העברת מט"ח') && Math.abs(formatAmount(transaction.amount)) < 400) ||
(description.includes('מטח') && Math.abs(formatAmount(transaction.amount)) < 400) ||
description.includes('F.C.COM') ||
description.includes('ע.מפעולות-ישיר') ||
description.includes('ריבית חובה') ||
description.includes('FEE'))) {
return {
business: poalimBusinessId,
};
}
if (poalimBusinessId && description.includes('ריבית זכות')) {
return {
business: poalimBusinessId,
};
}
if (poalimBusinessId && description.includes('פועלים- דמי כרטיס')) {
return {
business: poalimBusinessId,
};
}
if (description.includes('אריה קריסטל')) {
return {
business: '245e149b-b328-471f-9e11-bdaa10dc5fb7', //name: 'Arye Kristal',
};
}
if (description.includes('aleksandra')) {
return {
business: 'b001b503-93ce-497c-bb6c-1c5f1eb6b776', //name: 'ALEKSANDRA MONWID-OLECHNOWICZ'
};
}
if (description.includes('denelop')) {
const current = new Date();
current.setMonth(current.getMonth() - 1);
return {
business: '737df651-8e02-40dd-b749-228bad16f279',
};
}
if (description.includes('slava')) {
return {
business: '74c8b843-3819-44a0-abc6-730d4d829e9f', // SLAVA UKRAINI
};
}
if (description.includes('COURIER PLUS INC')) {
return {
business: '74c8a4e0-ea3b-4906-a6b3-a868ee6d700d', // name: 'Courier Plus Inc DBA Dutchie'
};
}
if (description.includes('GOBRANDS')) {
return {
business: '011dac15-1a48-447e-91f0-ef4344137caa', // name: 'GoBrands Inc'
};
}
if (description.includes('MEDIC FIRST AI')) {
return {
business: 'dcb28428-6ba4-4ee3-94a9-ebd188c82822', // name: 'HSI Workplace Compliance Solutions, Inc'
};
}
if (formatAmount(transaction.amount) === -12_000) {
const current = new Date();
current.setMonth(current.getMonth() - 1);
return {
business: 'd140fae3-f841-464c-84a7-c526c0123f36', //name: 'Saihajpreet Singh',
};
}
if (formatAmount(transaction.amount) === -600) {
return {
business: '02d467c9-0818-45e3-9a25-9f99d0101a9e', //name: 'ZAUM',
};
}
return null;
};
function missingInfoSuggestionsWrapper(...args) {
return missingInfoSuggestions(...args).then(res => {
if (res && 'business' in res) {
return {
...res,
business: args[2].injector
.get(FinancialEntitiesProvider)
.getFinancialEntityByIdLoader.load(res.business),
};
}
return res;
});
}
export const transactionSuggestionsResolvers = {
Query: {
similarTransactions: async (_, { transactionId, withMissingInfo = false }, { injector }) => {
if (!transactionId?.trim()) {
throw new GraphQLError('Transaction ID is required');
}
const mainTransactionPromise = injector
.get(TransactionsProvider)
.transactionByIdLoader.load(transactionId)
.catch(e => {
console.error('Error fetching transaction', { transactionId, error: e });
throw new GraphQLError('Error fetching transaction');
});
const chargePromise = injector
.get(ChargesProvider)
.getChargeByTransactionIdLoader.load(transactionId)
.catch(e => {
console.error('Error fetching charge', { transactionId, error: e });
throw new GraphQLError('Error fetching charge');
});
const [mainTransaction, charge] = await Promise.all([mainTransactionPromise, chargePromise]);
if (!mainTransaction) {
throw new GraphQLError(`Transaction not found: ${transactionId}`);
}
if (!charge) {
throw new GraphQLError(`Charge not found for transaction: ${transactionId}`);
}
const similarTransactions = await injector
.get(TransactionsProvider)
.getSimilarTransactions({
details: mainTransaction.source_description,
counterAccount: mainTransaction.counter_account,
withMissingInfo,
ownerId: charge.owner_id,
})
.catch(e => {
console.error('Error fetching similar transactions:', {
transactionId,
details: mainTransaction.source_description,
counterAccount: mainTransaction.counter_account,
error: e.message,
});
throw new GraphQLError('Error fetching similar transactions');
});
return similarTransactions.map(txn => txn.id);
},
},
ConversionTransaction: {
missingInfoSuggestions: missingInfoSuggestionsWrapper,
},
CommonTransaction: {
missingInfoSuggestions: missingInfoSuggestionsWrapper,
},
};
//# sourceMappingURL=transaction-suggestions.resolver.js.map