diff --git a/src/constants/analytics.js b/src/constants/analytics.js index c3416edea..f6534cda4 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -12,6 +12,8 @@ const GaEvents = { const GaCategories = { API_RESPONSE: "Api_Response", EVOX: "Evox", + APPOINTMENT: "Appointment", + SERVICE_LOCATION: "service-location", }; const GaActions = { @@ -21,6 +23,10 @@ const GaActions = { SUBMITTED: "Submitted", DISPLAYED: "Displayed", CASH_QUOTE_DISPLAYED: "Cash quote displayed", + SERVICE_PACKAGE_PRICE: "Service package price", + MOBILE_AVAILABLE: "mobile_available", + SHOPS_FIRST_DISPLAYED: "shops_first_displayed", + MORE_LOCATIONS_CLICKED: "more_locations_clicked", }; const GaLabels = { @@ -29,6 +35,8 @@ const GaLabels = { LICENSE_PLATE_LOOKUP: "License_Plate_Look_Up", VIN_LOOKUP: "Vin_Look_Up", ADDRESS_LOOKUP: "Address_Look_up", + YES: "yes", + NO: "no", }; const ValueToLogTypes = { diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index ed880269c..16e0803e6 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -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", diff --git a/src/constants/experiments.js b/src/constants/experiments.js index 52017ce47..eeabeb7d6 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -13,6 +13,8 @@ const experimentSettings = { IS_EMAIL_OPTIONAL: "isEmailOptional", SERVICE_PACKAGE_DISCOUNT: "OfferServicePackageDiscount", RECAL_PRICE_REMOVE: "RecalPriceRemove", + INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold", + INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold", }; const experimentTriggers = { diff --git a/src/constants/header-keys.js b/src/constants/header-keys.js index 7caaef65c..2f7598244 100644 --- a/src/constants/header-keys.js +++ b/src/constants/header-keys.js @@ -4,5 +4,5 @@ export const headerKeys = { REFERRAL_SEQUENCE_NUMBER: "X-Referral-Sequence-Number", SESSION_SEQUENCE_NUMBER: "X-Session-Sequence-Number", TRANSACTION_ID: "X-Transaction-Id", - EON: "X-EON", + EON: "X-Enterprise-Order-Number", }; diff --git a/src/constants/insurance.js b/src/constants/insurance.js index 1df261d63..f7550513b 100644 --- a/src/constants/insurance.js +++ b/src/constants/insurance.js @@ -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; + } +} diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js index 3ad66c40a..4681e2776 100644 --- a/src/constants/part-type-strings.js +++ b/src/constants/part-type-strings.js @@ -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", diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 4cd03771e..185d3d369 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -54,6 +54,7 @@ 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_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies", diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 90c1adf98..e6b66d4e6 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -305,8 +305,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; }, @@ -1007,6 +1007,11 @@ export default { }, salesTax() { if (!this.lineItems || this.lineItems.length < 1) return; + + if (this.isInsurance && !this.showCoverageAsVerified) { + 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 diff --git a/src/fmg-components/funnel-sub-header/funnel-sub-header.vue b/src/fmg-components/funnel-sub-header/funnel-sub-header.vue index fa8aea13f..54c8aa34d 100644 --- a/src/fmg-components/funnel-sub-header/funnel-sub-header.vue +++ b/src/fmg-components/funnel-sub-header/funnel-sub-header.vue @@ -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"); diff --git a/src/helpers/heritage-integration/navigation-helper.js b/src/helpers/heritage-integration/navigation-helper.js index 208a301e4..00687155b 100644 --- a/src/helpers/heritage-integration/navigation-helper.js +++ b/src/helpers/heritage-integration/navigation-helper.js @@ -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. diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js index 07c469ae8..555c41325 100644 --- a/src/helpers/heritage-integration/order-helper.js +++ b/src/helpers/heritage-integration/order-helper.js @@ -57,7 +57,7 @@ export async function saveSession({ pageNameToLog, shouldAwaitSaveSessionQueue = false, submitAfterSave = false, - createDeleteStatusWorkOrderForPia = false, + createUnscheduledStatusWorkOrderForPIA = false, }) { var saveSessionPromise; if (store.getters.applicationUser.saveSessionPromise) { @@ -67,7 +67,7 @@ export async function saveSession({ return saveSessionHelper( pageNameToLog, submitAfterSave, - createDeleteStatusWorkOrderForPia + createUnscheduledStatusWorkOrderForPIA ); }); } else { @@ -75,7 +75,7 @@ export async function saveSession({ saveSessionPromise = saveSessionHelper( pageNameToLog, submitAfterSave, - createDeleteStatusWorkOrderForPia + createUnscheduledStatusWorkOrderForPIA ); } store.commit(storeMutations.UPDATE_SAVE_SESSION_PROMISE, saveSessionPromise); @@ -88,13 +88,13 @@ export async function saveSession({ export async function submitWorkOrder({ pageNameToLog, submitAfterSave = false, - createDeleteStatusWorkOrderForPia = false, + createUnscheduledStatusWorkOrderForPIA = false, }) { await saveSession({ pageNameToLog: pageNameToLog, shouldAwaitSaveSessionQueue: true, submitAfterSave: submitAfterSave, - createDeleteStatusWorkOrderForPia: createDeleteStatusWorkOrderForPia, + createUnscheduledStatusWorkOrderForPIA: createUnscheduledStatusWorkOrderForPIA, }); } @@ -139,13 +139,13 @@ async function loadSession( async function saveSessionHelper( pageNameToLog, submitAfterSave = false, - createDeleteStatusWorkOrderForPia = false + createUnscheduledStatusWorkOrderForPIA = false ) { const savedSessionInfo = await baseMixin.methods.dispatchStoreActionWithLogging( storeActions.SAVE_SESSION, { submitAfterSave: submitAfterSave, - createDeleteStatusWorkOrderForPia: createDeleteStatusWorkOrderForPia, + createUnscheduledStatusWorkOrderForPIA: createUnscheduledStatusWorkOrderForPIA, }, pageNameToLog ); diff --git a/src/helpers/heritage-integration/order-helper.spec.js b/src/helpers/heritage-integration/order-helper.spec.js index daf9af6bb..ac8d08e9f 100644 --- a/src/helpers/heritage-integration/order-helper.spec.js +++ b/src/helpers/heritage-integration/order-helper.spec.js @@ -179,7 +179,7 @@ describe("saveSession", () => { // Assert expect(mocks.baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith( storeActions.SAVE_SESSION, - { createDeleteStatusWorkOrderForPia: false, submitAfterSave: false }, + { createUnscheduledStatusWorkOrderForPIA: false, submitAfterSave: false }, "test" ); expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith( @@ -287,7 +287,7 @@ describe("submitWorkOrder", () => { // Assert expect(mocks.baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith( storeActions.SAVE_SESSION, - { createDeleteStatusWorkOrderForPia: false, submitAfterSave: true }, + { createUnscheduledStatusWorkOrderForPIA: false, submitAfterSave: true }, "test" ); expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith( diff --git a/src/helpers/recal-helper.js b/src/helpers/recal-helper.js new file mode 100644 index 000000000..fa33e7849 --- /dev/null +++ b/src/helpers/recal-helper.js @@ -0,0 +1,52 @@ +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; +} diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index acb97cba2..dc3cc8085 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -60,6 +60,7 @@ v-model="lineItems" :showAsPaid="isPia" servicePackageOptionsCmsName="ServicePackageTitle" + :isInsurance="isInsurance" :insuranceDeductible="currentDeductible" :insuranceCompanyName="insuranceCompanyName" :showInsuranceCoverageAs="showInsuranceCoverageAs" @@ -93,7 +94,6 @@ import textBlock from "@/digital-components/text-block/text-block"; //Supporting files import baseMixin from "@/mixins/base-mixin.js"; -import analyticsMixin from "@/mixins/analytics-mixin"; import store from "@/store"; import { storeActions } from "@/constants/store-actions"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; @@ -107,7 +107,9 @@ import { get12HourTimeFormat, get12HourTimeMobileFormat } from "@/helpers/date-h import { coverageStatus } from "@/constants/insurance"; import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper"; import { experimentSettings } from "@/constants/experiments"; -import experimentMixin from "@/mixins/experiment-mixin.js"; +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", @@ -169,8 +171,14 @@ export default { }; }, computed: { + isRecalibrationOnOrder() { + return containsRecalParts(this?.lineItems); + }, shouldHideRecalibration() { - return this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE); + return ( + this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)?.toLowerCase() === + "true" && this.isRecalibrationOnOrder + ); }, ShowCart() { if (this.isPia && this.submittedOrder?.settledTenderAmount == 0) { diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index ff7e6a8d8..6106e4008 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -153,6 +153,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)); @@ -551,23 +552,21 @@ export default { }, async setupPia() { this.$refs.loadingModal.showModal(); - - // if we don't have a work order in delete substatus, set the flag and submit. // we need a work order number for pia so we can pass it to safeliteHop. - if (!store.getters.order.workOrderNumber) { - try { - await submitWorkOrder({ - pageNameToLog: "payment-method", - submitAfterSave: false, - createDeleteStatusWorkOrderForPia: true, - }); - } catch (error) { - console.log("error: response from pia submit work order:" + error.message); - this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); - this.$route.query[queryStrings.DISPLAY_PIA_ALERT] = true; - return; - } + // this will create one in delete substatus. + try { + await submitWorkOrder({ + pageNameToLog: "payment-method", + submitAfterSave: false, + createUnscheduledStatusWorkOrderForPIA: true, + }); + } catch (error) { + console.log("error: response from pia submit work order:" + error.message); + this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); + this.$route.query[queryStrings.DISPLAY_PIA_ALERT] = true; + return; } + this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); this.$router.navigateWithoutSaving( this.navigationScenarios.CLICKED_PAY_NOW, @@ -588,13 +587,17 @@ 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 this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE); + return ( + this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)?.toLowerCase() === + "true" && this.isRecalibrationOnOrder + ); }, showApplePay() { return baseMixin.methods.showApplePay(); @@ -625,13 +628,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() diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index d8e08a77d..d9c1ffabf 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -7,6 +7,7 @@ import store from "@/store"; import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper"; import { nextTick } from "vue"; import { experimentSettings } from "@/constants/experiments"; +import experimentMixin from "@/mixins/experiment-mixin.js"; import baseMixin from "@/mixins/base-mixin.js"; import { containsLineItemWithPartType } from "../../helpers/service-package-helper"; @@ -391,12 +392,11 @@ describe("quote.vue", () => { }, }; const { wrapper } = setupMocks({}); - wrapper.vm.$route = { query: { isInsurance: "false" } }; //Act await quote.beforeRouteEnter.call( wrapper.vm, - { query: { fmgPage: "quote" } }, + { query: { fmgPage: "quote", isInsurance: false } }, undefined, (c) => c(wrapper.vm) ); @@ -747,6 +747,14 @@ function setupMocks({ customMountOptions }) { ...customMountOptions, }); + mountOptions.mixins = [ + { + methods: { + getSettingValue: jest.fn(), + }, + }, + ]; + baseMixin.methods.ResetExternalParamsAndHideModal = jest.fn(); baseMixin.methods.isFormValid = jest.fn().mockReturnValue(true); mountOptions.global.mocks["$store"] = store; diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index f98d4dab0..cfd51dc8b 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -113,8 +113,7 @@ import { storeActions } from "@/constants/store-actions"; import { deepClone } from "@/helpers/object-helper"; import store from "@/store"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; -import { experimentUniverses, experimentSettings } from "@/constants/experiments"; -import { getSessionKeyValue, getUserIdValue } from "@/helpers/heritage-integration/cookie-helper"; +import { experimentSettings } from "@/constants/experiments"; import { required } from "@/helpers/validation-rules"; import { errorMessages } from "@/constants/error-messages"; import { Form, defineRule } from "vee-validate"; @@ -129,12 +128,18 @@ import { queryStrings } from "@/constants/query-strings"; import { getQuerystringParameter } from "@/helpers/querystring-helper"; import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question"; import { partTypeStrings } from "@/constants/part-type-strings"; -import { containsLineItemWithPartType } from "@/helpers/service-package-helper"; +import { + containsLineItemWithPartType, + findLineItemsWithPartType, +} from "@/helpers/service-package-helper"; import { nextTick } from "vue"; import { packageNames } from "@/constants/package-names"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; +import { containsRecalParts } from "@/helpers/recal-helper"; defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); +const INSURANCE_TAB_TO_DISPLAY_THRESHOLD_DEFAULT = 300; + export default { name: "quote", async beforeRouteEnter(to, from, next) { @@ -262,7 +267,6 @@ export default { vm.addableVaps = addableVaps; vm.lineItems = lineItems; vm.availableLineItems = pricingResults; - vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems); vm.holdParentAccountNumber = holdParentAccountNumber; vm.holdBillToAccountNumber = holdBillToAccountNumber; @@ -291,8 +295,62 @@ export default { navigateToHeritageFunnel({ shouldSaveSession: true, pageNameToLog: "quote" }); } - if (store.getters.externalParameterState?.isExternalParameter) { + 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; + const defaultIsInsuranceSelectedValue = store.getters.order.payment.isInsurance; + const hideRecalCost = + experimentMixin.methods + .getSettingValue(experimentSettings.RECAL_PRICE_REMOVE) + ?.toLowerCase() === "true"; + + //Default to Insurance Tab if user Service zip is from certain States + 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; + } + } + }; + let thresholdToUse = INSURANCE_TAB_TO_DISPLAY_THRESHOLD_DEFAULT; + let internalThreshold = experimentMixin.methods.getSettingValue( + experimentSettings.INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL + ); + + if (!store.getters.externalParameterState?.isExternalParameter) { + // there are no external parameters + + if (internalThreshold) thresholdToUse = internalThreshold; + vm.isInsuranceSelected = getIsInsuranceSelectedValue( + vm.availableLineItems, + thresholdToUse + ); + baseMixin.methods.ResetExternalParamsAndHideModal(); + } else { + // there ARE external parameters + if (store.getters.externalParameterQuote.isInsurance == true) { + // did user intentionally select insurance? vm.isInsuranceSelected = true; vm.servicePackage = store.getters.externalParameterQuote.servicePackage; await nextTick(); @@ -303,10 +361,26 @@ export default { 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; + } + vm.isInsuranceSelected = getIsInsuranceSelectedValue( + vm.availableLineItems, + thresholdToUse + ); baseMixin.methods.ResetExternalParamsAndHideModal(); } - } else { - baseMixin.methods.ResetExternalParamsAndHideModal(); } }); }, @@ -326,16 +400,25 @@ export default { return Object.assign({}, this.lineItems); }, isRecalibrationOnOrder() { + return containsRecalParts(this.lineItems); + }, + isServicePackageDiscountOnOrder() { return containsLineItemWithPartType( - partTypeStrings.RECALIBRATION, - this?.availableLineItems + partTypeStrings.SERVICE_PACKAGE_DISCOUNT, + this.availableLineItems + ); + }, + servicePackageDiscountParts() { + return findLineItemsWithPartType( + partTypeStrings.SERVICE_PACKAGE_DISCOUNT, + this.availableLineItems ); }, shouldHideRecalibration() { - const recalSettingValue = experimentMixin.methods.getSettingValue( - experimentSettings.RECAL_PRICE_REMOVE + return ( + this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)?.toLowerCase() === + "true" && this.isRecalibrationOnOrder ); - return recalSettingValue; }, showAfterpayBanner() { return ( @@ -355,7 +438,7 @@ export default { const payment = store.getters.order.payment; const log = getQuerystringParameter(queryStrings.LOG); if (eval(log)) { - console.log("quote.vue pagePrereqs:"); + console.log("------------- quote.vue pagePrereqs start -----------------"); console.log( new Date() + " store.getters.order.serviceLocation.zipCode: " + @@ -381,6 +464,7 @@ export default { " store.getters.order.payment.insuranceCoverage?.isVerified: " + payment?.insuranceCoverage?.isVerified ); + console.log("----------------- quote.vue pagePrereqs end -----------------"); } return ( store.getters.order.serviceLocation.zipCode && @@ -392,30 +476,6 @@ export default { payment.insuranceCoverage?.isVerified === false) ); }, - getDefaultIsInsuranceSelectedValue(availableLineItems) { - const serviceLocationState = store.getters.order.serviceLocation.state; - // Override if coming back from QuoteDetails. Remove override after Quote release - const isInsuranceOverrideValue = this.$route.query?.isInsurance; - - const defaultIsInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance; - //Default to Insurance Tab if user Service zip is from certain States - if ( - serviceLocationState != null && - payWithInsuranceStates.find((item) => item === serviceLocationState) - ) - return true; - else if (isInsuranceOverrideValue != null) { - return isInsuranceOverrideValue == "true"; - } else if (defaultIsInsuranceSelectedValue != null) { - return defaultIsInsuranceSelectedValue; - } else { - return availableLineItems - ? baseMixin.methods.getTierOnePackagePrice( - baseMixin.methods.filterOutFees(availableLineItems) - ) > 300 - : null; - } - }, vapsItemsSelectedAction(vapsItemsSelected) { this.lineItems.vaps = vapsItemsSelected; }, @@ -517,6 +577,40 @@ export default { ); } }); + this.$nextTick(() => { + const availablePackageNames = this.$refs.servicePackage.servicePackageAnswers; + var eventLabel = ""; + availablePackageNames?.forEach((tier) => { + if (tier) { + let lineItemsToPrice = this.availableLineItems; + if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) { + lineItemsToPrice = + baseMixin.methods.filterOutRecalibration(lineItemsToPrice); + } + if (this.isServicePackageDiscountOnOrder) { + lineItemsToPrice = lineItemsToPrice?.filter((item) => { + return ( + item.partType != this.servicePackageDiscountParts[0].partType + ); + }); + } + let price = 0; + if (tier.value == "TierTwo" && this.isServicePackageDiscountOnOrder) { + lineItemsToPrice.push(...this.servicePackageDiscountParts); + } + price += baseMixin.methods.getTierOnePackagePrice( + baseMixin.methods.filterOutFees(lineItemsToPrice) + ); + price += this.$refs.servicePackage.getVapsPrice(tier.value); + const formattedPrice = parseFloat(price).toFixed(2); + eventLabel += tier.value + "_" + formattedPrice; + } + eventLabel += ","; + }); + eventLabel = eventLabel.slice(0, -1); + + this.pushEventToGA("quote", this.GaActions.SERVICE_PACKAGE_PRICE, eventLabel, true); + }); }, }, mounted() { diff --git a/src/layouts/quote/service-package-question/service-package-question.vue b/src/layouts/quote/service-package-question/service-package-question.vue index 4ef027ad5..afd592e72 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -15,7 +15,6 @@ import buttonQuestion from "@/digital-components/button-question/button-question"; import baseMixin from "@/mixins/base-mixin.js"; import { processIfStatements } from "@/helpers/cms-content-helper"; -import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected"; import servicePackageRadio from "./service-package-radio/service-package-radio"; import { partTypeStrings } from "@/constants/part-type-strings"; import { packageNames } from "@/constants/package-names"; @@ -36,6 +35,7 @@ import { getPromosWithAddableVaps, getLineItemsThatMatchPromos, } from "@/helpers/promotions-helper"; +import { containsRecalParts, getItemsWithoutRecalParts } from "@/helpers/recal-helper"; export default { name: "servicePackageQuestion", @@ -237,12 +237,10 @@ export default { return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat; }, getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) { - var lineItemsToPrice = [...this.nullSafeAvailableLineItems]; + 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 diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 022fa1183..1922d098a 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -433,6 +433,18 @@ export default { if (shopProviderData) { this.shopProviderData = shopProviderData; } + + var gaLabel = this.GaLabels.NO; + if (this.isServiceableMobile) { + gaLabel = this.GaLabels.YES; + } + + this.pushEventToGA( + this.GaCategories.APPOINTMENT, + this.GaActions.MOBILE_AVAILABLE, + gaLabel, + true + ); }, setContainsMilitaryBase(val) { if (this.zipContainsMilitaryBase !== val) { @@ -607,7 +619,7 @@ export default { }, false ); - console.log(this.billToAccountNumber); + if (this.billToAccountNumber) { this.dispatchStoreAction( this.storeActions.SAVE_BILL_TO_ACCOUNT_NUMBER, diff --git a/src/layouts/service-location/shop-question/shop-question.vue b/src/layouts/service-location/shop-question/shop-question.vue index 1b4d7dea8..d77a7eb04 100644 --- a/src/layouts/service-location/shop-question/shop-question.vue +++ b/src/layouts/service-location/shop-question/shop-question.vue @@ -52,6 +52,7 @@ import { nextTick } from "vue"; import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; import { Provider } from "@/layouts/service-location/classes/provider"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); @@ -125,6 +126,8 @@ export default { }, methods: { async getNextShopsFromList(numberToGet = 3) { + const logFirstShopsDisplayed = this.shopIndex == 0; + const shopIterator = (array, n) => { const l = array.length; return () => { @@ -160,6 +163,27 @@ export default { }; }); + var gaAction = this.GaActions.MORE_LOCATIONS_CLICKED; + if (logFirstShopsDisplayed) { + gaAction = this.GaActions.SHOPS_FIRST_DISPLAYED; + } + if (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP) { + var shops = mappedData.map((shop) => { + if (shop.value.length > 5 && shop.value.startsWith("00")) { + return shop.value.substring(1); + } else { + return shop.value; + } + }); + + this.pushEventToGA( + this.GaCategories.SERVICE_LOCATION, + gaAction, + shops.join("/"), + true + ); + } + if (this.answers.length === 0) { this.answers = mappedData; } else { diff --git a/src/layouts/service-zip/service-zip.vue b/src/layouts/service-zip/service-zip.vue index 34efac1a1..a60eb4aff 100644 --- a/src/layouts/service-zip/service-zip.vue +++ b/src/layouts/service-zip/service-zip.vue @@ -213,7 +213,7 @@ export default { const supportingItemsPromise = await this.dispatchStoreActionWithLogging( storeActions.GET_SUPPORTING_ITEMS, null, - "estimate" + "service-zip" ); const promiseResultMap = [ @@ -232,7 +232,7 @@ export default { ); // call saveSession here - navigateWithSaving saves too late in the flow - await saveSession({ pageNameToLog: "estimate" }); + await saveSession({ pageNameToLog: "service-zip" }); return this.$router.navigateWithSaving( this.navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS, this.$route diff --git a/src/layouts/vehicle/vehicle.vue b/src/layouts/vehicle/vehicle.vue index 7f629db0a..30191e4fa 100644 --- a/src/layouts/vehicle/vehicle.vue +++ b/src/layouts/vehicle/vehicle.vue @@ -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( diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index e0517080e..b66ad59dc 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -211,6 +211,13 @@ export default { payload.glassToReplace = glassString; } + //EON + if (order.eon) { + payload.eon = order.eon; + } else { + payload.eon = ""; + } + // Work Order Id if (order.workOrderId) { const parsedId = parseInt(order.workOrderId); diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index c1e60d16a..12b9a99a2 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -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,7 +95,7 @@ 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") @@ -103,14 +104,11 @@ export default { 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) { diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js index 9a062a831..a3493910e 100644 --- a/src/mixins/base-mixin.spec.js +++ b/src/mixins/base-mixin.spec.js @@ -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", () => { diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index 2749d1933..517fc7c90 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -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; diff --git a/src/router/index.js b/src/router/index.js index 266ccb855..4c57921b7 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -683,10 +683,7 @@ function updateExternalParameterState() { ); } if (externalParameterSource) { - store.commit( - storeMutations.UPDATE_EXTERNAL_PARAMETER_SOURCE, - externalParameterSource - ); + store.commit(storeMutations.UPDATE_EXTERNAL_PARAMETER_SOURCE, externalParameterSource); } } diff --git a/src/store/index.js b/src/store/index.js index a724d26c1..29289db6e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -36,9 +36,12 @@ import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations"; import { coverageStatus, coverageStatusValue, + coverageStatusEnum, coverageType, coverageTypeValue, + coverageTypeEnum, } from "@/constants/insurance"; +import { containsRecalParts } from "@/helpers/recal-helper"; // Export State const getDefaultState = () => { @@ -109,6 +112,7 @@ const getDefaultState = () => { insuranceCoverage: { isVerified: null, coverageStatus: null, + coverageSubStatus: null, coverageType: null, coverageVerificationType: null, }, @@ -690,6 +694,8 @@ export const mutations = { state.order.payment.insuranceCoverage.coverageStatus = coverageStatusValue( sessionInformation?.order.insuranceCoverage.coverageStatus ); + state.order.payment.insuranceCoverage.coverageSubStatus = + sessionInformation?.order.insuranceCoverage.coverageSubStatus; state.order.payment.insuranceCoverage.coverageType = coverageTypeValue( sessionInformation?.order.insuranceCoverage.coverageType ); @@ -769,6 +775,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; }, @@ -894,13 +918,13 @@ export const getters = { 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; } + return false; }, requiresVerifiedRedirecting: (state) => { @@ -1528,15 +1552,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, }); @@ -1818,8 +1865,8 @@ export const actions = { const lineItems = context.state.order.lineItems; const submitAfterSave = payload?.submitAfterSave === "true"; - const createDeleteStatusWorkOrderForPia = - payload?.createDeleteStatusWorkOrderForPia === "true"; + const createUnscheduledStatusWorkOrderForPIA = + payload?.createUnscheduledStatusWorkOrderForPIA === "true"; // create a new array to avoid mutating state const newGlassToReplace = convertGlassPieceNamingForApi(damage.glassToReplace); @@ -1954,7 +2001,7 @@ export const actions = { referralNumber: order.referralNumber?.toString(), referralSequenceNumber: order.referralSequenceNumber, eon: order.eon, - createDeleteStatusWorkOrderForPia: createDeleteStatusWorkOrderForPia, + createUnscheduledStatusWorkOrderForPIA: createUnscheduledStatusWorkOrderForPIA, lockToken: order.lockToken, isRecalAckOptIn: order.isRecalAckOptIn, }, @@ -2900,6 +2947,46 @@ export const actions = { externalParameterMMS.externalParameterStyle ); }, + + async getRecalPartsAndSaveToLineItems(context, { pageNameToLog }) { + // identify each part that needs recal + const glassParts = 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); + } + } + } + }, }; export default createStore({ @@ -2922,34 +3009,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) { diff --git a/src/store/store.spec.js b/src/store/store.spec.js index fb1c73f98..9c546d3be 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -3950,6 +3950,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,