diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index e9152dd9d..4da3982cd 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -322,7 +322,6 @@ export default { false ); } - this.$emit("itemRemoved"); }, async saveVaps(lineItems) { diff --git a/src/fmg-components/promo-modal-question/promo-modal-question.vue b/src/fmg-components/promo-modal-question/promo-modal-question.vue index 1e87d5bb5..8efad6f3e 100644 --- a/src/fmg-components/promo-modal-question/promo-modal-question.vue +++ b/src/fmg-components/promo-modal-question/promo-modal-question.vue @@ -290,18 +290,8 @@ export default { ); if (promoCodeData.isValid) { if (this.taxPromos) { - const vapsToAdd = getVapsThatNeedToBeAddedToSatisfyPromos( - promoCodeData.promoCode, - this.addableVaps, - this.lineItems - ); - const pricedLineItemsToTax = { - glassParts: this.lineItems.glassParts, - promos: promoCodeData.promoCode, - supportingItems: this.lineItems.supportingItems, - vaps: [...this.lineItems.vaps, ...vapsToAdd], - }; - + const pricedLineItemsToTax = []; + pricedLineItemsToTax.push(...promoCodeData.promoCode); // promoCodeData.promoCode should be an array const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging( storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA, @@ -324,10 +314,25 @@ export default { // Match all line items to the line items as they are in the store // and rebuild the original structure. - this.lineItems = taxedLineItems; - } else { - this.lineItems?.promos.push(...promoCodeData.promoCode); + this.lineItems = mapTaxedLineItemsToStoreFormat( + taxedLineItems, + this.lineItems + ); + const taxedVaps = mapTaxedLineItemsToStoreFormat( + taxedLineItems, + this.addableVaps + ); + + const getVaps = getVapsThatNeedToBeAddedToSatisfyPromos( + promoCodeData.promoCode, + taxedVaps, + this.lineItems + ); + + this.lineItems.vaps?.push(...getVaps); } + this.lineItems?.promos.push(...promoCodeData.promoCode); + this.$emit("promoAdded", this.lineItems); this.closeModal(); diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 214b6111e..3f7124f84 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -34,8 +34,7 @@ :isItac="isItac" :isNoComp="isNoComp" :isExpandedOnLoad="false" - :isMSRFeeApplicable="isMSRFeeApplicable" - @itemRemoved="reTaxItemsOnOrder" /> + :isMSRFeeApplicable="isMSRFeeApplicable" /> wiper.partType == partTypeStrings.FRONT_WIPER + ) ?? []; + + const rearWipersOnOrder = + lineItemsFromStore.vaps.filter( + (wiper) => wiper.partType == partTypeStrings.REAR_WIPER + ) ?? []; + + const orderHasFrontWipers = frontWipersOnOrder.length > 0; + const orderHasRearWipers = rearWipersOnOrder.length > 0; + + const wipersPromise = + !orderHasFrontWipers || !orderHasRearWipers + ? baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.GET_WIPERS, + { + serviceZipCode: store.getters.order.serviceLocation.zipCode, + carId: store.getters.vehicle.carId, + }, + "payment-method" + ) + : Promise.resolve([]); + + const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.GET_RAIN_DEFENSE, null, "payment-method" ); + // const reviewDropdownPromise = reviewDropdown.methods.loadInitialData(); + // (removed temporarily for Heritage parity effort) + const promiseResultMap = [ { resultKey: "cmsContent", @@ -220,62 +240,80 @@ export default { resultKey: "rainRepel", promise: rainRepelPromise, }, + // { + // resultKey: "reviewDropdownData", + // promise: reviewDropdownPromise, + // }, + // (removed temporarily for Heritage parity effort) ]; const resultMap = await settleAllPromises(promiseResultMap); - let lineItemsFromStore = deepClone(store.getters.order.lineItems); const glassParts = lineItemsFromStore.glassParts ?? []; const supportingItems = lineItemsFromStore.supportingItems ?? []; const vaps = lineItemsFromStore.vaps ?? []; - const availableVaps = [resultMap.rainRepel, ...resultMap.wipers]; + // if the order already has wipers on it from the quote page, use those as the available wipers + // instead of what comes from the backend. This is to prevent issues with part interchange. + const availableFrontWipers = orderHasFrontWipers + ? frontWipersOnOrder + : (resultMap.wipers.filter((wiper) => wiper.partType == partTypeStrings.FRONT_WIPER) ?? + []); + const availableRearWipers = orderHasRearWipers + ? rearWipersOnOrder + : (resultMap.wipers.filter((wiper) => wiper.partType == partTypeStrings.REAR_WIPER) ?? + []); - const pricedAvailableVaps = await baseMixin.methods.dispatchStoreActionWithLogging( + const allLineItems = [ + resultMap.rainDefense, + ...supportingItems, + ...availableFrontWipers, + ...availableRearWipers, + ...glassParts, + ...vaps, + ]; + + const lineItemsToTax = Array.from( + new Map(allLineItems.map((item) => [item.partNumber, item])).values() + ); + + const availableVaps = [ + resultMap.rainDefense, + ...availableFrontWipers, + ...availableRearWipers, + ]; + + const pricedLineItemsToTax = await baseMixin.methods.dispatchStoreActionWithLogging( storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, { - availableLineItems: availableVaps, + availableLineItems: lineItemsToTax, }, "payment-method", false ); - const lineItemsOnOrderAndAvailableVaps = [ - ...pricedAvailableVaps, - ...glassParts, - ...supportingItems, - ...vaps, - ]; - // Promo logic // Populate the previous state of promos for toast message usage in "next()" const oldActivePromos = store.getters.lineItems.promos?.slice(0); const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0); const promoCodeFromQueryString = consumeQueryFromStash(queryStrings.PROMO); - // New promos are saved to store with this const { validatePromoResponse, revalidatePromoResponse } = await revalidatePromosAndValidateQueryStringPromo( promoCodeFromQueryString, - lineItemsOnOrderAndAvailableVaps, + pricedLineItemsToTax, "payment-method" ); - // update lineItemsFromStore with newly added promos - let lineItemsForCart = deepClone(store.getters.order.lineItems); - delete lineItemsForCart.serverData; - // End of promo logic - // - - const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos( - lineItemsForCart.promos ?? [], - availableVaps, - lineItemsForCart + // Add newly validated promos to the array to get taxed + const newValidatedPromos = validatePromoResponse?.orderPromos ?? []; + newValidatedPromos.push( + ...(revalidatePromoResponse ? revalidatePromoResponse.promoLineItems : []) ); - lineItemsForCart.vaps = lineItemsForCart.vaps ?? []; - lineItemsForCart.vaps.push(...vapsToAddToCart); - // Tax items on order - lineItemsForCart = await baseMixin.methods.dispatchStoreActionWithLogging( + pricedLineItemsToTax.push(...newValidatedPromos); + // End of promo logic + + const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging( storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA, { billToAccountNumber: store.getters.payment.billToAccountNumber, @@ -284,17 +322,31 @@ export default { serviceLocationCity: store.getters.order.serviceLocation.city, serviceLocationState: store.getters.order.serviceLocation.state, serviceLocationZipCode: store.getters.order.serviceLocation.zipCode, - pricedLineItems: lineItemsForCart, + pricedLineItems: pricedLineItemsToTax, }, "payment-method", false ); + // Match all line items to the line items as they are in the store + // and rebuild the original structure. + const lineItems = mapTaxedLineItemsToStoreFormat(taxedLineItems, lineItemsFromStore); + const taxedVaps = mapTaxedLineItemsToStoreFormat(taxedLineItems, availableVaps); + + lineItems.promos = newValidatedPromos ?? []; + const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos( + newValidatedPromos, + taxedVaps, + lineItems + ); + lineItems.vaps = lineItems.vaps ?? []; + lineItems.vaps.push(...vapsToAddToCart); + // Call the "next" function to complete the transition to this page. next((vm) => { vm.setCmsContent(resultMap.cmsContent); - vm.availableVaps = pricedAvailableVaps; - vm.lineItems = lineItemsForCart; + vm.availableVaps = taxedVaps; + vm.lineItems = lineItems; vm.inactivePromos = removeCurrentlyActivePromoCodesFromInactivePromos( vm.lineItems.promos, vm.inactivePromos @@ -589,22 +641,6 @@ export default { this.pageName ); }, - async reTaxItemsOnOrder() { - this.lineItems = await baseMixin.methods.dispatchStoreActionWithLogging( - storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA, - { - billToAccountNumber: store.getters.payment.billToAccountNumber, - providerNumber: store.getters.order.serviceLocation.provider.providerNumber, - appointmentType: store.getters.order.serviceLocation.appointmentType, - serviceLocationCity: store.getters.order.serviceLocation.city, - serviceLocationState: store.getters.order.serviceLocation.state, - serviceLocationZipCode: store.getters.order.serviceLocation.zipCode, - pricedLineItems: this.lineItems, - }, - "payment-method", - false - ); - }, hasSubmittedOrder() { return baseMixin.methods.hasSubmittedOrder(); }, @@ -825,6 +861,7 @@ export default { ) { return; } + if (oldValue.promos.length < newValue.promos.length) { const oldPromoCodes = oldValue.promos.map( (promoObject) => promoObject.promoCode diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 8bab96d5f..c76222cac 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -65,7 +65,6 @@ pageName="quote" :taxPromos="false" :useDefaultCashParentAccount="true" - @promoAdded="promoAdded" modalWidgetName="PromoModalWidget" /> @@ -577,6 +576,9 @@ export default { isInsuranceContinueButtonText() { return this.getCmsContent("isInsuranceContinueButtonText", "Text"); }, + lineItemsCloneForWatcher() { + return Object.assign({}, this.lineItems); + }, isRecalibrationOnOrder() { return store.getters.isRecalibrationOnOrder; }, @@ -852,9 +854,31 @@ export default { await this.forwardButtonAction(); } }, - promoAdded(lineItems) { - const alert = createPromoSuccessAlert(lineItems.promos[0].promoCode); - this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade); + }, + watch: { + lineItemsCloneForWatcher: { + handler(newValue, oldValue) { + if ( + !oldValue || + oldValue.length == 0 || + !oldValue.vaps || + !newValue || + newValue.length == 0 + ) { + return; + } + if (oldValue.promos.length < newValue.promos.length) { + const oldPromoCodes = oldValue.promos.map( + (promoObject) => promoObject.promoCode + ); + const newlyActivatedPromoCodes = newValue.promos.filter( + (newPromo) => !oldPromoCodes.includes(newPromo.promoCode) + ); + const alert = createPromoSuccessAlert(newlyActivatedPromoCodes[0].promoCode); + this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade); + } + }, + deep: true, }, }, components: { diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 7c9d9f444..716c819e2 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -460,47 +460,111 @@ export default { }; }, async beforeRouteEnter(to, from, next) { - const serviceZipCode = store.getters.order.serviceLocation.zipCode; + const isPricingByDayExperiment = experimentMixin.methods.hasSettingEqualTo( + experimentSettings.PRICING_BY_DAY, + "true" + ); + const showPricingByDay = !store.getters.payment.isInsurance && isPricingByDayExperiment; + // Get pricingByDayBasePrice needed for Pricing By Day + const lineItems = deepClone(store.getters.order.lineItems); + const isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder; + const shouldHideRecalibration = + experimentMixin.methods.hasSettingEqualTo( + experimentSettings.RECAL_PRICE_REMOVE, + "true" + ) && isRecalibrationOnOrder; + const glassParts = + isRecalibrationOnOrder && shouldHideRecalibration + ? getItemsWithoutRecalParts(lineItems.glassParts) + : (lineItems.glassParts ?? []); + const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers( + lineItems.supportingItems, + { + partNumbersToRemove: [ + partNumberStrings.RECYCLE_FEE, + partNumberStrings.PRICING_BY_DAY_UPCHARGE, + ], + } + ); + const lineItemsToBePriced = { + glassParts: glassParts, + supportingItems: supportingItemsWithoutFees, + vaps: lineItems.vaps ?? [], + promos: lineItems.promos ?? [], + }; + const priceString = getAmountDue(lineItemsToBePriced, false); // pass the IncludeTax param as false + const priceStringIntegerRoundedDown = priceString?.split(".")[0]; // same method used as getDisplayPrice() in service-package-radio used on /quote + const pricingByDayBasePrice = parseInt(priceStringIntegerRoundedDown); + let includePricingByDayUpcharge = false; + let appointmentType = store.getters.order.serviceLocation.appointmentType; + + // Check to see if date should be pre-selected + let scheduleFromStore = await store.getters.order.schedule; + let preSelectedDate; + + if (scheduleFromStore.date && scheduleFromStore.date.length > 0) { + preSelectedDate = scheduleFromStore.date; + + if (appointmentType === AppointmentTypeStrings.MOBILE) { + preSelectedDate += "-mobile"; + } + + // Check to see if pre-selected date should have pricing by day upcharge + if (showPricingByDay) { + // is this preSelectedDate a higher priced pricingByDay day? + const dayIndex = convertDateStringToDate(scheduleFromStore?.date).getDay(); + const dayObject = DAYS_OF_WEEK[dayIndex]; + if (dayObject.isPricingByDayUpchargeDay) { + includePricingByDayUpcharge = true; + } + } + } + + // Set up promises const cmsContentPromise = fetchCmsContentForPage(to.name); + const alertReasonsPromise = locationAlerts.methods.loadInitialData( store.getters.order.serviceLocation.zipCodeCtu, store.getters.order.serviceLocation.provider?.address?.zipCodeCtu ); + + const serviceZipCode = store.getters.order.serviceLocation.zipCode; + const zipCodeDataPromise = getZipCodeData(serviceZipCode, to.name); const serviceabilityDetailsPromise = getServiceabilityDetails( serviceZipCode, null, - "schedule" - ); - let zipCodeData; - const zipCodeDataPromise = getZipCodeData(serviceZipCode, to.name); - let shopProviderData; - const shopProviderDataPromise = getShopProviderData(serviceZipCode, to.name); - const zipCodeDataAndShopProviderDataPromise = Promise.all([ - zipCodeDataPromise, - shopProviderDataPromise, - ]); - const mobileFeePartPromise = zipCodeDataAndShopProviderDataPromise.then( - ([zipCodeDataResult, shopProviderDataResult]) => { - zipCodeData = zipCodeDataResult; - shopProviderData = shopProviderDataResult; - return baseMixin.methods.dispatchStoreActionWithLogging( - storeActions.GET_MOBILE_FEE_PART, - { - serviceZipCode: serviceZipCode, - serviceZipCodeCtu: zipCodeData.zipCodeCtu, - mobileProviderNumber: shopProviderData.data.mobileProviderNumber, - }, - to.name, - false - ); - } + to.name ); + const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, to.name); + const shopProviderData = await getShopProviderData(serviceZipCode, to.name); + const providerNumber = shopProviderData?.data?.shopProviders[0]?.providerNumber; + + // Get pricingByDayUpcharge needed for Pricing By Day + const pricingByDayUpchargePartPromise = showPricingByDay + ? getPricingByDayPartWithPrice() + : null; + const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging( storeActions.GET_MOBILE_PREMIUM_FEE, null, to.name ); + const premiumFeeWithPricePromise = premiumFeePromise.then((result) => { + if (result.data) { + return baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, + { + availableLineItems: [result.data], + }, + to.name, + false + ); + } else { + return result.data; + } + }); + // Settle promises and get results const promiseResultMap = [ { @@ -511,6 +575,18 @@ export default { resultKey: "alertReasons", promise: alertReasonsPromise, }, + { + resultKey: "pricingByDayUpchargePart", + promise: pricingByDayUpchargePartPromise, + }, + { + resultKey: "premiumFeeWithPrice", + promise: premiumFeeWithPricePromise, + }, + { + resultKey: "zipCodeData", + promise: zipCodeDataPromise, + }, { resultKey: "mobileFeePart", promise: mobileFeePartPromise, @@ -519,70 +595,39 @@ export default { resultKey: "serviceabilityDetails", promise: serviceabilityDetailsPromise, }, - { - resultKey: "premiumFee", - promise: premiumFeePromise, - }, ]; + const resultMap = await settleAllPromises(promiseResultMap); - const itemsToPrice = []; - if (resultMap.mobileFeePart) { - itemsToPrice.push(resultMap.mobileFeePart); - } - if (resultMap.premiumFee) { - itemsToPrice.push(resultMap.premiumFee); - } - let pricedMobileFeePart = null; - let pricedPremiumFee = null; - - if (itemsToPrice.length) { - const pricedItems = await baseMixin.methods.dispatchStoreActionWithLogging( - storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, - { - availableLineItems: itemsToPrice, - }, - to.name, - false - ); - pricedMobileFeePart = pricedItems.find( - (item) => item.partNumber === resultMap.mobileFeePart.partNumber - ); - pricedPremiumFee = pricedItems.find( - (item) => item.partNumber === resultMap.premiumFee.partNumber - ); - } - - let appointmentType = store.getters.order.serviceLocation.appointmentType; - // Check to see if date should be pre-selected - let scheduleFromStore = await store.getters.order.schedule; - let preSelectedDate; - if (scheduleFromStore.date && scheduleFromStore.date.length > 0) { - preSelectedDate = scheduleFromStore.date; - - if (appointmentType === AppointmentTypeStrings.MOBILE) { - preSelectedDate += "-mobile"; - } - } + // add pricing by day data + const pricingByDayUpcharge = + showPricingByDay && resultMap.pricingByDayUpchargePart + ? await baseMixin.methods.getTotalLineItemPrice( + resultMap.pricingByDayUpchargePart, + false + ) + : null; // Call the "next" function to complete the transition to this page. next((vm) => { vm.setCmsContent(resultMap.cmsContent); vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons); - vm.mobilePremiumAppointmentFee = pricedPremiumFee; + vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice + ? resultMap.premiumFeeWithPrice[0] + : null; vm.updateFooterButtonText(vm.selectedTimeSlotInfo); - vm.pricingByDayUpchargeLineItem = null; // Pricing By Day Upcharge Line Item is not used in this version - vm.includePricingByDayUpcharge = false; // Pricing By Day Upcharge is not used in this version - vm.isPricingByDayExperiment = false; // Pricing By Day Experiment is not used in this version - vm.pricingByDayBasePrice = null; // Pricing By Day Base Price is not used in this version - vm.pricingByDayUpcharge = null; // Pricing By Day Upcharge is not used in this version - vm.showPricingByDay = false; // Pricing By Day is not used in this version + vm.pricingByDayUpchargeLineItem = resultMap.pricingByDayUpchargePart; + vm.includePricingByDayUpcharge = includePricingByDayUpcharge; + vm.isPricingByDayExperiment = isPricingByDayExperiment; + vm.pricingByDayBasePrice = pricingByDayBasePrice; + vm.pricingByDayUpcharge = pricingByDayUpcharge; + vm.showPricingByDay = showPricingByDay; vm.preSelectedDate = preSelectedDate; vm.appointmentType = appointmentType; vm.setData( - zipCodeData, + resultMap.zipCodeData, resultMap.serviceabilityDetails, - pricedMobileFeePart, + resultMap.mobileFeePart, shopProviderData.data ); vm.initializeDatePicker(); diff --git a/src/store/index.js b/src/store/index.js index 1fa9b4f14..402ff212f 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2958,14 +2958,8 @@ export const actions = { pageNameToLog, } ) { - const arrayOfLineItems = [ - ...(pricedLineItems.glassParts ?? []), - ...(pricedLineItems.promos ?? []), - ...(pricedLineItems.supportingItems ?? []), - ...(pricedLineItems.vaps ?? []), - ]; const flattenedLineItemsWithChildParts = - getFlattenedArrayOfLineItemsWithChildParts(arrayOfLineItems); + getFlattenedArrayOfLineItemsWithChildParts(pricedLineItems); const lineItemsWithOnlyPriceInfo = flattenedLineItemsWithChildParts.map((lineItem) => ({ partNumber: lineItem.partNumber, @@ -3014,12 +3008,8 @@ export const actions = { }); context.commit(storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA, response.data.serverData); - Object.keys(pricedLineItems).forEach((key) => { - pricedLineItems[key] = addTaxesToPricedLineItems( - pricedLineItems[key] ?? [], - response.data.taxedLineItems - ); - }); + + pricedLineItems = addTaxesToPricedLineItems(pricedLineItems, response.data.taxedLineItems); return pricedLineItems; },