Merge branch 'release/2025.03.27' into origin/feature/CASH-399

This commit is contained in:
mvalaiyapathi 2025-03-19 13:17:33 -04:00 committed by GitHub
commit fbbadd721f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 634 additions and 400 deletions

View file

@ -72,6 +72,10 @@ const endpoints = {
url: "/parts/api/v1/parts/mobile-fee",
method: "GET",
},
GetPricingByDayUpchargePart: {
url: "/parts/api/v1/parts/get-pricing-by-day-part-number",
method: "GET",
},
GetServicePackageDiscountPart: {
url: "/parts/api/v1/parts/service-package-discount",
method: "POST",

View file

@ -3,6 +3,8 @@ const partNumberStrings = {
MOBILE_STATIC_RECAL_FEE: "RECAL MOBILE",
MOBILE_DUAL_RECAL_FEE: "RECAL MOBILEDUAL",
DONATION: "DONATION",
// Fees
RECYCLE_FEE: "RECYCLE FEE",
};
export { partNumberStrings };

View file

@ -8,9 +8,17 @@ const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM";
const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";
const PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE = "DISC CASHSAVE20";
const RouteCodeFlags = {
ALL_DAY_DROP_OFF: "ALL DAY DROP OFF",
OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF",
};
export { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE, RouteCodeFlags };
export {
AppointmentTypeStrings,
PREMIUM_TIME_SLOT_ID_FLAG,
PREMIUM_FEE_PART_TYPE,
PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE,
RouteCodeFlags,
};

View file

@ -30,6 +30,7 @@ const storeActions = {
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer",
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
GET_MOBILE_FEE_PART: "getMobileFeePart",
GET_PRICING_BY_DAY_UPCHARGE_PART: "getPricingByDayUpchargePart",
GET_SERVICE_PACKAGE_DISCOUNT_PART: "getServicePackageDiscountPart",
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",

View file

@ -23,4 +23,6 @@ const MONTHS_OF_YEAR = [
const DAYS_OF_WEEK = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, DAYS_OF_WEEK };
const PREMIUM_DAY_INDEXES = [1, 5, 6]; // assign Monday, Friday, Saturday to be premium days
export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, DAYS_OF_WEEK, PREMIUM_DAY_INDEXES };

View file

@ -135,7 +135,7 @@ export default {
},
closeModal() {
const modal = Modal.getInstance(document.getElementById(this.modalId));
modal.hide();
modal?.hide();
this.$emit("isModalOpened", false);
},
},

View file

@ -1,5 +1,5 @@
<template>
<div class="date-picker text-center" :class="calendarViewDirection">
<div class="date-picker text-center" :class="`${calendarViewDirection} ${isPricingByDayClass}`">
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
<legend class="sr-only">Select a day and time</legend>
<div
@ -66,12 +66,17 @@
type="radio"
name="day-of-month"
v-model="selectedDate"
@click="fireDateSelectedEvent"
@keypress.enter="fireDateSelectedEvent"
@click="fireDateSelectedEvent($event, date)"
@keypress.enter="fireDateSelectedEvent($event, date)"
:value="date.inputValue"
:id="`${month.monthLabel}-${date.dateNum.toString()}`" />
<label :for="`${month.monthLabel}-${date.dateNum.toString()}`">
<span>{{ date.dateNum.toString() }}</span>
<span
v-if="isPricingByDayExperiment && showPricingByDay && date.isSelectable"
class="price">
{{ date.priceString }}
</span>
</label>
</div>
</div>
@ -103,6 +108,7 @@ import {
TIMINGFUNC_MAP,
BUFFER_OFFSET,
MONTHS_OF_YEAR,
PREMIUM_DAY_INDEXES,
} from "@/digital-components/date-picker/mixins/constants";
import {
selectableDaysOptions,
@ -156,6 +162,10 @@ export default {
type: String,
default: "",
},
showPricingByDay: Boolean,
baseDayPrice: Number,
pricingByDayUpcharge: Number,
isPricingByDayExperiment: Boolean,
},
setup(props) {
const uuid = uuidv4();
@ -207,6 +217,9 @@ export default {
if (this.selectableDatesSetting === "custom") return "future";
return "past";
},
isPricingByDayClass() {
return this.isPricingByDayExperiment ? "pricing-by-day" : "";
},
selectedDate: {
get() {
return this.modelValue;
@ -220,17 +233,12 @@ export default {
initializeComponent(initialData) {
this.setCalendarData(initialData);
},
fireDateSelectedEvent(event) {
fireDateSelectedEvent(event, date) {
// Ignore if arrow key selected radioButton
if (event.screenX === 0 && event.screenY === 0) {
return;
}
this.$emit("date-clicked");
},
fireDateSelectedEvent2(date) {
console.log("date: ", date);
this.selectedDate = date;
this.$emit("date-clicked");
this.$emit("date-clicked", date);
},
getWeekStartDate(dateString) {
const date = convertDateStringToDate(dateString);
@ -429,6 +437,8 @@ export default {
hideSomeDaysForInitialView: hideSomeDaysForInitialView,
hideSecondMonth: hideSecondMonth,
preSelectedDate: config.preSelectedDate,
baseDayPrice: config.baseDayPrice,
pricingByDayUpcharge: config.pricingByDayUpcharge,
};
return initialData;
});
@ -453,6 +463,8 @@ export default {
initialViewEndDate: config.initialViewEndDate,
hideSecondMonth: hideSecondMonth,
preSelectedDate: config.preSelectedDate,
baseDayPrice: config.baseDayPrice,
pricingByDayUpcharge: config.pricingByDayUpcharge,
};
if (direction === "future") {
// first 0, then 1
@ -586,6 +598,17 @@ export default {
("0" + monthNum).slice(-2) +
"-" +
("0" + i).slice(-2);
const dayIndex = convertDateStringToDate(dateString).getDay();
const isSelectable =
this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
? true
: false;
const isPricingByDayUpchargeDay = PREMIUM_DAY_INDEXES.includes(dayIndex);
let displayPrice = isPricingByDayUpchargeDay
? options.baseDayPrice + options.pricingByDayUpcharge
: options.baseDayPrice;
const priceString = "$" + displayPrice;
if (offset === 0 && i === this.todayDateNum) {
dayClasses += " current-day";
@ -596,7 +619,7 @@ export default {
if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") {
dayClasses += " unavailable-day";
}
if (convertDateStringToDate(dateString).getDay() === 0) {
if (dayIndex === 0) {
dayClasses += " sunday";
}
if (
@ -611,10 +634,9 @@ export default {
dateNum: i,
dayClasses: dayClasses,
inputValue: dateString,
isSelectable:
this.selectableDatesData.findIndex((date) => date.date === dateString) > -1
? true
: false,
priceString: priceString,
isSelectable: isSelectable,
isPricingByDayUpchargeDay: isPricingByDayUpchargeDay,
};
dates.push(dateObject);
}
@ -779,7 +801,7 @@ export default {
.grid-item {
text-align: center;
margin: 0;
margin: 10px 3px;
font-size: 0.875rem;
line-height: 1.5;
@ -814,13 +836,12 @@ export default {
}
.month-year {
grid-area: 1 / 1 / 2 / 8;
grid-area: 1 / 1 / 2 / 5;
text-transform: uppercase;
font-weight: 300;
letter-spacing: 0.75px;
}
.legend {
display: none;
grid-area: 1 / 5 / 2 / 8;
.legend-circle {
border-radius: 50%;
@ -839,27 +860,43 @@ export default {
position: relative;
display: flex;
justify-content: center;
align-items: flex-start;
align-items: center;
outline: none;
width: 100%;
height: 1.5rem;
opacity: 1;
transition:
height ease 250ms,
opacity ease 250ms;
color: $gray-500;
background-color: $gray-100;
input[type="radio"] {
position: absolute; //override bootstrap
height: 0;
opacity: 0;
&:checked + label {
background: $blue-100;
border: 2px solid $blue;
border-radius: 4px;
&:focus-visible + label {
box-shadow: 0 0 0 2.5px $blue;
}
&:focus + label,
&:checked:focus + label {
box-shadow:
0 0 0 3px #fff,
0 0 0 5.5px #1574a1;
background-color: $blue;
color: $white;
&.current-day {
&:after {
background-color: $white;
}
.first-day {
color: $white;
}
}
}
&:checked + label {
color: $white;
background: $blue;
&:after {
background-color: $white;
}
@ -878,28 +915,17 @@ export default {
z-index: 1;
}
button {
width: 2.5rem;
font-size: 8px;
background: pink;
}
label {
position: relative;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-width: 2.5rem;
height: 3rem;
width: 100%;
font-size: 0.75rem;
line-height: 1.2;
justify-content: flex-start;
padding: 0.25rem;
width: 2.5rem;
height: 2.5rem;
span {
margin-top: 0.25rem;
&.small {
font-size: 0.75rem;
color: $gray-550;
@ -931,17 +957,23 @@ export default {
}
&.selectable-day {
background-color: $white;
label {
color: $blue;
cursor: pointer;
span {
text-decoration: underline;
}
background-color: $blue-100;
border: 1px solid $blue;
min-width: 2.5rem;
width: 2.5rem;
border-radius: 50%;
}
}
&.unavailable-day {
label {
color: $gray-500;
background-color: $gray-100;
border: none;
pointer-events: none;
}
}
&.unavailable-day:not(&.sunday) {
label {
&::before {
@ -962,11 +994,12 @@ export default {
&:after {
content: "";
margin-top: 0.2rem;
width: 0.25rem;
height: 0.25rem;
border-radius: 50%;
background-color: $black;
position: absolute;
top: 28px;
}
.first-day {
color: $blue;
@ -1057,7 +1090,83 @@ export default {
}
}
}
&.pricing-by-day {
// pricing by day override styles
.calendar-grid-container .grid-item {
margin: 0;
}
.month-year {
grid-area: 1 / 1 / 2 / 8;
}
.legend {
display: none;
}
.radio-wrapper {
align-items: flex-start;
width: 100%;
height: 3rem;
color: $gray-500;
background-color: $gray-100;
input[type="radio"] {
&:focus-visible + label {
box-shadow: none;
}
&:focus + label,
&:checked:focus + label {
box-shadow: none;
background-color: $white;
color: $blue;
}
&:checked + label {
background: $blue-100;
border: 2px solid $blue;
border-radius: 4px;
color: $blue;
}
}
label {
flex-direction: column;
height: 3rem;
width: 100%;
font-size: 0.75rem;
line-height: 1.2;
justify-content: flex-start;
padding: 0.25rem;
span {
margin-top: 0.25rem;
}
}
&.selectable-day {
background-color: $white;
label {
background-color: $white;
border: none;
min-width: initial;
width: initial;
height: -webkit-fill-available;
border-radius: 0;
cursor: pointer;
span {
text-decoration: underline;
}
}
}
&.current-day {
label:after {
margin-top: 0.2rem;
position: relative;
top: 0;
}
}
}
}
}
.btn-link {
display: block;
position: relative;

View file

@ -169,10 +169,16 @@ import { deepClone } from "@/helpers/object-helper";
import {
getHighestFullySatisfiedTier,
getPackageContents,
getDiscountPackageName,
getDiscountedPackageName,
} from "@/helpers/service-package-helper";
import { getPromoCodeWithoutBundleIdentifier } from "@/helpers/promotions-helper";
import { storeActions } from "@/constants/store-actions";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
// Constants
import { partTypeStrings } from "@/constants/part-type-strings";
@ -467,7 +473,7 @@ export default {
const discountServicePackage = experimentMixin.methods.getSettingValue(
experimentSettings.PROMO_ON_PACKAGE
);
return getDiscountPackageName(discountServicePackage);
return getDiscountedPackageName(discountServicePackage);
},
servicePackageTitleWidget() {
const servicePackageNames = this.getCmsContent(
@ -1082,8 +1088,8 @@ export default {
subTotal() {
if (!this.lineItems || this.lineItems.length < 1) return;
return this.isInsurance || !this.shouldHideRecalibration
? baseMixin.methods.getSubTotal(this.lineItems) // calculate with recal (if on order)
: baseMixin.methods.getSubTotal(this.lineItemsWithoutRecal); // calculate without recal
? getSubTotal(this.lineItems) // calculate with recal (if on order)
: getSubTotal(this.lineItemsWithoutRecal); // calculate without recal
},
salesTax() {
if (!this.lineItems || this.lineItems.length < 1) return;
@ -1098,15 +1104,15 @@ export default {
}
return this.isInsurance || !this.shouldHideRecalibration
? baseMixin.methods.getSalesTax(this.lineItems) // calculate with recal (if on order)
: baseMixin.methods.getSalesTax(this.lineItemsWithoutRecal); // calculated without recal
? getSalesTax(this.lineItems) // calculate with recal (if on order)
: getSalesTax(this.lineItemsWithoutRecal); // calculated without recal
},
amountDue() {
if (!this.lineItems || this.lineItems.length < 1) return;
if (this.showAsPaid) return 0;
return this.isInsurance || !this.shouldHideRecalibration
? baseMixin.methods.getAmountDue(this.lineItems) // calculate with recal (if on order)
: baseMixin.methods.getAmountDue(this.lineItemsWithoutRecal); // calculated without recal
? getAmountDue(this.lineItems) // calculate with recal (if on order)
: getAmountDue(this.lineItemsWithoutRecal); // calculated without recal
},
amountPaid() {
if (!this.showAsPaid) {

View file

@ -56,7 +56,7 @@ export default {
},
unmounted() {
// remove any modal effects before leaving page (e.g. user hits browser back button)
this.modal.closeModal();
this.modal?.closeModal();
},
computed: {
buttonText() {

View file

@ -0,0 +1,93 @@
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
export function getDisplayAmountDue(lineItemsObject, includeTax = true) {
return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", {
style: "currency",
currency: "USD",
});
}
export function getAmountDue(lineItemsObject, includeTax = true) {
let amountDue = 0;
const order = baseMixin?.methods?.hasSubmittedOrder()
? baseMixin?.methods?.getSubmittedOrder()
: store.getters.order;
if (lineItemsObject?.glassParts) {
amountDue += baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts(
lineItemsObject.glassParts,
includeTax
);
}
if (lineItemsObject?.supportingItems) {
amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(
lineItemsObject.supportingItems,
includeTax
);
}
if (store.getters.coverageIsVerified && !order.policy.isNoComp && !order.policy.isItac) {
amountDue = order.policy.currentDeductible;
}
if (lineItemsObject?.vaps) {
amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(
lineItemsObject.vaps,
includeTax
);
}
if (lineItemsObject?.promos) {
amountDue += baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts(
lineItemsObject.promos,
includeTax
);
}
return ((amountDue * 100) / 100).toFixed(2);
}
export function getSubTotal(lineItemsObject) {
// this is amountDue without sales tax
return getAmountDue(lineItemsObject, false);
}
export function getSalesTax(lineItemsObject) {
// this is amountDue minus subTotal
return (((getAmountDue(lineItemsObject) - getSubTotal(lineItemsObject)) * 100) / 100).toFixed(
2
);
}
export async function getPriceUpchargeByDayPart(pageNameToLog) {
// Get the Pricing By Day Part
const basePriceByDayPart = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_PRICING_BY_DAY_UPCHARGE_PART,
null,
pageNameToLog,
false
);
if (
basePriceByDayPart?.data === null ||
basePriceByDayPart?.data === undefined ||
basePriceByDayPart?.data === ""
) {
return null;
}
// Get the Pricing By Day Base Part price
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{
availableLineItems: [basePriceByDayPart?.data],
},
pageNameToLog,
false
);
return pricingResults[0];
}

View file

@ -0,0 +1,64 @@
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 18"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB18",
partType: "FRONT WIPER",
salesTax: 2.0,
sellingPrice: 30.0,
},
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 26"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB26",
partType: "FRONT WIPER",
salesTax: 1.0,
sellingPrice: 20.0,
},
],
promos: [],
};
describe("pricing-helper", () => {
describe("getDisplayAmountDue", () => {
it("should return the correct display amount due", () => {
const result = getDisplayAmountDue(lineItems);
expect(result).toBe("53.00");
});
});
describe("getAmountDue", () => {
it("should return the correct amount due", () => {
const result = getAmountDue(lineItems, true);
expect(result).toBe("53.00");
});
});
describe("getSubTotal", () => {
it("should return the correct subtotal", () => {
const result = getSubTotal(lineItems);
expect(result).toBe("50.00");
});
});
describe("getSalesTax", () => {
it("should return the correct sales tax", () => {
const result = getSalesTax(lineItems);
expect(result).toBe("3.00");
});
});
});

View file

@ -35,10 +35,9 @@ export function containsRecalParts(lineItems) {
}
}
export function getItemsWithoutRecalParts(lineItems) {
const copy = deepClone(lineItems);
const firstLevelFiltered = copy.filter((li) => !isRecalPart(li));
export function getItemsWithoutRecalParts(lineItemsArray) {
if (!lineItemsArray || !Array.isArray(lineItemsArray)) return null;
const firstLevelFiltered = deepClone(lineItemsArray).filter((li) => !isRecalPart(li));
const childrenFiltered = firstLevelFiltered.map((li) => {
if (li.childParts && li.childParts.length > 0) {

View file

@ -1,6 +1,12 @@
import { partTypeStrings } from "@/constants/part-type-strings";
import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected";
import { packageNames } from "@/constants/package-names";
import baseMixin from "@/mixins/base-mixin.js";
import { getItemsWithoutRecalParts } from "@/helpers/recal-helper";
import {
getPromosThatMatchLineItemsOnOrder,
removeVapsPromosFromPromoArray,
} from "@/helpers/promotions-helper";
export function containsLineItemWithPartType(typeToFind, itemsToSearch) {
const partTypeMatches = findLineItemsWithPartType(typeToFind, itemsToSearch);
@ -239,7 +245,7 @@ export function getPackageNameByType(packageType) {
return package_names[packageType] || null;
}
export function getDiscountPackageName(discountPackage) {
export function getDiscountedPackageName(discountPackage) {
const package_names = {
ECON: packageNames.TIER_ONE,
STANDARD: packageNames.TIER_TWO,

View file

@ -48,14 +48,15 @@
v-model="isSmsOptIn" />
<techNotes v-model="techNotes" :textAreaLabelCopy="textAreaLabelCopy" />
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
</div>
</div>
</div>

View file

@ -16,6 +16,10 @@ jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({
submitWorkOrder: jest.fn(),
}));
jest.mock("@/helpers/pricing-helper.js", () => ({
getAmountDue: jest.fn(),
}));
let piaDisabledFlag = false;
describe("payment-method.vue", () => {

View file

@ -158,6 +158,12 @@ import { coverageStatus } from "@/constants/insurance";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper";
import { getBoolFromString } from "@/helpers/boolean-helper";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
@ -767,7 +773,7 @@ export default {
);
},
totalAmountDue() {
return baseMixin.methods.getAmountDue(this.lineItems);
return getAmountDue(this.lineItems);
},
isPiaEnabled() {
const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE);

View file

@ -10,11 +10,17 @@ import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form } from "vee-validate";
import { paymentMethods } from "@/constants/payment-method-constants";
import { routerParams } from "@/router/router-constants/router-params";
import baseMixin from "@/mixins/base-mixin.js";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
// iframeResizer IS loaded into the page and necessary for the package to
// to auto scale the iFrame this page is loaded in
// Do not remove despite showing as "unused" CASH-309
@ -163,7 +169,7 @@ export default {
}
},
getAmountDue() {
return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
return getAmountDue(store.getters.order.lineItems);
},
async saveAndSubmitWorkOrder() {
// Final work order submit after returning from PIA.

View file

@ -5,10 +5,14 @@ import payment from "@/layouts/payment/payment";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions";
import { paymentMethods } from "@/constants/payment-method-constants";
jest.mock("@/helpers/pricing-helper.js", () => ({
getAmountDue: jest.fn(),
getDisplayAmountDue: jest.fn(),
}));
// Constants
const parts = {
windshield: {

View file

@ -234,6 +234,12 @@ import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.
import { routerParams } from "@/router/router-constants/router-params";
import { coverageStatus } from "@/constants/insurance";
import { getBoolFromString } from "@/helpers/boolean-helper.js";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
export default {
name: "payment",
@ -656,10 +662,10 @@ export default {
this.piaLineItems = lineItems.join("||");
},
getAmountDue() {
return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
return getAmountDue(store.getters.order.lineItems);
},
getDisplayAmountDue() {
return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
return getDisplayAmountDue(store.getters.order.lineItems);
},
async paymentFailedPayLater() {
this.$refs.loadingModal.isModalVisible = true;

View file

@ -266,6 +266,7 @@ export default {
false
);
// This will remove any servicePackageDiscount item from lineItems.supportingItems
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SUPPORTING_ITEMS,
resultMap.supportingItems,
@ -484,7 +485,7 @@ export default {
};
},
mounted() {
this.attachCustomEvents();
this.attachCustomEventsForAnalytics();
},
computed: {
lineItemsCloneForWatcher() {
@ -670,7 +671,7 @@ export default {
);
}
},
attachCustomEvents() {
attachCustomEventsForAnalytics() {
this.prependActionToMethod(this, this.forwardButtonAction, () => {
if (!this.isInsuranceSelected && this.lineItems.vaps?.length == 0) {
const tierOnePrice =

View file

@ -30,7 +30,7 @@ import {
containsLineItemWithPartType,
findLineItemsWithPartType,
getPackageNameByType,
getDiscountPackageName,
getDiscountedPackageName,
} from "@/helpers/service-package-helper";
import {
getPromosThatMatchLineItemsOnOrder,
@ -166,7 +166,7 @@ export default {
const discountServicePackage = experimentMixin.methods.getSettingValue(
experimentSettings.PROMO_ON_PACKAGE
);
return getDiscountPackageName(discountServicePackage);
return getDiscountedPackageName(discountServicePackage);
},
frontWipersApplicableForTierTwo() {
return shouldFrontWipersBeAvailable(

View file

@ -130,6 +130,10 @@ jest.mock("@/mixins/base-mixin.js", () => ({
]);
}
}),
filterOutCertainPartTypesOrNumbers: jest.fn(),
hasSubmittedOrder: jest.fn(),
getTotalPriceOfAllLineItemsAndChildParts: jest.fn(),
getTotalLineItemPrice: jest.fn(),
},
}));
@ -631,34 +635,6 @@ describe("schedule.vue...", () => {
expect.anything()
);
});
test("if no EARLY BIRD supporting item, then updateSupportingItems should NOT call store action", async () => {
// Arrange
store.getters.order.serviceLocation.appointmentType = "Mobile";
store.getters.lineItems.supportingItems = [];
const { wrapper } = setupMocks({});
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
wrapper.vm.mobilePremiumAppointmentFee = 14.99;
wrapper.setData({
selectedTimeSlot: {
date: "2019-01-01",
startTime: "09:00",
endTime: "10:00",
routeCode: null,
isPremiumAppointment: false,
},
});
// Act
await wrapper.vm.updateSupportingItems();
// Assert
expect(wrapper.vm.dispatchStoreAction).not.toBeCalled();
});
});
const mockCmsContent = {};

View file

@ -19,7 +19,6 @@
</template>
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
<datePickerForPricingByDay
v-if="isPricingByDayExperiment"
customComponentId="dateQuestion"
selectableDatesSetting="custom"
ref="datePicker"
@ -27,17 +26,11 @@
class="text-link-small"
:customSelectableDatesCallback="getAvailableDatesMethod"
validationRules="date-required"
@date-clicked="openInshopTimeSlotsModal" />
<datePicker
v-else
customComponentId="dateQuestion"
selectableDatesSetting="custom"
ref="datePicker"
v-model="selectedDate"
class="text-link-small"
:customSelectableDatesCallback="getAvailableDatesMethod"
validationRules="date-required"
@date-clicked="openInshopTimeSlotsModal" />
:baseDayPrice="baseDayPrice"
:pricingByDayUpcharge="pricingByDayUpcharge"
:showPricingByDay="showPricingByDay"
:isPricingByDayExperiment="isPricingByDayExperiment"
@date-clicked="handleDateClicked" />
<timeSlotModalQuestion
ref="timeSlotModalQuestion"
customComponentId="timeSlotModalQuestion"
@ -84,7 +77,6 @@ import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form, defineRule } from "vee-validate";
import datePicker from "@/digital-components/date-picker/date-picker";
import datePickerForPricingByDay from "@/experiment-components/date-picker-for-pricing-by-day";
import locationAlerts from "@/layouts/schedule/location-alerts/location-alerts";
import timeSlotModalQuestion from "./time-slot-modal-question/time-slot-modal-question";
@ -103,12 +95,20 @@ import {
convertDateStringToDate,
sumDateString,
} from "@/layouts/schedule/helpers/schedule-helper";
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
import {
AppointmentTypeStrings,
PREMIUM_FEE_PART_TYPE,
PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE,
} from "@/constants/schedule-constants";
import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
import store from "@/store";
import experimentMixin from "@/mixins/experiment-mixin.js";
import { experimentSettings } from "@/constants/experiments";
import { getAmountDue, getPriceUpchargeByDayPart } from "@/helpers/pricing-helper.js";
import { getItemsWithoutRecalParts } from "@/helpers/recal-helper";
import { partNumberStrings } from "@/constants/part-number-strings";
import { deepClone } from "@/helpers/object-helper";
// DEFINE VALIDATION RULES
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
@ -223,32 +223,92 @@ export default {
mobilePremiumAppointmentFee: null,
waitListRequested: null,
displayWaitList: null,
pricingByDayUpchargeLineItem: null,
includePricingByDaySurcharge: null,
isPricingByDayExperiment: null,
baseDayPrice: null,
pricingByDayUpcharge: null,
showPricingByDay: null,
};
},
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
// Get data needed for Pricing By Day
const lineItems = deepClone(await store.getters.order.lineItems);
const isRecalibrationOnOrder = await store.getters.isRecalibrationOnOrder;
const shouldHideRecalibration =
experimentMixin.methods
.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)
?.toLowerCase() === "true" && isRecalibrationOnOrder;
const glassParts =
isRecalibrationOnOrder && shouldHideRecalibration
? getItemsWithoutRecalParts(lineItems.glassParts)
: (lineItems.glassParts ?? []);
const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers(
lineItems.supportingItems,
{ partNumbersToRemove: [partNumberStrings.RECYCLE_FEE] }
);
const lineItemsToBePriced = {
glassParts: glassParts,
supportingItems: supportingItemsWithoutFees,
vaps: lineItems.vaps ?? [],
promos: lineItems.promos ?? [],
};
const isPricingByDayExperiment =
(await experimentMixin.methods
.getSettingValue(experimentSettings.PRICING_BY_DAY)
?.toLowerCase()) === "true";
const priceString = await getAmountDue(lineItemsToBePriced, false); // pass the IncludeTax param as false
const priceStringIntegerRoundedDown = priceString?.split(".")[0]; // same method used as getDisplayPrice() in service-package-radio used on /quote
const baseDayPrice = parseInt(priceStringIntegerRoundedDown);
const showPricingByDay = !store.getters.payment.isInsurance && isPricingByDayExperiment;
const pricingByDayUpchargeLineItem = await getPriceUpchargeByDayPart();
const pricingByDayUpcharge = await baseMixin.methods.getTotalLineItemPrice(
pricingByDayUpchargeLineItem,
false
);
// Check to see if includePricingByDaySurcharge should already be set (based on order lineItems)
let includePricingByDaySurcharge = false;
if (supportingItemsWithoutFees) {
const pricingByDayUpchargeFeeIndex = supportingItemsWithoutFees.findIndex(
(item) => item.partType == PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE
);
if (pricingByDayUpchargeFeeIndex > -1) {
includePricingByDaySurcharge = true;
}
}
// Load page with date already selected?
let preSelectedDate = await store.getters.order.schedule.date;
if (!preSelectedDate || preSelectedDate.startTime === null) {
preSelectedDate = null;
}
const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
// setup config options for date-picker
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: getAvailableDates,
preSelectedDate: preSelectedDate,
});
// TODO - ONCE A DATEPICKER VERSION IS FINALIZED,
// MAKE SURE THE ABOVE METHOD loadInitialData POINTS TO THE RIGHT FILE
// Set up promises
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
store.getters.order.serviceLocation.zipCodeCtu,
store.getters.order.serviceLocation.provider?.address?.zipCodeCtu
);
// While Pricing By Day Experiment is ongoing, using the updated datePicker
const datePickerInitialDataPromise =
await datePickerForPricingByDay.methods.loadInitialData({
// setup config options for date-picker
selectableDatesSetting: "custom",
initialViewRowsToShow: 5,
customSelectableDatesCallback: getAvailableDates,
preSelectedDate: preSelectedDate,
baseDayPrice: baseDayPrice,
pricingByDayUpcharge: pricingByDayUpcharge,
});
const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_MOBILE_PREMIUM_FEE,
null,
"schedule"
);
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
if (result.data) {
return baseMixin.methods.dispatchStoreActionWithLogging(
@ -264,11 +324,6 @@ export default {
}
});
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
store.getters.order.serviceLocation.zipCodeCtu,
store.getters.order.serviceLocation.provider?.address?.zipCodeCtu
);
// Settle promises and get results
const promiseResultMap = [
{
@ -302,6 +357,12 @@ export default {
: null;
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
vm.setDisplayWaitList();
vm.pricingByDayUpchargeLineItem = pricingByDayUpchargeLineItem;
vm.includePricingByDaySurcharge = includePricingByDaySurcharge;
vm.isPricingByDayExperiment = isPricingByDayExperiment;
vm.baseDayPrice = baseDayPrice;
vm.pricingByDayUpcharge = pricingByDayUpcharge;
vm.showPricingByDay = showPricingByDay;
});
},
computed: {
@ -324,13 +385,6 @@ export default {
(selectableDate) => selectableDate.date === this.selectedDate
);
},
isPricingByDayExperiment() {
return (
experimentMixin.methods
.getSettingValue(experimentSettings.PRICING_BY_DAY)
?.toLowerCase() === "true"
);
},
},
methods: {
splitCopyOnCMSPlaceHolder,
@ -377,6 +431,7 @@ export default {
this.appointmentType,
this.$store.getters.order.serviceLocation.provider.providerNumber
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days = this.selectableDatesData.days.concat(
newShopTimeSlots.days
@ -512,11 +567,13 @@ export default {
false
);
this.dispatchStoreAction(
this.storeActions.SAVE_WAITLIST_REQUESTED,
this.waitListRequested,
false
);
if (this.waitListRequested !== null && this.waitListRequested !== undefined) {
this.dispatchStoreAction(
this.storeActions.SAVE_WAITLIST_REQUESTED,
this.waitListRequested,
false
);
}
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
@ -553,6 +610,28 @@ export default {
updateSupportingItems() {
const supportingItems = this.getSupportingItems();
// if we have a pricing by day upcharge, then save/update supporting items with it
const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex(
(item) => item.partType == PRICING_BY_DAY_UPCHARGE_FEE_PART_TYPE
);
if (this.includePricingByDaySurcharge) {
if (pricingByDayUpchargeFeeIndex > -1) {
supportingItems[pricingByDayUpchargeFeeIndex].laborAmount =
this.pricingByDayUpchargeLineItem.laborAmount;
supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice =
this.pricingByDayUpchargeLineItem.sellingPrice;
supportingItems[pricingByDayUpchargeFeeIndex].kitPrice =
this.pricingByDayUpchargeLineItem.kitPrice;
} else {
supportingItems.push(this.pricingByDayUpchargeLineItem);
}
} else {
if (pricingByDayUpchargeFeeIndex >= 0) {
supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
}
}
// if we have a premium fee(early bird), then save/update supporting items
if (
this.appointmentType === AppointmentTypeStrings.MOBILE &&
@ -562,7 +641,7 @@ export default {
(item) => item.partType == PREMIUM_FEE_PART_TYPE
);
if (premiumFeeIndex >= 0) {
if (premiumFeeIndex > -1) {
supportingItems[premiumFeeIndex].laborAmount =
this.mobilePremiumAppointmentFee.laborAmount;
supportingItems[premiumFeeIndex].sellingPrice =
@ -572,12 +651,6 @@ export default {
} else {
supportingItems.push(this.mobilePremiumAppointmentFee);
}
this.dispatchStoreAction(
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
supportingItems,
false
);
} else {
if (!supportingItems) {
return;
@ -590,17 +663,28 @@ export default {
if (removePremiumFeeIndex >= 0) {
supportingItems.splice(removePremiumFeeIndex, 1);
this.dispatchStoreAction(
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
supportingItems,
false
);
}
}
this.dispatchStoreAction(
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
supportingItems,
false
);
},
handleWaitListRequested(value) {
this.waitListRequested = value;
},
handleDateClicked(date) {
// do something to mark this as upcharge day or not...
if (date.isPricingByDayUpchargeDay) {
this.includePricingByDaySurcharge = true;
} else {
this.includePricingByDaySurcharge = false;
}
this.openInshopTimeSlotsModal();
},
},
watch: {
selectedDate(newValue, oldValue) {
@ -629,7 +713,6 @@ export default {
funnelSubHeader,
Form,
loadingModal,
datePicker,
datePickerForPricingByDay,
locationAlerts,
timeSlotModalQuestion,

View file

@ -39,6 +39,13 @@
v-model="waitListRequested"
@click="waitListChecked" />
</div>
<div
v-if="waitListRequested"
class="mt-5 rounded waitlist-success"
ref="waitlistSuccessMessage">
<img :src="WaitListSuccessImage" class="success-image" />
<span v-html="WaitListSuccessText" class="success-text"></span>
</div>
<div
class="mt-5 mb-2 supplemental-information"
v-if="supplementalInformationBlock"
@ -359,6 +366,12 @@ export default {
displayWaitListFeature() {
return this.displayWaitList;
},
WaitListSuccessImage() {
return this.getCmsContent("WaitListSuccessWidget", "Image");
},
WaitListSuccessText() {
return this.getCmsContent("WaitListSuccessWidget", "BodyText");
},
},
methods: {
openModal() {
@ -528,11 +541,24 @@ export default {
waitListChecked(event) {
this.$emit("waitListRequested-emitted", event.target.checked);
},
scrollToSuccessMessage() {
const successMessageElement = this.$refs.waitlistSuccessMessage;
if (successMessageElement) {
successMessageElement.scrollIntoView({ behavior: "smooth" });
}
},
},
unmounted() {
if (this.isModalOpened) this.closeModal();
},
watch: {
waitListRequested(newVal) {
if (newVal) {
this.$nextTick(() => {
this.scrollToSuccessMessage();
});
}
},
modelValue: {
handler(newValue) {
this.handleChange(newValue);
@ -583,10 +609,37 @@ export default {
margin-bottom: -8px;
.ui-checkbox {
padding-left: 0px;
input {
border-radius: 4px;
box-shadow: 0px 1px 4px 0px #00000033;
}
}
.form-check-input {
margin-left: 0px;
}
}
.waitlist-success {
background-color: #ecf5e9;
display: flex;
align-items: flex-start;
border: 1px solid #0c7e47;
border-radius: 5px;
font-size: $h5-font-size;
padding: 12px 16px;
margin-bottom: -8px;
.success-text {
margin-left: 8px;
strong {
font-weight: 600;
letter-spacing: 0%;
}
}
.success-image {
margin-top: 4.5px;
width: 16px;
height: 16px;
color: #0c7e47;
}
}
}
</style>

View file

@ -110,6 +110,25 @@ export default {
});
return filteredLineItems;
},
filterOutCertainPartTypesOrNumbers(
lineItemsArray,
{ partTypesToRemove = [], partNumbersToRemove = [] }
) {
// lineItems s/b an ARRAY here
if (!Array.isArray(lineItemsArray)) return;
partTypesToRemove.forEach((partType) => {
lineItemsArray = lineItemsArray.filter((item) => {
return !item?.partType?.includes(partType);
});
});
partNumbersToRemove.forEach((partNumber) => {
lineItemsArray = lineItemsArray.filter((item) => {
return !item?.partNumber?.includes(partNumber);
});
});
return lineItemsArray;
},
filterOutServicePackageDiscountPart(lineItems) {
const filteredLineItems = lineItems?.filter((item) => {
return !item?.partType?.includes(partTypeStrings.SERVICE_PACKAGE_DISCOUNT);
@ -133,75 +152,18 @@ export default {
return totalPrice;
},
getTotalLineItemPrice(lineItem, includeTax) {
const kitPrice = lineItem.kitPrice ?? 0;
const laborAmount = lineItem.laborAmount ?? 0;
const sellingPrice = lineItem.sellingPrice ?? 0;
const salesTax = lineItem.salesTax ?? 0;
if (includeTax) {
return (
lineItem.kitPrice +
lineItem.laborAmount +
lineItem.sellingPrice +
lineItem.salesTax
);
return kitPrice + laborAmount + sellingPrice + salesTax;
} else {
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
return kitPrice + laborAmount + sellingPrice;
}
},
getDisplayAmountDue(lineItems) {
return this.getAmountDue(lineItems).toLocaleString("en-US", {
style: "currency",
currency: "USD",
});
},
getAmountDue(lineItems, includeTax = true) {
var amountDue = 0;
if (lineItems?.glassParts) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.glassParts,
includeTax
);
}
if (lineItems?.supportingItems) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.supportingItems,
includeTax
);
}
const order = this.hasSubmittedOrder() ? this.getSubmittedOrder() : store.getters.order;
if (
store.getters.coverageIsVerified &&
!order.policy.isNoComp &&
!order.policy.isItac
) {
amountDue = order.policy.currentDeductible;
}
if (lineItems?.vaps) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.vaps,
includeTax
);
}
if (lineItems?.promos) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.promos,
includeTax
);
}
return ((amountDue * 100) / 100).toFixed(2);
},
getSubTotal(lineItems) {
// this is amountDue without sales tax
return this.getAmountDue(lineItems, false);
},
getSalesTax(lineItems) {
// this is amountDue minus subTotal
return (
((this.getAmountDue(lineItems) - this.getSubTotal(lineItems)) * 100) /
100
).toFixed(2);
},
scrollToPageTop() {
const container = document.getElementsByClassName("page-container-grouped-styles")[0];
container.scrollTo({ top: 0, left: 0, behavior: "smooth" });

View file

@ -144,180 +144,6 @@ describe("baseMixin.js", () => {
expect(mixIn.methods.dispatchStoreActionWithLogging).toBeCalled();
});
test("getAmountDue for verified deductible should be deductible plus any lineitems", () => {
const mixIn = getMixInInstance({});
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 18"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB18",
partType: "FRONT WIPER",
salesTax: 2.0,
sellingPrice: 30.0,
},
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 26"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB26",
partType: "FRONT WIPER",
salesTax: 1.0,
sellingPrice: 20.0,
},
],
promos: [],
};
const payment = { insuranceCoverage: { isVerified: false } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
store.getters = {
payment: payment,
policy: policy,
order: {
payment: payment,
policy: policy,
},
hasSubmittedOrder: false,
};
var amtDue = mixIn.methods.getAmountDue(lineItems);
expect(amtDue).toEqual("53.00");
});
test("getAmountDue should function with a submitted order", () => {
const mixIn = getMixInInstance({});
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 18"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB18",
partType: "FRONT WIPER",
salesTax: 2.0,
sellingPrice: 30.0,
},
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 26"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB26",
partType: "FRONT WIPER",
salesTax: 1.0,
sellingPrice: 20.0,
},
],
promos: [],
};
const payment = { insuranceCoverage: { isVerified: false } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(true);
mixIn.methods.getSubmittedOrder = jest.fn().mockReturnValue({
payment: payment,
policy: policy,
});
store.getters = {
payment: {},
policy: {},
order: {},
};
var amtDue = mixIn.methods.getAmountDue(lineItems);
expect(amtDue).toEqual("53.00");
});
test("getAmountDue for cash should sum lineitems", () => {
const mixIn = getMixInInstance({});
const lineItems = {
glassParts: [
{
partNumber: "FW06143GTYN",
description:
"solar, heads-up display, third visor frit, soundproofing, rain/light sensor, lane departure warning system",
color: "Green Tint",
partType: "WINDSHIELD",
canSafeliteRecalibrate: true,
requiresRecalibration: true,
requiresCapabilityQuestions: false,
recalibrationType: "STATIC",
childParts: [
{
partNumber: "RS 101 PAD",
kitPrice: 0,
laborAmount: 0,
salesTax: 5.0,
sellingPrice: 5.0,
},
],
id: "73e80976-79be-455a-b4e7-f9b058b61749",
kitPrice: 0,
laborAmount: 60,
salesTax: 20.0,
sellingPrice: 1000.0,
},
],
supportingItems: [],
vaps: [
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 18"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB18",
partType: "FRONT WIPER",
salesTax: 2.0,
sellingPrice: 30.0,
},
{
cartItemType: "FRONT WIPERS",
description: 'WIPER BLADE STANDARD 26"',
kitPrice: 0,
laborAmount: 0,
partNumber: "WB26",
partType: "FRONT WIPER",
salesTax: 1.0,
sellingPrice: 20.0,
},
],
promos: [],
};
const payment = { isInsurance: null, insuranceCoverage: { isVerified: null } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 0 };
mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(false);
store.getters = {
payment: payment,
policy: policy,
order: {
payment: payment,
policy: policy,
},
hasSubmittedOrder: false,
};
var amtDue = mixIn.methods.getAmountDue(lineItems);
expect(amtDue).toEqual("1143.00");
});
});
function getMixInInstance({ isDispatchSuccess = true }) {

View file

@ -1749,6 +1749,18 @@ export const actions = {
pageNameToLog: pageNameToLog,
});
},
async getPricingByDayUpchargePart(context, { pageNameToLog }) {
return await globalMethods
.callHttpClient({
method: endpoints.GetPricingByDayUpchargePart.method,
endpoint: endpoints.GetPricingByDayUpchargePart.url,
logApiCall: true,
pageNameToLog: pageNameToLog,
})
.then((response) => {
return response;
});
},
getServicePackageDiscountPart(context, { pageNameToLog }) {
const damage = context.getters.damage;
const damageType = damage.isRepair ? "Repair" : "Replace";