Merge branch 'release/2025.11.06' into feature/CASH-1712

This commit is contained in:
AdamCaouetteSafelite 2025-10-28 15:54:38 -04:00
commit 4d705ab3f1
10 changed files with 247 additions and 445 deletions

View file

@ -309,40 +309,6 @@ describe("cart.vue", () => {
expect(found).toBe(true);
});
// Service package discount Fee Cart Item
test("only if there is service package discount fee for the package, a service package discount cart item should be added to the cart", () => {
// Arrange
const lineItems = {
glassParts: [],
supportingItems: [
{
description: null,
id: "bc00294e-6baa-403e-866e-52c267187a15",
kitPrice: 0,
laborAmount: 0,
partNumber: "DISC CASHSAVE70",
partType: "SERVICE PACKAGE DISCOUNT",
salesTax: null,
sellingPrice: -70,
},
],
vaps: [],
promos: [],
};
const availableVaps = [];
// Act
const { wrapper } = setupMocks({
props: {
modelValue: lineItems,
availableVaps: availableVaps,
},
});
// Assert
expect(wrapper.vm.servicePackageDiscountCartItem).toBeNull();
});
// Other Supporting Items Cart Item
test("if there are other supporting items on the order, an other supporting items cart item should be added to the cart but should not be displayed", () => {
// Arrange

View file

@ -312,16 +312,30 @@ export default {
this.lineItems[category] = this.lineItems[category].filter(
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
);
let shouldSaveSupportingItems = category == cartItemCategories.SUPPORTING_ITEMS;
if (category == cartItemCategories.VAPS || category == cartItemCategories.PROMOS) {
this.saveVaps(this.lineItems);
// Check if service package discount should be removed after vaps change
if (
this.servicePackageDiscountCartItem &&
this.discountPackageNames != this.packageLevel
) {
this.lineItems.supportingItems = this.lineItems.supportingItems.filter(
(lineItemsToKeep) =>
lineItemsToKeep.cartItemType !=
this.servicePackageDiscountCartItem.cartItemType
);
shouldSaveSupportingItems = true;
}
}
if (category == cartItemCategories.SUPPORTING_ITEMS) {
if (shouldSaveSupportingItems) {
await this.dispatchStoreAction(
storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
this.lineItems.supportingItems,
false
);
}
this.$emit("itemRemoved");
},
async saveVaps(lineItems) {
@ -470,14 +484,7 @@ export default {
}
if (this.servicePackageDiscountCartItem) {
if (this.discountPackageNames != this.packageLevel) {
this.removeItem(
this.servicePackageDiscountCartItem.cartItemType,
cartItemCategories.SUPPORTING_ITEMS
);
} else {
cartItems.push(this.servicePackageDiscountCartItem);
}
cartItems.push(this.servicePackageDiscountCartItem);
}
if (this.premiumAppointmentDiscountCartItem) {

View file

@ -290,8 +290,18 @@ export default {
);
if (promoCodeData.isValid) {
if (this.taxPromos) {
const pricedLineItemsToTax = [];
pricedLineItemsToTax.push(...promoCodeData.promoCode); // promoCodeData.promoCode should be an array
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 taxedLineItems =
await baseMixin.methods.dispatchStoreActionWithLogging(
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
// and rebuild the original structure.
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 = taxedLineItems;
} else {
this.lineItems?.promos.push(...promoCodeData.promoCode);
}
this.lineItems?.promos.push(...promoCodeData.promoCode);
this.$emit("promoAdded", this.lineItems);
this.closeModal();

View file

@ -99,3 +99,25 @@ export async function getPricingByDayPartWithPrice(pageNameToLog) {
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"
:isNoComp="isNoComp"
:isExpandedOnLoad="false"
:isMSRFeeApplicable="isMSRFeeApplicable" />
:isMSRFeeApplicable="isMSRFeeApplicable"
@itemRemoved="evaluatePromosAndTaxItemsOnOrder" />
<afterpayBreakout
v-if="isAfterpayBreakoutDisplay"
@ -154,7 +155,6 @@ import {
getNewlyInactivatedPromos,
} from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper";
import { Form } from "vee-validate";
@ -162,18 +162,10 @@ import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { partTypeStrings } from "@/constants/part-type-strings";
import { mapTaxedLineItemsToStoreFormat } from "../../store";
import { coverageStatus } from "@/constants/insurance";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper";
import { getBoolFromString } from "@/helpers/boolean-helper";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { getAmountDue, addPricesToLineItems } from "@/helpers/pricing-helper.js";
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
import { debugLog } from "@/helpers/debug-log-helper";
import { ErrorMessage } from "vee-validate";
@ -182,6 +174,23 @@ import { Field } from "vee-validate";
defineRule("payment-method-required", required(errorMessages.OPTION_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 {
name: "paymentMethod",
props: {
@ -191,32 +200,14 @@ export default {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.name);
const lineItemsFromStore = deepClone(store.getters.order.lineItems);
const frontWipersOnOrder =
lineItemsFromStore.vaps.filter(
(wiper) => 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 wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_WIPERS,
{
serviceZipCode: store.getters.order.serviceLocation.zipCode,
carId: store.getters.vehicle.carId,
},
"payment-method"
);
const rainRepelPromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_RAIN_REPEL,
@ -224,9 +215,6 @@ export default {
"payment-method"
);
// const reviewDropdownPromise = reviewDropdown.methods.loadInitialData();
// (removed temporarily for Heritage parity effort)
const promiseResultMap = [
{
resultKey: "cmsContent",
@ -240,57 +228,40 @@ 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 ?? [];
// 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 availableVaps = [resultMap.rainRepel, ...resultMap.wipers];
const allLineItems = [
resultMap.rainRepel,
...supportingItems,
...availableFrontWipers,
...availableRearWipers,
const lineItemsOnOrderAndAvailableVaps = [
...availableVaps,
...glassParts,
...supportingItems,
...vaps,
];
const lineItemsToTax = Array.from(
new Map(allLineItems.map((item) => [item.partNumber, item])).values()
);
const availableVaps = [
resultMap.rainRepel,
...availableFrontWipers,
...availableRearWipers,
];
const pricedLineItemsToTax = await baseMixin.methods.dispatchStoreActionWithLogging(
// All line items are already priced except availableVaps
// 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
let pricedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{
availableLineItems: lineItemsToTax,
availableLineItems: lineItemsOnOrderAndAvailableVaps,
},
"payment-method",
false
);
pricedLineItems = getFlattenedArrayOfLineItemsWithChildParts(pricedLineItems);
// Add prices to the availableVaps
const pricedAvailableVaps = addPricesToLineItems(availableVaps, pricedLineItems);
// Promo logic
// 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 promoCodeFromQueryString = consumeQueryFromStash(queryStrings.PROMO);
// New promos are saved to store with this
const { validatePromoResponse, revalidatePromoResponse } =
await revalidatePromosAndValidateQueryStringPromo(
promoCodeFromQueryString,
pricedLineItemsToTax,
lineItemsOnOrderAndAvailableVaps,
"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
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,
{
billToAccountNumber: store.getters.payment.billToAccountNumber,
@ -322,31 +309,17 @@ export default {
serviceLocationCity: store.getters.order.serviceLocation.city,
serviceLocationState: store.getters.order.serviceLocation.state,
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
pricedLineItems: pricedLineItemsToTax,
pricedLineItems: lineItemsForCart,
},
"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 = taxedVaps;
vm.lineItems = lineItems;
vm.availableVaps = pricedAvailableVaps;
vm.lineItems = lineItemsForCart;
vm.inactivePromos = removeCurrentlyActivePromoCodesFromInactivePromos(
vm.lineItems.promos,
vm.inactivePromos
@ -641,6 +614,28 @@ export default {
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() {
return baseMixin.methods.hasSubmittedOrder();
},
@ -861,7 +856,6 @@ export default {
) {
return;
}
if (oldValue.promos.length < newValue.promos.length) {
const oldPromoCodes = oldValue.promos.map(
(promoObject) => promoObject.promoCode

View file

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

View file

@ -42,7 +42,7 @@
<cart
ref="cart"
:damage="damageInfo"
:availableVaps="availableVaps"
:availableVaps="[]"
:allowItemRemoval="false"
v-model="lineItems"
servicePackageOptionsCmsName="ServicePackageTitle"
@ -237,25 +237,11 @@ import { applicationConfig } from "@/constants/application-config";
import { paymentMethods } from "@/constants/payment-method-constants";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
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 { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js";
import { coverageStatus } from "@/constants/insurance";
import { getBoolFromString } from "@/helpers/boolean-helper.js";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { getDisplayAmountDue, getAmountDue } from "@/helpers/pricing-helper.js";
import { debugLog } from "@/helpers/debug-log-helper.js";
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";
@ -290,7 +276,6 @@ export default {
displayAmount: this.getDisplayAmountDue(),
piaLineItems: "",
lineItems: [],
availableVaps: [],
shouldBlockInteraction: false,
paymentMethodListButton: paymentMethodListButton,
showSwitchPaymentMethod: !this.isAfterpay(),
@ -321,21 +306,6 @@ export default {
"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 = [
{
resultKey: "cmsContent",
@ -345,92 +315,14 @@ export default {
resultKey: "signature",
promise: signaturePromise,
},
{
resultKey: "wipers",
promise: wipersPromise,
},
{
resultKey: "rainRepel",
promise: rainRepelPromise,
},
];
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.
next(async (vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.availableVaps = taxedVaps;
vm.lineItems = lineItems;
vm.lineItems = deepClone(store.getters.order.lineItems);
vm.$nextTick(() => {
if (vm.$refs.cart) {

View file

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

View file

@ -460,111 +460,47 @@ export default {
};
},
async beforeRouteEnter(to, from, next) {
const isPricingByDayExperiment = experimentMixin.methods.hasSettingEqualTo(
experimentSettings.PRICING_BY_DAY,
"true"
);
const showPricingByDay = !store.getters.payment.isInsurance && isPricingByDayExperiment;
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
// 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,
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(
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 = [
{
@ -575,18 +511,6 @@ export default {
resultKey: "alertReasons",
promise: alertReasonsPromise,
},
{
resultKey: "pricingByDayUpchargePart",
promise: pricingByDayUpchargePartPromise,
},
{
resultKey: "premiumFeeWithPrice",
promise: premiumFeeWithPricePromise,
},
{
resultKey: "zipCodeData",
promise: zipCodeDataPromise,
},
{
resultKey: "mobileFeePart",
promise: mobileFeePartPromise,
@ -595,39 +519,75 @@ 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);
}
// add pricing by day data
const pricingByDayUpcharge =
showPricingByDay && resultMap.pricingByDayUpchargePart
? await baseMixin.methods.getTotalLineItemPrice(
resultMap.pricingByDayUpchargePart,
false
)
: null;
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";
}
}
// 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 = resultMap.premiumFeeWithPrice
? resultMap.premiumFeeWithPrice[0]
: null;
vm.mobilePremiumAppointmentFee = pricedPremiumFee;
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
vm.pricingByDayUpchargeLineItem = resultMap.pricingByDayUpchargePart;
vm.includePricingByDayUpcharge = includePricingByDayUpcharge;
vm.isPricingByDayExperiment = isPricingByDayExperiment;
vm.pricingByDayBasePrice = pricingByDayBasePrice;
vm.pricingByDayUpcharge = pricingByDayUpcharge;
vm.showPricingByDay = showPricingByDay;
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.preSelectedDate = preSelectedDate;
vm.appointmentType = appointmentType;
<<<<<<< HEAD
vm.setDataOnLoad(
resultMap.zipCodeData,
=======
vm.setData(
zipCodeData,
>>>>>>> release/2025.11.06
resultMap.serviceabilityDetails,
resultMap.mobileFeePart,
pricedMobileFeePart,
shopProviderData.data
);
vm.initializeDatePicker();

View file

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