Merge branch 'release/heavy.truck' into rlsmerge/develop-to-heavy.truck

This commit is contained in:
CarlNation 2025-04-04 06:38:41 -04:00
commit ef10fb3be7
18 changed files with 345 additions and 98 deletions

View file

@ -5,6 +5,7 @@ const partNumberStrings = {
DONATION: "DONATION", DONATION: "DONATION",
// Fees // Fees
RECYCLE_FEE: "RECYCLE FEE", RECYCLE_FEE: "RECYCLE FEE",
PRICING_BY_DAY_UPCHARGE: "PREMAPPT GEN",
}; };
export { partNumberStrings }; export { partNumberStrings };

View file

@ -8,8 +8,7 @@ const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM";
const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";
// TODO; This will need updating with real PricingByDay part type const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN";
const PRICING_BY_DAY_PART_TYPE = "DISC CASHSAVE20";
const RouteCodeFlags = { const RouteCodeFlags = {
ALL_DAY_DROP_OFF: "ALL DAY DROP OFF", ALL_DAY_DROP_OFF: "ALL DAY DROP OFF",

View file

@ -10,6 +10,8 @@ const storeMutations = {
UPDATE_MODEL: "updateModel", UPDATE_MODEL: "updateModel",
UPDATE_STYLE: "updateStyle", UPDATE_STYLE: "updateStyle",
UPDATE_VEHICLE_SUBTYPE: "updateVehicleSubType", 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",

View file

@ -9,9 +9,3 @@ const vehicleCategories = {
}; };
export { vehicleCategories }; export { vehicleCategories };
const vehicleSubTypes = {
HEAVY_TRUCK: "HEAVY TRUCK",
};
export { vehicleSubTypes };

View file

@ -26,43 +26,36 @@ const DAYS_OF_WEEK = [
{ {
label: "Sunday", label: "Sunday",
cssClass: "sunday", cssClass: "sunday",
index: 0, isPricingByDayUpchargeDay: true,
isPricingByDayUpchargeDay: false,
}, },
{ {
label: "Monday", label: "Monday",
cssClass: "monday", cssClass: "monday",
index: 1,
isPricingByDayUpchargeDay: true, isPricingByDayUpchargeDay: true,
}, },
{ {
label: "Tuesday", label: "Tuesday",
cssClass: "tuesday", cssClass: "tuesday",
index: 2,
isPricingByDayUpchargeDay: false, isPricingByDayUpchargeDay: false,
}, },
{ {
label: "Wednesday", label: "Wednesday",
cssClass: "wednesday", cssClass: "wednesday",
index: 3,
isPricingByDayUpchargeDay: false, isPricingByDayUpchargeDay: false,
}, },
{ {
label: "Thursday", label: "Thursday",
cssClass: "thursday", cssClass: "thursday",
index: 4,
isPricingByDayUpchargeDay: false, isPricingByDayUpchargeDay: false,
}, },
{ {
label: "Friday", label: "Friday",
cssClass: "friday", cssClass: "friday",
index: 5,
isPricingByDayUpchargeDay: true, isPricingByDayUpchargeDay: true,
}, },
{ {
label: "Saturday", label: "Saturday",
cssClass: "saturday", cssClass: "saturday",
index: 6,
isPricingByDayUpchargeDay: true, isPricingByDayUpchargeDay: true,
}, },
]; ];

View file

@ -1,5 +1,7 @@
<template> <template>
<div class="date-picker text-center" :class="`${calendarViewDirection} ${isPricingByDayClass}`"> <div
class="date-picker text-center"
:class="`${calendarViewDirection} ${isPricingByDayClass} ${showPricingByDayClass}`">
<fieldset id="date-picker-fieldset" ref="datePickerFieldset"> <fieldset id="date-picker-fieldset" ref="datePickerFieldset">
<legend class="sr-only">Select a day and time</legend> <legend class="sr-only">Select a day and time</legend>
<div <div
@ -60,6 +62,7 @@
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '', date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
date.dayClasses, date.dayClasses,
date.isSelectable ? 'selectable-day' : '', date.isSelectable ? 'selectable-day' : '',
date.isPricingByDayUpchargeDay ? 'upch-day' : '',
]"> ]">
<input <input
:disabled="!date.isSelectable" :disabled="!date.isSelectable"
@ -220,6 +223,9 @@ export default {
isPricingByDayClass() { isPricingByDayClass() {
return this.isPricingByDayExperiment ? "pricing-by-day" : ""; return this.isPricingByDayExperiment ? "pricing-by-day" : "";
}, },
showPricingByDayClass() {
return this.showPricingByDay ? "show-pricing-by-day" : "";
},
selectedDate: { selectedDate: {
get() { get() {
return this.modelValue; return this.modelValue;
@ -1143,8 +1149,8 @@ export default {
label { label {
background-color: $white; background-color: $white;
border: none; border: none;
min-width: initial; min-width: 100%;
width: initial; width: 100%;
height: -webkit-fill-available; height: -webkit-fill-available;
border-radius: 0; border-radius: 0;
cursor: pointer; cursor: pointer;
@ -1163,6 +1169,35 @@ export default {
} }
} }
} }
&.date-picker.show-pricing-by-day {
.radio-wrapper {
&.selectable-day label span {
text-decoration: none;
color: $gray-600;
font-weight: 400;
font-family: "AvertaSemibold";
&.price {
color: $green;
}
}
&.upch-day label span.price {
color: $gray-600;
font-weight: 400;
font-family: $font-family-sans-serif;
}
input[type="radio"] {
&:focus + label,
&:checked:focus + label {
color: $gray-600;
}
&:checked + label {
color: $blue;
}
}
}
}
} }
.btn-link { .btn-link {

View file

@ -526,17 +526,9 @@ export default {
// try to call API to add donation to order // try to call API to add donation to order
// TEMP DUMMY CALL TO SIMULATE SUCCESS OR ERROR FROM API CALL // TEMP DUMMY CALL TO SIMULATE SUCCESS OR ERROR FROM API CALL
// TO BE REPLACED BY REAL CALL LATER const donationResponse = this.createMockedResponse(amount);
const donationResponse = await this.dispatchStoreActionWithLogging(
storeActions.LOOKUP_VIN_BY_PLATE,
{ licensePlate: plateTemp, licenseState: "OH" },
"test",
false
).catch((error) => {
return error;
});
if (donationResponse?.status == "200") { if (donationResponse?.wasSuccessful) {
this.showDonationSuccess = true; this.showDonationSuccess = true;
this.showDonationError = false; this.showDonationError = false;
const donationResponse = await baseMixin.methods.dispatchStoreAction( const donationResponse = await baseMixin.methods.dispatchStoreAction(
@ -554,6 +546,26 @@ export default {
); );
} }
}, },
//mocked response. should be removed once integrate with actual api call.
createMockedResponse(amount) {
let result;
switch (amount) {
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;
},
}, },
watch: { watch: {
donationAmount(newValue) { donationAmount(newValue) {

View file

@ -54,7 +54,7 @@
:estimatedServiceMinutesMaximum=" :estimatedServiceMinutesMaximum="
selectableDatesData.estimatedServiceMinutesMaximum selectableDatesData.estimatedServiceMinutesMaximum
" "
@waitListRequested-emitted="handleWaitListRequested" @waitListRequested="handleWaitListRequested"
@time-slot-modal-closed="timeSlotModalClosed" @time-slot-modal-closed="timeSlotModalClosed"
validationRules="time-slot-selection-required" validationRules="time-slot-selection-required"
@TimeSlotSelected="forwardButtonAction" /> @TimeSlotSelected="forwardButtonAction" />
@ -250,9 +250,15 @@ export default {
isRecalibrationOnOrder && shouldHideRecalibration isRecalibrationOnOrder && shouldHideRecalibration
? getItemsWithoutRecalParts(lineItems.glassParts) ? getItemsWithoutRecalParts(lineItems.glassParts)
: (lineItems.glassParts ?? []); : (lineItems.glassParts ?? []);
const supportingItemsFromStore = lineItems?.supportingItems;
const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers( const supportingItemsWithoutFees = baseMixin.methods.filterOutCertainPartTypesOrNumbers(
lineItems.supportingItems, lineItems.supportingItems,
{ partNumbersToRemove: [partNumberStrings.RECYCLE_FEE] } {
partNumbersToRemove: [
partNumberStrings.RECYCLE_FEE,
partNumberStrings.PRICING_BY_DAY_UPCHARGE,
],
}
); );
const lineItemsToBePriced = { const lineItemsToBePriced = {
glassParts: glassParts, glassParts: glassParts,
@ -266,8 +272,8 @@ export default {
// 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;
if (supportingItemsWithoutFees) { if (supportingItemsFromStore) {
const pricingByDayUpchargeFeeIndex = supportingItemsWithoutFees.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) {
@ -300,7 +306,9 @@ export default {
}); });
// Get pricingByDayUpcharge needed for Pricing By Day // Get pricingByDayUpcharge needed for Pricing By Day
const pricingByDayUpchargePartPromise = getPricingByDayPartWithPrice(); const pricingByDayUpchargePartPromise = showPricingByDay
? getPricingByDayPartWithPrice()
: null;
const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging( const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_MOBILE_PREMIUM_FEE, storeActions.GET_MOBILE_PREMIUM_FEE,
@ -348,10 +356,12 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const pricingByDayUpcharge = await baseMixin.methods.getTotalLineItemPrice( const pricingByDayUpcharge = showPricingByDay
resultMap.pricingByDayUpchargePart, ? await baseMixin.methods.getTotalLineItemPrice(
false resultMap.pricingByDayUpchargePart,
); false
)
: null;
const datePickerInitialData = resultMap.datePickerInitialData; const datePickerInitialData = resultMap.datePickerInitialData;
datePickerInitialData.pricingByDayBasePrice = pricingByDayBasePrice; datePickerInitialData.pricingByDayBasePrice = pricingByDayBasePrice;
@ -620,24 +630,26 @@ 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
const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex( if (this.showPricingByDay) {
(item) => item.partType == PRICING_BY_DAY_PART_TYPE const pricingByDayUpchargeFeeIndex = supportingItems?.findIndex(
); (item) => item.partType == PRICING_BY_DAY_PART_TYPE
);
if (this.includePricingByDayUpcharge) { if (this.includePricingByDayUpcharge) {
if (pricingByDayUpchargeFeeIndex > -1) { if (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 {
supportingItems.push(this.pricingByDayUpchargeLineItem); if (pricingByDayUpchargeFeeIndex >= 0) {
} supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
} else { }
if (pricingByDayUpchargeFeeIndex >= 0) {
supportingItems.splice(pricingByDayUpchargeFeeIndex, 1);
} }
} }

View file

@ -43,8 +43,8 @@
v-if="waitListRequested" v-if="waitListRequested"
class="mt-5 rounded waitlist-success" class="mt-5 rounded waitlist-success"
ref="waitlistSuccessMessage"> ref="waitlistSuccessMessage">
<img :src="WaitListSuccessImage" class="success-image" /> <img :src="waitListSuccessImage" class="success-image" />
<span v-html="WaitListSuccessText" class="success-text"></span> <span v-html="waitListSuccessText" class="success-text"></span>
</div> </div>
<div <div
class="mt-5 mb-2 supplemental-information" class="mt-5 mb-2 supplemental-information"
@ -366,10 +366,10 @@ export default {
displayWaitListFeature() { displayWaitListFeature() {
return this.displayWaitList; return this.displayWaitList;
}, },
WaitListSuccessImage() { waitListSuccessImage() {
return this.getCmsContent("WaitListSuccessWidget", "Image"); return this.getCmsContent("WaitListSuccessWidget", "Image");
}, },
WaitListSuccessText() { waitListSuccessText() {
return this.getCmsContent("WaitListSuccessWidget", "BodyText"); return this.getCmsContent("WaitListSuccessWidget", "BodyText");
}, },
}, },
@ -539,7 +539,7 @@ export default {
}; };
}, },
waitListChecked(event) { waitListChecked(event) {
this.$emit("waitListRequested-emitted", event.target.checked); this.$emit("waitListRequested", event.target.checked);
}, },
scrollToSuccessMessage() { scrollToSuccessMessage() {
const successMessageElement = this.$refs.waitlistSuccessMessage; const successMessageElement = this.$refs.waitlistSuccessMessage;
@ -625,10 +625,10 @@ export default {
border: 1px solid #0c7e47; border: 1px solid #0c7e47;
border-radius: 5px; border-radius: 5px;
font-size: $h5-font-size; font-size: $h5-font-size;
padding: 12px 16px; padding: 0.75rem 1rem;
margin-bottom: -8px; margin-bottom: -0.5rem;
.success-text { .success-text {
margin-left: 8px; margin-left: 0.5rem;
strong { strong {
font-weight: 600; font-weight: 600;
letter-spacing: 0%; letter-spacing: 0%;
@ -636,8 +636,8 @@ export default {
} }
.success-image { .success-image {
margin-top: 4.5px; margin-top: 4.5px;
width: 16px; width: 1rem;
height: 16px; height: 1rem;
color: #0c7e47; color: #0c7e47;
} }
} }

View file

@ -1,5 +1,6 @@
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { partNumberStrings } from "@/constants/part-number-strings";
export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) { export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
if (!serviceZipCode) { if (!serviceZipCode) {
@ -42,6 +43,50 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
return pricingResults[0]; return pricingResults[0];
} }
export async function getPricedRecycleFeePart(serviceZipCode, pageNameToLog) {
if (!serviceZipCode) {
return null;
}
const zipCodeData = await getZipCodeData(serviceZipCode);
// Get supporting items
const supportingItems = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_SUPPORTING_ITEMS,
null,
pageNameToLog,
false
);
if (
supportingItems.data === null ||
supportingItems.data === undefined ||
supportingItems.data === ""
) {
return null;
}
const recycleFeePart = supportingItems.data.find(
(item) => item.partNumber === partNumberStrings.RECYCLE_FEE
);
if (recycleFeePart) {
// Get the Recycle Fee Part Price
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{
availableLineItems: [recycleFeePart],
serviceZipCode: serviceZipCode,
serviceZipCodeCtu: zipCodeData.zipCodeCtu,
},
pageNameToLog,
false
);
return pricingResults[0];
}
}
export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) { export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) {
// Get the Mobile Fee Part // Get the Mobile Fee Part
return await baseMixin.methods.dispatchStoreActionWithLogging( return await baseMixin.methods.dispatchStoreActionWithLogging(
@ -104,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

@ -106,6 +106,23 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
return Promise.resolve(mobileFeePart); return Promise.resolve(mobileFeePart);
}; };
const mockGetPricedRecycleFeePart = (mockServiceZipCode) => {
let recycleFeePart = {};
if (mockServiceZipCode === "43235") {
recycleFeePart = {
partNumber: "RECYCLE FEE",
description: "RECYCLE FEE",
partType: "FEE",
laborAmount: 39.99,
sellingPrice: 0,
kitPrice: 0,
};
}
return Promise.resolve(recycleFeePart);
};
const mockGetServiceabilityDetails = (mockServiceZipCode) => { const mockGetServiceabilityDetails = (mockServiceZipCode) => {
const serviceabilityDetails = { const serviceabilityDetails = {
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
@ -123,6 +140,9 @@ jest.mock(
getPricedMobileFeePart: jest.fn((mockServiceZipCode) => { getPricedMobileFeePart: jest.fn((mockServiceZipCode) => {
return mockGetPricedMobileFeePart(mockServiceZipCode); return mockGetPricedMobileFeePart(mockServiceZipCode);
}), }),
getPricedRecycleFeePart: jest.fn((mockServiceZipCode) => {
return mockGetPricedRecycleFeePart(mockServiceZipCode);
}),
getServiceabilityDetails: jest.fn((mockServiceZipCode) => { getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode); return mockGetServiceabilityDetails(mockServiceZipCode);
}), }),

View file

@ -82,6 +82,7 @@ import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import { import {
getPricedMobileFeePart, getPricedMobileFeePart,
getPricedRecycleFeePart,
getServiceabilityDetails, getServiceabilityDetails,
getBillToAccountNumber, getBillToAccountNumber,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
@ -267,6 +268,12 @@ export default {
"service-location" "service-location"
); );
// retrieve recycle fee part
const recycleFeePart = await getPricedRecycleFeePart(
serviceZipCode,
"service-location"
);
// retrieve serviceability details // retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails( const serviceabilityDetails = await getServiceabilityDetails(
serviceZipCode, serviceZipCode,
@ -280,6 +287,7 @@ export default {
// update content related to service zip code // update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart); this.$emit("updated-mobile-fee-part", mobileFeePart);
this.$emit("updated-recycle-fee-part", recycleFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu); this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu);

View file

@ -15,8 +15,11 @@
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-serviceability="setServiceabilityDetails" @updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase" @updated-contains-military-base="setContainsMilitaryBase"
@updated-bill-to-account-number="setBillToAccountNumber" @updated-bill-to-account-number="setBillToAccountNumber"
@ -90,6 +93,7 @@
:mobileFeePart="mobileFeePart" :mobileFeePart="mobileFeePart"
:mobileFeeApplies="mobileFeeApplies" :mobileFeeApplies="mobileFeeApplies"
@updated-mobile-fee-part="setMobileFeePart" @updated-mobile-fee-part="setMobileFeePart"
@updated-recycle-fee-part="setRecycleFeePart"
@updated-serviceability="setServiceabilityDetails" @updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase" @updated-contains-military-base="setContainsMilitaryBase"
@updated-mobile-ctu="setCtuForMobile" @updated-mobile-ctu="setCtuForMobile"
@ -184,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,
@ -195,6 +201,7 @@ export default {
selectedAppointmentType: this.getSelectedAppointmentType(), selectedAppointmentType: this.getSelectedAppointmentType(),
selectedProvider: this.getSelectedProvider(), selectedProvider: this.getSelectedProvider(),
mobileFeePart: null, mobileFeePart: null,
recycleFeePart: null,
zipContainsMilitaryBase: false, zipContainsMilitaryBase: false,
zipCodeCtu: null, zipCodeCtu: null,
billToAccountNumber: null, billToAccountNumber: null,
@ -480,6 +487,9 @@ export default {
setMobileFeePart(mobileFeePart) { setMobileFeePart(mobileFeePart) {
this.mobileFeePart = mobileFeePart; this.mobileFeePart = mobileFeePart;
}, },
setRecycleFeePart(recycleFeePart) {
this.recycleFeePart = recycleFeePart;
},
//creating async function as the computed property can not directly handle asynchronous operations or promises. //creating async function as the computed property can not directly handle asynchronous operations or promises.
async setMobileLocationQuestions(newValue) { async setMobileLocationQuestions(newValue) {
var newZipCode = newValue.addressQuestions.zipCode; var newZipCode = newValue.addressQuestions.zipCode;
@ -508,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;
}, },
@ -523,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;
}, },
@ -592,6 +608,7 @@ export default {
}, },
updateAndSaveSupportingItems() { updateAndSaveSupportingItems() {
let supportingItems = store.getters.lineItems.supportingItems; let supportingItems = store.getters.lineItems.supportingItems;
let shouldSaveSupportingItems = false;
supportingItems = supportingItems =
!supportingItems && this.isMobileStaticRecalibrationApplicable !supportingItems && this.isMobileStaticRecalibrationApplicable
@ -603,6 +620,21 @@ export default {
return; return;
} }
// update recyle fee price
if (this.recycleFeePart) {
const recycleFeeIndex = supportingItems.findIndex(
(item) => item.partNumber == partNumberStrings.RECYCLE_FEE
);
if (recycleFeeIndex >= 0) {
supportingItems[recycleFeeIndex].laborAmount = this.recycleFeePart.laborAmount;
supportingItems[recycleFeeIndex].sellingPrice =
this.recycleFeePart.sellingPrice;
supportingItems[recycleFeeIndex].kitPrice = this.recycleFeePart.kitPrice;
shouldSaveSupportingItems = true;
}
}
// if we have a mobile fee, then save/update supporting items // if we have a mobile fee, then save/update supporting items
if (this.selectedAppointmentType == "Mobile") { if (this.selectedAppointmentType == "Mobile") {
const mobileFeeIndex = supportingItems.findIndex( const mobileFeeIndex = supportingItems.findIndex(
@ -617,11 +649,7 @@ export default {
if (this.mobileFeePart !== null) supportingItems.push(this.mobileFeePart); if (this.mobileFeePart !== null) supportingItems.push(this.mobileFeePart);
} }
this.dispatchStoreAction( shouldSaveSupportingItems = true;
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
supportingItems,
false
);
} else { } else {
// if it's not a mobile, then make sure we remove any that may have been added // if it's not a mobile, then make sure we remove any that may have been added
const removeMobileFeeIndex = supportingItems?.findIndex( const removeMobileFeeIndex = supportingItems?.findIndex(
@ -630,13 +658,18 @@ export default {
if (removeMobileFeeIndex >= 0) { if (removeMobileFeeIndex >= 0) {
supportingItems.splice(removeMobileFeeIndex, 1); supportingItems.splice(removeMobileFeeIndex, 1);
this.dispatchStoreAction( shouldSaveSupportingItems = true;
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
supportingItems,
false
);
} }
} }
// Use shouldSaveSupportingItems flag to determine if we need to save supporting items. Prevents unnecessary/multiple saves
if (shouldSaveSupportingItems) {
this.dispatchStoreAction(
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
supportingItems,
false
);
}
}, },
async forwardButtonAction() { async forwardButtonAction() {
this.navigatingForward = true; this.navigatingForward = true;

View file

@ -32,6 +32,23 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
return Promise.resolve(mobileFeePart); return Promise.resolve(mobileFeePart);
}; };
const mockGetPricedRecycleFeePart = (mockServiceZipCode) => {
let recycleFeePart = {};
if (mockServiceZipCode === "43235") {
recycleFeePart = {
partNumber: "RECYCLE FEE",
description: "RECYCLE FEE",
partType: "FEE",
laborAmount: 39.99,
sellingPrice: 0,
kitPrice: 0,
};
}
return Promise.resolve(recycleFeePart);
};
const mockGetServiceabilityDetails = (mockServiceZipCode) => { const mockGetServiceabilityDetails = (mockServiceZipCode) => {
const serviceabilityDetails = { const serviceabilityDetails = {
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
@ -49,6 +66,9 @@ jest.mock(
getPricedMobileFeePart: jest.fn((mockServiceZipCode) => { getPricedMobileFeePart: jest.fn((mockServiceZipCode) => {
return mockGetPricedMobileFeePart(mockServiceZipCode); return mockGetPricedMobileFeePart(mockServiceZipCode);
}), }),
getPricedRecycleFeePart: jest.fn((mockServiceZipCode) => {
return mockGetPricedRecycleFeePart(mockServiceZipCode);
}),
getServiceabilityDetails: jest.fn((mockServiceZipCode) => { getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode); return mockGetServiceabilityDetails(mockServiceZipCode);
}), }),

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>
@ -45,8 +52,10 @@ import alert from "@/ux-components/alert/alert";
import { import {
getPricedMobileFeePart, getPricedMobileFeePart,
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 {
@ -62,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: {
@ -72,6 +82,11 @@ export default {
zipCode: "", zipCode: "",
}), }),
}, },
carId: String,
isVehicleHeavyTruck: {
type: Boolean,
default: false,
},
mobileFeePart: { mobileFeePart: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
@ -106,6 +121,7 @@ export default {
methods: { methods: {
resetAlerts() { resetAlerts() {
this.displayInvalidZipAlert = false; this.displayInvalidZipAlert = false;
this.displayNoServiceAlert = false;
}, },
resetsOnZipInput() { resetsOnZipInput() {
this.resetAlerts(); this.resetAlerts();
@ -154,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;
@ -164,6 +196,12 @@ export default {
"service-location" "service-location"
); );
// retrieve recycle fee part
const recycleFeePart = await getPricedRecycleFeePart(
serviceZipCode,
"service-location"
);
// retrieve serviceability details // retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails( const serviceabilityDetails = await getServiceabilityDetails(
serviceZipCode, serviceZipCode,
@ -177,6 +215,7 @@ export default {
// update content related to service zip code // update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart); this.$emit("updated-mobile-fee-part", mobileFeePart);
this.$emit("updated-recycle-fee-part", recycleFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-bill-to-account-number", billToAccountNumber); this.$emit("updated-bill-to-account-number", billToAccountNumber);

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

@ -49,7 +49,7 @@
placeHolderText="Select style" placeHolderText="Select style"
customDropdownId="styleQuestionField" /> customDropdownId="styleQuestionField" />
<div v-if="isHeavyTruck"> <div v-if="isBigTruckAndCanService">
<div class="separator-line"></div> <div class="separator-line"></div>
<textboxQuestion <textboxQuestion
class="mb-4" class="mb-4"
@ -110,7 +110,6 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import { applicationConfig } from "../../constants/application-config"; import { applicationConfig } from "../../constants/application-config";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { vehicleSubTypes } from "@/constants/vehicle-categories.js";
//define validation rules //define validation rules
defineRule("year-required", required(errorMessages.YEAR_REQUIRED)); defineRule("year-required", required(errorMessages.YEAR_REQUIRED));
@ -130,11 +129,11 @@ 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, serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
vehicleSubType: this.getVehicleSubTypefromStore(),
yearOptions: [], yearOptions: [],
makeOptions: [], makeOptions: [],
modelOptions: [], modelOptions: [],
@ -325,7 +324,7 @@ export default {
this.selectedMake = null; this.selectedMake = null;
this.selectedModel = null; this.selectedModel = null;
this.selectedStyle = null; this.selectedStyle = null;
this.vehicleSubType = null; this.isBigTruck = false;
this.canSafeliteService = false; this.canSafeliteService = false;
this.carId = null; this.carId = null;
if (year) { if (year) {
@ -346,7 +345,7 @@ export default {
this.modelOptions = []; this.modelOptions = [];
this.selectedModel = null; this.selectedModel = null;
this.selectedStyle = null; this.selectedStyle = null;
this.vehicleSubType = 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);
@ -365,7 +364,7 @@ export default {
this.resetAlert(); this.resetAlert();
this.styleOptions = []; this.styleOptions = [];
this.selectedStyle = null; this.selectedStyle = null;
this.vehicleSubType = null; this.isBigTruck = false;
this.carId = null; this.carId = null;
if (model) { if (model) {
const result = await this.getStyleOptions( const result = await this.getStyleOptions(
@ -430,8 +429,8 @@ export default {
!!this.carId !!this.carId
); );
}, },
isHeavyTruck() { isBigTruckAndCanService() {
return this.vehicleSubType === vehicleSubTypes.HEAVY_TRUCK && this.canSafeliteService; return this.isBigTruck && this.canSafeliteService;
}, },
}, },
methods: { methods: {
@ -484,6 +483,8 @@ export default {
this.displayNoServiceAlert = !result?.data.canSafeliteService; this.displayNoServiceAlert = !result?.data.canSafeliteService;
this.canSafeliteService = result?.data.canSafeliteService; this.canSafeliteService = result?.data.canSafeliteService;
this.vehicleSubType = result?.data.vehicleSubType; this.vehicleSubType = result?.data.vehicleSubType;
this.vehicleSpecialClass = result?.data.vehicleSpecialClass;
this.isBigTruck = result?.data.isBigTruck;
}, },
resetAlert() { resetAlert() {
this.displayNoServiceAlert = false; this.displayNoServiceAlert = false;
@ -498,6 +499,8 @@ export default {
model: this.selectedModel, model: this.selectedModel,
style: this.selectedStyle, style: this.selectedStyle,
vehicleSubType: this.vehicleSubType, vehicleSubType: this.vehicleSubType,
vehicleSpecialClass: this.vehicleSpecialClass,
isBigTruck: this.isBigTruck,
carId: this.carId, carId: this.carId,
category: this.category, category: this.category,
canSafeliteService: this.canSafeliteService, canSafeliteService: this.canSafeliteService,
@ -508,7 +511,7 @@ export default {
false false
); );
if (this.isHeavyTruck) { if (this.isBigTruck) {
const zipCodeData = await this.getZipCodeData(this.serviceZipCode); const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
await this.dispatchStoreAction( await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO, storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
@ -608,8 +611,8 @@ export default {
getImageVifColorfromStore() { getImageVifColorfromStore() {
return store.getters.vehicle.imageVifColor; return store.getters.vehicle.imageVifColor;
}, },
getVehicleSubTypefromStore() { getIsBigTruckfromStore() {
return store.getters.vehicle.vehicleSubType; return store.getters.vehicle.isBigTruck;
}, },
}, },

View file

@ -51,7 +51,6 @@ import {
import { externalParameterStatus } from "@/constants/external-parameters"; import { externalParameterStatus } from "@/constants/external-parameters";
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 { vehicleSubTypes } from "@/constants/vehicle-categories.js";
// Export State // Export State
const getDefaultState = () => { const getDefaultState = () => {
@ -63,6 +62,8 @@ const getDefaultState = () => {
model: null, model: null,
style: null, style: null,
vehicleSubType: null, vehicleSubType: null,
vehicleSpecialClass: null,
isBigTruck: false,
carId: null, carId: null,
category: null, category: null,
vin: null, vin: null,
@ -217,6 +218,12 @@ export const mutations = {
updateVehicleSubType(state, vehicleSubType) { updateVehicleSubType(state, vehicleSubType) {
state.order.vehicle.vehicleSubType = 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;
}, },
@ -729,7 +736,8 @@ export const mutations = {
// if we're loading a session and we do not have a provider number yet, then set isInsurance to null so that // if we're loading a session and we do not have a provider number yet, then set isInsurance to null so that
// a service package is not selected by default on the quote page. // a service package is not selected by default on the quote page.
if ( if (
!sessionInformation.order.payment.isInsurance && (sessionInformation.order.payment.isInsurance === null ||
sessionInformation.order.payment.isInsurance === undefined) &&
!sessionInformation.order.serviceLocation.provider?.providerNumber !sessionInformation.order.serviceLocation.provider?.providerNumber
) { ) {
state.order.payment.isInsurance = null; state.order.payment.isInsurance = null;
@ -846,9 +854,6 @@ export const getters = {
return false; return false;
}, },
isHeavyTruckVehicle: (state) => {
return state.order.vehicle.vehicleSubType === vehicleSubTypes.HEAVY_TRUCK;
},
isMobileAppointment: (state) => { isMobileAppointment: (state) => {
return state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE; return state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
}, },
@ -1848,7 +1853,7 @@ export const actions = {
endPoint += `&${glassPieces}`; endPoint += `&${glassPieces}`;
} }
endPoint += `&isHeavyTruckVehicle=${context.getters.isHeavyTruckVehicle}`; endPoint += `&isHeavyTruckVehicle=${context.getters.order.vehicle.isBigTruck}`;
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetServiceabilityDetails.method, method: endpoints.GetServiceabilityDetails.method,
@ -1877,7 +1882,7 @@ export const actions = {
url += `/${windshieldPartWithRecal.partNumber}`; url += `/${windshieldPartWithRecal.partNumber}`;
} }
//heavy truck vehicle //heavy truck vehicle
url += `/${context.getters.isHeavyTruckVehicle}`; url += `/${context.getters.order.vehicle.isBigTruck}`;
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetProviders.method, method: endpoints.GetProviders.method,
@ -2109,6 +2114,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: {
@ -2163,6 +2171,8 @@ export const actions = {
style: vehicle.style, style: vehicle.style,
vin: vehicle.vin, vin: vehicle.vin,
vehicleSubType: vehicle.vehicleSubType, 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,
@ -2379,6 +2389,8 @@ export const actions = {
model, model,
style, style,
vehicleSubType, vehicleSubType,
vehicleSpecialClass,
isBigTruck,
carId, carId,
category, category,
canSafeliteService, canSafeliteService,
@ -2402,6 +2414,8 @@ export const actions = {
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_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_CAN_SAFELITE_SERVICE, canSafeliteService);