diff --git a/src/constants/cart-item-types.js b/src/constants/cart-item-types.js index d8ab9f66f..2f84d22c2 100644 --- a/src/constants/cart-item-types.js +++ b/src/constants/cart-item-types.js @@ -11,6 +11,7 @@ const cartItemTypes = { EARLY_BIRD: "EARLY BIRD", SUPPLIES_REPAIR: "SUPPLIES-REPAIR", SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT", + DONATION: "DONATION", }; export { cartItemTypes }; diff --git a/src/constants/experiments.js b/src/constants/experiments.js index 09dfc4140..d64849466 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -2,6 +2,7 @@ const experimentUniverses = { CONCEPT_FUNNEL: "ConceptFunnel", RECAL_PRICE_REMOVAL: "NextGen_RecalPriceRemoval", MSR: "MSR", + IGQ_SkipQuote: "NextGen_IGQSkipToInsurance", }; const experimentSettings = { @@ -22,6 +23,7 @@ const experimentSettings = { WAITLIST_THRESHOLD_DAYS: "Waitlist_Threshold_Days", PRICING_BY_DAY: "DisplayPricingByDay", FOSTER_LOVE: "DisplayFosterLove", + SKIP_TO_INSURANCE: "SkipToInsurance", }; const experimentTriggers = { diff --git a/src/constants/package-names.js b/src/constants/package-names.js index 221a75a49..0115daa61 100644 --- a/src/constants/package-names.js +++ b/src/constants/package-names.js @@ -3,3 +3,9 @@ export const packageNames = { TIER_TWO: "TierTwo", TIER_THREE: "TierThree", }; + +export const externalParamPackageLabels = { + GLASS_ONLY: "glassonly", + STANDARD: "standard", + PREMIUM: "premium", +}; diff --git a/src/constants/part-number-strings.js b/src/constants/part-number-strings.js index d373ba016..488bf0742 100644 --- a/src/constants/part-number-strings.js +++ b/src/constants/part-number-strings.js @@ -5,6 +5,7 @@ const partNumberStrings = { DONATION: "DONATION", // Fees RECYCLE_FEE: "RECYCLE FEE", + PRICING_BY_DAY_UPCHARGE: "PREMAPPT GEN", }; export { partNumberStrings }; diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js index 4681e2776..94e8598bd 100644 --- a/src/constants/part-type-strings.js +++ b/src/constants/part-type-strings.js @@ -10,6 +10,7 @@ const partTypeStrings = { REPAIR_FEE: "REPAIR FEE", EARLY_BIRD: "EARLY BIRD", SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT", + DONATION: "DONATION", }; export { partTypeStrings }; diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js index a0aaf8316..fbd165ad1 100644 --- a/src/constants/schedule-constants.js +++ b/src/constants/schedule-constants.js @@ -8,8 +8,7 @@ const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM"; const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; -// TODO; This will need updating with real PricingByDay part type -const PRICING_BY_DAY_PART_TYPE = "DISC CASHSAVE20"; +const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN"; const RouteCodeFlags = { ALL_DAY_DROP_OFF: "ALL DAY DROP OFF", diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index baa219aa1..e7cb38953 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -27,6 +27,7 @@ const storeActions = { GET_RAIN_DEFENSE: "getRainDefense", GET_SUPPORTING_ITEMS: "getSupportingItems", GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions", + SUBMIT_DONATION_PART_TO_SV2: "submitDonationPartToSV2", GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer", GET_MOLDING_QUESTIONS: "getMoldingQuestions", GET_MOBILE_FEE_PART: "getMobileFeePart", diff --git a/src/digital-components/date-picker/mixins/constants.js b/src/digital-components/date-picker/mixins/constants.js index b9092b5eb..52b973d3f 100644 --- a/src/digital-components/date-picker/mixins/constants.js +++ b/src/digital-components/date-picker/mixins/constants.js @@ -26,43 +26,36 @@ const DAYS_OF_WEEK = [ { label: "Sunday", cssClass: "sunday", - index: 0, - isPricingByDayUpchargeDay: false, + isPricingByDayUpchargeDay: true, }, { label: "Monday", cssClass: "monday", - index: 1, isPricingByDayUpchargeDay: true, }, { label: "Tuesday", cssClass: "tuesday", - index: 2, isPricingByDayUpchargeDay: false, }, { label: "Wednesday", cssClass: "wednesday", - index: 3, isPricingByDayUpchargeDay: false, }, { label: "Thursday", cssClass: "thursday", - index: 4, isPricingByDayUpchargeDay: false, }, { label: "Friday", cssClass: "friday", - index: 5, isPricingByDayUpchargeDay: true, }, { label: "Saturday", cssClass: "saturday", - index: 6, isPricingByDayUpchargeDay: true, }, ]; diff --git a/src/experiment-components/date-picker-for-pricing-by-day.vue b/src/experiment-components/date-picker-for-pricing-by-day.vue index 5bdcec336..cad976cf7 100644 --- a/src/experiment-components/date-picker-for-pricing-by-day.vue +++ b/src/experiment-components/date-picker-for-pricing-by-day.vue @@ -1,5 +1,7 @@ + + :isMSRFeeApplicable="isMSRFeeApplicable" + :donationAmount="donationAmount" + @update-donation-amount="updateDonationAmount" />
+ :showDonationError="showDonationError" + @DonationAddedEvent="addDonationToOrder" />

@@ -522,49 +525,29 @@ export default { forwardButtonAction() { window.location.assign(location.protocol + "//" + location.host); }, - async addDonation(amount, plateTemp) { - // try to call API to add donation to order - - // TEMP DUMMY CALL TO SIMULATE SUCCESS OR ERROR FROM API CALL - // TO BE REPLACED BY REAL CALL LATER - const donationResponse = await this.dispatchStoreActionWithLogging( - storeActions.LOOKUP_VIN_BY_PLATE, - { licensePlate: plateTemp, licenseState: "OH" }, - "test", + async addDonationToOrder() { + const donationResponse = await baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.SUBMIT_DONATION_PART_TO_SV2, + { + donationAmount: parseInt(this.donationAmount), + submittedOrder: this.submittedOrder, + }, false - ).catch((error) => { - return error; - }); + ); - if (donationResponse?.status == "200") { + if (donationResponse?.wasSuccessful) { this.showDonationSuccess = true; this.showDonationError = false; - const donationResponse = await baseMixin.methods.dispatchStoreAction( + await baseMixin.methods.dispatchStoreAction( storeActions.ADD_DONATION_TO_SUBMITTED_ORDER, - amount, + parseInt(this.donationAmount), false ); + this.lineItems = deepClone(this.getSubmittedOrder().lineItems); } else { this.showDonationError = true; this.showDonationSuccess = false; - const donationResponse = await baseMixin.methods.dispatchStoreAction( - storeActions.ADD_DONATION_TO_SUBMITTED_ORDER, - 0, - false - ); - } - }, - }, - watch: { - donationAmount(newValue) { - newValue = parseInt(newValue); - - // this.addDonation(newValue); - // TODO - when backend code is final, then remove the below and restore the line above - if (newValue === 5) { - this.addDonation(newValue, "FAILING_PLATE"); // throws error - } else { - this.addDonation(newValue, "2fast"); // should give successful response + this.donationAmount = 0; } }, }, diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index a87d6b0a6..49b90cfb3 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -836,6 +836,7 @@ describe("quote.vue", () => { nextFunction ); await nextTick(); + await nextTick(); expect(nextFunction).toHaveBeenCalled(); expect(baseMixin.methods.isFormValid).toHaveBeenCalled(); expect(baseMixin.methods.isFormValid()).toBe(true); diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index c062069af..fd21971b0 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -134,7 +134,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 { experimentSettings } from "@/constants/experiments"; +import { experimentSettings, experimentUniverses } from "@/constants/experiments"; import { required } from "@/helpers/validation-rules"; import { errorMessages } from "@/constants/error-messages"; import { Form, defineRule } from "vee-validate"; @@ -154,12 +154,18 @@ import { findLineItemsWithPartType, } from "@/helpers/service-package-helper"; import { nextTick } from "vue"; -import { packageNames } from "@/constants/package-names"; +import { packageNames, externalParamPackageLabels } from "@/constants/package-names"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { containsRecalParts } from "@/helpers/recal-helper"; import { externalParameterStatus } from "@/constants/external-parameters"; import { saveQuote } from "@/helpers/heritage-integration/order-helper.js"; import { getBoolFromString } from "@/helpers/boolean-helper"; +import { + getSessionKeyValue, + getUserIdValue, + getDeviceIdValue, +} from "@/helpers/heritage-integration/cookie-helper"; +import { showFmgLoadingModal } from "@/helpers/loading-modal-helper"; defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); @@ -189,6 +195,31 @@ export default { "quote" ); + const shouldExposeSkipQuote = store.getters.isRecalibrationOnOrder; + const skipQuoteExperiment = store.getters.applicationUser.experiments.find( + (e) => e.universeName === experimentUniverses.IGQ_SkipQuote + ); + const hasExposedSkipQuote = skipQuoteExperiment?.isExposed; + + const shouldSendExposeRequest = + shouldExposeSkipQuote && !hasExposedSkipQuote && skipQuoteExperiment; + + if (shouldSendExposeRequest) { + // Don't await -- response is not needed so can fire fully asynchronously. + baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE, + { + userId: getUserIdValue(), + deviceId: getDeviceIdValue(), + sessionKey: getSessionKeyValue(), + pageName: to.query.fmgPage, + experiment: skipQuoteExperiment, + }, + "quote", + false + ); + } + const promiseResultMap = [ { resultKey: "cmsContent", @@ -213,6 +244,12 @@ export default { const isEmailInStoreOnPageLoad = emailFromStore?.length > 0; const showSaveProgressPopup = isEmailInStoreOnPageLoad ? false : true; const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true; + const shouldSkipToInsurance = + getBoolFromString( + experimentMixin.methods.getSettingValue(experimentSettings.SKIP_TO_INSURANCE) + ) && + shouldExposeSkipQuote && + !hasExposedSkipQuote; const lineItems = deepClone(store.getters.order.lineItems); lineItems.vaps = lineItems.vaps ?? []; @@ -302,6 +339,7 @@ export default { vm.availableLineItems = pricingResults; vm.holdParentAccountNumber = holdParentAccountNumber; vm.holdBillToAccountNumber = holdBillToAccountNumber; + vm.skipToInsurance = shouldSkipToInsurance; if (revalidatePromoResponse) { const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse( @@ -434,21 +472,16 @@ export default { } } - // did user intentionally select insurance? - vm.isInsuranceSelected = true; - vm.servicePackage = store.getters.externalParameterQuote.servicePackage; - await nextTick(); + const insuranceSelection = true; + const servicePackage = store.getters.externalParameterQuote.servicePackage; if ( - vm.servicePackage != null && + servicePackage != null && isExternalParameter === externalParameterStatus.ACTIVE ) { - const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm); - if (isValid) { - baseMixin.methods.ResetExternalParamsAndHideModal(); - vm.forwardButtonAction(); - } + await vm.skip(insuranceSelection, servicePackage); } + baseMixin.methods.ResetExternalParamsAndHideModal(); } else { if (externalThreshold) thresholdToUse = externalThreshold; @@ -467,6 +500,11 @@ export default { baseMixin.methods.ResetExternalParamsAndHideModal(); } } + + if (vm.skipToInsurance && !vm.showSaveProgressPopup) { + // skip ahead only if no email popup. + await vm.skip(true, externalParamPackageLabels.GLASS_ONLY); + } }); }, data() { @@ -482,6 +520,7 @@ export default { showSaveProgressModal: null, isSaveProgressComplete: null, packageNumber: null, + skipToInsurance: null, }; }, mounted() { @@ -584,8 +623,13 @@ export default { updateSaveProgressAsSaved() { this.showSaveProgressModal = false; }, - closeSaveProgressPopup() { + async closeSaveProgressPopup() { this.showSaveProgressPopup = false; + + if (this.skipToInsurance) { + // skip ahead now if in IGQ skip experiment + await this.skip(true, externalParamPackageLabels.GLASS_ONLY); + } }, forwardButtonAction() { this.dispatchStoreAction( @@ -729,6 +773,20 @@ export default { this.pushEventToGA("quote", this.GaActions.SERVICE_PACKAGE_PRICE, eventLabel, true); }); }, + async skip(insuranceSelection, packageSelection) { + this.isInsuranceSelected = insuranceSelection; + this.servicePackage = packageSelection; + + // Allow selections to filter through sub-logic + await nextTick(); + await nextTick(); + + const isValid = await baseMixin.methods.isFormValid(this.$refs.theForm); + if (isValid) { + showFmgLoadingModal(true); + await this.forwardButtonAction(); + } + }, }, watch: { lineItemsCloneForWatcher: { 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 ab4772107..68f031768 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -102,6 +102,11 @@ export default { this.$emit("servicePackageDiscountSelected", supportingItems); }, + servicePackage(newValue) { + if (newValue !== null) { + this.selectDefaultPackage(); + } + }, }, computed: { nullSafeAvailableLineItems() { diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 90bd3d727..fcd20ace1 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -54,7 +54,7 @@ :estimatedServiceMinutesMaximum=" selectableDatesData.estimatedServiceMinutesMaximum " - @waitListRequested-emitted="handleWaitListRequested" + @waitListRequested="handleWaitListRequested" @time-slot-modal-closed="timeSlotModalClosed" validationRules="time-slot-selection-required" @TimeSlotSelected="forwardButtonAction" /> @@ -250,9 +250,15 @@ export default { isRecalibrationOnOrder && shouldHideRecalibration ? getItemsWithoutRecalParts(lineItems.glassParts) : (lineItems.glassParts ?? []); + const supportingItemsFromStore = lineItems?.supportingItems; const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers( lineItems.supportingItems, - { partNumbersToRemove: [partNumberStrings.RECYCLE_FEE] } + { + partNumbersToRemove: [ + partNumberStrings.RECYCLE_FEE, + partNumberStrings.PRICING_BY_DAY_UPCHARGE, + ], + } ); const lineItemsToBePriced = { glassParts: glassParts, @@ -266,8 +272,8 @@ export default { // Check to see if includePricingByDayUpcharge should already be set (based on order lineItems) let includePricingByDayUpcharge = false; - if (supportingItemsWithoutFees) { - const pricingByDayUpchargeFeeIndex = supportingItemsWithoutFees.findIndex( + if (supportingItemsFromStore) { + const pricingByDayUpchargeFeeIndex = supportingItemsFromStore?.findIndex( (item) => item.partType == PRICING_BY_DAY_PART_TYPE ); if (pricingByDayUpchargeFeeIndex > -1) { @@ -300,7 +306,9 @@ export default { }); // Get pricingByDayUpcharge needed for Pricing By Day - const pricingByDayUpchargePartPromise = getPricingByDayPartWithPrice(); + const pricingByDayUpchargePartPromise = showPricingByDay + ? getPricingByDayPartWithPrice() + : null; const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging( storeActions.GET_MOBILE_PREMIUM_FEE, @@ -348,10 +356,12 @@ export default { const resultMap = await settleAllPromises(promiseResultMap); - const pricingByDayUpcharge = await baseMixin.methods.getTotalLineItemPrice( - resultMap.pricingByDayUpchargePart, - false - ); + const pricingByDayUpcharge = showPricingByDay + ? await baseMixin.methods.getTotalLineItemPrice( + resultMap.pricingByDayUpchargePart, + false + ) + : null; const datePickerInitialData = resultMap.datePickerInitialData; datePickerInitialData.pricingByDayBasePrice = pricingByDayBasePrice; @@ -620,24 +630,26 @@ export default { const supportingItems = this.getSupportingItems(); // if we have a pricing by day upcharge, then save/update supporting items with it - const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex( - (item) => item.partType == PRICING_BY_DAY_PART_TYPE - ); + if (this.showPricingByDay) { + const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex( + (item) => item.partType == PRICING_BY_DAY_PART_TYPE + ); - if (this.includePricingByDayUpcharge) { - if (pricingByDayUpchargeFeeIndex > -1) { - supportingItems[pricingByDayUpchargeFeeIndex].laborAmount = - this.pricingByDayUpchargeLineItem.laborAmount; - supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice = - this.pricingByDayUpchargeLineItem.sellingPrice; - supportingItems[pricingByDayUpchargeFeeIndex].kitPrice = - this.pricingByDayUpchargeLineItem.kitPrice; + if (this.includePricingByDayUpcharge) { + if (pricingByDayUpchargeFeeIndex > -1) { + supportingItems[pricingByDayUpchargeFeeIndex].laborAmount = + this.pricingByDayUpchargeLineItem.laborAmount; + supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice = + this.pricingByDayUpchargeLineItem.sellingPrice; + supportingItems[pricingByDayUpchargeFeeIndex].kitPrice = + this.pricingByDayUpchargeLineItem.kitPrice; + } else { + supportingItems.push(this.pricingByDayUpchargeLineItem); + } } else { - supportingItems.push(this.pricingByDayUpchargeLineItem); - } - } else { - if (pricingByDayUpchargeFeeIndex >= 0) { - supportingItems.splice(pricingByDayUpchargeFeeIndex, 1); + if (pricingByDayUpchargeFeeIndex >= 0) { + supportingItems.splice(pricingByDayUpchargeFeeIndex, 1); + } } } diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue index 82e11ea41..0cc9cba78 100644 --- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue +++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue @@ -43,8 +43,8 @@ v-if="waitListRequested" class="mt-5 rounded waitlist-success" ref="waitlistSuccessMessage"> - - + +
item.partNumber === partNumberStrings.RECYCLE_FEE + ); + + if (recycleFeePart) { + // Get the Recycle Fee Part Price + const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, + { + availableLineItems: [recycleFeePart], + serviceZipCode: serviceZipCode, + serviceZipCodeCtu: zipCodeData.zipCodeCtu, + }, + pageNameToLog, + false + ); + + return pricingResults[0]; + } +} + export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) { // Get the Mobile Fee Part return await baseMixin.methods.dispatchStoreActionWithLogging( diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js index f38262005..3e2873fa1 100644 --- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js +++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js @@ -106,6 +106,23 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => { return Promise.resolve(mobileFeePart); }; +const mockGetPricedRecycleFeePart = (mockServiceZipCode) => { + let recycleFeePart = {}; + + if (mockServiceZipCode === "43235") { + recycleFeePart = { + partNumber: "RECYCLE FEE", + description: "RECYCLE FEE", + partType: "FEE", + laborAmount: 39.99, + sellingPrice: 0, + kitPrice: 0, + }; + } + + return Promise.resolve(recycleFeePart); +}; + const mockGetServiceabilityDetails = (mockServiceZipCode) => { const serviceabilityDetails = { isGlassServiceableInshop: true, @@ -123,6 +140,9 @@ jest.mock( getPricedMobileFeePart: jest.fn((mockServiceZipCode) => { return mockGetPricedMobileFeePart(mockServiceZipCode); }), + getPricedRecycleFeePart: jest.fn((mockServiceZipCode) => { + return mockGetPricedRecycleFeePart(mockServiceZipCode); + }), getServiceabilityDetails: jest.fn((mockServiceZipCode) => { return mockGetServiceabilityDetails(mockServiceZipCode); }), diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue index 4672cca7f..0d7a82248 100644 --- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue +++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue @@ -82,6 +82,7 @@ import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location import { deepClone } from "@/helpers/object-helper"; import { getPricedMobileFeePart, + getPricedRecycleFeePart, getServiceabilityDetails, getBillToAccountNumber, } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; @@ -267,6 +268,12 @@ export default { "service-location" ); + // retrieve recycle fee part + const recycleFeePart = await getPricedRecycleFeePart( + serviceZipCode, + "service-location" + ); + // retrieve serviceability details const serviceabilityDetails = await getServiceabilityDetails( serviceZipCode, @@ -280,6 +287,7 @@ export default { // update content related to service zip code this.$emit("updated-mobile-fee-part", mobileFeePart); + this.$emit("updated-recycle-fee-part", recycleFeePart); this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu); diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index ddb765da3..56c76bb2d 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -17,6 +17,7 @@ ref="serviceZipCodeQuestion" :mobileFeePart="mobileFeePart" @updated-mobile-fee-part="setMobileFeePart" + @updated-recycle-fee-part="setRecycleFeePart" @updated-serviceability="setServiceabilityDetails" @updated-contains-military-base="setContainsMilitaryBase" @updated-bill-to-account-number="setBillToAccountNumber" @@ -90,6 +91,7 @@ :mobileFeePart="mobileFeePart" :mobileFeeApplies="mobileFeeApplies" @updated-mobile-fee-part="setMobileFeePart" + @updated-recycle-fee-part="setRecycleFeePart" @updated-serviceability="setServiceabilityDetails" @updated-contains-military-base="setContainsMilitaryBase" @updated-mobile-ctu="setCtuForMobile" @@ -195,6 +197,7 @@ export default { selectedAppointmentType: this.getSelectedAppointmentType(), selectedProvider: this.getSelectedProvider(), mobileFeePart: null, + recycleFeePart: null, zipContainsMilitaryBase: false, zipCodeCtu: null, billToAccountNumber: null, @@ -480,6 +483,9 @@ export default { setMobileFeePart(mobileFeePart) { this.mobileFeePart = mobileFeePart; }, + setRecycleFeePart(recycleFeePart) { + this.recycleFeePart = recycleFeePart; + }, //creating async function as the computed property can not directly handle asynchronous operations or promises. async setMobileLocationQuestions(newValue) { var newZipCode = newValue.addressQuestions.zipCode; @@ -592,6 +598,7 @@ export default { }, updateAndSaveSupportingItems() { let supportingItems = store.getters.lineItems.supportingItems; + let shouldSaveSupportingItems = false; supportingItems = !supportingItems && this.isMobileStaticRecalibrationApplicable @@ -603,6 +610,21 @@ export default { return; } + // update recyle fee price + if (this.recycleFeePart) { + const recycleFeeIndex = supportingItems.findIndex( + (item) => item.partNumber == partNumberStrings.RECYCLE_FEE + ); + if (recycleFeeIndex >= 0) { + supportingItems[recycleFeeIndex].laborAmount = this.recycleFeePart.laborAmount; + supportingItems[recycleFeeIndex].sellingPrice = + this.recycleFeePart.sellingPrice; + supportingItems[recycleFeeIndex].kitPrice = this.recycleFeePart.kitPrice; + + shouldSaveSupportingItems = true; + } + } + // if we have a mobile fee, then save/update supporting items if (this.selectedAppointmentType == "Mobile") { const mobileFeeIndex = supportingItems.findIndex( @@ -617,11 +639,7 @@ export default { if (this.mobileFeePart !== null) supportingItems.push(this.mobileFeePart); } - this.dispatchStoreAction( - this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, - supportingItems, - false - ); + shouldSaveSupportingItems = true; } else { // if it's not a mobile, then make sure we remove any that may have been added const removeMobileFeeIndex = supportingItems?.findIndex( @@ -630,13 +648,18 @@ export default { if (removeMobileFeeIndex >= 0) { supportingItems.splice(removeMobileFeeIndex, 1); - this.dispatchStoreAction( - this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, - supportingItems, - false - ); + shouldSaveSupportingItems = true; } } + + // Use shouldSaveSupportingItems flag to determine if we need to save supporting items. Prevents unnecessary/multiple saves + if (shouldSaveSupportingItems) { + this.dispatchStoreAction( + this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, + supportingItems, + false + ); + } }, async forwardButtonAction() { this.navigatingForward = true; diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js index ed8a811ed..b1231991b 100644 --- a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js +++ b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.spec.js @@ -32,6 +32,23 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => { return Promise.resolve(mobileFeePart); }; +const mockGetPricedRecycleFeePart = (mockServiceZipCode) => { + let recycleFeePart = {}; + + if (mockServiceZipCode === "43235") { + recycleFeePart = { + partNumber: "RECYCLE FEE", + description: "RECYCLE FEE", + partType: "FEE", + laborAmount: 39.99, + sellingPrice: 0, + kitPrice: 0, + }; + } + + return Promise.resolve(recycleFeePart); +}; + const mockGetServiceabilityDetails = (mockServiceZipCode) => { const serviceabilityDetails = { isGlassServiceableInshop: true, @@ -49,6 +66,9 @@ jest.mock( getPricedMobileFeePart: jest.fn((mockServiceZipCode) => { return mockGetPricedMobileFeePart(mockServiceZipCode); }), + getPricedRecycleFeePart: jest.fn((mockServiceZipCode) => { + return mockGetPricedRecycleFeePart(mockServiceZipCode); + }), getServiceabilityDetails: jest.fn((mockServiceZipCode) => { return mockGetServiceabilityDetails(mockServiceZipCode); }), diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue index 24d063e5e..a2ab4134e 100644 --- a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue +++ b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue @@ -45,6 +45,7 @@ import alert from "@/ux-components/alert/alert"; import { getPricedMobileFeePart, + getPricedRecycleFeePart, getServiceabilityDetails, getBillToAccountNumber, } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; @@ -164,6 +165,12 @@ export default { "service-location" ); + // retrieve recycle fee part + const recycleFeePart = await getPricedRecycleFeePart( + serviceZipCode, + "service-location" + ); + // retrieve serviceability details const serviceabilityDetails = await getServiceabilityDetails( serviceZipCode, @@ -177,6 +184,7 @@ export default { // update content related to service zip code this.$emit("updated-mobile-fee-part", mobileFeePart); + this.$emit("updated-recycle-fee-part", recycleFeePart); this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-bill-to-account-number", billToAccountNumber); diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index 6fd6075ab..151d17dd9 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -230,6 +230,12 @@ export default { return "widget-name-" + this.cmsWidgetName; }, }, + + mounted() { + if (!store.getters.externalParameterState?.isExternalParameter) { + showFmgLoadingModal(false); + } + }, }; function encodeUriData(payload) { diff --git a/src/router/index.js b/src/router/index.js index 2230a652d..345790734 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -433,9 +433,6 @@ router.afterEach(async (to, from) => { await saveSession({ pageNameToLog: to.query.fmgPage }); } } - if (!store.getters.externalParameterState?.isExternalParameter) { - showFmgLoadingModal(false); - } // Push page view to GA analyticsMixin.methods.pushPageViewToGA(); diff --git a/src/store/index.js b/src/store/index.js index da849c127..032660c6f 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -720,7 +720,8 @@ export const mutations = { // if we're loading a session and we do not have a provider number yet, then set isInsurance to null so that // a service package is not selected by default on the quote page. if ( - !sessionInformation.order.payment.isInsurance && + (sessionInformation.order.payment.isInsurance === null || + sessionInformation.order.payment.isInsurance === undefined) && !sessionInformation.order.serviceLocation.provider?.providerNumber ) { state.order.payment.isInsurance = null; @@ -1891,6 +1892,97 @@ export const actions = { pageNameToLog: pageNameToLog, }); }, + submitDonationPartToSV2( + context, + { payload: { donationAmount, submittedOrder }, pageNameToLog } + ) { + donationAmount = parseInt(donationAmount); + /* Request contract + { + "donationAmount": 5, + "savedSessionId": "96b1a16e-410a-4d08-8421-fe36e82b2275", + "paymentInfo": { + "parentAccountNumber": 167132, + "billToAccountNumber": 87291, + "providerNumber": "005819", + "carId": "CR00069266", + "make": "Acura", + "model": "MDX", + "year": 2020, + "eon": "S1820397", + "zipCode": "43015", + "state": "OH", + "referralNumber": "1821895", + "referralDate": "2025-03-20T16:27:38.93", + "referralSequenceNumber": "1821895", + "serviceZipCode": "43015", + "isReplacement": true, + "deductible": 0, + "coverageStatus": null, + "coverageSubStatus": null, + "serverData": "ZtCNOF1tCLQBAx2NRKHE/n/fqD8zUxRfYtDuTu7uUiPpeg7i6TmwXwggssX5KsXVQbeft+PN+vXYfBFJytLVYgbafNDR0JH3izVTIOh7KGTeB6YaPuCJmodF4SfgVVDHD9LEmEH9wNzM9j3pFjgfXehOFH11SH9dNwTkXIbsGzAeu64DRyJsDdpamM/BPOxlJj60S/R8vnUM8mjyM7B9a63NskFI6BCVIh5mxDE4cSMUgb4zFtwvDgTPxJSwXqKShtoY6ryB8n5UPNkG/Z5tjVuJ7/8RV7WrURM7PN7xLfp6pVEeipWyeFnVEK33G9JOuB6yH75jPO6vMCYRNi0p0pg6laasFDy3hmfuy2W/N0qtcJLVa5N0bUMFFuPQusyEeqsEZ/zx1Srlvsk7i0DF8pTyBcm3JTF5BOWm60wg3ntEOqfRuQqzFc5updILwLAIp39CwJG/fs5Mj3HCDcUKwYlK70fAARtJwFu7wI0vqW5bUSUvJkvm1JRPlyWJ8JNY4Oq1apYqhZS9bps5ofgiOYJbBhPEboEnVukoTkQiYjgbMtBwVGKGL2nNEd77osoEg1KXhEQS+0KD3PoUu/bZkGub6FNGE6iKeFPt9gDSs+S21s22rVSaZkgaoC6SkufPpN6aV78Jx69nMIDd4go4pj2IJX4kS1pD1NEOSWjN4bgVKAtV6/bxBvhNJwnqUE+mE8eAmULsezub4LMhPqA58y/ZlO44jt/qkAw9nuVDRPBve+TdJTotID5dIqJJd6/vkDoHTVaSrbkCBbdG6d9By8AEbKuVrQS/+VurUnYEd2TGahPbOyRdNlud8RB4+4af0ZRFmiKkr6ppa5IbHbTIZ2qtyeOSbIOkv8O7xK/GL6e6Iaaz0wDHaQZC4Ea8FczjpFOlQgixfYcYJBb/W2db+uRvZ3lftVyJ/d082CGbYplOa+aTxymVcQSjBRbXzs6GZ2ytDqRYcRe94F9x/XSVj7g+mih7VS+YH0eOEp0ViTuWP8B1dVnOV5yTkdMbr8n9Q82G4v5brtQdiMDMY3HS6mxPb1IiKiqFX6Jgju8sIMxkJFZQ3iIkAJuOqUYkG1ZW7s4uI6hT46bRqzYaQA18CFu56qaMblOBFuxlJIsYsnMePh2xAH89oay5Kc88xwT2T/4+dzYQ0CqhQ4MTBB7nXelav2rMCjaLQ/V/a5r1TnLr2n0GbvODQeNl97qzi5lDYKlnuF8nOfOqZNOsPSsrsk3UZJJYZwNxDZaweHIeeg5P2ZW5agswPx/U5DkU1MzE1VAQ+DQAenaE2SLo4dSPBHMmt2gLpgu0LaCuzTR2IL/EM+Tpdn/Pcv1E7lPJIycIIwO7QGJcVkwCx8CJ/WCo6xDMq+9zjSVbqAaL5MzySZCeFCMyZxvEk7qXig6rflB3K3Jay4qSFjBxBXhOEuT6zKXyhR41IXRtZRmtPkscypFEUXvR5nJV7AQUIbljGEcikOLmSiCEnl8dI2Ny9xCHLOro8UXqVUy8dHGmTRGTE/mS1JNZ3y4XaZSyHcABVWbZcR3KIXVxd6rtUA/pBvWpbq8nH3WWlJGqkoxUVEridtXqcBuzTKg3/i6vLs0/pHGwdM38XizKcIxykvJrur3vEQ==" + } + } + */ + + /* Uncomment and use when endpoint integration is finalized + return globalMethods.callHttpClient({ + method: endpoints.AddDonationPart.method, + endpoint: endpoints.AddDonationPart.url, + payload: { + donationAmount: donationAmount, + savedSessionId: "96b1a16e-410a-4d08-8421-fe36e82b2275", + paymentInfo: { + parentAccountNumber: submittedSessionData.parentAccountNumber, + billToAccountNumber: submittedSessionData.billToAccountNumber, + providerNumber: submittedSessionData.providerNumber, + carId: "CR00069266", + make: "Acura", + model: "MDX", + year: 2020, + eon: "S1820397", + zipCode: "43015", + state: "OH", + referralNumber: "1821895", + referralDate: "2025-03-20T16:27:38.93", + referralSequenceNumber: "1821895", + serviceZipCode: "43015", + isReplacement: true, + deductible: 0, + coverageStatus: null, + coverageSubStatus: null, + serverData: "ZtCNOF1tCLQBAx2NRKHE/n/fqD8zUxRfYtDuTu7uUiPpeg7i6TmwXwggssX5KsXVQbeft+PN+vXYfBFJytLVYgbafNDR0JH3izVTIOh7KGTeB6YaPuCJmodF4SfgVVDHD9LEmEH9wNzM9j3pFjgfXehOFH11SH9dNwTkXIbsGzAeu64DRyJsDdpamM/BPOxlJj60S/R8vnUM8mjyM7B9a63NskFI6BCVIh5mxDE4cSMUgb4zFtwvDgTPxJSwXqKShtoY6ryB8n5UPNkG/Z5tjVuJ7/8RV7WrURM7PN7xLfp6pVEeipWyeFnVEK33G9JOuB6yH75jPO6vMCYRNi0p0pg6laasFDy3hmfuy2W/N0qtcJLVa5N0bUMFFuPQusyEeqsEZ/zx1Srlvsk7i0DF8pTyBcm3JTF5BOWm60wg3ntEOqfRuQqzFc5updILwLAIp39CwJG/fs5Mj3HCDcUKwYlK70fAARtJwFu7wI0vqW5bUSUvJkvm1JRPlyWJ8JNY4Oq1apYqhZS9bps5ofgiOYJbBhPEboEnVukoTkQiYjgbMtBwVGKGL2nNEd77osoEg1KXhEQS+0KD3PoUu/bZkGub6FNGE6iKeFPt9gDSs+S21s22rVSaZkgaoC6SkufPpN6aV78Jx69nMIDd4go4pj2IJX4kS1pD1NEOSWjN4bgVKAtV6/bxBvhNJwnqUE+mE8eAmULsezub4LMhPqA58y/ZlO44jt/qkAw9nuVDRPBve+TdJTotID5dIqJJd6/vkDoHTVaSrbkCBbdG6d9By8AEbKuVrQS/+VurUnYEd2TGahPbOyRdNlud8RB4+4af0ZRFmiKkr6ppa5IbHbTIZ2qtyeOSbIOkv8O7xK/GL6e6Iaaz0wDHaQZC4Ea8FczjpFOlQgixfYcYJBb/W2db+uRvZ3lftVyJ/d082CGbYplOa+aTxymVcQSjBRbXzs6GZ2ytDqRYcRe94F9x/XSVj7g+mih7VS+YH0eOEp0ViTuWP8B1dVnOV5yTkdMbr8n9Q82G4v5brtQdiMDMY3HS6mxPb1IiKiqFX6Jgju8sIMxkJFZQ3iIkAJuOqUYkG1ZW7s4uI6hT46bRqzYaQA18CFu56qaMblOBFuxlJIsYsnMePh2xAH89oay5Kc88xwT2T/4+dzYQ0CqhQ4MTBB7nXelav2rMCjaLQ/V/a5r1TnLr2n0GbvODQeNl97qzi5lDYKlnuF8nOfOqZNOsPSsrsk3UZJJYZwNxDZaweHIeeg5P2ZW5agswPx/U5DkU1MzE1VAQ+DQAenaE2SLo4dSPBHMmt2gLpgu0LaCuzTR2IL/EM+Tpdn/Pcv1E7lPJIycIIwO7QGJcVkwCx8CJ/WCo6xDMq+9zjSVbqAaL5MzySZCeFCMyZxvEk7qXig6rflB3K3Jay4qSFjBxBXhOEuT6zKXyhR41IXRtZRmtPkscypFEUXvR5nJV7AQUIbljGEcikOLmSiCEnl8dI2Ny9xCHLOro8UXqVUy8dHGmTRGTE/mS1JNZ3y4XaZSyHcABVWbZcR3KIXVxd6rtUA/pBvWpbq8nH3WWlJGqkoxUVEridtXqcBuzTKg3/i6vLs0/pHGwdM38XizKcIxykvJrur3vEQ==" + } + }, + logApiCall: true, + pageNameToLog: pageNameToLog, + }); + */ + + /* Response contract + { + "wasSuccessful": true, + "wasLocked": true + } + */ + let result; + + switch (donationAmount) { + case 1: + result = { wasSuccessful: true, wasLocked: false }; + break; + case 3: + result = { wasSuccessful: false, wasLocked: true }; + break; + case 5: + result = { wasSuccessful: false, wasLocked: false }; + break; + default: + result = { wasSuccessful: null, wasLocked: null }; // Default case if amount doesn't match + } + + return result; + }, getPartFromCapabilityQuestionAnswer(context, { payload, pageNameToLog }) { const glassLocation = payload; @@ -3206,16 +3298,16 @@ export const actions = { "running addDonationToSubmittedOrder()... adding donation to supportingItems in session storage " ); submittedOrder.lineItems.supportingItems.push({ - partNumber: "DONATION", - description: null, - partType: "DONATION", + partNumber: `DONATION_${donationAmount}`, + description: `DONATION $${donationAmount}`, + partType: partTypeStrings.DONATION, isInsurable: false, isChildPart: false, laborAmount: 0, sellingPrice: donationAmount, kitPrice: 0, - salesTax: null, - id: "donation-" + donationAmount, + salesTax: 0, + id: `donation-${donationAmount}`, }); } else { // filter out all donations