diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 3e779e027..5b0b66509 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -53,6 +53,7 @@ const storeMutations = { UPDATE_IS_PIA: "updateIsPia", UPDATE_PIA_TYPE: "updatePiaType", WORK_ORDER_NUMBER: "updateWorkOrderNumber", + WORK_ORDER_ID: "updateWorkOrderId", Customer_Portal_Login_Token: "updateCustomerPortalLoginToken", LOCK_TOKEN: "updateLockToken", UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount", diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js index e0873d822..38f4161b9 100644 --- a/src/helpers/heritage-integration/order-helper.js +++ b/src/helpers/heritage-integration/order-helper.js @@ -162,6 +162,7 @@ async function saveSessionHelper( crmCustomerId: savedSessionInfo.data.crmCustomerId?.toString(), eon: savedSessionInfo.data.eon, workOrderNumber: savedSessionInfo.data.workOrderNumber, + workOrderId: savedSessionInfo.data.workOrderId, customerPortalLoginToken: savedSessionInfo.data.customerPortalLoginToken, lockToken: savedSessionInfo.data.lockToken, settledTenderAmount: savedSessionInfo.data.settledTenderAmount, diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index 2724dbcb6..92a86e609 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -75,6 +75,7 @@ import cart from "@/fmg-components/cart/cart"; //Supporting files import baseMixin from "@/mixins/base-mixin.js"; +import analyticsMixin from "@/mixins/analytics-mixin"; import store from "@/store"; import { storeActions } from "@/constants/store-actions"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; @@ -134,6 +135,7 @@ export default { vm.setCmsContent(resultMap.cmsContent); vm.lineItems = lineItemsFromSubmittedOrder; vm.vaps = availableVaps; + analyticsMixin.methods.pushSubmittedOrderToDataLayer(); }); }, data() { diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 3f20adb59..e0d865c35 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -21,6 +21,7 @@ import { GaEvents, ValueToLogTypes, } from "@/constants/analytics"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import store from "@/store"; import baseMixin from "@/mixins/base-mixin"; @@ -119,6 +120,82 @@ export default { await this.logPageView(analyticsPageEvents.ENTRY); }, + pushSubmittedOrderToDataLayer() { + // check if submitted order exists; exit if not. + const hasSubmittedOrder = store.getters.hasSubmittedOrder; + + if (!hasSubmittedOrder) { + return; + } + + const order = store.getters.submittedOrder; + + // assemble data for payload + // // reduce promocode array + const promos = order.lineItems.promos ?? []; + const promoCodes = promos.map((promo) => promo.promoCode); + const promoString = + promoCodes.length === 0 ? "" : promoCodes.reduce((prev, next) => `${prev},${next}`); + + // // reduce glass array + const glassToReplace = order.damage.glassToReplace ?? []; + const glassToReplaceNames = glassToReplace.map( + (glassPiece) => `${glassPiece.glassLocation}/${glassPiece.glassName}` + ); + const glassString = + glassToReplaceNames.length === 0 + ? "" + : glassToReplaceNames.reduce((prev, next) => `${prev},${next}`); + + // // calculate subtotal + const lineItems = order.lineItems; + const combinedLineItems = [ + ...(lineItems.glassParts ?? []), + ...(lineItems.supportingItems ?? []), + ...(lineItems.vaps ?? []), + ...(lineItems.promos ?? []), + ]; + const subtotal = baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts( + combinedLineItems, + false + ); + + // // get correct zip code + const providerZip = order.serviceLocation.provider.address.zipCode; + const serviceZip = + order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE + ? order.serviceLocation.zipCode + : providerZip; + + // // calculate total + const total = baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts( + combinedLineItems, + true + ); + + const payload = { + serviceZipCode: serviceZip, + damageType: order.damage.isRepair ? "repair" : "replace", + accountType: order.payment.isInsurance ? "insurance" : "cash", + promoCodes: promoString, + vehicleYear: order.vehicle.year, + vehicleMake: order.vehicle.make, + vehicleModel: order.vehicle.model, + vehicleStyle: order.vehicle.style, + glassToReplace: glassString, + workOrderId: order.workOrderId, + providerCtu: order.serviceLocation.zipCodeCtu, + orderNumber: order.workOrderNumber, + priceTotal: total, + priceSubTotal: subtotal, + isRecalibrationOnOrder: store.getters.isRecalibrationOnSubmittedOrder, + appointmentType: order.serviceLocation.appointmentType, + }; + + // push to data layer. + pushToDataLayerIfDefined(payload); + }, + pushExperimentsToDataLayer() { const experiments = store.getters.applicationUser.experiments; experiments?.forEach((exp) => { diff --git a/src/mixins/analytics-mixin.spec.js b/src/mixins/analytics-mixin.spec.js index 567d9154e..ff6c30e7e 100644 --- a/src/mixins/analytics-mixin.spec.js +++ b/src/mixins/analytics-mixin.spec.js @@ -22,6 +22,46 @@ import { getUserIdValue, } from "@/helpers/heritage-integration/cookie-helper"; +const parts = { + windshield: { + name: "windshield", + canSafeliteRecalibrate: true, + childParts: [], + color: "Green Tint", + description: + "solar, soundproofing, lane keep assist, lane departure warning system, w/adaptive cruise control", + id: "db22fd44-10dd-456f-979b-ff88cf68cca6", + partNumber: "FW04896GTYN", + partType: "WINDSHIELD", + recalibrationType: "STATIC", + requiresCapabilityQuestions: false, + requiresRecalibration: true, + salesTax: 63.86, + sellingPrice: 791.46, + }, + frontWipers: { + name: "front wipers", + partNumber: "SBB16", + description: "SAFELITE BEAM BLADE 16", + partType: "FRONT WIPER", + price: 32.64, + }, + rearWipers: { + name: "rear wipers", + partNumber: "SBBR12A", + description: "SAFELITE REAR BLADE 12A", + partType: "REAR WIPER", + price: 24.48, + }, + rainDefense: { + name: "rain defense", + partNumber: "RAIN DEFENSE", + description: null, + partType: "RAIN DEFENSE", + price: 35.5, + }, +}; + describe("analyticsMixin.js", () => { beforeEach(() => { removeAllTestCookies(); @@ -284,6 +324,130 @@ describe("analyticsMixin.js", () => { ]); }); + describe("pushSubmittedOrderToDataLayer", () => { + beforeEach(() => { + store.getters.hasSubmittedOrder = true; + store.getters.submittedOrder = { + vehicle: { + year: "2020", + make: "acura", + model: "mdx", + style: "4-door sedan", + carId: "dummyCarId", + category: "dummyCategory", + vin: "dummyVin", + }, + serviceLocation: { + address: "add1", + address2: "add2", + city: "city", + state: "state", + zipCode: "zip", + zipCodeCtu: "zipCtu", + appointmentType: "IN_SHOP", + isVehicleProtected: true, + provider: { + providerNumber: 2, + address: { + streetAddress: "add3", + city: "city2", + state: "state2", + zipCode: "zip2", + zipCodeCtu: "zipCtu2", + }, + }, + techNotes: "", + }, + customer: { + firstName: "first", + lastName: "last", + emailAddress: "builddigitaltest@safelite.com", + phoneNumber: "555-555-5555", + isSmsOptIn: false, + }, + damage: { + isRepair: false, + numberOfChips: null, + glassToReplace: [{ glassName: "single", location: "windshield" }], + }, + lineItems: { + glassParts: [parts.windshield], + supportingItems: [], + vaps: [parts.frontWipers], + promos: [{ promoCode: "promoTEST" }, { promoCode: "promoTEST2" }], + }, + payment: { + isInsurance: false, + insuranceCoverage: { + isVerified: null, + coverageStatus: null, + coverageVerificationType: null, + }, + isPia: true, + piaType: "Afterpay", + inactivePromos: [], + }, + schedule: { + date: "date", + startTime: "start", + endTime: "end", + jobMinMinutes: "30", + jobMaxMinutes: "45", + }, + workOrderNumber: "01820-111111", + workOrderId: "222222222222", + }; + }); + + test("Pushes to data layer if nominal", () => { + // Arrange + const mockDataLayerFn = jest.fn(); + + window.dataLayer = { + push: mockDataLayerFn, + }; + + // Act + analyticsMixin.methods.pushSubmittedOrderToDataLayer(); + + // Assert + expect(mockDataLayerFn).toHaveBeenCalled(); + }); + + test("Does not push to data layer if no submitted order available.", () => { + // Arrange + store.getters.hasSubmittedOrder = false; + store.getters.submittedOrder = undefined; + + const mockDataLayerFn = jest.fn(); + + window.dataLayer = { + push: mockDataLayerFn, + }; + + // Act + analyticsMixin.methods.pushSubmittedOrderToDataLayer(); + + // Assert + expect(mockDataLayerFn).not.toHaveBeenCalled(); + }); + + test("Glass and Promo strings correctly formatted", () => { + // Arrange + window.dataLayer = []; + + // Act + analyticsMixin.methods.pushSubmittedOrderToDataLayer(); + + // Assert + const glassString = window.dataLayer[0].glassToReplace; + const promoString = window.dataLayer[0].promoCodes; + + expect(glassString).toMatch(/(\w+\/\w+)?(,\w+\/\w+)*/); + expect(promoString).toMatch(/(\w+)?(,\w+)*/); + }); + }); + test("Obj is not null after action prepended", () => { //Arrange const obj = { baseMethodName: "testMethodName", data: "testData" }; diff --git a/src/store/index.js b/src/store/index.js index 12140f66b..c78a7968d 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -132,6 +132,7 @@ const getDefaultState = () => { referralCorrelationId: null, eon: null, workOrderNumber: null, + workOrderId: null, customerPortalLoginToken: null, lockToken: null, settledTenderAmount: 0, @@ -254,6 +255,9 @@ export const mutations = { updateWorkOrderNumber(state, workOrderNumber) { state.order.workOrderNumber = workOrderNumber; }, + updateWorkOrderId(state, workOrderId) { + state.order.workOrderId = workOrderId; + }, updateCustomerPortalLoginToken(state, customerPortalLoginToken) { state.order.customerPortalLoginToken = customerPortalLoginToken; }, @@ -1027,6 +1031,7 @@ export const actions = { savedSessionId, crmCustomerId, workOrderNumber, + workOrderId, customerPortalLoginToken, lockToken, settledTenderAmount, @@ -1041,6 +1046,7 @@ export const actions = { context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId); context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId); context.commit(storeMutations.WORK_ORDER_NUMBER, workOrderNumber); + context.commit(storeMutations.WORK_ORDER_ID, workOrderId); context.commit(storeMutations.LOCK_TOKEN, lockToken); context.commit(storeMutations.Customer_Portal_Login_Token, customerPortalLoginToken); context.commit(storeMutations.UPDATE_SETTLED_TENDER_AMOUNT, settledTenderAmount);