UNPKG

@accounter/server

Version:
170 lines (149 loc) 5.94 kB
import type { Maybe, ResolverFn, ResolversParentTypes, ResolversTypes, } from '../../../../../__generated__/types.js'; import { EMPTY_UUID } from '../../../../../shared/constants.js'; import type { LedgerProto } from '../../../../../shared/types/index.js'; import { AdminContextProvider } from '../../../../admin-context/providers/admin-context.provider.js'; import { FinancialEntitiesProvider } from '../../../../financial-entities/providers/financial-entities.provider.js'; import { decorateLedgerRecords, getProfitLossReportAmounts, type DecoratedLedgerRecord, } from '../../../../reports/helpers/profit-and-loss.helper.js'; import { calculateCumulativeRnDExpenses, calculateTaxAmounts, } from '../../../../reports/helpers/tax.helper.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'; import { LedgerProvider } from '../../../providers/ledger.provider.js'; export const generateLedgerRecordsForTaxExpenses: ResolverFn< Maybe<ResolversTypes['GeneratedLedgerRecords']>, ResolversParentTypes['Charge'], GraphQLModules.Context, { insertLedgerRecordsIfNotExists: boolean } > = async (charge, { insertLedgerRecordsIfNotExists }, { injector }) => { try { const { defaultLocalCurrency, authorities: { taxBusinessId, taxExpensesTaxCategoryId }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext(); if (!charge.user_description) { return { __typename: 'CommonError', message: `Tax expenses charge must include user description with designated year`, }; } // 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: `Tax expenses 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: `Tax expenses charge description must include valid year (2000 - current year)`, }; } const from = new Date(year - 2, 0, 1, 0, 0, 1); const to = new Date(year + 1, 0, 0); const ledgerRecords = await injector .get(LedgerProvider) .getLedgerRecordsByDates({ fromDate: from, toDate: to }); const financialEntities = await injector .get(FinancialEntitiesProvider) .getAllFinancialEntities(); const financialEntitiesDict = new Map(financialEntities.map(entity => [entity.id, entity])); const decoratedLedgerByYear = new Map<number, DecoratedLedgerRecord[]>(); for (let year = from.getFullYear(); year <= to.getFullYear(); year++) { if (from.getFullYear() > to.getFullYear()) { break; } decoratedLedgerByYear.set(year, []); } ledgerRecords.map(record => { const year = record.invoice_date.getFullYear(); const [decoratedRecord] = decorateLedgerRecords([record], financialEntitiesDict); decoratedLedgerByYear.get(year)?.push(decoratedRecord); }); const profitLossByYear = new Map<number, ReturnType<typeof getProfitLossReportAmounts>>(); function getProfitLossReportAmountsByYear(year: number) { let amounts = profitLossByYear.get(year); if (!amounts) { const decoratedLedgerRecords = decoratedLedgerByYear.get(year) ?? []; amounts = getProfitLossReportAmounts(decoratedLedgerRecords); profitLossByYear.set(year, amounts); } return amounts; } const cumulativeResearchAndDevelopmentExpensesAmount = calculateCumulativeRnDExpenses( year, decoratedLedgerByYear, profitLossByYear, ); const taxableCumulativeResearchAndDevelopmentExpensesAmount = cumulativeResearchAndDevelopmentExpensesAmount / 3; const { researchAndDevelopmentExpenses, profitBeforeTax } = getProfitLossReportAmountsByYear(year); const { annualTaxExpenseAmount } = await calculateTaxAmounts( injector, year, decoratedLedgerByYear.get(year) ?? [], researchAndDevelopmentExpenses.amount * -1, taxableCumulativeResearchAndDevelopmentExpensesAmount, profitBeforeTax.amount, ); const ledgerEntry: LedgerProto = { id: EMPTY_UUID, invoiceDate: new Date(year, 11, 31), valueDate: new Date(year, 11, 31), currency: defaultLocalCurrency, isCreditorCounterparty: true, creditAccountID1: taxBusinessId, debitAccountID1: taxExpensesTaxCategoryId, localCurrencyCreditAmount1: Math.abs(annualTaxExpenseAmount), localCurrencyDebitAmount1: Math.abs(annualTaxExpenseAmount), description: `Tax expenses for ${year}`, ownerId: charge.owner_id, chargeId: charge.id, currencyRate: 1, }; const ledgerEntries = [ledgerEntry]; // generate ledger from misc expenses await generateMiscExpensesLedger(charge, injector).then(entries => { entries.map(entry => { entry.ownerId = charge.owner_id; ledgerEntries.push(entry); }); }); if (insertLedgerRecordsIfNotExists) { await storeInitialGeneratedRecords(charge.id, ledgerEntries, injector); } return { records: ledgerProtoToRecordsConverter(ledgerEntries), charge, balance: { isBalanced: true, unbalancedEntities: [], balanceSum: 0, financialEntities: [], }, errors: [], }; } catch (e) { return { __typename: 'CommonError', message: `Failed to generate ledger records for charge ID="${charge.id}"\n${e}`, }; } };