Help with JS Cash Register Project

Hi, my code outputs match what the test cases expect, but it is still marked wrong.
I would appreciate it if someone could help me.

let price = 3.26;
let cid = [
  ["PENNY", 0.01, 1.01],
  ["NICKEL", 0.05, 2.05],
  ["DIME", 0.1, 3.1],
  ["QUARTER", 0.25, 4.25],
  ["ONE", 1, 90],
  ["FIVE", 5, 55],
  ["TEN", 10, 20],
  ["TWENTY", 20, 60],
  ["ONE HUNDRED", 100, 100]
];

const priceDisplay = document.getElementById("price")
const cashInDrawer = document.getElementById("cid");

const drawerUpdate = () => {
  cashInDrawer.innerHTML = `
<ul>
 <h2>Change in drawer:</h2>
 <li>${cid[0][0]}: $${cid[0][2]}</li>
 <li>${cid[1][0]}: $${cid[1][2]}</li>
 <li>${cid[2][0]}: $${cid[2][2]}</li>
 <li>${cid[3][0]}: $${cid[3][2]}</li>
 <li>${cid[4][0]}: $${cid[4][2]}</li>
 <li>${cid[5][0]}: $${cid[5][2]}</li>
 <li>${cid[6][0]}: $${cid[6][2]}</li>
 <li>${cid[7][0]}: $${cid[7][2]}</li>
 <li>${cid[8][0]}: $${cid[8][2]}</li>
</ul>
`
}

window.onload = () => {
  priceDisplay.textContent = price;
  drawerUpdate();
}

const cashInput = document.getElementById("cash");
const changeDue = document.getElementById("change-due");
const purchaseBtn = document.getElementById("purchase-btn");

const roundAtMostTwoDecimal = (amount) => Math.round(amount * 100) / 100;

let coinsToReturn = {};

const returnedCoin = (num, arr) => {
  if (num < arr[arr.length - 1][1]) {
    changeDue.innerHTML = `
    <li>Status: OPEN</li>
    `;
    for (const key in coinsToReturn) {
      if (coinsToReturn.hasOwnProperty(key)) {
        changeDue.innerHTML += `
    <li>${key}: $${coinsToReturn[key]}</li>
    `
      }
    }
    drawerUpdate();
    return;
  }
  for (let i = 0; i < arr.length; i++) {
    if (num >= arr[i][1] && arr[i][2] >= arr[i][1]) {
      if (!coinsToReturn[arr[i][0]]) {
        coinsToReturn[arr[i][0]] = arr[i][1];
        console.log(coinsToReturn);
        arr[i][2] -= roundAtMostTwoDecimal(arr[i][1]);
        console.log(arr);
        console.log(roundAtMostTwoDecimal(num - arr[i][1]));
        returnedCoin(roundAtMostTwoDecimal(num - arr[i][1]), arr);
        return;
      } else {
        coinsToReturn[arr[i][0]] += arr[i][1];
        console.log(coinsToReturn);
        arr[i][2] -= roundAtMostTwoDecimal(arr[i][1]);
        console.log(arr);
        console.log(roundAtMostTwoDecimal(num - arr[i][1]));
        returnedCoin(roundAtMostTwoDecimal(num - arr[i][1]), arr);
        return;
      }
    }
  }
}

const calculation = (change) => {
  const applicableCurrencies = cid.filter((currency) => currency[1] <= change && currency[2] > 0).reverse();
  let valuesOfApplicableCurrencies = [];
  applicableCurrencies.forEach((currency) => valuesOfApplicableCurrencies.push(currency[2]));
  const sumOfApplicableCurrencies = valuesOfApplicableCurrencies.reduce((a, b) => a + b, 0);
  let valuesOfCid = [];
  cid.forEach((currency) => valuesOfCid.push(currency[2]));
  const sumOfCid = valuesOfCid.reduce((a, b) => a + b, 0);
  console.log(applicableCurrencies);
  console.log(sumOfCid);

  if (sumOfApplicableCurrencies === sumOfCid) {
    if (change > sumOfApplicableCurrencies) {
      changeDue.innerHTML = `
    <li>Status: INSUFFICIENT_FUNDS</li>
    `;
    } else if (change === sumOfApplicableCurrencies) {
      changeDue.innerHTML = `
    <li>Status: CLOSED</li>
    `
      applicableCurrencies.forEach((currency) =>
        changeDue.innerHTML += `
    <li>${currency[0]}: $${currency[2]}</li>
    `
      );
      cid.forEach((currency) => currency[2] = 0);
      drawerUpdate();
    } else if (change < sumOfApplicableCurrencies) {
      returnedCoin(change, applicableCurrencies);
    }
  } else
  // i.e.,(sumOfApplicableCurrencies < sumOfCid)
  // CLOSED cannot happen
  {
    if (change > sumOfApplicableCurrencies) {
      changeDue.innerHTML = `
    <li>Status: INSUFFICIENT_FUNDS</li>
    `;
    } else if (change <= sumOfApplicableCurrencies) {
      returnedCoin(change, applicableCurrencies);
    }
  }
}

const execute = () => {
  const cashInputValue = cashInput.value;
  const expectedChange = roundAtMostTwoDecimal(cashInputValue - price);

  if (expectedChange < 0) {
    alert("Customer does not have enough money to purchase the item")
  } else if (expectedChange == 0) {
    changeDue.innerHTML = `
    <li>No change due - customer paid with exact cash</li>
    `
  } else {
    calculation(expectedChange);
    console.log(expectedChange);
  }
}

purchaseBtn.addEventListener("click", execute);

Hello @Sbrttm1010 !

:balloon:Hello! Welcome to the forum!

Can you also send the html code?

Keep up the good progress!

Happy Coding! :slightly_smiling_face:

Hello @ios-man ! Thanks for your reply.
Below is the HTML code.

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8" />
    <title>Cash Register</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>

  <body>
    <main>
      <h1>Cash Register</h1>
      <div id="change-due">
       <ul>
       </ul>
      </div>
      <p>Enter cash from customer:</p>
      <input type="number" id="cash">
      <button type="click" id="purchase-btn">Purchase</button>

      <form class="cash-register">
        <div class="price">
        <p>Total: <span id="price"></span></p>
        </div>
        <div id="cid">
        </div>
      </form>

    </main>
    <script src="./script.js"></script>
  </body>

</html>
1 Like

It appears your code depends on some global variables, which causes side-effects between separate tests.

When changing code to check test case, preview will be reloaded, effectively running all the code. Tests are run one after another, without reloading whole code, but just changing the price, cid and #cash element.

Thank you for your reply!

Your code was used to create a codepen especially for you - use it to experiment with, change it as much as you like.

Interestingly, when your html is entered, the ui looks like this:
image

When your js code is added the ui changes as follows:
image

This means that there could be better code separation - the html should command the content for the large part, the css the styling and the js should react to user inputs. Maintaining this separation will make it easier to find bugs.

As @sanity states above the use of global variables should be avoided.

Despite the above you only an additional three tests to pass. The first of these is:

When price is 19.5 , the value in the #cash element is 20 , cid is [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]] , and the #purchase-btn element is clicked, the value in the #change-due element should be "Status: OPEN QUARTER: $0.5" .

Solve this problem and you will most likely find the key to passing the remaining two tests.

Does this help?

Thank you both @sanity and @ios-man for your replies.
Following your advice, I rewrote my code as bellow.

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8" />
    <title>Cash Register</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>

  <body>
    <main>
      <h1>Cash Register</h1>
      <div id="change-due">
      </div>
      <p>Enter cash from customer:</p>
      <input type="number" id="cash">
      <button type="click" id="purchase-btn">Purchase</button>

      <form class="cash-register">
        <div class="price">
        <p>Total: <span id="price"></span></p>
        </div>
        <div id="cid">
        </div>
      </form>

    </main>
    <script src="./script.js"></script>
  </body>

</html>
let price = 19.5;
let cid = [
  ["PENNY", 0.01, 0.5],
  ["NICKEL", 0.05, 0],
  ["DIME", 0.1, 0],
  ["QUARTER", 0.25, 0],
  ["ONE", 1, 2],
  ["FIVE", 5, 0],
  ["TEN", 10, 0],
  ["TWENTY", 20, 0],
  ["ONE HUNDRED", 100, 0]
];

const changeDue = document.getElementById("change-due");
const cash = document.getElementById("cash");
const purchaseBtn = document.getElementById("purchase-btn");
const priceDisplay = document.getElementById("price")
const cidDisplay = document.getElementById("cid");

const roundAtMostTwoDecimal = (amount) => Math.round(amount * 100) / 100;

const statusUpdate = (status, arr) => {
  changeDue.innerHTML = `<p>Status: ${status}</p>`;
  arr.map(
    currency => changeDue.innerHTML += `<p>${currency[0]}: $${currency[1]}</p>`
  );
  return;
}

const transaction = () => {
  let cashValue = cash.value;
  let change = roundAtMostTwoDecimal(cashValue - price);
  let changeInDrawer = cid;
  let applicableCurrencies = changeInDrawer.filter((currency) => currency[1] <= change && currency[2] > 0).reverse();
  let valuesOfApplicableCurrencies = [];
  applicableCurrencies.forEach((currency) => valuesOfApplicableCurrencies.push(currency[2]));
  let sumOfApplicableCurrencies = valuesOfApplicableCurrencies.reduce((a, b) => a + b, 0);
  let valuesOfCid = [];
  changeInDrawer.forEach((currency) => valuesOfCid.push(currency[2]));
  let sumOfCid = valuesOfCid.reduce((a, b) => a + b, 0);

  if (change < 0) {
    alert("Customer does not have enough money to purchase the item");
    return;
  }

  if (change == 0) {
    changeDue.innerHTML = `
    <p>No change due - customer paid with exact cash</p>
    `;
    return;
  }

  if (change > 0) {
    let changeToReturn = [];

    if (change > sumOfApplicableCurrencies) {
      changeDue.innerHTML = `<p>Status: INSUFFICIENT_FUNDS</p>`;
      return;
    }

    if (change === sumOfCid) {
      applicableCurrencies.forEach((currency) => changeToReturn.push([currency[0], currency[2]]));
      statusUpdate("CLOSED", changeToReturn);
      drawerUpdate(changeToReturn);
      return;
    }

    if ((change < sumOfApplicableCurrencies) || (change === sumOfApplicableCurrencies && sumOfApplicableCurrencies < sumOfCid)) {
      for (let i = 0; i < applicableCurrencies.length; i++) {
        if (change >= applicableCurrencies[i][1]) { //specify currency
          let count = 0;
          let denom = applicableCurrencies[i][1]
          let total = applicableCurrencies[i][2];
          while (change >= denom && total > 0) {
            total -= roundAtMostTwoDecimal(denom);
            change = roundAtMostTwoDecimal(change - denom);
            count++;
          }
          if (count > 0) {
            changeToReturn.push([applicableCurrencies[i][0], count * denom]);
          }
        }
      }
      statusUpdate("OPEN", changeToReturn);
      drawerUpdate(changeToReturn);
      return;
    }
  }
}

const drawerUpdate = (arr) => { //arr = changeToReturn
  if (arr) {
    arr.forEach(currency => {
      const spentArr = cid.find(money => money[0] === currency[0]);
      spentArr[2] = roundAtMostTwoDecimal(spentArr[2] - currency[1]);
      console.log(spentArr);
    })
  }

  priceDisplay.textContent = price;
  cidDisplay.innerHTML = `<p><strong>Change in drawer:</strong></p>
  ${cid
    .map(currency => `<p>${currency[0]}: $${currency[2]}</p>`)
    .join("")}
   `;
}

purchaseBtn.addEventListener("click", transaction);

drawerUpdate();

I think that this time I avoided using global variables as much as I could, but my code doesn’t still pass the test cases even though the outputs match what the test cases expect.

Are there still improperly used global variables or any other issues?

I haven’t noticed it before, but combining cash in drawer (cid) with denomination values in cid variable will not work. Arrays in cid has to contain two items - name of the currency and amount of money in that currency in drawer.

The cid variable is described as a 2-D array, but there is nothing stating that it cannot take-on a dimensional value at least temporarily. It is conceivable to add a denomination value, perform the necessary processing, remove the denomination value and return the result. A functional approach would make light work of that process.

Sure, I should be a bit more precise. Currently transaction function expects elements in cid to include additional information, than what will be in cid at the start of each test. When tests run and cid is being changed for specific test case, that additional data will not be there.

2 Likes

My code finally passed the test cases by keeping the cid variable two-dimensional.
(Since the three-dimensional cid somehow passed the test cases for insufficient funds, I assumed I could keep going with the 3-D cid and that seemed to result in the error as you point out.) :sweat_smile:

Thank you so much for your help @sanity @ios-man !! :pray:

1 Like