Merge pull request #1832 from Safelite/feature/CSR-1742-ajc

Feature/CSR-1742-ajc
This commit is contained in:
AdamCaouetteSafelite 2024-04-24 09:49:06 -04:00 committed by GitHub
commit 55f7e76c11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 230 additions and 32 deletions

View file

@ -16,3 +16,5 @@ export function coverageStatusValue(intCoverageStatus) {
return null; return null;
} }
} }
export { coverageStatus };

View file

@ -5,6 +5,13 @@ import cart from "@/fmg-components/cart/cart.vue";
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
global.$logger = {
logInformation: jest.fn(),
logWarning: jest.fn(),
logError: jest.fn(),
logCritical: jest.fn(),
};
describe("cart.vue", () => { describe("cart.vue", () => {
describe("cartItem computeds", () => { describe("cartItem computeds", () => {
test("if there are one or more front wipers on the order, a front wiper cart item should be added to the cart", () => { test("if there are one or more front wipers on the order, a front wiper cart item should be added to the cart", () => {

View file

@ -10,11 +10,28 @@
href="javascript:void(0)" href="javascript:void(0)"
class="col d-flex justify-content-between py-0"> class="col d-flex justify-content-between py-0">
<span class="label">{{ amountDueText }}</span> <span class="label">{{ amountDueText }}</span>
<span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span> <span class="label amount-due">{{
isCoverageTypeToDisplayPending
? verifyingCoverageText
: getFormattedAmount("", amountDue)
}}</span>
</a> </a>
</div> </div>
<div class="cart-panel"> <div class="cart-panel">
<div class="price-table"> <div class="price-table">
<!-- Deductible, if applicable -->
<div v-if="deductibleLineItem" class="deductible">
<textBlock cmsWidgetName="DeductibleTextWidget" />
<span>
{{
isCoverageTypeToDisplayPending
? verifyingCoverageText
: getFormattedAmount("", deductibleLineItem.subTotal)
}}
</span>
</div>
<!-- Service Type -->
<div class="service-type"> <div class="service-type">
<textBlock :cmsWidgetName="servicePackageTitleWidget" /> <textBlock :cmsWidgetName="servicePackageTitleWidget" />
<span> <span>
@ -78,27 +95,44 @@
<span v-else-if="cartItem == recycleFeeCartItem"> <span v-else-if="cartItem == recycleFeeCartItem">
{{ recycleFeeCartItem.name }} {{ recycleFeeCartItem.name }}
</span> </span>
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span> <span
v-if="
!isCoverageTypeToDisplayVerified ||
(isCoverageTypeToDisplayVerified && !cartItem.isCoveredByInsurance)
"
>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span
>
</div> </div>
<!-- Sub total, sales tax, total columns --> <!-- Sub total, sales tax, total columns -->
<div class="sub-total"> <div class="sub-total">
<span>{{ subtotalText }}</span <span>{{ subtotalText }}</span>
><span>{{ getFormattedAmount("", subTotal) }}</span> <span>{{
isCoverageTypeToDisplayPending
? verifyingCoverageText
: getFormattedAmount("", subTotal)
}}</span>
</div> </div>
<div class="sales-tax"> <div class="sales-tax">
<span>{{ salesTaxText }}</span <span>{{ salesTaxText }}</span>
><span>{{ getFormattedAmount("", salesTax) }}</span> <span>{{ getFormattedAmount("", salesTax) }}</span>
</div> </div>
<div v-if="showAsPaid" class="amount-paid"> <div v-if="showAsPaid" class="amount-paid">
<span>{{ amountPaidText }}</span <span>{{ amountPaidText }}</span
><span>{{ getFormattedAmount("", amountPaid) }}</span> ><span>{{ getFormattedAmount("", amountPaid) }}</span>
</div> </div>
<div class="amount-due"> <div class="amount-due">
<span>{{ amountDueText }}</span <span>{{ amountDueText }}</span>
><span>{{ getFormattedAmount("", amountDue) }}</span> <span>{{
isCoverageTypeToDisplayPending
? verifyingCoverageText
: getFormattedAmount("", amountDue)
}}</span>
</div> </div>
</div> </div>
<div v-if="insuranceCompanyName" class="insurance-company-name caption">
{{ insuranceCompanyName }}
</div>
<promoModalQuestion <promoModalQuestion
ref="promoModalQuestion" ref="promoModalQuestion"
class="small mt-4" class="small mt-4"
@ -141,6 +175,7 @@ import { getPromoCodeWithoutBundleIdentifier } from "@/helpers/promotions-helper
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { cartItemCategories } from "@/constants/cart-item-categories"; import { cartItemCategories } from "@/constants/cart-item-categories";
import { cartItemTypes } from "@/constants/cart-item-types"; import { cartItemTypes } from "@/constants/cart-item-types";
import { coverageStatus } from "@/constants/coverage-status";
export default { export default {
name: "cart", name: "cart",
@ -153,6 +188,9 @@ export default {
allowItemRemoval: Boolean, allowItemRemoval: Boolean,
recyclingModalCmsWidgetName: String, recyclingModalCmsWidgetName: String,
showAsPaid: Boolean, showAsPaid: Boolean,
insuranceDeductible: Number,
insuranceCompanyName: String,
insuranceCoverageTypeToDisplay: String,
}, },
data() { data() {
return { return {
@ -255,6 +293,19 @@ export default {
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
}, },
}, },
isCoverageTypeToDisplayPending() {
return this.insuranceCoverageTypeToDisplay === coverageStatus.PENDING;
},
isCoverageTypeToDisplayVerified() {
return this.insuranceCoverageTypeToDisplay === coverageStatus.VERIFIED;
},
currentDeductible() {
const deductible = this.insuranceDeductible;
if (!(deductible === 0 || deductible > 0)) {
global.$logger.logError("Error: currentDeductible should not be null");
}
return deductible;
},
screenReaderTotalAmountDueText() { screenReaderTotalAmountDueText() {
return this.getCmsContent("ScreenReaderTotalAmountDueWidget", "Text"); return this.getCmsContent("ScreenReaderTotalAmountDueWidget", "Text");
}, },
@ -364,11 +415,13 @@ export default {
return tier; return tier;
}, },
packagePrice() { packagePrice() {
let packagePrice = this.isInsuranceSelected let packagePrice = 0;
? 0
: baseMixin.methods.getTierOnePackagePrice( if (!this.isCoverageTypeToDisplayVerified) {
baseMixin.methods.filterOutFees(this.availableLineItems) packagePrice = baseMixin.methods.getTierOnePackagePrice(
); baseMixin.methods.filterOutFees(this.availableLineItems)
);
}
packagePrice += this.getVapsPrice(this.packageLevel); packagePrice += this.getVapsPrice(this.packageLevel);
@ -410,7 +463,25 @@ export default {
const promos = this.lineItems?.promos; const promos = this.lineItems?.promos;
return promos?.length > 0 ? promos : []; return promos?.length > 0 ? promos : [];
}, },
// Cart Items deductibleLineItemName() {
return this.getCmsContent("DeductibleTextWidget", "Text");
},
deductibleLineItem() {
let lineItem = null;
if (this.isCoverageTypeToDisplayVerified || this.isCoverageTypeToDisplayPending) {
lineItem = {
name: this.deductibleLineItemName,
subTotal: this.currentDeductible,
};
}
return lineItem;
},
verifyingCoverageText() {
return this.getCmsContent("VerifyingCoverageTextWidget", "Text");
},
// Cart Item Groupings
packageCartItems() { packageCartItems() {
return this.getVapsCartItemsForSelectedPackage(this.packageLevel); return this.getVapsCartItemsForSelectedPackage(this.packageLevel);
}, },
@ -426,6 +497,7 @@ export default {
}); });
return nonpackageCartItems; return nonpackageCartItems;
}, },
// Individual Cart Items
frontWiperCartItemName() { frontWiperCartItemName() {
return this.getCmsContentForVapsType(partTypeStrings.FRONT_WIPER); return this.getCmsContentForVapsType(partTypeStrings.FRONT_WIPER);
}, },
@ -446,6 +518,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: false,
}; };
frontWiperLineItems.forEach((lineItem) => { frontWiperLineItems.forEach((lineItem) => {
@ -483,6 +556,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: false,
}; };
rearWiperLineItems.forEach((lineItem) => { rearWiperLineItems.forEach((lineItem) => {
@ -520,6 +594,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: false,
}; };
rainDefenseLineItem.cartItemType = cartItem.cartItemType; rainDefenseLineItem.cartItemType = cartItem.cartItemType;
@ -553,6 +628,7 @@ export default {
salesTax: lineItem.salesTax ?? 0, salesTax: lineItem.salesTax ?? 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: true,
}; };
if (lineItem.childParts?.length > 0) { if (lineItem.childParts?.length > 0) {
@ -592,6 +668,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: true,
}; };
recycleFeeLineItem.cartItemType = cartItem.cartItemType; recycleFeeLineItem.cartItemType = cartItem.cartItemType;
@ -629,6 +706,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: true,
}; };
suppliesRepairLineItem.cartItemType = cartItem.cartItemType; suppliesRepairLineItem.cartItemType = cartItem.cartItemType;
@ -664,6 +742,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: false,
}; };
mobileFeeLineItem.cartItemType = cartItem.cartItemType; mobileFeeLineItem.cartItemType = cartItem.cartItemType;
@ -697,6 +776,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: true,
}; };
otherSupportingItemsLineItems.forEach((lineItem) => { otherSupportingItemsLineItems.forEach((lineItem) => {
@ -715,7 +795,7 @@ export default {
return cartItem; return cartItem;
}, },
premiumAppointmentDiscountCartItemName() { premiumAppointmentDiscountCartItemName() {
return "Early bird appointment"; // TODO: get from CMS return this.getCmsContent("PremiumAppointmentTextWidget", "Text");
}, },
premiumAppointmentDiscountCartItem() { premiumAppointmentDiscountCartItem() {
let cartItem = null; let cartItem = null;
@ -734,6 +814,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: false,
}; };
premiumAppointmentDiscountLineItem.cartItemType = cartItem.cartItemType; premiumAppointmentDiscountLineItem.cartItemType = cartItem.cartItemType;
@ -779,6 +860,7 @@ export default {
subTotal: 0, subTotal: 0,
salesTax: 0, salesTax: 0,
lineItems: [], lineItems: [],
isCoveredByInsurance: false,
}; };
promoLineItems.forEach((promoLineItem) => { promoLineItems.forEach((promoLineItem) => {
@ -820,17 +902,38 @@ export default {
subTotal() { subTotal() {
let subTotal = 0; let subTotal = 0;
this.cartItems.forEach((cartItem) => { if (this.isCoverageTypeToDisplayVerified) {
subTotal += cartItem.subTotal; // only include items NOT covered by insurance
}); this.cartItems.forEach((cartItem) => {
if (!cartItem.isCoveredByInsurance) {
subTotal += cartItem.subTotal ?? 0;
}
});
// add deductible amount still to pay
subTotal += this.deductibleLineItem.subTotal ?? 0;
} else {
this.cartItems.forEach((cartItem) => {
subTotal += cartItem.subTotal ?? 0;
});
}
return subTotal; return subTotal;
}, },
salesTax() { salesTax() {
let salesTax = 0; let salesTax = 0;
this.cartItems.forEach((cartItem) => {
salesTax += cartItem.salesTax ?? 0; if (this.isCoverageTypeToDisplayVerified) {
}); // only include items NOT covered by insurance
this.cartItems.forEach((cartItem) => {
if (!cartItem.isCoveredByInsurance) {
salesTax += cartItem.salesTax ?? 0;
}
});
} else {
this.cartItems.forEach((cartItem) => {
salesTax += cartItem.salesTax ?? 0;
});
}
return salesTax; return salesTax;
}, },
@ -893,7 +996,7 @@ export default {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 0.5rem 1.5rem; padding: 0.5rem 1.5rem;
&:nth-last-child(-n + 4) { &:nth-last-child(-n + 3) {
background-color: $green-100; background-color: $green-100;
} }
&:last-child { &:last-child {
@ -901,16 +1004,26 @@ export default {
color: #ffffff; color: #ffffff;
} }
} }
.deductible {
background-color: $gray-100;
}
.service-type { .service-type {
background-color: $gray-100; background-color: $gray-100;
align-items: center; }
.deductible ~ .service-type {
background: $white;
}
.service-type ~ .packaged-cart-item {
background-color: $gray-100;
}
.deductible ~ .packaged-cart-item {
background: $white;
} }
.struck-out-price { .struck-out-price {
text-decoration: line-through; text-decoration: line-through;
padding-left: 0.5rem; padding-left: 0.5rem;
} }
.packaged-cart-item { .packaged-cart-item {
background-color: $gray-100;
padding-left: 2.5rem; padding-left: 2.5rem;
align-items: center; align-items: center;
span { span {
@ -937,6 +1050,11 @@ export default {
background: $gray-100; background: $gray-100;
} }
} }
.deductible ~ .non-packaged-cart-item {
&.odd {
background: $white;
}
}
.non-packaged-cart-item { .non-packaged-cart-item {
&.even { &.even {
background-color: $white; background-color: $white;
@ -945,6 +1063,11 @@ export default {
line-height: 1.625; line-height: 1.625;
} }
} }
.deductible ~ .non-packaged-cart-item {
&.even {
background-color: $gray-100;
}
}
// Custom order/wrap for removable nonpackaged Cart Items // Custom order/wrap for removable nonpackaged Cart Items
.removable-cart-item { .removable-cart-item {
flex-wrap: wrap; flex-wrap: wrap;
@ -959,8 +1082,8 @@ export default {
} }
} }
.package-type, .package-type,
.service-type { .service-type,
//background-color: #f5f5f5; .deductible {
align-items: center; align-items: center;
} }
.sub-total, .sub-total,
@ -973,15 +1096,19 @@ export default {
.sub-total { .sub-total {
border-top: 1px solid $green; border-top: 1px solid $green;
} }
.service-type { .service-type,
// background-color: #f5f5f5; .deductible {
.text-block { .text-block {
background: transparent; background: transparent;
margin-top: 0; margin-top: 0;
padding: 0; padding: 0;
width: 50%;
} }
} }
} }
.insurance-company-name {
margin-top: 0.5rem;
}
.vin-toggle { .vin-toggle {
&:after { &:after {
content: ""; content: "";

View file

@ -53,7 +53,7 @@ export default {
return this.leftAlignHeader ? "justify-content-start" : "justify-content-center"; return this.leftAlignHeader ? "justify-content-start" : "justify-content-center";
}, },
subText() { subText() {
return this.getCmsContent(this.cmsWidgetName, "HeaderSubText"); return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
}, },
backButtonAccessibleText() { backButtonAccessibleText() {
return this.getCmsContent(this.cmsWidgetName, "BackButtonAccessibleText"); return this.getCmsContent(this.cmsWidgetName, "BackButtonAccessibleText");

View file

@ -53,6 +53,12 @@ function setupMocks() {
isPia: false, isPia: false,
}, },
}, },
policy: {
currentDeductible: 123,
},
payment: {
isInsurance: false,
},
}; };
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({

View file

@ -28,7 +28,10 @@
v-model="lineItems" v-model="lineItems"
pageName="payment-method" pageName="payment-method"
servicePackageOptionsCmsName="ServicePackageTitle" servicePackageOptionsCmsName="ServicePackageTitle"
recyclingModalCmsWidgetName="RecycleModal" /> recyclingModalCmsWidgetName="RecycleModal"
:insuranceDeductible="currentDeductible"
:insuranceCompanyName="insuranceCompanyName"
:insuranceCoverageTypeToDisplay="insuranceCoverageTypeToDisplay" />
<hr class="my-5" /> <hr class="my-5" />
@ -110,6 +113,7 @@ import { errorMessages } from "@/constants/error-messages";
import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { mapTaxedLineItemsToStoreFormat } from "../../store"; import { mapTaxedLineItemsToStoreFormat } from "../../store";
import { coverageStatus } from "@/constants/coverage-status";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
@ -331,7 +335,21 @@ export default {
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs); (isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
// Insurance // Insurance
const isInsuranceSet = store.getters.order.payment.isInsurance !== null; const isInsurance = store.getters.order.payment.isInsurance;
const insuranceCoverageStatus = store.getters.payment.insuranceCoverage.coverageStatus;
const currentDeductible = store.getters.policy.currentDeductible;
const isInsuranceSet = () => {
if (isInsurance !== null) {
if (
insuranceCoverageStatus === coverageStatus.VERIFIED &&
!(currentDeductible === 0 || currentDeductible > 0)
) {
return false; // if coverageStatus is verified there must be a valid deductible also
}
return true;
}
return false;
};
// Schedule // Schedule
const schedule = store.getters.order.schedule; const schedule = store.getters.order.schedule;
@ -496,6 +514,32 @@ export default {
damageInfo() { damageInfo() {
return this.$store.getters.damage; return this.$store.getters.damage;
}, },
isInsurance() {
return this.$store.getters.payment.isInsurance;
},
isNoComp() {
return this.$store.getters.policy.isNoComp;
},
hasVapsInCart() {
if (this.lineItems?.vaps?.length > 0) {
return true;
}
return false;
},
insuranceCoverageTypeToDisplay() {
if (!this.isInsurance) return null;
if (this.isNoComp) {
return coverageStatus.NOCOMP;
} else {
return this.$store.getters.payment.insuranceCoverage.coverageStatus;
}
},
currentDeductible() {
return this.$store.getters.policy.currentDeductible;
},
insuranceCompanyName() {
return this.$store.getters.policy.insuranceCompanyName;
},
noPiaDisclaimer() { noPiaDisclaimer() {
return this.getCmsContent("NoPiaDisclaimerWidget", "Text"); return this.getCmsContent("NoPiaDisclaimerWidget", "Text");
}, },

View file

@ -33,7 +33,7 @@ import {
} from "@/helpers/promotions-helper"; } from "@/helpers/promotions-helper";
import { getDateDifferenceInDays } from "@/helpers/date-helper"; import { getDateDifferenceInDays } from "@/helpers/date-helper";
import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations"; import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations";
import { coverageStatusValue } from "@/constants/coverage-status"; import { coverageStatus, coverageStatusValue } from "@/constants/coverage-status";
// Export State // Export State
const getDefaultState = () => { const getDefaultState = () => {
return { return {
@ -763,6 +763,18 @@ export const getters = {
.reduce((r, c) => Object.assign(r, c), {}) ?? {}, .reduce((r, c) => Object.assign(r, c), {}) ?? {},
submittedOrder: (state) => JSON.parse(window.sessionStorage.getItem("submittedOrder")), submittedOrder: (state) => JSON.parse(window.sessionStorage.getItem("submittedOrder")),
hasSubmittedOrder: (state) => window.sessionStorage.getItem("submittedOrder") !== null, hasSubmittedOrder: (state) => window.sessionStorage.getItem("submittedOrder") !== null,
isVerifiedAndDeductibleZeroConfirmed: (state) => {
// used as a CMS variable on /payment-method, needed for FunnelSubHeaderWidget on cart pages
if (
(state.order.policy.currentDeductible === 0 ||
state.order.policy.currentDeductible > 0) &&
state.order.payment.insuranceCoverage.coverageStatus === coverageStatus.VERIFIED &&
!state.order.policy.isNoComp
) {
return true;
}
return false;
},
}; };
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {