Merge branch 'develop' into feature/CSR-2091

This commit is contained in:
CarlNation 2024-06-18 06:59:29 -04:00
commit d85e375e1c
21 changed files with 590 additions and 59 deletions

View file

@ -21,7 +21,7 @@ body span {
line-height: 24px; line-height: 24px;
} }
body a { body a {
color: #1574A1; color: #0070D1;
text-decoration: none; text-decoration: none;
} }
body .form-group { body .form-group {

View file

@ -24,7 +24,7 @@ body {
} }
a { a {
color: #1574A1; color: #0070D1;
text-decoration: none; text-decoration: none;
} }

View file

@ -1,6 +1,3 @@
<svg version="1.1" id="SafeliteLogo" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 451.2 73.6" style="enable-background:new 0 0 451.2 73.6" xml:space="preserve"> <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 439.8 73.6" viewBox="0 0 439.8 73.6">
<style> <path d="M47.2 22.8c-.8 0-1.4-.6-1.4-1.4v-1.5c0-4.1-2.6-7.5-8.1-7.5-6.1 0-8.8 4.3-8.8 7.4 0 13.7 46.4 7 46.4 32.3 0 14.7-13.6 21.3-38.6 21.3C13.1 73.4 0 68.2 0 53.9v-1c0-.8.6-1.4 1.4-1.4H26c.8 0 1.4.6 1.4 1.4v1.5c0 5.9 3.8 8 9.3 8 5.8 0 9.2-4.2 9.2-8 0-13.7-44.5-6.9-44.5-31.4C1.4 9 13.1 1.4 37.1 1.4c23.8 0 34.5 6 35.2 19.8 0 .8-.6 1.5-1.4 1.5l-23.7.1zm106.4-7.1c0-9.8 6-13.4 23.8-13.4 3.3 0 7.3.1 11 .4.7.1 1.3.7 1.3 1.4v6c0 .8-.7 1.5-1.6 1.4-.8-.1-1.6-.1-2.7-.1-3.1 0-6.1 1-6.1 3.4v6.9c0 .8.6 1.4 1.4 1.4h7.6c.8 0 1.4.6 1.4 1.4v8.1c0 .8-.6 1.4-1.4 1.4h-7.6c-.8 0-1.4.6-1.4 1.4v35.2c0 .8-.6 1.4-1.4 1.4h-22.8c-.8 0-1.4-.6-1.4-1.4l-.1-54.9zM218 50.6c-.8 0-1.4.6-1.4 1.4v6.3c0 5.3 3.4 6.6 7 6.6 4.3 0 6.9-2.7 7.2-8.5 0-.7.7-1.3 1.4-1.3h20.2c.9 0 1.5.7 1.4 1.6-1 10.9-9.9 16.9-30.2 16.9-24.8 0-32.8-7.1-32.8-25.8 0-18.8 9.7-25.9 32.8-25.9 20.5 0 31 5.8 31 23.6v3.6c0 .8-.6 1.4-1.4 1.4l-35.2.1zm12.6-12.4c0-5.2-1.7-7.5-7.1-7.5-5.3 0-7 2.4-7 7.5v2.1c0 .8.6 1.4 1.4 1.4h11.3c.8 0 1.4-.6 1.4-1.4v-2.1zM265.2 0H288c.8 0 1.4.6 1.4 1.4v69.5c0 .8-.6 1.4-1.4 1.4h-22.8c-.8 0-1.4-.6-1.4-1.4V1.4c-.1-.8.6-1.4 1.4-1.4zM302 23.2h22.8c.8 0 1.4.6 1.4 1.4v46.3c0 .8-.6 1.4-1.4 1.4H302c-.8 0-1.4-.6-1.4-1.4V24.6c-.1-.8.6-1.4 1.4-1.4zM302 0h22.8c.8 0 1.4.6 1.4 1.4v12.1c0 .8-.6 1.4-1.4 1.4L302 15c-.8 0-1.4-.6-1.4-1.4V1.5c-.1-.9.6-1.5 1.4-1.5zm101.2 50.6c-.8 0-1.4.6-1.4 1.4v6.3c0 5.3 3.4 6.6 7 6.6 4.2 0 6.9-2.7 7.2-8.5 0-.7.7-1.3 1.4-1.3h20.2c.9 0 1.5.7 1.4 1.6-1 10.9-9.9 16.9-30.1 16.9-24.8 0-32.8-7.1-32.8-25.8 0-18.8 9.7-25.9 32.8-25.9 20.5 0 31 5.8 31 23.6v3.6c0 .8-.6 1.4-1.4 1.4l-35.3.1zm12.7-12.4c0-5.2-1.7-7.5-7.1-7.5-5.3 0-7 2.4-7 7.5v2.1c0 .8.6 1.4 1.4 1.4h11.3c.8 0 1.4-.6 1.4-1.4v-2.1zm-271.5-.5C144.3 25 131.8 22 113.9 22c-11.2 0-28.2 2.3-29.5 14.9-.1.8.6 1.6 1.4 1.6h19.4c.7 0 1.3-.6 1.4-1.3.5-4.3 2.8-6.5 7.2-6.5s6.5 1.5 6.5 4.5v4.4c0 .7-.5 1.3-1.2 1.4-1.2.2-2.9.4-6.3.9l-10.1 1.5c-16.4 2.4-21.4 6.7-21.4 15.8 0 8.4 6 14.4 21.4 14.4 7.3 0 13.9-2.2 17-6.2.2-.2.6-.2.6.1.3 1 .9 2.9 1.2 3.9.2.6.7.9 1.3.9h20c.8 0 1.4-.6 1.4-1.4l.2-33.2zm-32 26.3c-3.9 0-6.3-3.4-6.3-6.7 0-3.3 1.5-5.3 5.3-6.3 2.4-.7 5.5-1.1 7.4-1.4.9-.1 1.6.5 1.6 1.4v4.9c0 5.3-2.2 8.1-8 8.1zm251.8-29.7h7.3c.8 0 1.4-.6 1.4-1.4v-8.1c0-.8-.6-1.4-1.4-1.4h-7.3c-.8 0-1.4-.6-1.4-1.4l-.1-20.4v-.4c0-.6-.5-1.1-1.1-1.1-.2 0-.3 0-.4.1h-.1l-22.6 13.4c-1 .6-1.4.7-1.4 1.4v45.4c0 10.9 6.1 12.5 21.8 12.5 3.9 0 8.3-.2 12.7-.4.7-.1 1.3-.7 1.3-1.4v-6.8c0-.8-.6-1.4-1.4-1.4h-2.6c-4.1 0-6.1-1-6.1-3.8V35.8c0-.9.6-1.5 1.4-1.5z" style="fill:#dc1f26"/>
.st0{fill:#db0020}
</style>
<path class="st0" d="M439.8 67.9c0-3.1 2.6-5.7 5.7-5.7 3.2 0 5.7 2.6 5.7 5.7 0 3.2-2.5 5.7-5.7 5.7-3.1 0-5.7-2.5-5.7-5.7m10.5 0c0-2.6-2.1-4.7-4.7-4.7s-4.7 2.1-4.7 4.7 2.1 4.7 4.7 4.7c2.5 0 4.7-2.1 4.7-4.7m-6 .4v2.9h-1v-6.7h2.2c1.2 0 2.5.3 2.5 1.9 0 .8-.5 1.4-1.4 1.6.9.2 1 .6 1.1 1.3.1.6.2 1.3.4 1.9h-1.3c-.1-.4-.2-.8-.2-1.2-.1-.6-.1-1.1-.4-1.4-.3-.3-.6-.2-1-.3h-.9zm1.1-1.1c1 0 1.2-.4 1.2-1 0-.5-.2-.8-1.1-.8h-1.3v1.7h1.2zM47.2 22.8c-.8 0-1.4-.6-1.4-1.4v-1.5c0-4.1-2.6-7.5-8.1-7.5-6.1 0-8.8 4.3-8.8 7.4 0 13.7 46.4 7 46.4 32.3 0 14.7-13.6 21.3-38.6 21.3C13.1 73.4 0 68.2 0 53.9v-1c0-.8.6-1.4 1.4-1.4H26c.8 0 1.4.6 1.4 1.4v1.5c0 5.9 3.8 8 9.3 8 5.8 0 9.2-4.2 9.2-8 0-13.7-44.5-6.9-44.5-31.4C1.4 9 13.1 1.4 37.1 1.4c23.8 0 34.5 6 35.2 19.8 0 .8-.6 1.5-1.4 1.5l-23.7.1zM153.6 15.7c0-9.8 6-13.4 23.8-13.4 3.3 0 7.3.1 11 .4.7.1 1.3.7 1.3 1.4v6c0 .8-.7 1.5-1.6 1.4-.8-.1-1.6-.1-2.7-.1-3.1 0-6.1 1-6.1 3.4v6.9c0 .8.6 1.4 1.4 1.4h7.6c.8 0 1.4.6 1.4 1.4v8.1c0 .8-.6 1.4-1.4 1.4h-7.6c-.8 0-1.4.6-1.4 1.4v35.2c0 .8-.6 1.4-1.4 1.4h-22.8c-.8 0-1.4-.6-1.4-1.4l-.1-54.9zM218 50.6c-.8 0-1.4.6-1.4 1.4v6.3c0 5.3 3.4 6.6 7 6.6 4.3 0 6.9-2.7 7.2-8.5 0-.7.7-1.3 1.4-1.3h20.2c.9 0 1.5.7 1.4 1.6-1 10.9-9.9 16.9-30.2 16.9-24.8 0-32.8-7.1-32.8-25.8 0-18.8 9.7-25.9 32.8-25.9 20.5 0 31 5.8 31 23.6v3.6c0 .8-.6 1.4-1.4 1.4l-35.2.1zm12.6-12.4c0-5.2-1.7-7.5-7.1-7.5-5.3 0-7 2.4-7 7.5v2.1c0 .8.6 1.4 1.4 1.4h11.3c.8 0 1.4-.6 1.4-1.4v-2.1zM265.2 0H288c.8 0 1.4.6 1.4 1.4v69.5c0 .8-.6 1.4-1.4 1.4h-22.8c-.8 0-1.4-.6-1.4-1.4V1.4c-.1-.8.6-1.4 1.4-1.4zM302 23.2h22.8c.8 0 1.4.6 1.4 1.4v46.3c0 .8-.6 1.4-1.4 1.4H302c-.8 0-1.4-.6-1.4-1.4V24.6c-.1-.8.6-1.4 1.4-1.4zM302 0h22.8c.8 0 1.4.6 1.4 1.4v12.1c0 .8-.6 1.4-1.4 1.4L302 15c-.8 0-1.4-.6-1.4-1.4V1.5c-.1-.9.6-1.5 1.4-1.5zM403.2 50.6c-.8 0-1.4.6-1.4 1.4v6.3c0 5.3 3.4 6.6 7 6.6 4.2 0 6.9-2.7 7.2-8.5 0-.7.7-1.3 1.4-1.3h20.2c.9 0 1.5.7 1.4 1.6-1 10.9-9.9 16.9-30.1 16.9-24.8 0-32.8-7.1-32.8-25.8 0-18.8 9.7-25.9 32.8-25.9 20.5 0 31 5.8 31 23.6v3.6c0 .8-.6 1.4-1.4 1.4l-35.3.1zm12.7-12.4c0-5.2-1.7-7.5-7.1-7.5-5.3 0-7 2.4-7 7.5v2.1c0 .8.6 1.4 1.4 1.4h11.3c.8 0 1.4-.6 1.4-1.4v-2.1zM144.4 37.7C144.3 25 131.8 22 113.9 22c-11.2 0-28.2 2.3-29.5 14.9-.1.8.6 1.6 1.4 1.6h19.4c.7 0 1.3-.6 1.4-1.3.5-4.3 2.8-6.5 7.2-6.5s6.5 1.5 6.5 4.5v4.4c0 .7-.5 1.3-1.2 1.4-1.2.2-2.9.4-6.3.9l-10.1 1.5c-16.4 2.4-21.4 6.7-21.4 15.8 0 8.4 6 14.4 21.4 14.4 7.3 0 13.9-2.2 17-6.2.2-.2.6-.2.6.1.3 1 .9 2.9 1.2 3.9.2.6.7.9 1.3.9h20c.8 0 1.4-.6 1.4-1.4l.2-33.2zm-32 26.3c-3.9 0-6.3-3.4-6.3-6.7 0-3.3 1.5-5.3 5.3-6.3 2.4-.7 5.5-1.1 7.4-1.4.9-.1 1.6.5 1.6 1.4v4.9c0 5.3-2.2 8.1-8 8.1zM364.2 34.3h7.3c.8 0 1.4-.6 1.4-1.4v-8.1c0-.8-.6-1.4-1.4-1.4h-7.3c-.8 0-1.4-.6-1.4-1.4l-.1-20.4v-.4c0-.6-.5-1.1-1.1-1.1-.2 0-.3 0-.4.1h-.1l-22.6 13.4c-1 .6-1.4.7-1.4 1.4v45.4c0 10.9 6.1 12.5 21.8 12.5 3.9 0 8.3-.2 12.7-.4.7-.1 1.3-.7 1.3-1.4v-6.8c0-.8-.6-1.4-1.4-1.4h-2.6c-4.1 0-6.1-1-6.1-3.8V35.8c0-.9.6-1.5 1.4-1.5z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

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",
}, },
GetServicePackageDiscountPart: {
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",
GET_SERVICE_PACKAGE_DISCOUNT_PART: "getServicePackageDiscountPart",
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 ?? 0;
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" || category == "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,55 @@ 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;
const otherSupportingItems = this.supportingItems.filter((item) => {
return item.partType != partTypeStrings.SERVICE_PACKAGE_DISCOUNT;
});
// Don't include fees they are part of the package total // Don't include fees they are part of the package total
let otherSupportingItemsLineItems = baseMixin.methods.filterOutFees( let otherSupportingItemsLineItems =
this.supportingItems baseMixin.methods.filterOutFees(otherSupportingItems);
);
if (otherSupportingItemsLineItems.length > 0) { if (otherSupportingItemsLineItems.length > 0) {
cartItem = { cartItem = {

View file

@ -421,7 +421,7 @@ export default {
} }
.add-to-calendar-text { .add-to-calendar-text {
margin: 0 0 0 7px; margin: 0 0 0 7px;
color: #1574a1; color: #0070d1;
vertical-align: middle; vertical-align: middle;
font-weight: 500; font-weight: 500;
font-family: "Roboto"; font-family: "Roboto";

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,29 @@ 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.GET_SERVICE_PACKAGE_DISCOUNT_PART,
null,
"quote"
);
if (servicePackageDiscountPartResponse.data) {
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 +298,9 @@ export default {
vapsItemsSelectedAction(vapsItemsSelected) { vapsItemsSelectedAction(vapsItemsSelected) {
this.lineItems.vaps = vapsItemsSelected; this.lineItems.vaps = vapsItemsSelected;
}, },
servicePackageDiscountSelectedAction(supportingItemsSelected) {
this.lineItems.supportingItems = supportingItemsSelected;
},
backButtonAction() { backButtonAction() {
vehicleQuestionsMixin.methods.navigateBack(this); vehicleQuestionsMixin.methods.navigateBack(this);
}, },
@ -315,7 +338,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,17 @@ 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), //The package supports service-package-discount if it has the text value in CMS
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 +142,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 +194,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 +219,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 +258,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 +324,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

@ -158,12 +158,17 @@ export default {
includeTax includeTax
); );
} }
const order = store.getters.hasSubmittedOrder
? store.getters.submittedOrder
: store.getters.order;
if ( if (
store.getters.payment.insuranceCoverage.isVerified && order.payment.insuranceCoverage.isVerified &&
!store.getters.policy.isNoComp && !order.policy.isNoComp &&
!store.getters.policy.isItac !order.policy.isItac
) { ) {
amountDue += store.getters.policy.currentDeductible; amountDue += order.policy.currentDeductible;
} }
return ((amountDue * 100) / 100).toFixed(2); return ((amountDue * 100) / 100).toFixed(2);

View file

@ -175,9 +175,66 @@ describe("baseMixin.js", () => {
promos: [], promos: [],
}; };
const payment = { insuranceCoverage: { isVerified: true } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
store.getters = { store.getters = {
payment: { insuranceCoverage: { isVerified: true } }, payment: payment,
policy: { isNoComp: false, isItac: false, currentDeductible: 250 }, policy: policy,
order: {
payment: payment,
policy: policy,
},
hasSubmittedOrder: false,
};
var amtDue = mixIn.methods.getAmountDue(lineItems);
expect(amtDue).toEqual("303.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: true } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 250 };
store.getters = {
payment: {},
policy: {},
order: {},
submittedOrder: {
payment: payment,
policy: policy,
},
hasSubmittedOrder: true,
}; };
var amtDue = mixIn.methods.getAmountDue(lineItems); var amtDue = mixIn.methods.getAmountDue(lineItems);
@ -241,9 +298,17 @@ describe("baseMixin.js", () => {
promos: [], promos: [],
}; };
const payment = { isInsurance: null, insuranceCoverage: { isVerified: null } };
const policy = { isNoComp: false, isItac: false, currentDeductible: 0 };
store.getters = { store.getters = {
payment: { isInsurance: null, insuranceCoverage: { isVerified: null } }, payment: payment,
policy: { isNoComp: false, isItac: false, currentDeductible: 0 }, policy: policy,
order: {
payment: payment,
policy: policy,
},
hasSubmittedOrder: false,
}; };
var amtDue = mixIn.methods.getAmountDue(lineItems); var amtDue = mixIn.methods.getAmountDue(lineItems);

View file

@ -1422,6 +1422,26 @@ export const actions = {
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
}); });
}, },
getServicePackageDiscountPart(context, { pageNameToLog }) {
const damage = context.getters.damage;
const damageType = damage.isRepair ? "Repair" : "Replace";
const glassPieces = convertGlassPieceNamingForApi(damage.glassToReplace);
return globalMethods
.callHttpClient({
method: endpoints.GetServicePackageDiscountPart.method,
endpoint: endpoints.GetServicePackageDiscountPart.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(