From 63245465a8a947833071969d5f2a2ce6a951beaa Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Wed, 31 May 2023 11:44:57 -0400 Subject: [PATCH 01/16] CSR-1137 | Remove pre-emptive pricing call Added them in sequence to run together with the async group Also removed the logApiCall:false --- src/layouts/schedule/schedule.vue | 44 +++++++++++++------------------ src/store/index.js | 2 -- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 3a4290cb9..917613296 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -73,7 +73,7 @@ import { getRouterLinkRouteFromCopy, getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"; -import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "./constants/schedule-constants"; +import { AppointmentTypeStrings } from "./constants/schedule-constants"; import { errorMessages } from "@/constants/error-messages"; import { required } from "@/helpers/validation-rules"; import store from "@/store"; @@ -146,24 +146,24 @@ export default { customSelectableDatesCallback: getAvailableDates, }); - // Price EARLY BIRD pre-emptively to allow for asynchronous call to pricing - const pricingPromise = baseMixin.methods.dispatchStoreAction( - storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, - { - availableLineItems: [ - { - partNumber: PREMIUM_FEE_PART_TYPE, - partType: PREMIUM_FEE_PART_TYPE, - description: null, - }, - ], - }, - false - ); - const premiumFeePromise = baseMixin.methods.dispatchStoreAction( storeActions.GET_MOBILE_PREMIUM_FEE ); + + const premiumFeeWithPricePromise = premiumFeePromise.then(result => { + if (result.data) { + return baseMixin.methods.dispatchStoreAction( + storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, + { + availableLineItems: [result.data], + }, + false + ); + } else { + return result.data; + } + }); + const alertReasonsPromise = locationAlerts.methods.loadInitialData( store.getters.order.serviceLocation.zipCodeCtu ); @@ -182,12 +182,8 @@ export default { promise: datePickerInitialDataPromise, }, { - resultKey: "premiumFee", - promise: premiumFeePromise, - }, - { - resultKey: "pricingResults", - promise: pricingPromise, + resultKey: "premiumFeeWithPrice", + promise: premiumFeeWithPricePromise, }, ]; @@ -199,9 +195,7 @@ export default { vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData); vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons); vm.selectableDatesData = resultMap.datePickerInitialData.initialShopTimeSlotsResponse; - if (resultMap.premiumFee) { - vm.mobilePremiumAppointmentFee = resultMap.pricingResults[0]; - } + vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice ? resultMap.premiumFeeWithPrice[0] : null; }); }, computed: { diff --git a/src/store/index.js b/src/store/index.js index ee7ea4911..bb8ba2868 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1187,7 +1187,6 @@ export const actions = { method: endpoints.GetShopTimeSlots.method, endpoint: endpoints.GetShopTimeSlots.url, payload: payload, - logApiCall: false, }); }, @@ -1239,7 +1238,6 @@ export const actions = { method: endpoints.GetMobileTimeSlots.method, endpoint: endpoints.GetMobileTimeSlots.url, payload: payload, - logApiCall: false, }); }, From 122f9bffb5735cd54a379d475c8aeca61d752efe Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Wed, 31 May 2023 11:48:35 -0400 Subject: [PATCH 02/16] CSR-1137 | Formatting --- src/layouts/schedule/schedule.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 917613296..42775f476 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -150,7 +150,7 @@ export default { storeActions.GET_MOBILE_PREMIUM_FEE ); - const premiumFeeWithPricePromise = premiumFeePromise.then(result => { + const premiumFeeWithPricePromise = premiumFeePromise.then((result) => { if (result.data) { return baseMixin.methods.dispatchStoreAction( storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, @@ -195,7 +195,9 @@ export default { vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData); vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons); vm.selectableDatesData = resultMap.datePickerInitialData.initialShopTimeSlotsResponse; - vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice ? resultMap.premiumFeeWithPrice[0] : null; + vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice + ? resultMap.premiumFeeWithPrice[0] + : null; }); }, computed: { From db7ac46935ed739be8aac34eb6c28be1c6c8f993 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Mon, 5 Jun 2023 15:37:09 -0400 Subject: [PATCH 03/16] CSR-1137 | Handle auto-loading of existing store data Date and time-slot are auto selected Date-picker still needs work to handle dates that are beyond initial load set. --- src/layouts/schedule/schedule.vue | 14 +++++++------- .../time-slot-modal-question.vue | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 4a80e74d2..cbfa07d33 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -185,6 +185,7 @@ export default { vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice ? resultMap.premiumFeeWithPrice[0] : null; + vm.updateFooterButtonText(vm.selectedTimeSlotData); }); }, computed: { @@ -199,11 +200,11 @@ export default { return this.$store.getters.order.serviceLocation.appointmentType; }, timeSlotsForSelectedDate() { - if (this.selectedDate === null) { + if (!this.selectedDate) { return null; } return this.selectableDatesData.days?.find( - (selectableDate) => selectableDate.dateString === this.selectedDate.dateString + (selectableDate) => selectableDate.date === this.selectedDate ); }, appointmentDateAndTime() { @@ -213,10 +214,9 @@ export default { const timeSlotSelectedObject = this.getTimeSlotObjectFromTimeSlotId( this.selectedTimeSlotData.id ); - if (timeSlotSelectedObject) { return { - date: this.selectedDate.dateString, + date: this.selectedDate, startTime: timeSlotSelectedObject.startTime, endTime: timeSlotSelectedObject.endTime, routeCode: this.selectedTimeSlotData.id, @@ -258,7 +258,7 @@ export default { }, getTimeSlotObjectFromTimeSlotId(timeSlotId) { const timeSlots = this.selectableDatesData.days.find( - (selectableDate) => selectableDate.dateString === this.selectedDate.dateString + (selectableDate) => selectableDate.date === this.selectedDate ).timeSlots; return timeSlots.find((timeSlot) => timeSlot.id === timeSlotId); }, @@ -308,7 +308,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`); + const dateObject = new Date(`${selectedDate}T00:00:00`); // Ex: April 25 return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" }); }, @@ -348,7 +348,7 @@ export default { }; } }, - selectedTimeSlotData() { + selectedTimeSlotData(newValue) { this.updateFooterButtonText(this.selectedTimeSlotData); }, }, diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue index 62d052941..10640dfd9 100644 --- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue +++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue @@ -78,13 +78,14 @@ export default { }, data() { return { - selectedTimeSlotId: null, + selectedTimeSlotId: this.modelValue.id, timeSlotModalListButton: timeSlotModalListButton, }; }, setup(props) { const { handleChange } = useField("time-slot-modal-question", props.validationRules); - + // Run validation on component load + handleChange(props.modelValue.id); return { handleChange, }; @@ -92,6 +93,11 @@ export default { watch: { modelValue() { // Run component validation that is used at parent level + if (this.modelValue.isPremiumAppointment) { + this.selectedTimeSlotId = this.addPremiumFlagToInput(this.modelValue.id); + } else { + this.selectedTimeSlotId = this.modelValue.id; + } this.handleChange(this.modelValue.id); }, availableTimeSlots(newValue) { @@ -162,7 +168,7 @@ 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 dateObject = new Date(`${this.dateAndTimeSlotData.date}T00:00:00`); // Ex: Tuesday, April 22 return dateObject.toLocaleDateString("en-us", { weekday: "long", @@ -203,12 +209,6 @@ export default { }, // fires any time the modal is closed, AFTER "closeModal" fires if footer button is used onModalClosed() { - // Reset component state to parent's state - if (this.modelValue.isPremiumAppointment) { - this.selectedTimeSlotId = this.addPremiumFlagToInput(this.modelValue.id); - } else { - this.selectedTimeSlotId = this.modelValue.id; - } this.$emit("time-slot-modal-closed"); }, // Expected input: "HH:MM" From 849844faa3b96d1d7c55fa374b1e00f0f388625e Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 6 Jun 2023 09:43:29 -0400 Subject: [PATCH 04/16] CSR-1150 | Schedule Page pre-requisites --- src/layouts/schedule/schedule.vue | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index cbfa07d33..8f7c39338 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -234,8 +234,20 @@ export default { getRouterLinkRouteFromCopy, getRouterLinkDisplayTextFromCopy, arePagePrerequisitesValid() { - return true; - // NEED TODO - WHAT ARE PAGE REQ'S FOR THIS PAGE? + const serviceLocation = store.getters.order.serviceLocation; + const serviceLocationPreReqs = + serviceLocation.zipCode && + serviceLocation.zipCodeCtu && + serviceLocation.appointmentType && + (serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE || + serviceLocation.provider.providerNumber); + const paymentInfo = store.getters.payment.isInsurance !== null; + const supportingItems = store.getters.lineItems.supportingItems !== null; + const damageInfo = + store.getters.order.damage.isRepair || + (store.getters.order.lineItems?.glassParts != null && + store.getters.order.lineItems.glassParts.length > 0); + return serviceLocationPreReqs && paymentInfo && supportingItems && damageInfo; }, async getAvailableDatesMethod(startDate, endDate) { const newShopTimeSlots = await getAvailableDates( From 66c1292e52bb5ea93ec6521ae9090b42a9a434e6 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Tue, 6 Jun 2023 15:08:47 -0400 Subject: [PATCH 05/16] CSR-1144 Verify the appointment date is not in the past and still available --- .../constants/schedule-constants.js | 0 src/constants/store-mutations.js | 1 + src/layouts/schedule/schedule.vue | 2 +- .../time-slot-modal-question.vue | 2 +- src/store/index.js | 92 ++++++++++++++++++- 5 files changed, 94 insertions(+), 3 deletions(-) rename src/{layouts/schedule => }/constants/schedule-constants.js (100%) diff --git a/src/layouts/schedule/constants/schedule-constants.js b/src/constants/schedule-constants.js similarity index 100% rename from src/layouts/schedule/constants/schedule-constants.js rename to src/constants/schedule-constants.js diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 8eeb70c99..82cc2082d 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -58,6 +58,7 @@ const storeMutations = { RESET_GLASS_PARTS_STATE: "resetGlassPartsState", RESET_STATE: "resetState", RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise", + RESET_SCHEDULE: "resetSchedule", // OTHER MUTATIONS UPDATE_PAGE_DATA: "updatePageData", diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index cbfa07d33..c1d491deb 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -73,7 +73,7 @@ import { getRouterLinkRouteFromCopy, getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"; -import { AppointmentTypeStrings } from "./constants/schedule-constants"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import { errorMessages } from "@/constants/error-messages"; import { required } from "@/helpers/validation-rules"; import store from "@/store"; diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue index 10640dfd9..7e9b5a279 100644 --- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue +++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue @@ -53,7 +53,7 @@ import { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE, -} from "../constants/schedule-constants"; +} from "@/constants/schedule-constants"; // Validation for the modal button defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED)); diff --git a/src/store/index.js b/src/store/index.js index 67917bf01..eac41c311 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -11,6 +11,7 @@ import { damageLocationsSelected } from "@/constants/damage-locations-selected"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import router from "@/router"; import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper.js"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; // Export State const getDefaultState = () => { @@ -342,6 +343,13 @@ export const mutations = { state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null; state.applicationUser.pageData[fmgPageValues.CAPABILITY_QUESTIONS] = null; }, + resetSchedule(state) { + state.order.schedule.date = null; + state.order.schedule.startTime = null; + state.order.schedule.endTime = null; + state.order.schedule.routeCode = null; + state.order.schedule.jobMaxMinutes = null; + }, resetState(state) { Object.assign(state, getDefaultState()); }, @@ -1384,7 +1392,7 @@ export const actions = { }, }) .then( - (response) => { + async (response) => { // Flatten location and name properties response.data.order.damage?.glassToReplace?.map((glass) => { glass.glassLocation = glass.location; @@ -1402,6 +1410,8 @@ export const actions = { storeMutations.UPDATE_STATE_WITH_ORDER_INFORMATION, response.data ); + + await validateAppointment(context, response.data.order); return response; }, (error) => { @@ -2012,3 +2022,83 @@ function convertGlassPieceToBackEndCompatibleFormat(glassPieces) { }; }); } + +// This function will verify schedule info is still valid. +// check to see if we have an appointment date on the order object. +// if so, make sure it's not in the past. if in the past, clear schedule info in store. +// if date not in past, then call schedule service to verify appointment is still available. +async function validateAppointment(context, order) { + if (!order.schedule?.date) { + return; + } + + // Date string with slashes is parsed as local time, not UTC. Our date has dashes, '-'. + // If you put any kind of time stamp on the date string with dashes, then it IS parsed as local time. + var aptDate = new Date(order.schedule.date + "T00:00:00"); + var curDate = new Date(); + + // if appointment date is in the past, clear schedule + if (aptDate.getTime() < curDate.getTime()) { + context.commit(storeMutations.RESET_SCHEDULE); + return; + } + + // create date range to pass to the schedule service to see if our appointment is still available. + var endRange = new Date(aptDate); + endRange.setDate(aptDate.getDate() + 1); + + var endDay = "" + endRange.getDate(); + var endMonth = "" + (endRange.getMonth() + 1); // 0 based so add 1 + const endYear = endRange.getFullYear(); + + if (endMonth.length < 2) { + endMonth = "0" + endMonth; + } + if (endDay.length < 2) { + endDay = "0" + endDay; + } + + const endDate = [endYear, endMonth, endDay].join("-"); + + let newTimeSlotsResponse; + if (order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE) { + newTimeSlotsResponse = await context.dispatch( + storeActions.GET_MOBILE_TIME_SLOTS, + { + startDate: order.schedule.date, + endDate: endDate, + }, + false + ); + } else { + newTimeSlotsResponse = await context.dispatch( + storeActions.GET_SHOP_TIME_SLOTS, + { + startDate: order.schedule.date, + endDate: endDate, + shopAppointmentType: order.serviceLocation.appointmentType, + providerNumber: order.serviceLocation.provider.providerNumber, + }, + false + ); + } + + if (newTimeSlotsResponse?.data.days?.length === 0) { + context.commit(storeMutations.RESET_SCHEDULE); + return; + } + + var routeCodeFound = false; + newTimeSlotsResponse.data.days?.forEach((day) => { + day.timeSlots.forEach((ts) => { + if (ts.id === order.schedule.routeCode) { + routeCodeFound = true; + } + }); + }); + + if (!routeCodeFound) { + context.commit(storeMutations.RESET_SCHEDULE); + return; + } +} From af8db1c343168fa9e260253a956d7f73362c8f54 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Wed, 7 Jun 2023 07:42:42 -0400 Subject: [PATCH 06/16] CSR-1144 Save premium early bird appointment into supporting items --- src/layouts/schedule/schedule.vue | 35 +++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 675c79887..31382b471 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -73,7 +73,7 @@ import { getRouterLinkRouteFromCopy, getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"; -import { AppointmentTypeStrings } from "@/constants/schedule-constants"; +import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants"; import { errorMessages } from "@/constants/error-messages"; import { required } from "@/helpers/validation-rules"; import store from "@/store"; @@ -341,7 +341,8 @@ export default { backButtonAction() { this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); }, - async forwardButtonAction() { + forwardButtonAction() { + this.updateSupportingItems(); this.dispatchStoreAction( this.storeActions.SAVE_SCHEDULE, this.appointmentDateAndTime, @@ -349,6 +350,36 @@ export default { ); navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal }); }, + + updateSupportingItems() { + // if we have a premium fee(early bird), then save/update supporting items + if ( + this.appointmentType === AppointmentTypeStrings.MOBILE && + this.selectedTimeSlotData?.isPremiumAppointment > 0 + ) { + const supportingItems = store.getters.lineItems.supportingItems; + const earlyBirdIndex = supportingItems.findIndex( + (item) => item.partNumber == PREMIUM_FEE_PART_TYPE + ); + + if (earlyBirdIndex >= 0) { + supportingItems[earlyBirdIndex].laborAmount = + this.mobilePremiumAppointmentFee.laborAmount; + supportingItems[earlyBirdIndex].selingPrice = + this.mobilePremiumAppointmentFee.selingPrice; + supportingItems[earlyBirdIndex].kitPrice = + this.mobilePremiumAppointmentFee.kitPrice; + } else { + supportingItems.push(this.mobilePremiumAppointmentFee); + } + + this.dispatchStoreAction( + this.storeActions.SAVE_SUPPORTING_ITEMS, + supportingItems, + false + ); + } + }, }, watch: { selectedDate(newValue, oldValue) { From f0c7ecbb9b9641e63b90458c3dcf5cbbb7a4216d Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 7 Jun 2023 09:49:10 -0400 Subject: [PATCH 07/16] CSR-1421: refactor work to move router-link into textBlock instead of using it directly on page --- .../text-block/text-block.vue | 65 +++++++++++++++++-- .../address-vehicles/address-vehicles.vue | 21 +++--- src/layouts/schedule/schedule.vue | 23 +++---- src/ux-components/alert/alert.vue | 18 ++--- 4 files changed, 84 insertions(+), 43 deletions(-) diff --git a/src/digital-components/text-block/text-block.vue b/src/digital-components/text-block/text-block.vue index 2deb126e5..d8ed9ad54 100644 --- a/src/digital-components/text-block/text-block.vue +++ b/src/digital-components/text-block/text-block.vue @@ -1,11 +1,43 @@ diff --git a/src/layouts/address-vehicles/address-vehicles.vue b/src/layouts/address-vehicles/address-vehicles.vue index d45732217..de1fa9999 100644 --- a/src/layouts/address-vehicles/address-vehicles.vue +++ b/src/layouts/address-vehicles/address-vehicles.vue @@ -20,19 +20,12 @@ validationRules="vehicle-required" v-model="selectedVehicleVin" :isCarIdDifferent="isCarIdDifferent" /> -
- - - {{ getRouterLinkDisplayTextFromCopy(copy) }} - - - +
+
diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 8f7c39338..e316edfd2 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -4,20 +4,13 @@ -
- - - {{ getRouterLinkDisplayTextFromCopy(copy) }} - - - -
+ diff --git a/src/ux-components/alert/alert.vue b/src/ux-components/alert/alert.vue index c5899b440..c7e4aca30 100644 --- a/src/ux-components/alert/alert.vue +++ b/src/ux-components/alert/alert.vue @@ -14,17 +14,11 @@ v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)" v-html="paragraph">

-