Merge pull request #1832 from Safelite/feature/CSR-1742-ajc
Feature/CSR-1742-ajc
This commit is contained in:
commit
55f7e76c11
7 changed files with 230 additions and 32 deletions
|
|
@ -16,3 +16,5 @@ export function coverageStatusValue(intCoverageStatus) {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export { coverageStatus };
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ import cart from "@/fmg-components/cart/cart.vue";
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
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("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", () => {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,28 @@
|
|||
href="javascript:void(0)"
|
||||
class="col d-flex justify-content-between py-0">
|
||||
<span class="label">{{ amountDueText }}</span>
|
||||
<span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span>
|
||||
<span class="label amount-due">{{
|
||||
isCoverageTypeToDisplayPending
|
||||
? verifyingCoverageText
|
||||
: getFormattedAmount("", amountDue)
|
||||
}}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="cart-panel">
|
||||
<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">
|
||||
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
|
||||
<span>
|
||||
|
|
@ -78,27 +95,44 @@
|
|||
<span v-else-if="cartItem == recycleFeeCartItem">
|
||||
{{ recycleFeeCartItem.name }}
|
||||
</span>
|
||||
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
|
||||
<span
|
||||
v-if="
|
||||
!isCoverageTypeToDisplayVerified ||
|
||||
(isCoverageTypeToDisplayVerified && !cartItem.isCoveredByInsurance)
|
||||
"
|
||||
>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Sub total, sales tax, total columns -->
|
||||
<div class="sub-total">
|
||||
<span>{{ subtotalText }}</span
|
||||
><span>{{ getFormattedAmount("", subTotal) }}</span>
|
||||
<span>{{ subtotalText }}</span>
|
||||
<span>{{
|
||||
isCoverageTypeToDisplayPending
|
||||
? verifyingCoverageText
|
||||
: getFormattedAmount("", subTotal)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="sales-tax">
|
||||
<span>{{ salesTaxText }}</span
|
||||
><span>{{ getFormattedAmount("", salesTax) }}</span>
|
||||
<span>{{ salesTaxText }}</span>
|
||||
<span>{{ getFormattedAmount("", salesTax) }}</span>
|
||||
</div>
|
||||
<div v-if="showAsPaid" class="amount-paid">
|
||||
<span>{{ amountPaidText }}</span
|
||||
><span>{{ getFormattedAmount("", amountPaid) }}</span>
|
||||
</div>
|
||||
<div class="amount-due">
|
||||
<span>{{ amountDueText }}</span
|
||||
><span>{{ getFormattedAmount("", amountDue) }}</span>
|
||||
<span>{{ amountDueText }}</span>
|
||||
<span>{{
|
||||
isCoverageTypeToDisplayPending
|
||||
? verifyingCoverageText
|
||||
: getFormattedAmount("", amountDue)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="insuranceCompanyName" class="insurance-company-name caption">
|
||||
{{ insuranceCompanyName }}
|
||||
</div>
|
||||
<promoModalQuestion
|
||||
ref="promoModalQuestion"
|
||||
class="small mt-4"
|
||||
|
|
@ -141,6 +175,7 @@ import { getPromoCodeWithoutBundleIdentifier } from "@/helpers/promotions-helper
|
|||
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||
import { cartItemCategories } from "@/constants/cart-item-categories";
|
||||
import { cartItemTypes } from "@/constants/cart-item-types";
|
||||
import { coverageStatus } from "@/constants/coverage-status";
|
||||
|
||||
export default {
|
||||
name: "cart",
|
||||
|
|
@ -153,6 +188,9 @@ export default {
|
|||
allowItemRemoval: Boolean,
|
||||
recyclingModalCmsWidgetName: String,
|
||||
showAsPaid: Boolean,
|
||||
insuranceDeductible: Number,
|
||||
insuranceCompanyName: String,
|
||||
insuranceCoverageTypeToDisplay: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -255,6 +293,19 @@ export default {
|
|||
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() {
|
||||
return this.getCmsContent("ScreenReaderTotalAmountDueWidget", "Text");
|
||||
},
|
||||
|
|
@ -364,11 +415,13 @@ export default {
|
|||
return tier;
|
||||
},
|
||||
packagePrice() {
|
||||
let packagePrice = this.isInsuranceSelected
|
||||
? 0
|
||||
: baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(this.availableLineItems)
|
||||
);
|
||||
let packagePrice = 0;
|
||||
|
||||
if (!this.isCoverageTypeToDisplayVerified) {
|
||||
packagePrice = baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(this.availableLineItems)
|
||||
);
|
||||
}
|
||||
|
||||
packagePrice += this.getVapsPrice(this.packageLevel);
|
||||
|
||||
|
|
@ -410,7 +463,25 @@ export default {
|
|||
const promos = this.lineItems?.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() {
|
||||
return this.getVapsCartItemsForSelectedPackage(this.packageLevel);
|
||||
},
|
||||
|
|
@ -426,6 +497,7 @@ export default {
|
|||
});
|
||||
return nonpackageCartItems;
|
||||
},
|
||||
// Individual Cart Items
|
||||
frontWiperCartItemName() {
|
||||
return this.getCmsContentForVapsType(partTypeStrings.FRONT_WIPER);
|
||||
},
|
||||
|
|
@ -446,6 +518,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: false,
|
||||
};
|
||||
|
||||
frontWiperLineItems.forEach((lineItem) => {
|
||||
|
|
@ -483,6 +556,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: false,
|
||||
};
|
||||
|
||||
rearWiperLineItems.forEach((lineItem) => {
|
||||
|
|
@ -520,6 +594,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: false,
|
||||
};
|
||||
|
||||
rainDefenseLineItem.cartItemType = cartItem.cartItemType;
|
||||
|
|
@ -553,6 +628,7 @@ export default {
|
|||
|
||||
salesTax: lineItem.salesTax ?? 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: true,
|
||||
};
|
||||
|
||||
if (lineItem.childParts?.length > 0) {
|
||||
|
|
@ -592,6 +668,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: true,
|
||||
};
|
||||
|
||||
recycleFeeLineItem.cartItemType = cartItem.cartItemType;
|
||||
|
|
@ -629,6 +706,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: true,
|
||||
};
|
||||
|
||||
suppliesRepairLineItem.cartItemType = cartItem.cartItemType;
|
||||
|
|
@ -664,6 +742,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: false,
|
||||
};
|
||||
|
||||
mobileFeeLineItem.cartItemType = cartItem.cartItemType;
|
||||
|
|
@ -697,6 +776,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: true,
|
||||
};
|
||||
|
||||
otherSupportingItemsLineItems.forEach((lineItem) => {
|
||||
|
|
@ -715,7 +795,7 @@ export default {
|
|||
return cartItem;
|
||||
},
|
||||
premiumAppointmentDiscountCartItemName() {
|
||||
return "Early bird appointment"; // TODO: get from CMS
|
||||
return this.getCmsContent("PremiumAppointmentTextWidget", "Text");
|
||||
},
|
||||
premiumAppointmentDiscountCartItem() {
|
||||
let cartItem = null;
|
||||
|
|
@ -734,6 +814,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: false,
|
||||
};
|
||||
|
||||
premiumAppointmentDiscountLineItem.cartItemType = cartItem.cartItemType;
|
||||
|
|
@ -779,6 +860,7 @@ export default {
|
|||
subTotal: 0,
|
||||
salesTax: 0,
|
||||
lineItems: [],
|
||||
isCoveredByInsurance: false,
|
||||
};
|
||||
|
||||
promoLineItems.forEach((promoLineItem) => {
|
||||
|
|
@ -820,17 +902,38 @@ export default {
|
|||
subTotal() {
|
||||
let subTotal = 0;
|
||||
|
||||
this.cartItems.forEach((cartItem) => {
|
||||
subTotal += cartItem.subTotal;
|
||||
});
|
||||
if (this.isCoverageTypeToDisplayVerified) {
|
||||
// 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;
|
||||
},
|
||||
salesTax() {
|
||||
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;
|
||||
},
|
||||
|
|
@ -893,7 +996,7 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1.5rem;
|
||||
&:nth-last-child(-n + 4) {
|
||||
&:nth-last-child(-n + 3) {
|
||||
background-color: $green-100;
|
||||
}
|
||||
&:last-child {
|
||||
|
|
@ -901,16 +1004,26 @@ export default {
|
|||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.deductible {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
.service-type {
|
||||
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 {
|
||||
text-decoration: line-through;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
.packaged-cart-item {
|
||||
background-color: $gray-100;
|
||||
padding-left: 2.5rem;
|
||||
align-items: center;
|
||||
span {
|
||||
|
|
@ -937,6 +1050,11 @@ export default {
|
|||
background: $gray-100;
|
||||
}
|
||||
}
|
||||
.deductible ~ .non-packaged-cart-item {
|
||||
&.odd {
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
.non-packaged-cart-item {
|
||||
&.even {
|
||||
background-color: $white;
|
||||
|
|
@ -945,6 +1063,11 @@ export default {
|
|||
line-height: 1.625;
|
||||
}
|
||||
}
|
||||
.deductible ~ .non-packaged-cart-item {
|
||||
&.even {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
}
|
||||
// Custom order/wrap for removable nonpackaged Cart Items
|
||||
.removable-cart-item {
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -959,8 +1082,8 @@ export default {
|
|||
}
|
||||
}
|
||||
.package-type,
|
||||
.service-type {
|
||||
//background-color: #f5f5f5;
|
||||
.service-type,
|
||||
.deductible {
|
||||
align-items: center;
|
||||
}
|
||||
.sub-total,
|
||||
|
|
@ -973,15 +1096,19 @@ export default {
|
|||
.sub-total {
|
||||
border-top: 1px solid $green;
|
||||
}
|
||||
.service-type {
|
||||
// background-color: #f5f5f5;
|
||||
.service-type,
|
||||
.deductible {
|
||||
.text-block {
|
||||
background: transparent;
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.insurance-company-name {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.vin-toggle {
|
||||
&:after {
|
||||
content: "";
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
return this.leftAlignHeader ? "justify-content-start" : "justify-content-center";
|
||||
},
|
||||
subText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderSubText");
|
||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||
},
|
||||
backButtonAccessibleText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BackButtonAccessibleText");
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ function setupMocks() {
|
|||
isPia: false,
|
||||
},
|
||||
},
|
||||
policy: {
|
||||
currentDeductible: 123,
|
||||
},
|
||||
payment: {
|
||||
isInsurance: false,
|
||||
},
|
||||
};
|
||||
|
||||
const mountOptions = getMountOptions({
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@
|
|||
v-model="lineItems"
|
||||
pageName="payment-method"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
recyclingModalCmsWidgetName="RecycleModal" />
|
||||
recyclingModalCmsWidgetName="RecycleModal"
|
||||
:insuranceDeductible="currentDeductible"
|
||||
:insuranceCompanyName="insuranceCompanyName"
|
||||
:insuranceCoverageTypeToDisplay="insuranceCoverageTypeToDisplay" />
|
||||
|
||||
<hr class="my-5" />
|
||||
|
||||
|
|
@ -110,6 +113,7 @@ import { errorMessages } from "@/constants/error-messages";
|
|||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||
import { mapTaxedLineItemsToStoreFormat } from "../../store";
|
||||
import { coverageStatus } from "@/constants/coverage-status";
|
||||
|
||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
|
|
@ -331,7 +335,21 @@ export default {
|
|||
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
||||
|
||||
// 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
|
||||
const schedule = store.getters.order.schedule;
|
||||
|
|
@ -496,6 +514,32 @@ export default {
|
|||
damageInfo() {
|
||||
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() {
|
||||
return this.getCmsContent("NoPiaDisclaimerWidget", "Text");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
} from "@/helpers/promotions-helper";
|
||||
import { getDateDifferenceInDays } from "@/helpers/date-helper";
|
||||
import sharedMutations from "./vuex-shared-mutations/vuexSharedMutations";
|
||||
import { coverageStatusValue } from "@/constants/coverage-status";
|
||||
import { coverageStatus, coverageStatusValue } from "@/constants/coverage-status";
|
||||
// Export State
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
|
|
@ -763,6 +763,18 @@ export const getters = {
|
|||
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
|
||||
submittedOrder: (state) => JSON.parse(window.sessionStorage.getItem("submittedOrder")),
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue