import { Injector } from './injector';
/**
* Creates a taxes injector that all injectors require in order to calculate
* @class
*/
class InjectorTaxes extends Injector {
/**
* @param {struct} skv_config Struct passed in when initializing the injector
* @param {array} arr_tax_years Array of tax years
*/
constructor(skv_config = {}, arr_tax_years = []) {
super();
this.valid_keys = [];
if (!this.validate(this.valid_keys)) {
throw new Error('Invalid key passed in to InjectorTaxes');
}
this.skv_config = skv_config;
this.arr_tax_years = arr_tax_years;
// expose functions
this.calculate = InjectorTaxes.calculate;
this.calcEmployerNICSingleYear = InjectorTaxes.calcEmployerNICSingleYear;
this.calcEmployeeNICSingleYear = InjectorTaxes.calcEmployeeNICSingleYear;
this.calculateIncomeTaxTaxable = InjectorTaxes.calculateIncomeTaxTaxable;
this.calcExtraTaxFromLostPA = InjectorTaxes.calcExtraTaxFromLostPA;
this.calculateIncomeTaxRelief = InjectorTaxes.calculateIncomeTaxRelief;
this.totalIncome = InjectorTaxes.totalIncome;
}
/**
* Calculate the taxes for each year including totals
* @param {array} arr_tax_years
* @returns {array} tax years with calculated taxes and totals
*/
static calculate(arr_tax_years) {
const arr_return = [];
const cnt_tax_years = arr_tax_years.length;
for (let i = 0; i < cnt_tax_years; i += 1) {
const skv_tax_year = JSON.parse(JSON.stringify(arr_tax_years[i]));
// regions will be filtered down to 1 already at this point
const arr_income_tax_bands = skv_tax_year.skv_tax_charges.income_tax.regions[0].bands;
// Work out how much extra tax is paid due to lost personal allowance,
// split by the sources of income (including benefits as income)
const skv_extra_tax_pa = InjectorTaxes.calcExtraTaxFromLostPA(
skv_tax_year.arr_income,
arr_income_tax_bands,
);
skv_tax_year.arr_tax_on_lost_pa = skv_extra_tax_pa.arr_return;
// calculate the taxable income and relief income
skv_tax_year.arr_income_tax = InjectorTaxes.calcIncomeTax(
skv_tax_year.arr_income,
arr_income_tax_bands,
);
skv_tax_year.arr_employer_nic = InjectorTaxes.calcEmployerNICSingleYear(
skv_tax_year.arr_income,
skv_tax_year.skv_tax_charges.national_insurance.employer.class1A,
);
skv_tax_year.arr_employee_nic = InjectorTaxes.calcEmployeeNICSingleYear(
skv_tax_year.arr_income,
skv_tax_year.skv_tax_charges.national_insurance.employee.bands,
);
skv_tax_year.totals = InjectorTaxes.calculateTotals(skv_tax_year);
skv_tax_year.totals.tax.on_lost_pa_reduction = skv_extra_tax_pa.totals.tax_on_lost_pa_reduction;
skv_tax_year.totals.pa_reduction = skv_extra_tax_pa.totals.lost_pa_reduction;
// add to the array we'll return
arr_return.push(skv_tax_year);
}
return arr_return;
}
/**
* Do the final totalling
* @param {struct} skv_tax_year
* @returns {struct} Totals of income and tax
*/
static calculateTotals(skv_tax_year) {
const skv_return = {
income: {},
tax: {},
pa_reduction: 0,
};
// incomes are always for the whole year
skv_return.income.gross_value = this.totalIncome(skv_tax_year.arr_income);
/*
* TOTALS - INCOME
*/
// employer provided non benefits
skv_return.income.employer_provided_non_benefits = this.totalIncome(
skv_tax_year.arr_income,
'only_employer_provided_non_benefits',
);
/*
* TOTALS - P11D
*/
const p11d = this.totalIncome(
skv_tax_year.arr_income,
'p11d',
);
skv_return.income.p11d = p11d;
/*
* TOTALS - EMPLOYEE NIC
*/
skv_return.tax.employee_nic = this.totalEmployeeNIC(
skv_tax_year.arr_employee_nic,
);
/*
* TOTALS - EMPLOYER NIC with benefits
*/
skv_return.tax.employer_nic_benefits = this.totalEmployerNIC(
skv_tax_year.arr_employer_nic,
'benefits_only',
);
/*
* TOTALS - EMPLOYER NIC without benefits
*/
skv_return.tax.employer_nic_exc_benefits = this.totalEmployerNIC(
skv_tax_year.arr_employer_nic,
'no_benefits',
);
/*
* TOTALS - INCOME TAX
*/
skv_return.tax.income_tax_on_employer_provided = this.totalIncomeTaxes(
skv_tax_year.arr_income_tax,
'only_employer_provided',
);
/*
* TOTALS - INCOME TAX
*/
skv_return.tax.income_tax_on_employer_provided_benefits = this.totalIncomeTaxes(
skv_tax_year.arr_income_tax,
'only_employer_provided_benefits',
);
/*
* TOTALS - INCOME TAX RELIEF
*/
let income_tax_relief = this.totalTaxRelief(
skv_tax_year.arr_income_tax,
);
// we want to be removing the relief so change to a negative for clarity
if (income_tax_relief > 0) {
income_tax_relief *= -1;
}
skv_return.tax.relief = income_tax_relief;
/*
* TOTALS - NET PAY
*/
const net_pay = skv_return.income.employer_provided_non_benefits
- skv_return.tax.income_tax_on_employer_provided
- skv_return.tax.employee_nic
// relief is a negative number so add it
+ skv_return.tax.relief;
// format the value and put in to totals from the year
skv_return.income.net_pay = net_pay;
return skv_return;
}
/**
* Loops through the income tax looking for any taxed relief
* @param {array} arr_income_tax
* @returns {number} num_total
*/
static totalTaxRelief(
arr_income_tax,
) {
const cnt_income_tax = arr_income_tax.length;
let num_total = 0;
for (let i = 0; i < cnt_income_tax; i += 1) {
const skv_income_tax = arr_income_tax[i];
if (skv_income_tax.is_relief) {
num_total += skv_income_tax.tax_relief_total;
}
}
return num_total;
}
/**
* Loops through the employee NIC array
* @param {array} arr_employee_nic
* @returns {number} num_total
*/
static totalEmployeeNIC(
arr_employee_nic,
) {
const cnt_employee_nic = arr_employee_nic.length;
let num_total = 0;
for (let i = 0; i < cnt_employee_nic; i += 1) {
const skv_employee_nic = arr_employee_nic[i];
num_total += skv_employee_nic.tax_total;
}
return num_total;
}
/**
* Loops through the employer NIC array
* @param {array} arr_employer_nic
* @returns {number} num_total
*/
static totalEmployerNIC(
arr_employer_nic,
str_mode = '',
) {
const cnt_employee_nic = arr_employer_nic.length;
let num_total = 0;
for (let i = 0; i < cnt_employee_nic; i += 1) {
const skv_employee_nic = arr_employer_nic[i];
if (str_mode === '') {
num_total += skv_employee_nic.tax_total;
} else if (
str_mode === 'benefits_only'
&& skv_employee_nic.is_benefit
) {
num_total += skv_employee_nic.tax_total;
} else if (
str_mode === 'no_benefits'
&& !skv_employee_nic.is_benefit
) {
num_total += skv_employee_nic.tax_total;
}
}
return num_total;
}
/**
* Loops through the array totalling up the valuePounds
* @param {array} arr_income_tax Array of structures each containing an element with key: tax_total
* @param {string} str_mode what elements are we counting
* @returns {data_type} Description
*/
static totalIncomeTaxes(
arr_income_tax,
str_mode = '',
) {
const cnt_income_tax = arr_income_tax.length;
let num_total = 0;
for (let i = 0; i < cnt_income_tax; i += 1) {
const skv_income_tax = arr_income_tax[i];
if (str_mode === '') {
num_total += skv_income_tax.tax_total;
} else if (
str_mode === 'only_employer_provided'
&& skv_income_tax.is_provided_by_employer
) {
num_total += skv_income_tax.tax_total;
} else if (
str_mode === 'only_employer_provided_benefits'
&& skv_income_tax.is_provided_by_employer
&& skv_income_tax.is_benefit
) {
num_total += skv_income_tax.tax_total;
}
}
return num_total;
}
/**
* Loops through the income array totalling up the value
* @param {array} arr_income Array of income structures each containing an element with key 'value'
* @param {string} str_mode what elements are we counting
* @returns {number}
*/
static totalIncome(arr_income, str_mode = '') {
const cnt_income = arr_income.length;
let num_total = 0;
for (let i = 0; i < cnt_income; i += 1) {
// this particular type of income e.g salary, company car benefit etc
const skv_income = arr_income[i];
if (str_mode === '') {
num_total += skv_income.value;
} else if (
str_mode === 'only_employer_provided_non_benefits'
&& skv_income.is_provided_by_employer
&& !skv_income.is_benefit
) {
num_total += skv_income.value;
} else if (
str_mode === 'P11D'
&& skv_income.is_provided_by_employer
&& skv_income.is_benefit
) {
num_total += skv_income.value;
}
}
return num_total;
}
/**
* Work out how much extra tax is paid due to lost personal allowance,
* split by the sources of income (including benefits as income)
* @param {array} arr_income
* @param {array} arr_tax_bands
* @returns {struct}
*/
static calcExtraTaxFromLostPA(
arr_income,
arr_tax_bands,
) {
const skv_return = {
totals: {
tax_on_lost_pa_reduction: 0,
lost_pa_reduction: 0,
},
arr_return: [],
};
// the first band is the allowance
const skv_pa = arr_tax_bands[0];
// variables for original, remaining and lost pa
const original_pa = skv_pa.limit;
let remaining_pa = original_pa;
// get the reduction threshold and rate (the first tax band is PA)
const pa_restrict = skv_pa.restriction_threshold;
const pa_rate = skv_pa.restriction_rate;
// we need to get the highest tax bracket before the PA is lost,
// as the other bands will shift down, it's effectively the highest
// tax bracket that the lost allowance gets charged at
let lost_pa_tax_perc = 0;
const cnt_bands = arr_tax_bands.length;
for (let i = 0; i < cnt_bands; i += 1) {
const skv_band = arr_tax_bands[i];
lost_pa_tax_perc = skv_band.percentage;
if (
skv_band.limit === ''
|| skv_band.limit >= pa_restrict
) {
break;
}
}
// convert the lost_pa_tax_perc to a decimal
lost_pa_tax_perc /= 100;
// total income so far
let total_income = 0;
// total over limit and max over limit
let total_over_limit = 0;
const cnt_income = arr_income.length;
// Loop through each of the items in the arr_income array
for (let i = 0; i < cnt_income; i += 1) {
const skv_income_source = arr_income[i];
const num_income = skv_income_source.value;
total_income += num_income;
// amount the PA is to be reduced by
let reduction = 0;
// amount of extra tax due to lost pa
let extra_tax = 0;
// check if the total income has taken us over the
// allowance restriction, and if we have any allowance
// left to reduce
if (
total_income > pa_restrict
&& remaining_pa > 0
) {
// how much are we over
const over_limit = total_income
- pa_restrict
- total_over_limit;
total_over_limit += over_limit;
// amount to reduce by, limited to remaining PA
reduction = Math.min(
over_limit * pa_rate,
remaining_pa,
);
extra_tax = reduction * lost_pa_tax_perc;
remaining_pa -= reduction;
}
const skv_temp = {
description: `Income Tax on lost allowance due to ${skv_income_source.name}`,
name: skv_income_source.name,
// the tax is always at the basic rate
pa_reduction: reduction,
tax_total: extra_tax,
};
skv_return.arr_return.push(skv_temp);
skv_return.totals.lost_pa_reduction += skv_temp.pa_reduction;
skv_return.totals.tax_on_lost_pa_reduction += skv_temp.tax_total;
}
return skv_return;
}
/**
* Creates the structure for an income tax struct
* @param {struct} skv_income The structure representing a single year
* @returns {struct}
*/
static getTaxStartingStruct(type, skv_income) {
const skv_return = {};
skv_return.arr_tax_bands = [];
let str_name = '';
if (type === 'income_tax') {
str_name = 'Income Tax';
} else if (type === 'employee_nic') {
str_name = 'NIC';
}
skv_return.name = `${str_name} on ${skv_income.name}`;
skv_return.short_name = skv_income.name;
skv_return.gross_value = skv_income.value;
if (type === 'income_tax') {
skv_return.is_provided_by_employer = skv_income.is_provided_by_employer;
skv_return.is_taxed_at_source = skv_income.is_taxed_at_source;
skv_return.is_benefit = skv_income.is_benefit;
skv_return.is_relief = skv_income.is_relief;
}
return skv_return;
}
/**
* build a struct with the calculated income tax
* @param {array} skv_band basic tax band
* @param {number} amount_to_tax
* @param {number} tax_applied
* @returns {struct} tax band with calculated tax
*/
static addCalculatedTaxBand(
skv_band,
amount_to_tax,
tax_applied,
) {
return {
band: skv_band,
tax: tax_applied,
tax_relief: 0,
income: amount_to_tax,
};
}
/**
* Work out how much of the tax is in each band
* @param {array} arr_tax_bands
* @param {number} tax_total all the tax across the tax bands added up
* @returns {array} tax bands with % proportions included
*/
static addProportionsToBands(arr_tax_bands, tax_total) {
const arr_return = [];
const cnt_bands = arr_tax_bands.length;
for (let i = 0; i < cnt_bands; i += 1) {
const skv_band = arr_tax_bands[i];
let perc_proportion = 0;
if (skv_band.tax !== 0) {
perc_proportion = (skv_band.tax / tax_total) * 100;
}
skv_band.percentage_of_total = perc_proportion;
arr_return.push(skv_band);
}
return arr_return;
}
/**
* Get the highest tax band with tax due to be paid
* @param {array} arr_tax_bands Description
* @returns {number} index of highest tax band
*/
static getTopBandWithTaxIndex(arr_tax_bands) {
const cnt_tax_bands = arr_tax_bands.length;
let num_highest_idx = 0;
for (let i = 0; i < cnt_tax_bands; i += 1) {
const curr_tax_band = arr_tax_bands[i];
if (curr_tax_band.tax > 0) {
num_highest_idx = i;
}
}
return num_highest_idx;
}
/**
* Filter income by whether its income taxable
* Note: Relief such as Mileage Allowance Relief (MAR) is
* @param {array} arr_income
* @returns {array}
*/
static getTaxableIncomes(arr_income) {
return arr_income.filter((skv_curr_income) => {
let should_include_income = true;
// if there is an income taxable property use its value
if (
Object.prototype.hasOwnProperty.call(skv_curr_income, 'is_income_taxable')
) {
should_include_income = skv_curr_income.is_income_taxable;
}
return should_include_income;
});
}
/**
* Filter income by whether its consider as relief
* @param {array} arr_income
* @returns {array}
*/
static getReliefIncomes(arr_income) {
return arr_income.filter((skv_curr_income) => {
let should_include_income = false;
// if there is an income taxable property use its value
if (Object.prototype.hasOwnProperty.call(skv_curr_income, 'is_relief')) {
should_include_income = skv_curr_income.is_relief;
}
return should_include_income;
});
}
/**
* Filter income by whether its provided by employer
* @param {array} arr_income
* @returns {array}
*/
static getEmployerProvidedIncomes(arr_income) {
return arr_income.filter((skv_curr_income) => skv_curr_income.is_provided_by_employer);
}
/**
* Filter income by whether national insurance contributions are due on it
* @param {array} arr_income
* @returns {array}
*/
static getEmployeeNICTaxableIncomes(arr_income) {
return arr_income.filter((skv_curr_income) => skv_curr_income.is_employee_nic_taxable);
}
/**
* Creates and populates the arr_income_tax array for a single year
* @param {array} arr_income
* @param {array} arr_tax_bands
* @returns {struct}
*/
static calcIncomeTax(
arr_income,
arr_tax_bands,
) {
let arr_income_tax_return = [];
// calculated income tax
const arr_income_tax_taxable = InjectorTaxes.calculateIncomeTaxTaxable(
arr_income,
arr_tax_bands,
);
// combine arrays
arr_income_tax_return = arr_income_tax_return.concat(arr_income_tax_taxable);
// work out any tax relief
const arr_income_tax_relief = InjectorTaxes.calculateIncomeTaxRelief(
arr_income,
// pass in the tax calculated salary tax bands
arr_income_tax_return[0].arr_tax_bands,
);
// combine arrays
arr_income_tax_return = arr_income_tax_return.concat(arr_income_tax_relief);
return arr_income_tax_return;
}
/**
* calculate income tax that is taxable
* @param {array} arr_income
* @param {array} arr_tax_bands
* @returns {array} array of income tax
*/
static calculateIncomeTaxTaxable(
arr_income,
arr_tax_bands,
) {
const arr_income_tax_return = [];
// the first band is the allowance
const skv_pa = arr_tax_bands[0];
const cnt_bands = arr_tax_bands.length;
// variables for original and remaining
const original_pa = skv_pa.limit;
let remaining_pa = original_pa;
// ratio for months in taxed year
// get the amount of money earnt this year
// this will be the full salary plus the vehicle/fuel benefits
// reduced by the coverage ratio
const gross_annual_income = InjectorTaxes.totalIncome(arr_income);
// If the total of all remuneration is greater than the
// Personal Allowance Restriction (£100,000 at time of writing)
// then reduce the personal allowance by the amount over the threshold,
// multiplied by the restriciton rate (0.5 at the time of writing)
// so originally for every £2 over the limit you lose £1 of PA
if (
gross_annual_income > skv_pa.restriction_threshold
) {
const over_limit = (
gross_annual_income - skv_pa.restriction_threshold
) * skv_pa.restriction_rate;
// remove amount over limit, but max sure it doesn't go negative
remaining_pa = Math.max(remaining_pa - over_limit, 0);
}
// // the reduction in personal allowance
const pa_diff = original_pa - remaining_pa;
// If this income is NOT incomeTaxable, don't include in taxable income
// Relief such as Mileage Allowance Relief (MAR) is
const arr_taxable_incomes = InjectorTaxes.getTaxableIncomes(arr_income);
const cnt_income = arr_taxable_incomes.length;
// total of income and the amount that has been
// taxed so far
let total_income = 0;
let total_income_taxed = 0;
// Loop through each of the items in the taxable income array
for (let i = 0; i < cnt_income; i += 1) {
const skv_curr_income = arr_taxable_incomes[i];
const skv_new_income_tax = InjectorTaxes.getTaxStartingStruct('income_tax', skv_curr_income);
skv_new_income_tax.tax_total = 0;
if (Object.prototype.hasOwnProperty.call(skv_curr_income, 'is_allowance_sacrifice_taxed')) {
skv_new_income_tax.is_allowance_sacrifice_taxed = skv_curr_income.is_allowance_sacrifice_taxed;
skv_new_income_tax.ulev_salsac_co2_threshold = skv_curr_income.ulev_salsac_co2_threshold;
}
const income_amount = skv_curr_income.value;
total_income += income_amount;
let income_left_to_tax = income_amount;
// we need to know the limit at the end of the previous band,
// which at the beginning/bottom bracket, is nothing.
let prev_limit = 0;
let tax_applied = 0;
// loop over the tax bands and work out tax
// note that the brackets may shift down as the personal allowance
// is lost (bottom bracket)
// the top backet does not shift, nor does it have a limit
// so we will process this outside of the loop at the end
for (let j = 0; j < cnt_bands - 1; j += 1) {
const band = arr_tax_bands[j];
tax_applied = 0;
// if we're not in the penultimate band, we need to reduce the
// band limit by the lost_pa (which is capped at total pa)
const band_shift = j !== (cnt_bands - 2) ? pa_diff : 0;
const shifted_limit = band.limit - band_shift;
let amount_to_tax = 0;
if (
total_income_taxed < shifted_limit
&& total_income_taxed < total_income
) {
// work out the maximum that can be taxed
const max_taxable_amount = shifted_limit
- Math.max(
prev_limit,
total_income_taxed,
);
// we tax the smaller of:
// the remainder of the salary
// or the space left in the bracket
amount_to_tax = Math.min(
income_left_to_tax,
max_taxable_amount,
);
// reduce the income left to tax
income_left_to_tax -= amount_to_tax;
// increase the total_income_taxed
total_income_taxed += amount_to_tax;
tax_applied = amount_to_tax * (band.percentage / 100);
skv_new_income_tax.tax_total += tax_applied;
}
// store this limit
// we need to store it even if we didn't tax it
// so we can skip up to the next bracket
prev_limit = shifted_limit;
skv_new_income_tax.arr_tax_bands.push(InjectorTaxes.addCalculatedTaxBand(
band,
amount_to_tax,
tax_applied,
));
} // END: looping bands
// if there is anything left to tax, it goes on the top rate
const top_band = arr_tax_bands[cnt_bands - 1];
// var top_label = capitaliseWord( top_band.label );
let top_tax_applied = 0;
// work out the amount to tax
if (income_left_to_tax > 0) {
top_tax_applied = income_left_to_tax * (top_band.percentage / 100);
}
skv_new_income_tax.arr_tax_bands.push(InjectorTaxes.addCalculatedTaxBand(
top_band,
income_left_to_tax,
top_tax_applied,
));
// add top tax to the total
skv_new_income_tax.tax_total += top_tax_applied;
// in order to work out average % rates loop back over the bands and
// work out the proportion of tax per band in relation to the total tax for the period
skv_new_income_tax.arr_tax_bands = InjectorTaxes.addProportionsToBands(skv_new_income_tax.arr_tax_bands, skv_new_income_tax.tax_total);
// tax_total is proportional to the year so the gross_value should be adjusted too when creating the net
const net_value = (skv_new_income_tax.gross_value) - skv_new_income_tax.tax_total;
skv_new_income_tax.net_value = net_value;
arr_income_tax_return.push(skv_new_income_tax);
} // END: looping incomes for tax
return arr_income_tax_return;
}
/**
* calculate income tax that is classed as relief
* @param {array} arr_income
* @param {array} arr_calculated_salary_tax_bands We need tax that has already
* been calculated on income tax to ensure the relief is deducted from the
* highest band with tax in it
* @returns {array} array of income tax
*/
static calculateIncomeTaxRelief(
arr_income,
arr_calculated_salary_tax_bands,
) {
const arr_income_tax_return = [];
// get only sources of income that is considered relief
const arr_relief_incomes = InjectorTaxes.getReliefIncomes(arr_income);
const cnt_relief_income = arr_relief_incomes.length;
// include any tax relief
// Loop through each of the items in the arr_income array
for (let i = 0; i < cnt_relief_income; i += 1) {
const skv_curr_income = arr_relief_incomes[i];
const skv_income_tax_relief = InjectorTaxes.getTaxStartingStruct('income_tax', skv_curr_income);
// skv_income_tax_relief['tax_total'] = 0;
skv_income_tax_relief.tax_relief_total = 0;
// find the last band being taxed upon
const top_tax_band_idx = InjectorTaxes.getTopBandWithTaxIndex(arr_calculated_salary_tax_bands);
const cnt_bands = arr_calculated_salary_tax_bands.length;
for (let j = 0; j < cnt_bands; j += 1) {
// passed in to the function as an argument
const { band } = arr_calculated_salary_tax_bands[j];
let gross_amount = 0;
// we're not worried about tax going across bands or tax being
// pushed in to other bands
if (j === top_tax_band_idx) {
gross_amount = skv_curr_income.value;
}
skv_income_tax_relief.arr_tax_bands.push({
band,
tax: 0,
tax_relief: gross_amount * (band.percentage / 100),
income: gross_amount,
percentage_of_total: (gross_amount < 0) ? 100 : 0,
});
// skv_income_tax_relief['tax_total'] -= skv_income_tax_relief.arr_tax_bands[j].tax_relief;
skv_income_tax_relief.tax_relief_total += skv_income_tax_relief.arr_tax_bands[j].tax_relief;
} // END: looping bands
arr_income_tax_return.push(skv_income_tax_relief);
} // END: looping incomes for relief
return arr_income_tax_return;
}
/**
* Creates and populates an array with EMPLOYER class1a national insurance contributions
* for a single year
* @param {array} arr_income
* @param {number} class_1A_rate
* @returns {array} array of employer national insurance contributions
*/
static calcEmployerNICSingleYear(
arr_income,
class_1A_rate,
) {
const arr_incomes = InjectorTaxes.getEmployerProvidedIncomes(arr_income);
const cnt_income = arr_incomes.length;
const arr_employer_nic_return = [];
// Loop through each of the items in the arrClass1ANICTaxable array
for (let i = 0; i < cnt_income; i += 1) {
const skv_income = arr_income[i];
const skv_new_nic = {};
let str_A = '';
if (skv_income.is_benefit) {
str_A = 'A';
}
skv_new_nic.name = `Class 1${str_A} NIC on ${skv_income.name}`;
skv_new_nic.gross_value = skv_income.value;
skv_new_nic.is_benefit = skv_income.is_benefit;
// Now calculate the actual tax
const tax_total = skv_new_nic.gross_value * (class_1A_rate / 100);
skv_new_nic.tax_total = tax_total;
arr_employer_nic_return.push(skv_new_nic);
}
return arr_employer_nic_return;
}
/**
* Creates and populates an array with EMPLOYEE national insurance contributions
* for a single year
* @param {array} arr_income
* @param {number} employee_nic_bands
* @returns {array} array of employer national insurance contributions
*/
static calcEmployeeNICSingleYear(
arr_income,
employee_nic_bands,
) {
const arr_incomes = InjectorTaxes.getEmployeeNICTaxableIncomes(arr_income);
const cnt_income = arr_incomes.length;
const arr_employee_nic_return = [];
// As we loop through the taxable items this value will store what has been taxed so far
let tax_total = 0;
// Loop through each of the items in the arrIncome array
for (let i = 0; i < cnt_income; i += 1) {
const skv_income = arr_incomes[i];
// build the base structure for nic struct
const skv_new_nic = InjectorTaxes.getTaxStartingStruct('employee_nic', skv_income);
const cnt_bands = employee_nic_bands.length;
// where we're going to put the updated bands with tax calculated
const arr_new_bands = [];
let running_total_income = skv_new_nic.gross_value;
let prev_limit = 0;
// loop through the bands except the top band which doesn't have a limit
// and anything else will be stuck in there
for (let j = 0; j < cnt_bands - 1; j += 1) {
const skv_band = employee_nic_bands[j];
// if we've got income that falls with the bounds of this band's limit - the previous limit
if (running_total_income > (skv_band.limit - prev_limit)) {
// we put the income up to the limit of the band less the
// income we've already accounted for
skv_band.income = skv_band.limit - prev_limit;
// and take it off the running total income for the next band
running_total_income -= skv_band.income;
} else {
// if the income is below the limit it means this is the
// the last band to add income to to be taxed
skv_band.income = running_total_income;
running_total_income = 0;
}
prev_limit = skv_band.limit;
skv_band.tax = skv_band.income * (skv_band.percentage / 100);
tax_total += skv_band.tax;
arr_new_bands.push(skv_band);
}
// get the penultimate band
const skv_prev_band = arr_new_bands[cnt_bands - 2];
prev_limit = skv_prev_band.limit;
// work out what income is left to tax
const amount_left_to_tax = skv_income.value - prev_limit;
const skv_band = employee_nic_bands[cnt_bands - 1];
// default to nothing
skv_band.income = 0;
skv_band.tax = 0;
// anything left to tax?
if (amount_left_to_tax > 0) {
skv_band.income = amount_left_to_tax - skv_band.limit;
skv_band.tax = skv_band.income * (skv_band.percentage / 100);
}
tax_total += skv_band.tax;
arr_new_bands.push(skv_band);
// include the calculated tax bands
skv_new_nic.arr_tax_bands = arr_new_bands;
skv_new_nic.tax_total = tax_total;
skv_new_nic.net_value = skv_new_nic.gross_value - tax_total;
arr_employee_nic_return.push(skv_new_nic);
}
return arr_employee_nic_return;
}
}
export {
InjectorTaxes,
};