Build a Cash Register Project - Build a Cash Register

Tell us what’s happening:

My code is failing tests 18 and 19. I used console.log statements to figure out why, and it’s returning INSUFFICIENT-FUNDS for test 18. I’ve gone over my code but have no idea why. Can someone help me figure this out?

Your code so far

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale = 1.0" />
    <meta charset="UTF-8" />
    <title>freeCodeCamp Cash Register Project</title>
  </head>
  <body>
    <h1>Cash Register</h1>
    <form>
      <label for="cash">Enter  cash from customer:</label>
      <input id="cash" type="text" />
      <div id="change-due""></div>
      <button id="purchase-btn" type="button">Purchase</button>
    </form>
    <script src="script.js"></script>
  </body>
</html>

let price = 1.87;
let cid = [
  ['PENNY', 1.01],
  ['NICKEL', 2.05],
  ['DIME', 3.1],
  ['QUARTER', 4.25],
  ['ONE', 90],
  ['FIVE', 55],
  ['TEN', 20],
  ['TWENTY', 60],
  ['ONE HUNDRED', 100]
];

const purchaseBtn = document.getElementById("purchase-btn");
const changeDueDiv = document.getElementById("change-due");

purchaseBtn.addEventListener("click", () => {
  const cash = parseFloat(document.getElementById("cash").value);
  if (cash < price) {
    alert("Customer does not have enough money to purchase the item");
    changeDueDiv.innerText = "";
    return;
  }

  let changeDue = calculateChange(cash, price, cid);

  if (changeDue === "INSUFFICIENT_FUNDS") {
    changeDueDiv.innerText = `Status: ${changeDue}`;
    console.log(`Status: ${changeDue}`);
  } else if (changeDue.length === 0) {
    changeDueDiv.innerText = "No change due - customer paid with exact cash";
    console.log("No change due - customer paid with exact cash");
  } else if (changeDue.status === "CLOSED") {
    changeDueDiv.innerText = `Status: CLOSED, Change due: ${formatChange(changeDue.change)}`;
    console.log(`Status: CLOSED, Change due: ${formatChange(changeDue.change)}`);
  } else {
    changeDueDiv.innerText = `Status: OPEN, Change due: ${formatChange(changeDue.change)}`;
    console.log(`Status: OPEN, Change due: ${formatChange(changeDue.change)}`);
  }
});

const calculateChange = (cash, price, cid) => {
  let totalCid = parseFloat(cid.reduce((sum, curr) => sum + curr[1], 0).toFixed(2));
  let change = cash - price;
  let changeArr = [];
  const currencyUnits = [
    { name: "ONE HUNDRED", value: 100.00 },
    { name: "TWENTY", value: 20.00 },
    { name: "TEN", value: 10.00 },
    { name: "FIVE", value: 5.00 },
    { name: "ONE", value: 1.00 },
    { name: "QUARTER", value: 0.25 },
    { name: "DIME", value: 0.10 },
    { name: "NICKEL", value: 0.05 },
    { name: "PENNY", value: 0.01 }
  ];

  // Handle exact cash (no change needed)
  if (cash === price) {
    return [];
  }

  // Handle "CLOSED" status if total cash matches change due
  if (totalCid === change) {
    let closedChange = cid.map(item => [...item]); // Deep copy of cid
    closedChange.sort((a, b) => currencyUnits.findIndex(u => u.name === a[0]) - currencyUnits.findIndex(u => u.name === b[0]));
    return { status: "CLOSED", change: closedChange };
  }

  for (let unit of currencyUnits) {
    let amount = 0;
    while (change >= unit.value && getUnitAmount(unit.name, cid) > 0) {
      let available = getUnitAmount(unit.name, cid);
      let toTake = Math.min(available, Math.floor(change / unit.value) * unit.value); // Take multiples of unit.value
      change -= toTake;
      change = Math.round(change * 100) / 100;
      amount += toTake;
      updateCid(unit.name, cid, toTake);
    }
    if (amount > 0) {
      changeArr.push([unit.name, amount]);
    }
  }

  if (change > 0) {
    return "INSUFFICIENT_FUNDS";
  } else {
    // Proper sorting
    changeArr.sort((a, b) => currencyUnits.findIndex(u => u.name === a[0]) - currencyUnits.findIndex(u => u.name === b[0]));
    return { status: "OPEN", change: changeArr };
  }
};

const getUnitAmount = (unit, cid) => {
  for (let item of cid) {
    if (item[0] === unit) {
      return item[1];
    }
  }
  return 0;
};

const updateCid = (unit, cid, value) => {
  for (let item of cid) {
    if (item[0] === unit) {
      item[1] -= value;
      break;
    }
  }
};

const formatChange = (changeArr) => {
  return changeArr.map(item => `${item[0]}: $${item[1].toFixed(2)}`).join(', ');
};


No CSS yet

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0

Challenge Information:

Build a Cash Register Project - Build a Cash Register

If I test that manually I get Status: CLOSED, Change due: ONE HUNDRED: $0.00, TWENTY: $0.00, TEN: $0.00, FIVE: $0.00, ONE: $0.00, QUARTER: $0.00, DIME: $0.00, NICKEL: $0.00, PENNY: $0.50, but you should have only PENNY, not the other denominations

I’m trying to break this down so I can figure out why I’m getting the results I am. Right now I’m working on test 18. I don’t understand why I’m getting INSUFFICIENT_FUNDS instead of the correct response. I’ve made changes to my code, which now looks like this:

let price = 1.87;
let cid = [
  ['PENNY', 1.01],
  ['NICKEL', 2.05],
  ['DIME', 3.1],
  ['QUARTER', 4.25],
  ['ONE', 90],
  ['FIVE', 55],
  ['TEN', 20],
  ['TWENTY', 60],
  ['ONE HUNDRED', 100]
];

const cidCents = [];

const purchaseBtn = document.getElementById("purchase-btn");
const changeDueDiv = document.getElementById("change-due");

purchaseBtn.addEventListener("click", () => {
  const cash = parseFloat(document.getElementById("cash").value);
  if (cash < price) {
    alert("Customer does not have enough money to purchase the item");
    changeDueDiv.innerText = "";
    return;
  }

  let changeDue = calculateChange(cash, price, cid);

  if (changeDue === "INSUFFICIENT_FUNDS") {
    changeDueDiv.innerText = `Status: ${changeDue}`;
  } else if (changeDue.length === 0) {
    changeDueDiv.innerText = "No change due - customer paid with exact cash";
  } else if (changeDue.status === "CLOSED") {
    changeDueDiv.innerText = `Status: CLOSED, Change due: ${formatChange(changeDue.change)}`;
  } else {
    changeDueDiv.innerText = `Status: OPEN, Change due: ${formatChange(changeDue.change)}`;
  }
});

const calculateChange = (cash, price, cid) => {
  // Convert everything to cents
  let totalCid = Math.round(cid.reduce((sum, curr) => sum + curr[1] * 100, 0));
  let change = Math.round((cash - price) * 100);
  let changeArr = [];
  
  const currencyUnits = [
    { name: "ONE HUNDRED", value: 10000 },
    { name: "TWENTY", value: 2000 },
    { name: "TEN", value: 1000 },
    { name: "FIVE", value: 500 },
    { name: "ONE", value: 100 },
    { name: "QUARTER", value: 25 },
    { name: "DIME", value: 10 },
    { name: "NICKEL", value: 5 },
    { name: "PENNY", value: 1 }
  ];

  // Handle exact cash (no change needed)
  if (change === 0) {
    return [];
  }

  // Handle "CLOSED" status if total cash matches change due
  if (totalCid === change) {
    return { status: "CLOSED", change: cid };
  }

  for (let unit of currencyUnits) {
    let amount = 0;
    let unitValueInCents = unit.value;
    let availableInCents = Math.round(getUnitAmount(unit.name, cid) * 100);

    while (change >= unitValueInCents && availableInCents > 0) {
      change -= unitValueInCents;
      availableInCents -= unitValueInCents;
      amount += unitValueInCents;
    }

    if (amount > 0) {
      changeArr.push([unit.name, amount / 100]);
      updateCid(unit.name, cid, amount / 100);
    }
    
    // If we've given all the required change
    if (change === 0) break;
  }

  // If we can't give exact change
  if (change > 0) {
    return "INSUFFICIENT_FUNDS";
  } else {
    return { status: "OPEN", change: changeArr };
  }
};

const getUnitAmount = (unit, cid) => {
  for (let item of cid) {
    if (item[0] === unit) {
      return item[1];
    }
  }
  return 0;
};

const updateCid = (unit, cid, value) => {
  for (let item of cid) {
    if (item[0] === unit) {
      item[1] -= value;
      break;
    }
  }
};

const formatChange = (changeArr) => {
  return changeArr.map(item => `${item[0]}: $${item[1].toFixed(2)}`).join(', ');
};

I am not seeing INSUFFICIENT_FUNDS that you’re talking about when I test the scenario mentioned in test #18.

You will want to update your code to match that scenario. Which means erasing the price and cid that you currently have and replacing them as follows:

let price = 19.5;
let cid = [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]];

(I just copied these values from the testcase).

After that, you want to type 20 into the cash input field.

The output you get in the #change-due should be identical to the one they expect.
Right now your output is:

Status: CLOSED, Change due: PENNY: $0.50, NICKEL: $0.00, DIME: $0.00, QUARTER: $0.00, ONE: $0.00, FIVE: $0.00, TEN: $0.00, TWENTY: $0.00, ONE HUNDRED: $0.00

But the one they expected was:

 Status: CLOSED PENNY: $0.5

You will want to compare these and then realize that the difference is that in your output, you are placing $0 values in the change-due but they don’t want that. Only place values that are above $0 in the change-due area.

I did that and got INSUFFICIENT_FUNDS. I’ve been working on splitting my calculateChange function into two, one for converting to cents and another for calculating change. This is my code now:

let price = 1.87;
let cid = [
  ['PENNY', 1.01],
  ['NICKEL', 2.05],
  ['DIME', 3.1],
  ['QUARTER', 4.25],
  ['ONE', 90],
  ['FIVE', 55],
  ['TEN', 20],
  ['TWENTY', 60],
  ['ONE HUNDRED', 100]
];

const cidCents = [];

const purchaseBtn = document.getElementById("purchase-btn");
const changeDueDiv = document.getElementById("change-due");

purchaseBtn.addEventListener("click", () => {
  const cash = parseFloat(document.getElementById("cash").value);
  if (cash < price) {
    alert("Customer does not have enough money to purchase the item");
    changeDueDiv.innerText = "";
    return;
  }

  let changeDue = calculateChange(cash, price, cid);

  if (changeDue === "INSUFFICIENT_FUNDS") {
    changeDueDiv.innerText = `Status: ${changeDue}`;
  } else if (changeDue.length === 0) {
    changeDueDiv.innerText = "No change due - customer paid with exact cash";
  } else if (changeDue.status === "CLOSED") {
    changeDueDiv.innerText = `Status: CLOSED, Change due: ${formatChange(changeDue.change)}`;
  } else {
    changeDueDiv.innerText = `Status: OPEN, Change due: ${formatChange(changeDue.change)}`;
  }
});

const convertToScents = (cash, price, cid) => {
  let totalCid = Math.round(cid.reduce((sum, curr) => sum + curr[1] * 100, 0));
  let change = Math.round((cash - price) * 100);
  let priceCents = Math.round(price * 100);
  return { totalCid, change, price };
}

const calculateChange = (cash, price, cid) => {
  const { totalCid, change, price } = convertToScents(cash, price, cid)
  let changeArr = [];
  const currencyUnits = [
    { name: "ONE HUNDRED", value: 10000 },
    { name: "TWENTY", value: 2000 },
    { name: "TEN", value: 1000 },
    { name: "FIVE", value: 500 },
    { name: "ONE", value: 100 },
    { name: "QUARTER", value: 25 },
    { name: "DIME", value: 10 },
    { name: "NICKEL", value: 5 },
    { name: "PENNY", value: 1 }
  ];

  // Handle exact cash (no change needed)
  if (change === 0) {
      return [];
  }

  // Handle "CLOSED" status if total cash matches change due
  if (totalCid === change) {
    return { status: "CLOSED", change: cid };
  }

  for (let unit of currencyUnits) {
    let amount = 0;
    let unitValueInCents = unit.value;
    let availableInCents = Math.round(getUnitAmount(unit.name, cid) * 100);

    while (change >= unitValueInCents && availableInCents > 0) {
      change -= unitValueInCents;
      availableInCents -= unitValueInCents;
      amount += unitValueInCents;
    }

    if (amount > 0) {
      changeArr.push([unit.name, amount / 100]);
      updateCid(unit.name, cid, amount / 100);
    }
    
    // If we've given all the required change
    if (change === 0) break;
  }

  // If we can't give exact change
  if (change > 0) {
    return "INSUFFICIENT_FUNDS";
  } else {
    return { status: "OPEN", change: changeArr };
  }
};

const getUnitAmount = (unit, cid) => {
  for (let item of cid) {
    if (item[0] === unit) {
      return item[1];
    }
  }
  return 0;
};

const updateCid = (unit, cid, value) => {
  for (let item of cid) {
    if (item[0] === unit) {
      item[1] -= value;
      break;
    }
  }
};

const formatChange = (changeArr) => {
  return changeArr.map(item => `${item[0]}: $${item[1].toFixed(2)}`).join(', ');
};

The code you shared, does it fix the issue I reported to you?

I just completed it. I had to fix more floating point errors, break some functions up into smaller ones, and move the cash variable inside my event listener, and now it works. I won’t post the solution here, but if you want to check it out, it’s on my GitHub at Lanie-Carmelo/FCCCashRegisterProject