DigitalConsumer.FixMyGlass/src/layouts/quote/service-package-question/service-package-question.vue
2025-12-17 07:06:17 -05:00

444 lines
17 KiB
Vue

<template>
<buttonQuestion
ref="buttonQuestion"
:answers="servicePackageAnswers"
:groupName="groupName"
buttonTypeString="servicePackageRadio"
:buttonTypeObject="getButtonTypeObject()"
v-model="selectedPackageName"
:validationRules="validationRules"
:isRequired="isRequired"
:logDisplayedValuesEvent="true"
:isInsuranceSelected="isInsuranceSelected" />
</template>
<script>
import buttonQuestion from "@/digital-components/button-question/button-question";
import baseMixin from "@/mixins/base-mixin.js";
import { processIfStatements } from "@/helpers/cms-content-helper";
import servicePackageRadio from "./service-package-radio/service-package-radio";
import servicePackageRadioExperiment from "@/experiment-components/service-package-radio";
import servicePackageRadioAfterpayExperiment from "@/experiment-components/service-package-radio-for-afterpay";
import { partTypeStrings } from "@/constants/part-type-strings";
import { packageNames } from "@/constants/package-names";
import store from "@/store";
import { storeMutations } from "@/constants/store-mutations";
import {
shouldFrontWipersBeAvailable,
shouldRearWipersBeAvailable,
shouldRainRepelBeAvailable,
getAvailablePackages,
getHighestRequiredTier,
getPackageContents,
containsLineItemWithPartType,
findLineItemsWithPartType,
getPackageNameByType,
getDiscountedPackageName,
getPackageLabel,
} from "@/helpers/service-package-helper";
import {
getPromosThatMatchLineItemsOnOrder,
removeVapsPromosFromPromoArray,
getPromosWithAddableVaps,
getLineItemsThatMatchPromos,
} from "@/helpers/promotions-helper";
import { containsRecalParts, getItemsWithoutRecalParts } from "@/helpers/recal-helper";
import experimentMixin from "@/mixins/experiment-mixin.js";
import { experimentSettings } from "@/constants/experiments";
import { savePageData } from "@/router/methods/helpers/save-page-data";
import { debugLog } from "@/helpers/debug-log-helper";
export default {
name: "servicePackageQuestion",
props: {
groupName: String,
cashCmsWidgetName: String,
insuranceCmsWidgetName: String,
validationRules: String,
isRequired: Boolean,
isInsuranceSelected: Boolean,
availableLineItems: null,
activePromos: null,
servicePackage: null,
isRecalibrationOnOrder: Boolean,
shouldHideRecalibration: Boolean,
availableQuotePageDiscounts: null,
},
data() {
return {
servicePackageRadio: servicePackageRadio,
servicePackageRadioExperiment: servicePackageRadioExperiment,
servicePackageRadioAfterpayExperiment: servicePackageRadioAfterpayExperiment,
selectedPackageName: null,
};
},
watch: {
availableLineItems() {
if (
this.$store.getters.payment.isInsurance != null ||
getPromosWithAddableVaps(this.activePromos).length ||
this.servicePackage != null
) {
this.selectDefaultPackage();
}
},
activePromos() {
if (this.activePromos?.length) this.selectDefaultPackage();
},
selectedPackageName(newValue) {
debugLog("selectedPackageName:", newValue);
const packageLabel = getPackageLabel(newValue);
debugLog("getPackageLabel:", packageLabel);
this.$emit("servicePackageSelected", `ServicePackage-${packageLabel}`);
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
const discountPartInSelectedPackage = this.getDiscountPartForSelectedPackage(newValue);
let supportingItems = this.supportingLineItems;
supportingItems = supportingItems.filter(
(item) => item.partType !== partTypeStrings.QUOTE_PAGE_DISCOUNT
);
if (discountPartInSelectedPackage) {
supportingItems.push(discountPartInSelectedPackage);
}
this.$emit("quotePageDiscountSelected", supportingItems);
},
servicePackage(newValue) {
if (newValue !== null) {
this.selectDefaultPackage();
}
},
},
computed: {
nullSafeAvailableLineItems() {
return this.availableLineItems ?? [];
},
supportingLineItems() {
return this.$store.getters.lineItems?.supportingItems ?? [];
},
servicePackageAnswers() {
const cmsWidgetName = this.isInsuranceSelected
? this.insuranceCmsWidgetName
: this.cashCmsWidgetName;
let cmsAnswersContent = this.getCmsContent(cmsWidgetName, "Answers");
if (!cmsAnswersContent) {
return null;
}
const availablePackages = getAvailablePackages(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair
);
cmsAnswersContent = cmsAnswersContent.filter((answer) =>
availablePackages.some((tier) => answer.Name === tier.packageName)
);
const modifiedAnswers = cmsAnswersContent.map((answer) => ({
value: answer.Name,
buttonLabel: this.getHeaderTextFromCms(answer.SubWidgetName),
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName),
buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName),
//The package supports service-package-discount if it has the text value in CMS
buttonAuxillaryCopy: this.isInsuranceSelected
? this.getDiscountedPackagePriceString(answer.Name, false)
: this.getDiscountedPackagePriceString(
answer.Name,
this.hasDiscountForPackage(answer.Name)
),
buttonFooterCopy: this.getFooterTextFromCms(answer.SubWidgetName),
additionalButtonData: {
strikeThroughPrice: this.getPackagePriceString(answer.Name),
hasDiscount: this.hasDiscountForPackage(answer.Name),
Text: this.isInsuranceSelected
? false
: "Special: Save $" + this.getDiscountPrice(answer.Name),
isInsuranceSelected: this.isInsuranceSelected,
},
}));
this.$emit("currentNumberOfPackages", modifiedAnswers.length);
return modifiedAnswers;
},
isDiscountOnOrder() {
return this.availableQuotePageDiscounts?.length > 0;
},
frontWipersApplicableForTierTwo() {
return shouldFrontWipersBeAvailable(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
packageNames.TIER_TWO
);
},
rearWiperApplicableForTierTwo() {
return shouldRearWipersBeAvailable(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
packageNames.TIER_TWO
);
},
frontWipersApplicableForTierThree() {
return shouldFrontWipersBeAvailable(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
packageNames.TIER_THREE
);
},
rearWiperApplicableForTierThree() {
return shouldRearWipersBeAvailable(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
packageNames.TIER_THREE
);
},
rainRepelApplicableForTierThree() {
return shouldRainRepelBeAvailable(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
packageNames.TIER_THREE
);
},
glassToReplace() {
return this.$store.getters.order.damage.glassToReplace;
},
isRepair() {
return this.$store.getters.order.damage.isRepair;
},
showRecalInServicePackages() {
return this.isRecalibrationOnOrder && !this.shouldHideRecalibration;
},
},
methods: {
processIfStatements,
hasPricingByDay() {
return experimentMixin.methods.hasSettingEqualTo(
experimentSettings.PRICING_BY_DAY,
"true"
);
},
isAfterpayBreakoutDisplay() {
return (
!this.shouldHideRecalibration &&
experimentMixin.methods.hasSettingEqualTo(
experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY,
"true"
)
);
},
getButtonTypeObject() {
if (this.isAfterpayBreakoutDisplay()) {
return this.servicePackageRadioAfterpayExperiment;
}
if (this.hasPricingByDay()) {
return this.servicePackageRadioExperiment;
}
return this.servicePackageRadio;
},
getHeaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, "HeaderText");
},
getSubheaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, "SubheaderText");
},
getBodyTextFromCms(cmsWidgetName) {
const bodyText = this.getCmsContent(cmsWidgetName, "BodyText");
return this.processIfStatements(bodyText, "custom", this.getCustomValueFromString);
},
getFooterTextFromCms(cmsWidgetName) {
const footerText = this.getCmsContent(cmsWidgetName, "FooterText");
return this.processIfStatements(footerText, "custom", this.getCustomValueFromString);
},
getPackagePriceString(packageName) {
const formattedPriceFloat = parseFloat(
this.getPackagePrice(packageName, {
discountedPrice: false,
quotePageDiscount: false,
})
).toFixed(2);
return "$" + formattedPriceFloat;
},
getDiscountedPackagePriceString(packageName, quotePageDiscount) {
const formattedPriceFloat = parseFloat(
this.getPackagePrice(packageName, {
discountedPrice: true,
quotePageDiscount,
})
).toFixed(2);
if (this.hasPricingByDay() && !this.isAfterpayBreakoutDisplay()) {
return "$" + formattedPriceFloat;
}
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
},
getPackagePrice(packageName, { discountedPrice = false, quotePageDiscount = false }) {
let lineItemsToPrice = [...this.nullSafeAvailableLineItems];
if (this.isRecalibrationOnOrder && this.shouldHideRecalibration) {
lineItemsToPrice = getItemsWithoutRecalParts(lineItemsToPrice);
}
//remove service package discount part
if (this.isDiscountOnOrder) {
lineItemsToPrice = lineItemsToPrice.filter((item) => {
return item.partType != partTypeStrings.QUOTE_PAGE_DISCOUNT;
});
}
if (discountedPrice) {
lineItemsToPrice.push(...removeVapsPromosFromPromoArray(this.activePromos));
}
if (quotePageDiscount) {
const part = this.getDiscountPartForSelectedPackage(packageName);
if (part) {
lineItemsToPrice.push(part);
}
}
let priceFloat = this.isInsuranceSelected
? 0
: baseMixin.methods.getTierOnePackagePrice(
baseMixin.methods.filterOutFees(lineItemsToPrice)
);
priceFloat += this.getVapsPrice(packageName, discountedPrice);
return priceFloat;
},
getDiscountPrice(packageName) {
const part = this.getDiscountPartForSelectedPackage(packageName);
if (part) {
const price = baseMixin.methods.getTotalLineItemPrice(part);
return Math.abs(price);
}
return null;
},
getVapsPrice(packageName, applyPromoDiscounts = false) {
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
let price = 0;
vapsItems.forEach((item) => {
price += baseMixin.methods.getTotalLineItemPrice(item);
});
if (applyPromoDiscounts && this.activePromos) {
const relevantPromos = getPromosThatMatchLineItemsOnOrder(
this.activePromos,
vapsItems
);
relevantPromos.forEach((promo) => {
price += baseMixin.methods.getTotalLineItemPrice(promo);
});
}
return price;
},
selectDefaultPackage() {
if (this.servicePackage != null) {
this.selectedPackageName = getPackageNameByType(this.servicePackage);
return;
}
const promosWithAddableVaps = getPromosWithAddableVaps(this.activePromos);
const vapsThatMatchPromos = getLineItemsThatMatchPromos(
promosWithAddableVaps,
this.nullSafeAvailableLineItems
);
const vapsFromStore = this.$store.getters.lineItems.vaps ?? [];
const vapsToUseForDefaultPackageSelection = this.combineLineItemsWithoutDuplicates(
vapsThatMatchPromos,
vapsFromStore
);
this.selectedPackageName = getHighestRequiredTier(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
vapsToUseForDefaultPackageSelection
);
},
getVapsLineItemsForSelectedPackage(packageName) {
const packageContentTypes = getPackageContents(
this.glassToReplace,
this.nullSafeAvailableLineItems,
this.isRepair,
packageName
);
let vapsLineItemsForSelectedPackage = [];
packageContentTypes.forEach((vapType) => {
vapsLineItemsForSelectedPackage.push(
...findLineItemsWithPartType(vapType, this.nullSafeAvailableLineItems)
);
});
return vapsLineItemsForSelectedPackage;
},
getDiscountPartForSelectedPackage(packageName) {
if (this.availableQuotePageDiscounts?.length > 0) {
const match = this.availableQuotePageDiscounts.find(
(discountInfo) => discountInfo.packageLevel === packageName
);
return match?.item;
}
return null;
},
hasDiscountForPackage(packageName) {
return !!this.getDiscountPartForSelectedPackage(packageName);
},
combineLineItemsWithoutDuplicates(lineItemsOne, lineItemsTwo) {
const combinedLineItemArray = [...lineItemsTwo];
lineItemsOne.forEach((lineItemOne) => {
if (
!lineItemsTwo.filter((lineItemTwo) => lineItemTwo.id === lineItemOne.id).length
) {
combinedLineItemArray.push(lineItemOne);
}
});
return combinedLineItemArray;
},
getCustomValueFromString(str) {
switch (str) {
case "showRecalInServicePackages":
return this.showRecalInServicePackages;
case "frontWipersApplicableForTierTwo":
return this.frontWipersApplicableForTierTwo;
case "rearWiperApplicableForTierTwo":
return this.rearWiperApplicableForTierTwo;
case "frontWipersApplicableForTierThree":
return this.frontWipersApplicableForTierThree;
case "rearWiperApplicableForTierThree":
return this.rearWiperApplicableForTierThree;
case "rainRepelApplicableForTierThree":
return this.rainRepelApplicableForTierThree;
default:
return null;
}
},
async savePackageInfoToPageData() {
const answers = this.servicePackageAnswers;
const toLog = answers.map((ans) => ({
packageName: ans.buttonLabel,
price: ans.buttonAuxillaryCopy,
}));
await savePageData(this.pageName, { packages: toLog }, true);
},
},
components: {
buttonQuestion,
},
};
</script>