UNPKG

@accounter/server

Version:
211 lines (185 loc) • 7.64 kB
import { GraphQLError } from 'graphql'; import type { Maybe, ResolverFn, ResolversParentTypes, ResolversTypes, } from '../../../../__generated__/types.js'; import type { Currency } from '../../../../shared/enums.js'; import type { LedgerProto, StrictLedgerProto } from '../../../../shared/types/index.js'; import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js'; import { getChargeBusinesses } from '../../../charges/helpers/common.helper.js'; import { ExchangeProvider } from '../../../exchange-rates/providers/exchange.provider.js'; import { FinancialAccountsProvider } from '../../../financial-accounts/providers/financial-accounts.provider.js'; import { TransactionsProvider } from '../../../transactions/providers/transactions.provider.js'; import { ledgerEntryFromMainTransaction } from '../../helpers/common-charge-ledger.helper.js'; import { splitFeeTransactions } from '../../helpers/fee-transactions.js'; import { storeInitialGeneratedRecords } from '../../helpers/ledgrer-storage.helper.js'; import { generateMiscExpensesLedger } from '../../helpers/misc-expenses-ledger.helper.js'; import { getFinancialAccountTaxCategoryId, getLedgerBalanceInfo, LedgerError, ledgerProtoToRecordsConverter, updateLedgerBalanceByEntry, validateTransactionBasicVariables, } from '../../helpers/utils.helper.js'; export const generateLedgerRecordsForForeignSecurities: ResolverFn< Maybe<ResolversTypes['GeneratedLedgerRecords']>, ResolversParentTypes['Charge'], GraphQLModules.Context, { insertLedgerRecordsIfNotExists: boolean } > = async (charge, { insertLedgerRecordsIfNotExists }, { injector }) => { const chargeId = charge.id; const { ownerId, defaultLocalCurrency, foreignSecurities: { foreignSecuritiesFeesCategoryId }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext(); const errors: Set<string> = new Set(); try { // validate ledger records are balanced const ledgerBalance = new Map< string, { amount: number; entityId: string; foreignAmounts?: Partial<Record<Currency, { local: number; foreign: number }>>; } >(); const transactionsPromise = injector .get(TransactionsProvider) .transactionsByChargeIDLoader.load(chargeId); const financialAccountsPromise = injector .get(FinancialAccountsProvider) .getFinancialAccountsByOwnerIdLoader.load(ownerId); const [transactions, financialAccounts, { mainBusinessId }] = await Promise.all([ transactionsPromise, financialAccountsPromise, getChargeBusinesses(chargeId, injector), ]); const foreignSecuritiesAccountId = financialAccounts.find( account => account.type === 'FOREIGN_SECURITIES', )?.id; if (!foreignSecuritiesAccountId) { throw new GraphQLError('Foreign securities account is missing'); } const entriesPromises: Array<Promise<void>> = []; const financialAccountLedgerEntries: StrictLedgerProto[] = []; const feeFinancialAccountLedgerEntries: LedgerProto[] = []; const miscExpensesLedgerEntries: LedgerProto[] = []; // generate ledger from transactions const { mainTransactions, feeTransactions } = splitFeeTransactions(transactions); const mainTransactionsPromises = mainTransactions.map( async transaction => await ledgerEntryFromMainTransaction( { ...transaction, source_reference: 'foreign_securities' }, injector, chargeId, charge.owner_id, mainBusinessId ?? undefined, ) .then(ledgerEntry => { financialAccountLedgerEntries.push(ledgerEntry); updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency); }) .catch(e => { if (e instanceof LedgerError) { errors.add(e.message); } else { throw e; } }), ); // create a ledger record for fee transactions const feeTransactionsPromises = feeTransactions.map(async transaction => { if (!foreignSecuritiesFeesCategoryId) { errors.add(`Foreign securities fees category ID is missing`); return; } // for each transaction, create a ledger record const { currency, valueDate } = validateTransactionBasicVariables(transaction); let amount = Number(transaction.amount); let foreignAmount: number | undefined = undefined; if (currency !== defaultLocalCurrency) { // get exchange rate for currency const exchangeRate = await injector .get(ExchangeProvider) .getExchangeRates(currency, defaultLocalCurrency, valueDate); foreignAmount = amount; // calculate amounts in ILS: amount = exchangeRate * amount; } const accountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction); const isCreditorCounterparty = amount > 0; const ledgerEntry: StrictLedgerProto = { id: transaction.id, invoiceDate: transaction.event_date, valueDate, currency, creditAccountID1: isCreditorCounterparty ? foreignSecuritiesFeesCategoryId : accountTaxCategoryId, creditAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined, localCurrencyCreditAmount1: Math.abs(amount), debitAccountID1: isCreditorCounterparty ? accountTaxCategoryId : foreignSecuritiesFeesCategoryId, debitAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined, localCurrencyDebitAmount1: Math.abs(amount), description: transaction.source_description ?? undefined, reference: transaction.origin_key, isCreditorCounterparty, ownerId: charge.owner_id, currencyRate: transaction.currency_rate ? Number(transaction.currency_rate) : undefined, chargeId, }; feeFinancialAccountLedgerEntries.push(ledgerEntry); updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency); }); // generate ledger from misc expenses const expensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => { entries.map(entry => { entry.ownerId = charge.owner_id; miscExpensesLedgerEntries.push(entry); updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency); }); }); entriesPromises.push( ...mainTransactionsPromises, ...feeTransactionsPromises, expensesLedgerPromise, ); await Promise.all(entriesPromises); const records = [ ...financialAccountLedgerEntries, ...feeFinancialAccountLedgerEntries, ...miscExpensesLedgerEntries, ]; if (insertLedgerRecordsIfNotExists) { await storeInitialGeneratedRecords(charge.id, records, injector); } const allowedUnbalancedBusinesses = new Set<string>(); const mainLedgerEntry = financialAccountLedgerEntries[0]; if (!mainLedgerEntry.isCreditorCounterparty) { allowedUnbalancedBusinesses.add(mainLedgerEntry.debitAccountID1); } const ledgerBalanceInfo = await getLedgerBalanceInfo( injector, ledgerBalance, errors, allowedUnbalancedBusinesses, ); return { records: ledgerProtoToRecordsConverter(records), charge, balance: ledgerBalanceInfo, errors: Array.from(errors), }; } catch (e) { return { __typename: 'CommonError', message: `Failed to generate ledger records for charge ID="${chargeId}"\n${e}`, }; } };