Merge branch 'develop' into feature/Digital/CSR-1629

This commit is contained in:
hiteshkumar87 2024-09-20 15:06:56 +05:30 committed by GitHub
commit e162a5fd0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 826 additions and 312 deletions

View file

@ -143,6 +143,7 @@ body .buttonContainer button {
@media (hover: hover) { @media (hover: hover) {
body .buttonContainer button { body .buttonContainer button {
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%); background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
cursor: pointer;
} }
} }
body .buttonContainer button:focus { body .buttonContainer button:focus {

View file

@ -151,6 +151,7 @@ body {
height: 48px; height: 48px;
@media (hover: hover) { @media (hover: hover) {
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%); background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
cursor: pointer;
} }
&:focus { &:focus {
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c; box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;

View file

@ -24,6 +24,11 @@ const applicationConfig = {
"//" + "//" +
location.host + location.host +
"/fmg/?fmgPage=payment-method&src=concept-funnel", "/fmg/?fmgPage=payment-method&src=concept-funnel",
PIA_ERROR_URL:
location.protocol +
"//" +
location.host +
"/fmg/?fmgPage=payment-pia-return&src=concept-funnel",
CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/?fmgPage=confirmation", CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/?fmgPage=confirmation",
GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE", GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE",
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60", YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",

View file

@ -130,7 +130,7 @@ const endpoints = {
}, },
PriceOrderItems: { PriceOrderItems: {
url: "/price/api/v1/price/order-items", url: "/price/api/v1/price/order-items",
method: "GET", method: "POST",
}, },
ValidatePromo: { ValidatePromo: {
url: "/price/api/v1/price/order-promo", url: "/price/api/v1/price/order-promo",
@ -172,6 +172,10 @@ const endpoints = {
url: "/account/api/v1/account/insurance-companies", url: "/account/api/v1/account/insurance-companies",
method: "GET", method: "GET",
}, },
GetBillToAccountNumber: {
url: "/account/api/v1/account/bill-to-account-number",
method: "GET",
},
}; };
export { endpoints }; export { endpoints };

View file

@ -1,5 +1,6 @@
const experimentUniverses = { const experimentUniverses = {
CONCEPT_FUNNEL: "ConceptFunnel", CONCEPT_FUNNEL: "ConceptFunnel",
RECAL_PRICE_REMOVAL: "NextGen_RecalPriceRemoval",
}; };
const experimentSettings = { const experimentSettings = {
@ -11,6 +12,7 @@ const experimentSettings = {
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA", SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
IS_EMAIL_OPTIONAL: "isEmailOptional", IS_EMAIL_OPTIONAL: "isEmailOptional",
SERVICE_PACKAGE_DISCOUNT: "OfferServicePackageDiscount", SERVICE_PACKAGE_DISCOUNT: "OfferServicePackageDiscount",
RECAL_PRICE_REMOVE: "RecalPriceRemove",
}; };
const experimentTriggers = { const experimentTriggers = {

View file

@ -1,3 +1,8 @@
export const headerKeys = { export const headerKeys = {
EXPERIMENT: "X-Experiment-Data", EXPERIMENT: "X-Experiment-Data",
APPLICATION_NAME: "X-Application-Name",
REFERRAL_SEQUENCE_NUMBER: "X-Referral-Sequence-Number",
SESSION_SEQUENCE_NUMBER: "X-Session-Sequence-Number",
TRANSACTION_ID: "X-Transaction-Id",
EON: "X-EON",
}; };

View file

@ -36,6 +36,7 @@ const queryStrings = {
VIN_SELECTION: "vinselection", VIN_SELECTION: "vinselection",
SERVICE_PACKAGE: "servicepackage", SERVICE_PACKAGE: "servicepackage",
NUMBER_OF_CHIPS: "numberofchips", NUMBER_OF_CHIPS: "numberofchips",
LOG: "log",
UTM_SOURCE: "_source", UTM_SOURCE: "_source",
UTM_MEDIUM: "_medium", UTM_MEDIUM: "_medium",
UTM_CAMPAIGN: "_campaign", UTM_CAMPAIGN: "_campaign",
@ -43,6 +44,7 @@ const queryStrings = {
CJUNCTION: "cjunction", CJUNCTION: "cjunction",
ORGANIC: "organic", ORGANIC: "organic",
ORGANIC_SOCIAL: "organic_social", ORGANIC_SOCIAL: "organic_social",
EXPERIMENTS: "experiments",
}; };
export { queryStrings }; export { queryStrings };

View file

@ -53,6 +53,7 @@ const storeActions = {
VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA: "validateOrderPromoAndSaveServerData", VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA: "validateOrderPromoAndSaveServerData",
REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA: "revalidateOrderPromosAndSaveServerData", REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA: "revalidateOrderPromosAndSaveServerData",
GET_INSURANCE_COMPANY_LIST: "getInsuranceCompanyList", GET_INSURANCE_COMPANY_LIST: "getInsuranceCompanyList",
GET_BILL_TO_ACCOUNT_NUMBER: "getBillToAccountNumber",
// DEPENDENCY MUTATIONS // DEPENDENCY MUTATIONS
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies", RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",

View file

@ -103,6 +103,7 @@ const storeMutations = {
UPDATE_EXTERNAL_PARAMETER_IS_INSURANCE: "updateExternalParameterIsInsurance", UPDATE_EXTERNAL_PARAMETER_IS_INSURANCE: "updateExternalParameterIsInsurance",
UPDATE_EXTERNAL_PARAMETER_VIN_SELECTION: "updateExternalParameterVinSelection", UPDATE_EXTERNAL_PARAMETER_VIN_SELECTION: "updateExternalParameterVinSelection",
UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE: "updateExternalParameterServicePackage", UPDATE_EXTERNAL_PARAMETER_SERVICE_PACKAGE: "updateExternalParameterServicePackage",
UPDATE_EXTERNAL_PARAMETER_SOURCE: "updateExternalParameterSource",
//RESET EXTERNAL_PARAMETER MUTATIONS //RESET EXTERNAL_PARAMETER MUTATIONS
RESET_EXTERNAL_PARAMETER_VEHICLE_STATE: "resetExternalParameterVehicleState", RESET_EXTERNAL_PARAMETER_VEHICLE_STATE: "resetExternalParameterVehicleState",

View file

@ -1123,42 +1123,6 @@ describe("baseInputButton.vue", () => {
}); });
}); });
describe("buttonId", () => {
test("groupName and value combo yield correct id for input button with string value", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
groupName: "my test-name",
value: "Aaa-BBB CcC",
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.buttonId).toBe("my-test-name-Aaa-BBB-CcC");
expect(inputElement.attributes().id).toBe("my-test-name-Aaa-BBB-CcC");
});
test("groupName and value combo yield correct id for input button with number value", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
groupName: "my test-name",
value: 2,
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.buttonId).toBe("my-test-name-2");
expect(inputElement.attributes().id).toBe("my-test-name-2");
});
});
describe("inputType", () => { describe("inputType", () => {
test("isMultiSelect is true => inputType is 'checkbox'", () => { test("isMultiSelect is true => inputType is 'checkbox'", () => {
// Arrange/Act // Arrange/Act

View file

@ -34,6 +34,7 @@ import {
handleInputComponentBlur, handleInputComponentBlur,
} from "@/helpers/button-question-focus-helper"; } from "@/helpers/button-question-focus-helper";
import { inputButtonProps } from "@/digital-components/base-input-button/button-functionality-props"; import { inputButtonProps } from "@/digital-components/base-input-button/button-functionality-props";
import { v4 as uuidv4 } from "uuid";
export default { export default {
name: "base-input-button", name: "base-input-button",
@ -45,6 +46,7 @@ export default {
data() { data() {
return { return {
valueToEmit: null, valueToEmit: null,
buttonId: uuidv4(),
}; };
}, },
mounted() { mounted() {
@ -153,11 +155,6 @@ export default {
inputType() { inputType() {
return this.isMultiSelect ? "checkbox" : "radio"; return this.isMultiSelect ? "checkbox" : "radio";
}, },
buttonId() {
return `${this.groupName?.replace(" ", "-")}-${this.value
?.toString()
?.replace(" ", "-")}`;
},
isValueSelectedOnClick() { isValueSelectedOnClick() {
return this.isMultiSelect || this.selectingInitiatesLoad; return this.isMultiSelect || this.selectingInitiatesLoad;
}, },

View file

@ -1,10 +1,9 @@
<template> <template>
<div class="cart"> <div class="cart">
<div class="px-0"> <div class="px-0">
<!-- set class to 'expaned' on line 7 so cart is open on page load. This may be temporary -->
<div <div
class="row vin-toggle flex align-items-center pt-4" class="row vin-toggle flex align-items-center pt-4"
:class="[isExpanded ? '' : 'expanded']" :class="[isExpanded ? 'expanded' : '']"
@click="toggleIsExpanded()"> @click="toggleIsExpanded()">
<a <a
aria-label="expand cart" aria-label="expand cart"
@ -140,7 +139,7 @@
</div> </div>
<div <div
class="my-2 lh-1 applied-promo-tag" class="my-2 lh-1 applied-promo-tag"
v-for="(promoCode, i) in this.getPromoCodeList()" v-for="(promoCode, i) in getPromoCodeList"
:key="i"> :key="i">
<span class="caption" <span class="caption"
>Promo code <span class="promo-code">{{ promoCode }}</span> applied >Promo code <span class="promo-code">{{ promoCode }}</span> applied
@ -184,13 +183,15 @@ export default {
allowItemRemoval: Boolean, allowItemRemoval: Boolean,
recyclingModalCmsWidgetName: String, recyclingModalCmsWidgetName: String,
showAsPaid: Boolean, showAsPaid: Boolean,
isInsurance: Boolean,
insuranceDeductible: Number, insuranceDeductible: Number,
insuranceCompanyName: String, insuranceCompanyName: String,
showInsuranceCoverageAs: String, showInsuranceCoverageAs: String,
shouldHideRecalibration: Boolean,
}, },
data() { data() {
return { return {
isExpanded: false, isExpanded: true, // set default to true so cart is open on page load. This may be temporary.
currencyFormatter: new Intl.NumberFormat("en-US", { currencyFormatter: new Intl.NumberFormat("en-US", {
style: "currency", style: "currency",
currency: "USD", currency: "USD",
@ -283,6 +284,7 @@ export default {
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType (lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
); );
}, },
getPromoCodeList() { getPromoCodeList() {
if (this.$refs["promoModalQuestion"]) { if (this.$refs["promoModalQuestion"]) {
return this.$refs["promoModalQuestion"].getPromoCodeList(); return this.$refs["promoModalQuestion"].getPromoCodeList();
@ -299,6 +301,15 @@ export default {
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
}, },
}, },
lineItemsWithoutRecal() {
if (!this.lineItems || this.lineItems.length < 1) return;
const lineItemsCopy = deepClone(this.lineItems);
lineItemsCopy.supportingItems = lineItemsCopy.supportingItems
? baseMixin.methods.filterOutRecalibration(lineItemsCopy?.supportingItems)
: [];
return lineItemsCopy;
},
showCoverageAsPending() { showCoverageAsPending() {
return this.showInsuranceCoverageAs === coverageStatus.PENDING; return this.showInsuranceCoverageAs === coverageStatus.PENDING;
}, },
@ -427,7 +438,15 @@ export default {
packagePrice() { packagePrice() {
let packagePrice = 0; let packagePrice = 0;
if (!this.showCoverageAsVerified && !this.showCoverageAsPending) { if (!this.isInsurance && this.shouldHideRecalibration) {
// Update packagePrice if Cash only && is part of RemoveRecal experiment
packagePrice = baseMixin.methods.getTierOnePackagePrice(
baseMixin.methods.filterOutFees(
baseMixin.methods.filterOutRecalibration(this.availableLineItems) // Strip out recal before filtering out fees
)
);
} else if (!this.showCoverageAsVerified && !this.showCoverageAsPending) {
// Update packagePrice if is verified insurance OR if Cash && not part of RemoveRecal experiment
packagePrice = baseMixin.methods.getTierOnePackagePrice( packagePrice = baseMixin.methods.getTierOnePackagePrice(
baseMixin.methods.filterOutFees(this.availableLineItems) baseMixin.methods.filterOutFees(this.availableLineItems)
); );
@ -916,8 +935,6 @@ export default {
promoCartItems() { promoCartItems() {
const promoCartItems = []; const promoCartItems = [];
// clone the promos array
const promosClone = deepClone(this.promo);
// get unique promo codes // get unique promo codes
const uniquePromoCodes = [ const uniquePromoCodes = [
...new Set( ...new Set(
@ -983,16 +1000,23 @@ export default {
return this.getCmsContent("SubtotalTextWidget", "Text"); return this.getCmsContent("SubtotalTextWidget", "Text");
}, },
subTotal() { subTotal() {
return baseMixin.methods.getSubTotal(this.lineItems); 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
}, },
salesTax() { salesTax() {
return baseMixin.methods.getSalesTax(this.lineItems); if (!this.lineItems || this.lineItems.length < 1) return;
return this.isInsurance || !this.shouldHideRecalibration
? baseMixin.methods.getSalesTax(this.lineItems) // calculate with recal (if on order)
: baseMixin.methods.getSalesTax(this.lineItemsWithoutRecal); // calculated without recal
}, },
amountDue() { amountDue() {
if (this.showAsPaid) { if (!this.lineItems || this.lineItems.length < 1) return;
return 0; if (this.showAsPaid) return 0;
} return this.isInsurance || !this.shouldHideRecalibration
return baseMixin.methods.getAmountDue(this.lineItems); ? baseMixin.methods.getAmountDue(this.lineItems) // calculate with recal (if on order)
: baseMixin.methods.getAmountDue(this.lineItemsWithoutRecal); // calculated without recal
}, },
amountPaid() { amountPaid() {
if (!this.showAsPaid) { if (!this.showAsPaid) {

View file

@ -1,5 +1,6 @@
import axios from "axios"; import axios from "axios";
import analyticsMixIn from "@/mixins/analytics-mixin.js"; import analyticsMixIn from "@/mixins/analytics-mixin.js";
import baseMixin from "@/mixins/base-mixin.js";
import router from "@/router"; import router from "@/router";
import store from "@/store"; import store from "@/store";
@ -57,9 +58,17 @@ export default {
}) { }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO; const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
const submittedOrder = baseMixin.methods.getSubmittedOrder();
const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.getters.order;
const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: "FixMyGlass" }); const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: "FixMyGlass" });
const headers = { const headers = {
[headerKeys.EXPERIMENT]: JSON.stringify(store.getters.experimentSettings), [headerKeys.EXPERIMENT]: JSON.stringify(store.getters.experimentSettings),
[headerKeys.APPLICATION_NAME]: applicationConfig.APPLICATION_NAME,
[headerKeys.SESSION_SEQUENCE_NUMBER]: store.getters.applicationUser?.savedSessionId,
[headerKeys.REFERRAL_SEQUENCE_NUMBER]: order?.referralSequenceNumber,
[headerKeys.TRANSACTION_ID]: crypto.randomUUID(),
[headerKeys.EON]: order?.eon,
}; };
axios({ axios({

View file

@ -13,6 +13,7 @@ global.$logger = {
logError: jest.fn(), logError: jest.fn(),
logCritical: jest.fn(), logCritical: jest.fn(),
}; };
global.crypto = { randomUUID: jest.fn() };
it("Global Methods - Call Http Client - Should Resolve Promise", () => { it("Global Methods - Call Http Client - Should Resolve Promise", () => {
//Arrange //Arrange

View file

@ -8,6 +8,10 @@ export function getDateDifferenceInDays(startDate, endDate) {
return Math.round(difference / (1000 * 3600 * 24)); return Math.round(difference / (1000 * 3600 * 24));
} }
export function get12HourTimeFormat(time) { export function get12HourTimeFormat(time) {
if (!time) {
return null;
}
// Check correct time format and split into components // Check correct time format and split into components
time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time]; time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
@ -20,8 +24,12 @@ export function get12HourTimeFormat(time) {
return time.join(""); // return adjusted time or original string return time.join(""); // return adjusted time or original string
} }
export function get12HourTimeMobileFormat(time) { export function get12HourTimeMobileFormat(time) {
if (!time) {
return null;
}
// Check correct time format and split into components // Check correct time format and split into components
time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time]; time = time?.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
if (time.length > 1) { if (time.length > 1) {
// If time format correct // If time format correct

View file

@ -107,8 +107,12 @@ export async function getImplicitNavigation(toRoute) {
*/ */
export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLog }) { export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLog }) {
console.log(new Date() + " navigateToHeritageFunnel: " + JSON.stringify(applicationConfig)); const log = getQuerystringParameter(queryStrings.LOG);
console.log(new Date() + " navigateToHeritageFunnel: " + applicationConfig.HERITAGE_FUNNEL); if (eval(log)) {
console.log(new Date() + " applicationConfig: " + JSON.stringify(applicationConfig));
console.log(new Date() + " navigateToHeritageFunnel: " + applicationConfig.HERITAGE_FUNNEL);
}
// Create the order (or save existing order) when navigating to Heritage Funnel. // Create the order (or save existing order) when navigating to Heritage Funnel.
if (shouldSaveSession) { if (shouldSaveSession) {
await saveSession({ pageNameToLog: pageNameToLog, shouldAwaitSaveSessionQueue: true }); await saveSession({ pageNameToLog: pageNameToLog, shouldAwaitSaveSessionQueue: true });

View file

@ -150,7 +150,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -54,7 +54,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -34,6 +34,8 @@ import {
import store from "@/store"; import store from "@/store";
import { serviceType } from "@/constants/service-type"; import { serviceType } from "@/constants/service-type";
import { applicationConfig } from "@/constants/application-config.js"; import { applicationConfig } from "@/constants/application-config.js";
import baseMixin from "@/mixins/base-mixin.js";
export default { export default {
name: "add-to-calendar", name: "add-to-calendar",
components: { components: {
@ -110,10 +112,10 @@ export default {
return this.getCmsContent(this.sameDayDropOffWidgetName, "BodyText"); return this.getCmsContent(this.sameDayDropOffWidgetName, "BodyText");
}, },
UniqueId() { UniqueId() {
return store.getters.submittedOrder.referralNumber?.toString(); return this.getSubmittedOrder()?.referralNumber?.toString();
}, },
ServiceType() { ServiceType() {
const isRepair = store.getters.submittedOrder.damage.isRepair; const isRepair = this.getSubmittedOrder()?.damage.isRepair;
const funnelHasRecalibrationPart = store.getters.isRecalibrationOnSubmittedOrder; const funnelHasRecalibrationPart = store.getters.isRecalibrationOnSubmittedOrder;
if (!isRepair) { if (!isRepair) {
if (funnelHasRecalibrationPart) { if (funnelHasRecalibrationPart) {
@ -125,7 +127,7 @@ export default {
return serviceType.REPAIR; return serviceType.REPAIR;
}, },
RouteCode() { RouteCode() {
return store.getters.submittedOrder.schedule.routeCode; return this.getSubmittedOrder()?.schedule.routeCode;
}, },
Appointment() { Appointment() {
var subject = ""; var subject = "";
@ -206,6 +208,9 @@ export default {
}, },
methods: { methods: {
getSubmittedOrder() {
return baseMixin.methods.getSubmittedOrder();
},
setCalendarAppointment(calendarOption) { setCalendarAppointment(calendarOption) {
if ( if (
calendarOption.name == calendarOptions.ICAL || calendarOption.name == calendarOptions.ICAL ||

View file

@ -19,67 +19,6 @@ beforeEach(() => {
jest.restoreAllMocks(); jest.restoreAllMocks();
jest.clearAllMocks(); jest.clearAllMocks();
store.getters = { store.getters = {
submittedOrder: {
schedule: {
date: "2023-01-01",
startTime: "09:00",
endTime: "10:00",
routeCode: "000",
jobMaxMinutes: "120",
jobMinMinutes: "60",
},
lineItems: {
glassParts: [
{
partNumber: "ABC123",
},
],
supportingItems: [],
},
serviceLocation: {
address: "test",
address2: "123",
city: "test",
state: "AZ",
appointmentType: "Inshop",
zipCode: "12345",
zipCodeCtu: "01234",
provider: {
providerNumber: "123",
address: {
streetAddress: "test1",
city: "test",
state: "AZ",
zipCode: "12345",
zipCodeCtu: "01234",
},
},
},
damage: {
isRepair: false,
},
payment: {
isPia: true,
isInsurance: false,
insuranceCoverage: {
coverageStatus: "test",
},
},
policy: {
isNoComp: false,
currentDeductible: 1,
insuranceCompanyName: "test name",
},
referralNumber: "1234567",
vehicle: {
year: "2004",
make: "Ford",
model: "F Series F250",
},
customer: {
emailAddress: "test@test.com",
},
},
order: { order: {
schedule: { schedule: {
date: "2023-01-01", date: "2023-01-01",
@ -96,6 +35,7 @@ beforeEach(() => {
supportingItems: [], supportingItems: [],
}, },
serviceLocation: { serviceLocation: {
appointmentType: AppointmentTypeStrings.IN_SHOP,
address: "test", address: "test",
address2: "123", address2: "123",
city: "test", city: "test",
@ -180,6 +120,20 @@ describe("computed properties...", () => {
test("ScheduleDateFormatted should return date in expected format.", () => { test("ScheduleDateFormatted should return date in expected format.", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
startTime: "09:00",
endTime: "11:00",
date: "2023-01-01",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
// Act // Act
const testValue = wrapper.vm.ScheduleDateFormatted; const testValue = wrapper.vm.ScheduleDateFormatted;
@ -189,11 +143,22 @@ describe("computed properties...", () => {
}); });
test("ScheduleTimeFormatted should return mobile time in expected format.", () => { test("ScheduleTimeFormatted should return mobile time in expected format.", () => {
//Arrange //Arrange
store.getters.submittedOrder.serviceLocation.appointmentType =
AppointmentTypeStrings.MOBILE;
store.getters.submittedOrder.schedule.startTime = "09:00";
store.getters.submittedOrder.schedule.endTime = "11:00";
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
startTime: "09:00",
endTime: "11:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
wrapper.vm.submittedOrder.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE;
// Act // Act
const testValue = wrapper.vm.ScheduleTimeFormatted; const testValue = wrapper.vm.ScheduleTimeFormatted;
@ -203,11 +168,22 @@ describe("computed properties...", () => {
}); });
test("ScheduleTimeFormatted should return DROP_OFF time in expected format.", () => { test("ScheduleTimeFormatted should return DROP_OFF time in expected format.", () => {
//Arrange //Arrange
store.getters.submittedOrder.serviceLocation.appointmentType =
AppointmentTypeStrings.DROP_OFF;
store.getters.submittedOrder.schedule.startTime = "09:00";
store.getters.submittedOrder.schedule.endTime = "11:00";
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
startTime: "09:00",
endTime: "11:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
wrapper.vm.submittedOrder.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
// Act // Act
const testValue = wrapper.vm.ScheduleTimeFormatted; const testValue = wrapper.vm.ScheduleTimeFormatted;
@ -217,11 +193,22 @@ describe("computed properties...", () => {
}); });
test("ScheduleTimeFormatted should return Inshop time in expected format.", () => { test("ScheduleTimeFormatted should return Inshop time in expected format.", () => {
//Arrange //Arrange
store.getters.submittedOrder.serviceLocation.appointmentType =
AppointmentTypeStrings.IN_SHOP;
store.getters.submittedOrder.schedule.startTime = "09:00";
store.getters.submittedOrder.schedule.endTime = "11:00";
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
startTime: "09:00",
endTime: "11:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
wrapper.vm.submittedOrder.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP;
// Act // Act
const testValue = wrapper.vm.ScheduleTimeFormatted; const testValue = wrapper.vm.ScheduleTimeFormatted;
@ -231,10 +218,21 @@ describe("computed properties...", () => {
}); });
test("AppointmentWordingText should return mobile text in expected format.", () => { test("AppointmentWordingText should return mobile text in expected format.", () => {
//Arrange //Arrange
store.getters.submittedOrder.serviceLocation.appointmentType =
AppointmentTypeStrings.MOBILE;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
endTime: "10:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
wrapper.vm.submittedOrder.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE;
// Act // Act
const testValue = wrapper.vm.AppointmentWordingText; const testValue = wrapper.vm.AppointmentWordingText;
@ -243,10 +241,21 @@ describe("computed properties...", () => {
}); });
test("AppointmentWordingText should return DROP_OFF text in expected format.", () => { test("AppointmentWordingText should return DROP_OFF text in expected format.", () => {
//Arrange //Arrange
store.getters.submittedOrder.serviceLocation.appointmentType =
AppointmentTypeStrings.DROP_OFF;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
endTime: "10:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
wrapper.vm.submittedOrder.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
// Act // Act
const testValue = wrapper.vm.AppointmentWordingText; const testValue = wrapper.vm.AppointmentWordingText;
@ -256,10 +265,20 @@ describe("computed properties...", () => {
}); });
test("AppointmentWordingText should return IN_SHOP text in expected format.", () => { test("AppointmentWordingText should return IN_SHOP text in expected format.", () => {
//Arrange //Arrange
store.getters.submittedOrder.serviceLocation.appointmentType =
AppointmentTypeStrings.IN_SHOP;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
endTime: "10:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
// Act // Act
const testValue = wrapper.vm.AppointmentWordingText; const testValue = wrapper.vm.AppointmentWordingText;
@ -280,6 +299,19 @@ describe("computed properties...", () => {
test("ScheduleDate should return date in expected format.", () => { test("ScheduleDate should return date in expected format.", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2023-01-01",
endTime: "10:00",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
// Act // Act
const testValue = wrapper.vm.ScheduleDate; const testValue = wrapper.vm.ScheduleDate;
@ -290,6 +322,19 @@ describe("computed properties...", () => {
test("ScheduleStartTime should return time in expected format.", () => { test("ScheduleStartTime should return time in expected format.", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
startTime: "09:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
// Act // Act
const testValue = wrapper.vm.ScheduleStartTime; const testValue = wrapper.vm.ScheduleStartTime;
@ -300,6 +345,19 @@ describe("computed properties...", () => {
test("ScheduleEndTime should return time in expected format.", () => { test("ScheduleEndTime should return time in expected format.", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
endTime: "10:00",
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
// Act // Act
const testValue = wrapper.vm.ScheduleEndTime; const testValue = wrapper.vm.ScheduleEndTime;
@ -344,6 +402,18 @@ describe("computed properties...", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
// Act // Act
const testValue = wrapper.vm.ServiceLocationFullAddress; const testValue = wrapper.vm.ServiceLocationFullAddress;
@ -355,6 +425,18 @@ describe("computed properties...", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
serviceLocation: store.getters.order.serviceLocation,
};
// Act // Act
const testValue = wrapper.vm.ProviderFullAddress; const testValue = wrapper.vm.ProviderFullAddress;
@ -366,6 +448,17 @@ describe("computed properties...", () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
};
// Act // Act
const testValue = wrapper.vm.AppointmentDuration; const testValue = wrapper.vm.AppointmentDuration;
@ -375,9 +468,18 @@ describe("computed properties...", () => {
}); });
test("Appointment Duration should return with correct duration values in expected format for hours.", () => { test("Appointment Duration should return with correct duration values in expected format for hours.", () => {
//Arrange //Arrange
store.getters.submittedOrder.schedule.jobMaxMinutes = 120;
store.getters.submittedOrder.schedule.jobMinMinutes = 60;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2024-09-28",
jobMaxMinutes: 120,
jobMinMinutes: 60,
routeCode: "",
},
};
// Act // Act
const testValue = wrapper.vm.AppointmentDuration; const testValue = wrapper.vm.AppointmentDuration;
@ -387,9 +489,18 @@ describe("computed properties...", () => {
}); });
test("Appointment Duration should return with correct duration values in expected format for minutes.", () => { test("Appointment Duration should return with correct duration values in expected format for minutes.", () => {
//Arrange //Arrange
store.getters.submittedOrder.schedule.jobMaxMinutes = 45;
store.getters.submittedOrder.schedule.jobMinMinutes = 30;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2024-09-28",
jobMaxMinutes: 45,
jobMinMinutes: 30,
routeCode: "",
},
};
// Act // Act
const testValue = wrapper.vm.AppointmentDuration; const testValue = wrapper.vm.AppointmentDuration;
@ -400,10 +511,18 @@ describe("computed properties...", () => {
test("Appointment Duration should return with All Day.", () => { test("Appointment Duration should return with All Day.", () => {
//Arrange //Arrange
store.getters.submittedOrder.schedule.jobMaxMinutes = 45;
store.getters.submittedOrder.schedule.jobMinMinutes = 30;
store.getters.submittedOrder.schedule.routeCode = RouteCodeFlags.ALL_DAY_DROP_OFF;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2024-09-28",
jobMaxMinutes: 45,
jobMinMinutes: 30,
routeCode: RouteCodeFlags.ALL_DAY_DROP_OFF,
},
};
// Act // Act
const testValue = wrapper.vm.AppointmentDuration; const testValue = wrapper.vm.AppointmentDuration;
@ -414,10 +533,18 @@ describe("computed properties...", () => {
test("Appointment Duration should return with Overnight.", () => { test("Appointment Duration should return with Overnight.", () => {
//Arrange //Arrange
store.getters.submittedOrder.schedule.jobMaxMinutes = 45;
store.getters.submittedOrder.schedule.jobMinMinutes = 30;
store.getters.submittedOrder.schedule.routeCode = RouteCodeFlags.OVERNIGHT_DROP_OFF;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.submittedOrder = {
vehicle: {
imageUrl: "",
},
schedule: {
date: "2024-09-28",
jobMaxMinutes: 45,
jobMinMinutes: 30,
routeCode: RouteCodeFlags.OVERNIGHT_DROP_OFF,
},
};
// Act // Act
const testValue = wrapper.vm.AppointmentDuration; const testValue = wrapper.vm.AppointmentDuration;
@ -438,6 +565,7 @@ function setupMocks({ customMountOptions }) {
getCmsContent: jest.fn().mockImplementation(() => { getCmsContent: jest.fn().mockImplementation(() => {
return wordingText; return wordingText;
}), }),
getSettingValue: jest.fn(),
}, },
}, },
]; ];

View file

@ -62,7 +62,8 @@
servicePackageOptionsCmsName="ServicePackageTitle" servicePackageOptionsCmsName="ServicePackageTitle"
:insuranceDeductible="currentDeductible" :insuranceDeductible="currentDeductible"
:insuranceCompanyName="insuranceCompanyName" :insuranceCompanyName="insuranceCompanyName"
:showInsuranceCoverageAs="showInsuranceCoverageAs" /> :showInsuranceCoverageAs="showInsuranceCoverageAs"
:shouldHideRecalibration="shouldHideRecalibration" />
<hr class="mb-5" /> <hr class="mb-5" />
@ -105,20 +106,23 @@ import { Form } from "vee-validate";
import { get12HourTimeFormat, get12HourTimeMobileFormat } from "@/helpers/date-helper"; import { get12HourTimeFormat, get12HourTimeMobileFormat } from "@/helpers/date-helper";
import { coverageStatus } from "@/constants/insurance"; import { coverageStatus } from "@/constants/insurance";
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper"; import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
import { experimentSettings } from "@/constants/experiments";
import experimentMixin from "@/mixins/experiment-mixin.js";
export default { export default {
name: "confirmation", name: "confirmation",
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); // This nulls the Store order
// Call APIs // Call APIs
const submittedOrder = baseMixin.methods.getSubmittedOrder();
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging( const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_WIPERS, storeActions.GET_WIPERS,
{ {
serviceZipCode: store.getters.submittedOrder.serviceLocation.zipCode, serviceZipCode: submittedOrder.serviceLocation.zipCode,
carId: store.getters.submittedOrder.vehicle.carId, carId: submittedOrder.vehicle.carId,
}, },
"confirmation" "confirmation"
); );
@ -147,24 +151,29 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const availableVaps = [resultMap.rainDefense, ...resultMap.wipers]; const availableVaps = [resultMap.rainDefense, ...resultMap.wipers];
const lineItemsFromSubmittedOrder = deepClone(store.getters.submittedOrder.lineItems); const lineItemsFromSubmittedOrder = deepClone(submittedOrder.lineItems);
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.lineItems = lineItemsFromSubmittedOrder; vm.lineItems = lineItemsFromSubmittedOrder;
vm.vaps = availableVaps; vm.vaps = availableVaps;
vm.submittedOrder = submittedOrder;
}); });
}, },
data() { data() {
return { return {
lineItems: [], lineItems: [],
vaps: [], vaps: [],
submittedOrder: null,
}; };
}, },
computed: { computed: {
shouldHideRecalibration() {
return this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE);
},
ShowCart() { ShowCart() {
if (this.isPia && store.getters.submittedOrder.settledTenderAmount == 0) { if (this.isPia && this.submittedOrder?.settledTenderAmount == 0) {
// settleTenderAmount always shows 0 via localhost or dev. // settleTenderAmount always shows 0 via localhost or dev.
// Temporarily set return true to see cart in localhost or dev environment // Temporarily set return true to see cart in localhost or dev environment
return false; return false;
@ -179,62 +188,102 @@ export default {
return this.getCmsContent("ScheduleConfirmationWidget", "Image"); return this.getCmsContent("ScheduleConfirmationWidget", "Image");
}, },
CustomerPortalLoginToken() { CustomerPortalLoginToken() {
return store.getters.submittedOrder.customerPortalLoginToken; return this.submittedOrder?.customerPortalLoginToken;
}, },
ConfirmationEmailText() { ConfirmationEmailText() {
return this.getCmsContent("ConfirmationEmailWidget", "BodyText") return this.getCmsContent("ConfirmationEmailWidget", "BodyText")
?.replaceAll(
"{custom:submittedOrder.customer.emailAddress}",
this.submittedOrder?.customer?.emailAddress
)
?.replaceAll("{custom:MY_ACCOUNT_URL}", applicationConfig.MY_ACCOUNT) ?.replaceAll("{custom:MY_ACCOUNT_URL}", applicationConfig.MY_ACCOUNT)
?.replaceAll("{custom:CUSTOMER_PORTAL_LOGIN_TOKEN}", this.CustomerPortalLoginToken) ?.replaceAll("{custom:CUSTOMER_PORTAL_LOGIN_TOKEN}", this.CustomerPortalLoginToken)
?.replaceAll("&lt;", "<") ?.replaceAll("&lt;", "<")
?.replaceAll("&gt;", ">"); ?.replaceAll("&gt;", ">");
}, },
ScheduleDate() { ScheduleDate() {
return store.getters.submittedOrder.schedule.date; return this.submittedOrder?.schedule?.date;
}, },
AppointmentType() { AppointmentType() {
return store.getters.submittedOrder.serviceLocation.appointmentType; return this.submittedOrder?.serviceLocation?.appointmentType;
}, },
ScheduleStartTime() { ScheduleStartTime() {
return store.getters.submittedOrder.schedule.startTime; return this.submittedOrder?.schedule?.startTime;
}, },
ScheduleEndTime() { ScheduleEndTime() {
return store.getters.submittedOrder.schedule.endTime; return this.submittedOrder?.schedule?.endTime;
}, },
InShopWordingText() { InShopWordingText() {
return this.getCmsContent("InShopWordingWidget", "BodyText"); return this.getCmsContent("InShopWordingWidget", "BodyText")
?.replaceAll(
"{custom:submittedOrder.vehicle.year}",
this.submittedOrder?.vehicle?.year
)
?.replaceAll(
"{custom:submittedOrder.vehicle.make}",
this.submittedOrder?.vehicle?.make
)
?.replaceAll(
"{custom:submittedOrder.vehicle.model}",
this.submittedOrder?.vehicle?.model
);
}, },
MobileWordingText() { MobileWordingText() {
return this.getCmsContent("MobileWordingWidget", "BodyText"); return this.getCmsContent("MobileWordingWidget", "BodyText")
?.replaceAll(
"{custom:submittedOrder.vehicle.year}",
this.submittedOrder?.vehicle?.year
)
?.replaceAll(
"{custom:submittedOrder.vehicle.make}",
this.submittedOrder?.vehicle?.make
)
?.replaceAll(
"{custom:submittedOrder.vehicle.model}",
this.submittedOrder?.vehicle?.model
);
}, },
DropOffWordingText() { DropOffWordingText() {
return this.getCmsContent("DropOffWordingWidget", "BodyText"); return this.getCmsContent("DropOffWordingWidget", "BodyText")
?.replaceAll(
"{custom:submittedOrder.vehicle.year}",
this.submittedOrder?.vehicle?.year
)
?.replaceAll(
"{custom:submittedOrder.vehicle.make}",
this.submittedOrder?.vehicle?.make
)
?.replaceAll(
"{custom:submittedOrder.vehicle.model}",
this.submittedOrder?.vehicle?.model
);
}, },
ServiceLocationAddress() { ServiceLocationAddress() {
return store.getters.submittedOrder.serviceLocation.address; return this.submittedOrder?.serviceLocation?.address;
}, },
ServiceLocationAddress2() { ServiceLocationAddress2() {
return store.getters.submittedOrder.serviceLocation.address2; return this.submittedOrder?.serviceLocation?.address2;
}, },
ServiceLocationCity() { ServiceLocationCity() {
return store.getters.submittedOrder.serviceLocation.city; return this.submittedOrder?.serviceLocation?.city;
}, },
ServiceLocationState() { ServiceLocationState() {
return store.getters.submittedOrder.serviceLocation.state; return this.submittedOrder?.serviceLocation?.state;
}, },
ServiceLocationZipCode() { ServiceLocationZipCode() {
return store.getters.submittedOrder.serviceLocation.zipCode; return this.submittedOrder?.serviceLocation?.zipCode;
}, },
ProviderAddress() { ProviderAddress() {
return store.getters.submittedOrder.serviceLocation.provider.address.streetAddress; return this.submittedOrder?.serviceLocation?.provider?.address?.streetAddress;
}, },
ProviderCity() { ProviderCity() {
return store.getters.submittedOrder.serviceLocation.provider.address.city; return this.submittedOrder?.serviceLocation?.provider?.address?.city;
}, },
ProviderState() { ProviderState() {
return store.getters.submittedOrder.serviceLocation.provider.address.state; return this.submittedOrder?.serviceLocation?.provider?.address?.state;
}, },
ProviderZipCode() { ProviderZipCode() {
return store.getters.submittedOrder.serviceLocation.provider.address.zipCode; return this.submittedOrder?.serviceLocation?.provider?.address?.zipCode;
}, },
AppointmentWordingText() { AppointmentWordingText() {
if (this.AppointmentType == AppointmentTypeStrings.MOBILE) { if (this.AppointmentType == AppointmentTypeStrings.MOBILE) {
@ -268,7 +317,7 @@ export default {
// This conversion ensures we don't get get GMT induced date changes // This conversion ensures we don't get get GMT induced date changes
const dateObject = convertDateStringToDate(this.ScheduleDate); const dateObject = convertDateStringToDate(this.ScheduleDate);
// Ex: Tuesday, April 22 // Ex: Tuesday, April 22
return dateObject.toLocaleDateString("en-us", { return dateObject?.toLocaleDateString("en-us", {
weekday: "long", weekday: "long",
month: "long", month: "long",
day: "numeric", day: "numeric",
@ -286,19 +335,19 @@ export default {
} }
}, },
vehicleBannerImageUrl() { vehicleBannerImageUrl() {
return store.getters.submittedOrder?.vehicle.imageUrl; return this.submittedOrder?.vehicle.imageUrl;
}, },
damageInfo() { damageInfo() {
return store.getters.submittedOrder.damage; return this.submittedOrder?.damage;
}, },
isInsurance() { isInsurance() {
return store.getters.submittedOrder.payment.isInsurance; return this.submittedOrder?.payment?.isInsurance;
}, },
isNoComp() { isNoComp() {
return store.getters.submittedOrder.policy.isNoComp; return this.submittedOrder?.policy?.isNoComp;
}, },
isItac() { isItac() {
return store.getters.submittedOrder.policy.isItac; return this.submittedOrder?.policy?.isItac;
}, },
hasVapsInCart() { hasVapsInCart() {
if (this.lineItems?.vaps?.length > 0) { if (this.lineItems?.vaps?.length > 0) {
@ -311,32 +360,28 @@ export default {
if (this.isNoComp || this.isItac) { if (this.isNoComp || this.isItac) {
return coverageStatus.NOCOMP; return coverageStatus.NOCOMP;
} else { } else {
return store.getters.submittedOrder.payment.insuranceCoverage.coverageStatus; return this.submittedOrder?.payment?.insuranceCoverage?.coverageStatus;
} }
}, },
currentDeductible() { currentDeductible() {
return store.getters.submittedOrder.policy.currentDeductible; return this.submittedOrder?.policy?.currentDeductible;
}, },
insuranceCompanyName() { insuranceCompanyName() {
return store.getters.submittedOrder.policy.insuranceCompanyName; return this.submittedOrder?.policy?.insuranceCompanyName;
}, },
isPia() { isPia() {
return store.getters.submittedOrder?.payment.isPia; return this.submittedOrder?.payment?.isPia;
}, },
AppointmentDuration() { AppointmentDuration() {
const durationMaximum = store.getters.submittedOrder?.schedule?.jobMaxMinutes; const durationMaximum = this.submittedOrder?.schedule?.jobMaxMinutes;
const durationMinimum = store.getters.submittedOrder?.schedule?.jobMinMinutes; const durationMinimum = this.submittedOrder?.schedule?.jobMinMinutes;
const durationLengthString = "Duration: "; const durationLengthString = "Duration: ";
if ( if (
store.getters.submittedOrder?.schedule?.routeCode.includes( this.submittedOrder?.schedule?.routeCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)
RouteCodeFlags.ALL_DAY_DROP_OFF
)
) { ) {
return durationLengthString.concat("All Day"); return durationLengthString.concat("All Day");
} else if ( } else if (
store.getters.submittedOrder?.schedule?.routeCode.includes( this.submittedOrder?.schedule?.routeCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)
RouteCodeFlags.OVERNIGHT_DROP_OFF
)
) { ) {
return durationLengthString.concat("Overnight"); return durationLengthString.concat("Overnight");
} else { } else {
@ -348,7 +393,7 @@ export default {
}, },
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
if (store.getters.hasSubmittedOrder) { if (baseMixin.methods.hasSubmittedOrder()) {
return true; return true;
} }

View file

@ -24,6 +24,7 @@ jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: jest.fn(), fetchCmsContentForPage: jest.fn(),
})); }));
global.crypto = { randomUUID: jest.fn() };
describe("estimate.vue", () => { describe("estimate.vue", () => {
test("Selected vin option is emitted upon selection.", async () => { test("Selected vin option is emitted upon selection.", async () => {
//Arrange //Arrange
@ -216,7 +217,7 @@ describe("estimate.vue", () => {
describe("estimate.vue", () => { describe("estimate.vue", () => {
test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => { test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
store.getters = { store.getters = {
isExternalParameter: true, externalParameterState: { isExternalParameter: true },
externalParameterEstimate: { vinSelection: "decline" }, externalParameterEstimate: { vinSelection: "decline" },
}; };
// Set up the component // Set up the component

View file

@ -146,7 +146,7 @@ export default {
} }
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterEstimate.vinSelection) { if (store.getters.externalParameterEstimate.vinSelection) {
vm.selectedVinLookupMethod = vinPagesMixin.methods.getVinlookupMethod( vm.selectedVinLookupMethod = vinPagesMixin.methods.getVinlookupMethod(
store.getters.externalParameterEstimate.vinSelection store.getters.externalParameterEstimate.vinSelection

View file

@ -76,7 +76,7 @@ export default {
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.originalList = resultMap.insuranceCompanyList; vm.originalList = resultMap.insuranceCompanyList;
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -148,7 +148,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -54,7 +54,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -54,7 +54,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -30,9 +30,11 @@
pageName="payment-method" pageName="payment-method"
servicePackageOptionsCmsName="ServicePackageTitle" servicePackageOptionsCmsName="ServicePackageTitle"
recyclingModalCmsWidgetName="RecycleModal" recyclingModalCmsWidgetName="RecycleModal"
:isInsurance="isInsurance"
:insuranceDeductible="currentDeductible" :insuranceDeductible="currentDeductible"
:insuranceCompanyName="insuranceCompanyName" :insuranceCompanyName="insuranceCompanyName"
:showInsuranceCoverageAs="showInsuranceCoverageAs" /> :showInsuranceCoverageAs="showInsuranceCoverageAs"
:shouldHideRecalibration="shouldHideRecalibration" />
<hr class="my-5" /> <hr class="my-5" />
@ -46,14 +48,16 @@
v-bind:isDismissible="false" /> v-bind:isDismissible="false" />
</div> </div>
<div v-if="hasRecal && !isInsurance" class="questions-about-service my-5"> <div v-if="!isInsurance" class="questions-about-service my-5">
<textBlock <textBlock
cmsWidgetName="QuestionsAboutYourServiceWidget" cmsWidgetName="QuestionsAboutYourServiceWidget"
justifyText="left" justifyText="left"
class="service-questions" /> class="service-questions" />
<textBlock cmsWidgetName="CallOrTextWidget" justifyText="left" /> <textBlock cmsWidgetName="CallOrTextWidget" justifyText="left" />
<hr class="my-5" /> <hr class="my-5" />
<div class="d-flex flex-row checkbox-group"> <div
class="d-flex flex-row checkbox-group"
v-if="isRecalibrationOnOrder && shouldHideRecalibration">
<checkboxQuestion <checkboxQuestion
cmsWidgetName="RecalConfirmWidget" cmsWidgetName="RecalConfirmWidget"
class="mb-5" class="mb-5"
@ -124,6 +128,7 @@ import {
} from "@/helpers/cms-content-helper"; } from "@/helpers/cms-content-helper";
import { paymentMethods } from "@/constants/payment-method-constants"; import { paymentMethods } from "@/constants/payment-method-constants";
import { experimentSettings } from "@/constants/experiments"; import { experimentSettings } from "@/constants/experiments";
import experimentMixin from "@/mixins/experiment-mixin.js";
import { import {
revalidatePromosAndValidateQueryStringPromo, revalidatePromosAndValidateQueryStringPromo,
buildToastMessagesFromRevalidateOrValidatePromoResponse, buildToastMessagesFromRevalidateOrValidatePromoResponse,
@ -147,6 +152,7 @@ import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { mapTaxedLineItemsToStoreFormat } from "../../store"; import { mapTaxedLineItemsToStoreFormat } from "../../store";
import { coverageStatus } from "@/constants/insurance"; import { coverageStatus } from "@/constants/insurance";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
@ -571,22 +577,24 @@ export default {
); );
}, },
hasSubmittedOrder() { hasSubmittedOrder() {
return this.$store.getters.hasSubmittedOrder; return baseMixin.methods.hasSubmittedOrder();
},
getSubmittedOrder() {
return baseMixin.methods.getSubmittedOrder();
}, },
openModal(modalName) { openModal(modalName) {
this.$refs[modalName].openModal(); this.$refs[modalName].openModal();
}, },
}, },
computed: { computed: {
hasRecal() { isRecalibrationOnOrder() {
const recalLineItem = this.$store.getters.order.lineItems?.supportingItems?.find( return containsLineItemWithPartType(
(lineItem) => lineItem.partType.indexOf(partTypeStrings.RECALIBRATION) !== -1 partTypeStrings.RECALIBRATION,
this.$store.getters.order.lineItems?.supportingItems
); );
},
if (recalLineItem) { shouldHideRecalibration() {
return true; return this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE);
}
return false;
}, },
showApplePay() { showApplePay() {
return baseMixin.methods.showApplePay(); return baseMixin.methods.showApplePay();
@ -596,17 +604,17 @@ export default {
}, },
isInsurance() { isInsurance() {
return this.hasSubmittedOrder() return this.hasSubmittedOrder()
? this.$store.getters.submittedOrder.payment.isInsurance ? this.getSubmittedOrder()?.payment.isInsurance
: this.$store.getters.payment.isInsurance; : this.$store.getters.payment.isInsurance;
}, },
isNoComp() { isNoComp() {
return this.hasSubmittedOrder() return this.hasSubmittedOrder()
? this.$store.getters.submittedOrder.policy.isNoComp ? this.getSubmittedOrder()?.policy.isNoComp
: this.$store.getters.policy.isNoComp; : this.$store.getters.policy.isNoComp;
}, },
isItac() { isItac() {
return this.hasSubmittedOrder() return this.hasSubmittedOrder()
? this.$store.getters.submittedOrder.policy.isItac ? this.getSubmittedOrder()?.policy.isItac
: this.$store.getters.policy.isItac; : this.$store.getters.policy.isItac;
}, },
hasVapsInCart() { hasVapsInCart() {
@ -621,13 +629,13 @@ export default {
return coverageStatus.NOCOMP; return coverageStatus.NOCOMP;
} else { } else {
return this.hasSubmittedOrder() return this.hasSubmittedOrder()
? this.$store.getters.submittedOrder.payment.insuranceCoverage.coverageStatus ? this.getSubmittedOrder()?.payment.insuranceCoverage.coverageStatus
: this.$store.getters.payment.insuranceCoverage.coverageStatus; : this.$store.getters.payment.insuranceCoverage.coverageStatus;
} }
}, },
currentDeductible() { currentDeductible() {
return this.hasSubmittedOrder() return this.hasSubmittedOrder()
? this.$store.getters.submittedOrder.policy.currentDeductible ? this.getSubmittedOrder()?.policy.currentDeductible
: this.$store.getters.policy.currentDeductible; : this.$store.getters.policy.currentDeductible;
}, },
insuranceCompanyName() { insuranceCompanyName() {
@ -658,7 +666,7 @@ export default {
return true; return true;
} }
} else { } else {
if (this.isPiaEnabled && this.totalAmountDue > 0 && !this.hasRecal) { if (this.isPiaEnabled && this.totalAmountDue > 0 && !this.shouldHideRecalibration) {
return true; return true;
} }
} }
@ -764,7 +772,7 @@ export default {
} }
.questions-about-service { .questions-about-service {
.service-questions { .service-questions {
font-weight: $font-weight-bold; font-weight: 600;
} }
} }
.cart { .cart {

View file

@ -171,7 +171,7 @@
<input type="hidden" name="paypalSuccessUrl" :value="piaResponseUrl" /> <input type="hidden" name="paypalSuccessUrl" :value="piaResponseUrl" />
<input type="hidden" name="paypalCancelUrl" :value="piaCancelUrl" /> <input type="hidden" name="paypalCancelUrl" :value="piaCancelUrl" />
<input type="hidden" name="sgCCDeclineURL" :value="piaCancelUrl" /> <input type="hidden" name="sgCCDeclineURL" :value="piaErrorUrl" />
<input type="hidden" name="sgCCTimeoutURL" :value="piaCancelUrl" /> <input type="hidden" name="sgCCTimeoutURL" :value="piaCancelUrl" />
<input type="hidden" name="sgTransactionType" value="authorization" /> <input type="hidden" name="sgTransactionType" value="authorization" />
@ -235,6 +235,7 @@ export default {
checkoutUrl: externalUrls.SAFELITE_HOP, checkoutUrl: externalUrls.SAFELITE_HOP,
piaResponseUrl: applicationConfig.PIA_RESPONSE_URL, piaResponseUrl: applicationConfig.PIA_RESPONSE_URL,
piaCancelUrl: applicationConfig.PIA_CANCEL_URL, piaCancelUrl: applicationConfig.PIA_CANCEL_URL,
piaErrorUrl: applicationConfig.PIA_ERROR_URL,
paymentType: this.getPaymentType(), paymentType: this.getPaymentType(),
authToken: "", authToken: "",
authSignature: "", authSignature: "",

View file

@ -238,6 +238,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -281,6 +284,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -322,6 +328,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -360,6 +369,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -399,6 +411,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -438,6 +453,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -478,6 +496,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -520,6 +541,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -561,6 +585,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -654,6 +681,9 @@ describe("quote.vue", () => {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
}, },
applicationUser: {
experiments: [],
},
order: { order: {
lineItems: { lineItems: {
glassParts: ["item", "item2"], glassParts: ["item", "item2"],
@ -671,7 +701,7 @@ describe("quote.vue", () => {
inactivePromos: [], inactivePromos: [],
}, },
}, },
isExternalParameter: true, externalParameterState: { isExternalParameter: true },
externalParameterQuote: { externalParameterQuote: {
isInsurance: true, isInsurance: true,
servicePackage: "glassonly", servicePackage: "glassonly",
@ -720,7 +750,10 @@ function setupMocks({ customMountOptions }) {
baseMixin.methods.ResetExternalParamsAndHideModal = jest.fn(); baseMixin.methods.ResetExternalParamsAndHideModal = jest.fn();
baseMixin.methods.isFormValid = jest.fn().mockReturnValue(true); baseMixin.methods.isFormValid = jest.fn().mockReturnValue(true);
mountOptions.global.mocks["$store"] = store; mountOptions.global.mocks["$store"] = store;
store.getters.experimentSettings = "test value";
mountOptions["attachTo"] = document.body; mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(quote, mountOptions); const wrapper = shallowMount(quote, mountOptions);
wrapper.vm.setCmsContent = jest.fn(); wrapper.vm.setCmsContent = jest.fn();
return { wrapper }; return { wrapper };

View file

@ -31,6 +31,8 @@
groupName="ServicePackageQuestion" groupName="ServicePackageQuestion"
:availableLineItems="availableLineItems" :availableLineItems="availableLineItems"
:isInsuranceSelected="isInsuranceSelected" :isInsuranceSelected="isInsuranceSelected"
:isRecalibrationOnOrder="isRecalibrationOnOrder"
:shouldHideRecalibration="shouldHideRecalibration"
@vapsItemsSelected="vapsItemsSelectedAction" @vapsItemsSelected="vapsItemsSelectedAction"
@servicePackageDiscountSelected="servicePackageDiscountSelectedAction" @servicePackageDiscountSelected="servicePackageDiscountSelectedAction"
:servicePackage="servicePackage" :servicePackage="servicePackage"
@ -42,15 +44,17 @@
</div> </div>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<!-- If Cash AND vehicle does not require recal OR if Cash AND State requires showing recal, then show Afterpay banner -->
<afterpayModalBanner <afterpayModalBanner
v-if="!isInsuranceSelected && !isRecalibrationOnOrder" v-if="showAfterpayBanner"
cmsWidgetName="AfterpayModalWidget" cmsWidgetName="AfterpayModalWidget"
:lineItems="lineItems" /> :lineItems="lineItems" />
<!-- If vehicle requires recal AND we are hiding recal pricing info, then show recal disclaimer banner. -->
<recalDisclaimer <recalDisclaimer
class="mb-0" class="mb-0"
cmsWidgetName="RecalDisclaimerWidget" cmsWidgetName="RecalDisclaimerWidget"
v-if="isRecalibrationOnOrder" v-if="showRecalDisclaimer"
v-on="{ textLinkClicked: openModalAction }" v-on="{ textLinkClicked: openModalAction }"
alertClass="alert-info" /> alertClass="alert-info" />
@ -99,6 +103,8 @@ import contentGroupModal from "@/fmg-components/content-group-modal/content-grou
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner"; import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner";
import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue"; import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue";
// Supporting files
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import experimentMixin from "@/mixins/experiment-mixin.js"; import experimentMixin from "@/mixins/experiment-mixin.js";
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin"; import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
@ -107,6 +113,8 @@ import { storeActions } from "@/constants/store-actions";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import store from "@/store"; import store from "@/store";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { experimentUniverses, experimentSettings } from "@/constants/experiments";
import { getSessionKeyValue, getUserIdValue } from "@/helpers/heritage-integration/cookie-helper";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import { Form, defineRule } from "vee-validate"; import { Form, defineRule } from "vee-validate";
@ -120,7 +128,6 @@ import {
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question"; import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
import { experimentSettings } from "@/constants/experiments";
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper"; import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { nextTick } from "vue"; import { nextTick } from "vue";
@ -286,7 +293,7 @@ export default {
} }
} }
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterQuote.isInsurance == true) { if (store.getters.externalParameterQuote.isInsurance == true) {
vm.isInsuranceSelected = true; vm.isInsuranceSelected = true;
vm.servicePackage = store.getters.externalParameterQuote.servicePackage; vm.servicePackage = store.getters.externalParameterQuote.servicePackage;
@ -324,6 +331,21 @@ export default {
this?.availableLineItems this?.availableLineItems
); );
}, },
shouldHideRecalibration() {
const recalSettingValue = experimentMixin.methods.getSettingValue(
experimentSettings.RECAL_PRICE_REMOVE
);
return recalSettingValue;
},
showAfterpayBanner() {
return (
!this.isInsuranceSelected &&
(!this.isRecalibrationOnOrder || !this.shouldHideRecalibration)
);
},
showRecalDisclaimer() {
return this.isRecalibrationOnOrder && this.shouldHideRecalibration;
},
}, },
methods: { methods: {
openModalAction(modalName) { openModalAction(modalName) {

View file

@ -475,6 +475,7 @@ describe("service-package-question.vue, matching business rules for package disp
mockProps.availableLineItems.push(recalLineItem); mockProps.availableLineItems.push(recalLineItem);
mockProps.availableLineItems.push(driverFrontWiperLineItem); mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem); mockProps.availableLineItems.push(passengerFrontWiperLineItem);
mockProps.isRecalibrationOnOrder = true;
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]); Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
const wrapper = setupMocks({ const wrapper = setupMocks({
mountOptionsMockData: { mountOptionsMockData: {

View file

@ -49,6 +49,8 @@ export default {
availableLineItems: null, availableLineItems: null,
activePromos: null, activePromos: null,
servicePackage: null, servicePackage: null,
isRecalibrationOnOrder: Boolean,
shouldHideRecalibration: Boolean,
}, },
data() { data() {
return { return {
@ -141,12 +143,6 @@ export default {
})); }));
return modifiedAnswers; return modifiedAnswers;
}, },
isRecalibrationOnOrder() {
return containsLineItemWithPartType(
partTypeStrings.RECALIBRATION,
this.nullSafeAvailableLineItems
);
},
isServicePackageDiscountOnOrder() { isServicePackageDiscountOnOrder() {
return containsLineItemWithPartType( return containsLineItemWithPartType(
partTypeStrings.SERVICE_PACKAGE_DISCOUNT, partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
@ -242,7 +238,8 @@ export default {
}, },
getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) { getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) {
var lineItemsToPrice = [...this.nullSafeAvailableLineItems]; var lineItemsToPrice = [...this.nullSafeAvailableLineItems];
if (this.isRecalibrationOnOrder) {
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
lineItemsToPrice = lineItemsToPrice.filter((item) => { lineItemsToPrice = lineItemsToPrice.filter((item) => {
return item.partType != partTypeStrings.RECALIBRATION; return item.partType != partTypeStrings.RECALIBRATION;
}); });

View file

@ -3,7 +3,7 @@ import baseMixin from "@/mixins/base-mixin.js";
export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) { export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
if (!serviceZipCode) { if (!serviceZipCode) {
return Promise.resolve(null); return null;
} }
const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode, pageNameToLog); const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode, pageNameToLog);
@ -36,12 +36,12 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
false false
); );
return Promise.resolve(pricingResults[0]); return pricingResults[0];
} }
export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) { export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) {
// Get the Mobile Fee Part // Get the Mobile Fee Part
const serviceabilityDetails = await baseMixin.methods.dispatchStoreActionWithLogging( return await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_SERVICEABILITY_DETAILS, storeActions.GET_SERVICEABILITY_DETAILS,
{ {
serviceZipCode: serviceZipCode, serviceZipCode: serviceZipCode,
@ -50,20 +50,26 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNa
pageNameToLog, pageNameToLog,
false false
); );
}
return Promise.resolve(serviceabilityDetails); export async function getBillToAccountNumber(providerNumber) {
// Get the BillTo Account Number
return await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_BILL_TO_ACCOUNT_NUMBER,
{ providerNumber: providerNumber },
"service-location",
false
);
} }
export async function getShopProviderData(serviceZipCode) { export async function getShopProviderData(serviceZipCode) {
const shopProviderData = await baseMixin.methods.dispatchStoreActionWithLogging( return await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_PROVIDERS, storeActions.GET_PROVIDERS,
{ {
serviceZipCode: serviceZipCode, serviceZipCode: serviceZipCode,
}, },
"service-location" "service-location"
); );
return Promise.resolve(shopProviderData);
} }
export async function getAvailabilityRating( export async function getAvailabilityRating(
@ -92,5 +98,5 @@ export async function getAvailabilityRating(
const shopStatus = isGoodAvailability ? "high" : "low"; const shopStatus = isGoodAvailability ? "high" : "low";
return Promise.resolve(shopStatus); return shopStatus;
} }

View file

@ -2,6 +2,7 @@ import mobileLocationModalQuestions from "./mobile-location-modal-questions";
import { mount, shallowMount } from "@vue/test-utils"; import { mount, shallowMount } from "@vue/test-utils";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { getBillToAccountNumber } from "../helpers/service-location-helper/service-location-helper";
const linkWidgetName = "linkWidgetName"; const linkWidgetName = "linkWidgetName";
const modalWidgetName = "modalWidgetName"; const modalWidgetName = "modalWidgetName";
@ -125,6 +126,9 @@ jest.mock(
getServiceabilityDetails: jest.fn((mockServiceZipCode) => { getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode); return mockGetServiceabilityDetails(mockServiceZipCode);
}), }),
getBillToAccountNumber: jest.fn(() => {
return "87291";
}),
}) })
); );

View file

@ -83,6 +83,7 @@ import { deepClone } from "@/helpers/object-helper";
import { import {
getPricedMobileFeePart, getPricedMobileFeePart,
getServiceabilityDetails, getServiceabilityDetails,
getBillToAccountNumber,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
// Validation // Validation
@ -91,7 +92,12 @@ import { v4 as uuidv4 } from "uuid";
export default { export default {
name: "mobile-location-modal-questions", name: "mobile-location-modal-questions",
emits: ["update:modelValue", "updated-mobile-fee-part", "updated-contains-military-base"], emits: [
"update:modelValue",
"updated-mobile-fee-part",
"updated-contains-military-base",
"updated-bill-to-account-number",
],
data() { data() {
return { return {
internalModel: deepClone(this.modelValue), internalModel: deepClone(this.modelValue),
@ -266,11 +272,16 @@ export default {
"service-location" "service-location"
); );
const billToAccountNumber = await getBillToAccountNumber(
this.internalModel.zipCodeCtu
);
// update content related to service zip code // update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart); this.$emit("updated-mobile-fee-part", mobileFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu); this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu);
this.$emit("updated-bill-to-account-number", billToAccountNumber);
// update the page level model // update the page level model
this.$emit("update:modelValue", this.internalModel); this.$emit("update:modelValue", this.internalModel);

View file

@ -19,6 +19,7 @@
@updated-mobile-fee-part="setMobileFeePart" @updated-mobile-fee-part="setMobileFeePart"
@updated-serviceability="setServiceabilityDetails" @updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase" @updated-contains-military-base="setContainsMilitaryBase"
@updated-bill-to-account-number="setBillToAccountNumber"
linkWidgetName="ServiceZipLinkWidget" linkWidgetName="ServiceZipLinkWidget"
modalWidgetName="ServiceZipModalWidget" /> modalWidgetName="ServiceZipModalWidget" />
@ -191,6 +192,7 @@ export default {
mobileFeePart: null, mobileFeePart: null,
zipContainsMilitaryBase: false, zipContainsMilitaryBase: false,
zipCodeCtu: null, zipCodeCtu: null,
billToAccountNumber: null,
shopProviderData: null, shopProviderData: null,
navigatingForward: false, navigatingForward: false,
}; };
@ -437,6 +439,9 @@ export default {
this.zipContainsMilitaryBase = val; this.zipContainsMilitaryBase = val;
} }
}, },
setBillToAccountNumber(val) {
this.billToAccountNumber = val;
},
setCtuForMobile(val) { setCtuForMobile(val) {
this.zipCodeCtu = val; this.zipCodeCtu = val;
}, },
@ -601,6 +606,14 @@ export default {
}, },
false false
); );
console.log(this.billToAccountNumber);
if (this.billToAccountNumber) {
this.dispatchStoreAction(
this.storeActions.SAVE_BILL_TO_ACCOUNT_NUMBER,
this.billToAccountNumber,
false
);
}
this.updateAndSaveSupportingItems(); this.updateAndSaveSupportingItems();
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route); this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);

View file

@ -1,5 +1,6 @@
import { mount, shallowMount } from "@vue/test-utils"; import { mount, shallowMount } from "@vue/test-utils";
import serviceZipModalQuestion from "./service-zip-modal-question"; import serviceZipModalQuestion from "./service-zip-modal-question";
import { getBillToAccountNumber } from "../helpers/service-location-helper/service-location-helper";
jest.mock("@/digital-components/textbox-question/textbox-question", () => ({ jest.mock("@/digital-components/textbox-question/textbox-question", () => ({
getCmsContent: jest.fn((widgetName, cmsFieldName) => { getCmsContent: jest.fn((widgetName, cmsFieldName) => {
@ -51,6 +52,9 @@ jest.mock(
getServiceabilityDetails: jest.fn((mockServiceZipCode) => { getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode); return mockGetServiceabilityDetails(mockServiceZipCode);
}), }),
getBillToAccountNumber: jest.fn((mockServiceZipCodeCtu) => {
return "87291";
}),
}) })
); );

View file

@ -46,11 +46,17 @@ import alert from "@/ux-components/alert/alert";
import { import {
getPricedMobileFeePart, getPricedMobileFeePart,
getServiceabilityDetails, getServiceabilityDetails,
getBillToAccountNumber,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
export default { export default {
name: "service-zip-modal-question", name: "service-zip-modal-question",
emits: ["update:modelValue", "updated-mobile-fee-part", "updated-contains-military-base"], emits: [
"update:modelValue",
"updated-mobile-fee-part",
"updated-contains-military-base",
"updated-bill-to-account-number",
],
data() { data() {
return { return {
internalModel: this.copyModel(this.modelValue), internalModel: this.copyModel(this.modelValue),
@ -165,10 +171,15 @@ export default {
"service-location" "service-location"
); );
const billToAccountNumber = await getBillToAccountNumber(
this.internalModel.zipCodeCtu
);
// update content related to service zip code // update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart); this.$emit("updated-mobile-fee-part", mobileFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-bill-to-account-number", billToAccountNumber);
// update the page level model // update the page level model
this.$emit("update:modelValue", this.internalModel); this.$emit("update:modelValue", this.internalModel);

View file

@ -169,6 +169,7 @@ function resetMockStoreData() {
zipCode: null, zipCode: null,
emailAddress: null, emailAddress: null,
}, },
externalParameterState: { isExternalParameter: false },
}; };
} }
@ -412,7 +413,7 @@ describe("service-zip.vue", () => {
describe("service-zip.vue", () => { describe("service-zip.vue", () => {
test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => { test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
// Set up the store with isExternalParameter as true // Set up the store with isExternalParameter as true
store.getters.isExternalParameter = true; store.getters.externalParameterState = { isExternalParameter: true };
// Set up the component // Set up the component
const wrapper = setupMocks({}); const wrapper = setupMocks({});
@ -436,7 +437,7 @@ describe("service-zip.vue", () => {
}); });
test("should not call forwardButtonAction if isExternalParameter is true and form is invalid", async () => { test("should not call forwardButtonAction if isExternalParameter is true and form is invalid", async () => {
// Set up the store with isExternalParameter as true // Set up the store with isExternalParameter as true
store.getters.isExternalParameter = true; store.getters.externalParameterState = { isExternalParameter: true };
// Set up the component // Set up the component
const wrapper = setupMocks({}); const wrapper = setupMocks({});

View file

@ -132,7 +132,7 @@ export default {
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm); const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
if (isValid) { if (isValid) {
vm.forwardButtonAction(); vm.forwardButtonAction();
@ -188,7 +188,7 @@ export default {
if (!zipCodeData.isValid) { if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true; this.displayInvalidZipAlert = true;
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
return this.$refs.navbar.removeLoader(); return this.$refs.navbar.removeLoader();
@ -197,7 +197,7 @@ export default {
if (!zipCodeData.isServiceable) { if (!zipCodeData.isServiceable) {
this.displayNonServiceableZipAlert = true; this.displayNonServiceableZipAlert = true;
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
return this.$refs.navbar.removeLoader(); return this.$refs.navbar.removeLoader();

View file

@ -66,7 +66,7 @@ jest.mock("@/store", () => ({
glassToReplace: [], glassToReplace: [],
}, },
}, },
isExternalParameter: false, externalParameterState: { isExternalParameter: false },
externalParameterDamage: { externalParameterDamage: {
damageType: null, damageType: null,
isRepair: null, isRepair: null,
@ -813,7 +813,7 @@ describe("vehicle-damage.vue", () => {
describe("vehicle-damage.vue", () => { describe("vehicle-damage.vue", () => {
test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => { test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
// Set up the store with isExternalParameter as true // Set up the store with isExternalParameter as true
store.getters.isExternalParameter = true; store.getters.externalParameterState = { isExternalParameter: true };
store.getters.externalParameterDamage = { store.getters.externalParameterDamage = {
damageType: "windshieldReplace", damageType: "windshieldReplace",
isRepair: false, isRepair: false,

View file

@ -1,5 +1,5 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }"> <Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
<div class="container-fluid page-container-grouped-styles vehicle-damage"> <div class="container-fluid page-container-grouped-styles vehicle-damage">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
@ -70,7 +70,7 @@
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!isContinueEnabled"
:isBackButtonHidden="shouldHideBackButton" :isBackButtonHidden="shouldHideBackButton"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
@ -151,7 +151,7 @@ export default {
vm.$refs.backGlassOptions.initializeComponent( vm.$refs.backGlassOptions.initializeComponent(
resultMap.damageOptions.backGlassOptions.availableReplacementOptions resultMap.damageOptions.backGlassOptions.availableReplacementOptions
); );
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
await nextTick(); await nextTick();
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm); const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
if (isValid) { if (isValid) {
@ -180,6 +180,36 @@ export default {
this.attachCustomEvents(); this.attachCustomEvents();
}, },
methods: { methods: {
clearChildEntries() {
if (!this.isSideDoorDamageLocation) {
this.sideDoorOptionsData = {
selectedDoorSides: [],
selectedDriverSideReplaceOptions: [],
selectedPassengerSideReplaceOptions: [],
};
} else {
if (!this.isDriverSideReplace) {
this.sideDoorOptionsData.selectedDriverSideReplaceOptions = [];
}
if (!this.isPassengerSideReplace) {
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions = [];
}
}
if (!this.isRearWindowDamageLocation) {
this.selectedRearReplaceOptions = "";
}
if (!this.isWindshieldDamageLocation) {
this.selectedWindshieldOptions = {
selectedWindshieldDamageType: "",
selectedWindshieldChipCount: null,
selectedWindshieldReplaceOptions: [],
};
} else {
if (!this.isWindshieldRepair) {
this.selectedWindshieldOptions.selectedWindshieldChipCount = null;
}
}
},
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
if (store.getters.vehicle.carId) { if (store.getters.vehicle.carId) {
return true; return true;
@ -584,6 +614,94 @@ export default {
shouldHideBackButton() { shouldHideBackButton() {
return this.$store.getters.requiresVerifiedRedirecting; return this.$store.getters.requiresVerifiedRedirecting;
}, },
isContinueEnabled() {
return (
this.isAnyDamageSelected &&
this.isWindshieldValid &&
this.isRearGlassValid &&
this.isSideGlassValid
);
},
isAnyDamageSelected() {
return this.selectedDamageLocations != null && this.selectedDamageLocations.length != 0;
},
isSideGlassValid() {
if (this.isSideDoorDamageLocation) {
if (
this.sideDoorOptionsData.selectedDoorSides == null ||
this.sideDoorOptionsData.selectedDoorSides == 0
) {
// SideDoor is selected, but "which side" question is unanswered
return false;
} else {
if (this.isPassengerSideReplace) {
if (
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions == null ||
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.length == 0
) {
// PassengerSide is selected, but no sub option is
return false;
}
}
if (this.isDriverSideReplace) {
if (
this.sideDoorOptionsData.selectedDriverSideReplaceOptions == null ||
this.sideDoorOptionsData.selectedDriverSideReplaceOptions.length == 0
) {
// DriverSide is selected, but no sub option is
return false;
}
}
}
}
return true;
},
isRearGlassValid() {
if (this.isRearWindowDamageLocation) {
if (!this.selectedRearReplaceOptions) {
// RearDoor is selected, but rear option question is unanswered
return false;
}
}
return true;
},
isWindshieldValid() {
if (this.isWindshieldDamageLocation) {
if (!this.selectedWindshieldOptions.selectedWindshieldDamageType) {
// Windshield is selected but no replace/repair option is selected
return false;
} else {
if (this.hasSplitSingleConflict) {
return false;
}
if (this.isWindshieldRepair) {
if (this.hasRepairReplaceConflict) {
return false;
}
if (!this.selectedWindshieldOptions.selectedWindshieldChipCount) {
// Repair selected but no chips selected
return false;
}
} else {
if (
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions ==
null ||
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions
.length == 0
) {
// Replace selected but follow-up split/single question not answered
return false;
}
}
}
}
return true;
},
},
watch: {
selectedDamageLocations() {
this.clearChildEntries();
},
}, },
components: { components: {

View file

@ -102,7 +102,7 @@ export default {
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget, FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
}) })
); );
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -28,7 +28,7 @@ jest.mock("@/store", () => ({
applicationUser: { applicationUser: {
experiments: [{ universeName: "ConceptFunnel" }], experiments: [{ universeName: "ConceptFunnel" }],
}, },
isExternalParameter: false, externalParameterState: { isExternalParameter: false },
externalParameterVehicle: { externalParameterVehicle: {
year: null, year: null,
make: null, make: null,
@ -94,7 +94,7 @@ describe("vehicle.vue", () => {
describe("vehicle.vue", () => { describe("vehicle.vue", () => {
test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => { test("should call forwardButtonAction if isExternalParameter is true and form is valid", async () => {
store.getters.isExternalParameter = true; store.getters.externalParameterState.isExternalParameter = true;
// Create a shallow mount of MyComponent // Create a shallow mount of MyComponent
const { wrapper } = setupMocks(); const { wrapper } = setupMocks();
// Set displayNoServiceAlert to false // Set displayNoServiceAlert to false

View file

@ -221,7 +221,7 @@ export default {
let resultMap = await settleAllPromises(promiseResultMap); let resultMap = await settleAllPromises(promiseResultMap);
//ExternalParameter widget have different case style value so need to parse with NextGen //ExternalParameter widget have different case style value so need to parse with NextGen
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
if ( if (
makeQuestionInitialDataPromise && makeQuestionInitialDataPromise &&
modelQuestionInitialDataPromise && modelQuestionInitialDataPromise &&
@ -269,7 +269,7 @@ export default {
resultMap.modelQuestionInitialData, resultMap.modelQuestionInitialData,
resultMap.styleQuestionInitialData resultMap.styleQuestionInitialData
); );
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
await vm.getVehicleDetails(); await vm.getVehicleDetails();
if (!vm.displayNoServiceAlert) { if (!vm.displayNoServiceAlert) {
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm); const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);

View file

@ -192,7 +192,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
baseMixin.methods.ResetExternalParamsAndHideModal(); baseMixin.methods.ResetExternalParamsAndHideModal();
} }
}); });

View file

@ -37,9 +37,11 @@ export default {
const currentPageName = getPageNameByQueryString(); const currentPageName = getPageNameByQueryString();
await this.validateSession(); await this.validateSession();
const submittedOrder = baseMixin.methods.getSubmittedOrder();
const refSequenceNum = const refSequenceNum =
store.getters.order.referralSequenceNumber || store.getters.order.referralSequenceNumber ||
store.getters.submittedOrder?.referralSequenceNumber; submittedOrder?.referralSequenceNumber;
var payload = { var payload = {
userId: getUserIdValue(), userId: getUserIdValue(),
@ -125,8 +127,9 @@ export default {
const isDefined = (x) => x !== null && x !== undefined; const isDefined = (x) => x !== null && x !== undefined;
// Get correct order object // Get correct order object
const hasSubmittedOrder = store.getters.hasSubmittedOrder; const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder();
const order = hasSubmittedOrder ? store.getters.submittedOrder : store.getters.order; const submittedOrder = baseMixin.methods.getSubmittedOrder();
const order = hasSubmittedOrder ? submittedOrder : store.getters.order;
// Begin assembling payload for data layer // Begin assembling payload for data layer
const payload = {}; const payload = {};
@ -423,7 +426,9 @@ export default {
} else { } else {
coupon += productSku; coupon += productSku;
} }
discount += productPrice * -1; discount += productPrice * -1;
} else { } else {
products.push({ products.push({
productType: productType, productType: productType,

View file

@ -21,6 +21,7 @@ import {
getSessionKeyValue, getSessionKeyValue,
getUserIdValue, getUserIdValue,
} from "@/helpers/heritage-integration/cookie-helper"; } from "@/helpers/heritage-integration/cookie-helper";
import baseMixin from "./base-mixin";
const parts = { const parts = {
windshield: { windshield: {
@ -470,8 +471,9 @@ describe("analyticsMixin.js", () => {
test("Pushes correct data when submitted order is present", () => { test("Pushes correct data when submitted order is present", () => {
// Arrange // Arrange
window.dataLayer = []; window.dataLayer = [];
store.getters.hasSubmittedOrder = true; baseMixin.methods.hasSubmittedOrder = jest.fn().mockReturnValue(true);
store.getters.submittedOrder = store.getters.order; baseMixin.methods.getSubmittedOrder = jest.fn().mockReturnValue(store.getters.order);
store.getters.order = { store.getters.order = {
vehicle: { vehicle: {
year: null, year: null,

View file

@ -102,6 +102,12 @@ export default {
}); });
return filteredLineItems; return filteredLineItems;
}, },
filterOutRecalibration(lineItems) {
const filteredLineItems = lineItems.filter((item) => {
return !item.partType.includes(partTypeStrings.RECALIBRATION);
});
return filteredLineItems;
},
getTotalPriceOfAllLineItemsAndChildParts(lineItems, includeTax) { getTotalPriceOfAllLineItemsAndChildParts(lineItems, includeTax) {
let totalPrice = 0; let totalPrice = 0;
lineItems.forEach((lineItem) => { lineItems.forEach((lineItem) => {
@ -160,9 +166,7 @@ export default {
); );
} }
const order = store.getters.hasSubmittedOrder const order = this.hasSubmittedOrder() ? this.getSubmittedOrder() : store.getters.order;
? store.getters.submittedOrder
: store.getters.order;
if ( if (
order.payment.insuranceCoverage.isVerified && order.payment.insuranceCoverage.isVerified &&
@ -218,6 +222,12 @@ export default {
return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)]; return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
} }
}, },
getSubmittedOrder() {
return JSON.parse(window.sessionStorage.getItem("submittedOrder"));
},
hasSubmittedOrder() {
return window.sessionStorage.getItem("submittedOrder") !== null;
},
}, },
computed: { computed: {
storeActions() { storeActions() {

View file

@ -226,15 +226,16 @@ describe("baseMixin.js", () => {
const payment = { insuranceCoverage: { isVerified: true } }; const payment = { insuranceCoverage: { isVerified: true } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 250 }; 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 = { store.getters = {
payment: {}, payment: {},
policy: {}, policy: {},
order: {}, order: {},
submittedOrder: {
payment: payment,
policy: policy,
},
hasSubmittedOrder: true,
}; };
var amtDue = mixIn.methods.getAmountDue(lineItems); var amtDue = mixIn.methods.getAmountDue(lineItems);
@ -301,6 +302,8 @@ describe("baseMixin.js", () => {
const payment = { isInsurance: null, insuranceCoverage: { isVerified: null } }; const payment = { isInsurance: null, insuranceCoverage: { isVerified: null } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 0 }; const policy = { isNoComp: false, isItac: false, currentDeductible: 0 };
mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(false);
store.getters = { store.getters = {
payment: payment, payment: payment,
policy: policy, policy: policy,

View file

@ -64,7 +64,7 @@ const routes = [
} }
// Intercept all navigation if a submitted order exists in storage // Intercept all navigation if a submitted order exists in storage
if (store.getters.hasSubmittedOrder) { if (window.sessionStorage.getItem("submittedOrder") !== null) {
if (to.query.fmgPage !== funnelStartPageName) { if (to.query.fmgPage !== funnelStartPageName) {
to.query.fmgPage = fmgPageValues.CONFIRMATION; to.query.fmgPage = fmgPageValues.CONFIRMATION;
} }
@ -251,7 +251,7 @@ router.beforeEach(async (to, from, next) => {
const notToPIAReturn = toQueryPage != fmgPageValues.PAYMENT_PIA_RETURN; const notToPIAReturn = toQueryPage != fmgPageValues.PAYMENT_PIA_RETURN;
const isInIframe = window !== window.top; const isInIframe = window !== window.top;
if (store.getters.isExternalParameter) { if (store.getters.externalParameterState?.isExternalParameter) {
// Check if alert event is on the bus // Check if alert event is on the bus
const alertEvent = eventBus.readEventFromBus( const alertEvent = eventBus.readEventFromBus(
globalEvents.Categories.GLOBAL_ALERT, globalEvents.Categories.GLOBAL_ALERT,
@ -301,7 +301,7 @@ router.afterEach(async (to, from) => {
await saveSession({ pageNameToLog: to.query.fmgPage }); await saveSession({ pageNameToLog: to.query.fmgPage });
} }
} }
if (!store.getters.isExternalParameter) { if (!store.getters.externalParameterState?.isExternalParameter) {
showFmgLoadingModal(false); showFmgLoadingModal(false);
} }
@ -426,6 +426,7 @@ async function navigate(
const zip = getQuerystringParameter(queryStrings.ZIP_CODE); const zip = getQuerystringParameter(queryStrings.ZIP_CODE);
const promo = getQuerystringParameter(queryStrings.PROMO); const promo = getQuerystringParameter(queryStrings.PROMO);
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR); const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
const log = getQuerystringParameter(queryStrings.LOG);
if ( if (
hasZip && hasZip &&
@ -445,6 +446,10 @@ async function navigate(
queryStringsObject[queryStrings.PAGE_ERROR] = pageError; queryStringsObject[queryStrings.PAGE_ERROR] = pageError;
} }
if (log) {
queryStringsObject[queryStrings.LOG] = log;
}
router.push({ router.push({
name: "root", name: "root",
query: Object.assign(optionalQuery, queryStringsObject), query: Object.assign(optionalQuery, queryStringsObject),
@ -627,6 +632,7 @@ function updateExternalParameterState() {
const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION); const externalParameterVinSelection = getQuerystringParameter(queryStrings.VIN_SELECTION);
const externalParameterServicePackage = getQuerystringParameter(queryStrings.SERVICE_PACKAGE); const externalParameterServicePackage = getQuerystringParameter(queryStrings.SERVICE_PACKAGE);
const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS); const externalParameterNumberOfChips = getQuerystringParameter(queryStrings.NUMBER_OF_CHIPS);
const externalParameterSource = getQuerystringParameter(queryStrings.EXPERIMENTS);
if ( if (
externalParameterYear && externalParameterYear &&
externalParameterMake && externalParameterMake &&
@ -676,6 +682,12 @@ function updateExternalParameterState() {
externalParameterVinSelection externalParameterVinSelection
); );
} }
if (externalParameterSource) {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_SOURCE,
externalParameterSource
);
}
} }
export default router; export default router;

View file

@ -472,6 +472,10 @@ export const mutations = {
externalParameterState.quote.servicePackage = servicePackage; externalParameterState.quote.servicePackage = servicePackage;
saveExternalParameterState(externalParameterState); saveExternalParameterState(externalParameterState);
}, },
updateExternalParameterSource(state, source) {
externalParameterState.source = source;
saveExternalParameterState(externalParameterState);
},
//RESET ExternalParameter MUTATIONS //RESET ExternalParameter MUTATIONS
resetExternalParameterVehicleState(state) { resetExternalParameterVehicleState(state) {
externalParameterState.vehicle.year = null; externalParameterState.vehicle.year = null;
@ -502,6 +506,10 @@ export const mutations = {
externalParameterState.quote.servicePackage = null; externalParameterState.quote.servicePackage = null;
saveExternalParameterState(externalParameterState); saveExternalParameterState(externalParameterState);
}, },
resetExternalParameterSourceState(state) {
externalParameterState.source = null;
saveExternalParameterState(externalParameterState);
},
resetIsExternalParameter(state) { resetIsExternalParameter(state) {
externalParameterState.isExternalParameter = null; externalParameterState.isExternalParameter = null;
saveExternalParameterState(externalParameterState); saveExternalParameterState(externalParameterState);
@ -883,8 +891,6 @@ export const getters = {
.filter((x) => !!x.isActive) .filter((x) => !!x.isActive)
.map((x) => x.settings) .map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {}, .reduce((r, c) => Object.assign(r, c), {}) ?? {},
submittedOrder: (state) => JSON.parse(window.sessionStorage.getItem("submittedOrder")),
hasSubmittedOrder: (state) => window.sessionStorage.getItem("submittedOrder") !== null,
isVerifiedAndDeductibleZeroConfirmed: (state) => { isVerifiedAndDeductibleZeroConfirmed: (state) => {
// used as a CMS variable on /payment-method, needed for FunnelSubHeaderWidget on cart pages // used as a CMS variable on /payment-method, needed for FunnelSubHeaderWidget on cart pages
if ( if (
@ -900,7 +906,7 @@ export const getters = {
requiresVerifiedRedirecting: (state) => { requiresVerifiedRedirecting: (state) => {
return state.order?.referralNumber?.length === 6; return state.order?.referralNumber?.length === 6;
}, },
isExternalParameter: (state) => externalParameterState?.isExternalParameter, externalParameterState: (state) => externalParameterState,
externalParameterVehicle: (state) => externalParameterState?.vehicle, externalParameterVehicle: (state) => externalParameterState?.vehicle,
externalParameterDamage: (state) => externalParameterState?.vehicleDamage, externalParameterDamage: (state) => externalParameterState?.vehicleDamage,
externalParameterQuote: (state) => externalParameterState?.quote, externalParameterQuote: (state) => externalParameterState?.quote,
@ -1883,6 +1889,7 @@ export const actions = {
}, },
isInsurance: order.payment.isInsurance, isInsurance: order.payment.isInsurance,
parentAccountNumber: order.payment.parentAccountNumber, parentAccountNumber: order.payment.parentAccountNumber,
billToAccountNumber: order.payment.billToAccountNumber,
inactivePromos: order.payment.inactivePromos, inactivePromos: order.payment.inactivePromos,
isCreditCard: isCreditCard:
order.payment.piaType == paymentMethods.CREDIT_CARD ? true : false, order.payment.piaType == paymentMethods.CREDIT_CARD ? true : false,
@ -2415,14 +2422,6 @@ export const actions = {
context, context,
{ payload: { availableLineItems, serviceZipCode, serviceZipCodeCtu }, pageNameToLog } { payload: { availableLineItems, serviceZipCode, serviceZipCodeCtu }, pageNameToLog }
) { ) {
const zipCodeToUse = serviceZipCode
? serviceZipCode
: context.getters.order.serviceLocation.zipCode;
const ctuToUse = serviceZipCodeCtu
? serviceZipCodeCtu
: context.getters.order.serviceLocation.zipCodeCtu;
const flattenedLineItemsWithChildParts = const flattenedLineItemsWithChildParts =
getFlattenedArrayOfLineItemsWithChildParts(availableLineItems); getFlattenedArrayOfLineItemsWithChildParts(availableLineItems);
@ -2430,34 +2429,40 @@ export const actions = {
partNumber: lineItem.partNumber, partNumber: lineItem.partNumber,
})); }));
const availableLineItemsFormattedForRequest = const order = context.getters.order;
buildQueryStringParameterFromArrayOfComplexObjects(
lineItemsWithOnlyPartNumbers,
"lineItems"
);
const vehicle = context.getters.order.vehicle; var providerNumber =
order.serviceLocation?.provider?.providerNumber ?? order.serviceLocation?.zipCodeCtu;
// TODO: Insurance pricing...`ParentAccountNumber=${context.getters.order.payment.parentAccountNumber}` if (providerNumber.startsWith("00") && providerNumber.length > 5) {
let queryString = providerNumber = providerNumber.substring(1);
`ParentAccountNumber=${context.getters.order.payment.parentAccountNumber}` +
`&CTU=${ctuToUse}` +
`&CarId=${vehicle.carId}` +
`&Make=${vehicle.make}` +
`&Model=${vehicle.model}` +
`&Year=${vehicle.year}` +
`&EON=${context.getters.order.eon}` +
`&ZipCode=${zipCodeToUse}` +
`&${availableLineItemsFormattedForRequest}`;
const lineItemServerData = context.getters.order.lineItems.serverData;
if (lineItemServerData) {
queryString += `&ServerData=${encodeURIComponent(lineItemServerData)}`;
} }
const response = await globalMethods.callHttpClient({ const response = await globalMethods.callHttpClient({
method: endpoints.PriceOrderItems.method, method: endpoints.PriceOrderItems.method,
endpoint: `${endpoints.PriceOrderItems.url}?${queryString}`, endpoint: endpoints.PriceOrderItems.url,
payload: {
parentAccountNumber: order.payment?.parentAccountNumber,
billToAccountNumber:
order.payment?.billToAccountNumber ??
applicationConfig.CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER,
providerNumber: providerNumber,
carId: order.vehicle.carId,
year: order.vehicle.year,
make: order.vehicle.make,
model: order.vehicle.model,
eon: order.eon,
state: order.serviceLocation?.state,
referralNumber: order.referralNumber,
referralSequenceNumber: order.referralSequenceNumber,
zipCode: order.serviceLocation?.zipCode,
serviceZipCode: serviceZipCode ?? order.serviceLocation?.zipCode,
referralDate: order.referralDate,
isReplacement: !order.damage.isRepair,
deductible: order.policy?.currentDeductible,
coverageStatus: order.payment?.insuranceCoverage?.coverageStatus,
lineItems: lineItemsWithOnlyPartNumbers,
serverData: order.lineItems.serverData ? order.lineItems.serverData : "",
},
logApiCall: true, logApiCall: true,
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
}); });
@ -2674,6 +2679,10 @@ export const actions = {
return revalidateResponse?.data; return revalidateResponse?.data;
}, },
///////////////////////////
// Account Service Calls //
///////////////////////////
// List all insurance companies // List all insurance companies
async getInsuranceCompanyList(context, { pageNameToLog }) { async getInsuranceCompanyList(context, { pageNameToLog }) {
const insuranceCompanyList = await globalMethods.callHttpClient({ const insuranceCompanyList = await globalMethods.callHttpClient({
@ -2696,6 +2705,40 @@ export const actions = {
return 0; return 0;
}); });
}, },
// Get BillToAccountNumber
async getBillToAccountNumber(
context,
{
payload: {
parentAccountNumber = context.getters.order.payment.parentAccountNumber,
providerNumber = context.getters.order.serviceLocation.zipCodeCtu,
isItac = context.getters.order.policy.isItac,
},
pageNameToLog,
}
) {
// Cash orders default to 87291 - update if/when more detailed
// billToAccountNumbers are needed for cash
if (parentAccountNumber == applicationConfig.CASH_PARENT_ACCOUNT_NUMBER) {
return applicationConfig.CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER;
}
const queryString =
`ParentAccountNumber=${parentAccountNumber}` +
`&ProviderNumber=${providerNumber}` +
`&IsItac=${isItac}`;
const billToAccountNumberResponse = await globalMethods.callHttpClient({
method: endpoints.GetBillToAccountNumber.method,
endpoint: `${endpoints.GetBillToAccountNumber.url}?${queryString}`,
logApiCall: true,
pageNameToLog: pageNameToLog,
});
return billToAccountNumberResponse.data.toString();
},
//////////////////////////////////
// END OF Account Service Calls //
//////////////////////////////////
// Misc order actions // Misc order actions
saveSchedule(context, scheduleInfo) { saveSchedule(context, scheduleInfo) {
@ -2798,7 +2841,7 @@ export const actions = {
}, },
createSubmittedOrder(context) { createSubmittedOrder(context) {
if (context.getters.hasSubmittedOrder) { if (window.sessionStorage.getItem("submittedOrder") !== null) {
return; return;
} }
@ -3368,6 +3411,7 @@ function createExternalParameterDefaultState() {
isInsurance: null, isInsurance: null,
servicePackage: null, servicePackage: null,
}, },
source: null,
}; };
// set to session storage // set to session storage
saveExternalParameterState(externalParameterDefaultState); saveExternalParameterState(externalParameterDefaultState);