@accounter/server
Version:
Accounter GraphQL server
144 lines • 7.22 kB
JavaScript
import { sub } from 'date-fns';
import { GraphQLError } from 'graphql';
import { EMPTY_UUID } from '../../../../../shared/constants.js';
import { Currency } from '../../../../../shared/enums.js';
import { dateToTimelessDateString, formatCurrency } from '../../../../../shared/helpers/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 = 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`;
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 = Object.entries(bankDepositsBalance)
.filter(([key, value]) => Object.values(Currency).includes(key) &&
(value.total !== 0 ||
value.debit !== 0 ||
value.credit !== 0))
.map(([key]) => key);
const exchangeRates = new Map();
await Promise.all(currencies.map(currency => injector
.get(ExchangeProvider)
.getExchangeRates(currency, defaultLocalCurrency, new Date(revaluationDate))
.then(rates => exchangeRates.set(currency, rates))));
const errors = new Set();
const ledgerEntries = [];
const currenciesLedgerPromise = Object.entries(bankDepositsBalance).map(async ([currency, sum]) => {
if (!currencies.includes(currency) || currency === defaultLocalCurrency) {
return;
}
const rate = exchangeRates.get(formatCurrency(currency));
if (!rate) {
errors.add(`No exchange rate found for date ${revaluationDate}, currency "${currency}"`);
return;
}
const cumulativeLocalBalance = bankDepositsBalance[defaultLocalCurrency].total;
const cumulativeForeignBalance = sum.total;
const revaluationDiff = cumulativeLocalBalance - cumulativeForeignBalance * rate;
if (Math.abs(revaluationDiff) < 0.005) {
return;
}
const isCreditorCounterparty = revaluationDiff < 0;
const ledgerEntry = {
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}`,
};
}
};
//# sourceMappingURL=bank-deposits-revaluation-ledger-generation.resolver.js.map