Merge branch 'develop' into feature/CSR-2199

This commit is contained in:
AMSNEHA 2024-11-04 12:04:55 +05:30 committed by GitHub
commit c56c1338f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 983 additions and 338 deletions

View file

@ -84,6 +84,10 @@ const endpoints = {
url: "/parts/api/v1/parts/supporting-items",
method: "POST",
},
GetRecalPart: {
url: "/parts/api/v1/parts/recal-parts",
method: "GET",
},
GetAlertReasons: {
url: "/location/api/v1/location/alert-reasons",
method: "GET",

View file

@ -12,6 +12,7 @@ const experimentSettings = {
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
IS_EMAIL_OPTIONAL: "isEmailOptional",
SERVICE_PACKAGE_DISCOUNT: "OfferServicePackageDiscount",
PROMO_ON_PACKAGE: "Offer_Promo_On_Pkg",
RECAL_PRICE_REMOVE: "RecalPriceRemove",
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold",
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold",

View file

@ -0,0 +1,6 @@
const externalParameterStatus = {
NOT_SET: null,
ACTIVE: 1,
INACTIVE: 0,
};
export { externalParameterStatus };

View file

@ -29,6 +29,19 @@ export function coverageStatusValue(intCoverageStatus) {
}
}
export function coverageStatusEnum(strCoverageStatus) {
switch (strCoverageStatus) {
case coverageStatus.PENDING:
return 0;
case coverageStatus.NOCOMP:
return 1;
case coverageStatus.VERIFIED:
return 2;
default:
return null;
}
}
export const coverageType = {
NONE: "None",
DEDUCTIBLE: "Deductible",
@ -50,3 +63,18 @@ export function coverageTypeValue(intCoverageType) {
return null;
}
}
export function coverageTypeEnum(strCoverageType) {
switch (strCoverageType) {
case coverageType.NONE:
return 0;
case coverageType.DEDUCTIBLE:
return 1;
case coverageType.ITAC:
return 2;
case coverageType.NOCOMP:
return 3;
default:
return null;
}
}

View file

@ -2,6 +2,7 @@ const partTypeStrings = {
FRONT_WIPER: "FRONT WIPER",
REAR_WIPER: "REAR WIPER",
RAIN_DEFENSE: "RAIN DEFENSE",
ADAS_RECALIBRATION: "ADAS RECALIBRATION",
RECALIBRATION: "RECALIBRATION",
REPLACE_FEE: "REPLACE FEE",
RECYCLE_FEE: "RECYCLE FEE",

View file

@ -54,8 +54,10 @@ const storeActions = {
REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA: "revalidateOrderPromosAndSaveServerData",
GET_INSURANCE_COMPANY_LIST: "getInsuranceCompanyList",
GET_BILL_TO_ACCOUNT_NUMBER: "getBillToAccountNumber",
GET_RECAL_PARTS_AND_SAVE_TO_LINE_ITEMS: "getRecalPartsAndSaveToLineItems",
// DEPENDENCY MUTATIONS
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleStateAndDependencies",
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
RESET_REGISTRATION_STATE_AND_DEPENDENCIES: "resetRegistrationAndDependencies",
RESET_PARTS_STATE_AND_DEPENDENCIES: "resetPartsAndDependencies",

View file

@ -602,6 +602,24 @@ function setupMocks({
if (props) resultingMountOptions.propsData = props;
if (isShallowMount) {
resultingMountOptions.global.stubs = {
...(resultingMountOptions.global.stubs ?? {}),
textboxQuestion: {
template: `<span></span>`,
methods: {
handleChange: jest.fn(),
},
},
dropdownQuestion: {
template: `<span></span>`,
methods: {
handleChange: jest.fn(),
},
},
};
}
const wrapper = isShallowMount
? shallowMount(addressQuestions, resultingMountOptions)
: mount(addressQuestions, resultingMountOptions);

View file

@ -371,6 +371,12 @@ export default {
self.addressModel.state = state;
self.addressModel.zipCode = zipCode;
this.$refs.autocomplete.handleChange(streetNumber);
this.$refs.apartmentNumberOrBusinessName.handleChange(route);
this.$refs.city.handleChange(city);
this.$refs.state.handleChange(state);
this.$refs.zipCode.handleChange(zipCode);
if (!this.displayVerificationWarning) {
this.displayVerificationWarning = place.partial_match || !isAddressComplete;
}

View file

@ -110,7 +110,7 @@
<span>{{ subtotalText }}</span>
<span>{{ getLineItemAmount(subTotal, showCoverageAsPending) }}</span>
</div>
<div class="sales-tax">
<div class="sales-tax" v-show="showSalesTax">
<span>{{ salesTaxText }}</span>
<span>{{ getLineItemAmount(salesTax) }}</span>
</div>
@ -188,6 +188,8 @@ export default {
insuranceCompanyName: String,
showInsuranceCoverageAs: String,
shouldHideRecalibration: Boolean,
isItac: Boolean,
isNoComp: Boolean,
},
data() {
return {
@ -305,8 +307,8 @@ export default {
if (!this.lineItems || this.lineItems.length < 1) return;
const lineItemsCopy = deepClone(this.lineItems);
lineItemsCopy.supportingItems = lineItemsCopy.supportingItems
? baseMixin.methods.filterOutRecalibration(lineItemsCopy?.supportingItems)
lineItemsCopy.glassParts = lineItemsCopy.glassParts
? baseMixin.methods.filterOutRecalibration(lineItemsCopy?.glassParts)
: [];
return lineItemsCopy;
},
@ -316,6 +318,10 @@ export default {
showCoverageAsVerified() {
return this.showInsuranceCoverageAs === coverageStatus.VERIFIED;
},
showSalesTax() {
const tax = parseFloat(this.salesTax).toFixed(2);
return tax > 0;
},
currentDeductible() {
const deductible = this.insuranceDeductible;
if (!(deductible === 0 || deductible > 0)) {
@ -1007,6 +1013,16 @@ export default {
},
salesTax() {
if (!this.lineItems || this.lineItems.length < 1) return;
if (
this.isInsurance &&
!this.showCoverageAsVerified &&
!this.isItac &&
!this.isNoComp
) {
return 0;
}
return this.isInsurance || !this.shouldHideRecalibration
? baseMixin.methods.getSalesTax(this.lineItems) // calculate with recal (if on order)
: baseMixin.methods.getSalesTax(this.lineItemsWithoutRecal); // calculated without recal

View file

@ -53,7 +53,7 @@ export default {
return this.leftAlignHeader ? "justify-content-start" : "justify-content-center";
},
subText() {
return this.getCmsContent(this.cmsWidgetName, "HeaderSubText");
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
},
backButtonAccessibleText() {
return this.getCmsContent(this.cmsWidgetName, "BackButtonAccessibleText");

View file

@ -6,6 +6,7 @@ import store from "@/store";
import { applicationConfig } from "@/constants/application-config.js";
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
import { getSessionKeyValue } from "@/helpers/heritage-integration/cookie-helper";
import { headerKeys } from "@/constants/header-keys";
import axiosResponseInterceptorMessages from "@/constants/axios-response-interceptor-messages.js";
@ -65,7 +66,7 @@ export default {
const headers = {
[headerKeys.EXPERIMENT]: JSON.stringify(store.getters.experimentSettings),
[headerKeys.APPLICATION_NAME]: applicationConfig.APPLICATION_NAME,
[headerKeys.SESSION_SEQUENCE_NUMBER]: store.getters.applicationUser?.savedSessionId,
[headerKeys.SESSION_SEQUENCE_NUMBER]: getSessionKeyValue(),
[headerKeys.REFERRAL_SEQUENCE_NUMBER]: order?.referralSequenceNumber,
[headerKeys.TRANSACTION_ID]: crypto.randomUUID(),
[headerKeys.EON]: order?.eon,

View file

@ -109,8 +109,10 @@ export async function getImplicitNavigation(toRoute) {
export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLog }) {
const log = getQuerystringParameter(queryStrings.LOG);
if (eval(log)) {
console.log("------------- Navigate to heritage start -----------------");
console.log(new Date() + " applicationConfig: " + JSON.stringify(applicationConfig));
console.log(new Date() + " navigateToHeritageFunnel: " + applicationConfig.HERITAGE_FUNNEL);
console.log("------------- Navigate to heritage end -----------------");
}
// Create the order (or save existing order) when navigating to Heritage Funnel.
@ -132,6 +134,8 @@ export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLo
}
if (
(process.env.VUE_APP_HERITAGE_FUNNEL.includes("fixmyglasstest") &&
process.env.VUE_APP_CURRENT_ENVIRONMENT === "Localhost") ||
(process.env.VUE_APP_HERITAGE_FUNNEL.includes("fixmyglassdev") &&
process.env.VUE_APP_CURRENT_ENVIRONMENT === "Localhost") ||
(process.env.VUE_APP_HERITAGE_FUNNEL.includes("localhost") &&

View file

@ -0,0 +1,60 @@
import { deepClone } from "@/helpers/object-helper";
import { partTypeStrings } from "@/constants/part-type-strings";
const recalPartTypes = [partTypeStrings.RECALIBRATION, partTypeStrings.ADAS_RECALIBRATION];
export function isRecalPartOrHasChildRecalPart(lineItem) {
if (lineItem.childParts && lineItem.childParts.length > 0) {
return isRecalPart(lineItem) || containsRecalParts(lineItem.childParts);
} else {
return isRecalPart(lineItem);
}
}
export function isRecalPart(lineItem) {
return recalPartTypes.some((type) => lineItem.partType === type);
}
export function containsRecalParts(lineItems) {
if (!lineItems) {
return false;
}
if (Array.isArray(lineItems)) {
return lineItems.some((li) => isRecalPartOrHasChildRecalPart(li));
} else {
// complex object form -- flatten and re-call.
const flattened = [
...(lineItems.glassParts ?? []),
...(lineItems.supportingItems ?? []),
...(lineItems.vaps ?? []),
...(lineItems.promos ?? []),
];
return flattened.some((li) => isRecalPartOrHasChildRecalPart(li));
}
}
export function getItemsWithoutRecalParts(lineItems) {
const copy = deepClone(lineItems);
const firstLevelFiltered = copy.filter((li) => !isRecalPart(li));
const childrenFiltered = firstLevelFiltered.map((li) => {
if (li.childParts && li.childParts.length > 0) {
li.childParts = getItemsWithoutRecalParts(li.childParts);
}
return li;
});
return childrenFiltered;
}
export function getTopLevelPartsWithRecal(lineItems) {
if (!lineItems) {
return null;
}
return lineItems.filter((li) => isRecalPartOrHasChildRecalPart(li));
}

View file

@ -239,6 +239,15 @@ export function getPackageNameByType(packageType) {
return package_names[packageType] || null;
}
export function getDiscountPackageName(discountPackage) {
const package_names = {
ECON: packageNames.TIER_ONE,
STANDARD: packageNames.TIER_TWO,
PREMIUM: packageNames.TIER_THREE,
};
return package_names[discountPackage] || null;
}
function maxTier(tierA, tierB) {
if (tierA === packageNames.TIER_THREE || tierB === packageNames.TIER_THREE) {
return packageNames.TIER_THREE;

View file

@ -375,6 +375,7 @@ export default {
},
false
);
await this.getAndSaveBillToAccountNumber();
return await this.navigateForward(carsFound);
},

View file

@ -24,6 +24,7 @@ import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
@ -52,9 +53,20 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent);
if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: store.getters.externalParameterServiceZip.zipCode,
state: null,
zipCodeCtu: null,
},
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
});

View file

@ -237,7 +237,7 @@ describe("computed properties...", () => {
const testValue = wrapper.vm.AppointmentWordingText;
// Assert
expect(testValue).toEqual("wording Text 123, test,<br/> test, AZ 12345");
expect(testValue).toEqual("wording Text test, 123,<br/> test, AZ 12345");
});
test("AppointmentWordingText should return DROP_OFF text in expected format.", () => {
//Arrange
@ -419,7 +419,7 @@ describe("computed properties...", () => {
const testValue = wrapper.vm.ServiceLocationFullAddress;
// Assert
expect(testValue).toEqual("123, test,<br/> test, AZ 12345");
expect(testValue).toEqual("test, 123,<br/> test, AZ 12345");
});
test("ProviderFullAddress should return text in expected format.", () => {
//Arrange

View file

@ -60,10 +60,13 @@
v-model="lineItems"
:showAsPaid="isPia"
servicePackageOptionsCmsName="ServicePackageTitle"
:isInsurance="isInsurance"
:insuranceDeductible="currentDeductible"
:insuranceCompanyName="insuranceCompanyName"
:showInsuranceCoverageAs="showInsuranceCoverageAs"
:shouldHideRecalibration="shouldHideRecalibration" />
:shouldHideRecalibration="shouldHideRecalibration"
:isItac="isItac"
:isNoComp="isNoComp" />
<hr class="mb-5" />
@ -108,6 +111,7 @@ import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helpe
import { experimentSettings } from "@/constants/experiments";
import { partTypeStrings } from "@/constants/part-type-strings";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper.js";
export default {
name: "confirmation",
@ -170,10 +174,7 @@ export default {
},
computed: {
isRecalibrationOnOrder() {
return containsLineItemWithPartType(
partTypeStrings.RECALIBRATION,
this?.lineItems?.supportingItems
);
return containsRecalParts(this?.lineItems);
},
shouldHideRecalibration() {
return (
@ -313,11 +314,7 @@ export default {
}
},
ServiceLocationFullAddress() {
return `${this.ServiceLocationAddress2 ? this.ServiceLocationAddress2 + "," : ""} ${
this.ServiceLocationAddress
},<br/> ${this.ServiceLocationCity}, ${this.ServiceLocationState} ${
this.ServiceLocationZipCode
}`;
return `${this.ServiceLocationAddress}${this.ServiceLocationAddress2 ? ", " + this.ServiceLocationAddress2 : ""},<br/> ${this.ServiceLocationCity}, ${this.ServiceLocationState} ${this.ServiceLocationZipCode}`;
},
ProviderFullAddress() {
return `${this.ProviderAddress},<br/> ${this.ProviderCity}, ${this.ProviderState} ${this.ProviderZipCode}`;

View file

@ -74,6 +74,10 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
//Reload page if serve from bfcache.
if (performance.getEntriesByType("navigation")[0].type === "back_forward") {
vm.handlePageShow();
}
vm.setCmsContent(resultMap.cmsContent);
vm.originalList = resultMap.insuranceCompanyList;
if (store.getters.externalParameterState?.isExternalParameter) {
@ -93,6 +97,9 @@ export default {
},
},
methods: {
handlePageShow() {
window.location.reload();
},
isCashParentAccountNumber() {
return (
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER.toString() ===
@ -121,6 +128,20 @@ export default {
this.parentAccountNumber.toString(),
false
);
const billToAccountNumber = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_BILL_TO_ACCOUNT_NUMBER,
{},
"insurance-company",
false
);
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_BILL_TO_ACCOUNT_NUMBER,
billToAccountNumber,
false
);
navigateToHeritageFunnel({
shouldSaveSession: true,
pageNameToLog: "insurance-company",

View file

@ -323,6 +323,7 @@ export default {
false
);
await this.getAndSaveBillToAccountNumber();
return await this.navigateForward();
},
lookupVin(plate, state) {

View file

@ -24,6 +24,7 @@ import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
@ -52,9 +53,21 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent);
if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: store.getters.externalParameterServiceZip.zipCode,
state: null,
zipCodeCtu: null,
},
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
});

View file

@ -24,6 +24,7 @@ import questionsPageLayout from "@/fmg-components/layouts/questions-page-layout/
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
@ -52,9 +53,22 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent);
if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) {
const serviceState = store.getters.order.serviceLocation.state;
const serviceZipCtu = store.getters.order.serviceLocation.zipCodeCtu;
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: store.getters.externalParameterServiceZip.zipCode,
state: serviceState ?? null,
zipCodeCtu: serviceZipCtu ?? null,
},
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
});

View file

@ -34,7 +34,9 @@
:insuranceDeductible="currentDeductible"
:insuranceCompanyName="insuranceCompanyName"
:showInsuranceCoverageAs="showInsuranceCoverageAs"
:shouldHideRecalibration="shouldHideRecalibration" />
:shouldHideRecalibration="shouldHideRecalibration"
:isItac="isItac"
:isNoComp="isNoComp" />
<hr class="my-5" />
@ -76,7 +78,7 @@
validationRules="payment-method-required" />
<alert
v-if="!showPaymentMethodQuestions && !forwardClicked"
v-if="showHavePaymentReadyAlert"
:isDismissible="false"
alertClass="alert-info"
cmsWidgetName="NoPiaDisclaimerWidget"
@ -153,6 +155,7 @@ import { partTypeStrings } from "@/constants/part-type-strings";
import { mapTaxedLineItemsToStoreFormat } from "../../store";
import { coverageStatus } from "@/constants/insurance";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper";
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
@ -586,10 +589,11 @@ export default {
},
computed: {
isRecalibrationOnOrder() {
return containsLineItemWithPartType(
partTypeStrings.RECALIBRATION,
this.$store.getters.order.lineItems?.supportingItems
);
const order = baseMixin.methods.hasSubmittedOrder()
? baseMixin.methods.getSubmittedOrder()
: this.$store.getters.order;
return containsRecalParts(order.lineItems);
},
shouldHideRecalibration() {
return (
@ -626,13 +630,16 @@ export default {
},
showInsuranceCoverageAs() {
if (!this.isInsurance) return null;
if (this.isNoComp || this.isItac) {
return coverageStatus.NOCOMP;
} else {
return this.hasSubmittedOrder()
? this.getSubmittedOrder()?.payment.insuranceCoverage.coverageStatus
: this.$store.getters.payment.insuranceCoverage.coverageStatus;
}
if (this.$store.getters.coverageIsVerified) {
return coverageStatus.VERIFIED;
}
return coverageStatus.PENDING;
},
currentDeductible() {
return this.hasSubmittedOrder()
@ -674,6 +681,11 @@ export default {
return false;
},
showHavePaymentReadyAlert() {
return (
!this.showPaymentMethodQuestions && !this.forwardClicked && this.totalAmountDue > 0
);
},
totalAmountDue() {
return baseMixin.methods.getAmountDue(this.lineItems);
},

View file

@ -57,7 +57,9 @@
recyclingModalCmsWidgetName="RecycleModal"
:insuranceDeductible="currentDeductible"
:insuranceCompanyName="insuranceCompanyName"
:showInsuranceCoverageAs="showInsuranceCoverageAs" />
:showInsuranceCoverageAs="showInsuranceCoverageAs"
:isItac="isItac"
:isNocomp="isNocomp" />
<hr class="mb-5" />

View file

@ -30,7 +30,7 @@
import { splitCopyOnCMSPlaceHolder } from "@/helpers/cms-content-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { getPromosThatMatchLineItemsOnOrder } from "@/helpers/promotions-helper";
import { getArrayOfAllLineItems } from "@/store";
import { getArrayOfAllLineItemsAndChildParts } from "@/store";
const INLINE_IMAGE_TOKEN = "custom:inlineImage";
const AFTERPAY_PRICE_TOKEN = "custom:afterpayPrice";
@ -76,7 +76,7 @@ export default {
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
},
afterpayPrice() {
let allLineItems = getArrayOfAllLineItems(this.lineItems);
let allLineItems = getArrayOfAllLineItemsAndChildParts(this.lineItems);
if (this.lineItems.promos) {
allLineItems = allLineItems?.filter((item) => item.partType !== "PROMO_DISCOUNT");
}
@ -91,7 +91,7 @@ export default {
});
if (this.lineItems.promos) {
let allLineItems = getArrayOfAllLineItems(this.lineItems);
let allLineItems = getArrayOfAllLineItemsAndChildParts(this.lineItems);
const promos = getPromosThatMatchLineItemsOnOrder(
this.lineItems.promos,
allLineItems

View file

@ -65,6 +65,28 @@ jest.mock("@/mixins/base-mixin", () => ({
},
}));
const mockExperimentSettings = experimentSettings;
jest.mock("@/mixins/experiment-mixin.js", () => ({
methods: {
getSettingValue(settingName) {
if (settingName === mockExperimentSettings.SERVICE_PACKAGE_DISCOUNT) {
return true;
}
if (
settingName === mockExperimentSettings.INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL
) {
return 300;
} else if (
settingName === mockExperimentSettings.INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL
) {
return 500;
}
return "test";
},
},
}));
const mockMixin = {
methods: {
filterOutFees: jest.fn().mockImplementation(() => {
@ -73,12 +95,6 @@ const mockMixin = {
isFormValid: jest.fn().mockImplementation(() => {
return true;
}),
getSettingValue: jest.fn((settingName) => {
if (settingName === experimentSettings.SERVICE_PACKAGE_DISCOUNT) {
return true;
}
return false;
}),
},
};
let mockTierOnePrice = 501;
@ -309,7 +325,7 @@ describe("quote.vue", () => {
wrapper.vm.$route = { query: { isInsurance: "false" } };
const isServicePackageDiscount = mockMixin.methods.getSettingValue(
const isServicePackageDiscount = experimentMixin.methods.getSettingValue(
experimentSettings.SERVICE_PACKAGE_DISCOUNT
);
@ -364,7 +380,7 @@ describe("quote.vue", () => {
//Assert
expect(wrapper.vm.isInsuranceSelected).toBe(true);
});
test("should default to cash if query param 'isInsurance' is false", async () => {
test("should use amount threshold if query param 'isInsurance' is false", async () => {
//Arrange
store.getters = {
lineItems: {
@ -391,6 +407,7 @@ describe("quote.vue", () => {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 200;
const { wrapper } = setupMocks({});
//Act
@ -701,7 +718,7 @@ describe("quote.vue", () => {
inactivePromos: [],
},
},
externalParameterState: { isExternalParameter: true },
externalParameterState: { isExternalParameter: 1 },
externalParameterQuote: {
isInsurance: true,
servicePackage: "glassonly",
@ -758,8 +775,6 @@ function setupMocks({ customMountOptions }) {
baseMixin.methods.ResetExternalParamsAndHideModal = jest.fn();
baseMixin.methods.isFormValid = jest.fn().mockReturnValue(true);
mountOptions.global.mocks["$store"] = store;
store.getters.experimentSettings = "test value";
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(quote, mountOptions);

View file

@ -59,7 +59,7 @@
alertClass="alert-info" />
<promoModalQuestion
class="small mt-6 d-md-flex justify-content-md-center"
class="small mt-6 d-flex justify-content-center"
v-model="lineItems"
:addableVaps="addableVaps"
pageName="quote"
@ -69,9 +69,9 @@
<textBlock
cmsWidgetName="quoteDisclaimer"
justifyText="left"
justifyText="center"
typeStyle="caption"
class="mt-6" />
class="mt-6 quote-disclaimer" />
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
@ -135,6 +135,9 @@ import {
import { nextTick } from "vue";
import { packageNames } from "@/constants/package-names";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { containsRecalParts } from "@/helpers/recal-helper";
import { externalParameterStatus } from "@/constants/external-parameters";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
const INSURANCE_TAB_TO_DISPLAY_THRESHOLD_DEFAULT = 300;
@ -296,8 +299,9 @@ export default {
const getIsInsuranceSelectedValue = (availableLineItems, insuranceThreshold) => {
const serviceLocationState = store.getters.order.serviceLocation.state;
// Override if coming back from QuoteDetails. Remove override after Quote release
const isInsuranceOverrideValue = to.query?.isInsurance;
// usually true when returning from heritage but can be false when returning from heritage on a save quote
const isInsuranceFromQueryString = to.query?.isInsurance || to.query?.isinsurance;
const defaultIsInsuranceSelectedValue = store.getters.order.payment.isInsurance;
const hideRecalCost =
experimentMixin.methods
@ -308,72 +312,88 @@ export default {
if (
serviceLocationState != null &&
payWithInsuranceStates.find((item) => item === serviceLocationState)
)
) {
return true;
else if (isInsuranceOverrideValue != null) {
return isInsuranceOverrideValue == "true";
} else if (defaultIsInsuranceSelectedValue != null) {
return defaultIsInsuranceSelectedValue;
} else {
if (availableLineItems) {
const lineItemsForCalculatingPrice = hideRecalCost
? baseMixin.methods.filterOutFees(
baseMixin.methods.filterOutRecalibration(availableLineItems) // Strip out recal before filtering out fees
)
: baseMixin.methods.filterOutFees(availableLineItems);
return (
baseMixin.methods.getTierOnePackagePrice(lineItemsForCalculatingPrice) >
insuranceThreshold
); // compare base price vs arbitrary threshold (representing insurance price)
} else {
return null;
}
}
if (defaultIsInsuranceSelectedValue != null) {
return defaultIsInsuranceSelectedValue;
}
if (availableLineItems) {
const lineItemsForCalculatingPrice = hideRecalCost
? baseMixin.methods.filterOutFees(
baseMixin.methods.filterOutRecalibration(availableLineItems) // Strip out recal before filtering out fees
)
: baseMixin.methods.filterOutFees(availableLineItems);
if (isInsuranceFromQueryString != null) {
if (isInsuranceFromQueryString.toLowerCase() === "true") {
return true;
}
}
return (
baseMixin.methods.getTierOnePackagePrice(lineItemsForCalculatingPrice) >
insuranceThreshold
); // compare base price vs arbitrary threshold (representing insurance price)
}
return null;
};
let thresholdToUse = INSURANCE_TAB_TO_DISPLAY_THRESHOLD_DEFAULT;
let internalThreshold = experimentMixin.methods.getSettingValue(
experimentSettings.INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL
);
let externalThreshold = experimentMixin.methods.getSettingValue(
experimentSettings.INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL
);
const isExternalParameter = store.getters.externalParameterState?.isExternalParameter;
if (!store.getters.externalParameterState?.isExternalParameter) {
// there are no external parameters
// prettier-ignore
{
const log = getQuerystringParameter(queryStrings.LOG);
if (eval(log)) {
console.log("------------- quote.vue thresholds start -----------------");
console.log(new Date() + " isExternalParameter: " + isExternalParameter);
console.log(new Date() + " internalThreshold: " + internalThreshold);
console.log(new Date() + " externalThreshold: " + externalThreshold);
console.log("------------- quote.vue thresholds end -----------------");
}
}
if (isExternalParameter === externalParameterStatus.NOT_SET) {
// there are no active or inactive external parameters; use internal threshold
if (internalThreshold) thresholdToUse = internalThreshold;
vm.isInsuranceSelected = getIsInsuranceSelectedValue(
vm.availableLineItems,
thresholdToUse
);
baseMixin.methods.ResetExternalParamsAndHideModal();
} else {
// there ARE external parameters
if (store.getters.externalParameterQuote.isInsurance == true) {
// user came from an external source, however, the externalParms may have been reset on service-zip, property-questions, etc...
if (eval(store.getters.externalParameterQuote?.isInsurance)) {
// did user intentionally select insurance?
vm.isInsuranceSelected = true;
vm.servicePackage = store.getters.externalParameterQuote.servicePackage;
await nextTick();
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
if (isValid) {
vm.forwardButtonAction();
} else {
baseMixin.methods.ResetExternalParamsAndHideModal();
}
} else {
// did user come from external source (LeadGen)?
let externalSource = store.getters.externalParameterSource
? store.getters.externalParameterSource
: null;
// Business logic to determine what "external" source is
if (externalSource?.includes("LeadGen")) {
const externalThreshold = experimentMixin.methods.getSettingValue(
experimentSettings.INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL
);
if (externalThreshold) thresholdToUse = externalThreshold;
} else {
if (internalThreshold) thresholdToUse = internalThreshold;
if (
vm.servicePackage != null &&
isExternalParameter === externalParameterStatus.ACTIVE
) {
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
if (isValid) {
baseMixin.methods.ResetExternalParamsAndHideModal();
vm.forwardButtonAction();
}
}
baseMixin.methods.ResetExternalParamsAndHideModal();
} else {
if (externalThreshold) thresholdToUse = externalThreshold;
vm.isInsuranceSelected = getIsInsuranceSelectedValue(
vm.availableLineItems,
thresholdToUse
@ -399,10 +419,7 @@ export default {
return Object.assign({}, this.lineItems);
},
isRecalibrationOnOrder() {
return containsLineItemWithPartType(
partTypeStrings.RECALIBRATION,
this?.availableLineItems
);
return store.getters.isRecalibrationOnOrder;
},
isServicePackageDiscountOnOrder() {
return containsLineItemWithPartType(
@ -417,10 +434,7 @@ export default {
);
},
shouldHideRecalibration() {
return (
this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)?.toLowerCase() ===
"true" && this.isRecalibrationOnOrder
);
return store.getters.shouldHideRecalibration;
},
showAfterpayBanner() {
return (
@ -438,35 +452,21 @@ export default {
},
arePagePrerequisitesValid() {
const payment = store.getters.order.payment;
const log = getQuerystringParameter(queryStrings.LOG);
if (eval(log)) {
console.log("quote.vue pagePrereqs:");
console.log(
new Date() +
" store.getters.order.serviceLocation.zipCode: " +
store.getters.order.serviceLocation?.zipCode
);
console.log(
new Date() +
" store.getters.order.serviceLocation.zipCodeCtu: " +
store.getters.order.serviceLocation?.zipCodeCtu
);
console.log(
new Date() +
" store.getters.order.damage.isRepair: " +
store.getters.order.damage?.isRepair
);
console.log(
new Date() +
" store.getters.order.lineItems?.glassParts: " +
JSON.stringify(store.getters.order.lineItems?.glassParts)
);
console.log(
new Date() +
" store.getters.order.payment.insuranceCoverage?.isVerified: " +
payment?.insuranceCoverage?.isVerified
);
// prettier-ignore
{
const log = getQuerystringParameter(queryStrings.LOG);
if (eval(log)) {
console.log("------------- quote.vue pagePrereqs start -----------------");
console.log(new Date() + " store.getters.order.serviceLocation.zipCode: " + store.getters.order.serviceLocation?.zipCode);
console.log(new Date() +" store.getters.order.serviceLocation.zipCodeCtu: " + store.getters.order.serviceLocation?.zipCodeCtu);
console.log(new Date() + " store.getters.order.damage.isRepair: " + store.getters.order.damage?.isRepair);
console.log(new Date() + " store.getters.order.lineItems?.glassParts: " + JSON.stringify(store.getters.order.lineItems?.glassParts));
console.log(new Date() + " store.getters.order.payment.insuranceCoverage?.isVerified: " + payment?.insuranceCoverage?.isVerified);
console.log("------------- quote.vue pagePrereqs end -----------------");
}
}
return (
store.getters.order.serviceLocation.zipCode &&
store.getters.order.serviceLocation.zipCodeCtu &&
@ -579,15 +579,14 @@ export default {
}
});
this.$nextTick(() => {
const availablePackageNames = this.$refs.servicePackage.servicePackageAnswers;
const availablePackageNames = this.$refs.servicePackage?.servicePackageAnswers;
var eventLabel = "";
availablePackageNames?.forEach((tier) => {
if (tier) {
let lineItemsToPrice = this.availableLineItems;
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
lineItemsToPrice = lineItemsToPrice?.filter((item) => {
return item.partType != partTypeStrings.RECALIBRATION;
});
lineItemsToPrice =
baseMixin.methods.filterOutRecalibration(lineItemsToPrice);
}
if (this.isServicePackageDiscountOnOrder) {
lineItemsToPrice = lineItemsToPrice?.filter((item) => {
@ -611,12 +610,7 @@ export default {
});
eventLabel = eventLabel.slice(0, -1);
this.pushEventToGA(
"quote",
this.GaActions.SERVICE_PACKAGE_PRICE,
eventLabel,
true
);
this.pushEventToGA("quote", this.GaActions.SERVICE_PACKAGE_PRICE, eventLabel, true);
});
},
},

View file

@ -31,7 +31,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -40,7 +40,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -48,7 +48,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -58,7 +58,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -67,7 +67,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -75,7 +75,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -85,7 +85,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -94,7 +94,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -102,7 +102,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -112,7 +112,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -121,7 +121,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -129,7 +129,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -139,7 +139,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -148,7 +148,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -156,7 +156,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -166,7 +166,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -175,7 +175,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -183,7 +183,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -193,7 +193,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -202,7 +202,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -210,7 +210,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -220,7 +220,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -229,7 +229,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -237,7 +237,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -247,7 +247,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
@ -256,7 +256,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
@ -264,7 +264,7 @@ export const mockProcessedCmsContent = {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>{if:custom:badCustomValue}DO NOT SHOW{end}New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
"<ul><li>{if:custom:badCustomValue}DO NOT SHOW{end}New replacement windshield</li><li>Expert installation{if:custom:showRecalInServicePackages} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},

View file

@ -10,6 +10,7 @@ import {
containsLineItemWithPartType,
findLineItemsWithPartType,
} from "@/helpers/service-package-helper";
import { experimentSettings } from "@/constants/experiments";
import baseMixin from "@/mixins/base-mixin.js";
import { partTypeStrings } from "@/constants/part-type-strings";
import { nextTick } from "vue";
@ -19,6 +20,18 @@ jest.mock("@/store", () => ({
dispatch: jest.fn(),
}));
const mockExperimentSettings = experimentSettings;
jest.mock("@/mixins/experiment-mixin.js", () => ({
methods: {
getSettingValue(settingName) {
if (settingName === mockExperimentSettings.PROMO_ON_PACKAGE) {
return true;
}
},
},
}));
describe("service-package-question.vue", () => {
beforeEach(async () => {
processedCmsContent = inputQuestionWidgetAnswers;
@ -162,6 +175,19 @@ describe("service-package-question.vue", () => {
wrapper.vm.servicePackageAnswers[0].buttonAuxillaryCopy.includes("As little as")
).toBe(true);
});
it("should show service package discount when insurance is not selected", () => {
// Arrange
mockProps.isInsuranceSelected = false;
const wrapper = setupMocks({});
// Act
// Assert
expect(
wrapper.vm.servicePackageAnswers[0].additionalButtonData.Text.includes(
"Special: Save $"
)
).toBe(true);
});
it("should return [] from nullSafeAvailableLineItems and not error out if availableLineItems is null", () => {
// Arrange
mockProps.availableLineItems = null;
@ -476,6 +502,8 @@ describe("service-package-question.vue, matching business rules for package disp
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
mockProps.isRecalibrationOnOrder = true;
mockProps.shouldHideRecalibration = false;
mockProps.showRecalInServicePackages = true;
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
const wrapper = setupMocks({
mountOptionsMockData: {

View file

@ -28,6 +28,7 @@ import {
containsLineItemWithPartType,
findLineItemsWithPartType,
getPackageNameByType,
getDiscountPackageName,
} from "@/helpers/service-package-helper";
import {
getPromosThatMatchLineItemsOnOrder,
@ -35,6 +36,9 @@ import {
getPromosWithAddableVaps,
getLineItemsThatMatchPromos,
} from "@/helpers/promotions-helper";
import { containsRecalParts, getItemsWithoutRecalParts } from "@/helpers/recal-helper";
import experimentMixin from "@/mixins/experiment-mixin.js";
import { experimentSettings } from "@/constants/experiments";
export default {
name: "servicePackageQuestion",
@ -128,16 +132,22 @@ export default {
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName),
buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName),
//The package supports service-package-discount if it has the text value in CMS
buttonAuxillaryCopy: this.getDiscountedPackagePriceString(
answer.Name,
this.isServicePackageDiscountOnOrder && answer.Text != ""
),
buttonAuxillaryCopy: this.isInsuranceSelected
? this.getDiscountedPackagePriceString(answer.Name, false)
: this.getDiscountedPackagePriceString(
answer.Name,
this.isServicePackageDiscountOnOrder &&
this.discountPackageNames == answer.Name
),
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
additionalButtonData: {
strikeThroughPrice: this.getPackagePriceString(answer.Name),
servicePackageDiscount:
this.isServicePackageDiscountOnOrder && answer.Text != "",
Text: answer.Text + this.getServicePackageDiscountPrice(),
this.isServicePackageDiscountOnOrder &&
this.discountPackageNames == answer.Name,
Text: this.isInsuranceSelected
? false
: "Special: Save $" + this.getServicePackageDiscountPrice(),
},
}));
return modifiedAnswers;
@ -148,6 +158,12 @@ export default {
this.nullSafeAvailableLineItems
);
},
discountPackageNames() {
const discountServicePackage = experimentMixin.methods.getSettingValue(
experimentSettings.PROMO_ON_PACKAGE
);
return getDiscountPackageName(discountServicePackage);
},
frontWipersApplicableForTierTwo() {
return shouldFrontWipersBeAvailable(
this.glassToReplace,
@ -200,6 +216,9 @@ export default {
this.nullSafeAvailableLineItems
);
},
showRecalInServicePackages() {
return this.isRecalibrationOnOrder && !this.shouldHideRecalibration;
},
},
methods: {
processIfStatements,
@ -239,9 +258,7 @@ export default {
let lineItemsToPrice = [...this.nullSafeAvailableLineItems];
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
lineItemsToPrice = lineItemsToPrice.filter((item) => {
return item.partType != partTypeStrings.RECALIBRATION;
});
lineItemsToPrice = getItemsWithoutRecalParts(lineItemsToPrice);
}
//remove service package discount part
@ -358,8 +375,8 @@ export default {
},
getCustomValueFromString(str) {
switch (str) {
case "isRecalibrationOnOrder":
return this.isRecalibrationOnOrder;
case "showRecalInServicePackages":
return this.showRecalInServicePackages;
case "frontWipersApplicableForTierTwo":
return this.frontWipersApplicableForTierTwo;
case "rearWiperApplicableForTierTwo":

View file

@ -1,15 +1,8 @@
<template>
<div
:class="[this.additionalButtonData.Text ? 'discount-text' : '']"
v-if="this.additionalButtonData.servicePackageDiscount"
v-html="this.additionalButtonData.Text"></div>
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
<div
class="package-label"
:class="[
this.buttonLabelSubCopy ? 'has-subheader' : '',
this.additionalButtonData.servicePackageDiscount ? 'has-text' : '',
]"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs">
<div v-if="this.additionalButtonData.servicePackageDiscount" class="row">
@ -78,10 +71,19 @@
</ul>
<!-- End of body text parsing -->
<div
class="package-footer fw-bold caption ms-n6"
class="package-footer fw-bold caption ms-n6 mt-4"
v-if="this.buttonFooterCopy"
v-html="this.buttonFooterCopy"></div>
</div>
<div
v-if="
this.additionalButtonData.servicePackageDiscount &&
this.additionalButtonData.Text
"
class="special-save-box">
<span v-html="this.additionalButtonData.Text"></span>
</div>
</div>
</div>
</baseInputButton>
@ -138,25 +140,18 @@ export default {
<style lang="scss">
.package-main {
.package-wrapper {
margin: 0.5rem 0;
margin: 1rem 0;
@include media-breakpoint-up(md) {
margin: 1rem 0.5rem;
display: flex;
flex-direction: column;
align-self: flex-end;
}
label {
display: block;
@include media-breakpoint-up(md) {
display: flex;
min-height: 336px;
}
//Unique breakpoint for this page only to accommodate price spacing issues
//from use of position: absolute on .service-package-discount (per Design Team request)
@media only screen and (min-width: 860px) {
display: flex;
min-height: 276px;
height: 100%;
}
}
@ -179,19 +174,16 @@ export default {
border-radius: 0.5rem;
overflow: hidden;
min-height: 60px;
max-height: 100px;
max-height: 126px;
@include media-breakpoint-up(md) {
max-height: 500px;
padding-bottom: 3rem;
}
transition: max-height 0.25s ease-in;
&.has-subheader {
min-height: 80px;
}
&.has-text {
border-radius: 0 0 0.5rem 0.5rem;
border: 1px solid $green;
}
&:before {
content: "";
@ -245,6 +237,9 @@ export default {
background-color: $blue-100;
border: 1px solid $blue;
max-height: 500px;
.special-save-box {
background-color: $green-200;
}
}
+ .package-label {
&:before {
@ -294,8 +289,8 @@ export default {
bottom: 0.75rem;
left: 0;
width: 100%;
text-align: center;
font-size: 1.125rem;
margin-left: 1.25rem;
font-weight: 600;
}
span.strikethrough-price {
text-decoration: line-through;
@ -316,28 +311,32 @@ export default {
&.service-package-discount {
color: $green;
text-align: end;
font-weight: 500;
font-size: 13px;
font-weight: 600;
font-size: 0.875rem;
display: flex;
justify-content: space-between;
div.strikethrough-discount-price {
text-decoration: line-through;
margin-right: 0;
font-weight: 400;
font-size: 0.875rem;
margin-right: 0.5rem;
font-weight: 600;
font-size: 0.75rem;
line-height: 20px;
color: $gray-500;
padding-right: 2px;
color: $gray-550;
@include media-breakpoint-up(md) {
margin-right: 0.5rem;
font-size: 0.75rem;
}
}
div.discount-price {
color: $green;
font-weight: 600;
font-size: 1.625rem;
}
@include media-breakpoint-up(md) {
position: absolute;
bottom: 0.75rem;
left: 0;
width: 100%;
text-align: center;
left: 0.5rem;
display: flex;
align-items: center;
justify-content: flex-start;
}
}
}
@ -388,6 +387,21 @@ export default {
font-weight: 600;
letter-spacing: 0.75px;
}
.special-save-box {
background-color: $green-100;
display: flex;
flex-direction: row;
margin: auto;
margin-left: -2rem;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
margin-top: 0.5rem;
span {
color: $green;
font-size: 0.875rem;
font-weight: 600;
}
}
}
}
</style>

View file

@ -75,6 +75,8 @@ import timeSlotModalQuestion from "./time-slot-modal-question/time-slot-modal-qu
import textBlock from "@/digital-components/text-block/text-block";
// Supporting files
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
@ -305,6 +307,22 @@ export default {
arePagePrerequisitesValid() {
const serviceLocation = store.getters.order.serviceLocation;
// prettier-ignore
{
const log = getQuerystringParameter(queryStrings.LOG);
if (eval(log)) {
console.log("------------- schedule.vue pagePrereqs start -----------------");
console.log(new Date() + " store.getters.order.serviceLocation.zipCode: " + serviceLocation.zipCode);
console.log(new Date() + " store.getters.order.serviceLocation.zipCodeCtu: " + serviceLocation.zipCodeCtu);
console.log(new Date() + " store.getters.order.serviceLocation.appointmentType: " + serviceLocation.appointmentType);
console.log(new Date() + " store.getters.order.serviceLocation.provider.providerNumber: " + serviceLocation.provider.providerNumber);
console.log(new Date() + " store.getters.payment.isInsurance: " + store.getters.payment?.isInsurance);
console.log(new Date() + " store.getters.order.damage.isRepair: " + store.getters.order.damage.isRepair);
console.log(new Date() + " store.getters.order.lineItems.glassParts: " + JSON.stringify(store.getters.order.lineItems?.glassParts));
console.log("------------- schedule.vue pagePrereqs end -------------------");
}
}
const serviceLocationPreReqs =
serviceLocation.zipCode &&
serviceLocation.zipCodeCtu &&

View file

@ -8,15 +8,15 @@
:aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
<div class="row-one">
<span class="m-0 button-label-copy" :class="textPosition">{{
<span class="m-0 button-label-copy text-truncate" :class="textPosition">{{
buttonLabel
}}</span>
<span class="m-0 caption ms-1" :class="textPosition">{{
<span class="m-0 caption mx-1 text-nowrap" :class="textPosition">{{
buttonLabelSubCopy
}}</span>
<div
v-if="displayAvailabilityIndicators"
class="availability-indicator rounded-pill"
class="availability-indicator rounded-pill text-nowrap"
:class="availabilityRatingClass">
<div
v-if="!isLoaderDisplayed"

View file

@ -167,7 +167,10 @@ export default {
if (logFirstShopsDisplayed) {
gaAction = this.GaActions.SHOPS_FIRST_DISPLAYED;
}
if (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP) {
if (
this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP ||
this.selectedAppointmentType === AppointmentTypeStrings.DROP_OFF
) {
var shops = mappedData.map((shop) => {
if (shop.value.length > 5 && shop.value.startsWith("00")) {
return shop.value.substring(1);
@ -179,7 +182,7 @@ export default {
this.pushEventToGA(
this.GaCategories.SERVICE_LOCATION,
gaAction,
shops.join("/"),
shops.join(","),
true
);
}

View file

@ -485,6 +485,7 @@ function setupMocks({ customMountOptions, customZipQuery, customZipDataResponse
}),
setCmsContent: jest.fn(),
navigateForwardWithSingleCarMatch: jest.fn(),
getAndSaveBillToAccountNumber: jest.fn(),
getZipCodeData: jest.fn().mockImplementation(() => {
if (customZipDataResponse) {
return customZipDataResponse;

View file

@ -132,11 +132,24 @@ export default {
next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent);
if (store.getters.externalParameterState?.isExternalParameter) {
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
if (isValid) {
vm.forwardButtonAction();
} else {
if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: store.getters.externalParameterServiceZip.zipCode,
state: null,
zipCodeCtu: null,
},
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
}
@ -185,6 +198,7 @@ export default {
},
false
);
await this.getAndSaveBillToAccountNumber();
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;

View file

@ -157,6 +157,17 @@ export default {
if (isValid) {
vm.forwardButtonAction();
} else {
if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: store.getters.externalParameterServiceZip.zipCode,
state: null,
zipCodeCtu: null,
},
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
} else {

View file

@ -98,6 +98,9 @@ import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import baseMixin from "@/mixins/base-mixin.js";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { applicationConfig } from "../../constants/application-config";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
//define validation rules
defineRule("year-required", required(errorMessages.YEAR_REQUIRED));
@ -127,6 +130,12 @@ export default {
},
async beforeRouteEnter(to, from, next) {
const log = getQuerystringParameter(queryStrings.LOG);
if (eval(log)) {
console.log("----------------- applicationConfig start -----------------");
console.log(new Date() + JSON.stringify(applicationConfig));
console.log("----------------- applicationConfig end -----------------");
}
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const experimentForLogging = store.getters.applicationUser.experiments.find(

View file

@ -346,6 +346,7 @@ export default {
false
);
await this.getAndSaveBillToAccountNumber();
return await this.navigateForward();
}

View file

@ -446,6 +446,7 @@ export default {
}
}
pushToDataLayerIfDefined({
event: "productArray",
productArray: {
coupon: coupon,
discount: discount.toFixed(2),
@ -544,7 +545,11 @@ export default {
);
}
pushToDataLayerIfDefined({
eCommerceCart: { products: combinedLineItems, subTotal: parseInt(subtotal) },
event: "eCommerceCart",
eCommerceCart: {
products: combinedLineItems,
subTotal: parseInt(subtotal),
},
});
}
},
@ -632,6 +637,7 @@ export default {
}
}
pushToDataLayerIfDefined({
event: "commissionJunctionGtmData",
commissionJunctionGtmData: {
cj_commission_junction_event: cjEvent,
cj_referral_sequence_number: refSequenceNum,

View file

@ -8,6 +8,7 @@ import { queryStrings } from "@/constants/query-strings";
import { dynamicStrings } from "@/constants/dynamic-strings";
import { partTypeStrings } from "../constants/part-type-strings";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import { getItemsWithoutRecalParts } from "@/helpers/recal-helper";
export default {
data() {
@ -82,7 +83,7 @@ export default {
};
},
getTierOnePackagePrice(lineItems) {
let lineItemsToPrice = lineItems.filter((lineItem) => {
let lineItemsToPrice = lineItems?.filter((lineItem) => {
return (
lineItem.partType != partTypeStrings.FRONT_WIPER &&
lineItem.partType != partTypeStrings.REAR_WIPER &&
@ -94,23 +95,20 @@ export default {
return totalPrice;
},
filterOutFees(lineItems) {
const filteredLineItems = lineItems.filter((item) => {
const filteredLineItems = lineItems?.filter((item) => {
return (
(!item.partType.includes("FEE") && !item.partType.includes("EARLY BIRD")) ||
(item.partType === "REPAIR FEE" && item.partNumber != "SUPPLIES-REPAIR")
(!item?.partType?.includes("FEE") && !item?.partType?.includes("EARLY BIRD")) ||
(item?.partType === "REPAIR FEE" && item?.partNumber != "SUPPLIES-REPAIR")
);
});
return filteredLineItems;
},
filterOutRecalibration(lineItems) {
const filteredLineItems = lineItems.filter((item) => {
return !item.partType.includes(partTypeStrings.RECALIBRATION);
});
return filteredLineItems;
return getItemsWithoutRecalParts(lineItems);
},
getTotalPriceOfAllLineItemsAndChildParts(lineItems, includeTax) {
let totalPrice = 0;
lineItems.forEach((lineItem) => {
lineItems?.forEach((lineItem) => {
totalPrice += this.getTotalLineItemPrice(lineItem, includeTax);
if (lineItem.childParts) {
totalPrice += this.getTotalPriceOfAllLineItemsAndChildParts(
@ -141,41 +139,43 @@ export default {
},
getAmountDue(lineItems, includeTax = true) {
var amountDue = 0;
if (lineItems.glassParts) {
if (lineItems?.glassParts) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.glassParts,
includeTax
);
}
if (lineItems.supportingItems) {
if (lineItems?.supportingItems) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.supportingItems,
includeTax
);
}
if (lineItems.vaps) {
const order = this.hasSubmittedOrder() ? this.getSubmittedOrder() : store.getters.order;
if (
store.getters.coverageIsVerified &&
!order.policy.isNoComp &&
!order.policy.isItac
) {
amountDue = order.policy.currentDeductible;
}
if (lineItems?.vaps) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.vaps,
includeTax
);
}
if (lineItems.promos) {
if (lineItems?.promos) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
lineItems.promos,
includeTax
);
}
const order = this.hasSubmittedOrder() ? this.getSubmittedOrder() : store.getters.order;
if (
order.payment.insuranceCoverage.isVerified &&
!order.policy.isNoComp &&
!order.policy.isItac
) {
amountDue += order.policy.currentDeductible;
}
return ((amountDue * 100) / 100).toFixed(2);
},
getSubTotal(lineItems) {

View file

@ -175,7 +175,7 @@ describe("baseMixin.js", () => {
promos: [],
};
const payment = { insuranceCoverage: { isVerified: true } };
const payment = { insuranceCoverage: { isVerified: false } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
store.getters = {
@ -190,7 +190,7 @@ describe("baseMixin.js", () => {
var amtDue = mixIn.methods.getAmountDue(lineItems);
expect(amtDue).toEqual("303.00");
expect(amtDue).toEqual("53.00");
});
test("getAmountDue should function with a submitted order", () => {
@ -223,7 +223,7 @@ describe("baseMixin.js", () => {
promos: [],
};
const payment = { insuranceCoverage: { isVerified: true } };
const payment = { insuranceCoverage: { isVerified: false } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(true);
@ -240,7 +240,7 @@ describe("baseMixin.js", () => {
var amtDue = mixIn.methods.getAmountDue(lineItems);
expect(amtDue).toEqual("303.00");
expect(amtDue).toEqual("53.00");
});
test("getAmountDue for cash should sum lineitems", () => {

View file

@ -450,7 +450,17 @@ export default {
const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions);
// save to store lineItems.glassParts
self.dispatchStoreAction(storeActions.SAVE_GLASS_PARTS, collectedGlassParts, false);
await self.dispatchStoreAction(
storeActions.SAVE_GLASS_PARTS,
collectedGlassParts,
false
);
await self.dispatchStoreActionWithLogging(
storeActions.GET_RECAL_PARTS_AND_SAVE_TO_LINE_ITEMS,
null,
currentPage
);
const payment = store.getters.payment;

View file

@ -42,5 +42,19 @@ export default {
};
return vinLookupMethods[vinSelection] || null;
},
async getAndSaveBillToAccountNumber() {
const pageName = this.$options?.name;
const billToAccountNumber = await this.dispatchStoreActionWithLogging(
storeActions.GET_BILL_TO_ACCOUNT_NUMBER,
{},
pageName,
false
);
await this.dispatchStoreAction(
this.storeActions.SAVE_BILL_TO_ACCOUNT_NUMBER,
billToAccountNumber,
false
);
},
},
};

View file

@ -10,6 +10,7 @@ import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import { fmgPageValues, funnelStartPageName } from "@/router/router-constants/fmgPage-values";
import { externalParameterStatus } from "@/constants/external-parameters";
// Heritage integration
import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper";
@ -96,8 +97,6 @@ const routes = [
);
updateOrCreateFunnelCookie();
}
//Update external parameter state
updateExternalParameterState();
//Affiliate Cookies
const affiliateCookies = getAffiliateCookies();
@ -114,6 +113,17 @@ const routes = [
to.query.fmgPage
);
//Update external parameter state
updateExternalParameterState();
// clear part related state because heritage selected a new vehicle
if (
to.query.fmgPage === fmgPageValues.VEHICLE &&
eval(getFunnelCookie()?.HasDelayedClaimRegistration)
) {
store.commit(storeMutations.RESET_GLASS_PARTS_STATE);
}
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to);
// This logic may determine that the user should be sent to heritage -- if so, do that here.
@ -264,7 +274,10 @@ router.beforeEach(async (to, from, next) => {
);
// If alert event is on the bus, then display the alert
if (alertEvent !== undefined || unknownAlertEvent !== undefined) {
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, false);
store.commit(
storeMutations.UPDATE_IS_EXTERNAL_PARAMETER,
externalParameterStatus.INACTIVE
);
}
}
// fromPaymentToConfirmation workaround for navigating from an iframe but
@ -315,12 +328,12 @@ router.afterEach(async (to, from) => {
analyticsMixin.methods.pushOrderToDataLayer();
if (to.query.fmgPage == fmgPageValues.CONFIRMATION) {
//Push Product Array to Data Layer
analyticsMixin.methods.pushProductArrayToDataLayer();
//Push Ecommerce Cart to Data Layer
analyticsMixin.methods.pushECommerceCartToDataLayer();
//Push Product Array to Data Layer
analyticsMixin.methods.pushProductArrayToDataLayer();
//Push Commission Junction Gtm Data To Data Layer
analyticsMixin.methods.pushCommissionJunctionGtmDataToDataLayer();
}
@ -540,7 +553,7 @@ async function DisplayPageError() {
type: globalEventTypes.Danger,
}
);
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, false);
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, externalParameterStatus.INACTIVE);
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR);
@ -621,6 +634,22 @@ function getRedirectedStartPage() {
}
function updateExternalParameterState() {
// if there is an existing external parameter state then they have already been through from an external source(LeadGen) and
// are returning. clear out prior related things that need to be selected again like parts and damage type.
if (
store.getters.externalParameterState?.isExternalParameter ==
externalParameterStatus.INACTIVE
) {
store.dispatch(storeActions.RESET_VEHICLE_STATE_AND_DEPENDENCIES);
store.dispatch(storeActions.SAVE_SERVICE_ZIP_CODE_INFO, {
state: null,
zipCode: null,
zipCodeCtu: null,
});
store.dispatch(storeActions.SAVE_EMAIL, null);
store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
}
const externalParameterYear = getQuerystringParameter(queryStrings.VEHICLE_YEAR);
const externalParameterMake = getQuerystringParameter(queryStrings.VEHICLE_MAKE);
const externalParameterModel = getQuerystringParameter(queryStrings.VEHICLE_MODEL);
@ -639,7 +668,7 @@ function updateExternalParameterState() {
externalParameterModel &&
externalParameterStyle
) {
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, true);
store.commit(storeMutations.UPDATE_IS_EXTERNAL_PARAMETER, externalParameterStatus.ACTIVE);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_YEAR, externalParameterYear);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MAKE, externalParameterMake);
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_MODEL, externalParameterModel);
@ -662,8 +691,10 @@ function updateExternalParameterState() {
);
}
}
if (externalParameterZipCode && externalParameterEmail) {
if (externalParameterZipCode) {
store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_ZIP_CODE, externalParameterZipCode);
}
if (externalParameterEmail) {
store.commit(
storeMutations.UPDATE_EXTERNAL_PARAMETER_EMAIL_ADDRESS,
externalParameterEmail

View file

@ -36,9 +36,15 @@ import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations";
import {
coverageStatus,
coverageStatusValue,
coverageStatusEnum,
coverageType,
coverageTypeValue,
coverageTypeEnum,
} from "@/constants/insurance";
import { containsRecalParts, getTopLevelPartsWithRecal } from "@/helpers/recal-helper";
import { externalParameterStatus } from "@/constants/external-parameters";
import { experimentSettings } from "@/constants/experiments";
import experimentMixin from "@/mixins/experiment-mixin.js";
// Export State
const getDefaultState = () => {
@ -473,10 +479,6 @@ export const mutations = {
externalParameterState.quote.servicePackage = servicePackage;
saveExternalParameterState(externalParameterState);
},
updateExternalParameterSource(state, source) {
externalParameterState.source = source;
saveExternalParameterState(externalParameterState);
},
//RESET ExternalParameter MUTATIONS
resetExternalParameterVehicleState(state) {
externalParameterState.vehicle.year = null;
@ -503,16 +505,11 @@ export const mutations = {
saveExternalParameterState(externalParameterState);
},
resetExternalParameterQuoteState(state) {
externalParameterState.quote.isInsurance = null;
externalParameterState.quote.servicePackage = null;
saveExternalParameterState(externalParameterState);
},
resetExternalParameterSourceState(state) {
externalParameterState.source = null;
saveExternalParameterState(externalParameterState);
},
resetIsExternalParameter(state) {
externalParameterState.isExternalParameter = null;
externalParameterState.isExternalParameter = externalParameterStatus.INACTIVE;
saveExternalParameterState(externalParameterState);
},
@ -682,7 +679,17 @@ export const mutations = {
sessionInformation.order.serviceLocation.provider?.address?.zipCodeCtu;
state.order.serviceLocation.techNotes = sessionInformation.order.serviceLocation.techNotes;
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
// if we're loading a session and we do not have a provider number yet, then set isInsurance to null so that
// a service package is not selected by default on the quote page.
if (
!sessionInformation.order.payment.isInsurance &&
!sessionInformation.order.serviceLocation.provider?.providerNumber
) {
state.order.payment.isInsurance = null;
} else {
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
}
state.order.payment.insuranceCoverage.isVerified =
coverageStatusValue(sessionInformation?.order.insuranceCoverage.coverageStatus) ===
coverageStatus.VERIFIED
@ -772,6 +779,24 @@ export const getters = {
);
return !!nonWindshieldItems?.length;
},
coverageIsVerified: (state) => {
let order;
if (window.sessionStorage.getItem("submittedOrder") !== null) {
order = JSON.parse(window.sessionStorage.getItem("submittedOrder"));
} else {
order = state.order;
}
if (
order.payment?.insuranceCoverage?.isVerified &&
order.policy?.currentDeductible != 9999 &&
order.policy?.currentDeductible != 7777
) {
return true;
}
return false;
},
isMobileAppointment: (state) => {
return state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
},
@ -793,6 +818,13 @@ export const getters = {
return false;
},
shouldHideRecalibration: (state) => {
return (
experimentMixin.methods
.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)
?.toLowerCase() === "true" && getters.isRecalibrationOnOrder
);
},
areRearWipersOnOrder: (state) => {
return !!state.order.lineItems.vaps?.some(
(vap) => vap.partType.toUpperCase() === partTypeStrings.REAR_WIPER.toUpperCase()
@ -894,12 +926,12 @@ export const getters = {
.filter((x) => !!x.isActive)
.map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
isVerifiedAndDeductibleZeroConfirmed: (state) => {
// used as a CMS variable on /payment-method, needed for FunnelSubHeaderWidget on cart pages
if (
(state.order.policy.currentDeductible === 0 ||
state.order.policy.currentDeductible > 0) &&
state.order.payment.insuranceCoverage.coverageStatus === coverageStatus.VERIFIED &&
state.order.policy.currentDeductible === 0 &&
getters.coverageIsVerified(state) &&
!state.order.policy.isNoComp
) {
return true;
@ -915,7 +947,7 @@ export const getters = {
externalParameterQuote: (state) => externalParameterState?.quote,
externalParameterServiceZip: (state) => externalParameterState?.serviceZip,
externalParameterEstimate: (state) => externalParameterState?.estimate,
externalParameterSource: (state) => externalParameterState?.source,
isExternalParameter: (state) => externalParameterState?.isExternalParameter,
};
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
@ -1121,6 +1153,12 @@ export const actions = {
},
// Dependency Actions
resetVehicleStateAndDependencies(context) {
context.commit(storeMutations.RESET_VEHICLE_STATE);
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
},
resetDamageAndDependencies(context) {
context.commit(storeMutations.RESET_DAMAGE_STATE);
@ -1531,15 +1569,38 @@ export const actions = {
},
getMobileFeePart(context, { pageNameToLog }) {
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
const serviceType = context.getters.damage.isRepair ? "Repair" : "Install";
const facilityType = "Mobile";
const parentAccountNumber = context.getters.payment.parentAccountNumber;
const billToAccountNumber = context.getters.payment.billToAccountNumber
? context.getters.payment.billToAccountNumber
: applicationConfig.CASH_DEFAULT_BILL_TO_ACCOUNT_NUMBER;
const order = context.getters.order;
const coverageStatus = coverageStatusEnum(order.payment?.insuranceCoverage?.coverageStatus);
const coverageType = coverageTypeEnum(order.payment?.insuranceCoverage?.coverageType);
const isItacOptimized = order.policy?.isItac ?? false;
const zipCode =
order.serviceLocation?.provider?.address?.zipCode ?? order.serviceLocation?.zipCode;
var providerNumber =
order.serviceLocation?.provider?.providerNumber ?? order.serviceLocation?.zipCodeCtu;
if (providerNumber.startsWith("00") && providerNumber.length > 5) {
providerNumber = providerNumber.substring(1);
}
var endPoint = `${endpoints.GetMobileFeePart.url}/?serviceType=${serviceType}&facilityType=${facilityType}&parentAccountNumber=${parentAccountNumber}&billToAccountNumber=${billToAccountNumber}&providerNumber=${providerNumber}&isItacOptimized=${isItacOptimized}&zipCode=${zipCode}`;
if (coverageStatus) {
endPoint = `${endPoint}&coverageStatus=${coverageStatus}`;
}
if (coverageType) {
endPoint = `${endPoint}&coverageType=${coverageType}`;
}
return globalMethods.callHttpClient({
method: endpoints.GetMobileFeePart.method,
endpoint: `${endpoints.GetMobileFeePart.url}/${damageType}/${parentAccountNumber}/${billToAccountNumber}`,
endpoint: endPoint,
logApiCall: true,
pageNameToLog: pageNameToLog,
});
@ -1566,16 +1627,21 @@ export const actions = {
},
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems?.map(
const escapeRecalibrationType = (rt) => rt.split("&").join("%26");
const partialLineItemsObjects = context.getters.order.lineItems.glassParts?.map(
(lineItem) => ({
partNumber: lineItem.partNumber,
recalibrationType: lineItem.recalibrationType
? escapeRecalibrationType(lineItem.recalibrationType)
: undefined,
})
);
var lineItems = null;
if (lineItemsWithOnlyPartNumbers) {
if (partialLineItemsObjects) {
lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
lineItemsWithOnlyPartNumbers,
partialLineItemsObjects,
"lineItems"
);
}
@ -1590,7 +1656,12 @@ export const actions = {
"glassPieces"
);
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}`;
const parentAccountNumber =
context.getters.order.payment.parentAccountNumber ??
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
const referralSequenceNumber = context.getters.order.referralSequenceNumber;
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&parentAccountNumber=${parentAccountNumber}&referralSequenceNumber=${referralSequenceNumber}&applicationName=${applicationConfig.ANALYTICS_APPLICATION_NAME}`;
if (lineItems) {
endPoint += `&${lineItems}`;
}
@ -1609,11 +1680,26 @@ export const actions = {
getProviders(context, { payload: { serviceZipCode }, pageNameToLog }) {
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
const accountNumber =
context.getters.order.payment.parentAccountNumber ??
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
const carId = context.getters.order.vehicle.carId;
const partsWithRecal = getTopLevelPartsWithRecal(
context.getters.order.lineItems.glassParts
);
const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null;
const shopRadiusInMiles = 100;
let url = `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}/${accountNumber}/true/${carId}`;
if (windshieldPartWithRecal) {
url += `/${windshieldPartWithRecal.partNumber}`;
}
return globalMethods.callHttpClient({
method: endpoints.GetProviders.method,
endpoint: `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}`,
endpoint: url,
logApiCall: true,
pageNameToLog: pageNameToLog,
});
@ -2579,9 +2665,10 @@ export const actions = {
carId: order.vehicle.carId,
correlationId: order.referralCorrelationId,
eon: order.eon,
isInsurance: order.payment.isInsurance,
isRepair: order.damage.isRepair,
glassToReplace: renameGlassToReplaceAttributes(order.damage.glassToReplace),
lineItemsOnOrder: getArrayOfAllLineItems(lineItemsToUse),
lineItemsOnOrder: getArrayOfAllLineItemsAndChildParts(lineItemsToUse),
parentAccountNumber: useDefaultCashParentAccount
? applicationConfig.CASH_PARENT_ACCOUNT_NUMBER
: order.payment.parentAccountNumber,
@ -2651,9 +2738,10 @@ export const actions = {
carId: order.vehicle.carId,
correlationId: order.referralCorrelationId,
eon: order.eon,
isInsurance: order.payment.isInsurance,
isRepair: order.damage.isRepair,
glassToReplace: renameGlassToReplaceAttributes(order.damage.glassToReplace),
lineItemsOnOrder: getArrayOfAllLineItems(lineItemsToUse),
lineItemsOnOrder: getArrayOfAllLineItemsAndChildParts(lineItemsToUse),
parentAccountNumber: useDefaultCashParentAccount
? applicationConfig.CASH_PARENT_ACCOUNT_NUMBER
: order.payment.parentAccountNumber,
@ -2721,10 +2809,8 @@ export const actions = {
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;
if (!parentAccountNumber) {
parentAccountNumber = applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
}
const queryString =
`ParentAccountNumber=${parentAccountNumber}` +
@ -2885,7 +2971,6 @@ export const actions = {
context.commit(storeMutations.RESET_EXTERNAL_PARAMETER_ESTIMATE_STATE);
context.commit(storeMutations.RESET_EXTERNAL_PARAMETER_SERVICEZIP_STATE);
context.commit(storeMutations.RESET_EXTERNAL_PARAMETER_QUOTE_STATE);
context.commit(storeMutations.RESET_EXTERNAL_PARAMETER_SOURCE_STATE);
context.commit(storeMutations.RESET_IS_EXTERNAL_PARAMETER);
}
},
@ -2903,6 +2988,49 @@ export const actions = {
externalParameterMMS.externalParameterStyle
);
},
async getRecalPartsAndSaveToLineItems(context, { pageNameToLog }) {
// identify each part that needs recal
const glassParts = context.state.order?.lineItems?.glassParts
? deepClone(context.state.order.lineItems.glassParts)
: [];
for (let i = 0; i < glassParts.length; i++) {
// does this part need recal?
const needsRecal =
!!glassParts[i].requiresRecalibration && !!glassParts[i].recalibrationType;
if (needsRecal) {
const recalType = glassParts[i].recalibrationType;
const parentAccountNumber =
context.state.order.payment.parentAccountNumber ??
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
// Get part from API
const urlExtension = `${context.state.order.vehicle.carId}/${glassParts[i].partNumber}/${recalType}/${parentAccountNumber}/${context.state.order.serviceLocation.zipCode}/${applicationConfig.ANALYTICS_APPLICATION_NAME}/${context.state.order.referralSequenceNumber}`;
const recalPartResponse = await globalMethods.callHttpClient({
method: endpoints.GetRecalPart.method,
endpoint: `${endpoints.GetRecalPart.url}/${urlExtension}`,
payload: {},
pageNameToLog: pageNameToLog,
});
// If any parts retrieved, add as children to the glass part.
if (
recalPartResponse.status === 200 &&
recalPartResponse.data.recalibrationParts?.length > 0
) {
if (!glassParts[i].childParts) {
glassParts[i].childParts = [];
}
glassParts[i].childParts.push(...recalPartResponse.data.recalibrationParts);
context.dispatch(storeActions.SAVE_GLASS_PARTS, glassParts);
}
}
}
},
};
export default createStore({
@ -2925,34 +3053,7 @@ export default createStore({
// Private Functions
function getHasRecalibrationPart(state) {
var hasRequiresRecalibration =
getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"requiresRecalibration"
)?.length > 0;
var hasRecalibrationType =
getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"recalibrationType"
)?.length > 0;
if (hasRequiresRecalibration) {
if (hasRecalibrationType) {
// Has both 'requiresRecalibration' and 'recalibrationType' and 'recalibrationType'
return (
getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
"recalibrationType"
)[0].toLowerCase() != "unknown"
);
} else {
// Has 'requiresRecalibration' but no 'recalibrationType' at all
return true;
}
} else {
// Does not have 'requiresRecalibration'
return false;
}
return containsRecalParts(state.order.lineItems);
}
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
@ -3120,32 +3221,46 @@ export function mapTaxedLineItemsToStoreFormat(availableLineItems, storeLineItem
return lineItems;
}
export function getArrayOfAllLineItems(lineItems) {
export function getArrayOfAllLineItemsAndChildParts(lineItems) {
let consolidatedLineItemsArray = [];
if (lineItems.glassParts != null)
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.glassParts];
consolidatedLineItemsArray = [
...consolidatedLineItemsArray,
...getFlattenedArrayOfLineItemsWithChildParts(lineItems.glassParts),
];
if (lineItems.supportingItems != null)
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.supportingItems];
consolidatedLineItemsArray = [
...consolidatedLineItemsArray,
...getFlattenedArrayOfLineItemsWithChildParts(lineItems.supportingItems),
];
if (lineItems.vaps != null)
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.vaps];
consolidatedLineItemsArray = [
...consolidatedLineItemsArray,
...getFlattenedArrayOfLineItemsWithChildParts(lineItems.vaps),
];
if (lineItems.promos != null)
consolidatedLineItemsArray = [...consolidatedLineItemsArray, ...lineItems.promos];
consolidatedLineItemsArray = [
...consolidatedLineItemsArray,
...getFlattenedArrayOfLineItemsWithChildParts(lineItems.promos),
];
return consolidatedLineItemsArray;
}
function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {
function getFlattenedArrayOfLineItemsWithChildParts(lineItems, childPartRecursiveCall = false) {
let flattenedArray = [];
lineItems?.forEach((lineItem) => {
// this assumes childparts will never be a glass part
lineItem.isChildPart = childPartRecursiveCall;
flattenedArray.push(lineItem);
if (lineItem.childParts) {
flattenedArray = [
...flattenedArray,
...getFlattenedArrayOfLineItemsWithChildParts(lineItem.childParts),
...getFlattenedArrayOfLineItemsWithChildParts(lineItem.childParts, true),
];
}
});
@ -3168,7 +3283,7 @@ function getFlattenedLineItemsWithGlassPartTag(lineItems) {
return {
partNumber: lineItem.partNumber,
partType: lineItem.partType,
isGlassPart: true,
isGlassPart: lineItem.isChildPart ? false : true,
};
});
@ -3212,6 +3327,9 @@ function addGuidToLineItemsIfNotAlreadyThere(lineItems) {
if (!lineItem.id) {
lineItem.id = crypto.randomUUID();
}
if (lineItem.childParts) {
addGuidToLineItemsIfNotAlreadyThere(lineItem.childParts);
}
});
}
@ -3393,7 +3511,7 @@ async function resetScheduleIfUnavailable(context, order, pageNameToLog) {
function createExternalParameterDefaultState() {
// create default externalParameter state
const externalParameterDefaultState = {
isExternalParameter: null,
isExternalParameter: externalParameterStatus.NOT_SET,
vehicle: {
year: null,
make: null,
@ -3416,7 +3534,6 @@ function createExternalParameterDefaultState() {
isInsurance: null,
servicePackage: null,
},
source: null,
};
// set to session storage
saveExternalParameterState(externalParameterDefaultState);

View file

@ -5,7 +5,6 @@ import { storeActions } from "@/constants/store-actions";
import { experimentTriggers } from "@/constants/experiments";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { coverageStatus } from "../constants/insurance";
// Mock global method
globalMethods.callHttpClient = jest.fn();
@ -2935,7 +2934,10 @@ describe("Actions", () => {
// Arrange
const context = state;
const promoCode = "testPromo";
const lineItemsToUse = { vaps: [{ partNumber: 1 }], promos: [2] };
const lineItemsToUse = {
vaps: [{ partNumber: 1 }],
promos: [{ promoCode: "1wiper0" }],
};
const addableVaps = [{ partNumber: "addableVap" }];
context["getters"] = {
@ -2999,7 +3001,7 @@ describe("Actions", () => {
{ partNumber: "SBB22", id: "GUID1" },
{ partNumber: "SBB22", id: "GUID2" },
],
promos: [2],
promos: [{ promoCode: "1wiper0" }],
};
const addableVaps = [{ partNumber: "SBB22" }, { partNumber: "SBB22" }];
@ -3059,7 +3061,10 @@ describe("Actions", () => {
// Arrange
const context = state;
const promoCode = "testPromo";
const lineItemsToUse = { vaps: [{ partNumber: 1 }], promos: [2] };
const lineItemsToUse = {
vaps: [{ partNumber: 1 }],
promos: [{ promoCode: "1wiper0" }],
};
const addableVaps = [{ partNumber: "addableVap" }];
context["getters"] = {
@ -3140,7 +3145,7 @@ describe("Actions", () => {
referralSequenceNumber: "test",
lineItems: {
vaps: [{ partNumber: 1 }],
promos: [2],
promos: [{ promoCode: "1wiper0" }],
serverData: "test",
},
},
@ -3152,7 +3157,10 @@ describe("Actions", () => {
crypto.randomUUID = jest.fn(() => "GUID");
const expectedLineItemsOnOrder = [{ partNumber: 1, id: "GUID" }, 2];
const expectedLineItemsOnOrder = [
{ partNumber: 1, isChildPart: false, id: "GUID" },
{ promoCode: "1wiper0", isChildPart: false },
];
// Act
actions.validateOrderPromoAndSaveServerData(context, {
@ -3200,7 +3208,7 @@ describe("Actions", () => {
referralSequenceNumber: "test",
lineItems: {
vaps: [{ partNumber: 1 }],
promos: [2],
promos: [{ promoCode: "1wiper0" }],
serverData: "test",
},
},
@ -3258,7 +3266,7 @@ describe("Actions", () => {
referralSequenceNumber: "test",
lineItems: {
vaps: [{ partNumber: 1 }],
promos: [2],
promos: [{ promoCode: "1wiper0" }],
serverData: "test",
},
},
@ -3350,9 +3358,9 @@ describe("Actions", () => {
const context = state;
context.commit = jest.fn(() => {});
const activePromosToUse = [{ promoCode: "providedPromo" }];
const activePromosToUse = [{ promoCode: "providedPromo", isChildPart: false }];
const inactivePromosToUse = ["providedInactivePromo"];
const vapsProvided = [{ id: 123 }];
const vapsProvided = [{ id: 123, isChildPart: false }];
const lineItemsToUse = { vaps: vapsProvided };
context["getters"] = {
@ -3440,8 +3448,8 @@ describe("Actions", () => {
referralSequenceNumber: "test",
lineItems: {
serverData: "test",
promos: [{ promoCode: "test" }],
vaps: [{ partNumber: "testVap", id: "providedId" }],
promos: [{ promoCode: "test", isChildPart: false }],
vaps: [{ partNumber: "testVap", isChildPart: false, id: "providedId" }],
},
},
};
@ -3950,6 +3958,111 @@ describe("Getters", () => {
mutations.updateVaps(storeState, mockStateValues.funnelVaps);
mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace);
//Assert
expect(getters.experimentOrder(storeState)).toEqual({
funnelVehicleYear: mockStateValues.vehicleYear,
funnelVehicleMake: mockStateValues.vehicleMake,
funnelVehicleModel: mockStateValues.vehicleModel,
funnelVehicleStyle: mockStateValues.vehicleStyle,
funnelIsRepair: mockStateValues.isRepair,
funnelNumberOfChips: mockStateValues.numberOfChips,
funnelCarId: mockStateValues.carId,
funnelServiceCity: mockStateValues.serviceCity,
funnelServiceState: mockStateValues.serviceState,
funnelServiceZipCode: mockStateValues.serviceZipCode,
funnelParentAccountNumber: mockStateValues.parentAccountNumber,
funnelIsCoverageVerified: mockStateValues.isCoverageVerified,
funnelOrderPartNumbers: ["WINDSHIELDPARTNUMBER"],
funnelOrderPartTypes: ["ADAS, maybe"],
funnelHasRecalibrationPart: false,
funnelSelectedMultiGlass: false,
funnelSelectedWindshieldGlass: true,
funnelSelectedBackGlass: false,
funnelSelectedDriverSideGlass: false,
funnelSelectedPassengerSideGlass: false,
funnelProviderNumber: mockStateValues.funnelProviderNumber,
funnelReferralType: mockStateValues.funnelReferralType,
funnelServiceZipCodeCtu: mockStateValues.funnelServiceZipCodeCtu,
});
});
test("Single windshield with recalibration child part > return correct experimentOrder values", () => {
// Arrange
const storeState = state;
const mockStateValues = {
vehicleYear: 1000,
vehicleMake: "CarMake",
vehicleModel: "CarModel",
vehicleStyle: "SuperCoolStyle",
isRepair: false,
numberOfChips: 0,
carId: "Gibberish",
serviceCity: "Columbus",
serviceState: "OH-IO",
serviceZipCode: 43215,
parentAccountNumber: "999999",
isCoverageVerified: false,
glassParts: [
{
partNumber: "WINDSHIELDPARTNUMBER",
description: "This is a windshield",
recalibrationType: "ADAS, maybe",
requiresRecalibration: true,
requiresCapabilityQuestions: false,
childParts: [
{
partNumber: "THIRD RECAL",
description: "Additional Static Recal",
recalibrationType:
"DUAL & RECAL THIRD & HC FUNCTION TEST & RL SENSOR",
ribCode: "RL",
safelitePartNumber: "THIRD RECAL",
status: "ACTIVE",
partType: "ADAS RECALIBRATION",
childParts: [],
recalibrationFees: [],
salesTax: null,
},
],
},
],
otherParts: [],
glassToReplace: [
{
glassLocation: "Windshield",
glassName: "Single",
},
],
funnelProviderNumber: "1",
funnelReferralType: "CASH QUOTE",
funnelServiceZipCodeCtu: "11111",
};
//Act
mutations.updateYear(storeState, mockStateValues.vehicleYear);
mutations.updateMake(storeState, mockStateValues.vehicleMake);
mutations.updateModel(storeState, mockStateValues.vehicleModel);
mutations.updateStyle(storeState, mockStateValues.vehicleStyle);
mutations.updateIsRepair(storeState, mockStateValues.isRepair);
mutations.updateNumberOfChips(storeState, mockStateValues.numberOfChips);
mutations.updateCarId(storeState, mockStateValues.carId);
mutations.updateServiceLocation(storeState, {
city: mockStateValues.serviceCity,
state: mockStateValues.serviceState,
zipCode: mockStateValues.serviceZipCode,
zipCodeCtu: mockStateValues.funnelServiceZipCodeCtu,
provider: {
providerNumber: mockStateValues.funnelProviderNumber,
},
});
mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber);
mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified);
mutations.updateIsInsurance(storeState, false);
mutations.updateGlassParts(storeState, mockStateValues.glassParts);
mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems);
mutations.updateVaps(storeState, mockStateValues.funnelVaps);
mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace);
//Assert
expect(getters.experimentOrder(storeState)).toEqual({
funnelVehicleYear: mockStateValues.vehicleYear,

View file

@ -6,7 +6,7 @@ process.env.VUE_APP_CURRENT_ENVIRONMENT = "Localhost";
process.env.VUE_APP_MY_ACCOUNT = "https://myaccountdev.safelite.com/";
//process.env.VUE_APP_SAFELITE_HOP = "http://localhost:60966/fmgCheckoutShared.aspx";
process.env.VUE_APP_SAFELITE_HOP = "https://sv2-safelitehop-sys.safelite.com/fmgCheckoutShared.aspx";
process.env.VUE_APP_SAFELITE_HOP = "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx";
// GA & GTM
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY =