UNPKG

@accounter/server

Version:
183 lines • 9.09 kB
import { GraphQLError } from 'graphql'; import { formatFinancialAmount } from '../../../../shared/helpers/index.js'; import { AdminContextProvider } from '../../../admin-context/providers/admin-context.provider.js'; import { FinancialEntitiesProvider } from '../../../financial-entities/providers/financial-entities.provider.js'; import { LedgerProvider } from '../../../ledger/providers/ledger.provider.js'; import { decorateLedgerRecords, getProfitLossReportAmounts, } from '../../helpers/profit-and-loss.helper.js'; import { calculateCumulativeRnDExpenses, calculateTaxAmounts } from '../../helpers/tax.helper.js'; export const taxReport = async (_, { reportYear, referenceYears }, context) => { const { injector } = context; referenceYears = referenceYears.filter(year => year !== reportYear); const years = [reportYear, ...referenceYears]; years.map(year => { if (year < 2000 || year > new Date().getFullYear()) { throw new GraphQLError('Invalid year'); } }); const from = new Date(Math.min(...years) - 2, 0, 1, 0, 0, 0, 1); // Note: take 2 years before the earliest year requested, to calculate R&D spread over 3 years const to = new Date(Math.max(...years) + 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(); 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(); const yearlyReports = []; for (const year of years) { const decoratedLedgerRecords = decoratedLedgerByYear.get(year) ?? []; let profitAndLoss = profitLossByYear.get(year); if (!profitAndLoss) { profitAndLoss = getProfitLossReportAmounts(decoratedLedgerRecords); profitLossByYear.set(year, profitAndLoss); } const { profitBeforeTax, researchAndDevelopmentExpenses } = profitAndLoss; const adjustedResearchAndDevelopmentExpensesAmount = researchAndDevelopmentExpenses.amount * -1; const cumulativeResearchAndDevelopmentExpensesAmount = calculateCumulativeRnDExpenses(year, decoratedLedgerByYear, profitLossByYear); const taxableCumulativeResearchAndDevelopmentExpensesAmount = cumulativeResearchAndDevelopmentExpensesAmount / 3; const { researchAndDevelopmentExpensesForTax, fines, untaxableGifts, businessTripsExcessExpensesAmount, salaryExcessExpensesAmount, reserves, nontaxableLinkage, taxableIncomeAmount, taxRate, specialTaxableIncome, specialTaxRate, annualTaxExpenseAmount, } = await calculateTaxAmounts(injector, year, decoratedLedgerRecords, adjustedResearchAndDevelopmentExpensesAmount, taxableCumulativeResearchAndDevelopmentExpensesAmount, profitBeforeTax.amount); yearlyReports.push({ year, profitBeforeTax, researchAndDevelopmentExpensesByRecords: { amount: adjustedResearchAndDevelopmentExpensesAmount, records: researchAndDevelopmentExpenses.records.map(record => ({ ...record, amount: record.amount * -1, records: record.records.map(subRecord => ({ ...subRecord, amount: subRecord.amount * -1, })), })), }, researchAndDevelopmentExpensesForTax, fines, untaxableGifts, businessTripsExcessExpensesAmount, salaryExcessExpensesAmount, reserves, nontaxableLinkage, taxableIncome: taxableIncomeAmount, taxRate, specialTaxableIncome, specialTaxRate, annualTaxExpense: annualTaxExpenseAmount, }); } return { id: `tax-report-${reportYear}`, report: yearlyReports.find(report => report.year === reportYear), reference: yearlyReports.filter(report => report.year !== reportYear), }; }; export const taxReportYearMapper = { id: parent => `profit-and-loss-year-${parent.year}`, year: parent => parent.year, profitBeforeTax: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return { amount: formatFinancialAmount(parent.profitBeforeTax.amount, defaultLocalCurrency), records: parent.profitBeforeTax.records, }; }, researchAndDevelopmentExpensesByRecords: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return { amount: formatFinancialAmount(parent.researchAndDevelopmentExpensesByRecords.amount, defaultLocalCurrency), records: parent.researchAndDevelopmentExpensesByRecords.records, }; }, researchAndDevelopmentExpensesForTax: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return formatFinancialAmount(parent.researchAndDevelopmentExpensesForTax, defaultLocalCurrency); }, fines: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return { amount: formatFinancialAmount(parent.fines.amount, defaultLocalCurrency), records: parent.fines.records, }; }, untaxableGifts: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return { amount: formatFinancialAmount(parent.untaxableGifts.amount, defaultLocalCurrency), records: parent.untaxableGifts.records, }; }, businessTripsExcessExpensesAmount: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return formatFinancialAmount(parent.businessTripsExcessExpensesAmount, defaultLocalCurrency); }, salaryExcessExpensesAmount: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return formatFinancialAmount(parent.salaryExcessExpensesAmount, defaultLocalCurrency); }, reserves: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return { amount: formatFinancialAmount(parent.reserves.amount, defaultLocalCurrency), records: parent.reserves.records, }; }, nontaxableLinkage: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return { amount: formatFinancialAmount(parent.nontaxableLinkage.amount, defaultLocalCurrency), records: parent.nontaxableLinkage.records, }; }, taxableIncome: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return formatFinancialAmount(parent.taxableIncome, defaultLocalCurrency); }, taxRate: parent => parent.taxRate, specialTaxableIncome: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return { amount: formatFinancialAmount(parent.specialTaxableIncome.amount, defaultLocalCurrency), records: parent.specialTaxableIncome.records, }; }, specialTaxRate: parent => parent.specialTaxRate, annualTaxExpense: async (parent, _, { injector }) => { const { defaultLocalCurrency } = await injector .get(AdminContextProvider) .getVerifiedAdminContext(); return formatFinancialAmount(parent.annualTaxExpense, defaultLocalCurrency); }, }; //# sourceMappingURL=tax-report.js.map