DigitalConsumer.FixMyGlass/src/mixins/vehicle-questions-mixin.js
CarlNation a3dff865ea CSR-1625
router/index.js assumes a load-session successful response should always go to heritage.  This may have been true in the initial version of NextGen but now we can load and direct it to the correct page.  The incorrect routing to heritage was also failing here for returning users that already had a NextGen cookie.  The error caused us to route to the beginning.
Fixed that error by removing the default true in the navigateToHeritageFunnel and changing all calls to pass a true or false.
2023-09-08 08:25:44 -04:00

527 lines
26 KiB
JavaScript

import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigationScenarios } from "../router/router-constants/navigation-scenarios";
import { storeActions } from "@/constants/store-actions";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store";
export default {
methods: {
hasPartQuestions(partsOrQuestions) {
return partsOrQuestions?.some((pq) => pq.partQuestions?.length > 0);
},
hasGlassLocationWithMultipleParts(partsOrQuestions) {
return partsOrQuestions?.some((pq) => pq.parts?.length > 1);
},
hasChildPartQuestions(partsOrQuestions) {
return partsOrQuestions?.some((pq) => {
return pq.parts?.some((part) => part.childPartQuestions?.length > 0);
});
},
hasCapabilityQuestions(partsOrQuestions) {
return partsOrQuestions?.some((pq) => {
return pq.parts?.some((part) => part.requiresCapabilityQuestions === true);
});
},
// method to only include keys listed for lineItems.glassParts in
// https://safelite.atlassian.net/wiki/spaces/DC/pages/17137665/Catalog+Front-End+State#glassParts
reducedGlassPartsArray(glassParts) {
const reducedGlassParts = [];
glassParts.forEach((glass) => {
if (Array.isArray(glass.parts) && glass.parts.length === 1) {
const singlePart = glass.parts[0];
reducedGlassParts.push({
partNumber: singlePart.partNumber,
description: singlePart.description,
color: singlePart.color,
partType: singlePart.partType,
canSafeliteRecalibrate: singlePart.canSafeliteRecalibrate,
requiresRecalibration: singlePart.requiresRecalibration,
requiresCapabilityQuestions: singlePart.requiresCapabilityQuestions,
recalibrationType: singlePart.recalibrationType,
childParts: singlePart.childParts,
kitPrice: singlePart.kitPrice,
sellingPrice: singlePart.sellingPrice,
laborAmount: singlePart.laborAmount,
});
}
});
return reducedGlassParts;
},
comparePageIndices(currentPage, fmgPage) {
const orderedVehicleQuestionPages = [
fmgPageValues.PART_QUESTIONS,
fmgPageValues.VEHICLE_PARTS,
fmgPageValues.MOLDING_QUESTIONS,
fmgPageValues.CAPABILITY_QUESTIONS,
fmgPageValues.QUOTE,
];
return (
orderedVehicleQuestionPages.indexOf(currentPage) -
orderedVehicleQuestionPages.indexOf(fmgPage)
);
},
currentPageComesBeforePage(currentPage = this.$route.query.fmgPage, fmgPage) {
return this.comparePageIndices(currentPage, fmgPage) < 0;
},
currentPageComesAfterPage(currentPage = this.$route.query.fmgPage, fmgPage) {
return this.comparePageIndices(currentPage, fmgPage) > 0;
},
setupInitialData(glass, index, alreadyAnsweredQuestions, vm) {
const self = vm ?? this;
// clear answerData if no questions are already answered
if (!alreadyAnsweredQuestions) {
glass.answerData = null;
}
alreadyAnsweredQuestions?.forEach((answeredGlass) => {
// if answeredGlass lacks any of these properties then exit
if (
!answeredGlass.glassLocation ||
!answeredGlass.glassName ||
!answeredGlass.answeredQuestions ||
(!answeredGlass.result && !answeredGlass.partNum)
) {
return;
}
// test if glass parts match
if (
glass.glassLocation === answeredGlass.glassLocation &&
glass.glassName === answeredGlass.glassName
) {
let answerString = "";
// loop through answeredQuestions for matches
answeredGlass.answeredQuestions.forEach((answeredQuestion) => {
if (!answeredQuestion.questionNum || !answeredQuestion.selectedAnswerText) {
return;
}
// determine which answer was previously chosen
const chosenAns = glass.questions[
answeredQuestion.questionNum - 1
].answers.find((a) => {
return (
a.answerText.toUpperCase() ===
answeredQuestion.selectedAnswerText.toUpperCase()
);
});
// set the answerString to use for answerSelected
if (chosenAns.nextQuestionSequence) {
answerString = `${answeredQuestion.questionNum}|nextQuestion|${chosenAns.nextQuestionSequence}|${chosenAns.answerText}`;
} else {
answerString = `${answeredQuestion.questionNum}|answer|${chosenAns.answerResult}|${chosenAns.answerText}`;
}
// mark this question as answered (question-chain will read this)
glass.questions[answeredQuestion.questionNum - 1].answerSelected =
answerString;
// mark this question as suppressed if needed (question-chain uses this)
if (answeredQuestion.suppressThisQuestion) {
glass.questions[
answeredQuestion.questionNum - 1
].suppressThisQuestion = true;
}
});
// advance the currentGlassIndex
self.currentGlassIndex = index;
const answerResult = answeredGlass.partNum
? answeredGlass.partNum
: answeredGlass.result;
// the above logic for answerResult covers all 3 ___-questions page scenarios
// add answerData to current glass
glass.answerData = {
answerResult: answerResult,
answeredQuestions: answeredGlass.answeredQuestions,
};
}
});
return glass;
},
handleCompletedQuestionChainAnswers(answer, glassKey, vm) {
// only runs when all questions in a question-chain have been answered
// when selectedAnswers updates, user has completed this part's question chain and has a final answer
// (does not get run for each invididual question's answer, only when
// all relevant questions for the current part have been answered)
/* answer example format:
{
"answerResult": "DD11132",
"answeredQuestions": [
{
"questionText": "Is your Grand Cherokee the Laredo model?",
"selectedAnswerText": "Yes",
"questionNum": 1,
}
],
"index": 0
}
*/
const self = vm ?? this;
// clear out any preloaded answers
self.selectedAnswers = {};
// loop through every answered question on the currently answered glass part
answer.answeredQuestions?.forEach((answeredQuestion, answeredQuestionIndex) => {
/* answeredQuestion example format:
{
"questionText": "Is your Grand Cherokee the Laredo model?",
"selectedAnswer": "1|nextQuestion|3|Yes",
"selectedAnswerText": "Yes",
"questionNum": 1,
}
*/
const answeredQuestionText = answeredQuestion.questionText.toUpperCase();
const answeredQuestionAnswer = answeredQuestion.selectedAnswer.toUpperCase();
const answeredQuestionAnswerText =
answeredQuestion.selectedAnswerText.toUpperCase();
const answeredQuestionNum = answeredQuestion.questionNum;
// HANDLE DUPLICATE QUESTIONS
// loop through all glass data
self.questionsData.forEach((glass, glassIndex) => {
if (glassIndex === answer.index) {
glass.questions.forEach((question, questionIndex) => {
// clear out any previously set answers on first pass with first answered question
if (answeredQuestionIndex === 0) question.answerSelected = null;
if (answeredQuestionNum - 1 === questionIndex) {
// on the right question
question.answerSelected = answeredQuestionAnswer;
}
});
}
// limit duplicate search to glass pieces that are in or follow after the currently being answered glass piece
if (glassIndex > answer.index) {
let indexToSuppressTo;
// reset this glass piece, in case user is changing their previous answers
glass.answerData = null;
glass.isSuppressedPart = null;
// loop through this glass piece's questions, looking for a questionText match
glass.questions.forEach((question, questionIndex) => {
// clear out any previously set answers on first pass with first answer
if (answeredQuestionIndex === 0) question.answerSelected = null;
// clear or set suppressThisQuestion property for each question
if (indexToSuppressTo && questionIndex + 1 < indexToSuppressTo) {
question.suppressThisQuestion = true;
} else {
question.suppressThisQuestion = null;
}
// handle duplicate questions
if (question.questionText.toUpperCase() === answeredQuestionText) {
let matchedAnswer;
// handle matching answer in duplicated question
question.answers.forEach((ans) => {
if (
ans.answerText.toUpperCase() === answeredQuestionAnswerText
) {
matchedAnswer = ans;
ans.selected = true;
} else {
ans.selected = null;
}
});
// handle duplicate's nextQuestion logic on other related questions
if (matchedAnswer.nextQuestionSequence) {
// clear any suppression on the nextQuestion
glass.questions[
matchedAnswer.nextQuestionSequence - 1
].suppressThisQuestion = null;
// if the duplicate is 1ST question in array, set indexToSuppressTo
if (questionIndex === 0) {
if (
!indexToSuppressTo ||
matchedAnswer.nextQuestionSequence < indexToSuppressTo
) {
indexToSuppressTo = matchedAnswer.nextQuestionSequence;
}
}
}
// update answers in this glass piece's questions with duplication logic modifications
glass.questions.forEach((q) => {
q.answers.forEach((a) => {
// revert any previously set nextQuestion logic modifications
if (
a.originalNextQuestionSequence ===
question.questionSequence
) {
// restore original nextQuestionSequence
a.nextQuestionSequence = a.originalNextQuestionSequence;
self.originalNextQuestionSequence = null;
// restore original answerResult
if (a.originalAnswerResult) {
a.answerResult = a.originalAnswerResult;
a.originalAnswerResult = null;
}
}
// modify logic on any related questions
if (a.nextQuestionSequence === question.questionSequence) {
// update either nextQuestionSequence or answerResult
// update questions that lead to duplicated question
if (matchedAnswer.nextQuestionSequence) {
a.originalNextQuestionSequence =
a.nextQuestionSequence;
a.nextQuestionSequence =
matchedAnswer.nextQuestionSequence;
} else {
a.originalNextQuestionSequence =
a.nextQuestionSequence;
a.nextQuestionSequence = null;
a.originalAnswerResult =
a.originalAnswerResult || a.answerResult;
a.answerResult = matchedAnswer.answerResult;
}
}
});
});
// suppress current question
question.suppressThisQuestion = true;
// are there any questions left that are not suppressed?
const remainingQuestions = glass.questions.filter((q) => {
return !q.suppressThisQuestion;
});
if (remainingQuestions.length < 1) {
// this is the final answer for this glass piece
// mark this glass piece as completely answered by adding answerData
const answeredQuestionObj = {
questionText: question.questionText,
selectedAnswerText: matchedAnswer.answerText,
questionNum: question.questionSequence,
suppressThisQuestion: question.suppressThisQuestion,
};
// set the answerData (used as indicator that it has been already answered)
glass.answerData = {
answerResult: matchedAnswer.nextQuestionSequence
? matchedAnswer.nextQuestionSequence
: matchedAnswer.answerResult,
answeredQuestions: [answeredQuestionObj],
};
// suppress this glass piece because it has an answer
glass.isSuppressedPart = true;
}
}
}); // DONE looping through glass.questions
// Update key to force re-render of glass piece with duplicate question in case user changes previous related answer in the chain
self.questionsData[glassIndex].key =
self.questionsData[glassIndex].key + Date.now().toString();
}
});
});
// set final answer data for the current answered glass part
self.questionsData[answer.index].answerData = {
answerResult: answer.answerResult,
answeredQuestions: answer.answeredQuestions,
};
// this part has been fully answered, so advance to next part's question chain
for (let i = answer.index + 1; i < this.questionsData.length; i++) {
// if this part has not yet been fully answered, then make it the current part
if (!this.questionsData[i].answerData?.answerResult) {
this.currentGlassIndex = i;
break;
}
}
},
// Can't use `this` because navigateForward is also called from vin-pages-mixin
async navigateForward(partsOrQuestions, vm) {
const self = vm ?? this;
const currentPage = self.$route.query.fmgPage;
const hasPartQuestions = this.hasPartQuestions(partsOrQuestions);
const hasGlassLocationWithMultipleParts =
this.hasGlassLocationWithMultipleParts(partsOrQuestions);
const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions);
const hasCapabilityQuestions = this.hasCapabilityQuestions(partsOrQuestions);
if (
hasPartQuestions &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.PART_QUESTIONS)
) {
self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
self.$route,
{},
{},
{ partsOrQuestions: partsOrQuestions }
);
} else if (
hasGlassLocationWithMultipleParts &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.VEHICLE_PARTS)
) {
// if multiple parts on any glass
// go to vehicle-parts page and pass the partsData
self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
self.$route,
{},
{},
{ partsOrQuestions: partsOrQuestions }
);
} else if (
hasChildPartQuestions &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.MOLDING_QUESTIONS)
) {
// if any childpart questions
// go to molding-questions page and pass the partsData
self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
self.$route,
{},
{},
{ partsOrQuestions: partsOrQuestions }
);
} else if (
hasCapabilityQuestions &&
this.currentPageComesBeforePage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS)
) {
// if has capability questions
// go to capability-questions page and pass the partsData
// mimic part-questions page data for consistency
for (let partOrQuestion of partsOrQuestions) {
if (this.hasCapabilityQuestions([partOrQuestion])) {
let capabilityQuestionsForGlassLocation = (
await baseMixin.methods.dispatchStoreAction(
storeActions.GET_CAPABILITY_QUESTIONS,
{
carId: store.getters.vehicle.carId,
partNumber: partOrQuestion.parts[0].partNumber,
}
)
).data;
capabilityQuestionsForGlassLocation.forEach((question) => {
question.answers = question.answers.map((answer) => {
return {
...answer,
answerResult: answer.answerResult1,
};
});
});
partOrQuestion.capabilityQuestions = capabilityQuestionsForGlassLocation;
}
}
self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
self.$route,
{},
{},
{ partsOrQuestions }
);
} else {
// if single parts only
const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions);
// save to store lineItems.glassParts
self.dispatchStoreAction(storeActions.SAVE_GLASS_PARTS, collectedGlassParts, false);
const payment = store.getters.payment;
if (store.getters.order.referralNumber?.length === 6) {
navigateToHeritageFunnel({
shouldSaveSession: true,
loadingModal: self.$refs.loadingModal,
});
} else if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
navigateToHeritageFunnel({
shouldSaveSession: true,
loadingModal: self.$refs.loadingModal,
});
} else {
self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
self.$route
);
}
}
},
// Can't use `this` because navigateForward is also called from quote
async navigateBack(vm) {
const self = vm ?? this;
const currentPage = self.$route.query.fmgPage;
const pageDataCapabilityQuestions = self.$store.getters.pageData(
fmgPageValues.CAPABILITY_QUESTIONS
);
const pageDataMoldingQuestions = self.$store.getters.pageData(
fmgPageValues.MOLDING_QUESTIONS
);
const pageDataVehicleParts = self.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
const pageDataPartQuestions = self.$store.getters.pageData(
fmgPageValues.PART_QUESTIONS
);
const allPartsOrQuestions = [
...(pageDataCapabilityQuestions?.partsOrQuestions || []),
...(pageDataMoldingQuestions?.partsOrQuestions || []),
...(pageDataVehicleParts?.partsOrQuestions || []),
...(pageDataPartQuestions?.partsOrQuestions || []),
];
const hasPartQuestions = this.hasPartQuestions(allPartsOrQuestions);
const hasGlassLocationWithMultipleParts =
this.hasGlassLocationWithMultipleParts(allPartsOrQuestions);
const hasChildPartQuestions = this.hasChildPartQuestions(allPartsOrQuestions);
const hasCapabilityQuestions = this.hasCapabilityQuestions(allPartsOrQuestions);
const vin = self.$store.getters.vehicle.vin;
let backNavigationScenario =
!vin || vin === ""
? navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS
: navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS;
if (
hasCapabilityQuestions &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS)
) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_CAPABILITY_QUESTIONS;
} else if (
hasChildPartQuestions &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.MOLDING_QUESTIONS)
) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_MOLDING_QUESTIONS;
} else if (
hasGlassLocationWithMultipleParts &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.VEHICLE_PARTS)
) {
backNavigationScenario =
navigationScenarios.CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE;
} else if (
hasPartQuestions &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.PART_QUESTIONS)
) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS;
}
self.$router.navigateWithoutSaving(backNavigationScenario, self.$route);
},
},
};