Merge pull request #1970 from Safelite/feature/CSR-2179-ajc
Feature/CSR-2179-ajc
This commit is contained in:
commit
b42553aec0
8 changed files with 129 additions and 39 deletions
|
|
@ -1,5 +1,6 @@
|
|||
const experimentUniverses = {
|
||||
CONCEPT_FUNNEL: "ConceptFunnel",
|
||||
RECAL_PRICE_REMOVAL: "NextGen_RecalPriceRemoval",
|
||||
};
|
||||
|
||||
const experimentSettings = {
|
||||
|
|
@ -11,6 +12,7 @@ const experimentSettings = {
|
|||
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
|
||||
IS_EMAIL_OPTIONAL: "isEmailOptional",
|
||||
SERVICE_PACKAGE_DISCOUNT: "OfferServicePackageDiscount",
|
||||
RECAL_PRICE_REMOVE: "RecalPriceRemove",
|
||||
};
|
||||
|
||||
const experimentTriggers = {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<template>
|
||||
<div class="cart">
|
||||
<div class="px-0">
|
||||
<!-- set class to 'expaned' on line 7 so cart is open on page load. This may be temporary -->
|
||||
<div
|
||||
class="row vin-toggle flex align-items-center pt-4"
|
||||
:class="[isExpanded ? '' : 'expanded']"
|
||||
:class="[isExpanded ? 'expanded' : '']"
|
||||
@click="toggleIsExpanded()">
|
||||
<a
|
||||
aria-label="expand cart"
|
||||
|
|
@ -188,10 +187,11 @@ export default {
|
|||
insuranceDeductible: Number,
|
||||
insuranceCompanyName: String,
|
||||
showInsuranceCoverageAs: String,
|
||||
shouldHideRecalibration: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExpanded: false,
|
||||
isExpanded: true, // set default to true so cart is open on page load. This may be temporary.
|
||||
currencyFormatter: new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
|
|
@ -303,12 +303,11 @@ export default {
|
|||
},
|
||||
lineItemsWithoutRecal() {
|
||||
if (!this.lineItems || this.lineItems.length < 1) return;
|
||||
if (!this.lineItems.supportingItems) return this.lineItems;
|
||||
|
||||
const lineItemsCopy = deepClone(this.lineItems);
|
||||
lineItemsCopy.supportingItems = baseMixin.methods.filterOutRecalibration(
|
||||
lineItemsCopy.supportingItems
|
||||
);
|
||||
lineItemsCopy.supportingItems = lineItemsCopy.supportingItems ? baseMixin.methods.filterOutRecalibration(
|
||||
lineItemsCopy?.supportingItems
|
||||
) : [];
|
||||
return lineItemsCopy;
|
||||
},
|
||||
showCoverageAsPending() {
|
||||
|
|
@ -439,14 +438,13 @@ export default {
|
|||
packagePrice() {
|
||||
let packagePrice = 0;
|
||||
|
||||
if (!this.isInsurance) {
|
||||
// CASH ONLY
|
||||
if (!this.isInsurance && this.shouldHideRecalibration) { // Update packagePrice if Cash only && is part of RemoveRecal experiment
|
||||
packagePrice = baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(
|
||||
baseMixin.methods.filterOutRecalibration(this.availableLineItems)
|
||||
baseMixin.methods.filterOutRecalibration(this.availableLineItems) // Strip out recal before filtering out fees
|
||||
)
|
||||
);
|
||||
} else if (!this.showCoverageAsVerified && !this.showCoverageAsPending) {
|
||||
} else if (!this.showCoverageAsVerified && !this.showCoverageAsPending) { // Update packagePrice if is verified insurance OR if Cash && not part of RemoveRecal experiment
|
||||
packagePrice = baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(this.availableLineItems)
|
||||
);
|
||||
|
|
@ -1001,22 +999,22 @@ export default {
|
|||
},
|
||||
subTotal() {
|
||||
if (!this.lineItems || this.lineItems.length < 1) return;
|
||||
return this.isInsurance
|
||||
? baseMixin.methods.getSubTotal(this.lineItems)
|
||||
: baseMixin.methods.getSubTotal(this.lineItemsWithoutRecal);
|
||||
return this.isInsurance || !this.shouldHideRecalibration
|
||||
? baseMixin.methods.getSubTotal(this.lineItems) // calculate with recal (if on order)
|
||||
: baseMixin.methods.getSubTotal(this.lineItemsWithoutRecal); // calculated without recal
|
||||
},
|
||||
salesTax() {
|
||||
if (!this.lineItems || this.lineItems.length < 1) return;
|
||||
return this.isInsurance
|
||||
? baseMixin.methods.getSalesTax(this.lineItems)
|
||||
: baseMixin.methods.getSalesTax(this.lineItemsWithoutRecal);
|
||||
return this.isInsurance || !this.shouldHideRecalibration
|
||||
? baseMixin.methods.getSubTotal(this.lineItems) // calculate with recal (if on order)
|
||||
: baseMixin.methods.getSubTotal(this.lineItemsWithoutRecal); // calculated without recal
|
||||
},
|
||||
amountDue() {
|
||||
if (!this.lineItems || this.lineItems.length < 1) return;
|
||||
if (this.showAsPaid) return 0;
|
||||
return this.isInsurance
|
||||
? baseMixin.methods.getAmountDue(this.lineItems)
|
||||
: baseMixin.methods.getAmountDue(this.lineItemsWithoutRecal);
|
||||
return this.isInsurance || !this.shouldHideRecalibration
|
||||
? baseMixin.methods.getSubTotal(this.lineItems) // calculate with recal (if on order)
|
||||
: baseMixin.methods.getSubTotal(this.lineItemsWithoutRecal); // calculated without recal
|
||||
},
|
||||
amountPaid() {
|
||||
if (!this.showAsPaid) {
|
||||
|
|
|
|||
|
|
@ -565,6 +565,7 @@ function setupMocks({ customMountOptions }) {
|
|||
getCmsContent: jest.fn().mockImplementation(() => {
|
||||
return wordingText;
|
||||
}),
|
||||
getSettingValue: jest.fn(),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@
|
|||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
:insuranceDeductible="currentDeductible"
|
||||
:insuranceCompanyName="insuranceCompanyName"
|
||||
:showInsuranceCoverageAs="showInsuranceCoverageAs" />
|
||||
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
||||
:shouldHideRecalibration="shouldHideRecalibration" />
|
||||
|
||||
<hr class="mb-5" />
|
||||
|
||||
|
|
@ -105,11 +106,13 @@ import { Form } from "vee-validate";
|
|||
import { get12HourTimeFormat, get12HourTimeMobileFormat } from "@/helpers/date-helper";
|
||||
import { coverageStatus } from "@/constants/insurance";
|
||||
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
|
||||
export default {
|
||||
name: "confirmation",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER);
|
||||
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); // This nulls the Store order
|
||||
|
||||
// Call APIs
|
||||
const submittedOrder = baseMixin.methods.getSubmittedOrder();
|
||||
|
|
@ -166,6 +169,11 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
shouldHideRecalibration() {
|
||||
return this.getSettingValue(
|
||||
experimentSettings.RECAL_PRICE_REMOVE
|
||||
);
|
||||
},
|
||||
ShowCart() {
|
||||
if (this.isPia && this.submittedOrder?.settledTenderAmount == 0) {
|
||||
// settleTenderAmount always shows 0 via localhost or dev.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@
|
|||
:isInsurance="isInsurance"
|
||||
:insuranceDeductible="currentDeductible"
|
||||
:insuranceCompanyName="insuranceCompanyName"
|
||||
:showInsuranceCoverageAs="showInsuranceCoverageAs" />
|
||||
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
||||
:shouldHideRecalibration="shouldHideRecalibration" />
|
||||
|
||||
<hr class="my-5" />
|
||||
|
||||
|
|
@ -47,14 +48,14 @@
|
|||
v-bind:isDismissible="false" />
|
||||
</div>
|
||||
|
||||
<div v-if="hasRecal && !isInsurance" class="questions-about-service my-5">
|
||||
<div v-if="!isInsurance" class="questions-about-service my-5">
|
||||
<textBlock
|
||||
cmsWidgetName="QuestionsAboutYourServiceWidget"
|
||||
justifyText="left"
|
||||
class="service-questions" />
|
||||
<textBlock cmsWidgetName="CallOrTextWidget" justifyText="left" />
|
||||
<hr class="my-5" />
|
||||
<div class="d-flex flex-row checkbox-group">
|
||||
<div class="d-flex flex-row checkbox-group" v-if="isRecalibrationOnOrder && shouldHideRecalibration">
|
||||
<checkboxQuestion
|
||||
cmsWidgetName="RecalConfirmWidget"
|
||||
class="mb-5"
|
||||
|
|
@ -125,6 +126,7 @@ import {
|
|||
} from "@/helpers/cms-content-helper";
|
||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
import {
|
||||
revalidatePromosAndValidateQueryStringPromo,
|
||||
buildToastMessagesFromRevalidateOrValidatePromoResponse,
|
||||
|
|
@ -148,6 +150,7 @@ import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
|||
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||
import { mapTaxedLineItemsToStoreFormat } from "../../store";
|
||||
import { coverageStatus } from "@/constants/insurance";
|
||||
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
|
||||
|
||||
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
|
||||
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
|
||||
|
|
@ -582,15 +585,16 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
hasRecal() {
|
||||
const recalLineItem = this.$store.getters.order.lineItems?.supportingItems?.find(
|
||||
(lineItem) => lineItem.partType.indexOf(partTypeStrings.RECALIBRATION) !== -1
|
||||
isRecalibrationOnOrder() {
|
||||
return containsLineItemWithPartType(
|
||||
partTypeStrings.RECALIBRATION,
|
||||
this.$store.getters.order.lineItems?.supportingItems
|
||||
);
|
||||
},
|
||||
shouldHideRecalibration() {
|
||||
return this.getSettingValue(
|
||||
experimentSettings.RECAL_PRICE_REMOVE
|
||||
);
|
||||
|
||||
if (recalLineItem) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
showApplePay() {
|
||||
return baseMixin.methods.showApplePay();
|
||||
|
|
@ -662,7 +666,7 @@ export default {
|
|||
return true;
|
||||
}
|
||||
} else {
|
||||
if (this.isPiaEnabled && this.totalAmountDue > 0 && !this.hasRecal) {
|
||||
if (this.isPiaEnabled && this.totalAmountDue > 0 && !this.isRecalibrationOnOrder) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -768,7 +772,7 @@ export default {
|
|||
}
|
||||
.questions-about-service {
|
||||
.service-questions {
|
||||
font-weight: $font-weight-bold;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.cart {
|
||||
|
|
|
|||
|
|
@ -238,6 +238,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -281,6 +284,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -322,6 +328,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -360,6 +369,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -399,6 +411,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -438,6 +453,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -478,6 +496,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -520,6 +541,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -561,6 +585,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
@ -654,6 +681,9 @@ describe("quote.vue", () => {
|
|||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
order: {
|
||||
lineItems: {
|
||||
glassParts: ["item", "item2"],
|
||||
|
|
|
|||
|
|
@ -42,15 +42,17 @@
|
|||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<!-- If Cash AND vehicle does not require recal OR if Cash AND State requires showing recal, then show Afterpay banner -->
|
||||
<afterpayModalBanner
|
||||
v-if="!isInsuranceSelected && !isRecalibrationOnOrder"
|
||||
v-if="showAfterpayBanner"
|
||||
cmsWidgetName="AfterpayModalWidget"
|
||||
:lineItems="lineItems" />
|
||||
|
||||
<!-- If vehicle requires recal AND we are hiding recal pricing info, then show recal disclaimer banner. -->
|
||||
<recalDisclaimer
|
||||
class="mb-0"
|
||||
cmsWidgetName="RecalDisclaimerWidget"
|
||||
v-if="isRecalibrationOnOrder"
|
||||
v-if="showRecalDisclaimer"
|
||||
v-on="{ textLinkClicked: openModalAction }"
|
||||
alertClass="alert-info" />
|
||||
|
||||
|
|
@ -99,6 +101,8 @@ 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 recalDisclaimer from "@/layouts/quote/recal-disclaimer/recal-disclaimer.vue";
|
||||
|
||||
// Supporting files
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
|
||||
|
|
@ -107,6 +111,8 @@ import { storeActions } from "@/constants/store-actions";
|
|||
import { deepClone } from "@/helpers/object-helper";
|
||||
import store from "@/store";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { experimentUniverses, experimentSettings } from "@/constants/experiments";
|
||||
import { getSessionKeyValue, getUserIdValue } from "@/helpers/heritage-integration/cookie-helper";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
|
|
@ -120,7 +126,6 @@ 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";
|
||||
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
|
||||
import { nextTick } from "vue";
|
||||
|
|
@ -133,6 +138,9 @@ export default {
|
|||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
const experimentForLogging = store.getters.applicationUser.experiments.find(
|
||||
(e) => e.universeName === experimentUniverses.RECAL_PRICE_REMOVAL
|
||||
);
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_WIPERS,
|
||||
{
|
||||
|
|
@ -200,6 +208,33 @@ export default {
|
|||
...servicePackageDiscountPart,
|
||||
];
|
||||
|
||||
// If the recal experiment is found then log the experiment exposure.
|
||||
const isRecalibrationOnOrder = containsLineItemWithPartType(
|
||||
partTypeStrings.RECALIBRATION,
|
||||
availableLineItems
|
||||
);
|
||||
const canSafeliteRecalibrate = nullSafeGlassParts.some((glassPart) => {
|
||||
return glassPart.partType === "WINDSHIELD" && glassPart.canSafeliteRecalibrate;
|
||||
});
|
||||
if (
|
||||
experimentForLogging !== undefined &&
|
||||
isRecalibrationOnOrder &&
|
||||
canSafeliteRecalibrate
|
||||
) {
|
||||
// Log experiment exposure
|
||||
baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.LOG_EXPERIMENT_EXPOSURE,
|
||||
{
|
||||
userId: getUserIdValue(),
|
||||
sessionKey: getSessionKeyValue(),
|
||||
pageName: to.query.fmgPage,
|
||||
experiment: experimentForLogging,
|
||||
},
|
||||
"quote",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// When calling pricing from the quote page, always use the cash parent account
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PARENT_ACCOUNT_NUMBER,
|
||||
|
|
@ -324,6 +359,18 @@ export default {
|
|||
this?.availableLineItems
|
||||
);
|
||||
},
|
||||
shouldHideRecalibration() {
|
||||
const recalSettingValue = experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.RECAL_PRICE_REMOVE
|
||||
);
|
||||
return recalSettingValue;
|
||||
},
|
||||
showAfterpayBanner() {
|
||||
return !this.isInsuranceSelected && (!this.isRecalibrationOnOrder || !this.shouldHideRecalibration)
|
||||
},
|
||||
showRecalDisclaimer() {
|
||||
return this.isRecalibrationOnOrder && this.shouldHideRecalibration
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModalAction(modalName) {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default {
|
|||
},
|
||||
filterOutRecalibration(lineItems) {
|
||||
const filteredLineItems = lineItems.filter((item) => {
|
||||
return item.partType != partTypeStrings.RECALIBRATION;
|
||||
return !item.partType.includes(partTypeStrings.RECALIBRATION);
|
||||
});
|
||||
return filteredLineItems;
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue