Cash Register Feedback

Hi coders! Would love feedback on my solution for the cash register project. I’m especially interested in feedback on best practices, so things like my variable names, modularity, and especially the comments. I’m trying to learn how to wirte “good” code too, and since this took me waaay longer than anything else so far, I put a lot of effort into making it pretty. Thank you!

const cashInput = document.getElementById("cash");
const changeDueDiv = document.getElementById("change-due");
const purchaseBtn = document.getElementById("purchase-btn");
let price = 19.5;

/*
CID array is Cash in Drawer.
If new currency added, the function getDenominationValue must be updated.
*/
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]
];

/*
Handles overhead when a purchase is submitted.
Uses functions to calculate the change due, update CID, and then updates the changeDue display.
Alerts when a non-number is entered for cash, or if cash is not sufficient for the price.
Calls:  calculateChange, generateChangeDueDiv.
*/
function handlePurchase() {
  clearChangeDueDiv();
  let cash = Number(cashInput.value);
  if (isNaN(cash)) {
   alert("Please enter a number");
  } else if (cash < price) {
    alert("Customer does not have enough money to purchase the item");
  } else if (cash === price) {
    changeDueDiv.innerHTML = "No change due - customer paid with exact cash";
  } else {
    const changeDueArray = calculateChange(cash);
    completePurchase(changeDueArray);
    changeDueDiv.innerHTML = generateChangeDueDiv(changeDueArray);
  }
}

/*
Calculates the change due, using the highest denominations first, if available.
Does not update CID but verifies sufficient funds. Assumes cash > price.
Params:     cash(number), the amount chash entered.
Returns:    An array of the denomination names and total value of each to give in change.
            Returns NULL if change cannot be given.
Called by:  handlePurchase.
Calls:      getDenominationValue.
*/
function calculateChange(cash) {
  let changeDue = cash - price;
  let changeArray = [];
  // Loop through denominations in CID
  for (let i = cid.length - 1; i >= 0; i--) {
    let denomination = getDenominationValue(cid[i][0]);
    if (!denomination) {
      console.error("Invalid denomination. Check CID array and getDenominationValue");
      return;
    }
    let changeInDenomination = 0;
    // While current CID denomination is less than the remaining change that is due, and if CID has funds available for it,
    // then increment the change in that denomination and decrement remaining change due
    while (Math.round(denomination * 100) <= Math.round(changeDue * 100)) {
      if (Math.round(cid[i][1] * 100) > Math.round(changeInDenomination * 100)) {
        changeDue -= denomination;
        changeInDenomination += denomination;
        // If CID does not have sufficient funds to make the change
      } else if (i === 0) {
        return null;
      } else {
        break;
      }
    }
    // Only add to changeArray if that denomination gets payout
    if (changeInDenomination > 0) {
      changeArray.push([cid[i][0], changeInDenomination])
    }
  }
  return changeArray;
}

/*
Helper function. 
Params:     String version of denomination.
Returns:    the number version of the named denomination.
Returns     null if denomination name invalid.
Called by:  calculateChange
*/
function getDenominationValue(denomination) {
  switch (denomination) {
    case "ONE HUNDRED":
      return 100;
    case "TWENTY":
      return 20;
    case "TEN":
      return 10;
    case "FIVE":
      return 5;
    case "ONE":
      return 1;
    case "QUARTER":
      return 0.25;
    case "DIME":
      return 0.10;
    case "NICKEL":
      return 0.05;
    case "PENNY":
      return 0.01;
    default:
      return null;
  }
}

/*
Removes change being given from the CID array.
Params:     An array of length-2 arrays, the string representation of the denomination,
            and the total value of change for each.
Returns:    True if the register remains open, false if it has run out of funds.
Called by:  handlePurchase.
*/
function completePurchase(changeDueArray) {
  if (changeDueArray) {
    for (const changeDueItem of changeDueArray) {
      const cidIndex = cid.findIndex(cidItem => cidItem[0] === changeDueItem[0]);
      cid[cidIndex][1] -= changeDueItem[1];
    }
  }
}

/*
Updates the display with the change due.
Params:     An array of length-2 arrays, the string representation of the denomination,
            and the total value of change for each.
Called by:  handlePurchase.
Calls:      isRegisterClosed.
*/
function generateChangeDueDiv(changeDueArray) {
  let html;
  if (!changeDueArray) {
    html = "Status: INSUFFICIENT_FUNDS";
  } else {
    html = `<p>Status: ${isRegisterClosed() ? "CLOSED" : "OPEN"}`;
    for (const changeDueItem of changeDueArray) {
      html += `<p>${changeDueItem[0]}: $${Math.round(changeDueItem[1] * 100) / 100}`;
    }
  }
  return html;
}

function isRegisterClosed() {
  return !cid.some(cidItem => Math.round(cidItem[1] * 100) > 0);
}

function clearChangeDueDiv() {
  changeDueDiv.innerHTML = "";
}

cashInput.addEventListener("keydown", event => {
  if (event.key === "Enter") handlePurchase();
});
purchaseBtn.addEventListener("click", handlePurchase);
1 Like

I’d start with a bit of self-reflection. What did you find the hardest, the easiest? Are there any specific parts that you’d like to improve? Anything that surprised you?