From f04c3c607cb7b91be1f491f18e2f9e451a5abcee Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Thu, 15 Jan 2026 14:45:42 -0500 Subject: [PATCH 1/4] CASH-2180: Remove QPD parts for insurance orders --- .../insurance-company/insurance-company.vue | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/layouts/insurance-company/insurance-company.vue b/src/layouts/insurance-company/insurance-company.vue index 21c62078a..0ec8fd5e9 100644 --- a/src/layouts/insurance-company/insurance-company.vue +++ b/src/layouts/insurance-company/insurance-company.vue @@ -59,6 +59,7 @@ import store from "@/store"; import { flushSaveSessionQueue } from "@/helpers/heritage-integration/order-helper"; import { errorMessages } from "@/constants/error-messages"; import { required, regex } from "@/helpers/validation-rules"; +import { partTypeStrings } from "@/constants/part-type-strings"; defineRule("insurance-company-required", required(errorMessages.INSURANCE_COMPANY_REQUIRED)); @@ -164,6 +165,8 @@ export default { // await any pending save-sessions. if you don't, we will save the new parent account into vuex and then a pending async save-session response overwrites it await flushSaveSessionQueue(); + this.removeQPDLineItemIfExists(); + await this.dispatchStoreAction( this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER, this.parentAccountNumber.toString(), @@ -193,6 +196,20 @@ export default { handlePageShow() { window.location.reload(); }, + removeQPDLineItemIfExists() { + const lineItems = deepClone(store.getters.order.lineItems); + const removeQPDpartIndex = lineItems?.supportingItems.findIndex( + (item) => item.partType == partTypeStrings.QUOTE_PAGE_DISCOUNT + ); + if (removeQPDpartIndex >= 0) { + lineItems.supportingItems.splice(removeQPDpartIndex, 1); + baseMixin.methods.dispatchStoreAction( + storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, + lineItems.supportingItems, + false + ); + } + }, }, unmounted() { window.removeEventListener("pageshow", this.handlePageShow); From 35cb35100e259a2f4abc7683950f6631aa016902 Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Thu, 15 Jan 2026 15:35:59 -0500 Subject: [PATCH 2/4] CASH-2180: Filter out QPD parts --- .../insurance-company/insurance-company.vue | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/layouts/insurance-company/insurance-company.vue b/src/layouts/insurance-company/insurance-company.vue index 0ec8fd5e9..4da99fec9 100644 --- a/src/layouts/insurance-company/insurance-company.vue +++ b/src/layouts/insurance-company/insurance-company.vue @@ -59,7 +59,6 @@ import store from "@/store"; import { flushSaveSessionQueue } from "@/helpers/heritage-integration/order-helper"; import { errorMessages } from "@/constants/error-messages"; import { required, regex } from "@/helpers/validation-rules"; -import { partTypeStrings } from "@/constants/part-type-strings"; defineRule("insurance-company-required", required(errorMessages.INSURANCE_COMPANY_REQUIRED)); @@ -198,17 +197,14 @@ export default { }, removeQPDLineItemIfExists() { const lineItems = deepClone(store.getters.order.lineItems); - const removeQPDpartIndex = lineItems?.supportingItems.findIndex( - (item) => item.partType == partTypeStrings.QUOTE_PAGE_DISCOUNT + lineItems.supportingItems = baseMixin.methods.filterOutQuotePageDiscountPart( + lineItems.supportingItems + ); + baseMixin.methods.dispatchStoreAction( + storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, + lineItems.supportingItems, + false ); - if (removeQPDpartIndex >= 0) { - lineItems.supportingItems.splice(removeQPDpartIndex, 1); - baseMixin.methods.dispatchStoreAction( - storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, - lineItems.supportingItems, - false - ); - } }, }, unmounted() { From 570b70465959d69dd0b099b1dd764ab58343e269 Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Thu, 15 Jan 2026 15:52:08 -0500 Subject: [PATCH 3/4] CASH-2180: Make asynchronous --- src/layouts/insurance-company/insurance-company.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/layouts/insurance-company/insurance-company.vue b/src/layouts/insurance-company/insurance-company.vue index 4da99fec9..1455847bd 100644 --- a/src/layouts/insurance-company/insurance-company.vue +++ b/src/layouts/insurance-company/insurance-company.vue @@ -164,7 +164,7 @@ export default { // await any pending save-sessions. if you don't, we will save the new parent account into vuex and then a pending async save-session response overwrites it await flushSaveSessionQueue(); - this.removeQPDLineItemIfExists(); + await this.removeQPDLineItemIfExists(); await this.dispatchStoreAction( this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER, @@ -195,12 +195,12 @@ export default { handlePageShow() { window.location.reload(); }, - removeQPDLineItemIfExists() { + async removeQPDLineItemIfExists() { const lineItems = deepClone(store.getters.order.lineItems); lineItems.supportingItems = baseMixin.methods.filterOutQuotePageDiscountPart( lineItems.supportingItems ); - baseMixin.methods.dispatchStoreAction( + await baseMixin.methods.dispatchStoreAction( storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, lineItems.supportingItems, false From e639edd15c4999e39531bd6dd1f93c5a56130b27 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Mon, 19 Jan 2026 06:28:15 -0500 Subject: [PATCH 4/4] CASH-2149 CASH-2149 recal ack modal for scheduling --- src/constants/analytics.js | 2 + src/constants/schedule-constants.js | 3 + src/constants/store-actions.js | 1 + src/constants/store-mutations.js | 1 + src/helpers/recal-helper.js | 8 + .../recal-ack-modal/recal-ack-modal.vue | 223 ++++++++++++++++++ src/layouts/schedule/schedule.vue | 57 ++++- src/store/index.js | 11 + src/styles/ux-variables.scss | 2 + 9 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue diff --git a/src/constants/analytics.js b/src/constants/analytics.js index 00088baa7..e156de11b 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -15,6 +15,7 @@ const GaCategories = { APPOINTMENT: "Appointment", SERVICE_LOCATION: "service-location", CONFIRMATION_CLICKED: "confirmation clicked", + RECAL_DISCLAIMER: "Recalibration disclaimer", }; const GaActions = { @@ -29,6 +30,7 @@ const GaActions = { MORE_LOCATIONS_CLICKED: "more_locations_clicked", YES: "yes", NO: "no", + ACKNOWLEDGED: "Acknowledged", }; const GaLabels = { diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js index 2b626df18..2b6171075 100644 --- a/src/constants/schedule-constants.js +++ b/src/constants/schedule-constants.js @@ -11,6 +11,8 @@ const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN"; +const RECAL_ACK_YES = "Yes"; + const RouteCodeFlags = { ALL_DAY_DROP_OFF: "ALL DAY DROP OFF", OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF", @@ -31,4 +33,5 @@ export { PRICING_BY_DAY_PART_TYPE, RouteCodeFlags, cmsWidgetFieldMappings, + RECAL_ACK_YES, }; diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 2fc0c88eb..3afd4df24 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -111,6 +111,7 @@ const storeActions = { SAVE_PAYPAL_TOKEN: "savePaypalToken", SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount", SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn", + SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling", SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected", SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable", diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 5167581e0..700f527c2 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -69,6 +69,7 @@ const storeMutations = { LOCK_TOKEN: "updateLockToken", UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount", UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn", + UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "updateIsRecalAcknowledgedForScheduling", UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected", UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable", UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal", diff --git a/src/helpers/recal-helper.js b/src/helpers/recal-helper.js index 2794bbcf6..2a3281185 100644 --- a/src/helpers/recal-helper.js +++ b/src/helpers/recal-helper.js @@ -3,6 +3,14 @@ import { partTypeStrings } from "@/constants/part-type-strings"; const recalPartTypes = [partTypeStrings.RECALIBRATION, partTypeStrings.ADAS_RECALIBRATION]; +export function anyPartWithRequiresRecalFlag(lineItems) { + if (!lineItems) { + return false; + } + + return lineItems.glassParts?.some((li) => li.requiresRecalibration === true); +} + export function isRecalPartOrHasChildRecalPart(lineItem) { if (lineItem.childParts && lineItem.childParts.length > 0) { return isRecalPart(lineItem) || containsRecalParts(lineItem.childParts); diff --git a/src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue b/src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue new file mode 100644 index 000000000..e1c823cfe --- /dev/null +++ b/src/layouts/schedule/recal-ack-modal/recal-ack-modal.vue @@ -0,0 +1,223 @@ +// This modal is used to acknowledge the recal disclaimer during scheduling. // It will trigger when +there is a glass part with requiresRecalibration = true // but there is no recalibration part in +lineItems. This mostly occurs when // a zip code that does mobile only is selected but the vehicle +requires // recalibration. + + + + diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 1ebf47c7e..a812ab5f6 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -6,6 +6,12 @@ modalWidgetName="MobileFirstModalWidget" ref="mobileFirstModal" @confirm-appointment="updateTimeSlotandNavigateForward" /> +
@@ -190,6 +196,7 @@ import shopLocation from "@/layouts/schedule/shop-location/shop-location"; import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue"; import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue"; import mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-modal.vue"; +import recalAckModal from "@/layouts/schedule/recal-ack-modal/recal-ack-modal.vue"; import shopQuestion from "@/layouts/service-location/shop-question/shop-question"; import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup"; @@ -216,6 +223,7 @@ import { RouteCodeFlags, PREMIUM_FEE_PART_TYPE, PRICING_BY_DAY_PART_TYPE, + RECAL_ACK_YES, } from "@/constants/schedule-constants"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; @@ -242,6 +250,7 @@ import { isDropOffRouteCode, getTodayDate, } from "@/layouts/schedule/helpers/schedule-helper"; +import { containsRecalParts, anyPartWithRequiresRecalFlag } from "@/helpers/recal-helper"; import { DAYS_OF_WEEK } from "@/digital-components/date-picker/mixins/constants"; import { required } from "@/helpers/validation-rules"; @@ -430,6 +439,7 @@ export default { name: "schedule", data() { return { + isRecalAcknowledgedForScheduling: this.getIsRecalAcknowledgedForSchedulingFromStore(), selectedDate: this.getSelectedDateFromStore(), selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(), mobilePremiumAppointmentFee: null, @@ -1252,6 +1262,9 @@ export default { this.isShowMobileFirstAppt && this.hideLoadingModal(); }, + getIsRecalAcknowledgedForSchedulingFromStore() { + return store.getters.order.isRecalAcknowledgedForScheduling; + }, getScheduleApiResponse, getServiceZipCtuCodeFromStore() { return store.getters.order.serviceLocation.zipCodeCtu; @@ -1370,7 +1383,25 @@ export default { hideLoadingModal() { this.$refs.loadingModal.hideModal(); }, + async closeRecalAcknowledgementPopup() { + this.showRecalAcknowledgementPopup = false; + }, async forwardButtonAction() { + const showRecalAcknowledgementModal = await this.showRecalAcknowledgementModal(); + if ( + showRecalAcknowledgementModal && + this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES + ) { + this.showRecalAcknowledgementPopup = true; + return; + } + + this.dispatchStoreAction( + storeActions.SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING, + this.isRecalAcknowledgedForScheduling, + false + ); + this.navigatingForward = true; var ctu = this.zipCodeCtu; if (this.appointmentType === AppointmentTypeStrings.MOBILE) { @@ -1772,6 +1803,26 @@ export default { setSelectedDateToFirstAvailable() { this.selectedDate = this.getFirstAvailableDate(); }, + async showRecalAcknowledgementModal() { + const order = this.$store.getters.order; + const containsRecalPart = containsRecalParts(order.lineItems); + const hasPartWithRecalRequirement = anyPartWithRequiresRecalFlag(order.lineItems); + + // if any of the glass parts contain an item with the requiresRecalibration flag set to true + // and the order does not contain a recalibration part, show the recalibration acknowledgement modal + if ( + !containsRecalPart && + hasPartWithRecalRequirement && + this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES + ) { + this.$refs.navbar.removeLoader(); + this.$refs.recalAckModal.openModal(); + return true; + } + + this.closeRecalAcknowledgementPopup(); + return false; + }, async showMobileFirstModal() { if (!this.selectedRouteCodeData?.routeCode) { const pmMobileDays = experimentMixin.methods.getSettingValue( @@ -1895,6 +1946,10 @@ export default { this.updateTimeSlot(timeSlotObj); this.forwardButtonAction(); }, + updateRecalAcknowledgedAndNavigateForward(isAcknowledged) { + this.isRecalAcknowledgedForScheduling = isAcknowledged; + this.forwardButtonAction(); + }, }, watch: { appointmentTypeFromAppointmentTypeQuestion: { @@ -1940,7 +1995,7 @@ export default { textBlock, timeSlotQuestion, mobileFirstModal, - + recalAckModal, alert, serviceZipModalQuestion, appointmentTypeQuestion, diff --git a/src/store/index.js b/src/store/index.js index 8faf39f73..e9b379b56 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -190,6 +190,7 @@ const getDefaultState = () => { lockToken: null, settledTenderAmount: 0, isRecalAckOptIn: false, + isRecalAcknowledgedForScheduling: "", isMSRFeeApplicable: false, }, applicationUser: { @@ -343,6 +344,9 @@ export const mutations = { updateIsRecalAckOptIn(state, isRecalAckOptIn) { state.order.isRecalAckOptIn = isRecalAckOptIn; }, + updateIsRecalAcknowledgedForScheduling(state, isRecalAcknowledgedForScheduling) { + state.order.isRecalAcknowledgedForScheduling = isRecalAcknowledgedForScheduling; + }, updateIsOemGlassSelected(state, isOemGlassSelected) { state.order.damage.installOemGlass = isOemGlassSelected; }, @@ -2965,6 +2969,13 @@ export const actions = { context.commit(storeMutations.UPDATE_IS_RECAL_ACK_OPT_IN, isRecalAckOptIn); }, + saveIsRecalAcknowledgedForScheduling(context, isRecalAcknowledgedForScheduling) { + context.commit( + storeMutations.UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING, + isRecalAcknowledgedForScheduling + ); + }, + saveIsOemGlassSelected(context, isOemGlassSelected) { context.commit(storeMutations.UPDATE_IS_OEM_GLASS_SELECTED, isOemGlassSelected); }, diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss index 0c275ad51..76035c6ca 100644 --- a/src/styles/ux-variables.scss +++ b/src/styles/ux-variables.scss @@ -27,6 +27,8 @@ $red-600: #ac160b; $red-700: #840900; $red-800: #5b0600; $red-900: #330300; +$red-1000: #a80019; +$red-1100: #791710; // Greens $green-100: #e3f2ea;