diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 4196cbf19..3035b4ad7 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -112,10 +112,18 @@ const endpoints = { url: "/schedule/api/v1/schedule/shop-time-slots", method: "POST", }, + GetShopTimeSlotsV2: { + url: "/schedule/api/v2/schedule/shop-time-slots", + method: "POST", + }, GetMobileTimeSlots: { url: "/schedule/api/v1/schedule/mobile-time-slots", method: "POST", }, + GetMobileTimeSlotsV2: { + url: "/schedule/api/v2/schedule/mobile-time-slots", + method: "POST", + }, GetMobilePremiumFee: { url: "/parts/api/v1/parts/mobile-premium-fee", method: "GET", diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 7e136fefa..cb4c6ff54 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -35,7 +35,9 @@ const storeActions = { GET_PRICING_BY_DAY_PART: "getPricingByDayPart", GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails", GET_SHOP_TIME_SLOTS: "getShopTimeSlots", + GET_SHOP_TIME_SLOTS_V2: "getShopTimeSlotsV2", GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots", + GET_MOBILE_TIME_SLOTS_V2: "getMobileTimeSlotsV2", GET_PROVIDERS: "getProviders", GET_MOBILE_PREMIUM_FEE: "getMobilePremiumFee", SAVE_SESSION: "saveSession", diff --git a/src/layouts/scheduling/scheduling.vue b/src/layouts/scheduling/scheduling.vue index 7fa45f056..b4015bfe4 100644 --- a/src/layouts/scheduling/scheduling.vue +++ b/src/layouts/scheduling/scheduling.vue @@ -111,16 +111,46 @@ function appendDays(entry, newSlots) { } /** - * Returns a promise for inshop time slots for a single provider. - * @param {{ startDate: string, endDate: string, providerNumber: string, pageNameToLog: string }} params + * Assigns time slots from a v2 multi-provider response onto in-shop provider entries. + * @param {Array<{ provider: { providerNumber: string }, timeSlots: any }>} entries + * @param {{ providerTimeSlots?: Array<{ providerNumber: string, days: any[], provisionalTriggers?: string[] }> } | null | undefined} multiProviderResponse */ -function fetchInshopTimeSlots({ startDate, endDate, providerNumber, pageNameToLog }) { - return store.dispatch("getShopTimeSlots", { +function assignInshopTimeSlotsFromV2Response(entries, multiProviderResponse) { + const providerTimeSlots = multiProviderResponse?.providerTimeSlots ?? []; + entries.forEach((entry) => { + entry.timeSlots = + providerTimeSlots.find( + (pts) => pts.providerNumber === entry.provider.providerNumber + ) ?? null; + }); +} + +/** + * Appends days from a v2 multi-provider response onto existing in-shop provider entries. + * @param {Array<{ provider: { providerNumber: string }, timeSlots: any }>} entries + * @param {{ providerTimeSlots?: Array<{ providerNumber: string, days: any[] }> } | null | undefined} multiProviderResponse + */ +function appendInshopTimeSlotsFromV2Response(entries, multiProviderResponse) { + const providerTimeSlots = multiProviderResponse?.providerTimeSlots ?? []; + entries.forEach((entry) => { + const newSlots = providerTimeSlots.find( + (pts) => pts.providerNumber === entry.provider.providerNumber + ); + appendDays(entry, newSlots); + }); +} + +/** + * Returns a promise for inshop time slots for multiple providers. + * @param {{ startDate: string, endDate: string, providerNumbers: string[], pageNameToLog: string }} params + */ +function fetchInshopTimeSlots({ startDate, endDate, providerNumbers, pageNameToLog }) { + return store.dispatch("getShopTimeSlotsV2", { payload: { startDate, endDate, shopAppointmentType: "InshopOrDropoff", - providerNumber, + providerNumbers, }, pageNameToLog, }); @@ -131,8 +161,12 @@ function fetchInshopTimeSlots({ startDate, endDate, providerNumber, pageNameToLo * @param {{ startDate: string, endDate: string, zipCode: string, pageNameToLog: string }} params */ function fetchMobileTimeSlots({ startDate, endDate, zipCode, pageNameToLog }) { - return store.dispatch("getMobileTimeSlots", { - payload: { startDate, endDate, zipCode }, + return store.dispatch("getMobileTimeSlotsV2", { + payload: { + startDate, + endDate, + zipCode, + }, pageNameToLog, }); } @@ -176,16 +210,21 @@ export default { : null; const startDate = toDateString(0); const endDate = toDateString(SCHEDULE_FETCH_DAYS - 1); + const providerNumbers = providers.map((provider) => provider.providerNumber); const timeSlotsPromiseResultMap = [ - ...providers.map((provider, i) => ({ - resultKey: `inshopTimeSlots_${i}`, - promise: fetchInshopTimeSlots({ - startDate, - endDate, - providerNumber: provider.providerNumber, - pageNameToLog: to.name, - }), - })), + ...(providerNumbers.length + ? [ + { + resultKey: "inshopTimeSlots", + promise: fetchInshopTimeSlots({ + startDate, + endDate, + providerNumbers, + pageNameToLog: to.name, + }), + }, + ] + : []), // Get the mobile time slots if a mobile provider number is available ...(mobileProviderNumber ? [ @@ -202,9 +241,10 @@ export default { : []), ]; const timeSlotsResultMap = await settleAllPromises(timeSlotsPromiseResultMap); - vm.inShopProvidersAndTimeslots.forEach((entry, i) => { - entry.timeSlots = timeSlotsResultMap[`inshopTimeSlots_${i}`] ?? null; - }); + assignInshopTimeSlotsFromV2Response( + vm.inShopProvidersAndTimeslots, + timeSlotsResultMap.inshopTimeSlots + ); if (vm.mobileProviderAndTimeSlot) { vm.mobileProviderAndTimeSlot.timeSlots = timeSlotsResultMap.mobileTimeSlots ?? null; } @@ -332,17 +372,24 @@ export default { const newStartDate = toDateString(1, this.datePickerEndDate); const newEndDate = toDateString(SCHEDULE_FETCH_DAYS, this.datePickerEndDate); + const providerNumbers = this.inShopProvidersAndTimeslots.map( + ({ provider }) => provider.providerNumber + ); const promiseResultMap = [ - ...this.inShopProvidersAndTimeslots.map(({ provider }, i) => ({ - resultKey: `inshopTimeSlots_${i}`, - promise: fetchInshopTimeSlots({ - startDate: newStartDate, - endDate: newEndDate, - providerNumber: provider.providerNumber, - pageNameToLog: this.pageName, - }), - })), + ...(providerNumbers.length + ? [ + { + resultKey: "inshopTimeSlots", + promise: fetchInshopTimeSlots({ + startDate: newStartDate, + endDate: newEndDate, + providerNumbers, + pageNameToLog: this.pageName, + }), + }, + ] + : []), ...(this.mobileProviderAndTimeSlot ? [ { @@ -360,10 +407,10 @@ export default { const resultMap = await settleAllPromises(promiseResultMap); - // Provider order is fixed after beforeRouteEnter, so index keys are stable. - this.inShopProvidersAndTimeslots.forEach((entry, index) => { - appendDays(entry, resultMap[`inshopTimeSlots_${index}`]); - }); + appendInshopTimeSlotsFromV2Response( + this.inShopProvidersAndTimeslots, + resultMap.inshopTimeSlots + ); if (this.mobileProviderAndTimeSlot) { appendDays(this.mobileProviderAndTimeSlot, resultMap.mobileTimeSlots); diff --git a/src/store/index.js b/src/store/index.js index 76e98dd4f..2fff59ee3 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2543,6 +2543,149 @@ export const actions = { return globalMethods.callHttpClient(options); }, + getShopTimeSlotsV2( + context, + { payload: { startDate, endDate, shopAppointmentType, providerNumbers }, pageNameToLog } + ) { + const order = context.state.order; + const vehicle = context.state.order.vehicle; + const payment = context.state.order.payment; + + const lineItems = getFlattenedLineItemsWithGlassPartTag(order.lineItems); + + const glassPieces = order.damage.glassToReplace + ? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace) + : []; + var payload = { + providerNumbers: providerNumbers, + startDate: startDate, + endDate: endDate, + shopAppointmentType: shopAppointmentType, + applicationName: applicationConfig.APPLICATION_NAME, + parentAccountNumber: payment.parentAccountNumber, + carId: vehicle.carId, + lineItems: lineItems, + glassPieces: glassPieces, + eon: order.eon, + billToAccountNumber: context.getters.payment.billToAccountNumber, + coverage: { + status: payment.insuranceCoverage.coverageStatus, + deductible: order.policy.currentDeductible, + additionalAuthFlag: order.policy.additionalAuthFlag, + }, + partSelection: { + hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length, + hasAnsweredMoldingQuestions: !!order.damage.moldingQuestionAnswers?.length, + hasAnsweredCapabilityQuestions: !!order.damage.capabilityQuestionAnswers?.length, + hasManuallySelectedParts: + !!context.state.applicationUser.pageData["vehicle-parts"]?.partsOrQuestions + .length, + }, + vehicle: { + year: vehicle.year, + make: vehicle.make, + model: vehicle.model, + style: vehicle.style, + vin: vehicle.vin ?? "", + }, + }; + + let hasCalled = timeSlotCallFlags.shopV2; + if (!hasCalled) { + timeSlotCallFlags.shopV2 = true; + } + + const options = { + method: endpoints.GetShopTimeSlotsV2.method, + endpoint: endpoints.GetShopTimeSlotsV2.url, + payload: payload, + logApiCall: true, + pageNameToLog: pageNameToLog, + }; + + // Only set handler if this is the very first call in this session + if (!hasCalled) { + options.additionalSuccessEventDataHandler = (response) => { + const firstProviderTimeSlots = response.data.providerTimeSlots?.[0]; + return getTimeSlotsAdditionalEventData( + firstProviderTimeSlots?.provisionalTriggers, + order.serviceLocation.zipCode, + firstProviderTimeSlots?.days?.[0]?.date, + shopAppointmentType + ); + }; + } + + return globalMethods.callHttpClient(options); + }, + + getMobileTimeSlotsV2(context, { payload: { startDate, endDate, zipCode }, pageNameToLog }) { + const order = context.state.order; + const vehicle = context.state.order.vehicle; + const payment = context.state.order.payment; + const lineItems = getFlattenedLineItemsWithGlassPartTag(order.lineItems); + const glassPieces = order.damage.glassToReplace + ? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace) + : []; + var payload = { + startDate: startDate, + endDate: endDate, + applicationName: applicationConfig.APPLICATION_NAME, + parentAccountNumber: payment.parentAccountNumber, + carId: vehicle.carId, + lineItems: lineItems, + glassPieces: glassPieces, + eon: order.eon, + billToAccountNumber: context.getters.payment.billToAccountNumber, + coverage: { + status: payment.insuranceCoverage.coverageStatus, + deductible: order.policy.currentDeductible, + additionalAuthFlag: order.policy.additionalAuthFlag, + }, + partSelection: { + hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length, + hasAnsweredMoldingQuestions: !!order.damage.moldingQuestionAnswers?.length, + hasAnsweredCapabilityQuestions: !!order.damage.capabilityQuestionAnswers?.length, + hasManuallySelectedParts: + !!context.state.applicationUser.pageData["vehicle-parts"]?.partsOrQuestions + .length, + }, + vehicle: { + year: vehicle.year, + make: vehicle.make, + model: vehicle.model, + style: vehicle.style, + vin: vehicle.vin ?? "", + }, + zipCode: zipCode ?? order.serviceLocation.zipCode, + }; + + let hasCalled = timeSlotCallFlags.mobileV2; + if (!hasCalled) { + timeSlotCallFlags.mobileV2 = true; + } + + const options = { + method: endpoints.GetMobileTimeSlotsV2.method, + endpoint: endpoints.GetMobileTimeSlotsV2.url, + payload: payload, + logApiCall: true, + pageNameToLog: pageNameToLog, + }; + + // Only set handler if this is the very first call in this session + if (!hasCalled) { + options.additionalSuccessEventDataHandler = (response) => + getTimeSlotsAdditionalEventData( + response.data.provisionalTriggers, + order.serviceLocation.zipCode, + response.data.days?.[0]?.date + ); + } + + return globalMethods.callHttpClient(options); + }, + getMobilePremiumFee(context, { pageNameToLog }) { const damageType = context.getters.damage.isRepair ? "Repair" : "Replace"; const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash"; @@ -4530,6 +4673,8 @@ function saveExternalParameterState(externalParameterState) { const timeSlotCallFlags = { shop: false, mobile: false, + shopV2: false, + mobileV2: false, }; function checkIfMiscParts(partsOrQuestions) {