import { formatStruct } from '../../format';
import { averageFinanceCost } from '../../calculations/finance';
import * as utils from './utils';
import * as configuration from './configuration';
/**
* Extra salary sacrifice calculations to the ones done in tool/forecast
* @module
* @since 0.1.0
*/
/**
* Calculates what the salary sacrifice needs to be in order for the employer to have no difference
* @param {struct} skv_config
* @param {array} arr_tax_charges income tax, nic rates for the period retrieved from pluto
* @param {array} arr_tax_company_vehicle vehicle benefit, co2 percentage charge for the period retrieved from pluto
* @returns {array} Tax years with extra salary sacrifice info
*/
const calculateEmployerBreakEvenPoint = (
skv_config,
arr_tax_charges,
arr_tax_company_vehicle,
) => {
const arr_return = [];
const config = JSON.parse(JSON.stringify(skv_config));
const { skv_price } = config;
// check whether employer or employee pays for insurance
if (!config.employer_pays_insurance) {
config.insurance_cost_pa = 0;
}
if (!config.num_total_options_net) {
config.num_total_options_net = 0;
}
// run the forecast injectors
const arr_tax_years = utils.getTaxYears(
config,
arr_tax_charges,
arr_tax_company_vehicle,
);
let employer_break_even = 0;
const num_years = skv_price.months / 12;
const cnt_tax_years = arr_tax_years.length;
for (let i = 0; i < cnt_tax_years; i += 1) {
const skv_tax_year = arr_tax_years[i];
// get vat rate
const vat_percentage = skv_tax_year.skv_tax_charges.vat[0].standard;
// calculations to include in a new struct on array tax years
// essentially the impact on the employer
const skv_return = {};
// ratio for months in taxed year
const coverage_ratio = skv_tax_year.months_occupied / 12;
// work out the bch cost and irrecoverable vat
const skv_bch_result = utils.calculateBCHcost(
{
finance_cost: skv_price.finance_cost,
service_fee: skv_price.service_fee,
// get options in to months so its the same as finance cost and service fee
num_options_net: config.num_total_options_net / (num_years * 12),
vat_percentage,
employer_can_reclaim_vat_on_bch: config.employer_can_reclaim_vat_on_bch,
},
);
// cost of bch for this tax year (includes options)
const bch_avg_pm = averageFinanceCost(
{
price: skv_bch_result.bch_cost,
initial_payment: skv_price.initial_payment,
months: skv_price.months,
},
).per_month;
skv_return.bch_cost_total = bch_avg_pm * skv_tax_year.months_occupied;
// irrecoverable vat for the entire term
const non_recoverable_vat_avg_pm = averageFinanceCost(
{
price: skv_bch_result.non_recoverable_vat,
initial_payment: skv_price.initial_payment,
months: skv_price.months,
},
).per_month;
// irrecoverable vat for this tax year
skv_return.non_recoverable_vat = non_recoverable_vat_avg_pm * skv_tax_year.months_occupied;
// add the vat to the avg bch cost to get avg pch
// const pch_cost = avg_bch_cost_pa * (1 + vat_dec);
// const pch_cost_pm = pch_cost / 12;
// const pch_cost_total = pch_cost * coverage_ratio;
skv_return.insurance_cost = config.insurance_cost_pa * coverage_ratio;
// skv_return.bch_contingency = skv_return.bch_cost_total * (config.bch_contingency_percentage / 100);
skv_return.early_termination_ins = skv_return.bch_cost_total
+ skv_return.insurance_cost
+ skv_return.non_recoverable_vat;
skv_return.early_termination_ins *= (config.early_termination_ins_percent / 100);
skv_return.insurance_premium_tax = skv_return.early_termination_ins * (config.insurance_premium_tax_percentage / 100);
// The employer may choose to keep the saving in Employers National Insurance on the salary sacrifice,
// or it might choose to include the saving in calculating a neutral impact,
// Or there is the Net approach where the Employer keeps the NI saving on the sacrifice less the cost of the additional Class 1A NI on the car.
skv_return.company_vehicle_ben_nic = config.employer_keeps_ni === 'net' ? 0 : skv_tax_year.arr_employer_nic[1].tax_total;
skv_return.employer_net_vehicle_ben_nic_deduction = config.employer_keeps_ni !== 'net' ? 0 : skv_tax_year.arr_employer_nic[1].tax_total;
skv_return.fuel_ben_nic = 0;
if (config.with_private_fuel) {
skv_return.fuel_ben_nic = skv_tax_year.arr_employer_nic[2].tax_total;
}
// Charger installation cost should only be available for electrics if override set
let { charger_installation_cost } = config;
if (!config.charger_installation_available_for_phev) {
charger_installation_cost = arr_tax_company_vehicle[0].fuel_type === 'electric' ? charger_installation_cost : 0;
}
// only include charger installaition cost in the first year
skv_return.charger_installation_cost = i === 0
? charger_installation_cost
: 0;
skv_return.total = skv_return.bch_cost_total
+ skv_return.non_recoverable_vat
+ skv_return.insurance_cost
+ skv_return.charger_installation_cost
+ skv_return.early_termination_ins
+ skv_return.insurance_premium_tax
+ skv_return.company_vehicle_ben_nic
+ skv_return.fuel_ben_nic;
const er1anic_perc_dec = skv_tax_year.skv_tax_charges.national_insurance.employer.class1A / 100;
const er1anic_perc_int = 1 + er1anic_perc_dec;
skv_return.employer_break_even = skv_return.total / er1anic_perc_int;
employer_break_even += skv_return.employer_break_even;
skv_tax_year.skv_salary_sacrifice = {
skv_impact_on_er: skv_return,
// pch_cost_pm,
// pch_cost_total,
};
arr_return.push(skv_tax_year);
}
const employer_break_even_avg_pm = employer_break_even / config.months;
const employer_break_even_avg_yr = employer_break_even_avg_pm * 12;
return {
arr_tax_years: arr_return,
skv_salary_sacrifice_totals: {
employer_break_even,
employer_break_even_avg_pm,
employer_break_even_avg_yr,
employer_ni_savings: 0,
},
};
};
/**
* Description
* @param {data_type} param_name Description
* @returns {data_type} Description
*/
const getDifference = (
config,
arr_tax_years_no_salsac,
arr_tax_years_with_salsac,
) => {
const arr_return = [];
const cnt_tax_years = arr_tax_years_no_salsac.length;
const totals = {
employee: {
impact: 0,
},
employer: {
impact: 0,
},
};
for (let i = 0; i < cnt_tax_years; i += 1) {
const skv_yr_no_salsac = arr_tax_years_no_salsac[i];
const skv_yr_with_salsac = arr_tax_years_with_salsac[i];
// start with the standard tax year date info
const skv_return = {
tax_year: skv_yr_no_salsac.tax_year,
year_short: skv_yr_no_salsac.year_short,
year_slash: skv_yr_no_salsac.year_slash,
year_text: skv_yr_no_salsac.year_text,
date_end: skv_yr_no_salsac.date_end,
months_occupied: skv_yr_no_salsac.months_occupied,
description: skv_yr_no_salsac.description,
};
const coverage_ratio = arr_tax_years_with_salsac[i].months_occupied / 12;
/*
EMPLOYEE
*/
// changes to employee
skv_return.employee = {
salary: (skv_yr_no_salsac.arr_income[0].value - skv_yr_with_salsac.arr_income[0].value) * -1,
income_tax_on_salary: (skv_yr_no_salsac.arr_income_tax[0].tax_total - skv_yr_with_salsac.arr_income_tax[0].tax_total),
company_car_tax: skv_yr_with_salsac.arr_income_tax[1].tax_total * -1,
income_tax_on_fuel_ben: skv_yr_with_salsac.arr_income_tax[2].tax_total * -1,
insurance_cost: config.employer_pays_insurance ? 0 : (config.insurance_cost_pa * coverage_ratio) * -1,
amaps: (config.amaps * coverage_ratio) * -1,
nic_on_salary: (skv_yr_no_salsac.arr_employee_nic[0].tax_total - skv_yr_with_salsac.arr_employee_nic[0].tax_total),
net_pay: (skv_yr_no_salsac.totals.income.net_pay - skv_yr_with_salsac.totals.income.net_pay) * -1,
};
// separate subtotal as insurance isn't a part of the tax forecasters
skv_return.employee.total_impact = skv_return.employee.net_pay
+ skv_return.employee.insurance_cost
+ skv_return.employee.amaps;
// format entire struct to a currency type
skv_return.employee = formatStruct(skv_return.employee, 'currency', false);
/*
EMPLOYEE - TOTAL for entire period
*/
// convert object to key's array
let keys = Object.keys(skv_return.employee);
// iterate over object
keys.forEach((key) => {
// if we haven't added the key yet, default to 0
if (totals.employee[key] === undefined) {
totals.employee[key] = 0;
}
totals.employee[key] += skv_return.employee[key];
});
/*
EMPLOYER
*/
const { skv_impact_on_er } = skv_yr_with_salsac.skv_salary_sacrifice;
// Charger installation cost should only be available for electrics if override set
let { charger_installation_cost } = skv_impact_on_er;
if (!config.charger_installation_available_for_phev) {
charger_installation_cost = config.fuel_type === 'electric' ? skv_impact_on_er.charger_installation_cost : 0;
}
// changes to employer
skv_return.employer = {
salary: skv_yr_no_salsac.arr_income[0].value - skv_impact_on_er.salary,
nic_on_salary: skv_yr_no_salsac.arr_employer_nic[0].tax_total - skv_yr_with_salsac.arr_employer_nic[0].tax_total,
bch_cost_total: skv_impact_on_er.bch_cost_total * -1,
non_recoverable_vat: skv_impact_on_er.non_recoverable_vat * -1,
insurance_cost: skv_impact_on_er.insurance_cost * -1,
charger_installation_cost: charger_installation_cost * -1,
early_termination_ins: skv_impact_on_er.early_termination_ins * -1,
ins_premium_tax: skv_impact_on_er.insurance_premium_tax * -1,
company_vehicle_ben_nic: skv_impact_on_er.company_vehicle_ben_nic * -1,
fuel_ben_nic: skv_impact_on_er.fuel_ben_nic * -1,
cost_of_benefits: skv_impact_on_er.total * -1,
vehicle_ben_nic_savings_deduction: skv_impact_on_er.employer_net_vehicle_ben_nic_deduction,
};
skv_return.employer.savings_pool = (skv_return.employer.salary
+ skv_return.employer.nic_on_salary)
- skv_impact_on_er.employer_net_vehicle_ben_nic_deduction;
skv_return.employer.total_impact = skv_return.employer.savings_pool
+ skv_return.employer.cost_of_benefits;
// format entire struct to a currency type
skv_return.employer = formatStruct(skv_return.employer, 'currency', false);
/*
EMPLOYER - TOTAL for entire period
*/
// convert object to key's array
keys = Object.keys(skv_return.employer);
// iterate over object
keys.forEach((key) => {
// if we haven't added the key yet, default to 0
if (totals.employer[key] === undefined) {
totals.employer[key] = 0;
}
totals.employer[key] += skv_return.employer[key];
});
arr_return.push(skv_return);
}
return {
totals,
arr_differences: arr_return,
};
};
/**
* Determine what the tax years calcutions will be with and without the salary sacrifice
* @param {struct} skv_config
* @param {array} arr_tax_charges
* @param {array} arr_tax_company_vehicle
* @returns {array}
*/
const calculate = (
skv_config,
arr_tax_charges,
arr_tax_company_vehicle,
) => {
const init_config = configuration.create(skv_config, 'init');
const local_arr_tax_charges = JSON.parse(JSON.stringify(arr_tax_charges));
/*
CALCULATE WITH NO SALARY SACRIFICE
*/
const no_salsac_config = configuration.create(skv_config, 'without_salsac');
// calculate the tax years with no salary sacrifice for the employee
const arr_tax_years_no_salsac = utils.getTaxYears(
no_salsac_config,
local_arr_tax_charges,
arr_tax_company_vehicle,
);
/*
CALCULATE THE EMPLOYER'S BREAK EVEN POINT
*/
// calculate the employer break even point
const rst_tax_years_and_break_point = calculateEmployerBreakEvenPoint(
init_config,
local_arr_tax_charges,
arr_tax_company_vehicle,
);
const arr_tax_years_breakpoint = rst_tax_years_and_break_point.arr_tax_years;
/*
WORK OUT EFFECT OF SACRIFICE ON SALARY (inc whether employer is keeping ni savings)
*/
let { skv_salary_sacrifice_totals } = rst_tax_years_and_break_point;
// save the original employer break even point to compare to updated break even
// later
const original_employer_break_even_avg_yr = skv_salary_sacrifice_totals.employer_break_even_avg_yr;
// single number for break even point average across the years
const employer_break_even_avg_yr = utils.applyEmployerKeepsNIpercentage(
'updated',
init_config.employer_keeps_ni,
init_config.employer_keeps_ni_percentage,
skv_salary_sacrifice_totals.employer_break_even_avg_yr,
arr_tax_years_no_salsac,
);
// start a new config to calculate tax years with salsac
const config_with_salsac = configuration.create(
init_config,
'with_salsac',
{
employer_break_even_avg_yr,
},
);
/*
CALCULATE WITH SALARY SACRIFICE
*/
// calculate the tax years again with the salary sacrifice for the employee
const arr_tax_years_with_salsac = utils.getTaxYears(
config_with_salsac,
local_arr_tax_charges,
arr_tax_company_vehicle,
);
const cnt_years = arr_tax_years_breakpoint.length;
// update salsac tax years with amended salary with sacrifice from earlier
for (let i = 0; i < cnt_years; i += 1) {
const coverage_ratio = arr_tax_years_with_salsac[i].months_occupied / 12;
arr_tax_years_with_salsac[i].skv_salary_sacrifice = arr_tax_years_breakpoint[i].skv_salary_sacrifice;
const employer_ni_savings = (employer_break_even_avg_yr - original_employer_break_even_avg_yr) * coverage_ratio;
arr_tax_years_with_salsac[i].skv_salary_sacrifice.skv_impact_on_er.employer_ni_savings = employer_ni_savings;
// include the salary with employer ni savings included
arr_tax_years_with_salsac[i].skv_salary_sacrifice.skv_impact_on_er.employer_break_even = employer_break_even_avg_yr * coverage_ratio;
arr_tax_years_with_salsac[i].skv_salary_sacrifice.skv_impact_on_er.salary = (init_config.salary_pa - employer_break_even_avg_yr) * coverage_ratio;
// update totals with new salary sacrifice and ni savings
skv_salary_sacrifice_totals.employer_ni_savings += employer_ni_savings;
skv_salary_sacrifice_totals.employer_break_even_avg_yr = employer_break_even_avg_yr;
skv_salary_sacrifice_totals.employer_break_even_avg_pm = employer_break_even_avg_yr / 12;
}
/*
WORK OUT THE DIFFERENCE BETWEEN WITH AND WITHOUT SALARY SACRIFICE
*/
// find the differences between having salary sacrifice and put in an easy
// to use form
const skv_tax_years_difference = getDifference(
init_config,
arr_tax_years_no_salsac,
arr_tax_years_with_salsac,
);
/*
SOME ADDIIONAL TOTALS
*/
skv_salary_sacrifice_totals.employee = skv_tax_years_difference.totals.employee;
skv_salary_sacrifice_totals.employee.total_impact_avg_pm = skv_tax_years_difference.totals.employee.total_impact / init_config.months;
skv_salary_sacrifice_totals.employee.total_impact_avg_pa = skv_salary_sacrifice_totals.employee.total_impact_avg_pm * 12;
skv_salary_sacrifice_totals.employee.net_pay_pm = skv_tax_years_difference.totals.employee.total_impact / init_config.months;
skv_salary_sacrifice_totals.employee.net_pay_pa = skv_salary_sacrifice_totals.employee.net_pay_pm * 12;
skv_salary_sacrifice_totals.employer = skv_tax_years_difference.totals.employer;
skv_salary_sacrifice_totals.employer.total_impact_avg_pm = skv_tax_years_difference.totals.employer.total_impact / init_config.months;
skv_salary_sacrifice_totals.employer.total_impact_avg_pa = skv_salary_sacrifice_totals.employer.total_impact_avg_pm * 12;
// format the totals
skv_salary_sacrifice_totals = formatStruct(skv_salary_sacrifice_totals, 'currency', false);
return {
arr_no_salsac: arr_tax_years_no_salsac,
arr_with_salsac: arr_tax_years_with_salsac,
arr_difference: skv_tax_years_difference.arr_differences,
skv_salary_sacrifice_totals,
};
};
export {
calculateEmployerBreakEvenPoint,
calculate,
};