UNPKG

@accounter/server

Version:
205 lines (180 loc) • 7.27 kB
import { sub } from 'date-fns'; import { GraphQLError } from 'graphql'; import type { Maybe, ResolverFn, ResolversParentTypes, ResolversTypes, } from '../../../../../__generated__/types.js'; import { EMPTY_UUID } from '../../../../../shared/constants.js'; import { Currency } from '../../../../../shared/enums.js'; import { dateToTimelessDateString, formatCurrency } from '../../../../../shared/helpers/index.js'; import type { CurrencySum, LedgerProto, TimelessDateString, } from '../../../../../shared/types/index.js'; import { AdminContextProvider } from '../../../../admin-context/providers/admin-context.provider.js'; import { ExchangeProvider } from '../../../../exchange-rates/providers/exchange.provider.js'; import { businessTransactionsSumFromLedgerRecords } from '../../../../financial-entities/resolvers/business-transactions-sum-from-ledger-records.resolver.js'; import { storeInitialGeneratedRecords } from '../../../helpers/ledgrer-storage.helper.js'; import { generateMiscExpensesLedger } from '../../../helpers/misc-expenses-ledger.helper.js'; import { ledgerProtoToRecordsConverter } from '../../../helpers/utils.helper.js'; export const BANK_DEPOSITS_REVALUATION_LEDGER_DESCRIPTION = 'Bank deposits revaluation of currency'; export const generateLedgerRecordsForBankDepositsRevaluation: ResolverFn< Maybe<ResolversTypes['GeneratedLedgerRecords']>, ResolversParentTypes['Charge'], GraphQLModules.Context, { insertLedgerRecordsIfNotExists: boolean } > = async (charge, { insertLedgerRecordsIfNotExists }, context, info) => { try { const { injector } = context; if (!charge.user_description) { return { __typename: 'CommonError', message: `Bank deposits revaluation charge must include user description with designated year`, }; } const { defaultLocalCurrency, general: { taxCategories: { exchangeRevaluationTaxCategoryId }, }, bankDeposits: { bankDepositBusinessId }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext(); if (!bankDepositBusinessId) { return { __typename: 'CommonError', message: `Bank deposits business is not set`, }; } // get revaluation date - search for "yyyy-mm-dd" in description const dateRegex = /\b(\d{4})\b/; const matches = charge.user_description.match(dateRegex); if (!matches?.length) { return { __typename: 'CommonError', message: `Bank deposits revaluation charge description must include full year`, }; } const stringYear = matches[0]; const year = Number(stringYear); if (Number.isNaN(year) || year < 2000 || year > new Date().getFullYear()) { return { __typename: 'CommonError', message: `Bank deposits revaluation charge description must include valid year (2000 - current year)`, }; } const revaluationDate = `${year}-12-31` as TimelessDateString; const cumulativeDate = dateToTimelessDateString(sub(new Date(revaluationDate), { days: 1 })); const bankDepositsCumulativeBalance = await businessTransactionsSumFromLedgerRecords( {}, { filters: { ownerIds: [charge.owner_id], businessIDs: [bankDepositBusinessId], toDate: cumulativeDate, includeRevaluation: false, }, }, context, info, ); if ( !('businessTransactionsSum' in bankDepositsCumulativeBalance) || bankDepositsCumulativeBalance.businessTransactionsSum.length === 0 ) { throw new GraphQLError('Failed to get accounts transaction-based balance'); } const bankDepositsBalance = await bankDepositsCumulativeBalance.businessTransactionsSum[0]; const currencies: Currency[] = Object.entries(bankDepositsBalance) .filter( ([key, value]) => Object.values(Currency).includes(key as Currency) && ((value as CurrencySum).total !== 0 || (value as CurrencySum).debit !== 0 || (value as CurrencySum).credit !== 0), ) .map(([key]) => key as Currency); const exchangeRates = new Map<Currency, number>(); await Promise.all( currencies.map(currency => injector .get(ExchangeProvider) .getExchangeRates(currency, defaultLocalCurrency, new Date(revaluationDate)) .then(rates => exchangeRates.set(currency, rates)), ), ); const errors: Set<string> = new Set(); const ledgerEntries: LedgerProto[] = []; const currenciesLedgerPromise = Object.entries(bankDepositsBalance).map( async ([currency, sum]) => { if (!currencies.includes(currency as Currency) || currency === defaultLocalCurrency) { return; } const rate = exchangeRates.get(formatCurrency(currency as Currency)); if (!rate) { errors.add(`No exchange rate found for date ${revaluationDate}, currency "${currency}"`); return; } const cumulativeLocalBalance = bankDepositsBalance[defaultLocalCurrency].total; const cumulativeForeignBalance = (sum as CurrencySum).total; const revaluationDiff = cumulativeLocalBalance - cumulativeForeignBalance * rate; if (Math.abs(revaluationDiff) < 0.005) { return; } const isCreditorCounterparty = revaluationDiff < 0; const ledgerEntry: LedgerProto = { id: EMPTY_UUID, invoiceDate: new Date(revaluationDate), valueDate: new Date(revaluationDate), currency: defaultLocalCurrency, isCreditorCounterparty, ...(isCreditorCounterparty ? { creditAccountID1: bankDepositBusinessId, debitAccountID1: exchangeRevaluationTaxCategoryId, } : { creditAccountID1: exchangeRevaluationTaxCategoryId, debitAccountID1: bankDepositBusinessId, }), localCurrencyCreditAmount1: Math.abs(revaluationDiff), localCurrencyDebitAmount1: Math.abs(revaluationDiff), description: `${BANK_DEPOSITS_REVALUATION_LEDGER_DESCRIPTION} "${currency}"`, ownerId: charge.owner_id, chargeId: charge.id, currencyRate: rate, }; ledgerEntries.push(ledgerEntry); }, ); // generate ledger from misc expenses const expensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => { entries.map(entry => { entry.ownerId = charge.owner_id; ledgerEntries.push(entry); }); }); await Promise.all([expensesLedgerPromise, ...currenciesLedgerPromise]); if (insertLedgerRecordsIfNotExists) { await storeInitialGeneratedRecords(charge.id, ledgerEntries, injector); } return { records: ledgerProtoToRecordsConverter(ledgerEntries), charge, balance: { isBalanced: true, unbalancedEntities: [], balanceSum: 0, financialEntities: [], }, errors: Array.from(errors), }; } catch (e) { return { __typename: 'CommonError', message: `Failed to generate ledger records for charge ID="${charge.id}"\n${e}`, }; } };