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",
SUPPORTING_ITEMS: "supportingItems",
PROMOS: "promos",
SERVICE_PACKAGE_DISCOUNT: "servicePackageDiscount",
};
export { cartItemCategories };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -306,6 +306,49 @@ describe("cart.vue", () => {
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
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

View file

@ -225,6 +225,13 @@ export default {
return subTotal;
},
getServicePackageDiscount() {
const servicePackageDiscountLineItems = this.servicePackageDiscountCartItem;
let subTotal = 0;
subTotal += servicePackageDiscountLineItems?.subTotal;
return subTotal;
},
getVapsCartItemsForSelectedPackage(packageName) {
const packageContentTypes = getPackageContents(
this.glassToReplace,
@ -265,7 +272,7 @@ export default {
getLineItemAmount(amount, useVerifyingText, category) {
if (useVerifyingText) return this.verifyingCoverageText;
return category == "promos"
return category == ("promos" && "servicePackageDiscount")
? "(" + this.currencyFormatter.format(amount * -1) + ")"
: this.currencyFormatter.format(amount);
},
@ -372,6 +379,9 @@ export default {
if (this.mobileFeeCartItem) {
cartItems.push(this.mobileFeeCartItem);
}
if (this.servicePackageDiscountCartItem) {
cartItems.push(this.servicePackageDiscountCartItem);
}
if (this.premiumAppointmentDiscountCartItem) {
cartItems.push(this.premiumAppointmentDiscountCartItem);
@ -422,6 +432,9 @@ export default {
}
packagePrice += this.getVapsPrice(this.packageLevel);
if (this.servicePackageDiscountCartItem) {
packagePrice -= this.getServicePackageDiscount();
}
return packagePrice;
},
@ -769,13 +782,58 @@ export default {
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() {
let cartItem = null;
// Don't include fees they are part of the package total
let otherSupportingItemsLineItems = baseMixin.methods.filterOutFees(
this.supportingItems
const servicePackageDiscountLineItem = this.supportingItems.find(
(lineItem) => lineItem.partType == partTypeStrings.SERVICE_PACKAGE_DISCOUNT
);
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) {
cartItem = {

View file

@ -6,6 +6,8 @@ import quote from "@/layouts/quote/quote.vue";
import store from "@/store";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
import { nextTick } from "vue";
import { experimentSettings } from "@/constants/experiments";
import baseMixin from "@/mixins/base-mixin.js";
jest.mock("@/store", () => ({
commit: jest.fn(),
@ -60,9 +62,14 @@ const mockMixin = {
filterOutFees: jest.fn().mockImplementation(() => {
return null;
}),
getSettingValue: jest.fn((settingName) => {
if (settingName === experimentSettings.SERVICE_PACKAGE_DISCOUNT) {
return true;
}
return false;
}),
},
};
let mockTierOnePrice = 501;
const mockPriceOrderStoreAction = storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
@ -234,8 +241,12 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
//mock this to avoid needing to populate this.$route in an unrelated test
wrapper.vm.getDefaultIsInsuranceSelectedValue = jest.fn();
@ -254,6 +265,47 @@ describe("quote.vue", () => {
// This should have its own test
//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 () => {
//Arrange
store.getters = {
@ -274,6 +326,9 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: { isInsurance: "true" } };
@ -309,6 +364,9 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: { isInsurance: "false" } };
@ -345,6 +403,9 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
// Ensure that query param isn't overriding selection
@ -381,6 +442,9 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
// Ensure that query param isn't overriding selection
@ -418,6 +482,9 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 200;
const { wrapper } = setupMocks({});
@ -457,6 +524,9 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 505;
const { wrapper } = setupMocks({});
@ -495,6 +565,9 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: null };
@ -533,6 +606,35 @@ describe("quote.vue", () => {
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 }) {

View file

@ -29,6 +29,7 @@
:availableLineItems="availableLineItems"
:isInsuranceSelected="isInsuranceSelected"
@vapsItemsSelected="vapsItemsSelectedAction"
@servicePackageDiscountSelected="servicePackageDiscountSelectedAction"
:activePromos="lineItems.promos"
v-on="{ 'buttonEvent.openModal': openModalAction }"
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 afterpayModalBanner from "@/layouts/quote/afterpay-modal-banner/afterpay-modal-banner";
import baseMixin from "@/mixins/base-mixin.js";
import experimentMixin from "@/mixins/experiment-mixin.js";
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions";
@ -105,7 +107,7 @@ import {
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
import { experimentSettings } from "@/constants/experiments";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
export default {
@ -155,11 +157,27 @@ export default {
const lineItems = deepClone(store.getters.order.lineItems);
lineItems.vaps = lineItems.vaps ?? [];
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 = [
resultMap.rainDefense,
...resultMap.supportingItems,
...resultMap.wipers,
...nullSafeGlassParts,
...servicePackageDiscountPart,
];
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
@ -278,6 +296,9 @@ export default {
vapsItemsSelectedAction(vapsItemsSelected) {
this.lineItems.vaps = vapsItemsSelected;
},
servicePackageDiscountSelectedAction(servicePackageDiscountSelected) {
this.lineItems.supportingItems = servicePackageDiscountSelected;
},
backButtonAction() {
vehicleQuestionsMixin.methods.navigateBack(this);
},
@ -315,7 +336,11 @@ export default {
false
);
}
this.dispatchStoreAction(
this.storeActions.SAVE_SUPPORTING_ITEMS,
this.lineItems.supportingItems,
false
);
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.lineItems.vaps, false);
this.dispatchStoreAction(
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,

View file

@ -6,6 +6,12 @@ import {
mockProcessedCmsContent,
inputQuestionWidgetAnswers,
} 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";
jest.mock("@/store", () => ({
@ -45,7 +51,46 @@ describe("service-package-question.vue", () => {
partType: "RAIN DEFENSE",
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: [],
};
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", () => {
// Arrange
mockProps.isInsuranceSelected = true;

View file

@ -69,12 +69,34 @@ export default {
selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
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: {
nullSafeAvailableLineItems() {
return this.availableLineItems ?? [];
},
supportingLineItems() {
return this.$store.getters.lineItems?.supportingItems;
},
servicePackageAnswers() {
const cmsWidgetName = this.isInsuranceSelected
? this.insuranceCmsWidgetName
@ -99,10 +121,16 @@ export default {
buttonLabel: this.getHeaderTextFromCms(answer.SubWidgetName),
buttonLabelSubCopy: this.getSubheaderTextFromCms(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),
additionalButtonData: {
strikeThroughPrice: this.getPackagePriceString(answer.Name),
servicePackageDiscount:
this.isServicePackageDiscountOnOrder && answer.Text != "",
Text: answer.Text + this.getServicePackageDiscountPrice(),
},
}));
return modifiedAnswers;
@ -113,6 +141,12 @@ export default {
this.nullSafeAvailableLineItems
);
},
isServicePackageDiscountOnOrder() {
return containsLineItemWithPartType(
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
this.nullSafeAvailableLineItems
);
},
frontWipersApplicableForTierTwo() {
return shouldFrontWipersBeAvailable(
this.glassToReplace,
@ -159,6 +193,12 @@ export default {
isRepair() {
return this.$store.getters.order.damage.isRepair;
},
servicePackageDiscountParts() {
return findLineItemsWithPartType(
partTypeStrings.SERVICE_PACKAGE_DISCOUNT,
this.nullSafeAvailableLineItems
);
},
},
methods: {
processIfStatements,
@ -178,21 +218,35 @@ export default {
},
getPackagePriceString(packageName) {
const formattedPriceFloat = parseFloat(
this.getPackagePrice(packageName, { discountedPrice: false })
this.getPackagePrice(packageName, {
discountedPrice: false,
servicePackageDiscount: false,
})
).toFixed(2);
return "$" + formattedPriceFloat;
},
getDiscountedPackagePriceString(packageName) {
getDiscountedPackagePriceString(packageName, servicePackageDiscount) {
const formattedPriceFloat = parseFloat(
this.getPackagePrice(packageName, { discountedPrice: true })
this.getPackagePrice(packageName, { discountedPrice: true, servicePackageDiscount })
).toFixed(2);
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
},
getPackagePrice(packageName, { discountedPrice = false }) {
const lineItemsToPrice = [...this.nullSafeAvailableLineItems];
getPackagePrice(packageName, { discountedPrice = false, servicePackageDiscount = false }) {
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) {
lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos));
}
if (servicePackageDiscount) {
lineItemsToPrice.push(...this.servicePackageDiscountParts);
}
let priceFloat = this.isInsuranceSelected
? 0
: baseMixin.methods.getTierOnePackagePrice(
@ -203,9 +257,17 @@ export default {
return priceFloat;
},
getServicePackageDiscountPrice() {
if (this.isServicePackageDiscountOnOrder) {
let price = 0;
price += baseMixin.methods.getTotalLineItemPrice(
this.servicePackageDiscountParts[0]
);
return Math.abs(price);
}
},
getVapsPrice(packageName, applyPromoDiscounts = false) {
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
let price = 0;
vapsItems.forEach((item) => {
@ -261,6 +323,14 @@ export default {
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) {
const combinedLineItemArray = [...lineItemsTwo];
lineItemsOne.forEach((lineItemOne) => {

View file

@ -18,21 +18,6 @@ describe("service-package-radio.vue", () => {
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 () => {
// Arrange
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>",
buttonFooterCopy: "buttonFooterCopy test copy",
buttonAuxillaryCopy: "buttonAuxillaryCopy test copy",
additionalButtonData: { strikeThroughPrice: "strikeThroughPrice test copy" },
additionalButtonData: {
strikeThroughPrice: "strikeThroughPrice test copy",
servicePackageDiscount: true,
Text: "Special save",
},
};
function setupMocks({ mountOptionsMockData = {} }) {

View file

@ -1,24 +1,51 @@
<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">
<div
class="package-label"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
:class="[
this.buttonLabelSubCopy ? 'has-subheader' : '',
this.additionalButtonData.servicePackageDiscount ? 'has-text' : '',
]"
for="testradio">
<div class="package-specs">
<p class="m-0">
<span v-html="this.buttonLabel"></span>
<span class="pricing-info">
<span
class="strikethrough-price"
<div v-if="this.additionalButtonData.servicePackageDiscount" class="row">
<div class="col md-6">
<p class="m-0">
<span v-html="this.buttonLabel"></span>
</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-html="this.additionalButtonData.strikeThroughPrice"></span>
<span v-html="this.buttonAuxillaryCopy"></span>*
v-html="this.additionalButtonData.strikeThroughPrice"></div>
<div class="discount-price" v-html="this.buttonAuxillaryCopy"></div>
</span>
</p>
<p
class="sub-label m-0"
v-if="this.buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p>
</div>
<div v-else>
<p class="m-0">
<span v-html="this.buttonLabel"></span>
<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">
<!-- Parse the body text containing <ul> with logic -->
<ul>
@ -109,6 +136,18 @@ export default {
</script>
<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-wrapper {
margin: 0.5rem 0;
@ -139,6 +178,10 @@ export default {
&.has-subheader {
min-height: 80px;
}
&.has-text {
border-radius: 0 0 0.5rem 0.5rem;
border: 1px solid #469d2a !important;
}
&:before {
content: "";
@ -246,6 +289,28 @@ export default {
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 {
margin: 1rem 0 0 -15px;
padding: 0;

View file

@ -1422,6 +1422,26 @@ export const actions = {
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 }) {
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems?.map(