@accounter/server
Version:
Accounter GraphQL server
209 lines • 10.1 kB
JavaScript
import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js';
import { ExchangeProvider } from '../../../exchange-rates/providers/exchange.provider.js';
import { TransactionsProvider } from '../../../transactions/providers/transactions.provider.js';
import { getEntriesFromFeeTransaction, 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, isTransactionsOppositeSign, LedgerError, ledgerProtoToRecordsConverter, updateLedgerBalanceByEntry, validateTransactionBasicVariables, } from '../../helpers/utils.helper.js';
export const generateLedgerRecordsForInternalTransfer = async (charge, { insertLedgerRecordsIfNotExists }, { injector }) => {
const { defaultLocalCurrency, general: { taxCategories: { exchangeRateTaxCategoryId }, }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const chargeId = charge.id;
const errors = new Set();
try {
// validate ledger records are balanced
const ledgerBalance = new Map();
const dates = new Set();
const currencies = new Set();
// generate ledger from transactions
const mainFinancialAccountLedgerEntries = [];
const feeFinancialAccountLedgerEntries = [];
let originEntry = undefined;
let destinationEntry = undefined;
// Get all transactions
const transactions = await injector
.get(TransactionsProvider)
.transactionsByChargeIDLoader.load(chargeId);
const { mainTransactions, feeTransactions } = splitFeeTransactions(transactions);
if (mainTransactions.length !== 2) {
errors.add(`Internal transfer Charge must include two main transactions`);
}
try {
if (!isTransactionsOppositeSign(mainTransactions)) {
errors.add(`Internal transfer Charge must include two main transactions with opposite sign`);
}
}
catch (e) {
if (e instanceof LedgerError) {
errors.add(e.message);
}
else {
throw e;
}
}
// create a ledger record for main transactions
const mainFinancialAccountLedgerEntriesPromises = mainTransactions.map(async (transaction) => {
try {
const { currency, valueDate } = validateTransactionBasicVariables(transaction);
let amount = Number(transaction.amount);
let foreignAmount = 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 financialAccountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
const isCreditorCounterparty = amount > 0;
const ledgerEntry = {
id: transaction.id,
invoiceDate: transaction.event_date,
valueDate,
currency,
...(isCreditorCounterparty
? {
debitAccountID1: financialAccountTaxCategoryId,
}
: {
creditAccountID1: financialAccountTaxCategoryId,
}),
debitAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
localCurrencyDebitAmount1: Math.abs(amount),
creditAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
localCurrencyCreditAmount1: 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,
};
mainFinancialAccountLedgerEntries.push(ledgerEntry);
updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
dates.add(valueDate.getTime());
currencies.add(currency);
}
catch (e) {
if (e instanceof LedgerError) {
errors.add(e.message);
}
else {
throw e;
}
}
});
// create a ledger record for fee transactions
const feeFinancialAccountLedgerEntriesPromises = feeTransactions.map(async (transaction) => {
if (!transaction.is_fee) {
return;
}
try {
const ledgerEntries = await getEntriesFromFeeTransaction(transaction, charge, injector).catch(e => {
if (e instanceof LedgerError) {
errors.add(e.message);
}
else {
throw e;
}
});
if (!ledgerEntries) {
return;
}
feeFinancialAccountLedgerEntries.push(...ledgerEntries);
ledgerEntries.map(ledgerEntry => {
updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
dates.add(ledgerEntry.valueDate.getTime());
currencies.add(ledgerEntry.currency);
});
}
catch (e) {
if (e instanceof LedgerError) {
errors.add(e.message);
}
else {
throw e;
}
}
});
// create ledger records for misc expenses
const miscExpensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => {
entries.map(entry => {
entry.ownerId = charge.owner_id;
feeFinancialAccountLedgerEntries.push(entry);
updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency);
dates.add(entry.valueDate.getTime());
currencies.add(entry.currency);
});
});
await Promise.all([
...feeFinancialAccountLedgerEntriesPromises,
...mainFinancialAccountLedgerEntriesPromises,
miscExpensesLedgerPromise,
]);
for (const ledgerEntry of mainFinancialAccountLedgerEntries) {
if (ledgerEntry.isCreditorCounterparty) {
destinationEntry = ledgerEntry;
}
else {
originEntry = ledgerEntry;
}
}
const { balanceSum } = await getLedgerBalanceInfo(injector, ledgerBalance);
const miscLedgerEntries = [];
if (!originEntry || !destinationEntry) {
errors.add(`Internal transfer Charge must include two main transactions`);
}
else if (Math.abs(balanceSum) > 0.005) {
// Add ledger completion entries
const hasMultipleDates = dates.size > 1;
const hasForeignCurrency = currencies.size > (currencies.has(defaultLocalCurrency) ? 1 : 0);
if (hasMultipleDates && hasForeignCurrency) {
const amount = Math.abs(balanceSum);
const isCreditorCounterparty = balanceSum > 0;
const latestDate = new Date(Math.max(originEntry.valueDate.getTime(), originEntry.invoiceDate.getTime(), destinationEntry.valueDate.getTime(), destinationEntry.invoiceDate.getTime()));
const ledgerEntry = {
id: destinationEntry.id, // NOTE: this field is dummy
creditAccountID1: isCreditorCounterparty ? undefined : exchangeRateTaxCategoryId,
creditAmount1: undefined,
localCurrencyCreditAmount1: amount,
debitAccountID1: isCreditorCounterparty ? exchangeRateTaxCategoryId : undefined,
debitAmount1: undefined,
localCurrencyDebitAmount1: amount,
description: 'Exchange ledger record',
isCreditorCounterparty,
invoiceDate: latestDate,
valueDate: latestDate,
currency: defaultLocalCurrency,
ownerId: destinationEntry.ownerId,
chargeId,
};
miscLedgerEntries.push(ledgerEntry);
updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
}
}
const ledgerBalanceInfo = await getLedgerBalanceInfo(injector, ledgerBalance, errors);
const records = [
...mainFinancialAccountLedgerEntries,
...feeFinancialAccountLedgerEntries,
...miscLedgerEntries,
];
if (insertLedgerRecordsIfNotExists) {
await storeInitialGeneratedRecords(charge.id, records, injector);
}
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}`,
};
}
};
//# sourceMappingURL=internal-transfer-ledger-generation.resolver.js.map