Merge pull request #3001 from Safelite/feature/CASH-2025

Feature/cash 2025
This commit is contained in:
AdamCaouetteSafelite 2026-01-12 13:36:26 -05:00 committed by GitHub
commit 03cdb0058e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 229 additions and 12 deletions

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

@ -0,0 +1,139 @@
<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"
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,
},
data() {
return {
isExpanded: true,
};
},
computed: {
headerText() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
bodyText() {
return this.getCmsContent(this.cmsWidgetName, "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;
.oem-glass-question-header {
font-family: UrbanistSemibold, Arial, Helvetica, sans-serif;
color: $blue;
display: flex;
align-items: center;
justify-content: flex-start;
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='%230C7E47'/%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;
}
}
.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: rotate(180deg);
}
}
.oem-glass-question-body {
visibility: visible;
max-height: none;
padding-top: 0.5em;
margin-top: 0.5em;
}
}
}
</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,11 @@
isInsuranceSelected ? isInsuranceContinueButtonText : '' isInsuranceSelected ? isInsuranceContinueButtonText : ''
" /> " />
<oemGlassQuestion
cmsWidgetName="OemGlassQuestionWidget"
v-if="offerOem"
v-model="isOemGlassSelected" />
<saveProgressModalQuestion <saveProgressModalQuestion
modalWidgetName="SaveProgressModalWidget" modalWidgetName="SaveProgressModalWidget"
modalName="SaveProgressModal" modalName="SaveProgressModal"
@ -129,6 +134,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 +224,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 +306,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 +331,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) => ({
@ -403,6 +421,9 @@ 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 = alternateGlassPartsForOem; // oemParts
vm.isOemGlassSelected = isOemGlassSelected;
if (revalidatePromoResponse) { if (revalidatePromoResponse) {
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse( const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
@ -570,6 +591,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 +602,7 @@ export default {
packageNumber: null, packageNumber: null,
skipToInsurance: null, skipToInsurance: null,
servicePackageSelected: null, servicePackageSelected: null,
isOemGlassSelected: false,
}; };
}, },
async mounted() { async mounted() {
@ -743,6 +767,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 +813,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 +936,25 @@ 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() {
const windshieldString = "WINDSHIELD";
this.lineItems.glassParts.forEach((part, index) => {
if (part.partType === windshieldString) {
let altPartToUse = this.alternateGlassPartsForOem.find(
(altPart) => altPart.partType === windshieldString
);
if (altPartToUse) {
this.lineItems.glassParts[index] = altPartToUse;
}
}
});
},
}, },
components: { components: {
funnelHeader, funnelHeader,
@ -909,6 +971,7 @@ export default {
recalDisclaimer, recalDisclaimer,
saveProgressModalQuestion, saveProgressModalQuestion,
saveProgressPopupQuestion, saveProgressPopupQuestion,
oemGlassQuestion,
}, },
}; };
</script> </script>

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);
}, },