Merge branch 'release/2025.10.23' into rlsmerge/2025.10.23-to-dev

This commit is contained in:
CarlNation 2025-10-22 09:39:47 -04:00
commit 1148a5af07
8 changed files with 97 additions and 93 deletions

View file

@ -150,6 +150,7 @@ import {
import { import {
convertDateToDateString, convertDateToDateString,
convertDateStringToDate, convertDateStringToDate,
getTodayDateString,
} from "@/layouts/schedule/helpers/schedule-helper"; } from "@/layouts/schedule/helpers/schedule-helper";
import { useField, ErrorMessage } from "vee-validate"; import { useField, ErrorMessage } from "vee-validate";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
@ -227,7 +228,7 @@ export default {
}, },
computed: { computed: {
todayString() { todayString() {
return this.todayOverrideDateString || convertDateToDateString(new Date()); return this.todayOverrideDateString || getTodayDateString();
}, },
todayDayIndex() { todayDayIndex() {
return convertDateStringToDate(this.todayString).getDay(); return convertDateStringToDate(this.todayString).getDay();
@ -259,6 +260,17 @@ export default {
this.dispatchStoreAction(this.storeActions.SAVE_WAITLIST_REQUESTED, false, false); this.dispatchStoreAction(this.storeActions.SAVE_WAITLIST_REQUESTED, false, false);
}, },
}, },
firstAvailableSelectableDate() {
const mobileFirstDate = Array.isArray(this.selectableDatesMobile)
? this.selectableDatesMobile[0]?.date
: null;
const inshopFirstDate = Array.isArray(this.selectableDatesInshop)
? this.selectableDatesInshop[0]?.date
: null;
return this.isMobileSelected && mobileFirstDate
? mobileFirstDate + "-mobile"
: inshopFirstDate;
},
}, },
methods: { methods: {
async initializeComponent(initialData) { async initializeComponent(initialData) {
@ -414,7 +426,7 @@ export default {
} else if (config.todayOverrideDateString) { } else if (config.todayOverrideDateString) {
todayDateString = config.todayOverrideDateString; todayDateString = config.todayOverrideDateString;
} else { } else {
todayDateString = convertDateToDateString(new Date()); todayDateString = getTodayDateString();
} }
if (config.selectableDatesSetting === "past") calendarViewDirection = "past"; if (config.selectableDatesSetting === "past") calendarViewDirection = "past";
if (config.selectableDatesSetting === "custom") calendarViewDirection = "future"; if (config.selectableDatesSetting === "custom") calendarViewDirection = "future";
@ -834,6 +846,12 @@ export default {
}, },
}, },
watch: { watch: {
isLoading(newValue) {
// if done loading dates, then set to first available date
if (newValue === false && this.firstAvailableSelectableDate) {
this.selectedDate = this.firstAvailableSelectableDate;
}
},
modelValue(newValue) { modelValue(newValue) {
this.resetField({ this.resetField({
value: newValue, value: newValue,

View file

@ -99,3 +99,23 @@ 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);
}
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

@ -6,7 +6,7 @@ import baseMixin from "@/mixins/base-mixin.js";
export const promoPartNumberStrings = { export const promoPartNumberStrings = {
WIPER_DISCOUNT_PART_NUMBER: "WIPER DISCOUNT", WIPER_DISCOUNT_PART_NUMBER: "WIPER DISCOUNT",
RAIN_REPEL_DISCOUNT_PART_NUMBER: "DISC RAIN REPEL", RAIN_REPEL_DISCOUNT_PART_NUMBER: "DISC RAIN DEFEN",
GLASS_DISCOUNT_PART_NUMBER: "DISCOUNT", GLASS_DISCOUNT_PART_NUMBER: "DISCOUNT",
GLASS_CLEANER_DISCOUNT_PART_NUMBER: "DISC GLASS CLN", GLASS_CLEANER_DISCOUNT_PART_NUMBER: "DISC GLASS CLN",
}; };

View file

@ -155,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";
@ -163,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";
@ -231,21 +222,28 @@ export default {
const availableVaps = [resultMap.rainRepel, ...resultMap.wipers]; const availableVaps = [resultMap.rainRepel, ...resultMap.wipers];
const pricedAvailableVaps = await baseMixin.methods.dispatchStoreActionWithLogging( const lineItemsOnOrderAndAvailableVaps = [
...availableVaps,
...glassParts,
...supportingItems,
...vaps,
];
// 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
const pricedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{ {
availableLineItems: availableVaps, availableLineItems: lineItemsOnOrderAndAvailableVaps,
}, },
"payment-method", "payment-method",
false false
); );
const lineItemsOnOrderAndAvailableVaps = [ // Add prices to the availableVaps
...pricedAvailableVaps, const pricedAvailableVaps = addPricesToLineItems(availableVaps, pricedLineItems);
...glassParts,
...supportingItems,
...vaps,
];
// 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()"
@ -265,7 +263,6 @@ export default {
delete lineItemsForCart.serverData; delete lineItemsForCart.serverData;
// End of promo logic // End of promo logic
//
const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos( const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos(
lineItemsForCart.promos ?? [], lineItemsForCart.promos ?? [],

View file

@ -71,3 +71,11 @@ export function isDropOffRouteCode(routeCode) {
routeCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF) routeCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)
); );
} }
export function getTodayDate(routeCode) {
return new Date();
}
export function getTodayDateString(routeCode) {
return convertDateToDateString(getTodayDate());
}

View file

@ -233,6 +233,7 @@ import {
convertDateStringToDate, convertDateStringToDate,
sumDateString, sumDateString,
isDropOffRouteCode, isDropOffRouteCode,
getTodayDate,
} from "@/layouts/schedule/helpers/schedule-helper"; } from "@/layouts/schedule/helpers/schedule-helper";
import { DAYS_OF_WEEK } from "@/digital-components/date-picker/mixins/constants"; import { DAYS_OF_WEEK } from "@/digital-components/date-picker/mixins/constants";
@ -331,12 +332,16 @@ const getScheduleApiResponse = async ({
const mobileTimeSlotsData = { const mobileTimeSlotsData = {
days: [], days: [],
}; };
function compareDayStrings(a, b) { function compareDayStrings(a, b) {
if (a.date < b.date) return -1; if (a.date < b.date) return -1;
if (a.date > b.date) return 1; if (a.date > b.date) return 1;
return 0; return 0;
} }
function removePastDates(array, todaysDate) {
return array.filter(function (a) {
return !(a.date < todaysDate);
});
}
const makeParallelCalls = async () => { const makeParallelCalls = async () => {
await Promise.all( await Promise.all(
@ -389,6 +394,10 @@ const getScheduleApiResponse = async ({
inshopTimeSlotsData.days.sort(compareDayStrings); inshopTimeSlotsData.days.sort(compareDayStrings);
mobileTimeSlotsData.days.sort(compareDayStrings); mobileTimeSlotsData.days.sort(compareDayStrings);
const todaysDate = getTodayDate().toISOString().split("T")[0];
inshopTimeSlotsData.days = removePastDates(inshopTimeSlotsData.days, todaysDate);
mobileTimeSlotsData.days = removePastDates(mobileTimeSlotsData.days, todaysDate);
return { return {
inshopTimeSlotsData: inshopTimeSlotsData, inshopTimeSlotsData: inshopTimeSlotsData,
mobileTimeSlotsData: mobileTimeSlotsData, mobileTimeSlotsData: mobileTimeSlotsData,
@ -422,7 +431,6 @@ export default {
selectableDatesInshop: [], selectableDatesInshop: [],
selectableDatesMobile: [], selectableDatesMobile: [],
preSelectedDate: null, preSelectedDate: null,
streetAddress: this.getServiceAddressFromStore(), streetAddress: this.getServiceAddressFromStore(),
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(), apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
carId: this.getCarIdfromStore(), carId: this.getCarIdfromStore(),
@ -612,7 +620,7 @@ export default {
this.selectedDate.includes("mobile") && this.selectedDate.includes("mobile") &&
!this.isMobileSelected !this.isMobileSelected
) { ) {
this.getNewSelectedDate(); this.setSelectedDateToFirstAvailable();
} }
return this.selectedDate && this.appointmentTypeFromAppointmentTypeQuestion; return this.selectedDate && this.appointmentTypeFromAppointmentTypeQuestion;
}, },
@ -826,7 +834,7 @@ export default {
} }
}, },
isSameDay() { isSameDay() {
const todaysDate = new Date().toISOString().split("T")[0]; const todaysDate = getTodayDate().toISOString().split("T")[0];
return this.selectedDate === todaysDate; return this.selectedDate === todaysDate;
}, },
isOvernightDropoff() { isOvernightDropoff() {
@ -1100,7 +1108,7 @@ export default {
}, },
getTimeSlotInfo() { getTimeSlotInfo() {
// Get the current date // Get the current date
const currentDate = new Date(); const currentDate = getTodayDate();
// Add 10 days to the current date // Add 10 days to the current date
currentDate.setDate(currentDate.getDate() + 10); currentDate.setDate(currentDate.getDate() + 10);
@ -1179,45 +1187,23 @@ export default {
datePickerInitialData.pricingByDayUpcharge = this.pricingByDayUpcharge; datePickerInitialData.pricingByDayUpcharge = this.pricingByDayUpcharge;
await this.$refs.datePicker.initializeComponent(datePickerInitialData); await this.$refs.datePicker.initializeComponent(datePickerInitialData);
this.selectableDatesInshop = this.selectableDatesInshop =
datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData; await datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
this.selectableDatesMobile = this.selectableDatesMobile =
datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData; await datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
this.setDisplayWaitList(); this.setDisplayWaitList();
if (this.preSelectedDate) this.selectedDate = this.preSelectedDate; if (this.preSelectedDate) {
this.selectedDate = this.preSelectedDate;
if (!this.preSelectedDate) { } else {
// if no date is preselected on load, then select the first available // if no date is preselected on load, make sure there are some dates available
let selectedDateMobile = this.getSelectedDateForMobile();
let selectedDateInshop = this.getSelectedDateForInshop();
// if there is still no selected date, then load more and try again
if ( if (
(this.isServiceableMobile && !selectedDateMobile) || this.selectableDatesInshop.days.length < 1 ||
(this.isServiceableInshop && !selectedDateInshop) || this.selectableDatesMobile.days.length < 1
(this.isServiceableDropoff && !selectedDateInshop)
) { ) {
await this.$nextTick(); await this.$nextTick();
await this.$refs.datePicker.showAnotherMonth(); await this.$refs.datePicker.showAnotherMonth();
// update all dates
// > CHLOE HERD 7/22 -- CASH-1207
// > Do not update the available dates again here;
// > they have already been updated by `showAnotherMonth`.
// > Doing so will likely add or remove dates,
// > desyncing the schedule page and the date-picker.
this.setDisplayWaitList(); this.setDisplayWaitList();
} }
await this.$nextTick();
if (this.isMobileSelected) {
this.selectedDate = this.getSelectedDateForMobile();
} else {
this.selectedDate = this.getSelectedDateForInshop();
}
} }
}, },
getScheduleApiResponse, getScheduleApiResponse,
@ -1403,7 +1389,7 @@ export default {
const type = store.getters.isMobileAppointment ? "mobile" : "inshop"; const type = store.getters.isMobileAppointment ? "mobile" : "inshop";
gaLabel = `${status}_${type}`; gaLabel = `${status}_${type}`;
const currentDate = new Date(); const currentDate = getTodayDate();
const dateString = this.isMobileSelected const dateString = this.isMobileSelected
? this.selectableDatesMobile.days[0].date ? this.selectableDatesMobile.days[0].date
: this.selectableDatesInshop.days[0].date; : this.selectableDatesInshop.days[0].date;
@ -1477,7 +1463,7 @@ export default {
) { ) {
const [year, month, day] = dateString.split("-").map(Number); const [year, month, day] = dateString.split("-").map(Number);
const targetDate = new Date(year, month - 1, day); const targetDate = new Date(year, month - 1, day);
const currentDate = new Date(); const currentDate = getTodayDate();
const futureDate = new Date(currentDate); const futureDate = new Date(currentDate);
const experimentThresholdDays = experimentMixin.methods.hasSetting( const experimentThresholdDays = experimentMixin.methods.hasSetting(
experimentSettings.WAITLIST_THRESHOLD_DAYS experimentSettings.WAITLIST_THRESHOLD_DAYS
@ -1622,7 +1608,7 @@ export default {
? AppointmentTypeStrings.DROP_OFF ? AppointmentTypeStrings.DROP_OFF
: AppointmentTypeStrings.IN_SHOP; : AppointmentTypeStrings.IN_SHOP;
}, },
getSelectedDate() { getFirstAvailableDate() {
let dateToSelect; let dateToSelect;
if (this.isMobileSelected) { if (this.isMobileSelected) {
dateToSelect = returnFirstDate(this.selectableDatesMobile); dateToSelect = returnFirstDate(this.selectableDatesMobile);
@ -1632,16 +1618,6 @@ export default {
if (!dateToSelect) return null; if (!dateToSelect) return null;
return this.isMobileSelected ? dateToSelect + "-mobile" : dateToSelect; return this.isMobileSelected ? dateToSelect + "-mobile" : dateToSelect;
}, },
getSelectedDateForMobile() {
let dateToSelect = returnFirstDate(this.selectableDatesMobile);
if (!dateToSelect) return null;
return dateToSelect + "-mobile";
},
getSelectedDateForInshop() {
let dateToSelect = returnFirstDate(this.selectableDatesInshop);
if (!dateToSelect) return null;
return dateToSelect;
},
resetSelectedProvider() { resetSelectedProvider() {
this.selectedProvider = new Provider(); this.selectedProvider = new Provider();
this.updateSelectedProvider(); this.updateSelectedProvider();
@ -1740,11 +1716,11 @@ export default {
} else { } else {
this.appointmentType = null; this.appointmentType = null;
} }
this.selectedDate = this.getSelectedDate(); this.setSelectedDateToFirstAvailable();
this.setDisplayWaitList(); this.setDisplayWaitList();
}, },
getNewSelectedDate() { setSelectedDateToFirstAvailable() {
this.selectedDate = this.getSelectedDate(); this.selectedDate = this.getFirstAvailableDate();
}, },
}, },
watch: { watch: {

View file

@ -509,8 +509,12 @@ export default {
// this.availableTimeSlots only returns mobile/inshop slots so we know // this.availableTimeSlots only returns mobile/inshop slots so we know
// the only available slot is not dropOFf // the only available slot is not dropOFf
this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value; this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value;
// emit up to parent that the time slot has been selected (when mobile or inshop)
this.timeSlotSelectionChanged(this.availableTimeSlots[0].value);
} else { } else {
this.selectedAnswerForDropOffOrInshop = this.answersForDropOffQuestion[0].value; this.selectedAnswerForDropOffOrInshop = this.answersForDropOffQuestion[0].value;
// emit up to parent that the time slot has been selected (when only dropoff)
this.dropOffSelectionChanged(this.answersForDropOffQuestion[0].value);
} }
} }
}, },

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 = () => {
@ -3800,26 +3801,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(