This is a follow-up question to original Prorated Refund Calculator. Here is the summary of the changes I made since last, thanks to the good answers and some study of my own.
- Did away with bleeding-edge ECMAScript 6 code that was causing compatibility issues with some browsers.
- Added more input validation.
- Added a utility function to format dates to MM/DD/YYYY format.
- Input values are now obtained using
getElementById
instead of getting them from a numbered list. - Output is appended into the existing page, rather than updating placeholder HTML tags.
- Documentation throughout.
- Output is more comprehensive and user-friendly.
Is there anything else that I could improve before I share this with my colleagues?
<!DOCTYPE html>
<html>
<head>
<title>Prorated Refund Calculator</title>
<style>
body {
font-family: Calibri, Arial, sans-serif;
font-size: 1.0em;
}
h1 {
font-size: 1.2em;
}
</style>
</head>
<body>
<h1>Prorated Refund Calculator</h1>
<p>Input into the following fields and press Calculate.</p>
<p style="color: red;">Enter date format as MM/DD/YYYY</p>
<form>
<label>Product Purchase Date: <input type="text" id="productPurchaseDate" /> </label><br/>
<label>Contract Purchase Date: <input type="text" id="contractPurchaseDate" /> </label><br/>
<label>Purchase Price: <input type="text" id="purchasePrice" /> </label><br/>
<label>Term (in years): <input type="text" id="termInYears" value="10" /> </label><br/>
<label>Cancel Date: <input type="text" id="cancelDate" /> </label><br/>
<label>Amount paid in claims: <input type="text" id="amtPaidInClaims" value="0" /> </label><br/>
<label>Grace period (in days): <input type="text" id="gracePeriodInDays" value="60" /></label><br/>
</form>
<br/>
<button onclick="calculateProratedRefund()">Calculate</button>
<script type="text/javascript">
/**
* Main function called by HTML form.
* Validates input values, then performs calculations
* and adds the result into the HTML document to display to the user.
*/
function calculateProratedRefund() {
"use strict";
var productPurchaseDate = new Date(document.getElementById("productPurchaseDate").value);
var contractPurchaseDate = new Date(document.getElementById("contractPurchaseDate").value);
var purchasePrice = parseFloat(document.getElementById("purchasePrice").value).toFixed(2);
var termInYears = parseInt(document.getElementById("termInYears").value, 10);
var cancelDate = new Date(document.getElementById("cancelDate").value);
var amtPaidInClaims = parseFloat(document.getElementById("amtPaidInClaims").value).toFixed(2);
var gracePeriodInDays = parseInt(document.getElementById("gracePeriodInDays").value, 10);
/**
* Underwriting policies specify that a Year of coverage is exactly 365 Days, regardless of Leap Years.
* Hence the following calculations:
*/
var totalDays = (termInYears * 365);
var expirationDate = new Date(productPurchaseDate);
expirationDate.setDate(productPurchaseDate.getDate() + (totalDays));
/**
* Confirm that all dates are valid Date values and that they follow business rules before proceeding to calculations.
*/
if ( termInYears < 0 ) {
window.alert("Term in years must be greater than zero.")
}
else if ( !isValidDate(productPurchaseDate) ) {
window.alert("Invalid ProductPurchase Date.");
}
else if ( !isValidDate(contractPurchaseDate) ) {
window.alert("Invalid Contract Purchase Date.");
}
else if ( !isValidDate(cancelDate) ) {
window.alert("Invalid Cancel Date.");
}
else if ( cancelDate < productPurchaseDate || cancelDate < contractPurchaseDate ) {
window.alert("Cancel date cannot be prior to Product or Contract purchase date.");
}
else if ( cancelDate > expirationDate ) {
window.alert("Cancel date cannot be past Expiration date.");
}
/**
* Confirm that all numbers are valid numbers for calculations.
*/
else if ( isNaN(purchasePrice) || isNaN(termInYears) || isNaN(amtPaidInClaims) || isNaN(gracePeriodInDays) ) {
window.alert("Invalid Number Entry. Please check your entries and try again.");
}
/**
* If all the input values are valid, we proceed to calculations.
*/
else {
/**
* All terms are calculated in Days, hence the following conversion from JavaScript's millisecond precision to Days.
* (ms * secs * mins * hours) = 1 Day
*/
var msPerDay = (1000 * 60 * 60 * 24);
var daysElapsed = Math.floor(( Date.parse(cancelDate) - Date.parse(contractPurchaseDate) ) / msPerDay);
/**
* Policy holder is entitled to a full refund within the grace period.
*/
var termUsed = 0.0;
if ( daysElapsed <= gracePeriodInDays ) {
termUsed = 0.0;
}
else {
termUsed = (daysElapsed / totalDays);
}
var termLeft = 1.0 - termUsed;
/**
* Finally we calculate the refund amount
*/
var proratedRefund = (purchasePrice * termLeft).toFixed(2);
var finalRefund = (proratedRefund - amtPaidInClaims).toFixed(2);
if ( finalRefund < 0.0 ) {
finalRefund = 0.0;
}
/**
* Output calculation results to HTML document.
*/
var outputPar = document.createElement("p");
outputPar.appendChild(document.createTextNode("Product Purchase date: " + formatDate(productPurchaseDate)));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Contract Purchase date: " + formatDate(contractPurchaseDate)));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Total days: " + totalDays + " (" + termInYears + " years)"));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Expiration date: " + formatDate(expirationDate)));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Cancel date: " + formatDate(cancelDate)));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Grace period: " + gracePeriodInDays + " days"));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Days elapsed: " + daysElapsed + " (" + parseFloat(daysElapsed / 365).toFixed(2) + " years)"));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Refund percent: " + (termLeft * 100).toFixed(2) + " %"));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Purchase price: $ " + purchasePrice));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Prorated refund: $ " + proratedRefund));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Paid in claims: $ " + amtPaidInClaims));
outputPar.appendChild(document.createElement("br"));
outputPar.appendChild(document.createTextNode("Final refund: $ " + finalRefund));
outputPar.appendChild(document.createElement("br"));
document.body.appendChild(outputPar);
}
}
/**
* Utility function to make sure an input is a correct Date format
* @param date Date - A date or date-time value.
* @returns boolean - Whether the input date is a valid date.
*/
function isValidDate(date) {
"use strict";
if ( Object.prototype.toString.call(date) !== "[object Date]" ) {
return false;
}
else if ( isNaN(date.getTime()) ) {
return false;
}
else {
return true;
}
}
/**
* Utility function to convert a Date to MM/DD/YYYY formatted String.
* @param date Date - A date or date-time value.
* @returns String - A String formatted to MM/DD/YYYY date if date is valid, otherwise a String with error message.
*/
function formatDate(date) {
"use strict";
var inputDate = new Date(date);
if ( isValidDate(inputDate) ) {
var year = inputDate.getFullYear();
var month = (1 + inputDate.getMonth()).toString(); // months are 0-indexed hence the 1 + getMonth()
var day = inputDate.getDate().toString();
return(month + '/' + day + '/' + year);
}
else {
return("Invalid date: " + inputDate);
}
}
</script>
</body>
</html>
Sample input:
Product Purchase date: 6/1/2013
Contract Purchase date: 1/15/2014
Purchase Price: 699
Term (in years): 10
Cancel date: 07/13/2015
Paid in claims: 147
Grace period: 60
Resulting output:
Product Purchase date: 6/1/2013 Contract Purchase date: 1/15/2014 Total days: 3650 (10 years) Expiration date: 5/30/2023 Cancel date: 7/13/2015 Grace period: 60 days Days elapsed: 543 (1.49 years) Refund percent: 85.12 % Purchase price: $ 699.00 Prorated refund: $ 595.01 Paid in claims: $ 147.00 Final refund: $ 448.01