From 878bd8c318da9f48adb472f85587d6e98fb6ed60 Mon Sep 17 00:00:00 2001 From: Minojhini Valaiyapathi Date: Tue, 4 Aug 2026 10:23:10 -0400 Subject: [PATCH] CASH-3107 - Early bord fee --- src/layouts/scheduling/scheduling.spec.js | 68 +++++++++++++++++++++-- src/layouts/scheduling/scheduling.vue | 32 +++++++++-- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/layouts/scheduling/scheduling.spec.js b/src/layouts/scheduling/scheduling.spec.js index b76f2112d..42bdd7db1 100644 --- a/src/layouts/scheduling/scheduling.spec.js +++ b/src/layouts/scheduling/scheduling.spec.js @@ -78,15 +78,16 @@ jest.mock( }) ); -function setupMocks({ isAppleBrowser = false, cmsContent = {} } = {}) { +function setupMocks({ isAppleBrowser = false, cmsContent = {}, totalLineItemPrice = 0 } = {}) { const dispatchStoreAction = jest.fn().mockResolvedValue(null); + const getTotalLineItemPrice = jest.fn(() => totalLineItemPrice); const baseMixin = { methods: { getCmsContent: jest.fn((widgetName, fieldName) => { return cmsContent[widgetName]?.[fieldName] ?? ""; }), setCmsContent: jest.fn(), - getTotalLineItemPrice: jest.fn(() => 0), + getTotalLineItemPrice, isAppleBrowser: jest.fn(() => isAppleBrowser), dispatchStoreAction, }, @@ -126,7 +127,7 @@ function setupMocks({ isAppleBrowser = false, cmsContent = {} } = {}) { }); mountOptions.global.mixins = [baseMixin]; const wrapper = shallowMount(scheduling, mountOptions); - return { wrapper, dispatchStoreAction }; + return { wrapper, dispatchStoreAction, getTotalLineItemPrice }; } describe("scheduling.vue", () => { @@ -818,21 +819,80 @@ describe("scheduling.vue", () => { }); describe("loadFeeParts", () => { - test("loads priced mobile and recycle fee parts for the zip", async () => { + test("loads priced mobile, recycle, and early bird fee parts for the zip", async () => { + const pricedPremiumFee = { + partNumber: "EARLYBIRD", + partType: "EARLY BIRD", + laborAmount: 29.99, + sellingPrice: 0, + kitPrice: 0, + }; getPricedMobileFeePart.mockResolvedValueOnce({ partType: "MOBILE FEE" }); getPricedRecycleFeePart.mockResolvedValueOnce({ partNumber: partNumberStrings.RECYCLE_FEE, }); + store.dispatch.mockImplementation((action) => { + if (action === "getMobilePremiumFee") { + return Promise.resolve({ + data: { + partNumber: "EARLYBIRD", + partType: "EARLY BIRD", + laborAmount: 0, + sellingPrice: 0, + kitPrice: 0, + }, + }); + } + if (action === "priceOrderItemsAndSaveServerData") { + return Promise.resolve([pricedPremiumFee]); + } + return Promise.resolve(null); + }); const { wrapper } = setupMocks(); await wrapper.vm.loadFeeParts("44101"); expect(getPricedMobileFeePart).toHaveBeenCalledWith("44101", "scheduling"); expect(getPricedRecycleFeePart).toHaveBeenCalledWith("44101", "scheduling"); + expect(store.dispatch).toHaveBeenCalledWith("getMobilePremiumFee", { + pageNameToLog: "scheduling", + }); + expect(store.dispatch).toHaveBeenCalledWith("priceOrderItemsAndSaveServerData", { + payload: { + availableLineItems: [ + expect.objectContaining({ + partNumber: "EARLYBIRD", + partType: "EARLY BIRD", + }), + ], + }, + pageNameToLog: "scheduling", + }); expect(wrapper.vm.mobileFeePart).toEqual({ partType: "MOBILE FEE" }); expect(wrapper.vm.recycleFeePart).toEqual({ partNumber: partNumberStrings.RECYCLE_FEE, }); + expect(wrapper.vm.mobilePremiumAppointmentFee).toEqual(pricedPremiumFee); + wrapper.unmount(); + }); + }); + + describe("premiumTimeSlotPrice", () => { + test("uses priced early bird amounts instead of zeroed parts response", async () => { + const { wrapper, getTotalLineItemPrice } = setupMocks({ totalLineItemPrice: 29.99 }); + const pricedPremiumFee = { + partNumber: "EARLYBIRD", + partType: "EARLY BIRD", + laborAmount: 29.99, + sellingPrice: 0, + kitPrice: 0, + }; + await wrapper.setData({ + mobilePremiumAppointmentFee: pricedPremiumFee, + }); + + expect(wrapper.vm.premiumTimeSlotPrice).toBe(29.99); + expect(getTotalLineItemPrice).toHaveBeenCalledWith(pricedPremiumFee); wrapper.unmount(); }); }); diff --git a/src/layouts/scheduling/scheduling.vue b/src/layouts/scheduling/scheduling.vue index e7e1ffc06..26c1e1d7c 100644 --- a/src/layouts/scheduling/scheduling.vue +++ b/src/layouts/scheduling/scheduling.vue @@ -338,6 +338,30 @@ function getServiceabilityDetails(serviceZipCode, pageNameToLog) { ); } +/** + * Fetches the early bird / premium fee part and prices + * @param {string} pageNameToLog + * @returns {Promise} + */ +async function getPricedMobilePremiumFee(pageNameToLog) { + const response = await store.dispatch(storeActions.GET_MOBILE_PREMIUM_FEE, { + pageNameToLog, + }); + const premiumFee = response?.data ?? response ?? null; + if (!premiumFee?.partNumber) { + return null; + } + + const pricedItems = await store.dispatch(storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA, { + payload: { + availableLineItems: [premiumFee], + }, + pageNameToLog, + }); + + return pricedItems?.find((item) => item.partNumber === premiumFee.partNumber) ?? null; +} + export default { name: "scheduling", async beforeRouteEnter(to, from, next) { @@ -350,9 +374,7 @@ export default { }, { resultKey: "mobilePremiumFee", - promise: store.dispatch("getMobilePremiumFee", { - pageNameToLog: to.name, - }), + promise: getPricedMobilePremiumFee(to.name), }, { resultKey: "providers", @@ -548,12 +570,14 @@ export default { }, methods: { async loadFeeParts(serviceZipCode, pageNameToLog = this.pageName) { - const [mobileFeePart, recycleFeePart] = await Promise.all([ + const [mobileFeePart, recycleFeePart, mobilePremiumAppointmentFee] = await Promise.all([ getPricedMobileFeePart(serviceZipCode, pageNameToLog), getPricedRecycleFeePart(serviceZipCode, pageNameToLog), + getPricedMobilePremiumFee(pageNameToLog), ]); this.mobileFeePart = mobileFeePart ?? null; this.recycleFeePart = recycleFeePart ?? null; + this.mobilePremiumAppointmentFee = mobilePremiumAppointmentFee ?? null; }, async loadSchedulingData( serviceZipCode,