Merge branch 'release/2025.04.24' into feature/CASH-480

This commit is contained in:
scottkiener-at-safelite 2025-04-08 15:59:26 -04:00 committed by GitHub
commit 672cecd277
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 470 additions and 219 deletions

View file

@ -192,6 +192,14 @@ const endpoints = {
url: "/account/api/v1/account/bill-to-account-number", url: "/account/api/v1/account/bill-to-account-number",
method: "GET", method: "GET",
}, },
ClosestApplicableShops: {
url: "/location/api/v1/location/closest-applicable-shops",
method: "GET",
},
AddDonationPart: {
url: "/order/api/v1/order/add-donation-part",
method: "POST",
},
}; };
export { endpoints }; export { endpoints };

View file

@ -0,0 +1,3 @@
export const sessionStorageKeyConstants = {
SUBMITTED_STATE: "submittedState",
};

View file

@ -55,6 +55,7 @@ const storeActions = {
GET_INSURANCE_COMPANY_LIST: "getInsuranceCompanyList", GET_INSURANCE_COMPANY_LIST: "getInsuranceCompanyList",
GET_BILL_TO_ACCOUNT_NUMBER: "getBillToAccountNumber", GET_BILL_TO_ACCOUNT_NUMBER: "getBillToAccountNumber",
GET_RECAL_PARTS_AND_SAVE_TO_LINE_ITEMS: "getRecalPartsAndSaveToLineItems", GET_RECAL_PARTS_AND_SAVE_TO_LINE_ITEMS: "getRecalPartsAndSaveToLineItems",
GET_CLOSEST_APPLICABLE_SHOPS: "getClosestApplicableShops",
// Analytics // Analytics
LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE: "logExperimentExposureAndUpdateStore", LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE: "logExperimentExposureAndUpdateStore",
@ -109,9 +110,9 @@ const storeActions = {
SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn", SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn",
SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable", SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable",
CREATE_SUBMITTED_ORDER: "createSubmittedOrder", CREATE_SUBMITTED_STATE: "createSubmittedState",
RESET_SUBMITTED_ORDER: "resetSubmittedOrder", RESET_SUBMITTED_STATE: "resetSubmittedState",
ADD_DONATION_TO_SUBMITTED_ORDER: "addDonationToSubmittedOrder", ADD_DONATION_TO_SUBMITTED_STATE: "addDonationToSubmittedState",
RESET_EXTERNAL_PARAMETER_STATE: "resetExternalParameterState", RESET_EXTERNAL_PARAMETER_STATE: "resetExternalParameterState",
UPDATE_EXTERNAL_PARAMETER_MMS: "updateExternalParameterMMS", UPDATE_EXTERNAL_PARAMETER_MMS: "updateExternalParameterMMS",

View file

@ -9,6 +9,9 @@ const storeMutations = {
UPDATE_MAKE: "updateMake", UPDATE_MAKE: "updateMake",
UPDATE_MODEL: "updateModel", UPDATE_MODEL: "updateModel",
UPDATE_STYLE: "updateStyle", UPDATE_STYLE: "updateStyle",
UPDATE_VEHICLE_SUBTYPE: "updateVehicleSubType",
UPDATE_VEHICLE_SPECIAL_CLASS: "updateVehicleSpecialClass",
UPDATE_IS_BIGTRUCK: "updateIsBigTruck",
UPDATE_CAR_ID: "updateCarId", UPDATE_CAR_ID: "updateCarId",
UPDATE_VEHICLE_CATEGORY: "updateVehicleCategory", UPDATE_VEHICLE_CATEGORY: "updateVehicleCategory",
UPDATE_VEHICLE_IMAGE_URL: "updateVehicleImageUrl", UPDATE_VEHICLE_IMAGE_URL: "updateVehicleImageUrl",
@ -16,6 +19,7 @@ const storeMutations = {
UPDATE_VEHICLE_IMAGE_COLOR: "updateVehicleImageColor", UPDATE_VEHICLE_IMAGE_COLOR: "updateVehicleImageColor",
UPDATE_VEHICLE_VIN: "updateVehicleVin", UPDATE_VEHICLE_VIN: "updateVehicleVin",
UPDATE_VEHICLE: "updateVehicle", UPDATE_VEHICLE: "updateVehicle",
UPDATE_VEHICLE_CAN_SAFELITE_SERVICE: "updateVehicleCanSafeliteService",
UPDATE_IS_REPAIR: "updateIsRepair", UPDATE_IS_REPAIR: "updateIsRepair",
UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips", UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips",

View file

@ -62,11 +62,11 @@
<div class="col-12"> <div class="col-12">
<div <div
v-if="showDonationSuccess" v-if="showDonationSuccess"
class="d-flex justify-content-center align-items-center btn-success w-100" class="d-flex justify-content-center align-items-center btn-success w-100 mb-2"
v-html="DonationThanksAlertWidget"></div> v-html="DonationThanksAlertWidget"></div>
</div> </div>
</div> </div>
<div class="row px-4 mt-2"> <div class="row px-4">
<div class="col"> <div class="col">
<alert <alert
v-if="showDonationError" v-if="showDonationError"
@ -76,7 +76,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<p class="mt-0 mb-4 mx-0 text-center caption" v-html="DonationFooterText"></p> <p class="mt-2 mb-4 mx-0 text-center caption" v-html="DonationFooterText"></p>
</div> </div>
</div> </div>
</form> </form>
@ -261,6 +261,8 @@ export default {
} }
.alert { .alert {
border: 1px solid $red; border: 1px solid $red;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
} }
.btn-success { .btn-success {
background: $green-100; background: $green-100;

View file

@ -401,11 +401,15 @@ export default {
if (!this.lineItems || this.lineItems.length < 1) { if (!this.lineItems || this.lineItems.length < 1) {
return []; return [];
} }
let supportingItemsToAdd = [];
if (this.lineItems.supportingItems) {
supportingItemsToAdd = this.lineItems.supportingItems.filter(
(item) => item.partType !== partTypeStrings.DONATION
);
}
return [ return [
...(this.lineItems.glassParts ?? []), ...(this.lineItems.glassParts ?? []),
...(this.lineItems.supportingItems.filter( ...supportingItemsToAdd,
(item) => item.partType !== partTypeStrings.DONATION
) ?? []),
...(this.availableVaps ?? []), ...(this.availableVaps ?? []),
]; ];
}, },
@ -1074,11 +1078,7 @@ export default {
const donationLineItem = this.lineItems.supportingItems.find( const donationLineItem = this.lineItems.supportingItems.find(
(item) => item.partType === partTypeStrings.DONATION (item) => item.partType === partTypeStrings.DONATION
); );
if (donationLineItem?.sellingPrice !== 0) { return !!donationLineItem;
return true;
} else {
return false;
}
}, },
donationCartItemName() { donationCartItemName() {
return this.getCmsContent("DonationCartTextWidget", "Text"); return this.getCmsContent("DonationCartTextWidget", "Text");
@ -1092,7 +1092,7 @@ export default {
(item) => item.partType === partTypeStrings.DONATION (item) => item.partType === partTypeStrings.DONATION
); );
if (donationLineItem?.sellingPrice > 0) { if (donationLineItem) {
return { return {
name: this.donationCartItemName, name: this.donationCartItemName,
category: cartItemCategories.SUPPORTING_ITEMS, category: cartItemCategories.SUPPORTING_ITEMS,

View file

@ -0,0 +1,20 @@
export function getExperimentSettingsFromExperimentList(experimentList) {
return (
experimentList
.filter((x) => !!x.isActive)
.map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {}
);
}
export function getSettingValue(settingName, experimentSettings) {
return hasSetting(settingName, experimentSettings) ? experimentSettings[settingName] : null;
}
export function hasSettingEqualTo(settingName, settingValue, experimentSettings) {
return experimentSettings[settingName] == settingValue;
}
export function hasSetting(settingName, experimentSettings) {
return Object.hasOwn(experimentSettings, settingName);
}

View file

@ -115,7 +115,7 @@ export default {
}, },
ServiceType() { ServiceType() {
const isRepair = this.getSubmittedOrder()?.damage.isRepair; const isRepair = this.getSubmittedOrder()?.damage.isRepair;
const funnelHasRecalibrationPart = store.getters.isRecalibrationOnSubmittedOrder; const funnelHasRecalibrationPart = store.getters.isRecalibrationOnSubmittedState;
if (!isRepair) { if (!isRepair) {
if (funnelHasRecalibrationPart) { if (funnelHasRecalibrationPart) {
return serviceType.REPLACEMENT_AND_RECALIBRATION; return serviceType.REPLACEMENT_AND_RECALIBRATION;

View file

@ -63,11 +63,9 @@
:isExpandedOnLoad="false" :isExpandedOnLoad="false"
:isItac="isItac" :isItac="isItac"
:isNoComp="isNoComp" :isNoComp="isNoComp"
:isMSRFeeApplicable="isMSRFeeApplicable" :isMSRFeeApplicable="isMSRFeeApplicable" />
:donationAmount="donationAmount"
@update-donation-amount="updateDonationAmount" />
<div class="mt-3" v-if="DisplayFosterLove"> <div class="mt-3" v-if="shouldDisplayFosterLove">
<donationBlock <donationBlock
cmsWidgetName="DonationWidget" cmsWidgetName="DonationWidget"
v-model="donationAmount" v-model="donationAmount"
@ -118,8 +116,6 @@ import { coverageStatus } from "@/constants/insurance";
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper"; import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
import { experimentSettings } from "@/constants/experiments"; import { experimentSettings } from "@/constants/experiments";
import experimentMixin from "@/mixins/experiment-mixin.js"; import experimentMixin from "@/mixins/experiment-mixin.js";
import { partTypeStrings } from "@/constants/part-type-strings";
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
import { containsRecalParts } from "@/helpers/recal-helper.js"; import { containsRecalParts } from "@/helpers/recal-helper.js";
import donationBlock from "@/experiment-components/donation-block.vue"; import donationBlock from "@/experiment-components/donation-block.vue";
import { partNumberStrings } from "@/constants/part-number-strings"; import { partNumberStrings } from "@/constants/part-number-strings";
@ -127,7 +123,16 @@ import { partNumberStrings } from "@/constants/part-number-strings";
export default { export default {
name: "confirmation", name: "confirmation",
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Experiments
const hasRecalPriceRemoveExperiment = await store.getters.shouldHideRecalibration; const hasRecalPriceRemoveExperiment = await store.getters.shouldHideRecalibration;
let applicationUser = baseMixin.methods.hasSubmittedApplicationUser()
? baseMixin.methods.getSubmittedApplicationUser()
: store.getters.applicationUser;
let hasFosterLoveExperiment = experimentMixin.methods.hasSettingEqualTo(
experimentSettings.FOSTER_LOVE,
"true",
applicationUser.experiments
);
// send part question data to SPS API for logging // send part question data to SPS API for logging
const orderFromStore = await deepClone(store.getters.order); const orderFromStore = await deepClone(store.getters.order);
@ -166,7 +171,7 @@ export default {
} }
// Create order // Create order
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); // This nulls the Store order await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_STATE); // This nulls the Store order
// Call APIs // Call APIs
const submittedOrder = baseMixin.methods.getSubmittedOrder(); const submittedOrder = baseMixin.methods.getSubmittedOrder();
@ -215,7 +220,6 @@ export default {
if (isNoComp || isItac) { if (isNoComp || isItac) {
return false; return false;
} }
return hasRecalPriceRemoveExperiment && isRecalibrationOnOrder; return hasRecalPriceRemoveExperiment && isRecalibrationOnOrder;
}; };
@ -231,7 +235,11 @@ export default {
vm.vaps = availableVaps; vm.vaps = availableVaps;
vm.submittedOrder = submittedOrder; vm.submittedOrder = submittedOrder;
vm.shouldHideRecalibration = shouldHideRecalibration(); vm.shouldHideRecalibration = shouldHideRecalibration();
vm.shouldDisplayFosterLove = hasFosterLoveExperiment;
vm.donationAmount = donationAmount; vm.donationAmount = donationAmount;
vm.showDonationSuccess = lineItemsFromSubmittedOrder.supportingItems?.some(
(item) => item.partType === partNumberStrings.DONATION
);
}); });
}, },
data() { data() {
@ -240,28 +248,20 @@ export default {
vaps: [], vaps: [],
submittedOrder: null, submittedOrder: null,
shouldHideRecalibration: null, shouldHideRecalibration: null,
shouldDisplayFosterLove: null,
donationAmount: 0, donationAmount: 0,
showDonationSuccess: null, showDonationSuccess: null,
showDonationError: null, showDonationError: null,
}; };
}, },
computed: { computed: {
DisplayFosterLove() {
return experimentMixin.methods.hasSettingEqualTo(
experimentSettings.FOSTER_LOVE,
"true"
);
},
isRecalibrationOnOrder() { isRecalibrationOnOrder() {
return containsRecalParts(this?.lineItems); return containsRecalParts(this?.lineItems);
}, },
ShowCart() { ShowCart() {
if (this.isPia && this.submittedOrder?.settledTenderAmount == 0) { if (this.isPia && this.submittedOrder?.settledTenderAmount == 0) {
// settleTenderAmount always shows 0 via localhost or dev.
// Temporarily set return true to see cart in localhost or dev environment
return false; return false;
} }
return true; return true;
}, },
ScheduleConfirmationText() { ScheduleConfirmationText() {
@ -539,7 +539,7 @@ export default {
this.showDonationSuccess = true; this.showDonationSuccess = true;
this.showDonationError = false; this.showDonationError = false;
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(
storeActions.ADD_DONATION_TO_SUBMITTED_ORDER, storeActions.ADD_DONATION_TO_SUBMITTED_STATE,
parseInt(this.donationAmount), parseInt(this.donationAmount),
false false
); );

View file

@ -192,7 +192,7 @@ export default {
} }
this.$refs.loadingModal.isModalVisible = false; this.$refs.loadingModal.isModalVisible = false;
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_STATE);
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_SUCCESS, this.$route); this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_SUCCESS, this.$route);
}, },
}, },

View file

@ -683,7 +683,7 @@ export default {
submitAfterSave: true, submitAfterSave: true,
}); });
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER); await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_STATE);
window.location = applicationConfig.CONFIRMATION_URL; window.location = applicationConfig.CONFIRMATION_URL;
} catch (error) { } catch (error) {
console.log("error: response from submit work order(payment pg):" + error.message); console.log("error: response from submit work order(payment pg):" + error.message);

View file

@ -7,7 +7,6 @@ import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
import baseMixin from "../../mixins/base-mixin"; import baseMixin from "../../mixins/base-mixin";
import { experimentSettings } from "../../constants/experiments";
// Mock basemixin // Mock basemixin
jest.mock("@/mixins/base-mixin.js", () => ({ jest.mock("@/mixins/base-mixin.js", () => ({
@ -147,6 +146,9 @@ beforeEach(() => {
jest.restoreAllMocks(); jest.restoreAllMocks();
jest.clearAllMocks(); jest.clearAllMocks();
store.getters = { store.getters = {
applicationUser: {
experiments: [],
},
order: { order: {
schedule: { schedule: {
date: "2019-01-01", date: "2019-01-01",

View file

@ -269,6 +269,8 @@ export default {
const priceString = getAmountDue(lineItemsToBePriced, false); // pass the IncludeTax param as false const priceString = getAmountDue(lineItemsToBePriced, false); // pass the IncludeTax param as false
const priceStringIntegerRoundedDown = priceString?.split(".")[0]; // same method used as getDisplayPrice() in service-package-radio used on /quote const priceStringIntegerRoundedDown = priceString?.split(".")[0]; // same method used as getDisplayPrice() in service-package-radio used on /quote
const pricingByDayBasePrice = parseInt(priceStringIntegerRoundedDown); const pricingByDayBasePrice = parseInt(priceStringIntegerRoundedDown);
const payment = store.getters.order.payment;
const isInsurance = payment?.isInsurance;
// Check to see if includePricingByDayUpcharge should already be set (based on order lineItems) // Check to see if includePricingByDayUpcharge should already be set (based on order lineItems)
let includePricingByDayUpcharge = false; let includePricingByDayUpcharge = false;
@ -276,7 +278,7 @@ export default {
const pricingByDayUpchargeFeeIndex = supportingItemsFromStore?.findIndex( const pricingByDayUpchargeFeeIndex = supportingItemsFromStore?.findIndex(
(item) => item.partType == PRICING_BY_DAY_PART_TYPE (item) => item.partType == PRICING_BY_DAY_PART_TYPE
); );
if (pricingByDayUpchargeFeeIndex > -1) { if (pricingByDayUpchargeFeeIndex > -1 && !isInsurance) {
includePricingByDayUpcharge = true; includePricingByDayUpcharge = true;
} }
} }
@ -630,26 +632,25 @@ export default {
const supportingItems = this.getSupportingItems(); const supportingItems = this.getSupportingItems();
// if we have a pricing by day upcharge, then save/update supporting items with it // if we have a pricing by day upcharge, then save/update supporting items with it
if (this.showPricingByDay) { const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex(
const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex( (item) => item.partType == PRICING_BY_DAY_PART_TYPE
(item) => item.partType == PRICING_BY_DAY_PART_TYPE );
);
if (this.includePricingByDayUpcharge) { if (this.includePricingByDayUpcharge && this.showPricingByDay) {
if (pricingByDayUpchargeFeeIndex > -1) { if (pricingByDayUpchargeFeeIndex && pricingByDayUpchargeFeeIndex > -1) {
supportingItems[pricingByDayUpchargeFeeIndex].laborAmount = supportingItems[pricingByDayUpchargeFeeIndex].laborAmount =
this.pricingByDayUpchargeLineItem.laborAmount; this.pricingByDayUpchargeLineItem.laborAmount;
supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice = supportingItems[pricingByDayUpchargeFeeIndex].sellingPrice =
this.pricingByDayUpchargeLineItem.sellingPrice; this.pricingByDayUpchargeLineItem.sellingPrice;
supportingItems[pricingByDayUpchargeFeeIndex].kitPrice = supportingItems[pricingByDayUpchargeFeeIndex].kitPrice =
this.pricingByDayUpchargeLineItem.kitPrice; this.pricingByDayUpchargeLineItem.kitPrice;
} else {
supportingItems.push(this.pricingByDayUpchargeLineItem);
}
} else { } else {
if (pricingByDayUpchargeFeeIndex >= 0) { supportingItems.push(this.pricingByDayUpchargeLineItem);
supportingItems.splice(pricingByDayUpchargeFeeIndex, 1); }
} } else {
if (pricingByDayUpchargeFeeIndex >= 0) {
// remove pricing by day upcharge if it already was in store
supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
} }
} }

View file

@ -149,6 +149,23 @@ export async function getAvailabilityRating(
return shopStatus; return shopStatus;
} }
export async function getClosestApplicableShops(serviceZipCode, carId, pageNameToLog) {
if (!serviceZipCode) {
return null;
}
const closestShops = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_CLOSEST_APPLICABLE_SHOPS,
{
zip: serviceZipCode,
carId: carId,
},
pageNameToLog
);
return closestShops.data;
}
export async function getZipCodeData(serviceZipCode) { export async function getZipCodeData(serviceZipCode) {
return await baseMixin.methods.getZipCodeData(serviceZipCode, "service-location"); return await baseMixin.methods.getZipCodeData(serviceZipCode, "service-location");
} }

View file

@ -174,6 +174,7 @@ const mockMixin = {
beforeEach(() => { beforeEach(() => {
store.getters = { store.getters = {
applicationUser: { experiments: [] },
lineItems: { lineItems: {
supportingItems: [ supportingItems: [
{ {

View file

@ -15,6 +15,8 @@
editScreenReaderTextCmsWidgetName="ScreenReaderZipEditWidget" editScreenReaderTextCmsWidgetName="ScreenReaderZipEditWidget"
v-model="serviceZipCodeQuestion" v-model="serviceZipCodeQuestion"
ref="serviceZipCodeQuestion" ref="serviceZipCodeQuestion"
:carId="carId"
:isVehicleHeavyTruck="isVehicleHeavyTruck"
:mobileFeePart="mobileFeePart" :mobileFeePart="mobileFeePart"
@updated-mobile-fee-part="setMobileFeePart" @updated-mobile-fee-part="setMobileFeePart"
@updated-recycle-fee-part="setRecycleFeePart" @updated-recycle-fee-part="setRecycleFeePart"
@ -186,9 +188,11 @@ export default {
return { return {
streetAddress: this.getServiceAddressFromStore(), streetAddress: this.getServiceAddressFromStore(),
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(), apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
carId: this.getCarIdfromStore(),
city: this.getServiceCityFromStore(), city: this.getServiceCityFromStore(),
state: this.getServiceStateFromStore(), state: this.getServiceStateFromStore(),
zipCode: this.getServiceZipCodeFromStore(), zipCode: this.getServiceZipCodeFromStore(),
isVehicleHeavyTruck: this.getIsVehicleHeavyTruckFromStore(),
isVehicleProtected: this.getIsVehicleProtectedFromStore(), isVehicleProtected: this.getIsVehicleProtectedFromStore(),
isGlassServiceableInshop: null, isGlassServiceableInshop: null,
isRecalibrationServiceableInshop: null, isRecalibrationServiceableInshop: null,
@ -514,6 +518,9 @@ export default {
}); });
} }
}, },
getCarIdfromStore() {
return store.getters.vehicle.carId;
},
getServiceAddressFromStore() { getServiceAddressFromStore() {
return store.getters.order.serviceLocation.address; return store.getters.order.serviceLocation.address;
}, },
@ -529,6 +536,9 @@ export default {
getServiceZipCodeFromStore() { getServiceZipCodeFromStore() {
return store.getters.order.serviceLocation.zipCode; return store.getters.order.serviceLocation.zipCode;
}, },
getIsVehicleHeavyTruckFromStore() {
return store.getters.order.vehicle?.isBigTruck ?? false;
},
getIsVehicleProtectedFromStore() { getIsVehicleProtectedFromStore() {
return store.getters.order.serviceLocation.isVehicleProtected; return store.getters.order.serviceLocation.isVehicleProtected;
}, },

View file

@ -33,6 +33,13 @@
cmsWidgetName="AlertInvalidZipWidget" cmsWidgetName="AlertInvalidZipWidget"
alertClass="alert-danger" alertClass="alert-danger"
v-bind:isDismissible="false" /> v-bind:isDismissible="false" />
<alert
ref="AlertNoService"
v-if="displayNoServiceAlert"
class="my-4"
cmsWidgetName="AlertNoServiceWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
</modal> </modal>
</template> </template>
@ -48,6 +55,7 @@ import {
getPricedRecycleFeePart, getPricedRecycleFeePart,
getServiceabilityDetails, getServiceabilityDetails,
getBillToAccountNumber, getBillToAccountNumber,
getClosestApplicableShops,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
export default { export default {
@ -63,6 +71,7 @@ export default {
internalModel: this.copyModel(this.modelValue), internalModel: this.copyModel(this.modelValue),
serviceZipCodeTextInputId: "", serviceZipCodeTextInputId: "",
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
displayNoServiceAlert: false,
}; };
}, },
props: { props: {
@ -73,6 +82,11 @@ export default {
zipCode: "", zipCode: "",
}), }),
}, },
carId: String,
isVehicleHeavyTruck: {
type: Boolean,
default: false,
},
mobileFeePart: { mobileFeePart: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
@ -107,6 +121,7 @@ export default {
methods: { methods: {
resetAlerts() { resetAlerts() {
this.displayInvalidZipAlert = false; this.displayInvalidZipAlert = false;
this.displayNoServiceAlert = false;
}, },
resetsOnZipInput() { resetsOnZipInput() {
this.resetAlerts(); this.resetAlerts();
@ -155,6 +170,22 @@ export default {
this.focusOnZipInput(); this.focusOnZipInput();
this.resetModalButtonStyle(); this.resetModalButtonStyle();
} else { } else {
if (this.isVehicleHeavyTruck) {
// check the location endpoint to verify this zip can service a heavy truck
const closestShops = await getClosestApplicableShops(
this.internalModel.zipCode,
this.carId,
"service-location"
);
if (!closestShops || closestShops.providers?.length === 0) {
this.displayNoServiceAlert = true;
this.focusOnZipInput();
this.resetModalButtonStyle();
return null;
}
}
this.internalModel.state = zipCodeData.state; this.internalModel.state = zipCodeData.state;
this.internalModel.zipCodeCtu = zipCodeData.zipCodeCtu; this.internalModel.zipCodeCtu = zipCodeData.zipCodeCtu;

View file

@ -179,12 +179,12 @@ export default {
} }
}); });
this.pushEventToGA( var joinedShops = shops.join(",");
this.GaCategories.SERVICE_LOCATION, if (!joinedShops) {
gaAction, joinedShops = "no-shops";
shops.join(","), }
true
); this.pushEventToGA(this.GaCategories.SERVICE_LOCATION, gaAction, joinedShops, true);
} }
if (this.answers.length === 0) { if (this.answers.length === 0) {

View file

@ -173,9 +173,14 @@ function resetMockStoreData() {
}; };
} }
const applicationUser = {
experiments: [],
};
function applyMockStoreDataToGetters() { function applyMockStoreDataToGetters() {
store.getters = { store.getters = {
experimentSettings: mockExperimentSettings, experimentSettings: mockExperimentSettings,
applicationUser: applicationUser,
order: mockStoreData, order: mockStoreData,
damage: mockStoreData.damage, damage: mockStoreData.damage,
payment: mockStoreData.payment, payment: mockStoreData.payment,

View file

@ -48,6 +48,19 @@
validationRules="style-required" validationRules="style-required"
placeHolderText="Select style" placeHolderText="Select style"
customDropdownId="styleQuestionField" /> customDropdownId="styleQuestionField" />
<div v-if="isBigTruckAndCanService">
<div class="separator-line"></div>
<textboxQuestion
class="mb-4"
cmsWidgetName="ServiceZipQuestionWidget"
v-model="serviceZipCode"
inputId="serviceZipCode"
mask="#####"
isRequired
validationRules="zip-required|zip-format" />
</div>
<alert <alert
ref="AlertNoService" ref="AlertNoService"
v-if="displayNoServiceAlert" v-if="displayNoServiceAlert"
@ -76,6 +89,7 @@ import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import alert from "@/ux-components/alert/alert"; import alert from "@/ux-components/alert/alert";
import vehicleQuestion from "@/layouts/vehicle/vehicle-question/vehicle-question"; import vehicleQuestion from "@/layouts/vehicle/vehicle-question/vehicle-question";
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
// Supporting files // Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -87,7 +101,7 @@ import {
} from "@/helpers/heritage-integration/cookie-helper"; } from "@/helpers/heritage-integration/cookie-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { Form, defineRule } from "vee-validate"; import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules"; import { required, regex } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import store from "@/store"; import store from "@/store";
import { storeActions } from "@/constants/store-actions.js"; import { storeActions } from "@/constants/store-actions.js";
@ -102,6 +116,8 @@ defineRule("year-required", required(errorMessages.YEAR_REQUIRED));
defineRule("make-required", required(errorMessages.MAKE_REQUIRED)); defineRule("make-required", required(errorMessages.MAKE_REQUIRED));
defineRule("model-required", required(errorMessages.MODEL_REQUIRED)); defineRule("model-required", required(errorMessages.MODEL_REQUIRED));
defineRule("style-required", required(errorMessages.STYLE_REQUIRED)); defineRule("style-required", required(errorMessages.STYLE_REQUIRED));
defineRule("zip-required", required(errorMessages.ZIP_REQUIRED));
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.ZIP_FORMAT));
export default { export default {
name: "vehicle", name: "vehicle",
@ -113,23 +129,29 @@ export default {
selectedStyle: this.selectedStylefromStore(), selectedStyle: this.selectedStylefromStore(),
carId: this.getCarIdfromStore(), carId: this.getCarIdfromStore(),
category: this.getCategoryfromStore(), category: this.getCategoryfromStore(),
isBigTruck: this.getIsBigTruckfromStore(),
imageUrl: this.getImagefromStore(), imageUrl: this.getImagefromStore(),
imageVifNumber: this.getImageVifNumberfromStore(), imageVifNumber: this.getImageVifNumberfromStore(),
imageVifColor: this.getImageVifColorfromStore(), imageVifColor: this.getImageVifColorfromStore(),
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
yearOptions: [], yearOptions: [],
makeOptions: [], makeOptions: [],
modelOptions: [], modelOptions: [],
styleOptions: [], styleOptions: [],
displayNoServiceAlert: false, displayNoServiceAlert: false,
canSafeliteService: this.canSafeliteServiceFromStore(),
}; };
}, },
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
const log = getQuerystringParameter(queryStrings.LOG); if (store.getters.applicationUser.loggingOption) {
if (log === "true") { console.log(
console.log("----------------- applicationConfig start -----------------"); new Date() + "------------- vehicle.vue applicationConfig start -------------"
);
console.log(new Date() + JSON.stringify(applicationConfig)); console.log(new Date() + JSON.stringify(applicationConfig));
console.log("----------------- applicationConfig end -----------------"); console.log(
new Date() + "------------- vehicle.vue applicationConfig end -------------"
);
} }
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -293,12 +315,17 @@ export default {
}, },
watch: { watch: {
serviceZipCode(newValue, oldValue) {
this.resetAlert();
},
async selectedYear(year) { async selectedYear(year) {
this.resetAlert(); this.resetAlert();
this.makeOptions = []; this.makeOptions = [];
this.selectedMake = null; this.selectedMake = null;
this.selectedModel = null; this.selectedModel = null;
this.selectedStyle = null; this.selectedStyle = null;
this.isBigTruck = false;
this.canSafeliteService = false;
this.carId = null; this.carId = null;
if (year) { if (year) {
const result = await this.getMakeOptions(year); const result = await this.getMakeOptions(year);
@ -318,6 +345,7 @@ export default {
this.modelOptions = []; this.modelOptions = [];
this.selectedModel = null; this.selectedModel = null;
this.selectedStyle = null; this.selectedStyle = null;
this.isBigTruck = null;
this.carId = null; this.carId = null;
if (make) { if (make) {
const result = await this.getModelOptions(this.selectedYear, make); const result = await this.getModelOptions(this.selectedYear, make);
@ -336,6 +364,7 @@ export default {
this.resetAlert(); this.resetAlert();
this.styleOptions = []; this.styleOptions = [];
this.selectedStyle = null; this.selectedStyle = null;
this.isBigTruck = false;
this.carId = null; this.carId = null;
if (model) { if (model) {
const result = await this.getStyleOptions( const result = await this.getStyleOptions(
@ -400,8 +429,17 @@ export default {
!!this.carId !!this.carId
); );
}, },
isBigTruckAndCanService() {
return this.isBigTruck && this.canSafeliteService;
},
}, },
methods: { methods: {
getZipFromStore() {
return (
store.getters?.externalParameterServiceZip?.zipCode ??
store.getters.order?.serviceLocation?.zipCode
);
},
isDisabled(options) { isDisabled(options) {
if ( if (
!options.length || !options.length ||
@ -443,12 +481,16 @@ export default {
this.imageVifNumber = result?.data.imageVifNumber; this.imageVifNumber = result?.data.imageVifNumber;
this.imageVifColor = result?.data.imageVifColor; this.imageVifColor = result?.data.imageVifColor;
this.displayNoServiceAlert = !result?.data.canSafeliteService; this.displayNoServiceAlert = !result?.data.canSafeliteService;
this.canSafeliteService = result?.data.canSafeliteService;
this.vehicleSubType = result?.data.vehicleSubType;
this.vehicleSpecialClass = result?.data.vehicleSpecialClass;
this.isBigTruck = result?.data.isBigTruck;
}, },
resetAlert() { resetAlert() {
this.displayNoServiceAlert = false; this.displayNoServiceAlert = false;
}, },
async forwardButtonAction() { async forwardButtonAction() {
await this.dispatchStoreAction(storeActions.RESET_SUBMITTED_ORDER); await this.dispatchStoreAction(storeActions.RESET_SUBMITTED_STATE);
this.dispatchStoreAction( this.dispatchStoreAction(
storeActions.SAVE_VEHICLE, storeActions.SAVE_VEHICLE,
{ {
@ -456,14 +498,44 @@ export default {
make: this.selectedMake, make: this.selectedMake,
model: this.selectedModel, model: this.selectedModel,
style: this.selectedStyle, style: this.selectedStyle,
vehicleSubType: this.vehicleSubType,
vehicleSpecialClass: this.vehicleSpecialClass,
isBigTruck: this.isBigTruck,
carId: this.carId, carId: this.carId,
category: this.category, category: this.category,
canSafeliteService: this.canSafeliteService,
imageUrl: this.imageUrl, imageUrl: this.imageUrl,
imageVifNumber: this.imageVifNumber, imageVifNumber: this.imageVifNumber,
imageVifColor: this.imageVifColor, imageVifColor: this.imageVifColor,
}, },
false false
); );
if (this.isBigTruck) {
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: this.serviceZipCode,
state: zipCodeData.state,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
);
// check the location endpoint to verify this zip can service a heavy truck
var closestShops = await this.dispatchStoreActionWithLogging(
storeActions.GET_CLOSEST_APPLICABLE_SHOPS,
{ zip: this.serviceZipCode, carId: this.carId },
"vehicle"
);
if (!closestShops || closestShops.data?.providers?.length === 0) {
this.displayNoServiceAlert = true;
return this.$refs.navbar.removeLoader();
}
}
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route); this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}, },
initializeYearComponent(initialData) { initializeYearComponent(initialData) {
@ -489,6 +561,9 @@ export default {
selectedStylefromStore() { selectedStylefromStore() {
return store.getters.externalParameterVehicle.style ?? store.getters.vehicle.style; return store.getters.externalParameterVehicle.style ?? store.getters.vehicle.style;
}, },
canSafeliteServiceFromStore() {
return store.getters.vehicle.canSafeliteService ?? false;
},
async getMakeOptions(year) { async getMakeOptions(year) {
return await baseMixin.methods.dispatchStoreActionWithLogging( return await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_VEHICLE_MAKES, storeActions.GET_VEHICLE_MAKES,
@ -536,6 +611,9 @@ export default {
getImageVifColorfromStore() { getImageVifColorfromStore() {
return store.getters.vehicle.imageVifColor; return store.getters.vehicle.imageVifColor;
}, },
getIsBigTruckfromStore() {
return store.getters.vehicle.isBigTruck;
},
}, },
components: { components: {
@ -545,6 +623,7 @@ export default {
Form, Form,
alert, alert,
vehicleQuestion, vehicleQuestion,
textboxQuestion,
}, },
}; };
</script> </script>
@ -557,4 +636,9 @@ export default {
margin-top: 1.5rem; margin-top: 1.5rem;
margin-bottom: 0; margin-bottom: 0;
} }
.separator-line {
grid-area: 2/1/2/8;
border-top: 1px solid $gray-500;
margin: 0.5rem 0;
}
</style> </style>

View file

@ -337,7 +337,7 @@ export default {
// Recalibration // Recalibration
if (hasSubmittedOrder) { if (hasSubmittedOrder) {
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedOrder; payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnSubmittedState;
} else { } else {
payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder; payload.isRecalibrationOnOrder = store.getters.isRecalibrationOnOrder;
} }

View file

@ -425,7 +425,7 @@ describe("analyticsMixin.js", () => {
workOrderId: "222222222222", workOrderId: "222222222222",
}; };
store.getters.isRecalibrationOnOrder = true; store.getters.isRecalibrationOnOrder = true;
store.getters.isRecalibrationOnSubmittedOrder = false; store.getters.isRecalibrationOnSubmittedState = false;
}); });
test("Pushes to data layer if nominal", () => { test("Pushes to data layer if nominal", () => {

View file

@ -1,6 +1,7 @@
import store from "@/store"; import store from "@/store";
import { storeActions } from "@/constants/store-actions.js"; import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js"; import { storeMutations } from "@/constants/store-mutations.js";
import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { vehicleCategories } from "@/constants/vehicle-categories.js"; import { vehicleCategories } from "@/constants/vehicle-categories.js";
import { routerParams } from "@/router/router-constants/router-params"; import { routerParams } from "@/router/router-constants/router-params";
@ -198,10 +199,26 @@ export default {
} }
}, },
getSubmittedOrder() { getSubmittedOrder() {
return JSON.parse(window.sessionStorage.getItem("submittedOrder")); return JSON.parse(
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
)?.order;
}, },
hasSubmittedOrder() { hasSubmittedOrder() {
return window.sessionStorage.getItem("submittedOrder") !== null; const submittedState = window.sessionStorage.getItem(
sessionStorageKeyConstants.SUBMITTED_STATE
);
return submittedState !== null && submittedState.order !== null;
},
getSubmittedApplicationUser() {
return JSON.parse(
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
)?.applicationUser;
},
hasSubmittedApplicationUser() {
const submittedState = window.sessionStorage.getItem(
sessionStorageKeyConstants.SUBMITTED_STATE
);
return submittedState !== null && submittedState.applicationUser !== null;
}, },
}, },
computed: { computed: {

View file

@ -1,17 +1,36 @@
import store from "@/store"; import store from "@/store";
import * as experimentHelper from "../helpers/experiment-helper";
/*
This is a wrapper around experiment-helper.js methods that allows an override
to provide a custom list of experiments. At the time of implementation this
was necessary for checking submittedState for experiments
*/
export default { export default {
methods: { methods: {
hasSettingEqualTo(settingName, settingValue) { hasSettingEqualTo(
return store.getters.experimentSettings[settingName] == settingValue; settingName,
settingValue,
experimentList = store.getters.applicationUser.experiments
) {
const experimentSettings =
experimentHelper.getExperimentSettingsFromExperimentList(experimentList);
return experimentHelper.hasSettingEqualTo(
settingName,
settingValue,
experimentSettings
);
}, },
hasSetting(settingName) { hasSetting(settingName, experimentList = store.getters.applicationUser.experiments) {
return Object.hasOwn(store.getters.experimentSettings, settingName); const experimentSettings =
experimentHelper.getExperimentSettingsFromExperimentList(experimentList);
return experimentHelper.hasSetting(settingName, experimentSettings);
}, },
getSettingValue(settingName) { getSettingValue(settingName, experimentList = store.getters.applicationUser.experiments) {
return this.hasSetting(settingName) const experimentSettings =
? store.getters.experimentSettings[settingName] experimentHelper.getExperimentSettingsFromExperimentList(experimentList);
: null; return experimentHelper.getSettingValue(settingName, experimentSettings);
}, },
}, },
}; };

View file

@ -135,6 +135,15 @@ function setupMocks({ experimentSettings }) {
store.getters = { store.getters = {
experimentSettings: experimentSettings ?? testExperimentSettings, experimentSettings: experimentSettings ?? testExperimentSettings,
applicationUser: {
experiments: [
{
isActive: true,
isExposed: true,
settings: experimentSettings ?? testExperimentSettings,
},
],
},
}; };
const mockComponent = { const mockComponent = {

View file

@ -2,6 +2,7 @@
import { createWebHistory, createRouter } from "vue-router"; import { createWebHistory, createRouter } from "vue-router";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "../constants/store-mutations"; import { storeMutations } from "../constants/store-mutations";
import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { routingTable } from "@/router/router-constants/routing-table.js"; import { routingTable } from "@/router/router-constants/routing-table.js";
import { globalEvents, globalEventTypes } from "@/constants/events"; import { globalEvents, globalEventTypes } from "@/constants/events";
@ -98,10 +99,15 @@ const routes = [
log(" --to.query.fmgPage ", to.query?.fmgPage); log(" --to.query.fmgPage ", to.query?.fmgPage);
// Intercept all navigation if a submitted order exists in storage // Intercept all navigation if a submitted order exists in storage
if (window.sessionStorage.getItem("submittedOrder") !== null) { if (
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !==
null
) {
if (to.query.fmgPage !== funnelStartPageName) { if (to.query.fmgPage !== funnelStartPageName) {
to.query.fmgPage = fmgPageValues.CONFIRMATION; to.query.fmgPage = fmgPageValues.CONFIRMATION;
log(" --has submittedOrder go to confirmation"); log(
` --has ${sessionStorageKeyConstants.SUBMITTED_STATE} go to confirmation`
);
} }
} }
// On entering the funnel "fresh", read cookie information, decide what to do next. // On entering the funnel "fresh", read cookie information, decide what to do next.

View file

@ -1,6 +1,7 @@
import { createStore } from "vuex"; import { createStore } from "vuex";
import { endpoints } from "@/constants/endpoints.js"; import { endpoints } from "@/constants/endpoints.js";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import { sessionStorageKeyConstants } from "@/constants/session-storage.js";
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper"; import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper";
import createPersistedState from "vuex-persistedstate"; import createPersistedState from "vuex-persistedstate";
import globalMethods from "@/global-methods"; import globalMethods from "@/global-methods";
@ -20,6 +21,10 @@ import {
} from "@/constants/schedule-constants"; } from "@/constants/schedule-constants";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import {
getExperimentSettingsFromExperimentList,
hasSettingEqualTo,
} from "../helpers/experiment-helper";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { import {
@ -61,9 +66,13 @@ const getDefaultState = () => {
make: null, make: null,
model: null, model: null,
style: null, style: null,
vehicleSubType: null,
vehicleSpecialClass: null,
isBigTruck: false,
carId: null, carId: null,
category: null, category: null,
vin: null, vin: null,
canSafeliteService: null,
imageUrl: null, imageUrl: null,
imageVifNumber: null, imageVifNumber: null,
imageColor: null, imageColor: null,
@ -211,12 +220,24 @@ export const mutations = {
updateStyle(state, style) { updateStyle(state, style) {
state.order.vehicle.style = style; state.order.vehicle.style = style;
}, },
updateVehicleSubType(state, vehicleSubType) {
state.order.vehicle.vehicleSubType = vehicleSubType;
},
updateVehicleSpecialClass(state, vehicleSpecialClass) {
state.order.vehicle.vehicleSpecialClass = vehicleSpecialClass;
},
updateIsBigTruck(state, isBigTruck) {
state.order.vehicle.isBigTruck = isBigTruck;
},
updateCarId(state, carId) { updateCarId(state, carId) {
state.order.vehicle.carId = carId; state.order.vehicle.carId = carId;
}, },
updateVehicleCategory(state, category) { updateVehicleCategory(state, category) {
state.order.vehicle.category = category; state.order.vehicle.category = category;
}, },
updateVehicleCanSafeliteService(state, canSafeliteService) {
state.order.vehicle.canSafeliteService = canSafeliteService;
},
updateVehicleImageUrl(state, imageUrl) { updateVehicleImageUrl(state, imageUrl) {
state.order.vehicle.imageUrl = imageUrl; state.order.vehicle.imageUrl = imageUrl;
}, },
@ -822,8 +843,10 @@ export const getters = {
}, },
coverageIsVerified: (state) => { coverageIsVerified: (state) => {
let order; let order;
if (window.sessionStorage.getItem("submittedOrder") !== null) { if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
order = JSON.parse(window.sessionStorage.getItem("submittedOrder")); order = JSON.parse(
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
).order;
} else { } else {
order = state.order; order = state.order;
} }
@ -850,31 +873,34 @@ export const getters = {
isRecalibrationOnOrder: (state) => { isRecalibrationOnOrder: (state) => {
return getHasRecalibrationPart(state); return getHasRecalibrationPart(state);
}, },
isRecalibrationOnSubmittedOrder: (state) => { isRecalibrationOnSubmittedState: (state) => {
if (window.sessionStorage.getItem("submittedOrder") !== null) { if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
return getHasRecalibrationPart({ return getHasRecalibrationPart({
order: JSON.parse(window.sessionStorage.getItem("submittedOrder")), order: JSON.parse(
window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
).order,
}); });
} }
return false; return false;
}, },
shouldHideRecalibration: (state) => { shouldHideRecalibration: (state) => {
var order; if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
if (window.sessionStorage.getItem("submittedOrder") !== null) { state = JSON.parse(
order = JSON.parse(window.sessionStorage.getItem("submittedOrder")); window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
} else { );
order = state.order;
} }
let order = state.order;
let applicationUser = state.applicationUser;
if (order.payment.isInsurance) { if (order.payment.isInsurance) {
return false; return false;
} }
return ( return (
experimentMixin.methods hasSettingEqualTo(
.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE) experimentSettings.RECAL_PRICE_REMOVE,
?.toLowerCase() === "true" && getters.isRecalibrationOnOrder(state) "true",
getExperimentSettingsFromExperimentList(applicationUser.experiments)
) && getters.isRecalibrationOnOrder(state)
); );
}, },
areRearWipersOnOrder: (state) => { areRearWipersOnOrder: (state) => {
@ -974,10 +1000,7 @@ export const getters = {
}; };
}, },
experimentSettings: (state) => experimentSettings: (state) =>
state.applicationUser.experiments getExperimentSettingsFromExperimentList(state.applicationUser.experiments),
.filter((x) => !!x.isActive)
.map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
isVerifiedAndDeductibleZeroConfirmed: (state) => { isVerifiedAndDeductibleZeroConfirmed: (state) => {
// used in CMS on /payment-method in Header Sub Text, for FunnelSubHeaderWidget on cart pages // used in CMS on /payment-method in Header Sub Text, for FunnelSubHeaderWidget on cart pages
@ -1241,6 +1264,15 @@ export const actions = {
}); });
}, },
getClosestApplicableShops(context, { payload: { zip, carId }, pageNameToLog }) {
return globalMethods.callHttpClient({
methods: endpoints.ClosestApplicableShops.method,
endpoint: `${endpoints.ClosestApplicableShops.url}/${zip}/${carId}`,
logApiCall: true,
pageNameToLog: pageNameToLog,
});
},
// Dependency Actions // Dependency Actions
resetVehicleStateAndDependencies(context) { resetVehicleStateAndDependencies(context) {
context.commit(storeMutations.RESET_VEHICLE_STATE); context.commit(storeMutations.RESET_VEHICLE_STATE);
@ -1828,6 +1860,8 @@ export const actions = {
endPoint += `&${glassPieces}`; endPoint += `&${glassPieces}`;
} }
endPoint += `&isHeavyTruckVehicle=${context.getters.order.vehicle.isBigTruck}`;
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetServiceabilityDetails.method, method: endpoints.GetServiceabilityDetails.method,
endpoint: endPoint, endpoint: endPoint,
@ -1854,6 +1888,8 @@ export const actions = {
if (windshieldPartWithRecal) { if (windshieldPartWithRecal) {
url += `/${windshieldPartWithRecal.partNumber}`; url += `/${windshieldPartWithRecal.partNumber}`;
} }
//heavy truck vehicle
url += `/${context.getters.order.vehicle.isBigTruck}`;
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetProviders.method, method: endpoints.GetProviders.method,
@ -1892,96 +1928,48 @@ export const actions = {
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
}); });
}, },
submitDonationPartToSV2( async submitDonationPartToSV2(
context, context,
{ payload: { donationAmount, submittedOrder }, pageNameToLog } { payload: { donationAmount, submittedOrder }, pageNameToLog }
) { ) {
donationAmount = parseInt(donationAmount); const state = JSON.parse(
/* Request contract window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE)
{ );
"donationAmount": 5, const order = state.order;
"savedSessionId": "96b1a16e-410a-4d08-8421-fe36e82b2275", const payload = {
"paymentInfo": { donationAmount: parseInt(donationAmount),
"parentAccountNumber": 167132, savedSessionId: state.applicationUser.savedSessionId,
"billToAccountNumber": 87291, paymentInfo: {
"providerNumber": "005819", parentAccountNumber: order.payment.parentAccountNumber,
"carId": "CR00069266", billToAccountNumber: order.payment.billToAccountNumber,
"make": "Acura", providerNumber: order.serviceLocation.provider.providerNumber,
"model": "MDX", carId: order.vehicle.carId,
"year": 2020, make: order.vehicle.make,
"eon": "S1820397", model: order.vehicle.model,
"zipCode": "43015", year: order.vehicle.year,
"state": "OH", eon: order.eon,
"referralNumber": "1821895", zipCode: order.serviceLocation.zipCode,
"referralDate": "2025-03-20T16:27:38.93", state: order.serviceLocation.state,
"referralSequenceNumber": "1821895", referralNumber: order.referralNumber,
"serviceZipCode": "43015", referralDate: order.referralDate,
"isReplacement": true, referralSequenceNumber: order.referralSequenceNumber,
"deductible": 0, serviceZipCode: order.serviceLocation.zipCode,
"coverageStatus": null, isReplacement: !order.damage.isRepair,
"coverageSubStatus": null, deductible: order.policy.currentDeductible,
"serverData": "ZtCNOF1tCLQBAx2NRKHE/n/fqD8zUxRfYtDuTu7uUiPpeg7i6TmwXwggssX5KsXVQbeft+PN+vXYfBFJytLVYgbafNDR0JH3izVTIOh7KGTeB6YaPuCJmodF4SfgVVDHD9LEmEH9wNzM9j3pFjgfXehOFH11SH9dNwTkXIbsGzAeu64DRyJsDdpamM/BPOxlJj60S/R8vnUM8mjyM7B9a63NskFI6BCVIh5mxDE4cSMUgb4zFtwvDgTPxJSwXqKShtoY6ryB8n5UPNkG/Z5tjVuJ7/8RV7WrURM7PN7xLfp6pVEeipWyeFnVEK33G9JOuB6yH75jPO6vMCYRNi0p0pg6laasFDy3hmfuy2W/N0qtcJLVa5N0bUMFFuPQusyEeqsEZ/zx1Srlvsk7i0DF8pTyBcm3JTF5BOWm60wg3ntEOqfRuQqzFc5updILwLAIp39CwJG/fs5Mj3HCDcUKwYlK70fAARtJwFu7wI0vqW5bUSUvJkvm1JRPlyWJ8JNY4Oq1apYqhZS9bps5ofgiOYJbBhPEboEnVukoTkQiYjgbMtBwVGKGL2nNEd77osoEg1KXhEQS+0KD3PoUu/bZkGub6FNGE6iKeFPt9gDSs+S21s22rVSaZkgaoC6SkufPpN6aV78Jx69nMIDd4go4pj2IJX4kS1pD1NEOSWjN4bgVKAtV6/bxBvhNJwnqUE+mE8eAmULsezub4LMhPqA58y/ZlO44jt/qkAw9nuVDRPBve+TdJTotID5dIqJJd6/vkDoHTVaSrbkCBbdG6d9By8AEbKuVrQS/+VurUnYEd2TGahPbOyRdNlud8RB4+4af0ZRFmiKkr6ppa5IbHbTIZ2qtyeOSbIOkv8O7xK/GL6e6Iaaz0wDHaQZC4Ea8FczjpFOlQgixfYcYJBb/W2db+uRvZ3lftVyJ/d082CGbYplOa+aTxymVcQSjBRbXzs6GZ2ytDqRYcRe94F9x/XSVj7g+mih7VS+YH0eOEp0ViTuWP8B1dVnOV5yTkdMbr8n9Q82G4v5brtQdiMDMY3HS6mxPb1IiKiqFX6Jgju8sIMxkJFZQ3iIkAJuOqUYkG1ZW7s4uI6hT46bRqzYaQA18CFu56qaMblOBFuxlJIsYsnMePh2xAH89oay5Kc88xwT2T/4+dzYQ0CqhQ4MTBB7nXelav2rMCjaLQ/V/a5r1TnLr2n0GbvODQeNl97qzi5lDYKlnuF8nOfOqZNOsPSsrsk3UZJJYZwNxDZaweHIeeg5P2ZW5agswPx/U5DkU1MzE1VAQ+DQAenaE2SLo4dSPBHMmt2gLpgu0LaCuzTR2IL/EM+Tpdn/Pcv1E7lPJIycIIwO7QGJcVkwCx8CJ/WCo6xDMq+9zjSVbqAaL5MzySZCeFCMyZxvEk7qXig6rflB3K3Jay4qSFjBxBXhOEuT6zKXyhR41IXRtZRmtPkscypFEUXvR5nJV7AQUIbljGEcikOLmSiCEnl8dI2Ny9xCHLOro8UXqVUy8dHGmTRGTE/mS1JNZ3y4XaZSyHcABVWbZcR3KIXVxd6rtUA/pBvWpbq8nH3WWlJGqkoxUVEridtXqcBuzTKg3/i6vLs0/pHGwdM38XizKcIxykvJrur3vEQ==" coverageStatus: order.payment.insuranceCoverage.coverageStatus,
} coverageSubStatus: order.payment.insuranceCoverage.coverageSubStatus,
} serverData: order.lineItems.serverData,
*/ },
};
/* Uncomment and use when endpoint integration is finalized const response = await globalMethods.callHttpClient({
return globalMethods.callHttpClient({
method: endpoints.AddDonationPart.method, method: endpoints.AddDonationPart.method,
endpoint: endpoints.AddDonationPart.url, endpoint: endpoints.AddDonationPart.url,
payload: { payload: payload,
donationAmount: donationAmount,
savedSessionId: "96b1a16e-410a-4d08-8421-fe36e82b2275",
paymentInfo: {
parentAccountNumber: submittedSessionData.parentAccountNumber,
billToAccountNumber: submittedSessionData.billToAccountNumber,
providerNumber: submittedSessionData.providerNumber,
carId: "CR00069266",
make: "Acura",
model: "MDX",
year: 2020,
eon: "S1820397",
zipCode: "43015",
state: "OH",
referralNumber: "1821895",
referralDate: "2025-03-20T16:27:38.93",
referralSequenceNumber: "1821895",
serviceZipCode: "43015",
isReplacement: true,
deductible: 0,
coverageStatus: null,
coverageSubStatus: null,
serverData: "ZtCNOF1tCLQBAx2NRKHE/n/fqD8zUxRfYtDuTu7uUiPpeg7i6TmwXwggssX5KsXVQbeft+PN+vXYfBFJytLVYgbafNDR0JH3izVTIOh7KGTeB6YaPuCJmodF4SfgVVDHD9LEmEH9wNzM9j3pFjgfXehOFH11SH9dNwTkXIbsGzAeu64DRyJsDdpamM/BPOxlJj60S/R8vnUM8mjyM7B9a63NskFI6BCVIh5mxDE4cSMUgb4zFtwvDgTPxJSwXqKShtoY6ryB8n5UPNkG/Z5tjVuJ7/8RV7WrURM7PN7xLfp6pVEeipWyeFnVEK33G9JOuB6yH75jPO6vMCYRNi0p0pg6laasFDy3hmfuy2W/N0qtcJLVa5N0bUMFFuPQusyEeqsEZ/zx1Srlvsk7i0DF8pTyBcm3JTF5BOWm60wg3ntEOqfRuQqzFc5updILwLAIp39CwJG/fs5Mj3HCDcUKwYlK70fAARtJwFu7wI0vqW5bUSUvJkvm1JRPlyWJ8JNY4Oq1apYqhZS9bps5ofgiOYJbBhPEboEnVukoTkQiYjgbMtBwVGKGL2nNEd77osoEg1KXhEQS+0KD3PoUu/bZkGub6FNGE6iKeFPt9gDSs+S21s22rVSaZkgaoC6SkufPpN6aV78Jx69nMIDd4go4pj2IJX4kS1pD1NEOSWjN4bgVKAtV6/bxBvhNJwnqUE+mE8eAmULsezub4LMhPqA58y/ZlO44jt/qkAw9nuVDRPBve+TdJTotID5dIqJJd6/vkDoHTVaSrbkCBbdG6d9By8AEbKuVrQS/+VurUnYEd2TGahPbOyRdNlud8RB4+4af0ZRFmiKkr6ppa5IbHbTIZ2qtyeOSbIOkv8O7xK/GL6e6Iaaz0wDHaQZC4Ea8FczjpFOlQgixfYcYJBb/W2db+uRvZ3lftVyJ/d082CGbYplOa+aTxymVcQSjBRbXzs6GZ2ytDqRYcRe94F9x/XSVj7g+mih7VS+YH0eOEp0ViTuWP8B1dVnOV5yTkdMbr8n9Q82G4v5brtQdiMDMY3HS6mxPb1IiKiqFX6Jgju8sIMxkJFZQ3iIkAJuOqUYkG1ZW7s4uI6hT46bRqzYaQA18CFu56qaMblOBFuxlJIsYsnMePh2xAH89oay5Kc88xwT2T/4+dzYQ0CqhQ4MTBB7nXelav2rMCjaLQ/V/a5r1TnLr2n0GbvODQeNl97qzi5lDYKlnuF8nOfOqZNOsPSsrsk3UZJJYZwNxDZaweHIeeg5P2ZW5agswPx/U5DkU1MzE1VAQ+DQAenaE2SLo4dSPBHMmt2gLpgu0LaCuzTR2IL/EM+Tpdn/Pcv1E7lPJIycIIwO7QGJcVkwCx8CJ/WCo6xDMq+9zjSVbqAaL5MzySZCeFCMyZxvEk7qXig6rflB3K3Jay4qSFjBxBXhOEuT6zKXyhR41IXRtZRmtPkscypFEUXvR5nJV7AQUIbljGEcikOLmSiCEnl8dI2Ny9xCHLOro8UXqVUy8dHGmTRGTE/mS1JNZ3y4XaZSyHcABVWbZcR3KIXVxd6rtUA/pBvWpbq8nH3WWlJGqkoxUVEridtXqcBuzTKg3/i6vLs0/pHGwdM38XizKcIxykvJrur3vEQ=="
}
},
logApiCall: true, logApiCall: true,
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
}); });
*/ return response.data;
/* Response contract
{
"wasSuccessful": true,
"wasLocked": true
}
*/
let result;
switch (donationAmount) {
case 1:
result = { wasSuccessful: true, wasLocked: false };
break;
case 3:
result = { wasSuccessful: false, wasLocked: true };
break;
case 5:
result = { wasSuccessful: false, wasLocked: false };
break;
default:
result = { wasSuccessful: null, wasLocked: null }; // Default case if amount doesn't match
}
return result;
}, },
getPartFromCapabilityQuestionAnswer(context, { payload, pageNameToLog }) { getPartFromCapabilityQuestionAnswer(context, { payload, pageNameToLog }) {
@ -2176,6 +2164,9 @@ export const actions = {
year: vehicle.year, year: vehicle.year,
make: vehicle.make, make: vehicle.make,
model: vehicle.model, model: vehicle.model,
subType: vehicle.vehicleSubType,
specialClass: vehicle.vehicleSpecialClass,
isBigTruck: vehicle.isBigTruck,
isInsurance: order.payment.isInsurance ?? false, isInsurance: order.payment.isInsurance ?? false,
isVerified: order.payment.insuranceCoverage.isVerified ?? false, isVerified: order.payment.insuranceCoverage.isVerified ?? false,
lineItems: { lineItems: {
@ -2229,6 +2220,9 @@ export const actions = {
model: vehicle.model, model: vehicle.model,
style: vehicle.style, style: vehicle.style,
vin: vehicle.vin, vin: vehicle.vin,
vehicleSubType: vehicle.vehicleSubType,
vehicleSpecialClass: vehicle.vehicleSpecialClass,
isBigTruck: vehicle.isBigTruck,
registration: { registration: {
firstName: vehicle.registration.firstName, firstName: vehicle.registration.firstName,
lastName: vehicle.registration.lastName, lastName: vehicle.registration.lastName,
@ -2439,7 +2433,21 @@ export const actions = {
// Vehicle // Vehicle
saveVehicle( saveVehicle(
context, context,
{ year, make, model, style, carId, category, imageUrl, imageVifNumber, imageVifColor } {
year,
make,
model,
style,
vehicleSubType,
vehicleSpecialClass,
isBigTruck,
carId,
category,
canSafeliteService,
imageUrl,
imageVifNumber,
imageVifColor,
}
) { ) {
if ( if (
context.state.order.vehicle.year != year || context.state.order.vehicle.year != year ||
@ -2455,8 +2463,12 @@ export const actions = {
context.commit(storeMutations.UPDATE_MAKE, make); context.commit(storeMutations.UPDATE_MAKE, make);
context.commit(storeMutations.UPDATE_MODEL, model); context.commit(storeMutations.UPDATE_MODEL, model);
context.commit(storeMutations.UPDATE_STYLE, style); context.commit(storeMutations.UPDATE_STYLE, style);
context.commit(storeMutations.UPDATE_VEHICLE_SUBTYPE, vehicleSubType);
context.commit(storeMutations.UPDATE_VEHICLE_SPECIAL_CLASS, vehicleSpecialClass);
context.commit(storeMutations.UPDATE_IS_BIGTRUCK, isBigTruck);
context.commit(storeMutations.UPDATE_CAR_ID, carId); context.commit(storeMutations.UPDATE_CAR_ID, carId);
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category); context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category);
context.commit(storeMutations.UPDATE_VEHICLE_CAN_SAFELITE_SERVICE, canSafeliteService);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor);
@ -3257,19 +3269,25 @@ export const actions = {
return false; return false;
}, },
createSubmittedOrder(context) { createSubmittedState(context) {
if (window.sessionStorage.getItem("submittedOrder") !== null) { if (window.sessionStorage.getItem(sessionStorageKeyConstants.SUBMITTED_STATE) !== null) {
return; return;
} }
// create a submitted order object from vuex. // create a submitted order object from vuex.
const submittedOrder = context.state.order; const submittedState = {
order: context.state.order,
applicationUser: context.state.applicationUser,
};
const experiments = context.state.applicationUser.experiments; const experiments = context.state.applicationUser.experiments;
const affiliateCookies = context.state.applicationUser.affiliateCookies; const affiliateCookies = context.state.applicationUser.affiliateCookies;
// set to local storage // set to local storage
window.sessionStorage.setItem("submittedOrder", JSON.stringify(submittedOrder)); window.sessionStorage.setItem(
sessionStorageKeyConstants.SUBMITTED_STATE,
JSON.stringify(submittedState)
);
window.sessionStorage.setItem("createNewSessionForHeritage", true); window.sessionStorage.setItem("createNewSessionForHeritage", true);
// clear vuex // clear vuex
@ -3288,45 +3306,38 @@ export const actions = {
context.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies); context.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies);
}, },
addDonationToSubmittedOrder(context, donationAmount) { addDonationToSubmittedState(context, donationAmount) {
console.log("running addDonationToSubmittedOrder()... donationAmount: ", donationAmount);
const submittedOrder = deepClone(baseMixin.methods.getSubmittedOrder()); const submittedOrder = deepClone(baseMixin.methods.getSubmittedOrder());
const submittedApplicationUser = deepClone(baseMixin.methods.getSubmittedApplicationUser());
if (donationAmount > 0) { submittedOrder.lineItems.supportingItems.push({
console.log( partNumber: `DONATION_${donationAmount}`,
"running addDonationToSubmittedOrder()... adding donation to supportingItems in session storage " description: `DONATION $${donationAmount}`,
); partType: partTypeStrings.DONATION,
submittedOrder.lineItems.supportingItems.push({ isInsurable: false,
partNumber: `DONATION_${donationAmount}`, isChildPart: false,
description: `DONATION $${donationAmount}`, laborAmount: 0,
partType: partTypeStrings.DONATION, sellingPrice: donationAmount,
isInsurable: false, kitPrice: 0,
isChildPart: false, salesTax: 0,
laborAmount: 0, });
sellingPrice: donationAmount,
kitPrice: 0,
salesTax: 0,
id: `donation-${donationAmount}`,
});
} else {
// filter out all donations
console.log(
"running addDonationToSubmittedOrder()... removing all donation items in supportingItems in session storage "
);
const nonDonationItems = submittedOrder.lineItems.supportingItems.filter((item) => { addGuidToLineItemsIfNotAlreadyThere(submittedOrder.lineItems.supportingItems);
return item.partType !== "DONATION";
}); const submittedState = {
submittedOrder.lineItems.supportingItems = nonDonationItems; order: submittedOrder,
} applicationUser: submittedApplicationUser,
};
// set to local storage // set to local storage
window.sessionStorage.setItem("submittedOrder", JSON.stringify(submittedOrder)); window.sessionStorage.setItem(
sessionStorageKeyConstants.SUBMITTED_STATE,
JSON.stringify(submittedState)
);
}, },
resetSubmittedOrder(context) { resetSubmittedState(context) {
// clear from local storage // clear from local storage
window.sessionStorage.removeItem("submittedOrder"); window.sessionStorage.removeItem(sessionStorageKeyConstants.SUBMITTED_STATE);
}, },
resetExternalParameterState(context) { resetExternalParameterState(context) {
if (context.getters.isExternalParameter) { if (context.getters.isExternalParameter) {