import { Equipment } from './equipment';
import { State } from './state';
/**
* Creates a new equipment configurator.
* @class
*/
class Configurator {
/**
* @param {struct} skv_config
* @param {struct} skv_data Most likely data taken directly from Pluto
*/
constructor(skv_config = {}, skv_data = {}) {
const skv_config_defaults = {
skv_lease_terms: {},
cost_field: 'cost_net',
show_as_swatch: {
paint: false,
},
panels: {
paint: false,
'options-list': true,
},
vat_percentage: 20,
arr_preselected_options_ids: [],
skv_vehicle: {
battery_range: '',
battery_range_teh: '',
},
adjustment_percentage: 0,
is_salsac: false,
// only used for salsac. insurance companies like Allianz cannot reclaim vat
salsac_employer_can_reclaim_vat: true,
max_gross_total_price_limit: null,
max_gross_price_item_limit: null,
};
// add keys to the defaults
Object.keys(skv_config).forEach((key_top_level) => {
// if we've got any lease terms being passed in include the lease term struct too
if (
key_top_level === 'skv_lease_terms'
&& Object.keys(skv_config.skv_lease_terms).length
) {
Object.keys(skv_config).forEach((key_lease_term) => {
skv_config_defaults.skv_lease_terms[key_lease_term] = skv_config.skv_lease_terms[key_lease_term];
});
}
// if we've got any swatch enabling
if (
key_top_level === 'show_as_swatch'
&& Object.keys(skv_config.show_as_swatch).length
) {
Object.keys(skv_config).forEach((key) => {
skv_config_defaults.show_as_swatch[key] = skv_config.show_as_swatch[key];
});
}
// if we've got any extra panel requirements
if (
key_top_level === 'panels'
&& Object.keys(skv_config.panels).length
) {
Object.keys(skv_config.panels).forEach((key_panel) => {
skv_config_defaults.panels[key_panel] = skv_config.panels[key_panel];
});
}
// pass in vehicle details
if (
key_top_level === 'skv_vehicle'
&& Object.keys(skv_config.skv_vehicle).length
) {
Object.keys(skv_config.skv_vehicle).forEach((key) => {
skv_config_defaults.skv_vehicle[key] = skv_config.skv_vehicle[key];
});
// we need to cap co2 in between the tel and teh values so check
// we have properties passed in
if (
!Object.prototype.hasOwnProperty.call(skv_config.skv_vehicle, 'wltp_co2_tel')
|| !Object.prototype.hasOwnProperty.call(skv_config.skv_vehicle, 'wltp_co2_teh')
|| !Object.prototype.hasOwnProperty.call(skv_config.skv_vehicle, 'wltp_co2')
) {
throw new Error('Vehicle WLTP CO2, WLTP CO2 TEL and WLTP CO2 TEH figures are required to be passed in to the equipment configurator');
}
}
skv_config_defaults[key_top_level] = skv_config[key_top_level];
});
// work out whether prices will be in gross or net
if (
!skv_config_defaults.is_fqt
|| (
skv_config_defaults.is_fqt
&& skv_config.skv_lease_terms.agreement_type_code === 'pch'
)
) {
skv_config_defaults.cost_field = 'cost_gross';
}
this.skv_config = skv_config_defaults;
this.skv_config.num_VAT_factor = 1 + (this.skv_config.vat_percentage / 100);
this.equipment_map = [];
this.open_info_toggle_state = [];
this.open_dependency_toggle_state = [];
// when events are fired what to provide as the action
this.event_action = 'init';
this.setupData(this.skv_config, skv_data);
// expose functions
this.calculateSalsacCost = Configurator.calculateSalsacCost;
}
/**
* Combine the data in to one single equipment array
* @param {struct} skv_config
* @returns {struct} Pluto API data
*/
setupData(skv_config, skv_data) {
const arr_equipment = [];
// standard or option
Object.keys(skv_data).forEach((key) => {
const type = key;
const cnt_groups = skv_data[type].length;
for (let i = 0; i < cnt_groups; i += 1) {
const skv_group = skv_data[type][i];
let arr_temp_equipment = skv_group.items;
// give a option/standard type to all the options and standards
// and return array
arr_temp_equipment = arr_temp_equipment.filter((skv_equipment) => {
const skv_return = skv_equipment;
skv_return.type = type === 'standards' ? 'standard' : 'option';
return skv_return;
});
// for finance we need to remove options like warranties or
// grants because they have already been taken in to account by
// the lendor so filter out any options that have "is_finance_allowed"
// set to false
if (skv_config.is_fqt) {
arr_temp_equipment = arr_temp_equipment.filter((skv_equipment) => skv_equipment.is_finance_allowed);
}
// put all the standards / options from a group in to to the overall
// equipment array
// use spread operator to add array to another array
arr_equipment.push(...arr_temp_equipment);
}
});
const arr_packed = arr_equipment.filter((skv_equipment) => skv_equipment.is_packed);
// create empty array and load equipment into it
this.equipment = this.createEquipmentObjectsAndMap(skv_config, arr_equipment);
// some packed equipment may need its price modifying due to tax or finance
this.equipment = Configurator.updatePackedEquipmentCost(skv_config, this.equipment, this.equipment_map);
// set initial states
this.states = {
current: new State(this.equipment, this.equipment_map),
undo: new State(this.equipment, this.equipment_map),
initial: new State(this.equipment, this.equipment_map),
};
// if we have pre selected options, loop through each and process their
// dependencies
const cnt_preselected_equipment = skv_config.arr_preselected_options_ids.length;
for (let i = 0; i < cnt_preselected_equipment; i += 1) {
const num_option_id = skv_config.arr_preselected_options_ids[i];
const equipment_idx = this.equipment_map[num_option_id];
this.states.current.process(
equipment_idx,
true,
);
}
// if we have packed options, loop through each and process their
// dependencies
const cnt_arr_packed = arr_packed.length;
for (let i = 0; i < cnt_arr_packed; i += 1) {
const num_option_id = arr_packed[i].id;
const equipment_idx = this.equipment_map[num_option_id];
this.states.current.process(
equipment_idx,
true,
);
}
// update the current state with display costs
this.addAdditionalStateData('current');
}
/**
* remove equipment that isn't allowed for example those that fall outside
* option price limiting
* @param {struct} skv_config
* @param {array} arr_equipment Flatted array of equipment structs (standards and options)
* @returns {array} Array of any remaining equipment items
*/
static removeNotAllowedEquipment(skv_config, arr_equipment) {
const arr_return = [];
// create a map of ids that are outside the price limit
const remove_ids_map = arr_equipment.map((item) => {
// if the item is packed the cost is a part of the vehicle itself
if (
item.type === 'option'
&& !item.is_packed
) {
// if equipment is over total price limit don't show it as an available option
if (
typeof skv_config.max_gross_total_price_limit === 'number'
&& item.cost_gross > skv_config.max_gross_total_price_limit
) {
return item.id;
}
// if equipment is over item price limit don't show it as an available option
if (
typeof skv_config.max_gross_price_item_limit === 'number'
&& item.cost_gross > skv_config.max_gross_price_item_limit
) {
return item.id;
}
}
return null;
// remove any undefined values
}).filter((e) => e);
// if the equipment or any of its dependencies IDs is in the remove_ids_map
// then remove from dependencies and/or the equipment itself that we will return
arr_equipment.forEach((skv_equipment) => {
const skv_new_equipment = skv_equipment;
// may not have affected_by if running from unit tests
if (skv_equipment.affected_by) {
// only include "affected by" equipment that aren't in the remove ids map
skv_new_equipment.affected_by = skv_equipment.affected_by.filter((item) => !remove_ids_map.includes(item.id));
}
// may not have affects if running from unit tests
if (skv_equipment.affects) {
skv_new_equipment.affects = skv_equipment.affects.filter((item) => !remove_ids_map.includes(item.id));
}
// only add the equipment if its not to be removed
if (!remove_ids_map.includes(skv_new_equipment.id)) {
arr_return.push(skv_new_equipment);
}
});
return arr_return;
}
/**
* packed equipment may need its price modifying due to tax or finance
* @param {array} equipment
* @returns {struct}
*/
static updatePackedEquipmentCost(skv_config, equipment, equipment_map) {
const arr_returned_equipment = equipment;
const arr_packed_equipment = equipment.filter((skv_equipment) => skv_equipment.is_packed);
/**
* get a list of options that affect packed equipment
* if this is fqt, modify their price now to be the difference
* between their current price and the packed equipment
* they replace
*/
const cnt_packed_equipment = arr_packed_equipment.length;
for (let i = 0; i < cnt_packed_equipment; i += 1) {
const skv_packed_equipment = arr_packed_equipment[i];
const arr_affects_equipment = skv_packed_equipment.affects;
const cnt_affects_equipment = arr_affects_equipment.length;
for (let j = 0; j < cnt_affects_equipment; j += 1) {
const skv_affects = arr_affects_equipment[j];
// grab the affects equipment index using the equipment map
const affects_idx = equipment_map[skv_affects.id];
const skv_affects_equipment = arr_returned_equipment[affects_idx];
// ignore if this equipment has a parent as the price will be defaulted to 0
if (
!skv_affects_equipment.is_parent_packed
&& skv_affects_equipment.type !== 'standard'
) {
/**
* we need to modify the price of a packed option
*
* if this is fqt we add the absolute price difference
* so packed £120 at £10p/m option will show as £0
* a better £180 option at £15p/m will show as £5p/m (£5 diff from 10 up to 5)
* a cheaper £0 option at £0p/m will show as £10p/m (£10 diff from 10 down to 0)
* (cheaper option devalues the car so the driver has to make up for it)
*
* if it's tax, we add the price diffence (not absolute)
* so packed £120 will show as £0
* a better £180 will show as £60
* a cheaper £0 will show as -£120
* this is because we need the P11D to reflect the list prices
*/
// new price based on difference
let new_price_net = skv_affects_equipment.original_net - skv_packed_equipment.original_net;
// for fqt, convert negative values to positive
if (skv_config.is_fqt) {
new_price_net = Math.abs(new_price_net);
}
skv_affects_equipment.cost_net = new_price_net;
skv_affects_equipment.cost_gross = new_price_net * skv_config.num_VAT_factor;
// ensure we always show a cost as adding a standard may have
// a cost associated
skv_affects_equipment.show_cost = true;
}
// update the equipment data to be returned
arr_returned_equipment[affects_idx] = skv_affects_equipment;
}
}
return arr_returned_equipment;
}
/**
* Add any required properties for the initial setup
* @param {struct} skv_config
* @param {array} arr_equipment Flatted array of equipment structs (standards and options)
* @returns {array} Array of any additional or tweaked equipment structs
*/
createEquipmentObjectsAndMap(skv_config, arr_equipment) {
const arr_limited_equipment = Configurator.removeNotAllowedEquipment(skv_config, arr_equipment);
const cnt_equipment = arr_limited_equipment.length;
const arr_returned_equipment = [];
const arr_packed_equipment = [];
// create empty array and populate
for (let i = 0; i < cnt_equipment; i += 1) {
const skv_equipment = arr_limited_equipment[i];
skv_equipment.cost_field = skv_config.cost_field;
skv_equipment.show_as_swatch = skv_config.show_as_swatch;
skv_equipment.adjustment_percentage = skv_config.adjustment_percentage;
skv_equipment.is_salsac = skv_config.is_salsac;
skv_equipment.salsac_employer_can_reclaim_vat = skv_config.salsac_employer_can_reclaim_vat;
skv_equipment.vat_percentage = skv_config.vat_percentage;
// see if this option needs to be preselected by a previous user action
// most likely on another page and passed in via the url
const preselected_idx = skv_config.arr_preselected_options_ids.findIndex((preselected_id) => preselected_id === skv_equipment.id);
// if this option is preselected make sure we select it
if (preselected_idx > -1) {
skv_equipment.is_init_selected = true;
}
const new_equipment = new Equipment(skv_equipment);
// store all the packed equipment
if (skv_equipment.is_packed) {
arr_packed_equipment.push(skv_equipment);
}
arr_returned_equipment.push(new_equipment);
this.equipment_map[new_equipment.id] = i;
// we need to keep info open state separate to the rest of the
// equipment states
this.open_info_toggle_state.push(
{
id: new_equipment.id,
is_open: false,
},
);
// do the same for dependencies
this.open_dependency_toggle_state.push(
{
id: new_equipment.id,
is_open: false,
},
);
}
return arr_returned_equipment;
}
/**
* Deep clone State
* @param {string} state_name Which state to clone?
* @returns {object} State object
*/
cloneState(state_name) {
const arr_equipment = this.states[state_name].equipment;
// deep clone equipment
const clone_equipment = JSON.parse(JSON.stringify(arr_equipment));
return new State(clone_equipment, this.equipment_map);
}
/**
* Calculate lease cost
* @param {number} price
* @param {struct} skv_lease_terms
* @returns {number}
*/
static calculateLeaseCost(price, skv_lease_terms) {
const { initial_payment } = skv_lease_terms;
const total_payments = (skv_lease_terms.months + initial_payment) - 1;
// const residual_value = price * skv_lease_terms.residual_val_proportion;
// work out how much of the option is paid off over the contract
// period - this is price minus how much is paid in the initial_payment payment
// ( totalNumPayments - initial_payment ) / totalNumPayments
const portion_of_price_to_borrow = (
total_payments - initial_payment
) / total_payments;
const loan_amount = price * portion_of_price_to_borrow;
// work out the number of payments the person taking the lease pays
// this is the total number of months - 1, the first month they pay
// the initial_payment instead
// in this function, we can just take the total payments overall
// minus the initial_payment
// totalNumPayments = months + initial_payment - 1
const num_payments_to_make = total_payments - initial_payment;
// if we have a 0 interest rate, we just need to return the
// price divided by the total numnber of payments
if (skv_lease_terms.interest_rate === 0) {
return price / total_payments;
}
/* eslint-disable no-mixed-operators */
const adjusted_price = loan_amount
* skv_lease_terms.interest_rate
/ (
1 - 1
/ (
(1 + skv_lease_terms.interest_rate) ** num_payments_to_make
)
);
/* eslint-enable no-mixed-operators */
return adjusted_price;
}
/**
* Calculate Salsac cost
* @param {struct} skv_equipment
* @param {struct} skv_lease_terms
* @returns {number}
*/
static calculateSalsacCost(skv_equipment, skv_lease_terms) {
// for salsac cost net has half the vat added back on and initial payments
// ignored when calculating p/m figures
return skv_equipment.cost_net / skv_lease_terms.months;
}
/**
* Get the specified state data and do any calculating required
* @param {string} str_state current / undo / initial
*/
addAdditionalStateData(str_state) {
const arr_original_equipment = this.states[str_state].equipment;
const cnt_equipment = arr_original_equipment.length;
const arr_calculated_equpment = [];
for (let i = 0; i < cnt_equipment; i += 1) {
const skv_equipment = arr_original_equipment[i];
// set the display cost of the equipment
skv_equipment.display_cost = Configurator.getDisplayCost(
skv_equipment,
this.skv_config,
);
skv_equipment.is_info_open = this.getInfoOrDependencyState(skv_equipment.id, 'info');
skv_equipment.is_dependency_open = this.getInfoOrDependencyState(skv_equipment.id, 'dependency');
arr_calculated_equpment.push(skv_equipment);
}
this.states[str_state].equipment_extra = arr_calculated_equpment;
}
/**
* Update config
* @param payload
*/
updateConfig(payload) {
if (Object.prototype.hasOwnProperty.call(payload, 'cost_field')) {
this.skv_config.cost_field = payload.cost_field;
}
if (Object.prototype.hasOwnProperty.call(payload, 'skv_lease_terms')) {
this.skv_config.skv_lease_terms = payload.skv_lease_terms;
}
this.event_action = 'update configuration';
this.dispatch();
}
/**
* action
* @param {struct} payload Type of action (click, update etc), id of equipment
*/
action(payload) {
switch (payload.type) {
case 'click':
this.event_action = 'equipment interaction';
this.equipmentClicked(payload);
break;
case 'undo':
this.event_action = 'undo interaction';
this.undoOptionSelection();
break;
case 'info_toggle':
this.event_action = 'info interaction';
this.infoToggle(payload);
break;
case 'dependency_toggle':
this.event_action = 'dependency toggle';
this.dependencyToggle(payload);
break;
default:
this.event_action = 'unrecognised action';
throw new Error('action not recognised');
}
}
/**
* info button clicked
* @param {struct} payload
*/
infoToggle(payload) {
// toggle the payloads
this.open_info_toggle_state.forEach((skv_info) => {
if (payload.data.id === skv_info.id) {
// TODO: don't reassign this
// eslint-disable-next-line
skv_info.is_open = !skv_info.is_open;
}
});
// send out the current state of the options
this.dispatch();
}
/**
* dependency button clicked
* @param {struct} payload
*/
dependencyToggle(payload) {
// toggle the payloads
this.open_dependency_toggle_state.forEach((skv_info) => {
if (payload.data.id === skv_info.id) {
// TODO: don't reassign this
// eslint-disable-next-line
skv_info.is_open = !skv_info.is_open;
}
});
// send out the current state of the options
this.dispatch();
}
/**
* Starting point to handle the equipment click
* @param {struct} payload Type of interaction and id of equipment interacted with
*/
equipmentClicked(payload) {
const equipment_idx = this.equipment_map[payload.data.id];
this.currentInteraction = this.equipment[equipment_idx];
// set undo state
this.states.undo = this.cloneState('current');
// process new state
this.states.current.process(equipment_idx); // is_swatch was true
// send out the current state of the options
this.dispatch();
}
/**
* Undo options selection
*/
undoOptionSelection() {
// revert current to undo
this.states.current = this.cloneState('undo');
this.dispatch();
}
/**
* Find whether an info or dependency button is open
* @param {number} id Equipment id
* @returns {boolean}
*/
getInfoOrDependencyState(id, info_or_dependency) {
let value = false;
const button_states = info_or_dependency === 'info' ? this.open_info_toggle_state : this.open_dependency_toggle_state;
button_states.forEach((skv_info) => {
if (id === skv_info.id) {
value = skv_info.is_open;
}
});
return value;
}
/**
* Send out the current state of the equipment
* @fires EQUIPMENT:STATE_CHANGE
*/
dispatch() {
// whenever we make changes recalculate the current state and use that
// to determine totals, clicked equipment etc
this.addAdditionalStateData('current');
// generic event dispatch with the entire state of equipment
const event = new window.CustomEvent('EQUIPMENT:STATE_CHANGE', {
detail: {
grouped_equipment: this.equipment_data,
totals: this.totals_data,
// the equipment affected by dependencies
auto_actioned_equipment: this.states.current.arr_actioned_equipment,
// single equipment that was clicked
clicked_equipment: this.states.current.clicked_equipment,
selected_options: this.selected_options,
event_action: this.event_action,
},
});
window.dispatchEvent(event);
}
/**
* Loop through looking at panel group and whether its a standard or option
* @param {array} arr_flat_equipment Array of unordered equipment
* @param {string} panel_id String of panel id e.g paint or options-list
* @returns {array} Array of groups (with their items) based on the panel group
*/
static getStructuredMFgroupData(arr_flat_equipment, panel_id) {
const arr_groups = [];
arr_flat_equipment.forEach((skv_equipment) => {
// standard or option
const str_type = skv_equipment.type;
// check we're looking at an equipment in the right panel group
if (skv_equipment.panel_group !== panel_id) {
return;
}
// see if the group already exists
const num_group_idx = arr_groups.findIndex((skv_group) => skv_group.mf_group_name === skv_equipment.equipment_group);
// group doesn't exist, so add it
if (num_group_idx === -1) {
const skv_group = {
id: Configurator.createGroupId(skv_equipment.equipment_group),
mf_group_name: skv_equipment.equipment_group,
standards: [],
options: [],
};
skv_group[`${str_type}s`].push(skv_equipment);
arr_groups.push(skv_group);
} else {
// add the equipment to standards / options array of the existing
// group
arr_groups[num_group_idx][`${str_type}s`].push(skv_equipment);
}
});
return arr_groups;
}
/**
* Get the cost depending on whether we want net or gross, calculate effect of
* lease terms if present, do some rounding to 2 dp
* @param {object} skv_equipment Equipment object
* @param {struct} skv_config Struct of config set in constructor but may subsequently be updated
* @returns {number}
*/
static getDisplayCost(skv_equipment, skv_config) {
let value = skv_equipment[skv_config.cost_field];
if (Object.keys(skv_config.skv_lease_terms).length) {
if (skv_config.is_salsac) {
value = Configurator.calculateSalsacCost(
skv_equipment,
skv_config.skv_lease_terms,
);
} else {
value = Configurator.calculateLeaseCost(
value,
skv_config.skv_lease_terms,
);
}
}
return value.toFixed(2);
}
/**
* @param {struct} skv_data Struct of standards and options structured to match the API call
* @returns {array} Array of panels that have standards and options split up according
* to their panel group
*/
getPanels() {
const arr_panels = [];
// sort in to panels e.g paint, list
Object.keys(this.skv_config.panels).forEach((key) => {
const skv_panel = {
name: key,
groups: Configurator.getStructuredMFgroupData(this.states.current.equipment, key),
};
arr_panels.push(skv_panel);
});
return arr_panels;
}
/**
* All the equipment data
* @returns {array} Return a structured set of standards and options that match the API call
*/
get equipment_data() {
return this.getPanels();
}
/**
* Totals of options, co2 etc
* @returns {struct} Struct of totals
*/
get totals_data() {
return this.totals;
}
/**
* Loop through all equipment and add up totals
* @returns {struct} Struct of totals
*/
get totals() {
const skv_return = {};
let num_total_options = 0;
let num_total_taxable = 0;
let num_total_gross = 0;
let num_total_net = 0;
// what we're changing the vehicle by
let num_change_co2 = 0;
// what we're changing the vehicle by
let num_change_battery_range = 0;
const arr_equipment = this.states.current.equipment_extra;
const cnt_equipment = arr_equipment.length;
for (let i = 0; i < cnt_equipment; i += 1) {
const skv_equipment = arr_equipment[i];
const equipment_cost = skv_equipment.display_cost;
const { taxable_cost, cost_gross, cost_net } = skv_equipment;
if (
skv_equipment.is_selected
) {
num_total_options += parseFloat(equipment_cost);
num_total_gross += parseFloat(cost_gross);
num_total_net += parseFloat(cost_net);
if (skv_equipment.is_taxable) {
num_total_taxable += taxable_cost;
}
if (skv_equipment.change_co2) {
num_change_co2 += skv_equipment.change_co2;
}
if (skv_equipment.change_battery_range) {
num_change_battery_range += skv_equipment.change_battery_range;
}
}
}
skv_return.num_total_options = num_total_options;
skv_return.num_total_taxable = num_total_taxable;
skv_return.num_total_gross = num_total_gross;
skv_return.num_total_net = num_total_net;
// what we're changing the battery range by, defaults to 0
skv_return.num_change_battery_range = num_change_battery_range;
// if theres a valid battery range passed in add the battery change to it
if (
this.skv_config.skv_vehicle.battery_range
&& this.skv_config.skv_vehicle.battery_range_teh
) {
skv_return.num_vehicle_battery_range = this.skv_config.skv_vehicle.battery_range
+ num_change_battery_range;
// if the battery range falls below teh value, cap it to the teh value
if (skv_return.num_vehicle_battery_range < this.skv_config.skv_vehicle.battery_range_teh) {
skv_return.num_vehicle_battery_range = this.skv_config.skv_vehicle.battery_range_teh;
}
// also check whether we should cap the battery change value
const allowed_drop_in_range = this.skv_config.skv_vehicle.battery_range_teh - this.skv_config.skv_vehicle.battery_range;
if (num_change_battery_range < allowed_drop_in_range) {
skv_return.num_change_battery_range = allowed_drop_in_range;
}
} else {
skv_return.num_vehicle_battery_range = '';
}
// what we're changing the vehicle co2 by, defaults to 0
skv_return.num_change_co2 = num_change_co2;
const vehicle_co2_uncapped = this.skv_config.skv_vehicle.wltp_co2 + num_change_co2;
skv_return.num_vehicle_co2 = vehicle_co2_uncapped;
// ensure the change co2 value is in between the TEL (Test Energy Low,
// light vehicle more efficient) and TEH (Test Energy High, heavy
// vehicle with options less efficient) values
if (this.skv_config.skv_vehicle.wltp_co2_tel > vehicle_co2_uncapped) {
skv_return.num_vehicle_co2 = this.skv_config.skv_vehicle.wltp_co2_tel;
}
if (this.skv_config.skv_vehicle.wltp_co2_teh < vehicle_co2_uncapped) {
skv_return.num_vehicle_co2 = this.skv_config.skv_vehicle.wltp_co2_teh;
}
// if a total price limit is set check whether we've exceeded it
if (
typeof this.skv_config.max_gross_total_price_limit === 'number'
&& skv_return.num_total_gross > this.skv_config.max_gross_total_price_limit
) {
skv_return.is_total_gross_limit_exceeded = true;
} else {
skv_return.is_total_gross_limit_exceeded = false;
}
return skv_return;
}
/**
* Loop over the equipment to see which options are selected, run after
* dependencies have been handled
* @returns {array} Array of selected options
*/
get selected_options() {
// current equipment state
const arr_equipment = this.states.current.equipment_extra;
// array of equipment ids
const arr_selected_ids = this.states.current.arr_selected_option_ids;
const cnt_selected_ids = arr_selected_ids.length;
const arr_return = [];
for (let i = 0; i < cnt_selected_ids; i += 1) {
const id = arr_selected_ids[i];
// use the map to get the index in the flat equipment array
const equipment_idx = this.equipment_map[id];
// push in to an array to return
const skv_equipment = arr_equipment[equipment_idx];
arr_return.push(skv_equipment);
}
return arr_return;
}
/**
* Build a group id based on the mf group name
* @param {string} str_name Name of group
* @returns {string} id
*/
static createGroupId(str_name) {
// replace all alphanumeric characters with a dash
let str_id = str_name;
// get everything that isn't alphanumeric or is a dash surrounded by spaces
const regex = /( - )|([^a-zA-Z0-9])/gi;
// replace with a dash
str_id = str_id.replace(regex, '-');
// now lowercase everything
str_id = str_id.toLowerCase();
return str_id;
}
}
export {
Configurator,
};