Merge pull request #1855 from Safelite/feature/CSR-1994

Feature/csr 1994
This commit is contained in:
AMSNEHA 2024-06-10 16:23:49 +05:30 committed by GitHub
commit cb8e542672
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 507 additions and 43 deletions

View file

@ -3,6 +3,7 @@ const cartItemCategories = {
GLASS_PARTS: "glassParts", GLASS_PARTS: "glassParts",
SUPPORTING_ITEMS: "supportingItems", SUPPORTING_ITEMS: "supportingItems",
PROMOS: "promos", PROMOS: "promos",
SERVICE_PACKAGE_DISCOUNT: "servicePackageDiscount",
}; };
export { cartItemCategories }; export { cartItemCategories };

View file

@ -10,6 +10,7 @@ const cartItemTypes = {
PROMOS: "PROMOS", PROMOS: "PROMOS",
EARLY_BIRD: "EARLY BIRD", EARLY_BIRD: "EARLY BIRD",
SUPPLIES_REPAIR: "SUPPLIES-REPAIR", SUPPLIES_REPAIR: "SUPPLIES-REPAIR",
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
}; };
export { cartItemTypes }; export { cartItemTypes };

View file

@ -72,6 +72,10 @@ const endpoints = {
url: "/parts/api/v1/parts/mobile-fee", url: "/parts/api/v1/parts/mobile-fee",
method: "GET", method: "GET",
}, },
CashServicePackageDiscount: {
url: "/parts/api/v1/parts/service-package-discount",
method: "POST",
},
GetServiceabilityDetails: { GetServiceabilityDetails: {
url: "/location/api/v1/location/serviceability-details", url: "/location/api/v1/location/serviceability-details",
method: "GET", method: "GET",

View file

@ -9,6 +9,7 @@ const experimentSettings = {
PIA_INSURANCE: "DisplayPIAInsurance", PIA_INSURANCE: "DisplayPIAInsurance",
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA", SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
IS_EMAIL_OPTIONAL: "isEmailOptional", IS_EMAIL_OPTIONAL: "isEmailOptional",
SERVICE_PACKAGE_DISCOUNT: "OfferServicePackageDiscount",
}; };
const experimentTriggers = { const experimentTriggers = {

View file

@ -8,6 +8,7 @@ const partTypeStrings = {
MOBILE_FEE: "MOBILE FEE", MOBILE_FEE: "MOBILE FEE",
REPAIR_FEE: "REPAIR FEE", REPAIR_FEE: "REPAIR FEE",
EARLY_BIRD: "EARLY BIRD", EARLY_BIRD: "EARLY BIRD",
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
}; };
export { partTypeStrings }; export { partTypeStrings };

View file

@ -29,6 +29,7 @@ const storeActions = {
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer", GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer",
GET_MOLDING_QUESTIONS: "getMoldingQuestions", GET_MOLDING_QUESTIONS: "getMoldingQuestions",
GET_MOBILE_FEE_PART: "getMobileFeePart", GET_MOBILE_FEE_PART: "getMobileFeePart",
CASH_SERVICE_PACKAGE_DISCOUNT: "cashServicePackageDiscount",
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails", GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
GET_SHOP_TIME_SLOTS: "getShopTimeSlots", GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots", GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots",

View file

@ -306,6 +306,49 @@ describe("cart.vue", () => {
expect(found).toBe(true); expect(found).toBe(true);
}); });
// Service package discount Fee Cart Item
test("if there is service package discount fee on the order, a service package discount cart item should be added to the cart", () => {
// Arrange
const lineItems = {
glassParts: [],
supportingItems: [
{
description: null,
id: "bc00294e-6baa-403e-866e-52c267187a15",
kitPrice: 0,
laborAmount: 0,
partNumber: "DISC CASHSAVE70",
partType: "SERVICE PACKAGE DISCOUNT",
salesTax: null,
sellingPrice: -70,
},
],
vaps: [],
promos: [],
};
const availableVaps = [];
// Act
const { wrapper } = setupMocks({
props: {
modelValue: lineItems,
availableVaps: availableVaps,
},
});
// Assert
expect(wrapper.vm.servicePackageDiscountCartItem).not.toBeNull();
expect(wrapper.vm.servicePackageDiscountCartItem.subTotal).toEqual(-70);
expect(wrapper.vm.servicePackageDiscountCartItem.salesTax).toEqual(0);
const found =
wrapper.vm.cartItems.findIndex(
(cartItem) => cartItem == wrapper.vm.servicePackageDiscountCartItem
) >= 0;
expect(found).toBe(true);
});
// Other Supporting Items Cart Item // Other Supporting Items Cart Item
test("if there are other supporting items on the order, an other supporting items cart item should be added to the cart but should not be displayed", () => { test("if there are other supporting items on the order, an other supporting items cart item should be added to the cart but should not be displayed", () => {
// Arrange // Arrange

View file

@ -225,6 +225,13 @@ export default {
return subTotal; return subTotal;
}, },
getServicePackageDiscount() {
const servicePackageDiscountLineItems = this.servicePackageDiscountCartItem;
let subTotal = 0;
subTotal += servicePackageDiscountLineItems?.subTotal;
return subTotal;
},
getVapsCartItemsForSelectedPackage(packageName) { getVapsCartItemsForSelectedPackage(packageName) {
const packageContentTypes = getPackageContents( const packageContentTypes = getPackageContents(
this.glassToReplace, this.glassToReplace,
@ -265,7 +272,7 @@ export default {
getLineItemAmount(amount, useVerifyingText, category) { getLineItemAmount(amount, useVerifyingText, category) {
if (useVerifyingText) return this.verifyingCoverageText; if (useVerifyingText) return this.verifyingCoverageText;
return category == "promos" return category == ("promos" && "servicePackageDiscount")
? "(" + this.currencyFormatter.format(amount * -1) + ")" ? "(" + this.currencyFormatter.format(amount * -1) + ")"
: this.currencyFormatter.format(amount); : this.currencyFormatter.format(amount);
}, },
@ -372,6 +379,9 @@ export default {
if (this.mobileFeeCartItem) { if (this.mobileFeeCartItem) {
cartItems.push(this.mobileFeeCartItem); cartItems.push(this.mobileFeeCartItem);
} }
if (this.servicePackageDiscountCartItem) {
cartItems.push(this.servicePackageDiscountCartItem);
}
if (this.premiumAppointmentDiscountCartItem) { if (this.premiumAppointmentDiscountCartItem) {
cartItems.push(this.premiumAppointmentDiscountCartItem); cartItems.push(this.premiumAppointmentDiscountCartItem);
@ -422,6 +432,9 @@ export default {
} }
packagePrice += this.getVapsPrice(this.packageLevel); packagePrice += this.getVapsPrice(this.packageLevel);
if (this.servicePackageDiscountCartItem) {
packagePrice -= this.getServicePackageDiscount();
}
return packagePrice; return packagePrice;
}, },
@ -769,13 +782,58 @@ export default {
return cartItem; return cartItem;
}, },
servicePackageDiscountCartItemName() {
return this.getCmsContent("ServicePackageDiscountTextWidget", "Text");
},
servicePackageDiscountCartItem() {
let cartItem = null;
const servicePackageDiscountLineItem = this.supportingItems.find(
(lineItem) => lineItem.partType == partTypeStrings.SERVICE_PACKAGE_DISCOUNT
);
if (servicePackageDiscountLineItem) {
cartItem = {
name:
this.servicePackageDiscountCartItemName +
Math.abs(
baseMixin.methods.getTotalLineItemPrice(servicePackageDiscountLineItem)
),
category: cartItemCategories.SERVICE_PACKAGE_DISCOUNT,
cartItemType: cartItemTypes.SERVICE_PACKAGE_DISCOUNT,
isDisplayed: true,
isRemovable: false,
subTotal: 0,
salesTax: 0,
lineItems: [],
isCoveredByInsurance: false,
};
servicePackageDiscountLineItem.cartItemType = cartItem.cartItemType;
cartItem.lineItems.push(servicePackageDiscountLineItem);
cartItem.subTotal +=
(servicePackageDiscountLineItem.kitPrice ?? 0) +
(servicePackageDiscountLineItem.laborAmount ?? 0) +
(servicePackageDiscountLineItem.sellingPrice ?? 0);
cartItem.salesTax += servicePackageDiscountLineItem.salesTax ?? 0;
}
return cartItem;
},
otherSupportingItemsCartItem() { otherSupportingItemsCartItem() {
let cartItem = null; let cartItem = null;
// Don't include fees they are part of the package total const servicePackageDiscountLineItem = this.supportingItems.find(
let otherSupportingItemsLineItems = baseMixin.methods.filterOutFees( (lineItem) => lineItem.partType == partTypeStrings.SERVICE_PACKAGE_DISCOUNT
this.supportingItems
); );
const otherSupportingItems = this.supportingItems.filter((item) => {
return item != servicePackageDiscountLineItem;
});
// Don't include fees they are part of the package total
let otherSupportingItemsLineItems =
baseMixin.methods.filterOutFees(otherSupportingItems);
if (otherSupportingItemsLineItems.length > 0) { if (otherSupportingItemsLineItems.length > 0) {
cartItem = { cartItem = {

View file

@ -6,6 +6,8 @@ import quote from "@/layouts/quote/quote.vue";
import store from "@/store"; import store from "@/store";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper"; import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
import { nextTick } from "vue"; import { nextTick } from "vue";
import { experimentSettings } from "@/constants/experiments";
import baseMixin from "@/mixins/base-mixin.js";
jest.mock("@/store", () => ({ jest.mock("@/store", () => ({
commit: jest.fn(), commit: jest.fn(),
@ -60,9 +62,14 @@ const mockMixin = {
filterOutFees: jest.fn().mockImplementation(() => { filterOutFees: jest.fn().mockImplementation(() => {
return null; return null;
}), }),
getSettingValue: jest.fn((settingName) => {
if (settingName === experimentSettings.SERVICE_PACKAGE_DISCOUNT) {
return true;
}
return false;
}),
}, },
}; };
let mockTierOnePrice = 501; let mockTierOnePrice = 501;
const mockPriceOrderStoreAction = storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA; const mockPriceOrderStoreAction = storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
@ -234,8 +241,12 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//mock this to avoid needing to populate this.$route in an unrelated test //mock this to avoid needing to populate this.$route in an unrelated test
wrapper.vm.getDefaultIsInsuranceSelectedValue = jest.fn(); wrapper.vm.getDefaultIsInsuranceSelectedValue = jest.fn();
@ -254,6 +265,47 @@ describe("quote.vue", () => {
// This should have its own test // This should have its own test
//expect(vm.isInsuranceSelected !== null).toBe(true); //expect(vm.isInsuranceSelected !== null).toBe(true);
}); });
test("Returns true if service package discount setting is true", async () => {
//Arrange
store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
order: {
lineItems: {
glassParts: ["item", "item2"],
},
payment: {},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
},
},
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: { isInsurance: "false" } };
const isServicePackageDiscount = mockMixin.methods.getSettingValue(
experimentSettings.SERVICE_PACKAGE_DISCOUNT
);
//Act
await quote.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "quote" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
expect(isServicePackageDiscount).toBe(true);
});
test("should default to insurance if query param 'isInsurance' is true", async () => { test("should default to insurance if query param 'isInsurance' is true", async () => {
//Arrange //Arrange
store.getters = { store.getters = {
@ -274,6 +326,9 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: { isInsurance: "true" } }; wrapper.vm.$route = { query: { isInsurance: "true" } };
@ -309,6 +364,9 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: { isInsurance: "false" } }; wrapper.vm.$route = { query: { isInsurance: "false" } };
@ -345,6 +403,9 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
// Ensure that query param isn't overriding selection // Ensure that query param isn't overriding selection
@ -381,6 +442,9 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
// Ensure that query param isn't overriding selection // Ensure that query param isn't overriding selection
@ -418,6 +482,9 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
mockTierOnePrice = 200; mockTierOnePrice = 200;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -457,6 +524,9 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
mockTierOnePrice = 505; mockTierOnePrice = 505;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -495,6 +565,9 @@ describe("quote.vue", () => {
vehicle: { vehicle: {
cardId: "123", cardId: "123",
}, },
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: null }; wrapper.vm.$route = { query: null };
@ -533,6 +606,35 @@ describe("quote.vue", () => {
expect(wrapper.vm.lineItems.promos !== null).toBe(true); expect(wrapper.vm.lineItems.promos !== null).toBe(true);
}); });
test("On forward button action save supporting lineItems", async () => {
//Arrange
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
};
store.getters.order = {
lineItems: [],
payment: {
parentAccountNumber: 167132,
},
};
const { wrapper } = setupMocks({
customMountOptions: {
router: {
navigateWithSaving: jest.fn(),
},
route: { quote },
},
});
wrapper.vm.forwardButtonAction();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith(
"saveSupportingItems",
wrapper.vm.lineItems.supportingItems,
false
);
});
}); });
function setupMocks({ customMountOptions }) { function setupMocks({ customMountOptions }) {

View file

@ -29,6 +29,7 @@
:availableLineItems="availableLineItems" :availableLineItems="availableLineItems"
:isInsuranceSelected="isInsuranceSelected" :isInsuranceSelected="isInsuranceSelected"
@vapsItemsSelected="vapsItemsSelectedAction" @vapsItemsSelected="vapsItemsSelectedAction"
@servicePackageDiscountSelected="servicePackageDiscountSelectedAction"
:activePromos="lineItems.promos" :activePromos="lineItems.promos"
v-on="{ 'buttonEvent.openModal': openModalAction }" v-on="{ 'buttonEvent.openModal': openModalAction }"
validationRules="option-required" validationRules="option-required"
@ -86,6 +87,7 @@ import contentGroupModal from "@/fmg-components/content-group-modal/content-grou
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner"; import afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import experimentMixin from "@/mixins/experiment-mixin.js";
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin"; import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
@ -105,7 +107,7 @@ import {
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question"; import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
import { experimentSettings } from "@/constants/experiments";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
export default { export default {
@ -155,11 +157,27 @@ export default {
const lineItems = deepClone(store.getters.order.lineItems); const lineItems = deepClone(store.getters.order.lineItems);
lineItems.vaps = lineItems.vaps ?? []; lineItems.vaps = lineItems.vaps ?? [];
const nullSafeGlassParts = lineItems.glassParts ?? []; const nullSafeGlassParts = lineItems.glassParts ?? [];
const servicePackageDiscountSettingValue = experimentMixin.methods.getSettingValue(
experimentSettings.SERVICE_PACKAGE_DISCOUNT
);
const isServicePackageDiscount = servicePackageDiscountSettingValue === "True";
//Service package cash discount api call when experiment is active
var servicePackageDiscountPart = [];
if (isServicePackageDiscount) {
const servicePackageDiscountPartResponse =
await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.CASH_SERVICE_PACKAGE_DISCOUNT,
null,
"quote"
);
servicePackageDiscountPart.push(servicePackageDiscountPartResponse.data);
}
const availableLineItems = [ const availableLineItems = [
resultMap.rainDefense, resultMap.rainDefense,
...resultMap.supportingItems, ...resultMap.supportingItems,
...resultMap.wipers, ...resultMap.wipers,
...nullSafeGlassParts, ...nullSafeGlassParts,
...servicePackageDiscountPart,
]; ];
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging( const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
@ -278,6 +296,9 @@ export default {
vapsItemsSelectedAction(vapsItemsSelected) { vapsItemsSelectedAction(vapsItemsSelected) {
this.lineItems.vaps = vapsItemsSelected; this.lineItems.vaps = vapsItemsSelected;
}, },
servicePackageDiscountSelectedAction(servicePackageDiscountSelected) {
this.lineItems.supportingItems = servicePackageDiscountSelected;
},
backButtonAction() { backButtonAction() {
vehicleQuestionsMixin.methods.navigateBack(this); vehicleQuestionsMixin.methods.navigateBack(this);
}, },
@ -315,7 +336,11 @@ export default {
false false
); );
} }
this.dispatchStoreAction(
this.storeActions.SAVE_SUPPORTING_ITEMS,
this.lineItems.supportingItems,
false
);
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.lineItems.vaps, false); this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.lineItems.vaps, false);
this.dispatchStoreAction( this.dispatchStoreAction(
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS, storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,

View file

@ -6,6 +6,12 @@ import {
mockProcessedCmsContent, mockProcessedCmsContent,
inputQuestionWidgetAnswers, inputQuestionWidgetAnswers,
} from "./service-package-question-test-helper"; } from "./service-package-question-test-helper";
import {
containsLineItemWithPartType,
findLineItemsWithPartType,
} from "@/helpers/service-package-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { partTypeStrings } from "@/constants/part-type-strings";
import { nextTick } from "vue"; import { nextTick } from "vue";
jest.mock("@/store", () => ({ jest.mock("@/store", () => ({
@ -45,7 +51,46 @@ describe("service-package-question.vue", () => {
partType: "RAIN DEFENSE", partType: "RAIN DEFENSE",
price: 35.5, price: 35.5,
}, },
{
partNumber: "DISC CASHSAVE70",
description: null,
partType: "SERVICE PACKAGE DISCOUNT",
price: -70,
},
], ],
lineItems: {
supportingItems: [
{
description: null,
id: "b0c68683-da85-46ba-bc86-2642bd9d5fdb",
kitPrice: 0,
laborAmount: 0,
partNumber: "RECYCLE FEE",
partType: "REPLACE FEE",
salesTax: null,
sellingPrice: 0,
},
{
description: null,
id: "5f205a07-3c08-4244-9935-76d63a7bcd19",
kitPrice: 0,
laborAmount: 395,
partNumber: "RECAL STATIC",
partType: "RECALIBRATION",
salesTax: null,
sellingPrice: 0,
},
{
description: null,
kitPrice: 0,
laborAmount: 0,
partNumber: "DISC CASHSAVE70",
partType: "SERVICE PACKAGE DISCOUNT",
salesTax: null,
sellingPrice: -70,
},
],
},
activePromos: [], activePromos: [],
}; };
const packageNameKey = "05_01_CSR_Quote_Standard_Repair"; const packageNameKey = "05_01_CSR_Quote_Standard_Repair";
@ -68,6 +113,44 @@ describe("service-package-question.vue", () => {
}, },
]); ]);
}); });
it("should return price when there is discount", () => {
const wrapper = setupMocks({});
const partType = partTypeStrings.SERVICE_PACKAGE_DISCOUNT;
wrapper.vm.isServicePackageDiscountOnOrder = containsLineItemWithPartType(
partType,
mockProps.lineItems.supportingItems
);
const servicePackageDiscountLineItem = findLineItemsWithPartType(
partType,
mockProps.lineItems.supportingItems
);
wrapper.vm.getServicePackageDiscountPrice();
const price = baseMixin.methods.getTotalLineItemPrice(servicePackageDiscountLineItem[0]);
expect(Math.abs(price)).toEqual(70);
});
it("isServicePackageDiscountOnOrder returns true if it contains service package discount lineItems", () => {
// Arrange
const wrapper = setupMocks({});
// Assert
expect(wrapper.vm.isServicePackageDiscountOnOrder).toBe(true);
});
it("servicePackageDiscountParts should returns service package discount lineItems", () => {
// Arrange
const wrapper = setupMocks({});
// Assert
expect(wrapper.vm.servicePackageDiscountParts).toEqual([
{
partNumber: "DISC CASHSAVE70",
description: null,
partType: "SERVICE PACKAGE DISCOUNT",
price: -70,
},
]);
});
it("should have the correct insurance pricing text when insurance is selected", () => { it("should have the correct insurance pricing text when insurance is selected", () => {
// Arrange // Arrange
mockProps.isInsuranceSelected = true; mockProps.isInsuranceSelected = true;

View file

@ -69,12 +69,34 @@ export default {
selectedPackageName(newValue) { selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue); const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage); this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
const servicePackageDiscountPartInSelectedPackage =
this.getServicePackageDiscountPartForSelectedPackage(newValue);
let supportingItems = this.supportingLineItems;
if (servicePackageDiscountPartInSelectedPackage?.length > 0) {
supportingItems?.push(servicePackageDiscountPartInSelectedPackage[0]);
} else {
if (
supportingItems?.length > 0 &&
containsLineItemWithPartType(
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
supportingItems
)
) {
supportingItems = supportingItems.filter(
(item) => item.partType !== partTypeStrings.SERVICE_PACKAGE_DISCOUNT
);
}
}
this.$emit("servicePackageDiscountSelected", supportingItems);
}, },
}, },
computed: { computed: {
nullSafeAvailableLineItems() { nullSafeAvailableLineItems() {
return this.availableLineItems ?? []; return this.availableLineItems ?? [];
}, },
supportingLineItems() {
return this.$store.getters.lineItems?.supportingItems;
},
servicePackageAnswers() { servicePackageAnswers() {
const cmsWidgetName = this.isInsuranceSelected const cmsWidgetName = this.isInsuranceSelected
? this.insuranceCmsWidgetName ? this.insuranceCmsWidgetName
@ -99,10 +121,16 @@ export default {
buttonLabel: this.getHeaderTextFromCms(answer.SubWidgetName), buttonLabel: this.getHeaderTextFromCms(answer.SubWidgetName),
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName), buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName),
buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName), buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName),
buttonAuxillaryCopy: this.getDiscountedPackagePriceString(answer.Name), buttonAuxillaryCopy: this.getDiscountedPackagePriceString(
answer.Name,
this.isServicePackageDiscountOnOrder && answer.Text != ""
),
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName), buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
additionalButtonData: { additionalButtonData: {
strikeThroughPrice: this.getPackagePriceString(answer.Name), strikeThroughPrice: this.getPackagePriceString(answer.Name),
servicePackageDiscount:
this.isServicePackageDiscountOnOrder && answer.Text != "",
Text: answer.Text + this.getServicePackageDiscountPrice(),
}, },
})); }));
return modifiedAnswers; return modifiedAnswers;
@ -113,6 +141,12 @@ export default {
this.nullSafeAvailableLineItems this.nullSafeAvailableLineItems
); );
}, },
isServicePackageDiscountOnOrder() {
return containsLineItemWithPartType(
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
this.nullSafeAvailableLineItems
);
},
frontWipersApplicableForTierTwo() { frontWipersApplicableForTierTwo() {
return shouldFrontWipersBeAvailable( return shouldFrontWipersBeAvailable(
this.glassToReplace, this.glassToReplace,
@ -159,6 +193,12 @@ export default {
isRepair() { isRepair() {
return this.$store.getters.order.damage.isRepair; return this.$store.getters.order.damage.isRepair;
}, },
servicePackageDiscountParts() {
return findLineItemsWithPartType(
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
this.nullSafeAvailableLineItems
);
},
}, },
methods: { methods: {
processIfStatements, processIfStatements,
@ -178,21 +218,35 @@ export default {
}, },
getPackagePriceString(packageName) { getPackagePriceString(packageName) {
const formattedPriceFloat = parseFloat( const formattedPriceFloat = parseFloat(
this.getPackagePrice(packageName, { discountedPrice: false }) this.getPackagePrice(packageName, {
discountedPrice: false,
servicePackageDiscount: false,
})
).toFixed(2); ).toFixed(2);
return "$" + formattedPriceFloat; return "$" + formattedPriceFloat;
}, },
getDiscountedPackagePriceString(packageName) { getDiscountedPackagePriceString(packageName, servicePackageDiscount) {
const formattedPriceFloat = parseFloat( const formattedPriceFloat = parseFloat(
this.getPackagePrice(packageName, { discountedPrice: true }) this.getPackagePrice(packageName, { discountedPrice: true, servicePackageDiscount })
).toFixed(2); ).toFixed(2);
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat; return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
}, },
getPackagePrice(packageName, { discountedPrice = false }) { getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) {
const lineItemsToPrice = [...this.nullSafeAvailableLineItems]; var lineItemsToPrice = [...this.nullSafeAvailableLineItems];
//remove service package discount part
if (this.isServicePackageDiscountOnOrder) {
lineItemsToPrice = this.nullSafeAvailableLineItems.filter((item) => {
return item.partType != this.servicePackageDiscountParts[0].partType;
});
}
if (discountedPrice) { if (discountedPrice) {
lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos)); lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos));
} }
if (servicePackageDiscount) {
lineItemsToPrice.push(...this.servicePackageDiscountParts);
}
let priceFloat = this.isInsuranceSelected let priceFloat = this.isInsuranceSelected
? 0 ? 0
: baseMixin.methods.getTierOnePackagePrice( : baseMixin.methods.getTierOnePackagePrice(
@ -203,9 +257,17 @@ export default {
return priceFloat; return priceFloat;
}, },
getServicePackageDiscountPrice() {
if (this.isServicePackageDiscountOnOrder) {
let price = 0;
price += baseMixin.methods.getTotalLineItemPrice(
this.servicePackageDiscountParts[0]
);
return Math.abs(price);
}
},
getVapsPrice(packageName, applyPromoDiscounts = false) { getVapsPrice(packageName, applyPromoDiscounts = false) {
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName); const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
let price = 0; let price = 0;
vapsItems.forEach((item) => { vapsItems.forEach((item) => {
@ -261,6 +323,14 @@ export default {
return vapsLineItemsForSelectedPackage; return vapsLineItemsForSelectedPackage;
}, },
getServicePackageDiscountPartForSelectedPackage(packageName) {
const selectedPackage = this.servicePackageAnswers.filter(
(item) => item.value === packageName
);
if (selectedPackage?.[0]?.additionalButtonData?.servicePackageDiscount) {
return this.servicePackageDiscountParts;
} else return [];
},
combineLineItemsWithoutDuplicates(lineItemsOne, lineItemsTwo) { combineLineItemsWithoutDuplicates(lineItemsOne, lineItemsTwo) {
const combinedLineItemArray = [...lineItemsTwo]; const combinedLineItemArray = [...lineItemsTwo];
lineItemsOne.forEach((lineItemOne) => { lineItemsOne.forEach((lineItemOne) => {

View file

@ -18,21 +18,6 @@ describe("service-package-radio.vue", () => {
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"])); expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"]));
}); });
it("Should include buttonLabelAuxillaryCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelAuxillaryCopy"]));
});
it("Should include buttonLabelSubCopy in html", async () => { it("Should include buttonLabelSubCopy in html", async () => {
// Arrange // Arrange
let { wrapper } = setupMocks({ let { wrapper } = setupMocks({
@ -123,7 +108,11 @@ const mockProps = {
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>", "<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>",
buttonFooterCopy: "buttonFooterCopy test copy", buttonFooterCopy: "buttonFooterCopy test copy",
buttonAuxillaryCopy: "buttonAuxillaryCopy test copy", buttonAuxillaryCopy: "buttonAuxillaryCopy test copy",
additionalButtonData: { strikeThroughPrice: "strikeThroughPrice test copy" }, additionalButtonData: {
strikeThroughPrice: "strikeThroughPrice test copy",
servicePackageDiscount: true,
Text: "Special save",
},
}; };
function setupMocks({ mountOptionsMockData = {} }) { function setupMocks({ mountOptionsMockData = {} }) {

View file

@ -1,24 +1,51 @@
<template> <template>
<div
:class="[this.additionalButtonData.Text ? 'discount-text' : '']"
v-if="this.additionalButtonData.servicePackageDiscount"
v-html="this.additionalButtonData.Text"></div>
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue"> <baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
<div <div
class="package-label" class="package-label"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']" :class="[
this.buttonLabelSubCopy ? 'has-subheader' : '',
this.additionalButtonData.servicePackageDiscount ? 'has-text' : '',
]"
for="testradio"> for="testradio">
<div class="package-specs"> <div class="package-specs">
<p class="m-0"> <div v-if="this.additionalButtonData.servicePackageDiscount" class="row">
<span v-html="this.buttonLabel"></span> <div class="col md-6">
<span class="pricing-info"> <p class="m-0">
<span <span v-html="this.buttonLabel"></span>
class="strikethrough-price" </p>
<p
class="sub-label m-0"
v-if="this.buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p>
</div>
<span class="col md-6 service-package-discount">
<div
class="strikethrough-discount-price"
v-if="shouldDisplayStrikeThroughPrice" v-if="shouldDisplayStrikeThroughPrice"
v-html="this.additionalButtonData.strikeThroughPrice"></span> v-html="this.additionalButtonData.strikeThroughPrice"></div>
<span v-html="this.buttonAuxillaryCopy"></span>* <div class="discount-price" v-html="this.buttonAuxillaryCopy"></div>
</span> </span>
</p> </div>
<p <div v-else>
class="sub-label m-0" <p class="m-0">
v-if="this.buttonLabelSubCopy" <span v-html="this.buttonLabel"></span>
v-html="this.buttonLabelSubCopy"></p> <span class="pricing-info">
<span
class="strikethrough-price"
v-if="shouldDisplayStrikeThroughPrice"
v-html="this.additionalButtonData.strikeThroughPrice"></span>
<span v-html="this.buttonAuxillaryCopy"></span>*
</span>
</p>
<p
class="sub-label m-0"
v-if="this.buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p>
</div>
<div class="hide-when-closed"> <div class="hide-when-closed">
<!-- Parse the body text containing <ul> with logic --> <!-- Parse the body text containing <ul> with logic -->
<ul> <ul>
@ -109,6 +136,18 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.discount-text {
line-height: 36px;
display: flex;
border-top-right-radius: 0.5rem;
border-top-left-radius: 0.5rem;
background-color: #469d2a;
align-items: center;
justify-content: center;
padding: 5px;
width: 100%;
color: white;
}
.package-main { .package-main {
.package-wrapper { .package-wrapper {
margin: 0.5rem 0; margin: 0.5rem 0;
@ -139,6 +178,10 @@ export default {
&.has-subheader { &.has-subheader {
min-height: 80px; min-height: 80px;
} }
&.has-text {
border-radius: 0 0 0.5rem 0.5rem;
border: 1px solid #469d2a !important;
}
&:before { &:before {
content: ""; content: "";
@ -246,6 +289,28 @@ export default {
font-size: 0.75rem; font-size: 0.75rem;
} }
} }
span {
&.service-package-discount {
color: #469d2a;
text-align: end;
font-weight: 500;
font-size: 13px;
div.strikethrough-discount-price {
text-decoration: line-through;
margin-right: 0;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: $gray-550;
padding-right: 2px;
}
div.discount-price {
color: #469d2a;
font-weight: 600;
font-size: 26px;
}
}
}
ul { ul {
margin: 1rem 0 0 -15px; margin: 1rem 0 0 -15px;
padding: 0; padding: 0;

View file

@ -1422,6 +1422,26 @@ export const actions = {
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
}); });
}, },
cashServicePackageDiscount(context, { pageNameToLog }) {
const damage = context.getters.damage;
const damageType = damage.isRepair ? "Repair" : "Replace";
const glassPieces = convertGlassPieceNamingForApi(damage.glassToReplace);
return globalMethods
.callHttpClient({
method: endpoints.CashServicePackageDiscount.method,
endpoint: endpoints.CashServicePackageDiscount.url,
payload: {
glassPieces: glassPieces,
damageType: damageType,
},
logApiCall: true,
pageNameToLog: pageNameToLog,
})
.then((response) => {
return response;
});
},
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) { getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems?.map( const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems?.map(