How can I adjust my formula to continuously autofill the missing paramater?

Viewed 41

So i'm building a calculator/estimator that is basically just a more complicated version of this margin calculator: https://www.omnicalculator.com/finance/margin

So here's one edge case that I'm trying to fix right now.

My costs are broken into 3 parts due to outsourced data- labor, material and laborAndMaterial. LaborAndMaterial is the sum of labor and material, but it can be the only known cost factor so that's why it's broken into 3 parts.

So here's the problem. Say we know that laborAndMaterial is set to 100 and labor and material are 0

cost: {
  labor: 0,
  material: 0,
  laborAndMaterial: 100
}

Then the user enters 50 for labor. Because laborAndMaterial is 100 and labor is now 50 we can autofill material to 50.

But what's happening right now as the user is typing "50" it autofills material to 95 as the user types the 5 in 50. Then when they enter the "0" it sets the laborAndMaterial to 145 (50 + 95). But in that example I need to adjust how I autofill material to continue to update as the user enters more numbers (labor = 5 -> 50 -> 506) (material = 95, 50, -406). As of now I basically have my formula run like:

if(key === "cogs.labor") {
  if(laborAndMaterial > 0) {
    params["cogs.material"] = laborAndMaterial - value // value is what was entered
  }
}

But I still need to allow for the other edge cause that as labor is entered and material is known it updates the laborAndMaterial value

cost {
  labor: 50,
  material: 50,
  laborAndMaterial: 100
}

So if someone enters 100 for labor and we know material is 50 we can autofill laborAndMaterial to 150.

So I have something like:

if(material > 0) {
  params["cogs.laborAndMaterial"] = material + value // value is what was entered
}

Any ideas how I can tweak my formula to decide the autofill and continue to update that paramater while still allowing for multiple edge cases?

The margin calculator from omnicalculator is a good example as they solved the issue, but I've been scratching my head over it for some time.

3 Answers

I think you basically need to differentiate between which cost centers are treated as input and which are treated as output. So when you start, each piece of data you're provided is input, and the data you use to autofill the rest of the form is output.

As the user types, any information they give is then treated as input data. Given that any two values can be used to calculate the third, you can only have two of the fields be treated as input at a time.

Here's a code sample to get an idea of what I mean:

// This is a queue to hold your two input cost centers
const inputFields = [];

// Determine the odd one out that we need to calculate
function getVariableCostCenter() {
    if (!inputFields.includes('labor')) {
        return 'labor';
    }

    if (!inputFields.includes('material')) {
        return 'material';
    }

    return 'laborAndMaterial';
}

function calculateCostCenters(costCenters) {
    const variableCostCenter = getVariableCostCenter();
    if (variableCostCenter === 'labor') {
        return {
            ...costCenters,
            labor: costCenters.laborAndMaterial - costCenters.material,
        };
    }

    if (variableCostCenter === 'material') {
        return {
            ...costCenters,
            material: costCenters.laborAndMaterial - costCenters.labor,
        };
    }

    return {
        ...costCenters,
        laborAndMaterial: costCenters.labor + costCenters.material,
    };
}

function initializeCostCenters(costCenters) {
    // First, we determine which field(s) are known up front
    if (costCenters.labor > 0) {
        inputFields.push('labor');
    }
    if (costCenters.material > 0) {
        inputFields.push('material');
    }
    if (costCenters.laborAndMaterial > 0 && inputFields.length < 2) {
        inputFields.push('laborAndMaterial');
    }

    // ...then do whatever you normally do to populate the form
}

function updateCostCenters(previousCostCenters) {
    // Update the input fields so that the user's input
    // is always treated as one of the two input fields
    if (!inputFields.includes(key)) {
        inputFields.shift();
        inputFields.push(field);
    }

    const costCenters = calculateCostCenters({
        ...previousCostCenters,
        [key]: value,
    });

    params['cogs.labor'] = costCenters.labor;
    params['cogs.material'] = costCenters.material;
    params['cogs.laborAndMaterial'] = costCenters.laborAndMaterial;
}

Pretty roughly it might look like below.

Note that I remembering last touched fields, which are became "fixed", because we can not recalculate values circularly.

Also, note that I use direct value update, while in some frameworks/libs it might generate change/input event, so you would want to set values silently.

setup = {
  labor: {
    value: 0
  },
  material: {
    value: 0
  },
  laborAndMaterial: {
    value: 100
  }
};

// the number which we are treat as "fixed", may be changed later
let prevFixed = 'labor';
let fixed = 'labor';

const calculateTheRest = () => {
  if (!setup.material.touched && !setup.laborAndMaterial.touched ||
    !setup.labor.touched && !setup.laborAndMaterial.touched ||
    !setup.labor.touched && !setup.material.touched) {
    return false; // two unknowns, can't recalculate
  }

  if (!setup.labor.touched || fixed !== 'labor' && prevFixed !== 'labor') {
    setup.labor.value = setup.laborAndMaterial.value - setup.material.value;
  } else if (!setup.material.touched || fixed !== 'material' && prevFixed !== 'material') {
    setup.material.value = setup.laborAndMaterial.value - setup.labor.value;
  } else {
    setup.laborAndMaterial.value = setup.material.value + setup.labor.value;
  }
}

const $els = {};
Object.keys(setup).forEach(key => $els[key] = document.querySelector('#' + key))

const onInputChanged = (e) => {
  const key = e.target.id;
  setup[key].value = +e.target.value;
  setup[key].touched = true;
  if (fixed !== key) {
    prevFixed = fixed;
    fixed = key;
  }
  calculateTheRest();
  Object.keys(setup).forEach(key => $els[key].value = setup[key].value);
}

Object.keys(setup).forEach(key => {
  $els[key].value = setup[key].value; // initial set
  setup[key].touched = setup[key].value !== 0; // 0 on initial setup are the numbers that not set
  $els[key].addEventListener('input', onInputChanged);
})
<p><label>labor: <input id="labor" type="number"/></label></p>
<p><label>material: <input id="material" type="number"/></label></p>
<p><label> laborAndMaterial: <input id="laborAndMaterial" type="number" /></label></p>

I think you need to implement a condition on your laborAndMaterial field.

Check the condition: -

if(labor > 0 && material > 0){
    let laborAndMaterial = labor + material;
}

And after that set the laborAndMaterial variable value into field,

I think it will may help you.

Related