diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js
index 29d36d091..594227f7b 100644
--- a/src/constants/endpoints.js
+++ b/src/constants/endpoints.js
@@ -72,6 +72,10 @@ const endpoints = {
url: "/parts/api/v1/parts/mobile-fee",
method: "GET",
},
+ GetPricingByDayPart: {
+ url: "/parts/api/v1/parts/pricing-by-day-part-number",
+ method: "GET",
+ },
GetServicePackageDiscountPart: {
url: "/parts/api/v1/parts/service-package-discount",
method: "POST",
diff --git a/src/constants/part-number-strings.js b/src/constants/part-number-strings.js
index d21ab83ac..d373ba016 100644
--- a/src/constants/part-number-strings.js
+++ b/src/constants/part-number-strings.js
@@ -3,6 +3,8 @@ const partNumberStrings = {
MOBILE_STATIC_RECAL_FEE: "RECAL MOBILE",
MOBILE_DUAL_RECAL_FEE: "RECAL MOBILEDUAL",
DONATION: "DONATION",
+ // Fees
+ RECYCLE_FEE: "RECYCLE FEE",
};
export { partNumberStrings };
diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js
index 5bb784efb..a0aaf8316 100644
--- a/src/constants/schedule-constants.js
+++ b/src/constants/schedule-constants.js
@@ -8,9 +8,18 @@ const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM";
const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";
+// TODO; This will need updating with real PricingByDay part type
+const PRICING_BY_DAY_PART_TYPE = "DISC CASHSAVE20";
+
const RouteCodeFlags = {
ALL_DAY_DROP_OFF: "ALL DAY DROP OFF",
OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF",
};
-export { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE, RouteCodeFlags };
+export {
+ AppointmentTypeStrings,
+ PREMIUM_TIME_SLOT_ID_FLAG,
+ PREMIUM_FEE_PART_TYPE,
+ PRICING_BY_DAY_PART_TYPE,
+ RouteCodeFlags,
+};
diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js
index 6c6ba62b7..baa219aa1 100644
--- a/src/constants/store-actions.js
+++ b/src/constants/store-actions.js
@@ -30,6 +30,7 @@ const storeActions = {
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer",
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
GET_MOBILE_FEE_PART: "getMobileFeePart",
+ GET_PRICING_BY_DAY_PART: "getPricingByDayPart",
GET_SERVICE_PACKAGE_DISCOUNT_PART: "getServicePackageDiscountPart",
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
diff --git a/src/digital-components/date-picker/mixins/constants.js b/src/digital-components/date-picker/mixins/constants.js
index f37e312f5..b9092b5eb 100644
--- a/src/digital-components/date-picker/mixins/constants.js
+++ b/src/digital-components/date-picker/mixins/constants.js
@@ -21,6 +21,50 @@ const MONTHS_OF_YEAR = [
"December",
];
-const DAYS_OF_WEEK = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
+const DAYS_OF_WEEK = [
+ // Monday, Friday, Saturday are the pricing by day premium days
+ {
+ label: "Sunday",
+ cssClass: "sunday",
+ index: 0,
+ isPricingByDayUpchargeDay: false,
+ },
+ {
+ label: "Monday",
+ cssClass: "monday",
+ index: 1,
+ isPricingByDayUpchargeDay: true,
+ },
+ {
+ label: "Tuesday",
+ cssClass: "tuesday",
+ index: 2,
+ isPricingByDayUpchargeDay: false,
+ },
+ {
+ label: "Wednesday",
+ cssClass: "wednesday",
+ index: 3,
+ isPricingByDayUpchargeDay: false,
+ },
+ {
+ label: "Thursday",
+ cssClass: "thursday",
+ index: 4,
+ isPricingByDayUpchargeDay: false,
+ },
+ {
+ label: "Friday",
+ cssClass: "friday",
+ index: 5,
+ isPricingByDayUpchargeDay: true,
+ },
+ {
+ label: "Saturday",
+ cssClass: "saturday",
+ index: 6,
+ isPricingByDayUpchargeDay: true,
+ },
+];
export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, DAYS_OF_WEEK };
diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue
index 72ed56035..74cfc0964 100644
--- a/src/digital-components/modal/modal.vue
+++ b/src/digital-components/modal/modal.vue
@@ -135,7 +135,7 @@ export default {
},
closeModal() {
const modal = Modal.getInstance(document.getElementById(this.modalId));
- modal.hide();
+ modal?.hide();
this.$emit("isModalOpened", false);
},
},
diff --git a/src/digital-components/textarea-question/textarea-question.vue b/src/digital-components/textarea-question/textarea-question.vue
index 3e13769e6..760f1dad4 100644
--- a/src/digital-components/textarea-question/textarea-question.vue
+++ b/src/digital-components/textarea-question/textarea-question.vue
@@ -1,11 +1,6 @@
@@ -72,20 +67,18 @@
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
-import textareaQuestion from "@/digital-components/textarea-question/textarea-question";
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
import textBlock from "@/digital-components/text-block/text-block";
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
+import techNotes from "@/layouts/customer-details/tech-notes/tech-notes";
//Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions";
import { errorMessages } from "@/constants/error-messages";
-import { routerParams } from "@/router/router-constants/router-params";
import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
-import { useField, validate } from "vee-validate";
import store from "@/store";
import { paymentMethods } from "@/constants/payment-method-constants";
@@ -199,13 +192,13 @@ export default {
components: {
funnelHeader,
funnelSubHeader,
- textareaQuestion,
textboxQuestion,
navbar,
Form,
textBlock,
phoneNumberQuestion,
checkboxQuestion,
+ techNotes,
},
};
diff --git a/src/layouts/customer-details/tech-notes/tech-notes.spec.js b/src/layouts/customer-details/tech-notes/tech-notes.spec.js
new file mode 100644
index 000000000..650045e24
--- /dev/null
+++ b/src/layouts/customer-details/tech-notes/tech-notes.spec.js
@@ -0,0 +1,76 @@
+import { shallowMount, mount } from "@vue/test-utils";
+import TechNotes from "./tech-notes.vue";
+import TextareaQuestion from "@/digital-components/textarea-question/textarea-question.vue";
+
+const mockMixin = {
+ methods: {
+ getCmsContent() {
+ return "Mocked CMS Content";
+ },
+ },
+};
+
+describe("TechNotes.vue", () => {
+ it("Should render TechNotes", async () => {
+ // Arrange
+ const wrapper = shallowMount(TechNotes, {
+ props: {
+ modelValue: "Initial Tech Notes",
+ textAreaLabelCopy: "Notes for your technician",
+ },
+ global: {
+ mixins: [mockMixin],
+ },
+ });
+
+ // Act
+ const techNotes = wrapper.findComponent({ ref: "techNotes" });
+
+ // Assert
+ expect(techNotes.exists()).toBe(true);
+ });
+
+ it("Should populate the techNotes prop and render it correctly", async () => {
+ // Arrange
+ const wrapper = mount(TechNotes, {
+ props: {
+ modelValue: "Populated Tech Notes",
+ textAreaLabelCopy: "Notes for your technician",
+ },
+ global: {
+ components: {
+ TextareaQuestion,
+ },
+ mixins: [mockMixin],
+ },
+ });
+
+ // Act
+ const textarea = wrapper.find("textarea");
+
+ // Assert
+ expect(textarea.element.value).toBe("Populated Tech Notes");
+ });
+
+ it("Should toggle the active class when clicked", async () => {
+ // Arrange
+ const wrapper = shallowMount(TechNotes, {
+ props: {
+ modelValue: "Initial Tech Notes",
+ textAreaLabelCopy: "Notes for your technician",
+ },
+ global: {
+ mixins: [mockMixin],
+ },
+ });
+
+ const toggleElement = wrapper.find(".tech-notes-toggle");
+
+ // Act & Assert
+ expect(wrapper.vm.isActive).toBe(false); // inactive
+ await toggleElement.trigger("click");
+ expect(wrapper.vm.isActive).toBe(true); // Active after first click
+ await toggleElement.trigger("click");
+ expect(wrapper.vm.isActive).toBe(false); // Inactive after second click
+ });
+});
diff --git a/src/layouts/customer-details/tech-notes/tech-notes.vue b/src/layouts/customer-details/tech-notes/tech-notes.vue
new file mode 100644
index 000000000..ab1bbc3b0
--- /dev/null
+++ b/src/layouts/customer-details/tech-notes/tech-notes.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js
index bf1157f2c..013916af0 100644
--- a/src/layouts/payment-method/payment-method.spec.js
+++ b/src/layouts/payment-method/payment-method.spec.js
@@ -16,6 +16,10 @@ jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({
submitWorkOrder: jest.fn(),
}));
+jest.mock("@/helpers/pricing-helper.js", () => ({
+ getAmountDue: jest.fn(),
+}));
+
let piaDisabledFlag = false;
describe("payment-method.vue", () => {
diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue
index 47a6e2a14..33e070a93 100644
--- a/src/layouts/payment-method/payment-method.vue
+++ b/src/layouts/payment-method/payment-method.vue
@@ -158,6 +158,12 @@ import { coverageStatus } from "@/constants/insurance";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper";
import { getBoolFromString } from "@/helpers/boolean-helper";
+import {
+ getDisplayAmountDue,
+ getAmountDue,
+ getSubTotal,
+ getSalesTax,
+} from "@/helpers/pricing-helper.js";
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
@@ -767,7 +773,7 @@ export default {
);
},
totalAmountDue() {
- return baseMixin.methods.getAmountDue(this.lineItems);
+ return getAmountDue(this.lineItems);
},
isPiaEnabled() {
const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE);
diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue
index 6bb99f3c8..b9f19f64a 100644
--- a/src/layouts/payment-pia-return/payment-pia-return.vue
+++ b/src/layouts/payment-pia-return/payment-pia-return.vue
@@ -10,11 +10,17 @@ import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
-import baseMixin from "@/mixins/base-mixin.js";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form } from "vee-validate";
import { paymentMethods } from "@/constants/payment-method-constants";
import { routerParams } from "@/router/router-constants/router-params";
+import baseMixin from "@/mixins/base-mixin.js";
+import {
+ getDisplayAmountDue,
+ getAmountDue,
+ getSubTotal,
+ getSalesTax,
+} from "@/helpers/pricing-helper.js";
// iframeResizer IS loaded into the page and necessary for the package to
// to auto scale the iFrame this page is loaded in
// Do not remove despite showing as "unused" CASH-309
@@ -163,7 +169,7 @@ export default {
}
},
getAmountDue() {
- return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
+ return getAmountDue(store.getters.order.lineItems);
},
async saveAndSubmitWorkOrder() {
// Final work order submit after returning from PIA.
diff --git a/src/layouts/payment/payment.spec.js b/src/layouts/payment/payment.spec.js
index 7bc14f229..05ad6dbc9 100644
--- a/src/layouts/payment/payment.spec.js
+++ b/src/layouts/payment/payment.spec.js
@@ -5,10 +5,14 @@ import payment from "@/layouts/payment/payment";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper";
import store from "@/store";
-import baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions";
import { paymentMethods } from "@/constants/payment-method-constants";
+jest.mock("@/helpers/pricing-helper.js", () => ({
+ getAmountDue: jest.fn(),
+ getDisplayAmountDue: jest.fn(),
+}));
+
// Constants
const parts = {
windshield: {
diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue
index ecd2489c8..abf06927b 100644
--- a/src/layouts/payment/payment.vue
+++ b/src/layouts/payment/payment.vue
@@ -234,6 +234,12 @@ import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.
import { routerParams } from "@/router/router-constants/router-params";
import { coverageStatus } from "@/constants/insurance";
import { getBoolFromString } from "@/helpers/boolean-helper.js";
+import {
+ getDisplayAmountDue,
+ getAmountDue,
+ getSubTotal,
+ getSalesTax,
+} from "@/helpers/pricing-helper.js";
export default {
name: "payment",
@@ -656,10 +662,10 @@ export default {
this.piaLineItems = lineItems.join("||");
},
getAmountDue() {
- return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
+ return getAmountDue(store.getters.order.lineItems);
},
getDisplayAmountDue() {
- return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
+ return getDisplayAmountDue(store.getters.order.lineItems);
},
async paymentFailedPayLater() {
this.$refs.loadingModal.isModalVisible = true;
diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue
index d32054108..615662013 100644
--- a/src/layouts/quote/quote.vue
+++ b/src/layouts/quote/quote.vue
@@ -266,6 +266,7 @@ export default {
false
);
+ // This will remove any servicePackageDiscount item from lineItems.supportingItems
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SUPPORTING_ITEMS,
resultMap.supportingItems,
@@ -484,7 +485,7 @@ export default {
};
},
mounted() {
- this.attachCustomEvents();
+ this.attachCustomEventsForAnalytics();
},
computed: {
lineItemsCloneForWatcher() {
@@ -670,7 +671,7 @@ export default {
);
}
},
- attachCustomEvents() {
+ attachCustomEventsForAnalytics() {
this.prependActionToMethod(this, this.forwardButtonAction, () => {
if (!this.isInsuranceSelected && this.lineItems.vaps?.length == 0) {
const tierOnePrice =
diff --git a/src/layouts/quote/service-package-question/service-package-question.vue b/src/layouts/quote/service-package-question/service-package-question.vue
index ea40b2780..ab4772107 100644
--- a/src/layouts/quote/service-package-question/service-package-question.vue
+++ b/src/layouts/quote/service-package-question/service-package-question.vue
@@ -30,7 +30,7 @@ import {
containsLineItemWithPartType,
findLineItemsWithPartType,
getPackageNameByType,
- getDiscountPackageName,
+ getDiscountedPackageName,
} from "@/helpers/service-package-helper";
import {
getPromosThatMatchLineItemsOnOrder,
@@ -166,7 +166,7 @@ export default {
const discountServicePackage = experimentMixin.methods.getSettingValue(
experimentSettings.PROMO_ON_PACKAGE
);
- return getDiscountPackageName(discountServicePackage);
+ return getDiscountedPackageName(discountServicePackage);
},
frontWipersApplicableForTierTwo() {
return shouldFrontWipersBeAvailable(
diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js
index 48e6e8afa..3344e3d27 100644
--- a/src/layouts/schedule/schedule.spec.js
+++ b/src/layouts/schedule/schedule.spec.js
@@ -130,6 +130,10 @@ jest.mock("@/mixins/base-mixin.js", () => ({
]);
}
}),
+ filterOutCertainPartTypesOrNumbers: jest.fn(),
+ hasSubmittedOrder: jest.fn(),
+ getTotalPriceOfAllLineItemsAndChildParts: jest.fn(),
+ getTotalLineItemPrice: jest.fn(),
},
}));
@@ -631,34 +635,6 @@ describe("schedule.vue...", () => {
expect.anything()
);
});
-
- test("if no EARLY BIRD supporting item, then updateSupportingItems should NOT call store action", async () => {
- // Arrange
- store.getters.order.serviceLocation.appointmentType = "Mobile";
- store.getters.lineItems.supportingItems = [];
- const { wrapper } = setupMocks({});
- wrapper.vm.dispatchStoreAction = jest.fn(() => {
- return {
- data: [],
- };
- });
- wrapper.vm.mobilePremiumAppointmentFee = 14.99;
- wrapper.setData({
- selectedTimeSlot: {
- date: "2019-01-01",
- startTime: "09:00",
- endTime: "10:00",
- routeCode: null,
- isPremiumAppointment: false,
- },
- });
-
- // Act
- await wrapper.vm.updateSupportingItems();
-
- // Assert
- expect(wrapper.vm.dispatchStoreAction).not.toBeCalled();
- });
});
const mockCmsContent = {};
diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue
index 4f9f09cb1..48876d5e8 100644
--- a/src/layouts/schedule/schedule.vue
+++ b/src/layouts/schedule/schedule.vue
@@ -19,7 +19,6 @@
-
+ :pricingByDayBasePrice="pricingByDayBasePrice"
+ :pricingByDayUpcharge="pricingByDayUpcharge"
+ :showPricingByDay="showPricingByDay"
+ :isPricingByDayExperiment="isPricingByDayExperiment"
+ @date-clicked="handleDateClicked" />
item.partType == PRICING_BY_DAY_PART_TYPE
+ );
+ if (pricingByDayUpchargeFeeIndex > -1) {
+ includePricingByDayUpcharge = true;
+ }
+ }
+
+ // Load page with date already selected?
let preSelectedDate = await store.getters.order.schedule.date;
if (!preSelectedDate || preSelectedDate.startTime === null) {
preSelectedDate = null;
}
- const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({
- // setup config options for date-picker
- selectableDatesSetting: "custom",
- initialViewRowsToShow: 5,
- customSelectableDatesCallback: getAvailableDates,
- preSelectedDate: preSelectedDate,
- });
- // TODO - ONCE A DATEPICKER VERSION IS FINALIZED,
- // MAKE SURE THE ABOVE METHOD loadInitialData POINTS TO THE RIGHT FILE
+ // Set up promises
+ const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
+
+ const alertReasonsPromise = locationAlerts.methods.loadInitialData(
+ store.getters.order.serviceLocation.zipCodeCtu,
+ store.getters.order.serviceLocation.provider?.address?.zipCodeCtu
+ );
+
+ // While Pricing By Day Experiment is active, using the updated datePicker
+ const datePickerInitialDataPromise =
+ await datePickerForPricingByDay.methods.loadInitialData({
+ // setup config options for date-picker
+ selectableDatesSetting: "custom",
+ initialViewRowsToShow: 5,
+ customSelectableDatesCallback: getAvailableDates,
+ preSelectedDate: preSelectedDate,
+ });
+
+ // Get pricingByDayUpcharge needed for Pricing By Day
+ const pricingByDayUpchargePartPromise = getPricingByDayPartWithPrice();
const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_MOBILE_PREMIUM_FEE,
null,
"schedule"
);
-
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
if (result.data) {
return baseMixin.methods.dispatchStoreActionWithLogging(
@@ -264,11 +322,6 @@ export default {
}
});
- const alertReasonsPromise = locationAlerts.methods.loadInitialData(
- store.getters.order.serviceLocation.zipCodeCtu,
- store.getters.order.serviceLocation.provider?.address?.zipCodeCtu
- );
-
// Settle promises and get results
const promiseResultMap = [
{
@@ -283,6 +336,10 @@ export default {
resultKey: "datePickerInitialData",
promise: datePickerInitialDataPromise,
},
+ {
+ resultKey: "pricingByDayUpchargePart",
+ promise: pricingByDayUpchargePartPromise,
+ },
{
resultKey: "premiumFeeWithPrice",
promise: premiumFeeWithPricePromise,
@@ -291,10 +348,19 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap);
+ const pricingByDayUpcharge = await baseMixin.methods.getTotalLineItemPrice(
+ resultMap.pricingByDayUpchargePart,
+ false
+ );
+
+ const datePickerInitialData = resultMap.datePickerInitialData;
+ datePickerInitialData.pricingByDayBasePrice = pricingByDayBasePrice;
+ datePickerInitialData.pricingByDayUpcharge = pricingByDayUpcharge;
+
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
- vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData);
+ vm.$refs.datePicker.initializeComponent(datePickerInitialData);
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
vm.selectableDatesData = resultMap.datePickerInitialData.initialShopTimeSlotsResponse;
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice
@@ -302,6 +368,12 @@ export default {
: null;
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
vm.setDisplayWaitList();
+ vm.pricingByDayUpchargeLineItem = resultMap.pricingByDayUpchargePart;
+ vm.includePricingByDayUpcharge = includePricingByDayUpcharge;
+ vm.isPricingByDayExperiment = isPricingByDayExperiment;
+ vm.pricingByDayBasePrice = pricingByDayBasePrice;
+ vm.pricingByDayUpcharge = pricingByDayUpcharge;
+ vm.showPricingByDay = showPricingByDay;
});
},
computed: {
@@ -316,21 +388,12 @@ export default {
return this.$store.getters.order.serviceLocation.appointmentType;
},
timeSlotsForSelectedDate() {
- if (!this.selectedDate) {
- return null;
- }
+ if (!this.selectedDate) return null;
return this.selectableDatesData.days?.find(
(selectableDate) => selectableDate.date === this.selectedDate
);
},
- isPricingByDayExperiment() {
- return (
- experimentMixin.methods
- .getSettingValue(experimentSettings.PRICING_BY_DAY)
- ?.toLowerCase() === "true"
- );
- },
},
methods: {
splitCopyOnCMSPlaceHolder,
@@ -377,6 +440,7 @@ export default {
this.appointmentType,
this.$store.getters.order.serviceLocation.provider.providerNumber
);
+
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days = this.selectableDatesData.days.concat(
newShopTimeSlots.days
@@ -555,6 +619,28 @@ export default {
updateSupportingItems() {
const supportingItems = this.getSupportingItems();
+ // if we have a pricing by day upcharge, then save/update supporting items with it
+ const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex(
+ (item) => item.partType == PRICING_BY_DAY_PART_TYPE
+ );
+
+ if (this.includePricingByDayUpcharge) {
+ if (pricingByDayUpchargeFeeIndex > -1) {
+ supportingItems[pricingByDayUpchargeFeeIndex].laborAmount =
+ this.pricingByDayUpchargeLineItem.laborAmount;
+ supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice =
+ this.pricingByDayUpchargeLineItem.sellingPrice;
+ supportingItems[pricingByDayUpchargeFeeIndex].kitPrice =
+ this.pricingByDayUpchargeLineItem.kitPrice;
+ } else {
+ supportingItems.push(this.pricingByDayUpchargeLineItem);
+ }
+ } else {
+ if (pricingByDayUpchargeFeeIndex >= 0) {
+ supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
+ }
+ }
+
// if we have a premium fee(early bird), then save/update supporting items
if (
this.appointmentType === AppointmentTypeStrings.MOBILE &&
@@ -564,7 +650,7 @@ export default {
(item) => item.partType == PREMIUM_FEE_PART_TYPE
);
- if (premiumFeeIndex >= 0) {
+ if (premiumFeeIndex > -1) {
supportingItems[premiumFeeIndex].laborAmount =
this.mobilePremiumAppointmentFee.laborAmount;
supportingItems[premiumFeeIndex].sellingPrice =
@@ -574,12 +660,6 @@ export default {
} else {
supportingItems.push(this.mobilePremiumAppointmentFee);
}
-
- this.dispatchStoreAction(
- this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
- supportingItems,
- false
- );
} else {
if (!supportingItems) {
return;
@@ -592,17 +672,28 @@ export default {
if (removePremiumFeeIndex >= 0) {
supportingItems.splice(removePremiumFeeIndex, 1);
- this.dispatchStoreAction(
- this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
- supportingItems,
- false
- );
}
}
+
+ this.dispatchStoreAction(
+ this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
+ supportingItems,
+ false
+ );
},
handleWaitListRequested(value) {
this.waitListRequested = value;
},
+ handleDateClicked(date) {
+ // do something to mark this as upcharge day or not...
+ if (date.isPricingByDayUpchargeDay) {
+ this.includePricingByDayUpcharge = true;
+ } else {
+ this.includePricingByDayUpcharge = false;
+ }
+
+ this.openInshopTimeSlotsModal();
+ },
},
watch: {
selectedDate(newValue, oldValue) {
@@ -631,7 +722,6 @@ export default {
funnelSubHeader,
Form,
loadingModal,
- datePicker,
datePickerForPricingByDay,
locationAlerts,
timeSlotModalQuestion,
diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js
index 1983933ea..6fd6075ab 100644
--- a/src/mixins/base-mixin.js
+++ b/src/mixins/base-mixin.js
@@ -110,6 +110,25 @@ export default {
});
return filteredLineItems;
},
+ filterOutCertainPartTypesOrNumbers(
+ lineItemsArray,
+ { partTypesToRemove = [], partNumbersToRemove = [] }
+ ) {
+ // lineItems s/b an ARRAY here
+ if (!Array.isArray(lineItemsArray)) return;
+
+ partTypesToRemove.forEach((partType) => {
+ lineItemsArray = lineItemsArray.filter((item) => {
+ return !item?.partType?.includes(partType);
+ });
+ });
+ partNumbersToRemove.forEach((partNumber) => {
+ lineItemsArray = lineItemsArray.filter((item) => {
+ return !item?.partNumber?.includes(partNumber);
+ });
+ });
+ return lineItemsArray;
+ },
filterOutServicePackageDiscountPart(lineItems) {
const filteredLineItems = lineItems?.filter((item) => {
return !item?.partType?.includes(partTypeStrings.SERVICE_PACKAGE_DISCOUNT);
@@ -133,75 +152,18 @@ export default {
return totalPrice;
},
getTotalLineItemPrice(lineItem, includeTax) {
+ const kitPrice = lineItem.kitPrice ?? 0;
+ const laborAmount = lineItem.laborAmount ?? 0;
+ const sellingPrice = lineItem.sellingPrice ?? 0;
+ const salesTax = lineItem.salesTax ?? 0;
+
if (includeTax) {
- return (
- lineItem.kitPrice +
- lineItem.laborAmount +
- lineItem.sellingPrice +
- lineItem.salesTax
- );
+ return kitPrice + laborAmount + sellingPrice + salesTax;
} else {
- return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
+ return kitPrice + laborAmount + sellingPrice;
}
},
- getDisplayAmountDue(lineItems) {
- return this.getAmountDue(lineItems).toLocaleString("en-US", {
- style: "currency",
- currency: "USD",
- });
- },
- getAmountDue(lineItems, includeTax = true) {
- var amountDue = 0;
- if (lineItems?.glassParts) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
- lineItems.glassParts,
- includeTax
- );
- }
- if (lineItems?.supportingItems) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
- lineItems.supportingItems,
- includeTax
- );
- }
-
- const order = this.hasSubmittedOrder() ? this.getSubmittedOrder() : store.getters.order;
- if (
- store.getters.coverageIsVerified &&
- !order.policy.isNoComp &&
- !order.policy.isItac
- ) {
- amountDue = order.policy.currentDeductible;
- }
-
- if (lineItems?.vaps) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
- lineItems.vaps,
- includeTax
- );
- }
-
- if (lineItems?.promos) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
- lineItems.promos,
- includeTax
- );
- }
-
- return ((amountDue * 100) / 100).toFixed(2);
- },
- getSubTotal(lineItems) {
- // this is amountDue without sales tax
- return this.getAmountDue(lineItems, false);
- },
- getSalesTax(lineItems) {
- // this is amountDue minus subTotal
- return (
- ((this.getAmountDue(lineItems) - this.getSubTotal(lineItems)) * 100) /
- 100
- ).toFixed(2);
- },
scrollToPageTop() {
const container = document.getElementsByClassName("page-container-grouped-styles")[0];
container.scrollTo({ top: 0, left: 0, behavior: "smooth" });
diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js
index a3493910e..2484c0871 100644
--- a/src/mixins/base-mixin.spec.js
+++ b/src/mixins/base-mixin.spec.js
@@ -144,180 +144,6 @@ describe("baseMixin.js", () => {
expect(mixIn.methods.dispatchStoreActionWithLogging).toBeCalled();
});
-
- test("getAmountDue for verified deductible should be deductible plus any lineitems", () => {
- const mixIn = getMixInInstance({});
- const lineItems = {
- glassParts: [],
- supportingItems: [],
- vaps: [
- {
- cartItemType: "FRONT WIPERS",
- description: 'WIPER BLADE STANDARD 18"',
- kitPrice: 0,
- laborAmount: 0,
- partNumber: "WB18",
- partType: "FRONT WIPER",
- salesTax: 2.0,
- sellingPrice: 30.0,
- },
- {
- cartItemType: "FRONT WIPERS",
- description: 'WIPER BLADE STANDARD 26"',
- kitPrice: 0,
- laborAmount: 0,
- partNumber: "WB26",
- partType: "FRONT WIPER",
- salesTax: 1.0,
- sellingPrice: 20.0,
- },
- ],
- promos: [],
- };
-
- const payment = { insuranceCoverage: { isVerified: false } };
- const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
-
- store.getters = {
- payment: payment,
- policy: policy,
- order: {
- payment: payment,
- policy: policy,
- },
- hasSubmittedOrder: false,
- };
-
- var amtDue = mixIn.methods.getAmountDue(lineItems);
-
- expect(amtDue).toEqual("53.00");
- });
-
- test("getAmountDue should function with a submitted order", () => {
- const mixIn = getMixInInstance({});
- const lineItems = {
- glassParts: [],
- supportingItems: [],
- vaps: [
- {
- cartItemType: "FRONT WIPERS",
- description: 'WIPER BLADE STANDARD 18"',
- kitPrice: 0,
- laborAmount: 0,
- partNumber: "WB18",
- partType: "FRONT WIPER",
- salesTax: 2.0,
- sellingPrice: 30.0,
- },
- {
- cartItemType: "FRONT WIPERS",
- description: 'WIPER BLADE STANDARD 26"',
- kitPrice: 0,
- laborAmount: 0,
- partNumber: "WB26",
- partType: "FRONT WIPER",
- salesTax: 1.0,
- sellingPrice: 20.0,
- },
- ],
- promos: [],
- };
-
- const payment = { insuranceCoverage: { isVerified: false } };
- const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
-
- mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(true);
- mixIn.methods.getSubmittedOrder = jest.fn().mockReturnValue({
- payment: payment,
- policy: policy,
- });
-
- store.getters = {
- payment: {},
- policy: {},
- order: {},
- };
-
- var amtDue = mixIn.methods.getAmountDue(lineItems);
-
- expect(amtDue).toEqual("53.00");
- });
-
- test("getAmountDue for cash should sum lineitems", () => {
- const mixIn = getMixInInstance({});
- const lineItems = {
- glassParts: [
- {
- partNumber: "FW06143GTYN",
- description:
- "solar, heads-up display, third visor frit, soundproofing, rain/light sensor, lane departure warning system",
- color: "Green Tint",
- partType: "WINDSHIELD",
- canSafeliteRecalibrate: true,
- requiresRecalibration: true,
- requiresCapabilityQuestions: false,
- recalibrationType: "STATIC",
- childParts: [
- {
- partNumber: "RS 101 PAD",
- kitPrice: 0,
- laborAmount: 0,
- salesTax: 5.0,
- sellingPrice: 5.0,
- },
- ],
- id: "73e80976-79be-455a-b4e7-f9b058b61749",
- kitPrice: 0,
- laborAmount: 60,
- salesTax: 20.0,
- sellingPrice: 1000.0,
- },
- ],
- supportingItems: [],
- vaps: [
- {
- cartItemType: "FRONT WIPERS",
- description: 'WIPER BLADE STANDARD 18"',
- kitPrice: 0,
- laborAmount: 0,
- partNumber: "WB18",
- partType: "FRONT WIPER",
- salesTax: 2.0,
- sellingPrice: 30.0,
- },
- {
- cartItemType: "FRONT WIPERS",
- description: 'WIPER BLADE STANDARD 26"',
- kitPrice: 0,
- laborAmount: 0,
- partNumber: "WB26",
- partType: "FRONT WIPER",
- salesTax: 1.0,
- sellingPrice: 20.0,
- },
- ],
- promos: [],
- };
-
- const payment = { isInsurance: null, insuranceCoverage: { isVerified: null } };
- const policy = { isNoComp: false, isItac: false, currentDeductible: 0 };
-
- mixIn.methods.hasSubmittedOrder = jest.fn().mockReturnValue(false);
-
- store.getters = {
- payment: payment,
- policy: policy,
- order: {
- payment: payment,
- policy: policy,
- },
- hasSubmittedOrder: false,
- };
-
- var amtDue = mixIn.methods.getAmountDue(lineItems);
-
- expect(amtDue).toEqual("1143.00");
- });
});
function getMixInInstance({ isDispatchSuccess = true }) {
diff --git a/src/store/index.js b/src/store/index.js
index a7e7c6ae9..da849c127 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1749,6 +1749,18 @@ export const actions = {
pageNameToLog: pageNameToLog,
});
},
+ async getPricingByDayPart(context, { pageNameToLog }) {
+ return await globalMethods
+ .callHttpClient({
+ method: endpoints.GetPricingByDayPart.method,
+ endpoint: endpoints.GetPricingByDayPart.url,
+ logApiCall: true,
+ pageNameToLog: pageNameToLog,
+ })
+ .then((response) => {
+ return response;
+ });
+ },
getServicePackageDiscountPart(context, { pageNameToLog }) {
const damage = context.getters.damage;
const damageType = damage.isRepair ? "Repair" : "Replace";