Merge branch 'develop' into feature/CSR-1144

This commit is contained in:
CarlNation 2023-05-26 06:34:00 -04:00
commit 8d2f0f28dd
9 changed files with 218 additions and 106 deletions

View file

@ -26,7 +26,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
global: {
statements: 80,
statements: 78,
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
},
},

View file

@ -108,12 +108,14 @@ const endpoints = {
},
GetShopTimeSlots: {
url: "/schedule/api/v1/schedule/shop-time-slots",
mockUrl: "https://mockey.qa.sagaws.net/service/shop-time-slots", // TODO: REMOVE MOCKURL
method: "POST",
},
GetMobileTimeSlots: {
url: "/schedule/api/v1/schedule/mobile-time-slots",
method: "POST",
},
GetMobileEarlyBirdFee: {
url: "/parts/api/v1/parts/mobile-early-bird-fee",
mockUrl: "https://mockey.qa.sagaws.net/service/mobile-early-bird-fee", // TODO: REMOVE MOCKURL
method: "GET",
},
SaveSession: {

View file

@ -33,6 +33,7 @@ const storeActions = {
GET_MOBILE_FEE_PART: "getMobileFeePart",
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots",
GET_PROVIDERS: "getProviders",
GET_MOBILE_EARLY_BIRD_FEE: "getMobileEarlyBirdFee",
SAVE_SESSION: "saveSession",

View file

@ -245,9 +245,10 @@ export default {
let myPromise = new Promise((resolve, reject) => {
const response = config.customSelectableDatesCallback(
initialViewStartDate,
initialViewEndDate,
store.getters.order.serviceLocation.appointmentType
initialViewStartDate.toISOString().split("T")[0],
initialViewEndDate.toISOString().split("T")[0],
store.getters.order.serviceLocation.appointmentType,
store.getters.order.serviceLocation.provider.providerNumber
);
resolve(response);
});
@ -309,7 +310,6 @@ export default {
this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView;
const hideSecondMonth = config.hideSecondMonth;
const direction = config.calendarViewDirection;
const monthsAfterToLoadOffset = 12;
const monthsBeforeToLoadOffset = 36;
config.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
@ -526,7 +526,8 @@ export default {
const moreSelectableDates = await this.customSelectableDatesCallback(
monthStart,
monthEnd,
this.$store.getters.order.serviceLocation.appointmentType
this.$store.getters.order.serviceLocation.appointmentType,
this.$store.getters.order.serviceLocation.provider.providerNumber
);
moreSelectableDates.days.forEach((selectableDate) => {
const index = this.selectableDatesData.findIndex(

View file

@ -81,18 +81,33 @@ import store from "@/store";
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
defineRule("time-slot-selection-required", required(errorMessages.DATE_REQUIRED));
const getAvailableDates = async (startDate, endDate, appointmentType) => {
const getAvailableDates = async (startDate, endDate, appointmentType, providerNumber) => {
// USING DATES PASSED, MAKE AN API CALL
const newShopTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
storeActions.GET_SHOP_TIME_SLOTS,
{
startDate: startDate,
endDate: endDate,
shopAppointmentType: appointmentType,
},
false
);
const newShopTimeSlots = newShopTimeSlotsResponse.data;
let newTimeSlotsResponse;
if (appointmentType === AppointmentTypeStrings.MOBILE) {
newTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
storeActions.GET_MOBILE_TIME_SLOTS,
{
startDate: startDate,
endDate: endDate,
},
false
);
} else {
newTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
storeActions.GET_SHOP_TIME_SLOTS,
{
startDate: startDate,
endDate: endDate,
shopAppointmentType: appointmentType,
providerNumber: providerNumber,
},
false
);
}
const newShopTimeSlots = newTimeSlotsResponse.data;
return convertApiResponse(newShopTimeSlots);
};
const convertApiResponse = (responseData) => {
@ -152,7 +167,6 @@ export default {
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
store.getters.order.serviceLocation.zipCodeCtu
);
// Settle promises and get results
const promiseResultMap = [
{
@ -241,7 +255,8 @@ export default {
const newShopTimeSlots = await getAvailableDates(
startDate,
endDate,
this.appointmentType
this.appointmentType,
this.$store.getters.order.serviceLocation.provider.providerNumber
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days = this.selectableDatesData.days.concat(
@ -262,7 +277,7 @@ export default {
return timeSlots.find((timeSlot) => timeSlot.id === timeSlotId);
},
timeSlotModalClosed() {
// Clear the selectedDate if no timeslot has been selected
// Clear the selectedDate if no timeSlot has been selected
if (!this.selectedTimeSlotData.id) {
this.selectedDate = null;
}
@ -302,6 +317,7 @@ export default {
convertSelectedDateToShortMonthAndDay(selectedDate) {
// This conversion ensures we don't get get GMT induced date changes
const dateObject = new Date(`${selectedDate.dateString}T00:00:00`);
// Ex: April 25
return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" });
},
// Expected input: "HH:MM:SS"

View file

@ -15,7 +15,6 @@
{{ formattedButtonLabelSubCopy }}
</span>
</span>
<span v-if="screenReaderOnlyText" class="sr-only">
{{ screenReaderOnlyText }}
</span>
@ -28,7 +27,7 @@ import baseInputButton from "@/digital-components/base-input-button/base-input-b
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
export default {
name: "timeslotModalListButton",
name: "timeSlotModalListButton",
mixins: [inputButtonWrapperMixin],
computed: {
formattedButtonLabelSubCopy() {
@ -52,9 +51,6 @@ export default {
</script>
<style lang="scss" scoped>
.loader {
position: absolute;
}
.list-button {
outline: none;
input[type="radio"],

View file

@ -14,8 +14,8 @@
typeStyle="small"
class="duration-text-block" />
<buttonQuestion
buttonTypeString="timeslotModalListButton"
:buttonTypeObject="timeslotModalListButton"
buttonTypeString="timeSlotModalListButton"
:buttonTypeObject="timeSlotModalListButton"
:answers="availableTimeSlots"
groupName="ChooseTimeSlot"
textPosition="text-center"
@ -41,7 +41,7 @@
import modal from "@/digital-components/modal/modal";
import textBlock from "@/digital-components/text-block/text-block";
import buttonQuestion from "@/digital-components/button-question/button-question";
import timeslotModalListButton from "./timeslot-modal-list-button/timeslot-modal-list-button";
import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button";
// TODO: Move this somewhere more global
import { DAYS_OF_WEEK, MONTHS_OF_YEAR } from "@/digital-components/date-picker/mixins/constants.js";
@ -81,7 +81,7 @@ export default {
return {
selectedTimeSlotId: null,
isSelectedAppointmentPremium: null,
timeslotModalListButton: timeslotModalListButton,
timeSlotModalListButton: timeSlotModalListButton,
};
},
setup(props) {
@ -164,60 +164,24 @@ export default {
}
// This conversion ensures we don't get get GMT induced date changes
const dateObject = new Date(`${this.dateAndTimeSlotData.dateString}T00:00:00`);
const weekdayName = DAYS_OF_WEEK[dateObject.getDay()];
const month = MONTHS_OF_YEAR[dateObject.getMonth()];
const numberDayOfMonth = dateObject.getDate();
// Ex. Tuesday, April 23
return `${weekdayName}, ${month} ${numberDayOfMonth}`;
// Ex: Tuesday, April 22
return dateObject.toLocaleDateString("en-us", {
weekday: "long",
month: "long",
day: "numeric",
});
},
availableTimeSlots() {
if (this.dateAndTimeSlotData === null) {
return null;
}
let availableTimeSlots;
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
availableTimeSlots = [
{
value: this.dateAndTimeSlotData.timeSlots[0].id,
buttonLabel: this.dropoffButtonText,
},
];
return this.getAvailableTimeSlotsForDropOff(this.dateAndTimeSlotData.timeSlots);
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
return this.getAvailableTimeSlotsForMobile(this.dateAndTimeSlotData.timeSlots);
} else {
availableTimeSlots = this.dateAndTimeSlotData.timeSlots.map((timeslot) => {
let readableTime;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
readableTime = this.getDisplayTextForMilitaryTime(timeslot.startTime);
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
readableTime = `${this.getDisplayTextForMilitaryTime(
timeslot.startTime
)} - ${this.getDisplayTextForMilitaryTime(timeslot.endTime)}`;
}
return {
value: timeslot.id,
buttonLabel: readableTime,
};
});
return this.getAvailableTimeSlotsForInshop(this.dateAndTimeSlotData.timeSlots);
}
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
const isPremiumTimeSlot = this.dateAndTimeSlotData.timeSlots[0].offerPremium;
const hasPremiumPartAvailable =
this.premiumAppointmentFee?.partType === PREMIUM_FEE_PART_TYPE;
if (isPremiumTimeSlot && hasPremiumPartAvailable) {
const formattedPrice =
"+$" + this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2);
availableTimeSlots.unshift({
// Unique value is required for each <input> and the premium appoinment shares a timeslot ID
value: this.addPremiumFlagToInput(this.dateAndTimeSlotData.timeSlots[0].id),
buttonLabel: this.premiumAppointmentButtonText,
buttonLabelSubCopy: formattedPrice,
additionalButtonData: {
isPremiumAppointment: true,
},
});
}
}
return availableTimeSlots;
},
},
methods: {
@ -270,6 +234,58 @@ export default {
}
return displayTextForDurationLength;
},
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
return timeSlotsForSelectedDate.map((timeSlot) => {
const readableTime = this.getDisplayTextForMilitaryTime(timeSlot.startTime);
return {
value: timeSlot.id,
buttonLabel: readableTime,
};
});
},
getAvailableTimeSlotsForDropOff(timeSlotsForSelectedDate) {
return [
{
value: timeSlotsForSelectedDate[0].id,
buttonLabel: this.dropoffButtonText,
},
];
},
getAvailableTimeSlotsForMobile(timeSlotsForSelectedDate) {
const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => {
const readableTime = `${this.getDisplayTextForMilitaryTime(
timeSlot.startTime
)} - ${this.getDisplayTextForMilitaryTime(timeSlot.endTime)}`;
return {
value: timeSlot.id,
buttonLabel: readableTime,
};
});
const isPremiumTimeSlot = timeSlotsForSelectedDate[0].offerPremium;
const hasPremiumPartAvailable =
this.premiumAppointmentFee?.partType === PREMIUM_FEE_PART_TYPE;
if (isPremiumTimeSlot && hasPremiumPartAvailable) {
availableTimeSlots.unshift(
this.getPremiumAppointmentTimeSlot(timeSlotsForSelectedDate[0])
);
}
return availableTimeSlots;
},
getPremiumAppointmentTimeSlot(timeSlotData) {
const formattedPrice =
"+$" + this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2);
return {
// Unique value is required for each <input> and the premium appoinment shares a timeSlot ID
value: this.addPremiumFlagToInput(timeSlotData.id),
buttonLabel: this.premiumAppointmentButtonText,
buttonLabelSubCopy: formattedPrice,
additionalButtonData: {
isPremiumAppointment: true,
},
};
},
autoSelectTimeSlotIfOnlyOneIsAvailable(newdateAndTimeSlotDataValue) {
const numberOfOptions = newdateAndTimeSlotDataValue?.timeSlots.length;
if (

View file

@ -236,6 +236,17 @@ export default {
});
},
async setMobileLocation() {
// START - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD
let tempTest = false;
let internalModelAddressQuestions = this.internalModel.addressQuestions;
let modelValueAddressQuestions = this.modelValue.addressQuestions;
if (tempTest) {
// THIS WILL NEVER BE TRUE
console.warn(internalModelAddressQuestions);
console.warn(modelValueAddressQuestions);
}
// END - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD
if (
this.internalModel.addressQuestions.zipCode !==
this.modelValue.addressQuestions.zipCode
@ -270,6 +281,13 @@ export default {
this.closeModal();
}
} else {
// START - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD
if (tempTest) {
// THIS WILL NEVER BE TRUE
console.warn("the zips match and the exception was run");
}
// END - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD
}
},
},

View file

@ -1116,51 +1116,113 @@ export const actions = {
});
},
getShopTimeSlots(
context,
{ startDate = "2023-01-01", endDate = "2023-05-01", shopAppointmentType = "" }
) {
getShopTimeSlots(context, { startDate, endDate, shopAppointmentType, providerNumber }) {
const order = context.state.order;
const mockArray = [];
const vehicle = context.state.order.vehicle;
let partNumbers = [
...(order.lineItems.supportingItems ?? []),
...(order.lineItems.vaps ?? []),
...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts),
];
partNumbers = partNumbers.map((lineItem) => {
return lineItem.partNumber;
});
const glassPieces = order.damage.glassToReplace ?? [];
var payload = {
providerNumber: order.providerNumber,
providerNumber: providerNumber,
startDate: startDate,
endDate: endDate,
shopAppointmentType: shopAppointmentType,
applicationName: applicationConfig.APPLICATION_NAME,
applicationName: "Safelite.com funnel",
parentAccountNumber: context.getters.payment.parentAccountNumber,
carId: context.getters.vehicle.carId,
partNumbers: mockArray, // TODO: WHERE DO I GET THIS?
carId: vehicle.carId,
partNumbers: partNumbers,
glassPieces: glassPieces,
eon: order.eon,
provisionalReasons: mockArray, // TODO: WHERE DO I GET THIS?
coverage: {
status: "",
deductible: 0,
additionalAuthFlag: "",
},
partSelection: {
// TODO: Provisional booking will utilize these fields
hasAnsweredPartQuestions: false,
hasAnsweredMoldingQuestions: false,
hasAnsweredCapabilityQuestions: false,
hasManuallySelectedParts: false,
},
vehicle: {
year: vehicle.year,
make: vehicle.make,
model: vehicle.model,
style: vehicle.style,
vin: vehicle.vin,
},
};
// TODO: REMOVE MOCK CALL & USE REAL CALL BELOW
return globalMethods.callMockHttpClient({
return globalMethods.callHttpClient({
method: endpoints.GetShopTimeSlots.method,
endpoint: endpoints.GetShopTimeSlots.mockUrl,
endpoint: endpoints.GetShopTimeSlots.url,
payload: payload,
logApiCall: false,
});
},
getMobileTimeSlots(context, { startDate, endDate }) {
const order = context.state.order;
const vehicle = context.state.order.vehicle;
let partNumbers = [
...(order.lineItems.supportingItems ?? []),
...(order.lineItems.vaps ?? []),
...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts),
];
partNumbers = partNumbers.map((lineItem) => {
return lineItem.partNumber;
});
const glassPieces = order.damage.glassToReplace ?? [];
var payload = {
startDate: startDate,
endDate: endDate,
applicationName: "Safelite.com funnel",
parentAccountNumber: context.getters.payment.parentAccountNumber,
carId: vehicle.carId,
partNumbers: partNumbers,
glassPieces: glassPieces,
eon: order.eon,
coverage: {
status: "",
deductible: 0,
additionalAuthFlag: "",
},
partSelection: {
// TODO: Provisional booking will utilize these fields
hasAnsweredPartQuestions: false,
hasAnsweredMoldingQuestions: false,
hasAnsweredCapabilityQuestions: false,
hasManuallySelectedParts: false,
},
vehicle: {
year: vehicle.year,
make: vehicle.make,
model: vehicle.model,
style: vehicle.style,
vin: vehicle.vin,
},
zipCode: order.serviceLocation.zipCode,
};
return globalMethods.callHttpClient({
method: endpoints.GetMobileTimeSlots.method,
endpoint: endpoints.GetMobileTimeSlots.url,
payload: payload,
logApiCall: false,
});
// TODO: RESTORE THIS
// return globalMethods.callHttpClient({
// method: endpoints.GetShopTimeSlots.method,
// endpoint: endpoints.GetShopTimeSlots.url,
// payload: payload,
// logApiCall: false,
// });
},
getMobileEarlyBirdFee(context) {
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash";
return globalMethods.callMockHttpClient({
return globalMethods.callHttpClient({
method: endpoints.GetMobileEarlyBirdFee.method,
endpoint: `${endpoints.GetMobileEarlyBirdFee.mockUrl}/Cash/Replace`,
endpoint: `${endpoints.GetMobileEarlyBirdFee.url}/${paymentType}/${damageType}`,
});
// return globalMethods.callHttpClient({
// method: endpoints.GetMobileEarlyBirdFee.method,
// endpoint: `${endpoints.GetMobileEarlyBirdFee.url}/${paymentType}/${damageType}`,
// });
},
// Session API Actions
saveSession(context) {
@ -1863,7 +1925,7 @@ function addPricesToLineItems(lineItems, pricingLineItems) {
function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {
let flattenedArray = [];
lineItems.forEach((lineItem) => {
lineItems?.forEach((lineItem) => {
flattenedArray.push(lineItem);
if (lineItem.childParts) {
flattenedArray = [