Cash Register Fail

So I’ve been stuck on the cash register for a while. I put the test arguments in an array and added a function to run them all to see that my calculations match the expected results. I also added a button & select to load eaah set of arguments individually via the purchase button. My results:

Customer does not have enough money to purchase the item

No change due - customer paid with exact cash

Status: OPEN
QUARTER: $0.5

Status: OPEN
TWENTY: $60
TEN: $20
FIVE: $15
ONE: $1
QUARTER: $0.5
DIME: $0.2
PENNY: $0.04

Status: INSUFFICIENT_FUNDS

Status: INSUFFICIENT_FUNDS

Status: CLOSED
PENNY: $0.5

Everything after and including No change due fails. I have formatted my results in every permutation I can think of and returned various result objects but the tests do not give me enough feedback to determine what I am doing wrong

a litle detail

const changeDue = document.getElementById(“change-due”);
const messages = {
insufficient: “Customer does not have enough money to purchase the item”,
exact: “No change due - customer paid with exact cash”,
};

assuming the cash == price condition

changeDue.innerHTML = messages.exact;

should pass, period. full stop.

Could you share your full code - HTML and JavaScript? Without that it can be hard or impossible to tell what might be an issue.

it seems I don’t know how to post html in this forum

Code needs to be put within triple backticks to keep the formatting

```
code goes here
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <h1>Cash Register</h1>
    <div class="input-div">
      <div>
        <label for="cash">Cash</label>
        <input type="number" id="cash" />
      </div>
      <div>
        <label for="price">Price</label>
        <input type="number" id="price" disabled />
      </div>
    </div>
    <div class="drawer-display">
      <div class="denomination">
        <label for="drawer-HUNDRED">100</label>
        <input type="number" id="drawer-HUNDRED" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-TWENTY">20</label>
        <input type="number" id="drawer-TWENTY" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-TEN">10</label>
        <input type="number" id="drawer-TEN" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-FIVE">5</label>
        <input type="number" id="drawer-FIVE" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-ONE">1</label>
        <input type="number" id="drawer-ONE" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-QUARTER">0.25</label>
        <input type="number" id="drawer-QUARTER" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-DIME">0.10</label>
        <input type="number" id="drawer-DIME" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-NICKEL">0.05</label>
        <input type="number" id="drawer-NICKEL" disabled />
      </div>
      <div class="denomination">
        <label for="drawer-PENNY">0.01</label>
        <input type="number" id="drawer-PENNY" disabled />
      </div>
    </div>
    <div class="buttons-div">
      <button id="purchase-btn">Purchase</button>
      <button id="examples-btn">Run Examples</button>
      <button id="clear-btn">Clear</button>
    </div>
    <div class="buttons-div">
      <select name="example" id="example-select">
        <option value="0">Example #1</option>
        <option value="1">Example #2</option>
        <option value="2">Example #3</option>
        <option value="3">Example #4</option>
        <option value="4">Example #5</option>
        <option value="5">Example #6</option>
		<option value="6">Example #7</option>
      </select>
      <button id="load-example-btn">Load Example</button>
    </div>
	<fieldset>
		<legend>Change Due</legend>
		<div id="change-due"></div>
	</fieldset>
    <script src="script.js"></script>
  </body>
</html>

thanks for the formatting tips!!!

let price = 0;
let cid = [
  ["PENNY", 0],
  ["NICKEL", 0],
  ["DIME", 0],
  ["QUARTER", 0],
  ["ONE", 0],
  ["FIVE", 0],
  ["TEN", 0],
  ["TWENTY", 0],
  ["ONE HUNDRED", 0],
];
let cash = 0;
// change { denomination: string, quantity: number }[]
let change = [],
  STATUS = "OPEN",
  QUANTITIES = {};
// lower case status is deprecated keyword

const cashInput = document.getElementById("cash");
const purchaseButton = document.getElementById("purchase-btn");
const clearButton = document.getElementById("clear-btn");
const exampleButton = document.getElementById("examples-btn");
const changeDue = document.getElementById("change-due");
const loadExampleButton = document.getElementById("load-example-btn");
const exampleSelect = document.getElementById("example-select");
const priceInput = document.getElementById("price");

const currency = {
  PENNY: 0.01,
  NICKEL: 0.05,
  DIME: 0.1,
  QUARTER: 0.25,
  ONE: 1,
  FIVE: 5,
  TEN: 10,
  TWENTY: 20,
  "ONE HUNDRED": 100,
};
// use cents instead of dollars to avoid float rounding errors
const cents = {
  PENNY: 1,
  NICKEL: 5,
  DIME: 10,
  QUARTER: 25,
  ONE: 100,
  FIVE: 500,
  TEN: 1000,
  TWENTY: 2000,
  "ONE HUNDRED": 10000,
};
const currencies = [
  "ONE HUNDRED",
  "TWENTY",
  "TEN",
  "FIVE",
  "ONE",
  "QUARTER",
  "DIME",
  "NICKEL",
  "PENNY",
];

const messages = {
  insufficient: "Customer does not have enough money to purchase the item",
  exact: "No change due - customer paid with exact cash",
};

const examples = [
  {
    price: 2,
    cash: 1,
    cid: [
      ["PENNY", 0],
      ["NICKEL", 0],
      ["DIME", 0],
      ["QUARTER", 0],
      ["ONE", 0],
      ["FIVE", 0],
      ["TEN", 0],
      ["TWENTY", 0],
      ["ONE HUNDRED", 0],
    ],
  },
  {
    price: 1,
    cash: 1,
    cid: [
      ["PENNY", 0],
      ["NICKEL", 0],
      ["DIME", 0],
      ["QUARTER", 0],
      ["ONE", 0],
      ["FIVE", 0],
      ["TEN", 0],
      ["TWENTY", 0],
      ["ONE HUNDRED", 0],
    ],
  },
  {
    price: 19.5,
    cash: 20,
    cid: [
      ["PENNY", 1.01],
      ["NICKEL", 2.05],
      ["DIME", 3.1],
      ["QUARTER", 4.25],
      ["ONE", 90],
      ["FIVE", 55],
      ["TEN", 20],
      ["TWENTY", 60],
      ["ONE HUNDRED", 100],
    ],
  },
  {
    price: 3.26,
    cash: 100,
    cid: [
      ["PENNY", 1.01],
      ["NICKEL", 2.05],
      ["DIME", 3.1],
      ["QUARTER", 4.25],
      ["ONE", 90],
      ["FIVE", 55],
      ["TEN", 20],
      ["TWENTY", 60],
      ["ONE HUNDRED", 100],
    ],
  },
  {
    price: 19.5,
    cash: 20,
    cid: [
      ["PENNY", 0.01],
      ["NICKEL", 0],
      ["DIME", 0],
      ["QUARTER", 0],
      ["ONE", 0],
      ["FIVE", 0],
      ["TEN", 0],
      ["TWENTY", 0],
      ["ONE HUNDRED", 0],
    ],
  },
  {
    price: 19.5,
    cash: 20,
    cid: [
      ["PENNY", 0.01],
      ["NICKEL", 0],
      ["DIME", 0],
      ["QUARTER", 0],
      ["ONE", 1],
      ["FIVE", 0],
      ["TEN", 0],
      ["TWENTY", 0],
      ["ONE HUNDRED", 0],
    ],
  },
  {
    price: 19.5,
    cash: 20,
    cid: [
      ["PENNY", 0.5],
      ["NICKEL", 0],
      ["DIME", 0],
      ["QUARTER", 0],
      ["ONE", 0],
      ["FIVE", 0],
      ["TEN", 0],
      ["TWENTY", 0],
      ["ONE HUNDRED", 0],
    ],
  },
];

const loadExample = () => {
  const idx = parseInt(exampleSelect.value);
  const example = examples[idx];
  cash = cashInput.value = example.cash;
  price = priceInput.value = example.price;
  // make a copy because counting change would modify by reference
  cid = JSON.parse(JSON.stringify(example.cid));
  let input;
  for (let i = 0; i < cid.length; i++) {
    if (cid[i][0] == "ONE HUNDRED") {
      input = document.getElementById("drawer-HUNDRED");
    } else {
      input = document.getElementById(`drawer-${cid[i][0]}`);
    }
    if (input) input.value = cid[i][1];
  }
};

const countChange = (due) => {
  change = [];
  STATUS = "OPEN";
  let remaining = due;
  let quantity, inDrawer, max;
  filQuantities();
  for (let i = 0; i < currencies.length; i++) {
    const denomination = currencies[i];
    inDrawer = QUANTITIES[denomination] || 0;
    if (remaining > cents[denomination] && inDrawer > 0) {
      max = Math.floor(remaining / cents[denomination]);
      quantity = Math.min(inDrawer, max);
      addToChange(denomination, cents[denomination], quantity);
      remaining -= quantity * cents[denomination];
    }
  }
  if (remaining > 0) {
    STATUS = "INSUFFICIENT_FUNDS";
  }
};

const filQuantities = () => {
  QUANTITIES = [];
  for (let i = 0; i < cid.length; i++) {
    QUANTITIES[cid[i][0]] = Math.floor(cid[i][1] / currency[cid[i][0]]);
  }
};

const addToChange = (denomination, amount, quantity) => {
  // console.log({ denomination, amount, quantity })
  const idxD = drawerIndex(denomination);
  const drawerCents = Math.floor(cid[idxD][1] * 100) - amount * quantity;
  cid[idxD][1] = drawerCents / 100;
  const idxC = changeIndex(denomination);
  if (idxC == -1) {
    const item = { denomination, quantity };
    change.push(item);
  } else {
    // not likely to get here
    change[idxC].quantity += quantity;
  }
};

const drawerIndex = (denomitation) => {
  for (let i = 0; i < cid.length; i++) {
    if (cid[i][0] == denomitation) return i;
  }
  return -1;
};

const changeIndex = (denomitation) => {
  for (let i = 0; i < change.length; i++) {
    if (change[i].denomination == denomitation) return i;
  }
  return -1;
};

const countDrawer = () => {
  let total = 0;
  for (let i = 0; i < cid.length; i++) {
    total += Math.floor(cid[i][1] * 100);
  }
  return total / 100;
};

const adjustDrawer = (name, value) => {
  let idx = drawerIndex(name);
  if (idx == -1) {
    cid.push([name, parseFloat(value)]);
  } else {
    cid[idx][1] = parseFloat(value);
  }
};

const formatChange = () => {
  let div, amt;
  const wrapper = document.createElement("DIV");
  wrapper.classList.add("change");
  div = document.createElement("P");
  div.innerHTML = `Status: ${STATUS}`;
  wrapper.appendChild(div);
  for (let i = 0; i < change.length; i++) {
    const item = change[i];
    div = document.createElement("P");
    amt = (item.quantity * cents[item.denomination]) / 100;
    div.innerHTML = `${item.denomination}: \$${amt}`;
    wrapper.appendChild(div);
  }
  return wrapper;
};

const formatChangeText = () => {
  const parts = [];
  parts.push(`Status: ${STATUS}`);
  for (let i = 0; i < change.length; i++) {
    const item = change[i];
    const amt = (item.quantity * cents[item.denomination]) / 100;
    parts.push(`${item.denomination}: $${amt}`);
  }
  return parts.join(" ");
};

const handlePurchase = (cash, price, cid) => {
  const response = {
    status: "OPEN",
    change: [],
  };
  if (cash < price) {
    alert(messages.insufficient);
    return;
  }
  if (cash == price) {
    changeDue.innerText = messages.exact;
    return messages.exact;
  }
  QUANTITIES = {};
  let totalCents = 0;
  const indicees = [];
  for (let i = 0; i < cid.length; i++) {
    QUANTITIES[cid[i][0]] = Math.floor(cid[i][1] / currency[cid[i][0]]);
    totalCents += Math.floor(cid[i][1] * 100);
    indicees.push(cid[i][0]);
  }
  const cashCents = Math.floor(cash * 100);
  const priceCents = Math.floor(price * 100);
  const dueCents = cashCents - priceCents;
  let remaining = dueCents;
  let quantity, inDrawer, max, amount, denomination, item, idx;
  for (let i = 0; i < currencies.length; i++) {
    denomination = currencies[i];
    inDrawer = QUANTITIES[denomination] || 0;
    if (remaining > cents[denomination] && inDrawer > 0) {
      max = Math.floor(remaining / cents[denomination]);
      quantity = Math.min(inDrawer, max);
      amount = (quantity * cents[denomination]) / 100;
      item = [denomination, amount];
      response.change.push(item);
      remaining -= quantity * cents[denomination];
      QUANTITIES[denomination] -= quantity;
      idx = indicees.indexOf(denomination);
      if (idx != -1 && quantity > 0) {
        const offset = parseFloat(
          (quantity * currency[denomination]).toFixed(2)
        );
        cid[idx][1] = parseFloat((cid[idx][1] - offset).toFixed(2));
      }
    }
  }
  if (remaining > 0) {
    response.status = "INSUFFICIENT_FUNDS";
    response.change = [];
  }
  if (dueCents == totalCents && response.status !== "INSUFFICIENT_FUNDS") {
    response.status = "CLOSED";
  }
  let parts = [];
  parts.push(`Status: ${response.status}`);
  for (const item of response.change) {
    parts.push(`${item[0]}: $${item[1]}`);
  }
  const textResponse = parts.join(" ");
  changeDue.innerText = textResponse;

  return response;
};

const formatResponseChange = () => {
  const c = [];
  for (const item of change) {
    const { denomination, quantity } = item;
    c.push([denomination, quantity * currency[denomination]]);
  }
  return c;
};

const handleExamples = () => {
  handleClear();
  for (const ex of examples) {
    price = ex.price;
    cash = cashInput.value = ex.cash;
    cid = JSON.parse(JSON.stringify(ex.cid));
    if (cash < price) {
      changeDue.innerHTML += `<div class="change">${messages.insufficient}</div>`;
      continue;
    }
    if (cash == price) {
      changeDue.innerHTML += `<div class="change">${messages.exact}</div>`;
      continue;
    }
    const cashCents = Math.floor(cash * 100);
    const priceCents = Math.floor(price * 100);
    const due = cashCents - priceCents;
    const remaining = Math.floor(countDrawer() * 100);
    countChange(due);
    if (remaining == due && STATUS !== "INSUFFICIENT_FUNDS") {
      STATUS = "CLOSED";
    }
    if (STATUS === "INSUFFICIENT_FUNDS") {
      changeDue.innerHTML += `<div class="change">Status: ${STATUS}</div>`;
    } else {
      const formatted = formatChange();
      changeDue.innerHTML += formatted.outerHTML;
    }
  }
};

const handleClear = () => {
  cashInput.value = 0;
  priceInput.value = 0;
  changeDue.innerHTML = "";
  let input;
  for (let i = 0; i < cid.length; i++) {
    if (cid[i][0] == "ONE HUNDRED") {
      input = document.getElementById("drawer-HUNDRED");
    } else {
      input = document.getElementById(`drawer-${cid[i][0]}`);
    }
    if (input) input.value = 0;
  }
};

purchaseButton.addEventListener("click", (e) => {
  e.preventDefault();

  handlePurchase(cash, price, cid);
});
clearButton.addEventListener("click", handleClear);
exampleButton.addEventListener("click", handleExamples);
loadExampleButton.addEventListener("click", loadExample);

// window.onload = handleClear;

I’ve checked what are individual results when tests are run. All of them are Customer does not have enough money to purchase the item. When using the example loader (that’s pretty nice idea!) everything seems fine. However when changing manually the initial values of cid and price variables, with inputting cash to the #cash element, results are also Customer does not have enough money to purchase the item.

This suggests code depends on something that happens during the example loading, what won’t happen when test is directly modifying cid and price variable and the #cash element.

Thanks, that’s something for me to look at

try adding this to the beginning of handlePurchase

if (!cash) cash = parseFloat(cashInput.value);

thanks for the help. I had tunnel vision