CASH-2378 fix for multi location inshop appts CASH-2451
This commit is contained in:
parent
f686b684ce
commit
8d4d14f4c2
2 changed files with 218 additions and 84 deletions
|
|
@ -103,6 +103,7 @@ export default {
|
|||
timeSlot: this.selectedAppointment.timeSlot,
|
||||
routeCode: this.selectedAppointment.timeSlot.id,
|
||||
date: this.selectedAppointment.date,
|
||||
provider: this.selectedAppointment.provider,
|
||||
};
|
||||
this.confirmedAppointment = true;
|
||||
this.$emit("confirm-appointment", {
|
||||
|
|
|
|||
|
|
@ -252,6 +252,8 @@ import {
|
|||
sumDateString,
|
||||
isDropOffRouteCode,
|
||||
getTodayDate,
|
||||
getTodayDateString,
|
||||
getInitialViewWeeks,
|
||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||
import { containsRecalParts, anyPartWithRequiresRecalFlag } from "@/helpers/recal-helper";
|
||||
|
||||
|
|
@ -809,13 +811,6 @@ export default {
|
|||
};
|
||||
},
|
||||
selectedShopAnswer() {
|
||||
const toTitleCase = (str) => {
|
||||
if (!str) return "";
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
this.selectedProvider &&
|
||||
this.selectedProvider.address &&
|
||||
|
|
@ -824,18 +819,11 @@ export default {
|
|||
const provider = this.shopProviderData?.shopProviders?.find(
|
||||
(p) => p.providerNumber === this.selectedProvider?.providerNumber
|
||||
);
|
||||
const streetAddress = toTitleCase(this.selectedProvider.address.streetAddress);
|
||||
const city = toTitleCase(this.selectedProvider.address.city);
|
||||
const state = this.selectedProvider.address.state;
|
||||
const zipCode = this.selectedProvider.address.zipCode;
|
||||
const distanceInMiles = provider ? Math.round(provider.distanceInMiles * 2) / 2 : 0;
|
||||
const distanceInMiles = provider?.distanceInMiles
|
||||
? Math.round(provider.distanceInMiles * 2) / 2
|
||||
: 0;
|
||||
|
||||
return {
|
||||
city: `${city}`,
|
||||
distance: `${distanceInMiles} mi`,
|
||||
address1: `${streetAddress}`,
|
||||
address2: `${city}, ${state} ${zipCode}`,
|
||||
};
|
||||
return this.getFormattedShopAddress(provider?.address, distanceInMiles);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
|
@ -977,6 +965,17 @@ export default {
|
|||
"true"
|
||||
);
|
||||
},
|
||||
initialViewStartDate() {
|
||||
return getTodayDateString();
|
||||
},
|
||||
initialViewEndDate() {
|
||||
const initialViewWeeks = getInitialViewWeeks(
|
||||
this.initialViewStartDate,
|
||||
NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW,
|
||||
this.preSelectedDate
|
||||
);
|
||||
return initialViewWeeks[initialViewWeeks.length - 1].weekEndDate;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
|
|
@ -2054,7 +2053,14 @@ export default {
|
|||
}
|
||||
},
|
||||
async showMultiLocationModal() {
|
||||
if (!this.selectedRouteCodeData?.routeCode) {
|
||||
const showMultiLocationAppointment = experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.SHOW_MULTI_LOCATION_APPT
|
||||
);
|
||||
|
||||
if (
|
||||
!this.selectedRouteCodeData?.routeCode &&
|
||||
showMultiLocationAppointment?.toLowerCase() === "true"
|
||||
) {
|
||||
let maxDayRangeToShowPmTimeslot = experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.SHOW_PM_DAYS_MULTI_LOCATION
|
||||
);
|
||||
|
|
@ -2064,9 +2070,6 @@ export default {
|
|||
let maxDayRangeToShowAnyTimeslot = experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION
|
||||
);
|
||||
let showMultiLocationAppointment = experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.SHOW_MULTI_LOCATION_APPT
|
||||
);
|
||||
let experimentSettingsString =
|
||||
"ShowPm:" +
|
||||
maxDayRangeToShowPmTimeslot +
|
||||
|
|
@ -2089,25 +2092,23 @@ export default {
|
|||
};
|
||||
|
||||
// Fetch all appointments
|
||||
const [firstMobileAMAppt, firstMobilePMAppt, firstInshopAMAppt, firstInshopPMAppt] =
|
||||
await Promise.all([
|
||||
this.getFirstAvailableMobileApptByTOD("AM"),
|
||||
this.getFirstAvailableMobileApptByTOD("PM"),
|
||||
this.getFirstAvailableInshopApptByTOD("AM"),
|
||||
this.getFirstAvailableInshopApptByTOD("PM"),
|
||||
]);
|
||||
const [firstMobileAMAppt, firstMobilePMAppt, firstInshopAppts] = await Promise.all([
|
||||
this.getFirstAvailableMobileApptByTOD("AM"),
|
||||
this.getFirstAvailableMobileApptByTOD("PM"),
|
||||
this.getFirstAvailableInshopApptsByTOD(["AM", "PM"]),
|
||||
]);
|
||||
|
||||
// Extract dates
|
||||
const firstMobileAMDate = firstMobileAMAppt?.date;
|
||||
const firstMobilePMDate = firstMobilePMAppt?.date;
|
||||
const firstInshopAMDate = firstInshopAMAppt?.date;
|
||||
const firstInshopPMDate = firstInshopPMAppt?.date;
|
||||
const firstInshopAMDate = firstInshopAppts[0]?.date;
|
||||
const firstInshopPMDate = firstInshopAppts[1]?.date;
|
||||
|
||||
// Calculate days until appointments
|
||||
const numberOfDaysToFirstMobileAMDate = calculateDaysUntilDate(firstMobileAMAppt);
|
||||
const numberOfDaysToFirstMobilePMDate = calculateDaysUntilDate(firstMobilePMAppt);
|
||||
const numberOfDaysToFirstInshopAMDate = calculateDaysUntilDate(firstInshopAMAppt);
|
||||
const numberOfDaysToFirstInshopPMDate = calculateDaysUntilDate(firstInshopPMAppt);
|
||||
const numberOfDaysToFirstInshopAMDate = calculateDaysUntilDate(firstInshopAppts[0]);
|
||||
const numberOfDaysToFirstInshopPMDate = calculateDaysUntilDate(firstInshopAppts[1]);
|
||||
|
||||
const shouldExposeMultiLocationModal = () => {
|
||||
const isMobileAppointmentInDateRange =
|
||||
|
|
@ -2121,10 +2122,7 @@ export default {
|
|||
(firstInshopPMDate &&
|
||||
numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowAnyTimeslot);
|
||||
|
||||
if (showMultiLocationAppointment?.toLowerCase() === "true") {
|
||||
return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange;
|
||||
}
|
||||
return false;
|
||||
return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange;
|
||||
};
|
||||
|
||||
if (shouldExposeMultiLocationModal()) {
|
||||
|
|
@ -2158,8 +2156,8 @@ export default {
|
|||
// Selects the first available inshop time slot
|
||||
promotedInshopAppointment =
|
||||
numberOfDaysToFirstInshopAMDate <= numberOfDaysToFirstInshopPMDate
|
||||
? firstInshopAMAppt
|
||||
: firstInshopPMAppt;
|
||||
? firstInshopAppts[0]
|
||||
: firstInshopAppts[1];
|
||||
} else {
|
||||
if (
|
||||
firstMobilePMDate &&
|
||||
|
|
@ -2182,15 +2180,15 @@ export default {
|
|||
numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowPmTimeslot
|
||||
) {
|
||||
// Selects the first available PM time slot
|
||||
promotedInshopAppointment = firstInshopPMAppt;
|
||||
promotedInshopAppointment = firstInshopAppts[1];
|
||||
} else if (
|
||||
firstInshopPMDate &&
|
||||
numberOfDaysToFirstInshopPMDate >= maxDayRangeToShowNoPmTimeslot
|
||||
) {
|
||||
// Selects the first available AM time slot. If none, selects the first available PM time slot
|
||||
promotedInshopAppointment = firstInshopAMAppt
|
||||
? firstInshopAMAppt
|
||||
: firstInshopPMAppt;
|
||||
promotedInshopAppointment = firstInshopAppts[0]
|
||||
? firstInshopAppts[0]
|
||||
: firstInshopAppts[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2210,6 +2208,7 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getFirstAvailableMobileApptByTOD(timeOfDay) {
|
||||
const selectableDays = this.selectableDatesMobile?.days;
|
||||
if (!selectableDays?.length) {
|
||||
|
|
@ -2237,42 +2236,135 @@ export default {
|
|||
return null;
|
||||
}
|
||||
},
|
||||
async getFirstAvailableInshopApptByTOD(timeOfDay) {
|
||||
const selectableDays = this.selectableDatesInshop?.days;
|
||||
if (!selectableDays?.length) {
|
||||
return null;
|
||||
} else {
|
||||
for (const dateObj of selectableDays) {
|
||||
let matchingTimeSlot = {
|
||||
estimatedServiceMinutes: {
|
||||
minimum: this.estimatedServiceMinutesMinimum,
|
||||
maximum: this.estimatedServiceMinutesMaximum,
|
||||
},
|
||||
timeSlot: null,
|
||||
date: null,
|
||||
addressCopy: null,
|
||||
};
|
||||
const isMatchingApptDay = dateObj.timeSlots.some(
|
||||
(slot) =>
|
||||
slot.id.includes(timeOfDay) &&
|
||||
(matchingTimeSlot.timeSlot = slot) &&
|
||||
(matchingTimeSlot.date = dateObj.date)
|
||||
);
|
||||
if (
|
||||
this.selectedShopAnswer?.address1 &&
|
||||
this.selectedShopAnswer?.address2
|
||||
) {
|
||||
matchingTimeSlot.addressCopy =
|
||||
this.selectedShopAnswer?.address1 +
|
||||
", " +
|
||||
this.selectedShopAnswer?.address2;
|
||||
}
|
||||
if (isMatchingApptDay && matchingTimeSlot) {
|
||||
return matchingTimeSlot;
|
||||
}
|
||||
|
||||
findMatchingInshopAppt(selectableDays, timeOfDay, provider) {
|
||||
if (!selectableDays?.length) return null;
|
||||
for (const dateObj of selectableDays) {
|
||||
const distanceInMiles = provider?.distanceInMiles
|
||||
? Math.round(provider.distanceInMiles * 2) / 2
|
||||
: 0;
|
||||
const providerAddress = this.getFormattedShopAddress(
|
||||
provider?.address,
|
||||
distanceInMiles
|
||||
);
|
||||
let matchingTimeSlot = {
|
||||
estimatedServiceMinutes: {
|
||||
minimum: this.estimatedServiceMinutesMinimum,
|
||||
maximum: this.estimatedServiceMinutesMaximum,
|
||||
},
|
||||
timeSlot: null,
|
||||
date: null,
|
||||
addressCopy: providerAddress.address1 + ", " + providerAddress.address2,
|
||||
provider: provider,
|
||||
};
|
||||
const isMatchingApptDay = dateObj.timeSlots.some(
|
||||
(slot) =>
|
||||
slot.id.includes(timeOfDay) &&
|
||||
(matchingTimeSlot.timeSlot = slot) &&
|
||||
(matchingTimeSlot.date = dateObj.date)
|
||||
);
|
||||
if (isMatchingApptDay && matchingTimeSlot) {
|
||||
return matchingTimeSlot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
async getFirstAvailableInshopApptsByTOD(timeOfDayArray) {
|
||||
let matchingApptNearestAM1 = this.findMatchingInshopAppt(
|
||||
this.selectableDatesInshop?.days,
|
||||
timeOfDayArray[0],
|
||||
this.selectedProvider
|
||||
);
|
||||
let matchingApptNearestPM1 = this.findMatchingInshopAppt(
|
||||
this.selectableDatesInshop?.days,
|
||||
timeOfDayArray[1],
|
||||
this.selectedProvider
|
||||
);
|
||||
let matchingApptNearestAM2;
|
||||
let matchingApptNearestPM2;
|
||||
let matchingApptNearestAM3;
|
||||
let matchingApptNearestPM3;
|
||||
|
||||
const providerNearest2 = this.shopProviderData.shopProviders[1];
|
||||
const providerNearest3 = this.shopProviderData.shopProviders[2];
|
||||
|
||||
if (providerNearest2?.distanceInMiles && providerNearest2.distanceInMiles < 25) {
|
||||
this.calendarLoadingStatus = "more";
|
||||
const selectableDaysFromProviderNearest2 = await getScheduleApiResponse({
|
||||
startDateString: this.initialViewStartDate,
|
||||
endDateString: this.initialViewEndDate,
|
||||
inshopProviderNumber: providerNearest2.providerNumber,
|
||||
zipCode: this.zipCode,
|
||||
includeMobileTimeSlots: false,
|
||||
includeInshopTimeSlots: true,
|
||||
});
|
||||
matchingApptNearestAM2 = this.findMatchingInshopAppt(
|
||||
selectableDaysFromProviderNearest2?.inshopTimeSlotsData?.days,
|
||||
"AM",
|
||||
providerNearest2
|
||||
);
|
||||
matchingApptNearestPM2 = this.findMatchingInshopAppt(
|
||||
selectableDaysFromProviderNearest2?.inshopTimeSlotsData?.days,
|
||||
"PM",
|
||||
providerNearest2
|
||||
);
|
||||
}
|
||||
if (providerNearest3?.distanceInMiles && providerNearest3?.distanceInMiles < 25) {
|
||||
this.calendarLoadingStatus = "more";
|
||||
const selectableDaysFromProviderNearest3 = await getScheduleApiResponse({
|
||||
startDateString: this.initialViewStartDate,
|
||||
endDateString: this.initialViewEndDate,
|
||||
inshopProviderNumber: providerNearest3.providerNumber,
|
||||
zipCode: this.zipCode,
|
||||
includeMobileTimeSlots: false,
|
||||
includeInshopTimeSlots: true,
|
||||
});
|
||||
matchingApptNearestAM3 = this.findMatchingInshopAppt(
|
||||
selectableDaysFromProviderNearest3?.inshopTimeSlotsData?.days,
|
||||
"AM",
|
||||
providerNearest3
|
||||
);
|
||||
matchingApptNearestPM3 = this.findMatchingInshopAppt(
|
||||
selectableDaysFromProviderNearest3?.inshopTimeSlotsData?.days,
|
||||
"PM",
|
||||
providerNearest3
|
||||
);
|
||||
}
|
||||
this.calendarLoadingStatus = "none";
|
||||
|
||||
// WHICH OF THE 3 INSHOP APPTS IS THE EARLIEST?
|
||||
const compareAppointments = (appt1, appt2) => {
|
||||
// Handle null/undefined appointments
|
||||
if (!appt1?.date) return 1; // appt1 is later (or invalid)
|
||||
if (!appt2?.date) return -1; // appt2 is later (or invalid)
|
||||
|
||||
// Compare dates first (YYYY-MM-DD format allows string comparison)
|
||||
if (appt1.date < appt2.date) return -1; // appt1 is earlier
|
||||
if (appt1.date > appt2.date) return 1; // appt2 is earlier
|
||||
|
||||
// Dates are equal, compare times (HH:MM format allows string comparison)
|
||||
const time1 = appt1.timeSlot?.startTime || "23:59";
|
||||
const time2 = appt2.timeSlot?.startTime || "23:59";
|
||||
|
||||
if (time1 < time2) return -1; // appt1 is earlier
|
||||
if (time1 > time2) return 1; // appt2 is earlier
|
||||
|
||||
return 0; // Completely equal
|
||||
};
|
||||
// Find earliest AM appointment
|
||||
const preferredApptAM =
|
||||
[matchingApptNearestAM1, matchingApptNearestAM2, matchingApptNearestAM3]
|
||||
.filter((appt) => appt?.date) // Remove null/undefined
|
||||
.sort(compareAppointments)[0] || null;
|
||||
|
||||
// Find earliest PM appointment
|
||||
const preferredApptPM =
|
||||
[matchingApptNearestPM1, matchingApptNearestPM2, matchingApptNearestPM3]
|
||||
.filter((appt) => appt?.date)
|
||||
.sort(compareAppointments)[0] || null;
|
||||
|
||||
return [preferredApptAM, preferredApptPM];
|
||||
},
|
||||
updateMobileFirstTimeSlotandNavigateForward(timeSlotObj) {
|
||||
this.selectedMobileFirstAppointment = true;
|
||||
|
|
@ -2283,21 +2375,62 @@ export default {
|
|||
this.forwardButtonAction();
|
||||
},
|
||||
updateMultiLocationTimeSlotandNavigateForward({ selectedTimeSlot, appointmentType }) {
|
||||
if (appointmentType?.toLowerCase() == "mobile") {
|
||||
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||
} else if (appointmentType?.toLowerCase() == "inshop") {
|
||||
this.appointmentType = AppointmentTypeStrings.IN_SHOP;
|
||||
}
|
||||
if (!selectedTimeSlot) return;
|
||||
this.selectedMultiLocationAppointment = true;
|
||||
this.selectedDate = selectedTimeSlot.date;
|
||||
this.updateSelectedProvider();
|
||||
this.updateTimeSlot(selectedTimeSlot);
|
||||
if (appointmentType?.toLowerCase() == "mobile") {
|
||||
this.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||
this.updateSelectedProvider();
|
||||
this.updateTimeSlot(selectedTimeSlot);
|
||||
} else if (appointmentType?.toLowerCase() == "inshop") {
|
||||
this.appointmentType = AppointmentTypeStrings.IN_SHOP;
|
||||
|
||||
this.updateSelectedProvider(selectedTimeSlot.provider);
|
||||
|
||||
// UPDATE SELECTED TIME SLOT INFO
|
||||
this.selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
date: selectedTimeSlot.date,
|
||||
routeCode: selectedTimeSlot.routeCode,
|
||||
startTime: selectedTimeSlot.timeSlot.startTime,
|
||||
endTime: selectedTimeSlot.timeSlot.endTime,
|
||||
jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
|
||||
jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(),
|
||||
},
|
||||
isPremiumAppointment: selectedTimeSlot.isPremiumAppointment ? true : false,
|
||||
};
|
||||
|
||||
// UPDATE APPT TYPE
|
||||
this.appointmentType = this.getInShopOrDropOffApptType(selectedTimeSlot.routeCode);
|
||||
}
|
||||
this.forwardButtonAction();
|
||||
},
|
||||
updateRecalAcknowledgedAndNavigateForward(isAcknowledged) {
|
||||
this.isRecalAcknowledgedForScheduling = isAcknowledged;
|
||||
this.forwardButtonAction();
|
||||
},
|
||||
getFormattedShopAddress(providerAddress, distanceInMiles) {
|
||||
const toTitleCase = (str) => {
|
||||
if (!str) return "";
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
if (!providerAddress) return {};
|
||||
|
||||
const streetAddress = toTitleCase(providerAddress.streetAddress);
|
||||
const city = toTitleCase(providerAddress.city);
|
||||
const state = providerAddress.state;
|
||||
const zipCode = providerAddress.zipCode;
|
||||
|
||||
return {
|
||||
city: city,
|
||||
distance: `${distanceInMiles} mi`,
|
||||
address1: streetAddress,
|
||||
address2: `${city}, ${state} ${zipCode}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
appointmentTypeFromAppointmentTypeQuestion: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue