Merge pull request #3013 from Safelite/rlsmerge/2026.01.15-to-develop

rlsmerge 2026.01.15 to develop
This commit is contained in:
Chris 2026-01-15 10:48:58 -05:00 committed by GitHub
commit 63638f6f6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 396 additions and 73 deletions

View file

@ -12,6 +12,7 @@ const partTypeStrings = {
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT", SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT", QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT",
DONATION: "DONATION", DONATION: "DONATION",
WINDSHIELD: "WINDSHIELD",
}; };
export { partTypeStrings }; export { partTypeStrings };

View file

@ -111,6 +111,7 @@ const storeActions = {
SAVE_PAYPAL_TOKEN: "savePaypalToken", SAVE_PAYPAL_TOKEN: "savePaypalToken",
SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount", SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount",
SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn", SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn",
SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected",
SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable", SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable",
CREATE_SUBMITTED_STATE: "createSubmittedState", CREATE_SUBMITTED_STATE: "createSubmittedState",

View file

@ -69,6 +69,7 @@ const storeMutations = {
LOCK_TOKEN: "updateLockToken", LOCK_TOKEN: "updateLockToken",
UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount", UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount",
UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn", UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn",
UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected",
UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable", UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable",
UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal", UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal",

View file

@ -281,7 +281,7 @@ export default {
if (event.screenX === 0 && event.screenY === 0) { if (event.screenX === 0 && event.screenY === 0) {
return; return;
} }
this.$emit("date-selected", date); this.$emit("date-clicked", date);
}, },
getWeekStartDate(dateString) { getWeekStartDate(dateString) {
const date = convertDateStringToDate(dateString); const date = convertDateStringToDate(dateString);
@ -846,6 +846,12 @@ export default {
}, },
}, },
watch: { watch: {
isLoading(newValue) {
// if done loading dates, then set to first available date
if (newValue === false && this.firstAvailableSelectableDate) {
this.selectedDate = this.firstAvailableSelectableDate;
}
},
modelValue(newValue) { modelValue(newValue) {
this.resetField({ this.resetField({
value: newValue, value: newValue,

View file

@ -306,6 +306,12 @@ describe("partQuestions.vue...", () => {
//Assert //Assert
expect(spy).toHaveBeenNthCalledWith( expect(spy).toHaveBeenNthCalledWith(
1, 1,
storeActions.SAVE_IS_OEM_GLASS_SELECTED,
false,
false
);
expect(spy).toHaveBeenNthCalledWith(
2,
"savePartQuestionAnswers", "savePartQuestionAnswers",
[ [
{ {

View file

@ -188,6 +188,8 @@ export default {
} }
}); });
this.dispatchStoreAction(this.storeActions.SAVE_IS_OEM_GLASS_SELECTED, false, false);
// save to vuex store as order.damage.partQuestionAnswers (array) // save to vuex store as order.damage.partQuestionAnswers (array)
// used in GET_PARTS call following this one // used in GET_PARTS call following this one
await this.dispatchStoreAction( await this.dispatchStoreAction(

View file

@ -0,0 +1,167 @@
<template>
<div
class="oem-glass-question-container py-5 mt-3 mb-6"
:class="[isExpanded ? 'expanded' : '']">
<div class="oem-glass-question-header" @click="toggleIsExpanded">
{{ headerText }}
</div>
<div class="oem-glass-question-body">
<div class="oem-glass-question-copy" v-html="bodyText"></div>
<div class="oem-glass-question-option">
<checkboxQuestion
class="mt-3 ms-1"
cmsWidgetName="OemGlassInputQuestionWidget"
v-model="installOemGlass" />
</div>
</div>
</div>
</template>
<script>
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
export default {
name: "oem-part-question",
props: {
cmsWidgetName: String,
modelValue: Boolean,
oemGlassPartPrice: Number,
aftermarketGlassPartPrice: Number,
},
data() {
return {
isExpanded: false,
};
},
computed: {
headerText() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
bodyText() {
let priceDifference = this.oemGlassPartPrice - this.aftermarketGlassPartPrice;
let bodyText = this.getCmsContent(this.cmsWidgetName, "BodyText");
bodyText = bodyText.replace(
"{custom:OEMGLASSPRICE}",
"<span id='price-dif'>$" + priceDifference.toFixed(2) + "</span>"
);
return bodyText;
},
footerText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText");
},
installOemGlass: {
get() {
return this.modelValue;
},
set(newValue) {
this.$emit("update:modelValue", newValue);
},
},
},
methods: {
toggleIsExpanded() {
this.setIsExpanded(!this.isExpanded);
},
setIsExpanded(newVal) {
this.isExpanded = newVal;
},
},
components: {
checkboxQuestion,
},
};
</script>
<style lang="scss">
.oem-glass-question-container {
border-top: 1px solid gray;
@include media-breakpoint-up(lg) {
margin: 0 2rem;
}
.oem-glass-question-header {
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
color: $blue;
display: flex;
align-items: center;
justify-content: flex-start;
cursor: pointer;
span {
flex-grow: 1;
color: $black;
}
&::after {
content: "";
transition: all 0.5s ease;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 9'%3E%3Cpath d='M7.99282 0.117702C7.75716 0.116049 7.53044 0.207797 7.3623 0.37286L0.255553 7.46878C0.0862976 7.63796 -0.00878906 7.86742 -0.00878906 8.10667C-0.00878906 8.34593 0.0862976 8.57539 0.255553 8.74457C0.424808 8.91374 0.654368 9.00879 0.893731 9.00879C1.13309 9.00879 1.36265 8.91374 1.53191 8.74457L7.99282 2.27378L14.4614 8.74457C14.6307 8.91205 14.8595 9.00547 15.0977 9.00428C15.3359 9.00308 15.5638 8.90737 15.7314 8.73819C15.8989 8.56901 15.9924 8.34022 15.9912 8.10216C15.99 7.8641 15.8942 7.63627 15.725 7.46878L8.6259 0.377963C8.45765 0.21088 8.22999 0.117289 7.99282 0.117702Z' fill='%230070d1'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center center;
color: $blue;
width: 16px;
height: 16px;
display: inline-flex;
position: relative;
margin-left: 0.5rem;
transform: rotate(180deg);
}
}
.oem-glass-question-body {
max-height: 0;
transition: all 350ms ease-in;
overflow: hidden;
visibility: hidden;
.oem-glass-question-option {
margin-top: 0.5em;
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
.oem-glass-question-strikethrough-price {
font-size: smaller;
text-decoration: line-through;
}
.oem-glass-question-free-price {
display: inline-block;
font-weight: bolder;
border-radius: 1em;
background-color: $green-300;
padding: 0 0.5em;
margin-inline-start: 0.5em;
}
}
}
&.expanded {
.oem-glass-question-header {
&::after {
transform: unset;
}
}
.oem-glass-question-body {
visibility: visible;
max-height: none;
padding-top: 0.5em;
margin-top: 0.5em;
.oem-glass-question-copy {
#price-dif {
font-weight: $font-weight-700;
color: $black;
}
}
.oem-glass-question-option {
.small {
font-weight: $font-weight-700;
color: $black;
}
}
}
}
}
</style>

View file

@ -118,6 +118,9 @@ jest.mock("@/mixins/experiment-mixin.js", () => ({
if (settingName === mockExperimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY) { if (settingName === mockExperimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY) {
return false; return false;
} }
if (settingName === mockExperimentSettings.OFFER_OEM) {
return false;
}
return "test"; return "test";
}, },
}, },

View file

@ -86,6 +86,13 @@
isInsuranceSelected ? isInsuranceContinueButtonText : '' isInsuranceSelected ? isInsuranceContinueButtonText : ''
" /> " />
<oemGlassQuestion
cmsWidgetName="OemGlassQuestionWidget"
v-if="offerOem && !isInsuranceSelected"
v-model="isOemGlassSelected"
:oemGlassPartPrice="oemGlassPartPrice"
:aftermarketGlassPartPrice="aftermarketGlassPartPrice" />
<saveProgressModalQuestion <saveProgressModalQuestion
modalWidgetName="SaveProgressModalWidget" modalWidgetName="SaveProgressModalWidget"
modalName="SaveProgressModal" modalName="SaveProgressModal"
@ -129,6 +136,7 @@ import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-
import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue"; import recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue";
import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question"; import saveProgressModalQuestion from "@/fmg-components/save-progress-modal-question/save-progress-modal-question";
import saveProgressPopupQuestion from "@/fmg-components/save-progress-popup-question/save-progress-popup-question"; import saveProgressPopupQuestion from "@/fmg-components/save-progress-popup-question/save-progress-popup-question";
import oemGlassQuestion from "./oem-glass-question/oem-glass-question.vue";
// Supporting files // Supporting files
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
@ -218,19 +226,15 @@ export default {
null, null,
"quote" "quote"
); );
const experimentAllowsOfferOem = experimentMixin.methods.hasSettingEqualTo(
// call API oem-after-market-parts method
const offerOem = experimentMixin.methods.hasSettingEqualTo(
experimentSettings.OFFER_OEM, experimentSettings.OFFER_OEM,
"true" "true"
); );
if (offerOem) { const oemGlassCheckPromise = baseMixin.methods.dispatchStoreActionWithLogging(
const oemAfterMarketParts = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_OEM_AFTERMARKET_PARTS, storeActions.GET_OEM_AFTERMARKET_PARTS,
null, null,
"quote" "quote"
); );
}
const shouldExposeSkipQuote = store.getters.isRecalibrationOnOrder; const shouldExposeSkipQuote = store.getters.isRecalibrationOnOrder;
const skipQuoteExperiment = store.getters.applicationUser.experiments.find( const skipQuoteExperiment = store.getters.applicationUser.experiments.find(
@ -304,6 +308,10 @@ export default {
resultKey: "supportingItems", resultKey: "supportingItems",
promise: supportingItemsPromise, promise: supportingItemsPromise,
}, },
{
resultKey: "oemGlassCheck",
promise: oemGlassCheckPromise,
},
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
@ -325,6 +333,18 @@ export default {
lineItems.vaps = lineItems.vaps ?? []; lineItems.vaps = lineItems.vaps ?? [];
const nullSafeGlassParts = lineItems.glassParts ?? []; const nullSafeGlassParts = lineItems.glassParts ?? [];
const oemGlassCheck = resultMap.oemGlassCheck;
const offerOem = experimentAllowsOfferOem ? oemGlassCheck?.offerOem : false;
const oemGlassPieceParts = oemGlassCheck?.glassPieceParts;
const oemGlassParts =
oemGlassPieceParts && oemGlassPieceParts.length > 0
? oemGlassPieceParts[0].parts
: null;
const isOemGlassSelected = store.getters.order.damage?.installOemGlass ?? false;
let alternateGlassPartsForOem;
if (oemGlassParts) alternateGlassPartsForOem = oemGlassParts;
const quotePageDiscountPartsInfo = getAvailableQuotePageDiscounts(); const quotePageDiscountPartsInfo = getAvailableQuotePageDiscounts();
const quotePageDiscountParts = quotePageDiscountPartsInfo.map((partInfo) => ({ const quotePageDiscountParts = quotePageDiscountPartsInfo.map((partInfo) => ({
@ -342,6 +362,18 @@ export default {
...quotePageDiscountParts, ...quotePageDiscountParts,
]; ];
let pricedAlternateGlassParts = null;
if (alternateGlassPartsForOem && alternateGlassPartsForOem.length > 0) {
pricedAlternateGlassParts = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{
availableLineItems: alternateGlassPartsForOem,
},
"quote",
false
);
}
// When calling pricing from the quote page, always use the cash parent account but save the existing one in case it's unverified insurance navigating backwards // When calling pricing from the quote page, always use the cash parent account but save the existing one in case it's unverified insurance navigating backwards
var holdParentAccountNumber = store.getters.order.payment?.parentAccountNumber; var holdParentAccountNumber = store.getters.order.payment?.parentAccountNumber;
var holdBillToAccountNumber = store.getters.order.payment?.billToAccountNumber; var holdBillToAccountNumber = store.getters.order.payment?.billToAccountNumber;
@ -403,6 +435,41 @@ export default {
vm.holdParentAccountNumber = holdParentAccountNumber; vm.holdParentAccountNumber = holdParentAccountNumber;
vm.holdBillToAccountNumber = holdBillToAccountNumber; vm.holdBillToAccountNumber = holdBillToAccountNumber;
vm.skipToInsurance = shouldSkipToInsurance; vm.skipToInsurance = shouldSkipToInsurance;
vm.offerOem = offerOem;
vm.alternateGlassPartsForOem = pricedAlternateGlassParts; // oemParts
vm.isOemGlassSelected = isOemGlassSelected;
const getOEMGlassPartPrice = () => {
let price = 0;
if (vm.alternateGlassPartsForOem) {
let oemGlassPart = vm.isOemGlassSelected
? vm.lineItems.glassParts
: vm.alternateGlassPartsForOem;
if (oemGlassPart) {
price =
oemGlassPart.find(
(altPart) => altPart.partType === partTypeStrings.WINDSHIELD
)?.sellingPrice || 0;
}
}
return price;
};
const getAftermarketGlassPartPrice = () => {
let price = 0;
if (vm.alternateGlassPartsForOem) {
let aftermarketGlassPart = vm.isOemGlassSelected
? vm.alternateGlassPartsForOem
: vm.lineItems.glassParts;
if (aftermarketGlassPart) {
price = aftermarketGlassPart.find(
(altPart) => altPart.partType === partTypeStrings.WINDSHIELD
)?.sellingPrice;
}
}
return price;
};
vm.oemGlassPartPrice = getOEMGlassPartPrice();
vm.aftermarketGlassPartPrice = getAftermarketGlassPartPrice();
if (revalidatePromoResponse) { if (revalidatePromoResponse) {
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse( const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
@ -570,6 +637,8 @@ export default {
availableLineItems: null, availableLineItems: null,
lineItems: [], lineItems: [],
addableVaps: [], addableVaps: [],
offerOem: null,
alternateGlassPartsForOem: null,
servicePackage: null, servicePackage: null,
holdParentAccountNumber: null, holdParentAccountNumber: null,
holdBillToAccountNumber: null, holdBillToAccountNumber: null,
@ -579,6 +648,7 @@ export default {
packageNumber: null, packageNumber: null,
skipToInsurance: null, skipToInsurance: null,
servicePackageSelected: null, servicePackageSelected: null,
isOemGlassSelected: false,
}; };
}, },
async mounted() { async mounted() {
@ -743,6 +813,20 @@ export default {
this.lineItems.supportingItems = this.filterOutFees(this.lineItems.supportingItems); this.lineItems.supportingItems = this.filterOutFees(this.lineItems.supportingItems);
} }
if (
this.lineItems.glassParts &&
this.alternateGlassPartsForOem &&
this.alternateGlassPartsForOem.length > 0
) {
let isOEMGlassOnOrder = this.isOEMGlassOnOrder(this.lineItems.glassParts);
if (
(this.isOemGlassSelected && !isOEMGlassOnOrder) ||
(!this.isOemGlassSelected && isOEMGlassOnOrder)
) {
this.swapOEMGlassPartsOnOrder();
}
}
if (this.lineItems.glassParts?.length > 0) { if (this.lineItems.glassParts?.length > 0) {
this.dispatchStoreAction( this.dispatchStoreAction(
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING, this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
@ -775,6 +859,11 @@ export default {
}, },
false false
); );
this.dispatchStoreAction(
this.storeActions.SAVE_IS_OEM_GLASS_SELECTED,
this.isOemGlassSelected,
false
);
await savePageData( await savePageData(
this.pageName, this.pageName,
@ -893,6 +982,26 @@ export default {
const alert = createPromoSuccessAlert(lineItems.promos[0].promoCode); const alert = createPromoSuccessAlert(lineItems.promos[0].promoCode);
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade); this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
}, },
isOEMGlassOnOrder(glassParts) {
if (glassParts && glassParts.length > 0) {
return glassParts.some((part) => part.partNumber.endsWith("OEM"));
}
return false;
},
swapOEMGlassPartsOnOrder() {
this.lineItems.glassParts.forEach((part, index) => {
if (part.partType === partTypeStrings.WINDSHIELD) {
let altPartToUse = this.alternateGlassPartsForOem.find(
(altPart) => altPart.partType === partTypeStrings.WINDSHIELD
);
if (altPartToUse) {
let childParts = this.lineItems.glassParts[index].childParts;
altPartToUse.childParts = childParts;
this.lineItems.glassParts[index] = altPartToUse;
}
}
});
},
}, },
components: { components: {
funnelHeader, funnelHeader,
@ -909,6 +1018,7 @@ export default {
recalDisclaimer, recalDisclaimer,
saveProgressModalQuestion, saveProgressModalQuestion,
saveProgressPopupQuestion, saveProgressPopupQuestion,
oemGlassQuestion,
}, },
}; };
</script> </script>

View file

@ -22,7 +22,6 @@
cmsWidgetName="ScheduleYourServiceWidget" cmsWidgetName="ScheduleYourServiceWidget"
id="schedule-your-service" /> id="schedule-your-service" />
<appointmentTypeQuestion <appointmentTypeQuestion
v-if="hasSelectableDatesLoaded"
v-model="appointmentTypeFromAppointmentTypeQuestion" v-model="appointmentTypeFromAppointmentTypeQuestion"
v-show="isAppointmentTypeDisplayed" v-show="isAppointmentTypeDisplayed"
:isServiceableMobile="isServiceableMobile" :isServiceableMobile="isServiceableMobile"
@ -134,6 +133,8 @@
:isOvernightDropoff="isOvernightDropoff" /> :isOvernightDropoff="isOvernightDropoff" />
</div> </div>
<datePicker <datePicker
:currentZip="zipCode"
:currentProviderNumber="selectedProvider?.providerNumber"
v-show="appointmentType" v-show="appointmentType"
customComponentId="dateQuestion" customComponentId="dateQuestion"
selectableDatesSetting="custom" selectableDatesSetting="custom"
@ -143,7 +144,7 @@
class="text-link-small" class="text-link-small"
:getMoreDatesCallback="getMoreScheduleData" :getMoreDatesCallback="getMoreScheduleData"
validationRules="date-required" validationRules="date-required"
@date-selected="handleDateSelected" @date-clicked="handleDateClicked"
:pricingByDayBasePrice="pricingByDayBasePrice" :pricingByDayBasePrice="pricingByDayBasePrice"
:pricingByDayUpcharge="pricingByDayUpcharge" :pricingByDayUpcharge="pricingByDayUpcharge"
:showPricingByDay="showPricingByDay" :showPricingByDay="showPricingByDay"
@ -642,12 +643,12 @@ export default {
showTimeSlotQuestion() { showTimeSlotQuestion() {
if ( if (
this.selectedDate && this.selectedDate &&
this.appointmentTypeFromAppointmentTypeQuestion && this.selectedDate.includes("mobile") &&
this.timeSlotsForSelectedDate !this.isMobileSelected
) { ) {
return true; this.setSelectedDateToFirstAvailable();
} }
return false; return this.selectedDate && this.appointmentTypeFromAppointmentTypeQuestion;
}, },
isMobileStaticRecalibrationApplicable() { isMobileStaticRecalibrationApplicable() {
return ( return (
@ -875,13 +876,6 @@ export default {
? this.$refs.mobileFirstModal?.getIsModalOpen() ? this.$refs.mobileFirstModal?.getIsModalOpen()
: false; : false;
}, },
hasSelectableDatesLoaded() {
if (this.isMobileSelected) {
return this.selectableDatesMobile?.days?.length > 0;
} else {
return this.selectableDatesInshop?.days?.length > 0;
}
},
}, },
methods: { methods: {
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
@ -1196,6 +1190,8 @@ export default {
async initializeDatePicker() { async initializeDatePicker() {
this.isShowMobileFirstAppt && this.showLoadingModal(); this.isShowMobileFirstAppt && this.showLoadingModal();
this.selectedDate = null;
const includeMobileTimeSlots = this.isServiceableMobile; const includeMobileTimeSlots = this.isServiceableMobile;
const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff; const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff;
const datePickerInitialData = await this.$refs.datePicker.loadInitialData({ const datePickerInitialData = await this.$refs.datePicker.loadInitialData({
@ -1586,25 +1582,18 @@ export default {
handleWaitListRequested(value) { handleWaitListRequested(value) {
this.waitListRequested = value; this.waitListRequested = value;
}, },
handleDateSelected(date) { handleDateClicked(date) {
const previousDate = this.selectedDate; // do something to mark this as upcharge day or not...
if (date.isPricingByDayUpchargeDay) {
// check if date actually changed this.includePricingByDayUpcharge = true;
if (previousDate !== date.dateString) { } else {
this.selectedTimeSlotInfo = this.getEmptyTimeSlot(); this.includePricingByDayUpcharge = false;
this.updateFooterButtonText(this.selectedTimeSlotInfo);
} }
// TODO: REMOVE AS PART OF CASH-1634
// // do something to mark this as upcharge day or not...
// if (date.isPricingByDayUpchargeDay) {
// this.includePricingByDayUpcharge = true;
// } else {
// this.includePricingByDayUpcharge = false;
// }
}, },
getEmptyTimeSlot() { updateTimeSlot(timeSlotObj) {
return { if (!timeSlotObj?.routeCode) {
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
this.selectedTimeSlotInfo = {
timeSlot: { timeSlot: {
date: null, date: null,
routeCode: null, routeCode: null,
@ -1615,11 +1604,6 @@ export default {
}, },
isPremiumAppointment: null, isPremiumAppointment: null,
}; };
},
updateTimeSlot(timeSlotObj) {
if (!timeSlotObj?.routeCode) {
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
this.selectedTimeSlotInfo = this.getEmptyTimeSlot();
return; return;
} }
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find( const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
@ -1717,9 +1701,6 @@ export default {
}, },
handleAppointmentTypeChange(newAppointmentType) { handleAppointmentTypeChange(newAppointmentType) {
this.updateFooterButtonText(); this.updateFooterButtonText();
// if time appointment type changes, clear any selected time slot
this.selectedTimeSlotInfo = this.getEmptyTimeSlot();
if (newAppointmentType === AppointmentTypeStrings.MOBILE) { if (newAppointmentType === AppointmentTypeStrings.MOBILE) {
// Remember last shop selected if previous selection was inshop/dropoff // Remember last shop selected if previous selection was inshop/dropoff
if ( if (
@ -1728,6 +1709,17 @@ export default {
AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF) && AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF) &&
this.selectedProvider this.selectedProvider
) { ) {
this.selectedTimeSlotInfo = {
timeSlot: {
date: null,
routeCode: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
this.lastSelectedInshopOrDropoffProvider = this.selectedProvider; this.lastSelectedInshopOrDropoffProvider = this.selectedProvider;
} }
this.appointmentType = AppointmentTypeStrings.MOBILE; this.appointmentType = AppointmentTypeStrings.MOBILE;
@ -1754,8 +1746,8 @@ export default {
} else { } else {
this.appointmentType = null; this.appointmentType = null;
} }
this.setSelectedDateToFirstAvailable(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now this.setSelectedDateToFirstAvailable();
this.setDisplayWaitList(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now this.setDisplayWaitList();
}, },
setSelectedDateToFirstAvailable() { setSelectedDateToFirstAvailable() {
this.selectedDate = this.getFirstAvailableDate(); this.selectedDate = this.getFirstAvailableDate();
@ -1887,9 +1879,35 @@ export default {
watch: { watch: {
appointmentTypeFromAppointmentTypeQuestion: { appointmentTypeFromAppointmentTypeQuestion: {
handler(newValue, oldValue) { handler(newValue, oldValue) {
this.handleAppointmentTypeChange(newValue); // v-if on appointmentTypeQuestion ensures dates have been loaded by now this.handleAppointmentTypeChange(newValue);
}, },
}, },
selectedDate(newValue, oldValue) {
// Clear time slot selection if date selected changes
const selectedDate = this.getSelectedDateFromStore();
if (oldValue && this.appointmentType === AppointmentTypeStrings.MOBILE) {
oldValue = `${oldValue}-mobile`;
}
const hasValueChanged = newValue !== oldValue;
const isDateDifferent = (newValue || oldValue) !== selectedDate;
if (hasValueChanged && isDateDifferent && !this.selectedMobileFirstAppointment) {
this.selectedTimeSlotInfo = {
timeSlot: {
date: null,
routeCode: null,
startTime: null,
endTime: null,
jobMaxMinutes: null,
jobMinMinutes: null,
},
isPremiumAppointment: null,
};
}
},
selectedTimeSlotInfo(newValue) {
this.updateFooterButtonText(newValue);
},
}, },
components: { components: {
funnelHeader, funnelHeader,
@ -1902,6 +1920,7 @@ export default {
textBlock, textBlock,
timeSlotQuestion, timeSlotQuestion,
mobileFirstModal, mobileFirstModal,
alert, alert,
serviceZipModalQuestion, serviceZipModalQuestion,
appointmentTypeQuestion, appointmentTypeQuestion,

View file

@ -1,7 +1,6 @@
<template> <template>
<div class="time-slots-question"> <div class="time-slots-question">
<buttonQuestion <buttonQuestion
ref="chooseDropOffOrInshop"
v-if="isDropOffAppointmentAvailable" v-if="isDropOffAppointmentAvailable"
v-model="selectedAnswerForDropOffOrInshop" v-model="selectedAnswerForDropOffOrInshop"
@update:modelValue="dropOffSelectionChanged" @update:modelValue="dropOffSelectionChanged"
@ -24,9 +23,8 @@
</div> </div>
</buttonQuestion> </buttonQuestion>
<buttonQuestion <buttonQuestion
ref="chooseTimeSlot" ref="buttonQuestion"
v-if="displayTimeSlotQuestion" v-if="shouldDisplayTimeSlotQuestion"
v-model="selectedAnswerForTimeSlots"
@update:modelValue="timeSlotSelectionChanged" @update:modelValue="timeSlotSelectionChanged"
buttonTypeString="timeSlotModalListButton" buttonTypeString="timeSlotModalListButton"
:buttonTypeObject="timeSlotModalListButton" :buttonTypeObject="timeSlotModalListButton"
@ -36,12 +34,10 @@
isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '', isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '',
]" ]"
:answers="availableTimeSlots" :answers="availableTimeSlots"
:lazyLoad="{ :lazyLoad="{ isLazyLoad: !isMobileAppointment, amountToLoad: 10 }"
isLazyLoad: !isMobileAppointment && !selectedAnswerForTimeSlots,
amountToLoad: 10,
}"
groupName="chooseTimeSlot" groupName="chooseTimeSlot"
textPosition="text-center" textPosition="text-center"
v-model="selectedAnswerForTimeSlots"
questionText="Available times:" questionText="Available times:"
isRequired isRequired
validationRules="time-slot-required" /> validationRules="time-slot-required" />
@ -393,7 +389,7 @@ export default {
this.answersForDropOffQuestion[0].value !== PICK_A_TIME_BUTTON_VALUE this.answersForDropOffQuestion[0].value !== PICK_A_TIME_BUTTON_VALUE
); );
}, },
displayTimeSlotQuestion() { shouldDisplayTimeSlotQuestion() {
return ( return (
this.selectedAnswerForDropOffOrInshop == PICK_A_TIME_BUTTON_VALUE || this.selectedAnswerForDropOffOrInshop == PICK_A_TIME_BUTTON_VALUE ||
!this.isDropOffAppointmentAvailable !this.isDropOffAppointmentAvailable

View file

@ -80,7 +80,6 @@ export default {
}, },
set: function (newValue) { set: function (newValue) {
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
this.$emit("handle-appointment-type-change", newValue);
}, },
}, },
isMobileOnly() { isMobileOnly() {

View file

@ -198,6 +198,8 @@ export default {
false false
); );
this.dispatchStoreAction(this.storeActions.SAVE_IS_OEM_GLASS_SELECTED, false, false);
// Navigate to the next page // Navigate to the next page
this.navigateForward(matchedParts); this.navigateForward(matchedParts);
}, },

View file

@ -122,6 +122,7 @@ const getDefaultState = () => {
capabilityQuestionAnswers: null, capabilityQuestionAnswers: null,
dateOfLoss: null, dateOfLoss: null,
damageCause: null, damageCause: null,
installOemGlass: null,
}, },
lineItems: { lineItems: {
glassParts: null, glassParts: null,
@ -342,6 +343,9 @@ export const mutations = {
updateIsRecalAckOptIn(state, isRecalAckOptIn) { updateIsRecalAckOptIn(state, isRecalAckOptIn) {
state.order.isRecalAckOptIn = isRecalAckOptIn; state.order.isRecalAckOptIn = isRecalAckOptIn;
}, },
updateIsOemGlassSelected(state, isOemGlassSelected) {
state.order.damage.installOemGlass = isOemGlassSelected;
},
updateIsMSRFeeApplicable(state, isMSRFeeApplicable) { updateIsMSRFeeApplicable(state, isMSRFeeApplicable) {
state.order.isMSRFeeApplicable = isMSRFeeApplicable; state.order.isMSRFeeApplicable = isMSRFeeApplicable;
}, },
@ -712,6 +716,8 @@ export const mutations = {
state.order.damage.capabilityQuestionAnswers = state.order.damage.capabilityQuestionAnswers =
sessionInformation.order.damage.capabilityQuestionAnswers; sessionInformation.order.damage.capabilityQuestionAnswers;
state.order.damage.installOemGlass = sessionInformation.order.damage.installOemGlass;
state.order.lineItems.glassParts = sessionInformation.order.lineItems.glassParts; state.order.lineItems.glassParts = sessionInformation.order.lineItems.glassParts;
state.order.lineItems.supportingItems = sessionInformation.order.lineItems.supportingItems; state.order.lineItems.supportingItems = sessionInformation.order.lineItems.supportingItems;
state.order.lineItems.vaps = sessionInformation.order.lineItems.vaps ?? []; state.order.lineItems.vaps = sessionInformation.order.lineItems.vaps ?? [];
@ -1853,8 +1859,7 @@ export const actions = {
const serviceType = order.serviceLocation?.appointmentType; const serviceType = order.serviceLocation?.appointmentType;
const referralSeqNumber = order.referralSequenceNumber; const referralSeqNumber = order.referralSequenceNumber;
const parentAccountNumber = applicationConfig.CASH_PARENT_ACCOUNT_NUMBER; const parentAccountNumber = applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
const oemEndorsementFlag = false; // Need to update from the store when available const oemEndorsementFlag = order.damage.installOemGlass || false;
// create a new array to avoid mutating state // create a new array to avoid mutating state
const glassArrayForPayload = convertGlassPieceNamingForApi(glassArray); const glassArrayForPayload = convertGlassPieceNamingForApi(glassArray);
const resultsArrayForPayload = convertResultsForApi(resultsArray); const resultsArrayForPayload = convertResultsForApi(resultsArray);
@ -2453,6 +2458,7 @@ export const actions = {
partQuestionAnswers: order.damage.partQuestionAnswers, partQuestionAnswers: order.damage.partQuestionAnswers,
moldingQuestionAnswers: order.damage.moldingQuestionAnswers, moldingQuestionAnswers: order.damage.moldingQuestionAnswers,
capabilityQuestionAnswers: order.damage.capabilityQuestionAnswers, capabilityQuestionAnswers: order.damage.capabilityQuestionAnswers,
installOemGlass: order.damage.installOemGlass,
}, },
lineItems: { lineItems: {
glassParts: lineItems.glassParts, glassParts: lineItems.glassParts,
@ -2959,6 +2965,10 @@ export const actions = {
context.commit(storeMutations.UPDATE_IS_RECAL_ACK_OPT_IN, isRecalAckOptIn); context.commit(storeMutations.UPDATE_IS_RECAL_ACK_OPT_IN, isRecalAckOptIn);
}, },
saveIsOemGlassSelected(context, isOemGlassSelected) {
context.commit(storeMutations.UPDATE_IS_OEM_GLASS_SELECTED, isOemGlassSelected);
},
saveIsMSRFeeApplicable(context, isMSRFeeApplicable) { saveIsMSRFeeApplicable(context, isMSRFeeApplicable) {
context.commit(storeMutations.UPDATE_IS_MSR_FEE_APPLICABLE, isMSRFeeApplicable); context.commit(storeMutations.UPDATE_IS_MSR_FEE_APPLICABLE, isMSRFeeApplicable);
}, },

View file

@ -123,8 +123,8 @@ $body-color: $gray-600;
//Fonts //Fonts
$font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif; $font-family-sans-serif: UrbanistRegular, Arial, Helvetica, sans-serif;
$font-family-monospace: UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", $font-family-monospace:
monospace; UrbanistRegular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
// stylelint-enable value-keyword-case // stylelint-enable value-keyword-case
$font-family-base: $font-family-sans-serif; $font-family-base: $font-family-sans-serif;
$font-family-code: $font-family-monospace; $font-family-code: $font-family-monospace;