Merge remote-tracking branch 'origin/develop' into feature/skiener/CSR-1452

This commit is contained in:
Scott Kiener 2023-10-31 13:46:35 -04:00
commit dfd8413312
20 changed files with 466 additions and 300 deletions

View file

@ -22,6 +22,8 @@ module.exports = {
"!src/common-components/funnel-header/menu-modal/**/*.vue",
"!src/layouts/schedule/helpers/schedule-helper.js", // Temp test exclusion while in development
"!src/layouts/review/*.vue", // Temp test exclusion while in development
"!src/layouts/payment/*.vue", // Temp test exclusion while in development
"!src/layouts/payment-pia-return/*.vue", // Temp test exclusion while in development
// END
], // ! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],

View file

@ -12,7 +12,7 @@
z-index: 1040;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.35);
background-color: rgba(0, 0, 0, 0.4);
display: flex;
}
#pageLoadingModal .modal-content {
@ -41,26 +41,36 @@
border-radius: 8px;
background-clip: padding-box;
outline: 0;
width: 168px;
height: 168px;
width: 176px;
height: 136px;
display: flex;
justify-content: center;
align-items: center;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center center;
background-size: 96px;
}
#pageLoadingModal .modal-content:before, #pageLoadingModal .modal-content:after {
#pageLoadingModal .modal-content:after {
content: "";
border-radius: 50%;
position: absolute;
inset: 0;
box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.25) inset;
top: 24px;
bottom: 24px;
right: 24px;
left: 24px;
width: 5.5rem;
height: 5.5rem;
border: 0.45em solid #d4281c;
border-right-color: transparent;
animation: spinnerrotate 0.75s linear infinite;
}
#pageLoadingModal .modal-content:after {
box-shadow: 0 3px 0 #da291c inset;
animation: rotate 1s linear infinite;
@keyframes spinnerrotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
#pageLoadingModal .modal-content p {
display: none;
}
#pageLoadingModal .modal-backdrop {
position: absolute;

View file

@ -13,7 +13,7 @@
z-index: 1040;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.35);
background-color: rgba(0,0,0,0.40);
display: flex;
.modal-content {
@ -45,26 +45,31 @@
border-radius: 8px;
background-clip: padding-box;
outline: 0;
width: 168px;
height: 168px;
width: 176px;
height: 136px;
display: flex;
justify-content: center;
align-items: center;
&:before,
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center center;
background-size: 96px;
&:after {
content: '';
border-radius: 50%;
position: absolute;
inset: 0;
box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.25) inset;
top: 24px;
bottom: 24px;
right: 24px;
left: 24px;
width: 5.5rem;
height: 5.5rem;
border: 0.45em solid #d4281c;
border-right-color: transparent;
animation: spinnerrotate 0.75s linear infinite;
}
&:after {
box-shadow: 0 3px 0 #da291c inset;
animation: rotate 1s linear infinite;
@keyframes spinnerrotate {
0% { transform: rotate(0)}
100% { transform: rotate(360deg)}
}
p {
display: none;
}
}

View file

@ -1,11 +1,12 @@
const cartItemTypes = {
FRONT_WIPER: "FRONT WIPER",
REAR_WIPER: "REAR WIPER",
FRONT_WIPERS: "FRONT WIPERS",
REAR_WIPERS: "REAR WIPERS",
SUPPORTING_ITEMS: "SUPPORTING ITEMS",
GLASS_PARTS: "GLASS PARTS",
RAIN_DEFENSE: "RAIN DEFENSE",
RECYCLE_FEE: "RECYCLE FEE",
MOBILE_FEE: "MOBILE FEE",
PROMOS: "PROMOS",
};
export { cartItemTypes };

View file

@ -73,7 +73,7 @@ const storeActions = {
SAVE_VIN: "saveVin",
SAVE_REGISTRATION_ADDRESS_LOOKUP: "saveRegistrationAddressLookup",
SAVE_GLASS_PARTS: "saveGlassParts",
SAVE_GLASS_PART_PRICES: "saveGlassPartPrices",
SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING: "saveGlassPartsSuppressingStateResetting",
SAVE_PART_QUESTION_ANSWERS: "savePartQuestionAnswers",
RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED:
"resetMoldingAndCapabilityQuestionAnswersIfNeeded",

View file

@ -53,6 +53,7 @@ const storeMutations = {
UPDATE_IS_PIA: "updateIsPia",
UPDATE_PIA_TYPE: "updatePiaType",
WORK_ORDER_NUMBER: "updateWorkOrderNumber",
Customer_Portal_Login_Token: "updateCustomerPortalLoginToken",
// EVENT BUS MUTATIONS
ADD_EVENT_TO_BUS: "addEventToBus",

View file

@ -92,6 +92,7 @@ import listButton from "@/ux-components/list-button/list-button";
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
import listCard from "@/ux-components/list-card/list-card";
import radio from "@/ux-components/radio/radio";
import checkbox from "@/ux-components/checkbox/checkbox";
import { useField, ErrorMessage } from "vee-validate";
import { queryStrings } from "@/constants/query-strings";
@ -209,6 +210,9 @@ export default {
case "radio":
classes = "ui-radio d-flex";
break;
case "checkbox":
classes = "ui-checkbox d-flex";
break;
case "servicePackageRadio":
classes = "package-main";
break;
@ -317,6 +321,7 @@ export default {
listCard,
ErrorMessage,
radio,
checkbox,
},
};
</script>

View file

@ -9,7 +9,7 @@
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header mb-2 mt-2">
<div class="modal-header mb-0 mt-6">
<label
v-if="headerText"
class="modal-title d-flex justify-content-center pb-0 w-100">
@ -125,6 +125,7 @@ export default {
color: $black;
}
.modal-header {
padding: 1rem 0 0.5rem 0;
border-bottom: none;
.btn-close {
position: absolute;
@ -132,10 +133,13 @@ export default {
top: 1rem;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
opacity: 1;
margin: 0.3rem -0.5rem -0.5rem auto;
}
.modal-title {
font-weight: 500;
font-size: 1.25rem;
line-height: 32px;
color: rgb(0, 0, 0);
}
}

View file

@ -1,108 +0,0 @@
<template>
<transition name="fade" mode="out-in">
<div class="add-vaps-buttons" aria-live="polite" v-if="isDisplayed">
<buttonQuestion
ref="buttonQuestion"
:answers="answersToDisplay"
groupName="addVaps"
v-model="selectedValues"
buttonTypeString="addVapsButton"
:buttonTypeObject="addVapsButton"
:isWide="answersToDisplay.length > 1 ? false : true" />
</div>
</transition>
</template>
<script>
import buttonQuestion from "@/digital-components/button-question/button-question";
import addVapsButton from "./add-vaps-button/add-vaps-button";
export default {
name: "add-vaps-modal-buttons",
props: {
cartItems: Object,
vapsTilesCmsName: String,
availableLineItems: Object,
},
data() {
return {
addVapsButton: addVapsButton,
};
},
computed: {
selectedValues: {
get: function () {
return this.answersToDisplay;
},
set: function (newValue) {
// TODO - emit event to open modal
},
},
isDisplayed() {
return this.showAddRainDefense || this.showAddWipers;
},
showAddRainDefense() {
// if not found in the cart
return !this.cartItems.some((vap) => {
return vap?.partType === "RAIN DEFENSE";
});
},
showAddWipers() {
// if not found in the cart
return !this.cartItems.some((vap) => {
return vap?.partType?.includes(" WIPER");
});
},
rainDefensePrice() {
// const lineItem = this.rainDefenseItem;
const lineItem = this.availableLineItems.find((item) => {
return item.partType === "RAIN DEFENSE";
});
const price = lineItem?.laborAmount + lineItem?.sellingPrice + lineItem?.kitPrice;
return parseFloat(price).toFixed(2);
},
wipersPrice() {
let price = 0;
const lineItems = this.availableLineItems.filter((item) => {
return item.partType.includes(" WIPER");
});
lineItems?.forEach((item) => {
price += item?.laborAmount + item?.sellingPrice + item?.kitPrice;
});
return parseFloat(price).toFixed(2);
},
answersCmsData() {
return this.getCmsContent(this.vapsTilesCmsName, "Answers");
},
answersToDisplay() {
const answers = [];
if (!this.answersCmsData) return answers;
const getAnswer = (name, answers) => {
const answerIndex = answers.findIndex((answer) => {
return answer.Name === name;
});
return answers[answerIndex];
};
if (this.showAddWipers) {
const answer = getAnswer("WipersModal", this.answersCmsData);
answer.SubText = "+$" + this.wipersPrice;
answers.push(answer);
}
// if (this.showAddRainDefense) {
// const answer = getAnswer("RainDefenseModal", this.answersCmsData);
// answer.SubText = "+$" + this.rainDefensePrice;
// answers.push(answer);
// }
return answers;
},
},
components: {
buttonQuestion,
},
};
</script>

View file

@ -129,11 +129,9 @@ export default {
let vapsCartItemsForSelectedPackage = [];
console.log(this.cartItems);
console.log(packageContentTypes);
this.cartItems.forEach((cartItem) => {
packageContentTypes.forEach((vapType) => {
if (vapType == cartItem.cartItemType) {
if (cartItem.cartItemType.includes(vapType)) {
vapsCartItemsForSelectedPackage.push(cartItem);
}
});
@ -153,17 +151,10 @@ export default {
return vapsTypeName.Text;
},
removeItem(cartItemType, category) {
console.log(
this.lineItems[category].filter(
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
)
);
this.lineItems[category] = this.lineItems[category].filter(
(lineItemsToKeep) => lineItemsToKeep.cartItemType != cartItemType
);
//console.log(`lineItemToDelete: ${lineItemIdToDelete}`)
this.$emit("update:modelValue", this.lineItems);
},
},
@ -188,8 +179,8 @@ export default {
cartItems.push(this.glassPartsCartItem);
}
if (this.supportingItemsCartItem) {
cartItems.push(this.supportingItemsCartItem);
if (this.otherSupportingItemsCartItem) {
cartItems.push(this.otherSupportingItemsCartItem);
}
if (this.recycleFeeCartItem) {
@ -200,6 +191,25 @@ export default {
cartItems.push(this.mobileFeeCartItem);
}
// Create a cart item for each promo
this.promos.forEach((promoLineItem) => {
let cartItem = null;
cartItem = {
name: "Some Kind of Promo", // get from CMS?
category: cartItemCategories.PROMOS,
cartItemType: promoLineItem.partType,
isDisplayed: true,
subTotal:
(promoLineItem.kitPrice ?? 0) +
(promoLineItem.laborAmount ?? 0) +
(promoLineItem.sellingPrice ?? 0),
salesTax: promoLineItem.salesTax,
};
cartItems.push(cartItem);
});
return cartItems;
},
},
@ -262,6 +272,10 @@ export default {
const supportingItems = this.lineItems?.supportingItems;
return supportingItems?.length > 0 ? supportingItems : [];
},
promos() {
const promos = this.lineItems?.promos;
return promos?.length > 0 ? promos : [];
},
// Cart Items
packageCartItems() {
return this.getVapsCartItemsForSelectedPackage(this.packageLevel);
@ -269,8 +283,6 @@ export default {
nonpackageCartItems() {
const nonpackageCartItems = [];
console.log("packageCartItems");
this.cartItems.forEach((cartItem) => {
if (cartItem == this.recycleFeeCartItem || cartItem == this.mobileFeeCartItem) {
return;
@ -299,7 +311,7 @@ export default {
cartItem = {
name: this.frontWiperCartItemName,
category: cartItemCategories.VAPS,
cartItemType: cartItemTypes.FRONT_WIPER,
cartItemType: cartItemTypes.FRONT_WIPERS,
isDisplayed: true,
subTotal: 0,
salesTax: 0,
@ -335,7 +347,7 @@ export default {
cartItem = {
name: this.rearWiperCartItemName,
category: cartItemCategories.VAPS,
cartItemType: cartItemTypes.REAR_WIPER,
cartItemType: cartItemTypes.REAR_WIPERS,
isDisplayed: true,
subTotal: 0,
salesTax: 0,
@ -394,61 +406,31 @@ export default {
glassPartsCartItem() {
let cartItem = null;
const flattenedGlassPartsLineItems =
this.lineItemUtilities.getFlattenedArrayOfLineItemsWithChildParts(
this.lineItems.glassParts
);
if (this.lineItems?.glassParts?.length > 0) {
this.lineItems?.glassParts?.forEach((lineItem) => {
cartItem = {
name: null,
category: cartItemCategories.GLASS_PARTS,
cartItemType: cartItemTypes.GLASS_PARTS,
isDisplayed: false,
subTotal:
(lineItem.kitPrice ?? 0) +
(lineItem.laborAmount ?? 0) +
(lineItem.sellingPrice ?? 0),
salesTax: lineItem.salesTax ?? 0,
lineItems: [],
};
if (flattenedGlassPartsLineItems.length > 0) {
cartItem = {
name: null,
category: cartItemCategories.GLASS_PARTS,
cartItemType: cartItemTypes.GLASS_PARTS,
isDisplayed: false,
subTotal: 0,
salesTax: 0,
lineItems: [],
};
if (lineItem.childParts?.length > 0) {
lineItem.childParts.forEach((childPartLineItem) => {
cartItem.subTotal +=
(childPartLineItem.kitPrice ?? 0) +
(childPartLineItem.laborAmount ?? 0) +
(childPartLineItem.sellingPrice ?? 0);
flattenedGlassPartsLineItems.forEach((lineItem) => {
lineItem.cartItemType = cartItem.cartItemType;
cartItem.lineItems.push(lineItem);
cartItem.subTotal +=
(lineItem.kitPrice ?? 0) +
(lineItem.laborAmount ?? 0) +
(lineItem.sellingPrice ?? 0);
cartItem.salesTax += lineItem.salesTax ?? 0;
});
}
return cartItem;
},
supportingItemsCartItem() {
let cartItem = null;
if (this.supportingItems.length > 0) {
cartItem = {
name: null,
category: cartItemCategories.SUPPORTING_ITEMS,
cartItemType: cartItemTypes.SUPPORTING_ITEMS,
isDisplayed: false,
subTotal: 0,
salesTax: 0,
lineItems: [],
};
this.supportingItems.forEach((lineItem) => {
lineItem.cartItemType = cartItem.cartItemType;
cartItem.lineItems.push(lineItem);
cartItem.subTotal +=
(lineItem.kitPrice ?? 0) +
(lineItem.laborAmount ?? 0) +
(lineItem.sellingPrice ?? 0);
cartItem.salesTax += lineItem.salesTax ?? 0;
cartItem.salesTax += childPartLineItem.salesTax;
});
}
});
}
@ -475,15 +457,15 @@ export default {
lineItems: [],
};
recycleFeeLineItem.cartItemType = cartItem.cartItemType;
cartItem.lineItems.push(recycleFeeLineItem);
//recycleFeeLineItem.cartItemType = cartItem.cartItemType;
//cartItem.lineItems.push(recycleFeeLineItem);
cartItem.subTotal +=
(recycleFeeLineItem.kitPrice ?? 0) +
(recycleFeeLineItem.laborAmount ?? 0) +
(recycleFeeLineItem.sellingPrice ?? 0);
cartItem.salesTax += recycleFeeLineItem.salesTax ?? 0;
cartItem.salesTax = recycleFeeLineItem.salesTax ?? 0;
}
return cartItem;
@ -522,6 +504,41 @@ export default {
return cartItem;
},
otherSupportingItemsCartItem() {
let cartItem = null;
const otherSupportingItemsLineItems = this.supportingItems.filter(
(lineItem) =>
lineItem.partNumber != partTypeStrings.MOBILE_FEE &&
lineItem.partNumber != partTypeStrings.RECYCLE_FEE
);
if (otherSupportingItemsLineItems.length > 0) {
cartItem = {
name: null,
category: cartItemCategories.SUPPORTING_ITEMS,
cartItemType: cartItemTypes.SUPPORTING_ITEMS,
isDisplayed: false,
subTotal: 0,
salesTax: 0,
lineItems: [],
};
otherSupportingItemsLineItems.forEach((lineItem) => {
lineItem.cartItemType = cartItem.cartItemType;
cartItem.lineItems.push(lineItem);
cartItem.subTotal +=
(lineItem.kitPrice ?? 0) +
(lineItem.laborAmount ?? 0) +
(lineItem.sellingPrice ?? 0);
cartItem.salesTax += lineItem.salesTax ?? 0;
});
}
return cartItem;
},
removeLinkText() {
return this.getCmsContent("RemoveCartItemTextWidget", "Text");
},

View file

@ -162,6 +162,7 @@ async function saveSessionHelper(
crmCustomerId: savedSessionInfo.data.crmCustomerId?.toString(),
eon: savedSessionInfo.data.eon,
workOrderNumber: savedSessionInfo.data.workOrderNumber,
customerPortalLoginToken: savedSessionInfo.data.customerPortalLoginToken,
},
false
);

View file

@ -94,9 +94,13 @@ export default {
ScheduleConfirmationImage() {
return this.getCmsContent("ScheduleConfirmationWidget", "Image");
},
CustomerPortalLoginToken() {
return store.getters.order.customerPortalLoginToken;
},
ConfirmationEmailText() {
return this.getCmsContent("ConfirmationEmailWidget", "BodyText")
?.replaceAll("{custom:MY_ACCOUNT_URL}", applicationConfig.MY_ACCOUNT)
?.replaceAll("{custom:CUSTOMER_PORTAL_LOGIN_TOKEN}", this.CustomerPortalLoginToken)
?.replaceAll("&lt;", "<")
?.replaceAll("&gt;", ">");
},

View file

@ -26,30 +26,24 @@
v-if="wipersModal"
@footer-button-action="addWipersToCart('WipersModal', wipersModal)"
footerButtonActionName="footer-button-action">
<div v-if="wipersToDisplay === wipersToDisplayStrings.BOTH" class="wiper-options">
<div>
<checkboxQuestion
class="mb-5"
cmsWidgetName="AddFrontBladesQuestionWidget"
v-model="selectedFrontWipers" />
<p class="price">+${{ wipersModal.totalFrontPrice }}</p>
</div>
<div>
<checkboxQuestion
class="mb-5"
cmsWidgetName="AddRearBladesQuestionWidget"
v-model="selectedRearWipers" />
<p class="price">+${{ wipersModal.totalRearPrice }}</p>
</div>
<div v-if="wipersOffered === wipersOfferedStrings.BOTH" class="wiper-options">
<buttonQuestion
ref="buttonQuestion"
buttonTypeString="checkbox"
class="mt-5"
:answers="wipersOptions"
groupName="wipers-options"
textPosition="text-center"
v-model="selectedWipers"
isMultiSelect
isRequired
validationRules="checkbox-required"
:additionalButtonData="additionalButtonData" />
</div>
<div
v-else-if="wipersToDisplay === wipersToDisplayStrings.FRONT"
class="wiper-options">
<div v-else-if="wipersOffered === wipersOfferedStrings.FRONT" class="wiper-options">
<p class="price">+${{ wipersModal.totalFrontPrice }}</p>
</div>
<div
v-else-if="wipersToDisplay === wipersToDisplayStrings.REAR"
class="wiper-options">
<div v-else-if="wipersOffered === wipersOfferedStrings.REAR" class="wiper-options">
<p class="price">+${{ wipersModal.totalRearPrice }}</p>
</div>
</contentGroupModal>
@ -62,12 +56,17 @@
import buttonQuestion from "@/digital-components/button-question/button-question";
import addVapsButton from "./add-vaps-button/add-vaps-button";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
// SUPPORTING
import { partTypeStrings } from "@/constants/part-type-strings";
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
const wipersToDisplayStrings = {
// DEFINE VALIDATION RULES
defineRule("checkbox-required", required(errorMessages.OPTION_REQUIRED));
const wipersOfferedStrings = {
FRONT: "FRONT",
REAR: "REAR",
BOTH: "BOTH",
@ -83,15 +82,18 @@ export default {
data() {
return {
addVapsButton: addVapsButton,
selectedFrontWipers: false,
selectedRearWipers: false,
wipersToDisplay: null,
wipersOffered: null,
isModalOpened: false,
selectedWipers: this.getSelectedWipers(),
};
},
methods: {
openModal(modalName) {
this.$refs[modalName].openModal();
},
closeModal(modalName) {
this.$refs[modalName].closeModal();
},
addRainDefenseToCart(modalName, part) {
const alreadyInCart = this.currentCartItems.vaps.some((item) => {
return item.partType === part.partType;
@ -99,9 +101,11 @@ export default {
if (!alreadyInCart) {
this.currentCartItems.vaps.push(part);
}
this.$refs[modalName].closeModal();
this.closeModal(modalName);
},
addWipersToCart(modalName, part) {
const itemsForCart = [];
const frontWipersAlreadyInCart = this.currentCartItems.vaps.some((item) => {
return item.partType === part.frontWiperLineItems[0].partType;
});
@ -109,36 +113,43 @@ export default {
return item.partType === part.rearWiperLineItems[0]?.partType;
});
if (this.wipersToDisplay === wipersToDisplayStrings.FRONT) {
this.selectedFrontWipers = true; // set as true if front wipers is only option
if (this.wipersOffered === wipersOfferedStrings.BOTH) {
this.selectedWipers.forEach((wiper) => {
itemsForCart.push(wiper);
});
} else {
if (
!frontWipersAlreadyInCart &&
this.wipersOffered === wipersOfferedStrings.FRONT
) {
itemsForCart.push(wipersOfferedStrings.FRONT);
}
if (!rearWipersAlreadyInCart && this.wipersOffered === wipersOfferedStrings.REAR) {
itemsForCart.push(wipersOfferedStrings.REAR);
}
}
if (this.wipersToDisplay === wipersToDisplayStrings.REAR) {
this.selectedRearWipers = true; // set as true if rear wipers is only option
}
if (!frontWipersAlreadyInCart && this.selectedFrontWipers) {
if (part.frontWiperLineItems) {
// for wipers, a "combined" part (frontWiperLineItems)
itemsForCart.forEach((type) => {
if (type === wipersOfferedStrings.FRONT) {
part.frontWiperLineItems.forEach((part) => {
this.currentCartItems.vaps.push(part);
});
}
}
if (!rearWipersAlreadyInCart && this.selectedRearWipers) {
if (part.rearWiperLineItems) {
if (type === wipersOfferedStrings.REAR) {
part.rearWiperLineItems.forEach((part) => {
this.currentCartItems.vaps.push(part);
});
}
}
this.$refs[modalName].closeModal();
});
if (this.wipersToDisplay === wipersToDisplayStrings.BOTH) {
this.selectedFrontWipers = false; // set as false if both options are given
this.selectedRearWipers = false; // set as false if both options are given
}
this.closeModal(modalName);
this.selectedWipers;
},
updateWipersToDisplay(value) {
this.wipersToDisplay = value;
updateWipersOffered(value) {
this.wipersOffered = value;
},
getSelectedWipers() {
return null;
},
},
computed: {
@ -229,31 +240,72 @@ export default {
];
prices.sort((a, b) => a - b);
modalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1];
this.updateWipersToDisplay(wipersToDisplayStrings.BOTH);
this.updateWipersOffered(wipersOfferedStrings.BOTH);
buttons.push(modalData);
} else {
// NO FRONT WIPERS IN CART; SHOW ONLY FRONT WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalFrontPrice;
this.updateWipersToDisplay(wipersToDisplayStrings.FRONT);
this.updateWipersOffered(wipersOfferedStrings.FRONT);
buttons.push(modalData);
}
} else if (!rearWipersInCart && this.wipersModal?.rearWiperLineItems.length > 0) {
// NO REAR WIPERS IN CART; SHOW ONLY REAR WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalRearPrice;
this.updateWipersToDisplay(wipersToDisplayStrings.REAR);
this.updateWipersOffered(wipersOfferedStrings.REAR);
buttons.push(modalData);
}
return buttons;
},
wipersToDisplayStrings() {
return wipersToDisplayStrings;
wipersOfferedStrings() {
return wipersOfferedStrings;
},
wipersOptions() {
const wipersOptions = [];
const wipersFrontCopy = this.getCmsContent(
"AddFrontBladesQuestionWidget",
"QuestionText"
);
const wipersRearCopy = this.getCmsContent(
"AddRearBladesQuestionWidget",
"QuestionText"
);
const frontWipersLineItems = this.wipersModal.frontWiperLineItems;
const rearWipersLineItems = this.wipersModal.rearWiperLineItems;
if (frontWipersLineItems) {
const wipersOption = {
buttonLabel: wipersFrontCopy,
groupName: "wipers-options",
value: wipersOfferedStrings.FRONT,
buttonLabelSubCopy: "buttonLabelSubCopy",
buttonBodyCopy: "buttonBodyCopy",
buttonAuxillaryCopy: "buttonAuxillaryCopy",
buttonFooterCopy: "buttonFooterCopy",
additionalButtonData: "+$" + this.wipersModal.totalFrontPrice,
};
wipersOptions.push(wipersOption);
}
if (rearWipersLineItems) {
const wipersOption = {
buttonLabel: wipersRearCopy,
groupName: "wipers-options",
value: wipersOfferedStrings.REAR,
buttonLabelSubCopy: "buttonLabelSubCopy",
buttonBodyCopy: "buttonBodyCopy",
buttonAuxillaryCopy: "buttonAuxillaryCopy",
buttonFooterCopy: "buttonFooterCopy",
additionalButtonData: "+$" + this.wipersModal.totalRearPrice,
};
wipersOptions.push(wipersOption);
}
return wipersOptions;
},
},
components: {
buttonQuestion,
contentGroupModal,
checkboxQuestion,
},
};
</script>
@ -286,26 +338,31 @@ export default {
text-align: center;
}
.wiper-options {
& > div {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
.ui-checkbox {
display: block;
&:last-child {
margin-bottom: 0;
& > div.col {
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
}
.checkbox {
display: flex;
justify-content: space-between;
.form-check {
margin-bottom: 0;
.additional-button-data {
font-size: 0.875rem;
line-height: 1.75;
padding-left: 0.5rem;
color: $green;
}
}
}
p {
margin-bottom: 0;
}
p.price {
font-size: 0.875rem;
padding-left: 0.5rem;
color: $green;
line-height: 1.75;
}
}
}

View file

@ -90,7 +90,7 @@ import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { LineItemUtilities, mapTaxedAvailableLineItemsToStoreFormat } from "../../store";
import { mapTaxedAvailableLineItemsToStoreFormat } from "../../store";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
@ -174,9 +174,6 @@ export default {
false
);
const lineItemUtilities = new LineItemUtilities();
lineItemUtilities.addGuidToLineItemsIfNotAlreadyThere(availableLineItems);
const lineItems = mapTaxedAvailableLineItemsToStoreFormat(
availableLineItems,
lineItemsFromStore
@ -272,8 +269,19 @@ export default {
false
);
await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false);
// save lineitems as they now have salestax added
await this.dispatchStoreAction(
storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
this.lineItems.glassParts,
false
);
await this.dispatchStoreAction(
storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
this.lineItems.supportingItems,
false
);
await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false);
await this.dispatchStoreAction(storeActions.SAVE_PROMOS, this.lineItems.promos, false);
if (this.paymentMethod == paymentMethods.LATER) {

View file

@ -419,12 +419,10 @@ export default {
return itemsForPia;
},
getAmountDue() {
return 1600;
//return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
},
getDisplayAmountDue() {
return "$1600.00";
//return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
},
isPaypal() {
if (this.getPaymentType() == "pp") {

View file

@ -258,7 +258,7 @@ export default {
if (this.pricedGlassParts.length > 0) {
this.dispatchStoreAction(
this.storeActions.SAVE_GLASS_PART_PRICES,
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
this.pricedGlassParts,
false
);

View file

@ -114,7 +114,9 @@ export default {
return totalPrice;
},
getTotalLineItemPrice(lineItem) {
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
return (
lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice + lineItem.salesTax
);
},
getDisplayAmountDue(lineItems) {
return this.getAmountDue(lineItems).toLocaleString("en-US", {
@ -123,7 +125,6 @@ export default {
});
},
getAmountDue(lineItems) {
// TODO: verify tax is included in lineitems
var amountDue = 0;
if (lineItems.glassParts) {
amountDue = this.getTotalPriceOfAllLineItemsAndChildParts(lineItems.glassParts);
@ -133,7 +134,7 @@ export default {
lineItems.supportingItems
);
}
if (lineItems.supportingItems.vaps) {
if (lineItems.vaps) {
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(lineItems.vaps);
}
return amountDue;

View file

@ -126,6 +126,7 @@ const getDefaultState = () => {
referralCorrelationId: null,
eon: null,
workOrderNumber: null,
customerPortalLoginToken: null,
},
applicationUser: {
eventBus: [],
@ -245,6 +246,9 @@ export const mutations = {
updateWorkOrderNumber(state, workOrderNumber) {
state.order.workOrderNumber = workOrderNumber;
},
updateCustomerPortalLoginToken(state, customerPortalLoginToken) {
state.order.customerPortalLoginToken = customerPortalLoginToken;
},
updateCCToken(state, ccToken) {
state.order.payment.ccToken.subscriptionId = ccToken.subscriptionId;
state.order.payment.ccToken.expMonth = ccToken.expMonth;
@ -451,6 +455,7 @@ export const mutations = {
state.order.referralDate = sessionInformation.order.referralDate;
state.order.referralCorrelationId = sessionInformation.order.referralCorrelationId;
state.order.eon = sessionInformation.order.eon;
state.order.customerPortalLoginToken = sessionInformation.order.CustomerPortalLoginToken;
if (state.order.vehicle.vin !== sessionInformation.order.vehicle?.vin) {
state.applicationUser.pageData[fmgPageValues.PART_QUESTIONS] = null;
@ -1014,6 +1019,7 @@ export const actions = {
savedSessionId,
crmCustomerId,
workOrderNumber,
customerPortalLoginToken,
}
) {
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
@ -1025,6 +1031,7 @@ export const actions = {
context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId);
context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId);
context.commit(storeMutations.WORK_ORDER_NUMBER, workOrderNumber);
context.commit(storeMutations.Customer_Portal_Login_Token, customerPortalLoginToken);
},
logPageView(
@ -1291,7 +1298,7 @@ export const actions = {
});
syncLineItemIds(response.data, context.getters.order.lineItems.vaps);
console.log(response.data);
return response;
},
@ -2173,7 +2180,10 @@ export const actions = {
pageNameToLog,
}
) {
const lineItemsWithOnlyPriceInfo = pricedAvailableLineItems.map((lineItem) => ({
const flattenedLineItemsWithChildParts =
getFlattenedArrayOfLineItemsWithChildParts(pricedAvailableLineItems);
const lineItemsWithOnlyPriceInfo = flattenedLineItemsWithChildParts.map((lineItem) => ({
partNumber: lineItem.partNumber,
laborAmount: lineItem.laborAmount ?? 0,
kitPrice: lineItem.kitPrice ?? 0,
@ -2186,15 +2196,25 @@ export const actions = {
"lineItems"
);
let queryString =
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
`&BillToAccountNumber=${billToAccountNumber}` +
`&ProviderNumber=${providerNumber}` +
`&AppointmentType=${appointmentType}` +
`&ServiceLocation.City=${serviceLocationCity}` +
`&ServiceLocation.State=${serviceLocationState}` +
`&ServiceLocation.ZipCode=${serviceLocationZipCode}` +
`&${pricedLineItemsFormattedForRequest}`;
let queryString = "";
if (appointmentType == "Mobile") {
queryString =
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
`&BillToAccountNumber=${billToAccountNumber}` +
`&ProviderNumber=${providerNumber}` +
`&AppointmentType=${appointmentType}` +
`&ServiceLocation.City=${serviceLocationCity}` +
`&ServiceLocation.State=${serviceLocationState}` +
`&ServiceLocation.ZipCode=${serviceLocationZipCode}` +
`&${pricedLineItemsFormattedForRequest}`;
} else {
queryString =
`ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}` +
`&BillToAccountNumber=${billToAccountNumber}` +
`&ProviderNumber=${providerNumber}` +
`&AppointmentType=${appointmentType}` +
`&${pricedLineItemsFormattedForRequest}`;
}
const lineItemServerData = context.getters.order.lineItems.serverData;
if (lineItemServerData) {
@ -2378,7 +2398,7 @@ export const actions = {
context.commit(storeMutations.UPDATE_GLASS_PARTS, parts);
},
saveGlassPartPrices(context, parts) {
saveGlassPartsSuppressingStateResetting(context, parts) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, parts);
},

View file

@ -0,0 +1,66 @@
import { mount } from "@vue/test-utils";
import checkbox from "./checkbox";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
describe("checkbox.vue", () => {
it("Should have correct group name", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: {
groupName: "checkbox-button-test",
value: "test value",
},
},
});
// Act
const input = wrapper.find("input");
// Assert
expect(input.attributes().name).toEqual("checkbox-button-test");
});
it("Should have correct label text", async () => {
// Act
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: {
buttonLabel: "label text",
value: "test value",
},
},
});
// Arrange
const paragraph = wrapper.find("p");
// Assert
expect(paragraph.text()).toEqual("label text");
});
it("Should have correct screenreader-only text", async () => {
// Act
let { wrapper } = setupMocks({});
// Arrange
await wrapper.setProps({
screenReaderOnlyText: "screenreader text",
value: "test value",
});
const paragraph = wrapper.find(".sr-only");
// Assert
expect(paragraph.text()).toEqual("screenreader text");
});
});
function setupMocks({ mountOptionsMockData = {} }) {
const wrapper = mount(checkbox, {
...mountOptionsMockData,
mixins: [inputButtonWrapperMixin],
});
return { wrapper };
}

View file

@ -0,0 +1,74 @@
<template>
<baseInputButton
v-bind="$props"
buttonWrapperClasses="form-check base-input-button checkbox"
inputClasses="form-check-input"
v-model="selectedValue">
<div class="d-flex align-items-start form-check-label">
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
<span v-if="screenReaderOnlyText" class="sr-only">{{ screenReaderOnlyText }}</span>
</div>
<div
v-if="additionalButtonData"
v-html="additionalButtonData"
class="additional-button-data"></div>
</baseInputButton>
</template>
<script>
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
export default {
name: "checkbox",
mixins: [inputButtonWrapperMixin],
components: {
baseInputButton,
},
};
</script>
<style lang="scss">
.form-check {
position: relative;
.form-check-input {
border: 1px solid $gray-500; //
border-radius: 2px; //
margin-right: 0.5rem;
opacity: 1;
height: 1em;
width: 1em;
&:checked {
background-color: $blue; //
background-size: contain;
background-position: center;
border: 1px solid $blue;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
+ .form-check-label {
p {
font-weight: 400;
font-size: 0.875rem;
color: $gray-600;
}
}
}
&:focus {
box-shadow: 0 0 0 2.5px $blue;
}
}
&:hover {
.form-check-input:not(:checked) {
box-shadow: 0 0 0 4px $blue-300;
}
}
p {
font-weight: 400;
font-size: 0.875rem;
color: $gray-600;
}
}
</style>