import { Injector } from './injector';
/**
* Creates a company vehicle injector that covers company vehicle benefit and fuel benefit
* @class
*/
class InjectorCompanyVehicle extends Injector {
/**
* @param {struct} skv_config Struct passed in when initializing the injector
* @param {array} arr_tax_years Array of tax years to add basic salary to
*/
constructor(skv_config = {}, arr_tax_years = []) {
super();
// they keys we definitely need
this.required_keys = [
'co2_after_options',
'fuel_type',
];
const rst_required_keys = InjectorCompanyVehicle.requiredKeysCheck(skv_config, this.required_keys);
// check to see if any keys missing
if (
!rst_required_keys.success
|| rst_required_keys.data.length > 0
) {
throw new Error(`Missing required keys: ${rst_required_keys.data} for InjectorCompanyVehicle`);
}
this.skv_default_config = {
// required key, don't set a proper default
// co2gpkm emissions after options have been added
co2_after_options: null,
// required key, don't set a proper default
// For determining the correct benefit field. Currently default/electric
fuel_type: '',
// required for cars not for vans
p11d_inc_taxable_options: null,
// for whether to include fuel benefit
with_fuel: false,
// taxed as company car or private van?
income_tax_group: 'company car',
// does vehicle meets euro 6d standards
is_euro_6d: false,
// Is the private mileage large enough to warrent being taxed (vans only)
is_significant_priv_mileage: false,
// An amount that the employee can pay to reduce the BIK value
annual_contribution: 0,
// An amount that the employee can pay to reduce the BIK value
capital_contribution: 0,
// Either the car allowance or the amount of salary sacrificed. Leave 0 if neither apply
allowance_sacrifice_total: 0,
// Amount of allowance/salsac used towards fuel benefit
allowance_sacrifice_fuel: 0,
// Setting to true will make sure fuel benefit structure always included
explicit_zero_fuel: false,
// market value for private use assets
market_value: null,
};
this.skv_config = this.skv_default_config;
const keys = Object.keys(skv_config);
// overwrite the default keys with the config passed in
keys.forEach((key) => {
this.skv_config[key] = skv_config[key];
});
this.arr_tax_years = arr_tax_years;
// apply the basic initial structs to income section of arr_tax_years
// vehicle always present
this.arr_tax_years = InjectorCompanyVehicle.setup(this.skv_config, this.arr_tax_years, 'vehicle');
// work out whether to include fuel benefit
if (
this.skv_config.with_fuel
|| this.skv_config.explicit_zero_fuel
) {
this.arr_tax_years = InjectorCompanyVehicle.setup(this.skv_config, this.arr_tax_years, 'fuel');
}
this.arr_tax_years = InjectorCompanyVehicle.calculate(this.skv_config, this.arr_tax_years);
// expose functions
this.adjustBIKbySalarySacrifice = InjectorCompanyVehicle.adjustBIKbySalarySacrifice;
this.calculateStandardBIK = InjectorCompanyVehicle.calculateStandardBIK;
}
/**
* Calculate benefit in kind
* @param {struct} skv_config
* @param {array} arr_tax_years Basic tax years
* @returns {array} Amended tax years with benefit calculated
*/
static calculate(skv_config, arr_tax_years) {
const cnt_arr_tax_years = arr_tax_years.length;
const arr_return = [];
for (let i = 0; i < cnt_arr_tax_years; i += 1) {
const skv_curr_year = arr_tax_years[i];
const { arr_income } = skv_curr_year;
const cnt_arr_income = arr_income.length;
for (let j = 0; j < cnt_arr_income; j += 1) {
const skv_curr_income = arr_income[j];
if (
skv_curr_income.name === 'Company Vehicle Benefit'
|| skv_curr_income.name === 'Private Fuel Benefit'
) {
const skv_new_income = this.calculateSingleYear(
skv_config,
skv_curr_income,
skv_curr_year.skv_tax_company_vehicle,
skv_curr_year.months_occupied,
);
// over write the original income struct
skv_curr_year.arr_income[j] = skv_new_income;
}
}
arr_return.push(skv_curr_year);
}
return arr_return;
}
/**
* Description
* @param {string} str_type fuel / vehicle
* @param {struct} skv_tax_charges
* @returns {number}
*/
static getBenefit(
str_type,
skv_tax_charges,
) {
// get vehicle or fuel or private use asset benefit struct
if (str_type === 'fuel') {
return skv_tax_charges.fuel_benefit;
} if (skv_tax_charges.vehicle_benefit) {
return skv_tax_charges.vehicle_benefit;
}
return skv_tax_charges.private_use_asset_benefit;
}
/**
* work out the vehicle or fuel bik (no allowance sacrifice or anything
* complicated like that)
* @param {string} str_type fuel or vehicle
* @param {struct} skv_tax_company_vehicle
* @returns {struct} bik, bik_capcon_diff, bik_anncon_diff
*/
static calculateStandardBIK(
str_type,
skv_config,
skv_tax_company_vehicle,
) {
const bik = InjectorCompanyVehicle.getBenefit(
str_type,
skv_tax_company_vehicle,
);
// bik diff will be a reduction to the salary sacrifice/foregone cash
// benefit, based on capital contributions
let bik_capcon_diff = 0;
let bik_anncon_diff = 0;
if (
skv_config.income_tax_group === 'company car'
&& str_type === 'vehicle'
) {
const co2_percentage = skv_tax_company_vehicle.co2_charge_percentage;
// bik = skv_config.p11d_inc_taxable_options * (co2_percentage / 100);
let bik_less_capcon = (skv_config.p11d_inc_taxable_options - skv_config.capital_contribution) * (co2_percentage / 100);
// ensure bik is not negative
bik_less_capcon = Math.max(0, bik_less_capcon);
// work out the difference in the bik before and after contributions
bik_capcon_diff = bik - bik_less_capcon;
// car benefit will need the ann_con reduction
bik_anncon_diff = skv_config.annual_contribution;
}
return {
bik,
bik_capcon_diff,
bik_anncon_diff,
};
}
/**
* Description
* @param {string} str_type fuel / vehicle
* @param {struct} skv_config
* @param {struct} skv_bik
* @param {number} ulev_salsac_co2_threshold
* @returns {struct} bik and allowance sacrificed
*/
static adjustBIKbySalarySacrifice(
str_type,
skv_config,
skv_bik,
ulev_salsac_co2_threshold,
) {
const original_bik = skv_bik.bik;
const { bik_capcon_diff } = skv_bik;
const skv_return = {
bik: original_bik,
is_allowance_sacrifice_taxed: false,
};
// get the salsac/allowance value, which may replace the bik
let bik_salsac = skv_config.allowance_sacrifice_total;
// if it's fuel, use fuel salsac, otherwise remove fuel amount from total
if (str_type === 'fuel') {
bik_salsac = skv_config.allowance_sacrifice_fuel;
} else {
bik_salsac -= skv_config.allowance_sacrifice_fuel;
}
// if an allowance has been offered but not taken,
// or salary has been sacrificed, that must be taxed instead of
// the vehicle BIK as long as the car meets certain conditions:
// In Jan 2017: car must be taken after April 2017, 75g/km+
// (OPRA rules)
// check the salsac has been entered and if it's larger
if (
bik_salsac
&& skv_config.co2_after_options > ulev_salsac_co2_threshold
) {
if (bik_salsac > original_bik) {
skv_return.is_allowance_sacrifice_taxed = true;
// if the salsac is higher, use that instead of bik
// we still need to take off any potential savings
// a capcon would have made to the bik
skv_return.bik = bik_salsac - bik_capcon_diff;
}
}
// if sal sac has not been used, return the normal bik
// less any contributions
if (
!skv_return.is_allowance_sacrifice_taxed
&& str_type === 'vehicle'
) {
// after the comparison has been made, we can revert to
// using the bik with the capitalContribution deducted
skv_return.bik = original_bik - bik_capcon_diff;
}
return skv_return;
}
/**
* Calculate the benefit for a single year - work out elec/van/salsac stuff
* @param {struct} skv_config
* @param {struct} skv_income
* @param {struct} skv_tax_charges
* @returns {struct} Completed income struct
*/
static calculateSingleYear(skv_config, skv_income, skv_tax_company_vehicle, months_occupied) {
// the struct to return starting with the basic of the income struct
// passed in
const skv_return = skv_income;
const str_type = skv_income.name === 'Company Vehicle Benefit'
? 'vehicle'
: 'fuel';
let bik = 0;
// if we don't need fuel ben, but do need a 0 fuel value
// or
// Vehicle benefit on a van is only taxable
// if there is significant private mileage
if (
(
str_type === 'fuel'
&& !skv_config.with_fuel
&& skv_config.explicit_zero_fuel
) || (
skv_config.is_van
&& !skv_config.is_significant_priv_mileage
)
) {
skv_return.value = bik;
return skv_return;
}
// work out the benefit in kind before any allowance sacrifice rules
// are taken in to account later on
const skv_result_bik = InjectorCompanyVehicle.calculateStandardBIK(
str_type,
skv_config,
skv_tax_company_vehicle,
);
bik = skv_result_bik.bik;
// bik diff will be a reduction to the salary sacrifice/foregone cash
// benefit, based on capital contributions
const { bik_anncon_diff } = skv_result_bik;
skv_return.original_bik = bik;
// work out the adjustment to the benefit in kind based on vehicle or fuel
// allowance, whether its a ulev
const skv_adjusted_bik = InjectorCompanyVehicle.adjustBIKbySalarySacrifice(
str_type,
skv_config,
skv_result_bik,
skv_tax_company_vehicle.ulev_salsac_co2_threshold,
);
// use the adjusted bik
bik = skv_adjusted_bik.bik;
// whatever has happened, salsac or not, we need to remove
// the annual annualContribution for car benefit (not fuel, not vans)
bik -= bik_anncon_diff;
// make sure benefit is not negative
bik = Math.max(bik, 0);
// ratio for months in taxed year
const coverage_ratio = months_occupied / 12;
// Cars are always considered in the Employees tax and Employers 1A NIC
skv_return.value = bik * coverage_ratio;
// update the income struct
skv_return.is_allowance_sacrifice_taxed = skv_adjusted_bik.is_allowance_sacrifice_taxed;
return skv_return;
}
/**
* Do the setup to get basic structs for income
* @param {struct} skv_config Struct passed in when initializing the injector
* @param {array} arr_tax_years Array of tax years to add basic salary to
* @returns {array} Amended tax years
*/
static setup(skv_config, arr_tax_years, type) {
const arr_temp = arr_tax_years;
const cnt_arr_temp = arr_temp.length;
for (let i = 0; i < cnt_arr_temp; i += 1) {
const skv_return = {};
skv_return.name = type === 'vehicle' ? 'Company Vehicle Benefit' : 'Private Fuel Benefit';
skv_return.is_benefit = true;
skv_return.is_employee_nic_taxable = false;
skv_return.is_provided_by_employer = true;
skv_return.is_taxed_at_source = false;
skv_return.is_relief = false;
skv_return.is_allowance_sacrifice_taxed = false;
if (!Object.prototype.hasOwnProperty.call(arr_temp[i], 'arr_income')) {
arr_temp[i].arr_income = [];
}
arr_temp[i].arr_income.push(skv_return);
}
return arr_temp;
}
}
export {
InjectorCompanyVehicle,
};