diff --git a/package-lock.json b/package-lock.json index 583491f9..08b305fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16290,9 +16290,9 @@ } }, "node_modules/rollup": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", - "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "dev": true, "bin": { "rollup": "dist/bin/rollup" diff --git a/src/constants/coverage-statuses.js b/src/constants/coverage-statuses.js index bf9b299e..df6d550b 100644 --- a/src/constants/coverage-statuses.js +++ b/src/constants/coverage-statuses.js @@ -12,7 +12,16 @@ const coverageStatuses = Object.freeze({ /** * We have attempted claim registration, and we got a success response */ - VERIFIED: 2 + VERIFIED: 2, + + mapToApi: (status) => { + switch (status) { + case coverageStatuses.PENDING: return 'Pending'; + case coverageStatuses.NO_COVERAGE: return 'NoCoverage'; + case coverageStatuses.VERIFIED: return 'Verified'; + default: return null; + } + } }); export default coverageStatuses; diff --git a/src/constants/coverage-type.js b/src/constants/coverage-type.js index ec136d76..09c5d379 100644 --- a/src/constants/coverage-type.js +++ b/src/constants/coverage-type.js @@ -22,7 +22,17 @@ const coverageType = Object.freeze({ * The vehicle policy does not have comprehensive coverage * No Comp quotes are enabled by the client */ - NO_COMP: 3 + NO_COMP: 3, + + mapToApi: (status) => { + switch (status) { + case coverageType.NONE: return 'None'; + case coverageType.Deductible: return 'Deductible'; + case coverageType.ITAC: return 'ITAC'; + case coverageType.NO_COMP: return 'NoComp'; + default: return null; + } + } }); export default coverageType; diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index f6ac757a..ab149820 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -72,10 +72,6 @@ const endpoints = Object.freeze({ url: `${PARTS_BASE_URL}/parts`, method: 'POST' }, - GetInsurancePriceOrderItems: { - url: `${PRICE_BASE_URL}/order-items-with-insurance-pricing`, - method: 'GET' - }, GetITACPriceOrderItems: { url: `${PRICE_BASE_URL}/order-items-with-itac-pricing`, method: 'POST' @@ -104,6 +100,23 @@ const endpoints = Object.freeze({ url: `${PARTS_BASE_URL}/rain-defense`, method: 'GET' }, + GetRecalParts: { + url: ( + carId, + partNumber, + recalibrationType, + parentAccountNumber, + zipCode, + applicationName, + referralSequenceNumber + // eslint-disable-next-line max-len + ) => `${PARTS_BASE_URL}/recal-parts/${carId}/${partNumber}/${recalibrationType}/${parentAccountNumber}/${zipCode}/${applicationName}/${referralSequenceNumber}`, + method: 'GET' + }, + GetGlassFees: { + url: `${PARTS_BASE_URL}/glass-fees`, + method: 'GET' + }, GetSupportingItems: { url: `${PARTS_BASE_URL}/supporting-items`, method: 'POST' diff --git a/src/helpers/querystring-helper.js b/src/helpers/querystring-helper.js index 0327071d..b827441a 100644 --- a/src/helpers/querystring-helper.js +++ b/src/helpers/querystring-helper.js @@ -29,6 +29,12 @@ export function getLineItemQueryString(lineItems, parameterName) { return queryString.length !== 0 ? `&${queryString}` : ''; } +export function getPartNumbersListForQueryString(lineItems, parameterName) { + const queryString = getLineItemsFlattened(lineItems).map((lineItem) => + `${parameterName}=${lineItem.partNumber}`).join('&'); + return queryString.length !== 0 ? `&${queryString}` : ''; +} + export function getTaxLineItemQueryString(lineItems, parameterName) { const queryString = getLineItemsFlattened(lineItems).map((lineItem, index) => `${parameterName}[${index}].partNumber=${lineItem.partNumber}` diff --git a/src/layouts/coverage-statement/coverage-statement.vue b/src/layouts/coverage-statement/coverage-statement.vue index 136198a2..42c5bb20 100644 --- a/src/layouts/coverage-statement/coverage-statement.vue +++ b/src/layouts/coverage-statement/coverage-statement.vue @@ -158,7 +158,7 @@ export default { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to?.query?.issPage); - const supportingItemsPromise = store.getSupportingItems(); + const getRecalPartsPromise = store.getRecalParts(); // Settle promises and get results const promiseResultMap = [ @@ -167,8 +167,8 @@ export default { promise: cmsContentPromise }, { - resultKey: 'supportingItems', - promise: supportingItemsPromise + resultKey: 'recalParts', + promise: getRecalPartsPromise } ]; @@ -192,7 +192,6 @@ export default { next(async (vm) => { showIssLoadingModal(true); vm.setCmsContent(resultMap.cmsContent); - vm.setSupportingItems(resultMap.supportingItems); await vm.initializeComponent(); if (!vm.unverified) { useMainStore().disableKeyFields(); @@ -396,10 +395,9 @@ export default { showIssLoadingModal(false); }, async getPricedParts() { - const { glassParts, supportingItems } = this.mainStore.order.lineItems; + const { glassParts } = this.mainStore.order.lineItems; const availableLineItems = [ - ...(glassParts ?? []), - ...(supportingItems ?? []) + ...(glassParts ?? []) ]; // We only call the ITAC pricing endpoint if we are not repair or we are NoComp @@ -475,9 +473,6 @@ export default { return null; } }, - setSupportingItems(newSupportingItems) { - this.mainStore.updateSupportingItems(newSupportingItems); - }, setBaseServiceLineItems(lineItems) { this.baseServiceLineItems = lineItems; }, diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index cdbaa9c4..09772447 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -177,17 +177,12 @@ export default { async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); - - const serviceZipCode = - useMainStore().order.serviceLocation.zipCode - || useMainStore().order.customer.address.zipCode; + const serviceZipCode = useMainStore().order.serviceLocation.zipCode || useMainStore().order.customer.address.zipCode; const zipCodeData = getZipCodeData(serviceZipCode); - const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode); - const serviceabilityDetailsPromise = - getServiceabilityDetails(serviceZipCode); - const shopQuestionInitialDataPromise = - shopQuestion.methods.loadInitialData(serviceZipCode); + const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode); + const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode); + const getGlassFeesPromise = useMainStore().getGlassFees(); // Settle promises and get results const promiseResultMap = [ @@ -199,6 +194,10 @@ export default { resultKey: 'mobileFeePart', promise: mobileFeePartPromise }, + { + resultKey: 'glassFees', + promise: getGlassFeesPromise + }, { resultKey: 'serviceabilityDetails', promise: serviceabilityDetailsPromise @@ -215,13 +214,14 @@ export default { const resultMap = await settleAllPromises(promiseResultMap); useMainStore().updateIsSafeliteProvider(true); - next((vm) => { + next(async (vm) => { vm.setCmsContent(resultMap.cmsContent); - vm.setData( + await vm.setData( resultMap.zipCodeData, resultMap.serviceabilityDetails, resultMap.mobileFeePart, - resultMap.shopQuestionInitialData?.mobileProviderNumber + resultMap.shopQuestionInitialData?.mobileProviderNumber, + resultMap.glassFees ); vm.$refs[SHOP_QUESTION_REF_NAME].initializeComponent(resultMap.shopQuestionInitialData); }); @@ -308,10 +308,8 @@ export default { if (newValue.zipCode !== this.zipCode) { if ( !( - this.selectedAppointmentType - === AppointmentTypeStrings.MOBILE - || this.selectedAppointmentType - === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP + this.selectedAppointmentType === AppointmentTypeStrings.MOBILE + || this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP ) ) { this.selectedAppointmentType = null; @@ -393,8 +391,7 @@ export default { methods: { arePagePrerequisitesValid() { return ( - useMainStore().lineItems.supportingItems !== null - && useMainStore().order.serviceLocation.zipCode !== null + useMainStore().order.serviceLocation.zipCode !== null ); }, async reloadShopData(zipCode) { @@ -471,11 +468,12 @@ export default { getSelectedProvider() { return useMainStore().order.serviceLocation.provider; }, - setData( + async setData( zipCodeData, serviceabilityDetails, mobileFeePart, - mobileProviderNumber + mobileProviderNumber, + glassFees ) { if (zipCodeData) { this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase; @@ -493,12 +491,20 @@ export default { if (mobileProviderNumber) { this.setMobileProviderNumber(mobileProviderNumber); } + + if (glassFees) { + const pricedGlassFees = await useMainStore().getCombinedQuote(glassFees); + this.setGlassFeeItems(pricedGlassFees); + } }, setContainsMilitaryBase(val) { if (this.zipContainsMilitaryBase !== val) { this.zipContainsMilitaryBase = val; } }, + setGlassFeeItems(newGlassFeeItems) { + this.mainStore.updateGlassFees(newGlassFeeItems); + }, setMobileFeePart(mobileFeePart) { this.mobileFeePart = mobileFeePart; }, diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index de8250ed..1561a3bd 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -414,8 +414,8 @@ export default { ); if (this.isWindshieldRepair) { - const supportingItems = - await useMainStore().getSupportingItems(); + const results = await Promise.allSettled([useMainStore().getSupportingItems(), useMainStore().getRecalParts()]); + const supportingItems = results[0]; useMainStore().updateSupportingItems(supportingItems.data); } diff --git a/src/store/index.js b/src/store/index.js index d6eafcb8..c4c3b903 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -25,7 +25,12 @@ import bailoutMessage from '@/constants/bailoutMessage'; import bailoutCode from '@/constants/bailoutCode'; import partNumberStrings from '@/constants/part-number-strings'; import { findLineItemIndex, getLineItemsFlattened } from '@/helpers/line-items-helper'; -import { buildQueryStringParameterFromArrayOfComplexObjects, getLineItemQueryString, getTaxLineItemQueryString } from '@/helpers/querystring-helper'; +import { + buildQueryStringParameterFromArrayOfComplexObjects, + getLineItemQueryString, + getPartNumbersListForQueryString, + getTaxLineItemQueryString +} from '@/helpers/querystring-helper'; import coverageType from '@/constants/coverage-type'; import { getExperimentSettingValue, getFeatureTogglesQueryString } from '@/helpers/experiment-helper'; import { getSessionKeyValue, getUserIdValue, deleteISSCookie } from '@/helpers/cookie-helper'; @@ -159,6 +164,7 @@ export const getDefaultState = () => ({ insuranceCoverage: { coverageStatus: coverageStatuses.PENDING, coverageType: coverageType.NONE, + isItacOptimized: false, claimNumber: null }, payment: { @@ -584,6 +590,9 @@ export const useMainStore = defineStore({ updateCoverageType(type) { this.order.insuranceCoverage.coverageType = type; }, + updateIsItacOptimized(isItacOptimized) { + this.order.insuranceCoverage.isItacOptimized = isItacOptimized || false; + }, registerClaim() { const nonNumberCharRegex = /[^0-9]/g; const { order, isITAC } = this; @@ -1020,6 +1029,82 @@ export const useMainStore = defineStore({ }).catch((error) => reject(error)); }); }, + async getRecalParts() { + const { glassParts } = this.lineItems; + const { carId } = this.vehicle; + const { parentAccountNumber, referralSequenceNumber } = this.order; + const { zipCode } = this.order.serviceLocation; + + if (glassParts && glassParts.length > 0) { + const recalPromises = []; + glassParts.forEach((glassPart) => { + if (glassPart.requiresRecalibration) { + const partNumberChecked = glassPart.partNumber; + recalPromises.push(globalMethods + .callHttpClient({ + method: endpoints.GetRecalParts.method, + endpoint: endpoints.GetRecalParts.url( + carId, + partNumberChecked, + glassPart.recalibrationType, + parentAccountNumber, + zipCode, + applicationConfig.APPLICATION_NAME, + referralSequenceNumber + ) + }) + .then((response) => { + const recalResponse = response.data; + if (recalResponse && recalResponse.recalibrationParts && recalResponse.recalibrationParts.length > 0) { + this.addGlassPartRecalibrationInfo(partNumberChecked, recalResponse.recalibrationParts[0]); + } + })); + } + }); + await Promise.allSettled(recalPromises); + } + }, + addGlassPartRecalibrationInfo(partNumber, recalPartInformation) { + const glassPart = this.lineItems.glassParts.find((gp) => gp.partNumber === partNumber); + if (glassPart) { + const recalChildPart = glassPart.childParts?.find((cp) => cp.partNumber === recalPartInformation.partNumber); + if (!recalChildPart) { + if (!Array.isArray(glassPart.childParts) || !glassPart.childParts.length) { + glassPart.childParts = []; + } + glassPart.childParts.push(recalPartInformation); + } + } + }, + async getGlassFees() { + const { insuranceCoverage, vehicle } = this.order; + const { glassParts } = this.lineItems; + const { isRepair } = this.damage; + const { defaultProviderNumber, provider, zipCode } = this.order.serviceLocation; + const damageType = isRepair ? 'Repair' : 'Install'; + const facilityType = this.isMobileAppointment ? 'Mobile' : 'InShop'; + const providerToUse = provider?.providerNumber ?? defaultProviderNumber; + const partNumberListQueryString = getPartNumbersListForQueryString(glassParts, 'PartNumbers'); + + const params = new URLSearchParams({ + serviceType: damageType, + facilityType, + parentAccountNumber: this.order.parentAccountNumber, + billToAccountNumber: this.billToAccountNumber, + isPremiumAppointment: false, + isItacOptimized: insuranceCoverage.isItacOptimized, + zipCode, + coverageStatus: coverageStatuses.mapToApi(insuranceCoverage.coverageStatus), + coverageType: coverageType.mapToApi(insuranceCoverage.coverageType), + providerNumber: providerToUse, + carId: vehicle.carId + }); + + return globalMethods.callHttpClient({ + method: endpoints.GetGlassFees.method, + endpoint: `${endpoints.GetGlassFees.url}?${params.toString()}${partNumberListQueryString}` + }); + }, async getSupportingItems() { const { glassParts } = this.lineItems; const { carId } = this.vehicle; @@ -1039,46 +1124,6 @@ export const useMainStore = defineStore({ }); }, - async getInsurancePriceOrderItems(availableLineItems) { - const { serviceLocation, currentDeductible } = this.order; - const lineItemsQueryString = getLineItemQueryString(availableLineItems, 'lineItems'); - const featureTogglesQueryString = getFeatureTogglesQueryString(this.experimentSettings); - - let queryString = - `ParentAccountNumber=${this.order.parentAccountNumber}` - + `&BillToAccountNumber=${this.billToAccountNumber}` - + `&CTU=${serviceLocation.zipCodeCtu}` - + `&Deductible=${currentDeductible ?? 0}` - + `&ZipCode=${serviceLocation.zipCode}` - + `&${featureTogglesQueryString}` - + `${lineItemsQueryString}`; - - const lineItemServerData = this.order.lineItems?.serverData; - if (lineItemServerData) { - queryString += `&ServerData=${encodeURIComponent(lineItemServerData)}`; - } - - const response = await globalMethods - .callHttpClient({ - method: endpoints.GetInsurancePriceOrderItems.method, - endpoint: `${endpoints.GetInsurancePriceOrderItems.url}?${queryString}` - }).catch((error) => { - console.error(error); - throw error; - }); - - const { lineItems, serverData } = response.data; - if (serverData) { - this.order.lineItems.serverData = serverData; - } - - if (lineItems) { - const retAvailableLineItems = addPricesToLineItems(availableLineItems, lineItems); - return retAvailableLineItems; - } - return availableLineItems; - }, - async getITACPriceOrderItems(availableLineItems) { const { policy, vehicle, contactInfo, serviceLocation, insuranceCoverage } = this.order; const response = await globalMethods @@ -1128,13 +1173,14 @@ export const useMainStore = defineStore({ throw error; }); - const { lineItems, serverData, isItac, primaryBillToNumber, partsWerePriced } = response.data; + const { lineItems, serverData, isItac, primaryBillToNumber, partsWerePriced, isItacOptimized } = response.data; if (serverData) { this.order.lineItems.serverData = serverData; } if (partsWerePriced) { + this.updateIsItacOptimized(isItacOptimized); if (isItac) { // if the ITAC Pricing API call returns isItac = true then we should switch to ITAC coverage type this.updateCoverageType(coverageType.ITAC); @@ -1205,11 +1251,23 @@ export const useMainStore = defineStore({ }, getMobileFeePart() { - const damageType = this.damage.isRepair ? 'Repair' : 'Replace'; + const { vehicle, insuranceCoverage } = this.order; + + const params = new URLSearchParams({ + serviceType: this.damage.isRepair ? 'Repair' : 'Install', + facilityType: 'Mobile', + parentAccountNumber: this.order.parentAccountNumber, + billToAccountNumber: this.billToAccountNumber, + isItacOptimized: insuranceCoverage.isItacOptimized, + providerNumber: this.providerNumber, + carId: vehicle.carId, + coverageStatus: coverageStatuses.mapToApi(insuranceCoverage.coverageStatus), + coverageType: coverageType.mapToApi(insuranceCoverage.coverageType) + }); return globalMethods.callHttpClient({ method: endpoints.GetMobileFeePart.method, - endpoint: `${endpoints.GetMobileFeePart.url}/${damageType}/${this.order.parentAccountNumber}/${this.billToAccountNumber}` + endpoint: `${endpoints.GetMobileFeePart.url}?${params.toString()}` }); }, @@ -1762,9 +1820,25 @@ export const useMainStore = defineStore({ resetInsurance() { this.order.insuranceCoverage.coverageStatus = coverageStatuses.PENDING; this.order.insuranceCoverage.coverageType = coverageType.NONE; + this.order.insuranceCoverage.isItacOptimized = false; this.order.insuranceCoverage.claimNumber = null; }, + updateGlassFees(feeData) { + if (feeData == null) { + this.order.lineItems.feeItems = null; + return; + } + feeData.forEach((fee) => { + if (fee.partNumber === partTypeStrings.MOBILE_FEE) { + this.updateMobileFee(fee); + } else if (fee.partNumber === partTypeStrings.RECYCLE_FEE) { + this.updateRecycleFee(fee); + } else { + this.addPartNumberFeeItem(fee, fee.partNumber); + } + }); + }, updateSupportingItems(partsData) { if (partsData == null) { this.order.lineItems.supportingItems = null; diff --git a/src/store/store.spec.js b/src/store/store.spec.js index e461b626..c09a877c 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -2085,4 +2085,21 @@ describe('Store', () => { expect(store.order.payment.paypalToken).toEqual(paypalToken); }); }); + + describe.each([ + [true, true], + [false, false], + [false, null] + ])('updateIsItacOptimized', (expected, isItacOptimized) => { + test(`sets isItacOptimized to ${expected} when passed ${isItacOptimized}`, () => { + // Arrange + store.updateIsItacOptimized(isItacOptimized); + + // Act + const result = store.order.insuranceCoverage.isItacOptimized; + + // Assert + expect(result).toBe(expected); + }); + }); });