@accounter/server
Version:
Accounter GraphQL server
183 lines • 9.09 kB
JavaScript
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