UNPKG

@accounter/server

Version:
185 lines (167 loc) • 6.76 kB
import { GraphQLError } from 'graphql'; import type { Injector } from 'graphql-modules'; import { TimelessDateString } from '../../../shared/types/index.js'; import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js'; import { BusinessTripsProvider } from '../../business-trips/providers/business-trips.provider.js'; import { businessTripSummary } from '../../business-trips/resolvers/business-trip-summary.resolver.js'; import { BusinessTripProto } from '../../business-trips/types.js'; import { CorporateTaxesProvider } from '../../corporate-taxes/providers/corporate-taxes.provider.js'; import { TaxCategoriesProvider } from '../../financial-entities/providers/tax-categories.provider.js'; import { amountByFinancialEntityIdAndSortCodeValidations, FilteringOptions, recordsByFinancialEntityIdAndSortCodeValidations, } from './misc.helper.js'; import { DecoratedLedgerRecord, getProfitLossReportAmounts } from './profit-and-loss.helper.js'; export async function calculateTaxAmounts( injector: Injector, year: number, decoratedLedgerRecords: DecoratedLedgerRecord[], yearlyResearchAndDevelopmentExpensesAmount: number, threeYearsResearchAndDevelopmentExpensesForTax: number, profitBeforeTaxAmount: number, ) { const { general: { taxCategories: { fineTaxCategoryId, untaxableGiftsTaxCategoryId, salaryExcessExpensesTaxCategoryId, }, }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext(); const taxRateVariablesPromise = injector .get(CorporateTaxesProvider) .getCorporateTaxesByDateLoader.load(`${year}-01-01` as TimelessDateString); const businessTripsPromise = injector .get(BusinessTripsProvider) .getBusinessTripsByDates({ fromDate: `${year}-01-01` as TimelessDateString, toDate: `${year}-12-31` as TimelessDateString, }) .then( async businessTrips => await Promise.all( businessTrips.map(trip => businessTripSummary(injector, trip as BusinessTripProto)), ), ); const [taxRateVariables, businessTrips] = await Promise.all([ taxRateVariablesPromise, businessTripsPromise, ]); if (!taxRateVariables) { throw new GraphQLError('No tax rate for year'); } const excludedTaxCategories = await injector .get(TaxCategoriesProvider) .getAllTaxCategories() .then(res => res.filter(tc => !!tc.tax_excluded).map(tc => tc.id)); const untaxableGiftsFilter: FilteringOptions = { rule: taxCategoryId => taxCategoryId === untaxableGiftsTaxCategoryId, negate: true, }; const finesFilter: FilteringOptions = { rule: taxCategoryId => taxCategoryId === fineTaxCategoryId, negate: true, }; const reservesFilter: FilteringOptions = { rule: (financialEntityId, sortCode) => sortCode === 931 && !excludedTaxCategories.includes(financialEntityId), negate: true, }; const nontaxableLinkageFilter: FilteringOptions = { rule: (financialEntityId, sortCode) => sortCode === 990 && excludedTaxCategories.includes(financialEntityId), negate: true, }; // Special tax rate for special taxable income const specialTaxableIncomeFilter: FilteringOptions = { rule: (_, sortCode) => sortCode === 991, negate: false, }; const [untaxableGifts, fines, reserves, nontaxableLinkage, specialTaxableIncome] = recordsByFinancialEntityIdAndSortCodeValidations(decoratedLedgerRecords, [ untaxableGiftsFilter, finesFilter, reservesFilter, nontaxableLinkageFilter, specialTaxableIncomeFilter, ]); let businessTripsExcessExpensesAmount = 0; businessTrips.map(summary => { const amount = summary.rows.find(row => row.type === 'TOTAL')?.excessExpenditure?.raw ?? 0; businessTripsExcessExpensesAmount += amount; }); const businessTripsExcludedFilter: FilteringOptions = { rule: (financialEntityId: string, sortCode: number) => { return sortCode === 945 && excludedTaxCategories.includes(financialEntityId); }, negate: true, }; const salaryExcessExpensesFilter: FilteringOptions = { rule: financialEntityId => financialEntityId === salaryExcessExpensesTaxCategoryId, negate: true, }; const yearlyRndExcludedRndFilter: FilteringOptions = { rule: (financialEntityId: string, sortCode: number) => { return sortCode === 922 && excludedTaxCategories.includes(financialEntityId); }, negate: true, }; const [businessTripsExcludedAmount, salaryExcessExpensesAmount, yearlyRndExcludedRnd] = amountByFinancialEntityIdAndSortCodeValidations(decoratedLedgerRecords, [ businessTripsExcludedFilter, salaryExcessExpensesFilter, yearlyRndExcludedRndFilter, ]); businessTripsExcessExpensesAmount += businessTripsExcludedAmount; const researchAndDevelopmentExpensesForTax = threeYearsResearchAndDevelopmentExpensesForTax + yearlyRndExcludedRnd; const taxableIncomeAmount = profitBeforeTaxAmount + yearlyResearchAndDevelopmentExpensesAmount + fines.amount + untaxableGifts.amount + businessTripsExcessExpensesAmount + salaryExcessExpensesAmount + reserves.amount + nontaxableLinkage.amount + researchAndDevelopmentExpensesForTax - specialTaxableIncome.amount; const taxRate = Number(taxRateVariables.tax_rate) / 100; const specialTaxRate = Number(taxRateVariables.original_tax_rate) / 100; const annualTaxExpenseAmount = taxableIncomeAmount * taxRate + specialTaxableIncome.amount * specialTaxRate; return { researchAndDevelopmentExpensesForTax, fines, untaxableGifts, businessTripsExcessExpensesAmount, salaryExcessExpensesAmount, reserves, nontaxableLinkage, taxableIncomeAmount, taxRate, specialTaxableIncome, specialTaxRate, annualTaxExpenseAmount, }; } export function calculateCumulativeRnDExpenses( year: number, decoratedLedgerByYear: Map<number, DecoratedLedgerRecord[]>, profitLossByYear: Map<number, ReturnType<typeof getProfitLossReportAmounts>> = new Map(), ): number { let cumulativeResearchAndDevelopmentExpensesAmount = 0; for (const rndYear of [year - 2, year - 1, year]) { let profitLossHelperReportAmounts = profitLossByYear.get(rndYear); if (!profitLossHelperReportAmounts) { const rndDecoratedLedgerRecords = decoratedLedgerByYear.get(rndYear) ?? []; profitLossHelperReportAmounts = getProfitLossReportAmounts(rndDecoratedLedgerRecords); profitLossByYear.set(rndYear, profitLossHelperReportAmounts); } cumulativeResearchAndDevelopmentExpensesAmount += profitLossHelperReportAmounts.researchAndDevelopmentExpenses.amount; } return cumulativeResearchAndDevelopmentExpensesAmount; }