@accounter/server
Version:
Accounter GraphQL server
193 lines • 10.6 kB
JavaScript
import { format } from 'date-fns';
import { dateToTimelessDateString, getMonthFromDescription, } from '../../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js';
import { getChargeTransactionsMeta } from '../../../charges/helpers/common.helper.js';
import { ExchangeProvider } from '../../../exchange-rates/providers/exchange.provider.js';
import { getVatRecords } from '../../../reports/resolvers/get-vat-records.resolver.js';
import { TransactionsProvider } from '../../../transactions/providers/transactions.provider.js';
import { storeInitialGeneratedRecords } from '../../helpers/ledgrer-storage.helper.js';
import { generateMiscExpensesLedger } from '../../helpers/misc-expenses-ledger.helper.js';
import { getVatDataFromVatReportRecords } from '../../helpers/monthly-vat-ledger-generation.helper.js';
import { generatePartialLedgerEntry, getFinancialAccountTaxCategoryId, getLedgerBalanceInfo, LedgerError, ledgerProtoToRecordsConverter, updateLedgerBalanceByEntry, validateTransactionRequiredVariables, } from '../../helpers/utils.helper.js';
import { UnbalancedBusinessesProvider } from '../../providers/unbalanced-businesses.provider.js';
export const generateLedgerRecordsForMonthlyVat = async (charge, { insertLedgerRecordsIfNotExists }, { injector }, __) => {
const chargeId = charge.id;
const errors = new Set();
try {
const { defaultLocalCurrency, authorities: { vatBusinessId, inputVatTaxCategoryId, outputVatTaxCategoryId, propertyOutputVatTaxCategoryId, }, general: { taxCategories: { balanceCancellationTaxCategoryId }, }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
// figure out VAT month
const { transactionsMinDebitDate, transactionsMinEventDate } = await getChargeTransactionsMeta(chargeId, injector);
const transactionDate = transactionsMinDebitDate ?? transactionsMinEventDate ?? undefined;
if (!charge.user_description) {
errors.add(`Monthly VAT charge must have description that indicates it's month (ID="${chargeId}")`);
}
const vatDates = charge.user_description
? getMonthFromDescription(charge.user_description, transactionDate)
: null;
if (!vatDates?.length) {
errors.add(`Cannot extract charge ID="${chargeId}" VAT month`);
}
// get VAT relevant records
const vatRecordsPromises = (vatDates ?? []).map(async (vatDate) => {
const [year, month] = (vatDate ?? format(new Date(), 'yyyy-MM')).split('-').map(Number);
const ledgerDate = new Date(year, month, 0);
const monthDate = dateToTimelessDateString(new Date(year, month - 1, 15));
const { income, expenses } = await getVatRecords({ filters: { financialEntityId: charge.owner_id, monthDate } }, injector);
return {
income,
expenses,
ledgerDate,
vatDate,
};
});
const transactionsPromise = injector
.get(TransactionsProvider)
.transactionsByChargeIDLoader.load(chargeId);
const unbalancedBusinessesPromise = injector
.get(UnbalancedBusinessesProvider)
.getChargeUnbalancedBusinessesByChargeIds.load(chargeId);
const [vatRecords, transactions, unbalancedBusinesses] = await Promise.all([
Promise.all(vatRecordsPromises),
transactionsPromise,
unbalancedBusinessesPromise,
]);
const accountingLedgerEntries = [];
const financialAccountLedgerEntries = [];
const miscExpensesLedgerEntries = [];
const ledgerBalance = new Map();
// for each transaction, create a ledger record
const mainTransactionsPromises = transactions.map(async (preValidatedTransaction) => {
try {
const transaction = validateTransactionRequiredVariables(preValidatedTransaction);
if (transaction.currency !== defaultLocalCurrency) {
throw new LedgerError(`Monthly VAT currency supposed to be local, got ${transaction.currency}`);
}
// preparations for core ledger entries
let exchangeRate = undefined;
if (transaction.currency !== defaultLocalCurrency) {
// get exchange rate for currency
exchangeRate = await injector
.get(ExchangeProvider)
.getExchangeRates(transaction.currency, defaultLocalCurrency, transaction.debit_timestamp);
}
const partialEntry = generatePartialLedgerEntry(transaction, charge.owner_id, exchangeRate);
const financialAccountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
const ledgerEntry = {
...partialEntry,
...(partialEntry.isCreditorCounterparty
? {
creditAccountID1: vatBusinessId,
debitAccountID1: financialAccountTaxCategoryId,
}
: {
creditAccountID1: financialAccountTaxCategoryId,
debitAccountID1: vatBusinessId,
}),
};
financialAccountLedgerEntries.push(ledgerEntry);
updateLedgerBalanceByEntry(ledgerEntry, ledgerBalance, defaultLocalCurrency);
}
catch (e) {
if (e instanceof LedgerError) {
errors.add(e.message);
}
else {
throw e;
}
}
});
const miscExpensesLedgerPromise = generateMiscExpensesLedger(charge, injector).then(entries => {
entries.map(entry => {
entry.ownerId = charge.owner_id;
miscExpensesLedgerEntries.push(entry);
updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency);
});
});
const isProperty = charge.is_property;
if (isProperty && !propertyOutputVatTaxCategoryId) {
console.error(`Charge ID="${chargeId}" is marked as property but no Property Output VAT tax category is configured in admin context`);
return {
__typename: 'CommonError',
message: `One of the charges is marked as property but no Property Output VAT tax category is configured in admin context`,
};
}
await Promise.all([...mainTransactionsPromises, miscExpensesLedgerPromise]);
for (const { income, expenses, ledgerDate, vatDate } of vatRecords) {
const [incomeVat, roundedIncomeVat] = getVatDataFromVatReportRecords(income);
const [expensesVat, roundedExpensesVat] = getVatDataFromVatReportRecords(expenses);
const accountingLedgerMaterials = [];
accountingLedgerMaterials.push({
amount: roundedIncomeVat,
taxCategoryId: inputVatTaxCategoryId,
counterpartyId: vatBusinessId,
}, {
amount: roundedExpensesVat * -1,
taxCategoryId: isProperty ? propertyOutputVatTaxCategoryId : outputVatTaxCategoryId,
counterpartyId: vatBusinessId,
});
const roundedIncomeVatDiff = Math.round((roundedIncomeVat - incomeVat) * 100) / 100;
if (roundedIncomeVatDiff) {
accountingLedgerMaterials.push({
amount: roundedIncomeVatDiff * -1,
taxCategoryId: inputVatTaxCategoryId,
counterpartyId: balanceCancellationTaxCategoryId,
});
}
const roundedExpensesVatDiff = Math.round((roundedExpensesVat - expensesVat) * 100) / 100;
if (roundedExpensesVatDiff) {
accountingLedgerMaterials.push({
amount: roundedExpensesVatDiff,
taxCategoryId: isProperty ? propertyOutputVatTaxCategoryId : outputVatTaxCategoryId,
counterpartyId: balanceCancellationTaxCategoryId,
});
}
accountingLedgerMaterials.map(({ amount, taxCategoryId, counterpartyId }) => {
if (amount === 0) {
return;
}
const isCreditorCounterparty = amount > 0;
const ledgerProto = {
id: chargeId,
invoiceDate: ledgerDate,
valueDate: ledgerDate,
currency: defaultLocalCurrency,
creditAccountID1: isCreditorCounterparty ? counterpartyId : taxCategoryId,
localCurrencyCreditAmount1: Math.abs(amount),
debitAccountID1: isCreditorCounterparty ? taxCategoryId : counterpartyId,
localCurrencyDebitAmount1: Math.abs(amount),
description: counterpartyId === balanceCancellationTaxCategoryId
? 'Balance cancellation'
: `VAT command ${vatDate}`,
isCreditorCounterparty,
ownerId: charge.owner_id,
chargeId,
};
accountingLedgerEntries.push(ledgerProto);
updateLedgerBalanceByEntry(ledgerProto, ledgerBalance, defaultLocalCurrency);
});
}
const allowedUnbalancedBusinesses = new Set(unbalancedBusinesses.map(({ business_id }) => business_id));
const ledgerBalanceInfo = await getLedgerBalanceInfo(injector, ledgerBalance, errors, allowedUnbalancedBusinesses);
const records = [
...financialAccountLedgerEntries,
...accountingLedgerEntries,
...miscExpensesLedgerEntries,
];
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=monthly-vat-ledger-generation.resolver.js.map