Merge pull request #2918 from Safelite/feature/CASH-1529-revert-revert

Revert "Merge pull request #2914 from Safelite/feature/CASH-1529-revert"
This commit is contained in:
scottkiener-at-safelite 2025-10-27 14:49:30 -04:00 committed by GitHub
commit 56229ca650
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 228 additions and 403 deletions

View file

@ -322,6 +322,7 @@ export default {
false false
); );
} }
this.$emit("itemRemoved");
}, },
async saveVaps(lineItems) { async saveVaps(lineItems) {

View file

@ -290,8 +290,18 @@ export default {
); );
if (promoCodeData.isValid) { if (promoCodeData.isValid) {
if (this.taxPromos) { if (this.taxPromos) {
const pricedLineItemsToTax = []; const vapsToAdd = getVapsThatNeedToBeAddedToSatisfyPromos(
pricedLineItemsToTax.push(...promoCodeData.promoCode); // promoCodeData.promoCode should be an array 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 taxedLineItems = const taxedLineItems =
await baseMixin.methods.dispatchStoreActionWithLogging( await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA, storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
@ -314,25 +324,10 @@ export default {
// Match all line items to the line items as they are in the store // Match all line items to the line items as they are in the store
// and rebuild the original structure. // and rebuild the original structure.
this.lineItems = mapTaxedLineItemsToStoreFormat( this.lineItems = taxedLineItems;
taxedLineItems, } else {
this.lineItems this.lineItems?.promos.push(...promoCodeData.promoCode);
);
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.$emit("promoAdded", this.lineItems);
this.closeModal(); this.closeModal();

View file

@ -99,3 +99,25 @@ export async function getPricingByDayPartWithPrice(pageNameToLog) {
return pricingResults[0]; return pricingResults[0];
} }
export function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems.findIndex(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
);
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems);
}
if (lineItemIndex !== -1) {
const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0];
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
lineItem.salesTax = pricedLineItem.salesTax;
}
});
return lineItems;
}

View file

@ -34,7 +34,8 @@
:isItac="isItac" :isItac="isItac"
:isNoComp="isNoComp" :isNoComp="isNoComp"
:isExpandedOnLoad="false" :isExpandedOnLoad="false"
:isMSRFeeApplicable="isMSRFeeApplicable" /> :isMSRFeeApplicable="isMSRFeeApplicable"
@itemRemoved="evaluatePromosAndTaxItemsOnOrder" />
<afterpayBreakout <afterpayBreakout
v-if="isAfterpayBreakoutDisplay" v-if="isAfterpayBreakoutDisplay"
@ -154,7 +155,6 @@ import {
getNewlyInactivatedPromos, getNewlyInactivatedPromos,
} from "@/helpers/promotions-helper"; } from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import { Form } from "vee-validate"; import { Form } from "vee-validate";
@ -162,18 +162,10 @@ import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { partTypeStrings } from "@/constants/part-type-strings";
import { mapTaxedLineItemsToStoreFormat } from "../../store";
import { coverageStatus } from "@/constants/insurance"; import { coverageStatus } from "@/constants/insurance";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper"; import { containsRecalParts } from "@/helpers/recal-helper";
import { getBoolFromString } from "@/helpers/boolean-helper"; import { getBoolFromString } from "@/helpers/boolean-helper";
import { import { getAmountDue, addPricesToLineItems } from "@/helpers/pricing-helper.js";
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash"; import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { debugLog } from "@/helpers/debug-log-helper"; import { debugLog } from "@/helpers/debug-log-helper";
import { ErrorMessage } from "vee-validate"; import { ErrorMessage } from "vee-validate";
@ -182,6 +174,23 @@ import { Field } from "vee-validate";
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
function getFlattenedArrayOfLineItemsWithChildParts(lineItems, childPartRecursiveCall = false) {
let flattenedArray = [];
lineItems?.forEach((lineItem) => {
// this assumes childparts will never be a glass part
lineItem.isChildPart = childPartRecursiveCall;
flattenedArray.push(lineItem);
if (lineItem.childParts) {
flattenedArray = [
...flattenedArray,
...getFlattenedArrayOfLineItemsWithChildParts(lineItem.childParts, true),
];
}
});
return flattenedArray;
}
export default { export default {
name: "paymentMethod", name: "paymentMethod",
props: { props: {
@ -191,32 +200,14 @@ export default {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.name); const cmsContentPromise = fetchCmsContentForPage(to.name);
const lineItemsFromStore = deepClone(store.getters.order.lineItems); const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_WIPERS,
const frontWipersOnOrder = {
lineItemsFromStore.vaps.filter( serviceZipCode: store.getters.order.serviceLocation.zipCode,
(wiper) => wiper.partType == partTypeStrings.FRONT_WIPER carId: store.getters.vehicle.carId,
) ?? []; },
"payment-method"
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 rainRepelPromise = baseMixin.methods.dispatchStoreActionWithLogging( const rainRepelPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_RAIN_REPEL, storeActions.GET_RAIN_REPEL,
@ -224,9 +215,6 @@ export default {
"payment-method" "payment-method"
); );
// const reviewDropdownPromise = reviewDropdown.methods.loadInitialData();
// (removed temporarily for Heritage parity effort)
const promiseResultMap = [ const promiseResultMap = [
{ {
resultKey: "cmsContent", resultKey: "cmsContent",
@ -240,57 +228,40 @@ export default {
resultKey: "rainRepel", resultKey: "rainRepel",
promise: rainRepelPromise, promise: rainRepelPromise,
}, },
// {
// resultKey: "reviewDropdownData",
// promise: reviewDropdownPromise,
// },
// (removed temporarily for Heritage parity effort)
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
let lineItemsFromStore = deepClone(store.getters.order.lineItems);
const glassParts = lineItemsFromStore.glassParts ?? []; const glassParts = lineItemsFromStore.glassParts ?? [];
const supportingItems = lineItemsFromStore.supportingItems ?? []; const supportingItems = lineItemsFromStore.supportingItems ?? [];
const vaps = lineItemsFromStore.vaps ?? []; const vaps = lineItemsFromStore.vaps ?? [];
// if the order already has wipers on it from the quote page, use those as the available wipers const availableVaps = [resultMap.rainRepel, ...resultMap.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 allLineItems = [ const lineItemsOnOrderAndAvailableVaps = [
resultMap.rainRepel, ...availableVaps,
...supportingItems,
...availableFrontWipers,
...availableRearWipers,
...glassParts, ...glassParts,
...supportingItems,
...vaps, ...vaps,
]; ];
const lineItemsToTax = Array.from( // All line items are already priced except availableVaps
new Map(allLineItems.map((item) => [item.partNumber, item])).values() // Price everything again to ensure that serverData has all values
); // Specifically this addresses an error where insurance client glass parts are not in serverData
// See CASH-1713 for details
const availableVaps = [ let pricedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
resultMap.rainRepel,
...availableFrontWipers,
...availableRearWipers,
];
const pricedLineItemsToTax = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{ {
availableLineItems: lineItemsToTax, availableLineItems: lineItemsOnOrderAndAvailableVaps,
}, },
"payment-method", "payment-method",
false false
); );
pricedLineItems = getFlattenedArrayOfLineItemsWithChildParts(pricedLineItems);
// Add prices to the availableVaps
const pricedAvailableVaps = addPricesToLineItems(availableVaps, pricedLineItems);
// Promo logic // Promo logic
// Populate the previous state of promos for toast message usage in "next()" // Populate the previous state of promos for toast message usage in "next()"
@ -298,22 +269,38 @@ export default {
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0); const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
const promoCodeFromQueryString = consumeQueryFromStash(queryStrings.PROMO); const promoCodeFromQueryString = consumeQueryFromStash(queryStrings.PROMO);
// New promos are saved to store with this
const { validatePromoResponse, revalidatePromoResponse } = const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateQueryStringPromo( await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString, promoCodeFromQueryString,
pricedLineItemsToTax, lineItemsOnOrderAndAvailableVaps,
"payment-method" "payment-method"
); );
// update lineItemsFromStore with newly added promos
let lineItemsForCart = deepClone(store.getters.order.lineItems);
delete lineItemsForCart.serverData;
// Add newly validated promos to the array to get taxed
const newValidatedPromos = validatePromoResponse?.orderPromos ?? [];
newValidatedPromos.push(
...(revalidatePromoResponse ? revalidatePromoResponse.promoLineItems : [])
);
pricedLineItemsToTax.push(...newValidatedPromos);
// End of promo logic // End of promo logic
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging( const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos(
lineItemsForCart.promos ?? [],
availableVaps,
lineItemsForCart
);
// Add prices to all line items, sometimes they are not there when they come back from heritage
// See CASH-1713 for details
lineItemsForCart.glassParts = addPricesToLineItems(
lineItemsForCart.glassParts ?? [],
pricedLineItems
);
lineItemsForCart.supportingItems = addPricesToLineItems(
lineItemsForCart.supportingItems ?? [],
pricedLineItems
);
lineItemsForCart.vaps = addPricesToLineItems(lineItemsForCart.vaps ?? [], pricedLineItems);
lineItemsForCart.vaps.push(...vapsToAddToCart);
// Tax items on order
lineItemsForCart = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA, storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{ {
billToAccountNumber: store.getters.payment.billToAccountNumber, billToAccountNumber: store.getters.payment.billToAccountNumber,
@ -322,31 +309,17 @@ export default {
serviceLocationCity: store.getters.order.serviceLocation.city, serviceLocationCity: store.getters.order.serviceLocation.city,
serviceLocationState: store.getters.order.serviceLocation.state, serviceLocationState: store.getters.order.serviceLocation.state,
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode, serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
pricedLineItems: pricedLineItemsToTax, pricedLineItems: lineItemsForCart,
}, },
"payment-method", "payment-method",
false 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. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.availableVaps = taxedVaps; vm.availableVaps = pricedAvailableVaps;
vm.lineItems = lineItems; vm.lineItems = lineItemsForCart;
vm.inactivePromos = removeCurrentlyActivePromoCodesFromInactivePromos( vm.inactivePromos = removeCurrentlyActivePromoCodesFromInactivePromos(
vm.lineItems.promos, vm.lineItems.promos,
vm.inactivePromos vm.inactivePromos
@ -641,6 +614,28 @@ export default {
this.pageName this.pageName
); );
}, },
async evaluatePromosAndTaxItemsOnOrder() {
//Revalidate promos if there are any inactive, or active promos
const hasInactivePromos = this.inactivePromos.length > 0;
const hasActivePromos = this.lineItems.promos.length > 0;
if (hasInactivePromos || hasActivePromos) {
await this.revalidatePromos();
}
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() { hasSubmittedOrder() {
return baseMixin.methods.hasSubmittedOrder(); return baseMixin.methods.hasSubmittedOrder();
}, },
@ -861,7 +856,6 @@ export default {
) { ) {
return; return;
} }
if (oldValue.promos.length < newValue.promos.length) { if (oldValue.promos.length < newValue.promos.length) {
const oldPromoCodes = oldValue.promos.map( const oldPromoCodes = oldValue.promos.map(
(promoObject) => promoObject.promoCode (promoObject) => promoObject.promoCode

View file

@ -319,7 +319,6 @@ describe("payment.vue", () => {
); );
// Assert // Assert
expect(vmMock.availableVaps).not.toBeUndefined();
expect(vmMock.lineItems).not.toBeUndefined(); expect(vmMock.lineItems).not.toBeUndefined();
expect(vmMock.setCmsContent).toBeCalled(); expect(vmMock.setCmsContent).toBeCalled();

View file

@ -42,7 +42,7 @@
<cart <cart
ref="cart" ref="cart"
:damage="damageInfo" :damage="damageInfo"
:availableVaps="availableVaps" :availableVaps="[]"
:allowItemRemoval="false" :allowItemRemoval="false"
v-model="lineItems" v-model="lineItems"
servicePackageOptionsCmsName="ServicePackageTitle" servicePackageOptionsCmsName="ServicePackageTitle"
@ -237,25 +237,11 @@ import { applicationConfig } from "@/constants/application-config";
import { paymentMethods } from "@/constants/payment-method-constants"; import { paymentMethods } from "@/constants/payment-method-constants";
import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { mapTaxedLineItemsToStoreFormat } from "../../store";
import {
revalidatePromosAndValidateQueryStringPromo,
getAddableVapsFromAvailableLineItems,
getVapsThatNeedToBeAddedToSatisfyPromos,
} from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"; import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js"; import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js";
import { coverageStatus } from "@/constants/insurance"; import { coverageStatus } from "@/constants/insurance";
import { getBoolFromString } from "@/helpers/boolean-helper.js"; import { getDisplayAmountDue, getAmountDue } from "@/helpers/pricing-helper.js";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { debugLog } from "@/helpers/debug-log-helper.js"; import { debugLog } from "@/helpers/debug-log-helper.js";
import buttonQuestion from "@/digital-components/button-question/button-question"; import buttonQuestion from "@/digital-components/button-question/button-question";
import paymentMethodListButton from "@/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button"; import paymentMethodListButton from "@/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button";
@ -290,7 +276,6 @@ export default {
displayAmount: this.getDisplayAmountDue(), displayAmount: this.getDisplayAmountDue(),
piaLineItems: "", piaLineItems: "",
lineItems: [], lineItems: [],
availableVaps: [],
shouldBlockInteraction: false, shouldBlockInteraction: false,
paymentMethodListButton: paymentMethodListButton, paymentMethodListButton: paymentMethodListButton,
showSwitchPaymentMethod: !this.isAfterpay(), showSwitchPaymentMethod: !this.isAfterpay(),
@ -321,21 +306,6 @@ export default {
"payment" "payment"
); );
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_WIPERS,
{
serviceZipCode: store.getters.order.serviceLocation.zipCode,
carId: store.getters.vehicle.carId,
},
"payment"
);
const rainRepelPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_RAIN_REPEL,
null,
"payment"
);
const promiseResultMap = [ const promiseResultMap = [
{ {
resultKey: "cmsContent", resultKey: "cmsContent",
@ -345,92 +315,14 @@ export default {
resultKey: "signature", resultKey: "signature",
promise: signaturePromise, promise: signaturePromise,
}, },
{
resultKey: "wipers",
promise: wipersPromise,
},
{
resultKey: "rainRepel",
promise: rainRepelPromise,
},
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const lineItemsFromStore = deepClone(store.getters.order.lineItems);
const glassParts = lineItemsFromStore.glassParts ?? [];
const supportingItems = lineItemsFromStore.supportingItems ?? [];
const lineItemsToTax = [
resultMap.rainRepel,
...supportingItems,
...resultMap.wipers,
...glassParts,
];
const availableVaps = [resultMap.rainRepel, ...resultMap.wipers];
const pricedLineItemsToTax = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{
availableLineItems: lineItemsToTax,
},
"payment",
false
);
// Promo logic
const promoCodeFromQueryString = getQuerystringParameter(queryStrings.PROMO);
const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString,
pricedLineItemsToTax,
"payment"
);
const newValidatedPromos = validatePromoResponse?.orderPromos ?? [];
newValidatedPromos.push(
...(revalidatePromoResponse ? revalidatePromoResponse.promoLineItems : [])
);
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,
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: pricedLineItemsToTax,
},
"payment",
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);
// Add items that were not in the store yet but added via query string promo validation
//newValidatedPromos contains both validated and revalidated promos.
lineItems.promos = Array.from(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. // Call the "next" function to complete the transition to this page.
next(async (vm) => { next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.availableVaps = taxedVaps; vm.lineItems = deepClone(store.getters.order.lineItems);
vm.lineItems = lineItems;
vm.$nextTick(() => { vm.$nextTick(() => {
if (vm.$refs.cart) { if (vm.$refs.cart) {

View file

@ -65,6 +65,7 @@
pageName="quote" pageName="quote"
:taxPromos="false" :taxPromos="false"
:useDefaultCashParentAccount="true" :useDefaultCashParentAccount="true"
@promoAdded="promoAdded"
modalWidgetName="PromoModalWidget" /> modalWidgetName="PromoModalWidget" />
</div> </div>
</div> </div>
@ -576,9 +577,6 @@ export default {
isInsuranceContinueButtonText() { isInsuranceContinueButtonText() {
return this.getCmsContent("isInsuranceContinueButtonText", "Text"); return this.getCmsContent("isInsuranceContinueButtonText", "Text");
}, },
lineItemsCloneForWatcher() {
return Object.assign({}, this.lineItems);
},
isRecalibrationOnOrder() { isRecalibrationOnOrder() {
return store.getters.isRecalibrationOnOrder; return store.getters.isRecalibrationOnOrder;
}, },
@ -854,31 +852,9 @@ export default {
await this.forwardButtonAction(); await this.forwardButtonAction();
} }
}, },
}, promoAdded(lineItems) {
watch: { const alert = createPromoSuccessAlert(lineItems.promos[0].promoCode);
lineItemsCloneForWatcher: { this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
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: { components: {

View file

@ -460,111 +460,47 @@ export default {
}; };
}, },
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
const isPricingByDayExperiment = experimentMixin.methods.hasSettingEqualTo( const serviceZipCode = store.getters.order.serviceLocation.zipCode;
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 cmsContentPromise = fetchCmsContentForPage(to.name);
const alertReasonsPromise = locationAlerts.methods.loadInitialData( const alertReasonsPromise = locationAlerts.methods.loadInitialData(
store.getters.order.serviceLocation.zipCodeCtu, store.getters.order.serviceLocation.zipCodeCtu,
store.getters.order.serviceLocation.provider?.address?.zipCodeCtu store.getters.order.serviceLocation.provider?.address?.zipCodeCtu
); );
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const zipCodeDataPromise = getZipCodeData(serviceZipCode, to.name);
const serviceabilityDetailsPromise = getServiceabilityDetails( const serviceabilityDetailsPromise = getServiceabilityDetails(
serviceZipCode, serviceZipCode,
null, null,
to.name "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
);
}
); );
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( const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_MOBILE_PREMIUM_FEE, storeActions.GET_MOBILE_PREMIUM_FEE,
null, null,
to.name 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 // Settle promises and get results
const promiseResultMap = [ const promiseResultMap = [
{ {
@ -575,18 +511,6 @@ export default {
resultKey: "alertReasons", resultKey: "alertReasons",
promise: alertReasonsPromise, promise: alertReasonsPromise,
}, },
{
resultKey: "pricingByDayUpchargePart",
promise: pricingByDayUpchargePartPromise,
},
{
resultKey: "premiumFeeWithPrice",
promise: premiumFeeWithPricePromise,
},
{
resultKey: "zipCodeData",
promise: zipCodeDataPromise,
},
{ {
resultKey: "mobileFeePart", resultKey: "mobileFeePart",
promise: mobileFeePartPromise, promise: mobileFeePartPromise,
@ -595,39 +519,70 @@ export default {
resultKey: "serviceabilityDetails", resultKey: "serviceabilityDetails",
promise: serviceabilityDetailsPromise, promise: serviceabilityDetailsPromise,
}, },
{
resultKey: "premiumFee",
promise: premiumFeePromise,
},
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const itemsToPrice = [];
if (resultMap.mobileFeePart) {
itemsToPrice.push(resultMap.mobileFeePart);
}
if (resultMap.premiumFee) {
itemsToPrice.push(resultMap.premiumFee);
}
// add pricing by day data let pricedMobileFeePart = null;
const pricingByDayUpcharge = let pricedPremiumFee = null;
showPricingByDay && resultMap.pricingByDayUpchargePart
? await baseMixin.methods.getTotalLineItemPrice( if (itemsToPrice.length) {
resultMap.pricingByDayUpchargePart, const pricedItems = await baseMixin.methods.dispatchStoreActionWithLogging(
false storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
) {
: null; 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";
}
}
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons); vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice vm.mobilePremiumAppointmentFee = pricedPremiumFee;
? resultMap.premiumFeeWithPrice[0]
: null;
vm.updateFooterButtonText(vm.selectedTimeSlotInfo); vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
vm.pricingByDayUpchargeLineItem = resultMap.pricingByDayUpchargePart; vm.pricingByDayUpchargeLineItem = null; // Pricing By Day Upcharge Line Item is not used in this version
vm.includePricingByDayUpcharge = includePricingByDayUpcharge; vm.includePricingByDayUpcharge = false; // Pricing By Day Upcharge is not used in this version
vm.isPricingByDayExperiment = isPricingByDayExperiment; vm.isPricingByDayExperiment = false; // Pricing By Day Experiment is not used in this version
vm.pricingByDayBasePrice = pricingByDayBasePrice; vm.pricingByDayBasePrice = null; // Pricing By Day Base Price is not used in this version
vm.pricingByDayUpcharge = pricingByDayUpcharge; vm.pricingByDayUpcharge = null; // Pricing By Day Upcharge is not used in this version
vm.showPricingByDay = showPricingByDay; vm.showPricingByDay = false; // Pricing By Day is not used in this version
vm.preSelectedDate = preSelectedDate; vm.preSelectedDate = preSelectedDate;
vm.appointmentType = appointmentType; vm.appointmentType = appointmentType;
vm.setData( vm.setData(
resultMap.zipCodeData, zipCodeData,
resultMap.serviceabilityDetails, resultMap.serviceabilityDetails,
resultMap.mobileFeePart, pricedMobileFeePart,
shopProviderData.data shopProviderData.data
); );
vm.initializeDatePicker(); vm.initializeDatePicker();

View file

@ -59,6 +59,7 @@ import {
} from "@/helpers/recal-helper"; } from "@/helpers/recal-helper";
import { externalParameterStatus } from "@/constants/external-parameters"; import { externalParameterStatus } from "@/constants/external-parameters";
import { experimentSettings } from "@/constants/experiments"; import { experimentSettings } from "@/constants/experiments";
import { addPricesToLineItems } from "@/helpers/pricing-helper";
// Export State // Export State
const getDefaultState = () => { const getDefaultState = () => {
@ -3063,8 +3064,14 @@ export const actions = {
pageNameToLog, pageNameToLog,
} }
) { ) {
const arrayOfLineItems = [
...(pricedLineItems.glassParts ?? []),
...(pricedLineItems.promos ?? []),
...(pricedLineItems.supportingItems ?? []),
...(pricedLineItems.vaps ?? []),
];
const flattenedLineItemsWithChildParts = const flattenedLineItemsWithChildParts =
getFlattenedArrayOfLineItemsWithChildParts(pricedLineItems); getFlattenedArrayOfLineItemsWithChildParts(arrayOfLineItems);
const lineItemsWithOnlyPriceInfo = flattenedLineItemsWithChildParts.map((lineItem) => ({ const lineItemsWithOnlyPriceInfo = flattenedLineItemsWithChildParts.map((lineItem) => ({
partNumber: lineItem.partNumber, partNumber: lineItem.partNumber,
@ -3113,8 +3120,12 @@ export const actions = {
}); });
context.commit(storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA, response.data.serverData); context.commit(storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA, response.data.serverData);
Object.keys(pricedLineItems).forEach((key) => {
pricedLineItems = addTaxesToPricedLineItems(pricedLineItems, response.data.taxedLineItems); pricedLineItems[key] = addTaxesToPricedLineItems(
pricedLineItems[key] ?? [],
response.data.taxedLineItems
);
});
return pricedLineItems; return pricedLineItems;
}, },
@ -3796,26 +3807,6 @@ function convertGlassPieceNamingFromApi(glassArray) {
return glassArray; return glassArray;
} }
function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems.findIndex(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
);
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems);
}
const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0];
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
lineItem.salesTax = pricedLineItem.salesTax;
});
return lineItems;
}
function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) { function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) {
pricedLineItems.forEach((pricedLineItem) => { pricedLineItems.forEach((pricedLineItem) => {
const lineItemIndex = taxingLineItems.findIndex( const lineItemIndex = taxingLineItems.findIndex(