ace-customizer-lib
Version:
ACE knowledge platform scripts
345 lines (282 loc) • 10 kB
JavaScript
/**
* Credit note calculator
* Framework: Vue.js
* Author: Jannik Maag, Telia Company
* Date: 13 July 2022
* License: MIT
*/
Vue.component("creditnote-calculator", {
data: function () {
return {
dateRangeCollection: [],
startDate: null,
endDate: null,
calculationRows: [],
creditnoteDateInputValue: null,
BRAND_ENUMS: {
CALLME: "CALLME",
TELIA: "TELIA",
MITTELE: "MITTELE",
},
};
},
created() {
this.creditnoteDateInputValue = new Date();
},
mounted() {
const amountOfRows = 7;
for (let i = 0; i < amountOfRows; i++) {
this.calculationRows.push({
id: `calculation-${i}`,
inputPrice: null,
resultPrice: null,
note: "",
});
}
},
computed: {
totalWithVat() {
let sum = 0;
this.calculationRows.forEach((x) => (sum += parseFloat(x.resultPrice)));
return sum.toFixed(2);
},
totalWithoutVat() {
// Danish VAT is 25%
return parseFloat(this.totalWithVat / 1.25).toFixed(2);
},
creditnoteDate() {
return new Date(this.creditnoteDateInputValue);
},
visibleOnBillDate() {
const date = this.creditnoteDate;
const toString = this.convertDateToString;
const teliaBillingDate = this.dateIsBeforeDayInMonth(date, 5)
? this.addMonthsToDate(date, 1)
: this.addMonthsToDate(date, 2);
const callmeAndMitteleBillingDate = this.dateIsBeforeDayInMonth(date, 15)
? this.addMonthsToDate(date, 1)
: this.addMonthsToDate(date, 2);
return {
[this.BRAND_ENUMS.TELIA]: toString(teliaBillingDate),
[this.BRAND_ENUMS.CALLME]: toString(callmeAndMitteleBillingDate),
[this.BRAND_ENUMS.MITTELE]: toString(callmeAndMitteleBillingDate),
};
},
dateRangeIsValid() {
return !!this.startDate && !!this.endDate;
},
},
watch: {
startDate() {
this.dateChangeHandler();
},
endDate() {
this.dateChangeHandler();
},
calculationRows: {
deep: true,
handler() {
this.reCalculateAllRows();
},
},
creditnoteDateInputValue(date) {
if (!date) {
// Default to todays date if user clears the date input field.
this.creditnoteDateInputValue = new Date();
}
},
},
methods: {
calculateResultPrice(monthlyPrice) {
if (!monthlyPrice || !this.dateRangeIsValid) return 0;
const converted = parseFloat(monthlyPrice.replace(",", "."));
console.log(converted);
let result = 0;
this.dateRangeCollection.forEach((x) => {
// No reason to calculate if there's no days in the month
if (x.days <= 0) return;
// Find out how many calendar days there is in the given month
const daysInMonth = new Date(x.year, x.month + 1, 0).getDate();
/*
* BUSINESS RULES FOR CREDIT NOTE CALCULATION
* In a month of 28 days, 28 days should be considered a full month
* In a month of 30 days, 30 days should be considered a full month
* In a month of 31 days, both 30 days and 31 days should be considered a full month
*/
const shouldBeConsideredFullMonth = x.days >= daysInMonth;
if (shouldBeConsideredFullMonth) {
// Add the subscriptions full price for a single month
result += parseFloat(monthlyPrice);
} else {
// Calculate price based on subscription daily price
// Must always be based on 30 days as per business rule
result += (parseFloat(monthlyPrice) / 30) * x.days;
}
});
return parseFloat(result);
},
parseComma(value) {
return value ? value.replace(",", ".") : value;
},
reCalculateAllRows() {
this.calculationRows.forEach((x) => {
x.resultPrice = this.calculateResultPrice(
this.parseComma(x.inputPrice)
);
});
},
convertDateToString(date) {
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`;
},
dateChangeHandler() {
this.clearDateRangeCollection();
if (this.dateRangeIsValid) {
this.createDateRangeCollection();
this.reCalculateAllRows();
}
},
createDateRangeCollection() {
if (!this.dateRangeIsValid) return;
let loop = new Date(this.startDate);
const end = new Date(this.endDate);
while (loop <= end) {
const entry = this.dateRangeCollection.find((entry) => {
// Find an entry that matches the year and month
return (
entry.year === loop.getFullYear() && entry.month === loop.getMonth()
);
});
if (entry) entry.days++;
else
this.dateRangeCollection.push({
month: loop.getMonth(),
year: loop.getFullYear(),
days: 1, // TODO: should start from 1 perhaps somewhere else
});
const newDate = loop.setDate(loop.getDate() + 1);
loop = new Date(newDate);
}
},
clearDateRangeCollection() {
this.dateRangeCollection = [];
},
clearEverything() {
this.startDate = null;
this.endDate = null;
this.creditnoteDateInputValue = new Date();
this.clearDateRangeCollection();
this.calculationRows.forEach((row) => {
row.inputPrice = null;
row.resultPrice = null;
row.note = "";
});
},
copyToClipboard(value) {
navigator.clipboard
.writeText(value)
.then(() => {
alert("Kopieret ".concat(value));
})
.catch(() => {
alert(
"Der skete en FEJL. Din browser er muligvis ikke understøttet."
);
});
},
addMonthsToDate(date, monthsToAdd) {
if (!date || !monthsToAdd) return date;
return new Date(date.getFullYear(), date.getMonth() + monthsToAdd, 1);
},
dateIsBeforeDayInMonth(date, day) {
if (!date || !day) return false;
const dateToCompare = new Date(date.getFullYear(), date.getMonth(), day);
return date <= dateToCompare;
},
},
template: `
<div class="creditNoteCalc_wrapper" id="creditnotecalc3">
<div class="prop-container">
<div class="upper">
<div class="date-pick-block date-pick-block date-picker-x-container">
<div class="btn-keep">
<i class="fa fa-calendar" style="color: #393939; font-size: 22px;"></i>
</div>
<p class="date-pick-text">Periode:</p>
<input type="date"
class="date-period"
v-model="startDate"
/>
<p class="date-pick-text">til</p>
<input type="date"
class="date-period"
v-model="endDate"
/>
<button v-on:click="clearEverything()" class="calc-reset-btn">
Nulstil alt
</button>
</div>
</div>
<div class="main-area">
<section class="calc-row">
<div class="row-part note">Abonnementspris</div>
<div class="row-part note">Kreditnota</div>
<div class="row-part note">Notat</div>
</section>
<section v-for="row in calculationRows" key="row.id" class="calc-row">
<div class="row-part">
<input
class="priceInput"
placeholder="Tast pris"
type="text"
v-model="row.inputPrice"
/>
</div>
<div class="row-part">
<div class="calcResult">{{ !dateRangeIsValid ? "Vælg en periode" : row.resultPrice }}</div>
</div>
<div class="row-part">
<input class="noteInput" placeholder="Skriv notat" v-model="row.note" />
</div>
</section>
</div>
</div>
<div class="result-container">
<section class="upper">
<div class="result-header">
<div class="header-icon">
<i class="fa fa-money" style="color: #f4f4f4; font-size: 32px;"></i>
</div>
<h2 class="result-headline">Kreditnota</h2>
</div>
</section>
<section class="middle">
<div class="total-container">
<p class="total-text">Total kreditnota inkl. moms</p>
<div id="creditTotal">{{ totalWithVat }} kr.</div>
<div id="icon-copy-total-credit" v-on:click="copyToClipboard(totalWithVat)">
<i class="fa fa-copy" style="color: white; cursor: pointer; font-size: 20px;"></i>
</div>
<p class="total-text">Eksl. moms</p>
<div id="exMoms">{{ totalWithoutVat }} kr.</div>
<div id="icon-copy-ex-moms" v-on:click="copyToClipboard(totalWithoutVat)">
<i class="fa fa-copy" style="color: white; cursor: pointer; font-size: 20px;"></i>
</div>
</div>
<div class="littleTip">
<div style="font-size: 12px;">
<p>Hvis kreditnotaen lægges på d. {{ convertDateToString(creditnoteDate) }} <br />
Vil den fremgå af kundens regning d.:</p>
<p>
<div>Telia: {{ visibleOnBillDate[BRAND_ENUMS.TELIA] }}</div>
<div>Callme: {{ visibleOnBillDate[BRAND_ENUMS.CALLME] }}</div>
<div>Mit Tele: {{ visibleOnBillDate[BRAND_ENUMS.MITTELE] }}</div>
</p>
<p style="margin: unset;">Vælg en anden dato:</p>
<input style="line-height: unset; color: black;" type="date" v-model="creditnoteDateInputValue" />
</div>
</div>
</section>
</div>
</div>
`,
});