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/experiments.js b/src/constants/experiments.js
index a2a648a65..1d689d727 100644
--- a/src/constants/experiments.js
+++ b/src/constants/experiments.js
@@ -12,6 +12,9 @@ const experimentSettings = {
SUPPRESS_VIN_CAPTURE: "SuppressVinCapture",
DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators",
PIA_EXPERIENCE: "PIA Experience",
+ CONDITIONAL_PROVIDER_NUMBERS: "ConditionalProviderNumbers",
+ CONDITIONAL_PROVIDER_NUMBERS_2: "ConditionalProviderNumbers2",
+ CONDITIONAL_PROVIDER_SHUTOFF_DATE: "ConditionalProviderShutoffDate",
PIA_INSURANCE: "DisplayPIAInsurance",
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
IS_EMAIL_OPTIONAL: "isEmailOptional",
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/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue
index 42b7d03fb..9d5f21785 100644
--- a/src/layouts/payment-adyen/payment-adyen.vue
+++ b/src/layouts/payment-adyen/payment-adyen.vue
@@ -36,6 +36,10 @@
:isItac="isItac"
:isNoComp="isNoComp"
:isCollapsible="false" />
+
@@ -56,6 +60,7 @@ import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import alert from "@/ux-components/alert/alert";
+import textBlock from "@/digital-components/text-block/text-block";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
@@ -645,6 +650,7 @@ export default {
navbar,
cart,
alert,
+ textBlock,
},
};
@@ -671,5 +677,10 @@ export default {
margin: 0 0.5rem;
}
}
+
+ .payment-disclaimer p {
+ font-size: 0.875rem;
+ line-height: 1.2;
+ }
}
diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue
index 0fa02e80a..2a6d1d9ed 100644
--- a/src/layouts/payment-method/payment-method.vue
+++ b/src/layouts/payment-method/payment-method.vue
@@ -810,11 +810,51 @@ export default {
const piaInsurance = this.getSettingValue(experimentSettings.PIA_INSURANCE);
if (this.isInsurance) {
- return piaInsurance === "true" && this.currentDeductible > 0;
+ return (
+ (piaInsurance === "true" || this.isConditionalPIAEnabled) &&
+ this.currentDeductible > 0
+ );
} else {
- return piaExperience === "PIA Optional" || piaExperience === "PIA Required";
+ if (piaExperience?.includes("PIA Disabled")) {
+ return this.isConditionalPIAEnabled;
+ } else {
+ return piaExperience === "PIA Optional" || piaExperience === "PIA Required";
+ }
}
},
+ isConditionalPIAEnabled() {
+ const conditionalProviderNumbers = this.getSettingValue(
+ experimentSettings.CONDITIONAL_PROVIDER_NUMBERS
+ );
+ const conditionalProviderNumbers2 = this.getSettingValue(
+ experimentSettings.CONDITIONAL_PROVIDER_NUMBERS_2
+ );
+ const conditionalProviderShutoffDate = this.getSettingValue(
+ experimentSettings.CONDITIONAL_PROVIDER_SHUTOFF_DATE
+ );
+
+ const allConditionalProviderNumbers = [
+ conditionalProviderNumbers,
+ conditionalProviderNumbers2,
+ ]
+ .filter(Boolean)
+ .join(",");
+ const providerNumber =
+ this.$store.getters.order.serviceLocation.provider.providerNumber;
+ const appointmentDate = new Date(this.$store.getters.order.schedule.date);
+ const shutoffDate = new Date(conditionalProviderShutoffDate);
+ if (allConditionalProviderNumbers && conditionalProviderShutoffDate) {
+ const providerNumbersArray = allConditionalProviderNumbers
+ .split(",")
+ .map((num) => num.trim());
+ if (!providerNumbersArray.includes(providerNumber)) {
+ return false;
+ } else {
+ return appointmentDate <= shutoffDate;
+ }
+ }
+ return false;
+ },
paymentMethod() {
if (!this.isPiaEnabled) {
return paymentMethods.LATER;
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.
+
+
+
+
+
+
+ {{ modalHeaderText }}
+
+
+
+
+
+
+
diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue
index 87b542b80..f7c1ead18 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,
@@ -1233,6 +1243,9 @@ export default {
this.isShowMobileFirstAppt && this.hideLoadingModal();
},
+ getIsRecalAcknowledgedForSchedulingFromStore() {
+ return store.getters.order.isRecalAcknowledgedForScheduling;
+ },
getScheduleApiResponse,
getServiceZipCtuCodeFromStore() {
return store.getters.order.serviceLocation.zipCodeCtu;
@@ -1351,7 +1364,25 @@ export default {
hideLoadingModal() {
this.$refs.loadingModal.hideModal();
},
+ async closeRecalAcknowledgementPopup() {
+ this.showRecalAcknowledgementPopup = false;
+ },
async forwardButtonAction() {
+ const showRecalAcknowledgementModal = 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) {
@@ -1675,7 +1706,27 @@ export default {
this.selectedProvider = this.shopProviderData.shopProviders[0];
}
},
- handleZipCodeChange(newZipCode) {
+ async handleZipCodeChange(newZipCode) {
+ // if we were showing the recal ack modal and now are changing the zip, we need to navigate
+ // to service-zip so we can get call parts again to add the recal part if needed
+ if (this.shouldShowRecalAckModal()) {
+ await baseMixin.methods.dispatchStoreAction(
+ storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
+ {
+ zipCode: newZipCode.zipCode,
+ state: newZipCode.state,
+ zipCodeCtu: newZipCode.zipCodeCtu,
+ },
+ false
+ );
+
+ this.$router.navigateWithSaving(
+ this.navigationScenarios.ZIP_CODE_CHANGED_RECAL_ACK,
+ this.pageName
+ );
+ return;
+ }
+
this.resetMobileLocation();
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
@@ -1752,6 +1803,32 @@ export default {
setSelectedDateToFirstAvailable() {
this.selectedDate = this.getFirstAvailableDate();
},
+ showRecalAcknowledgementModal() {
+ if (this.shouldShowRecalAckModal()) {
+ this.$refs.navbar.removeLoader();
+ this.$refs.recalAckModal.openModal();
+ return true;
+ }
+
+ this.closeRecalAcknowledgementPopup();
+ return false;
+ },
+ shouldShowRecalAckModal() {
+ // 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
+ const order = this.$store.getters.order;
+ const containsRecalPart = containsRecalParts(order.lineItems);
+ const hasPartWithRecalRequirement = anyPartWithRequiresRecalFlag(order.lineItems);
+
+ if (
+ !containsRecalPart &&
+ hasPartWithRecalRequirement &&
+ this.isRecalAcknowledgedForScheduling !== RECAL_ACK_YES
+ ) {
+ return true;
+ }
+ return false;
+ },
async showMobileFirstModal() {
if (!this.selectedRouteCodeData?.routeCode) {
const pmMobileDays = experimentMixin.methods.getSettingValue(
@@ -1875,6 +1952,10 @@ export default {
this.updateTimeSlot(timeSlotObj);
this.forwardButtonAction();
},
+ updateRecalAcknowledgedAndNavigateForward(isAcknowledged) {
+ this.isRecalAcknowledgedForScheduling = isAcknowledged;
+ this.forwardButtonAction();
+ },
},
watch: {
appointmentTypeFromAppointmentTypeQuestion: {
@@ -1920,7 +2001,7 @@ export default {
textBlock,
timeSlotQuestion,
mobileFirstModal,
-
+ recalAckModal,
alert,
serviceZipModalQuestion,
appointmentTypeQuestion,
diff --git a/src/router/constants/navigation-scenarios.js b/src/router/constants/navigation-scenarios.js
index c2247d59b..0d0ee836f 100644
--- a/src/router/constants/navigation-scenarios.js
+++ b/src/router/constants/navigation-scenarios.js
@@ -57,6 +57,7 @@ const navigationScenarios = {
// Schedule
CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION",
+ ZIP_CODE_CHANGED_RECAL_ACK: "ZIP_CODE_CHANGED_RECAL_ACK",
// Payment
CLICKED_INSURANCE: "CLICKED_INSURANCE",
diff --git a/src/router/constants/routing-table.js b/src/router/constants/routing-table.js
index 48cd49b3f..3b237d747 100644
--- a/src/router/constants/routing-table.js
+++ b/src/router/constants/routing-table.js
@@ -486,6 +486,10 @@ const routingTable = function () {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,
destinationPageData: routeData.MOBILE_DETAILS,
},
+ {
+ scenario: navigationScenarios.ZIP_CODE_CHANGED_RECAL_ACK,
+ destinationPageData: routeData.SERVICE_ZIP,
+ },
],
},
{
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;