Merge branch 'develop' into feature/CSR-249
This commit is contained in:
commit
d0153869f8
7 changed files with 486 additions and 193 deletions
|
|
@ -20,10 +20,8 @@ module.exports = {
|
|||
"!src/layouts/reveal/**/*.vue",
|
||||
"!src/layouts/estimate/**/*.vue",
|
||||
// TODO REMOVE THESE AFTER WRITING UNIT TESTS
|
||||
"!src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue",
|
||||
"!src/ux-components/alert/alert.vue",
|
||||
"!src/helpers/validation-rules.js",
|
||||
"!src/common-components/question-chain/question-chain",
|
||||
// END
|
||||
], // ! means exclude from coverage.
|
||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
|
|
|
|||
241
src/common-components/question-chain/question-chain.spec.js
Normal file
241
src/common-components/question-chain/question-chain.spec.js
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import questionChain from "@/common-components/question-chain/question-chain.vue";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
jest.mock("@/store",()=>{return{};},{virtual:true});
|
||||
|
||||
describe("Question Chain component", () => {
|
||||
|
||||
it("Should not emit a modelValue change when setting selectedValue if isNewModelValueComplete is false", () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: [] });
|
||||
wrapper.vm.currentQuestion = 6;
|
||||
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
|
||||
const localThis = {
|
||||
$emit: jest.fn(),
|
||||
getNewModelValue: jest.fn(() => { return false })
|
||||
}
|
||||
|
||||
//Act
|
||||
questionChain.computed.selectedValue.set.call(localThis, answerReturned);
|
||||
|
||||
//Assert
|
||||
expect(localThis.$emit).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("Should emit a modelValue change when setting selectedValue if isNewModelValueComplete is true", () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: [] });
|
||||
wrapper.vm.currentQuestion = 6;
|
||||
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
|
||||
const localThis = {
|
||||
$emit: jest.fn(),
|
||||
getNewModelValue: jest.fn(() => { return true })
|
||||
}
|
||||
|
||||
//Act
|
||||
questionChain.computed.selectedValue.set.call(localThis, answerReturned);
|
||||
|
||||
//Assert
|
||||
expect(localThis.$emit).toBeCalledWith("update:modelValue", true);
|
||||
});
|
||||
|
||||
it("should return false if no returned answer is given", () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: [] });
|
||||
wrapper.vm.currentQuestion = 1;
|
||||
const answerReturned = null;
|
||||
|
||||
//Act
|
||||
const result = wrapper.vm.getNewModelValue(answerReturned);
|
||||
|
||||
//Assert
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return false if the user's answer on the current question leads to another question", () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: [] });
|
||||
wrapper.vm.currentQuestion = 1;
|
||||
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
|
||||
|
||||
//Act
|
||||
const result = wrapper.vm.getNewModelValue(answerReturned);
|
||||
|
||||
//Assert
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return an object with the final answer if the user's answer on the current question is a part number", () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: [] });
|
||||
wrapper.vm.currentQuestion = 6;
|
||||
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
|
||||
|
||||
//Act
|
||||
const result = wrapper.vm.getNewModelValue(answerReturned);
|
||||
|
||||
//Assert
|
||||
expect(result).toEqual(
|
||||
{
|
||||
answerResult: 'DW02102',
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: 'Is your vehicle equipped with heated seats?',
|
||||
selectedAnswerText: 'Yes'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
modelValueProp = "",
|
||||
questionDataProp = {
|
||||
"glassName": "Single",
|
||||
"glassLocation": "Windshield",
|
||||
"parts": null,
|
||||
"partQuestions": [
|
||||
{
|
||||
"questionSequence": 1,
|
||||
"questionText": "Is your Cherokee the Overland edition which can be identified by having a wood and leather wrapped steering wheel?",
|
||||
"answers": [
|
||||
{
|
||||
"answerText": "Yes",
|
||||
"nextQuestionSequence": 2,
|
||||
"answerResult": ""
|
||||
},
|
||||
{
|
||||
"answerText": "No",
|
||||
"nextQuestionSequence": 3,
|
||||
"answerResult": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"questionSequence": 2,
|
||||
"questionText": "Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
|
||||
"answers": [
|
||||
{
|
||||
"answerText": "Yes",
|
||||
"nextQuestionSequence": null,
|
||||
"answerResult": "DW02270"
|
||||
},
|
||||
{
|
||||
"answerText": "No",
|
||||
"nextQuestionSequence": null,
|
||||
"answerResult": "DW02264"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"questionSequence": 3,
|
||||
"questionText": "Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
|
||||
"answers": [
|
||||
{
|
||||
"answerText": "Yes",
|
||||
"nextQuestionSequence": null,
|
||||
"answerResult": "DW02268"
|
||||
},
|
||||
{
|
||||
"answerText": "No",
|
||||
"nextQuestionSequence": 4,
|
||||
"answerResult": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"questionSequence": 4,
|
||||
"questionText": "Is your vehicle equipped with automatic climate control which will change the fan speed automatically in order to maintain a set temperature?",
|
||||
"answers": [
|
||||
{
|
||||
"answerText": "Yes",
|
||||
"nextQuestionSequence": 5,
|
||||
"answerResult": ""
|
||||
},
|
||||
{
|
||||
"answerText": "No",
|
||||
"nextQuestionSequence": 6,
|
||||
"answerResult": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"questionSequence": 5,
|
||||
"questionText": "Is your vehicle equipped with heated seats?",
|
||||
"answers": [
|
||||
{
|
||||
"answerText": "Yes",
|
||||
"nextQuestionSequence": null,
|
||||
"answerResult": "DW02104"
|
||||
},
|
||||
{
|
||||
"answerText": "No",
|
||||
"nextQuestionSequence": null,
|
||||
"answerResult": "DW02103"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"questionSequence": 6,
|
||||
"questionText": "Is your vehicle equipped with heated seats?",
|
||||
"answers": [
|
||||
{
|
||||
"answerText": "Yes",
|
||||
"nextQuestionSequence": null,
|
||||
"answerResult": "DW02102"
|
||||
},
|
||||
{
|
||||
"answerText": "No",
|
||||
"nextQuestionSequence": null,
|
||||
"answerResult": "DW02101"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
methodsToMock = [],
|
||||
}) {
|
||||
|
||||
//Mock store
|
||||
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
|
||||
const mountOptions = getMountOptions({
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
},
|
||||
});
|
||||
|
||||
//Mock props
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn()
|
||||
}
|
||||
}
|
||||
mountOptions.propsData = {
|
||||
modelValue: modelValueProp,
|
||||
questionData: questionDataProp,
|
||||
};
|
||||
mountOptions.mixins = [mockMixin];
|
||||
|
||||
//Mock methods
|
||||
methodsToMock.forEach((methodName) => {
|
||||
questionChain.methods[methodName] = jest.fn();
|
||||
});
|
||||
|
||||
const wrapper = shallowMount(questionChain, mountOptions);
|
||||
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
};
|
||||
return { wrapper, cmsContent };
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
:groupName="`${questionData.glassName}-${questionData.glassLocation}-${i}`"
|
||||
textPosition="text-start"
|
||||
v-model="selectedValue"
|
||||
isRequired=true
|
||||
:isRequired=true
|
||||
:validationRules="validationRules"
|
||||
:clearOnUnmount=false
|
||||
/>
|
||||
|
|
@ -30,12 +30,14 @@ export default {
|
|||
};
|
||||
},
|
||||
props: {
|
||||
questionData: Array,
|
||||
questionData: Object,
|
||||
validationRules: String,
|
||||
modelValue: String,
|
||||
modelValue: Array,
|
||||
},
|
||||
computed: {
|
||||
|
||||
questions() {
|
||||
console.log("answeredQuestions: ", this.answeredQuestions)
|
||||
const questions = this.questionData.partQuestions.map((q, i) => {
|
||||
return {
|
||||
questionText: q.questionText,
|
||||
|
|
@ -52,7 +54,7 @@ export default {
|
|||
}
|
||||
});
|
||||
// add an empty item to be array[0] since we start with 1
|
||||
questions.unshift({});
|
||||
questions.unshift({ "DeliberatelyBlankObject": "This object has been added as a placeholder only for question #0"});
|
||||
return questions;
|
||||
},
|
||||
selectedValue: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question";
|
||||
|
||||
|
||||
describe("addressVehiclesQuestion.vue", () => {
|
||||
|
||||
it("Should return content for differentVehicleAlertHeader", () => {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,11 @@
|
|||
:buttonID="`${glassLocation}-${glassName}-${name}`"
|
||||
:groupName="`${glassLocation}-${glassName}`"
|
||||
@isCheckedChanged="ResetTintAndPartSelections()"
|
||||
:validationRules="validationRules"
|
||||
/>
|
||||
<div class="row form-test-error mt-1">
|
||||
<error-message :name="`${glassLocation}-${glassName}`" v-if="!suppressError"></error-message>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="fade" mode="out-in">
|
||||
|
|
@ -53,6 +57,7 @@
|
|||
:loaderEnabled="false"
|
||||
isRequired
|
||||
:groupName="`${glassLocation}-${glassName}-${name}`"
|
||||
:validationRules="validationRules"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -68,6 +73,7 @@ import buttonQuestion from "@/common-components/button-question/button-question"
|
|||
// Supporting files
|
||||
import { getTintImage } from "@/constants/tint-mapper";
|
||||
import { getCustomTransformValue } from "@/constants/dynamictext-mapper";
|
||||
import { ErrorMessage } from 'vee-validate';
|
||||
|
||||
export default {
|
||||
name: "glass-part-question",
|
||||
|
|
@ -84,6 +90,7 @@ export default {
|
|||
glassLocation: String,
|
||||
colorAnswers: Array,
|
||||
modelValue: Object,
|
||||
validationRules: String,
|
||||
},
|
||||
mounted() {
|
||||
this.LoadPreselectedValues();
|
||||
|
|
@ -91,6 +98,7 @@ export default {
|
|||
components: {
|
||||
listCard,
|
||||
buttonQuestion,
|
||||
ErrorMessage,
|
||||
},
|
||||
computed: {
|
||||
colorQuestionText() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
<template>
|
||||
<div class="page-container-grouped-styles vehicle-parts">
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
>
|
||||
<div class="page-container-grouped-styles vehicle-parts">
|
||||
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner ref="vehicleBanner" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
|
@ -8,10 +14,10 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false"
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -24,16 +30,24 @@
|
|||
</div>
|
||||
|
||||
<glassPartQuestion
|
||||
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
||||
v-model="glassParts[item.glassLocation + '-' + item.glassName]"
|
||||
:glassLocation="item.glassLocation"
|
||||
:glassName="item.glassName"
|
||||
:colorAnswers="item.colorAnswers"
|
||||
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
||||
v-model="glassParts[item.glassLocation + '-' + item.glassName]"
|
||||
:glassLocation="item.glassLocation"
|
||||
:glassName="item.glassName"
|
||||
:colorAnswers="item.colorAnswers"
|
||||
validationRules="replace-options-required"
|
||||
/>
|
||||
</div>
|
||||
<funnelFooter cmsWidgetName="FunnelFooterWidget" ref="funnelFooter" @back-clicked="backButtonAction" @ForwardClicked="forwardButtonAction" />
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -50,170 +64,177 @@ import { settleAllPromises } from "@/helpers/layout-helper";
|
|||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import store from "@/store";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("replace-options-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: "vehicle-parts",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
}, ];
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
name: "vehicle-parts",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
}, ];
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
|
||||
// Glass Part Question dynamic component
|
||||
Object.keys(vm.$refs)
|
||||
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
|
||||
.forEach((c) =>
|
||||
vm.$refs[c][0].initializeComponent({
|
||||
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
|
||||
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
glassParts: {},
|
||||
alertWidgetData: Object,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
glassPartQuestion,
|
||||
funnelHeader,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
alert,
|
||||
},
|
||||
computed: {
|
||||
PartsForQuestions() {
|
||||
const partsData = this.PartsFromApi;
|
||||
// Glass Part Question dynamic component
|
||||
Object.keys(vm.$refs)
|
||||
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
|
||||
.forEach((c) =>
|
||||
vm.$refs[c][0].initializeComponent({
|
||||
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
|
||||
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
glassParts: {},
|
||||
alertWidgetData: Object,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
Form,
|
||||
glassPartQuestion,
|
||||
funnelHeader,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
alert,
|
||||
},
|
||||
computed: {
|
||||
PartsForQuestions() {
|
||||
const partsData = this.PartsFromApi;
|
||||
|
||||
// Map API result data, to vehicle-parts data structure
|
||||
const mappedData = partsData.partsOrQuestions.map((g) => {
|
||||
return {
|
||||
glassName: g.glassName,
|
||||
glassLocation: g.glassLocation,
|
||||
colorAnswers: g.parts.reduce((arr, p) => {
|
||||
arr.push({
|
||||
ColorAnswerText: p.color,
|
||||
FeatureAnswers: [
|
||||
{
|
||||
FeatureAnswerText:
|
||||
p.description === "" ? p.color : p.description,
|
||||
PartNumber: p.partNumber,
|
||||
},
|
||||
],
|
||||
});
|
||||
return arr;
|
||||
}, []),
|
||||
};
|
||||
});
|
||||
|
||||
return mappedData;
|
||||
},
|
||||
|
||||
PartsFromApi() {
|
||||
return store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
|
||||
},
|
||||
|
||||
RefPrefix() {
|
||||
return "partQuestion";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Check if isRepair is populated and if the pageData we need is here (Parts data)
|
||||
if (
|
||||
(store.getters.damage.isRepair != null) &&
|
||||
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))
|
||||
.length !== 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
backButtonAction() {
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
forwardButtonAction() {
|
||||
const selectedGlassPartNumbers = [];
|
||||
const matchedParts = [];
|
||||
|
||||
// Compile all selected parts from the page.
|
||||
for (let [key, value] of Object.entries(this.glassParts)) {
|
||||
for (let [glassKey, glassValue] of Object.entries(value)) {
|
||||
selectedGlassPartNumbers.push(glassValue[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Match them to the parts from the API.
|
||||
for (let [key, value] of Object.entries(
|
||||
this.PartsFromApi.partsOrQuestions
|
||||
)) {
|
||||
for (let [partKey, partValue] of Object.entries(value.parts)) {
|
||||
const currentPart =
|
||||
this.PartsFromApi.partsOrQuestions[key].parts[partKey];
|
||||
const isMatched = selectedGlassPartNumbers.some(
|
||||
(p) => p === currentPart.partNumber
|
||||
);
|
||||
|
||||
if (isMatched) {
|
||||
matchedParts.push(currentPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no parts could be matched, throw an error.
|
||||
if (matchedParts.length === 0) {
|
||||
throw new Error("Could not match any parts to the selected parts");
|
||||
}
|
||||
|
||||
// Save parts to the store.
|
||||
store.commit(storeMutations.UPDATE_GLASS_PARTS, matchedParts);
|
||||
|
||||
// Navigate to the next page.
|
||||
this.$router.navigateAfterSave(
|
||||
this.navigationScenarios.SELECTED_PARTS,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
|
||||
resetDependentState() {
|
||||
// Nothing additional to reset here: The page save is already fully resetting all the line-items on the order
|
||||
},
|
||||
|
||||
LoadInitialPartsData() {
|
||||
const partsData = this.PartsFromApi;
|
||||
const alreadyPopulatedPartsData =
|
||||
this.$store.getters.lineItems.glassParts === null
|
||||
? {}
|
||||
: this.$store.getters.lineItems.glassParts;
|
||||
|
||||
partsData.partsOrQuestions.map((g) => {
|
||||
// If the part is already populated, use the value from the store and populate the v-model.
|
||||
Object.keys(alreadyPopulatedPartsData).forEach((key) => {
|
||||
const partNumber = alreadyPopulatedPartsData[key].partNumber;
|
||||
g.parts.forEach((p) => {
|
||||
if (p.partNumber === partNumber) {
|
||||
this.glassParts[g.glassLocation + "-" + g.glassName] = {
|
||||
[g.glassLocation]: [partNumber],
|
||||
};
|
||||
}
|
||||
// Map API result data, to vehicle-parts data structure
|
||||
const mappedData = partsData.partsOrQuestions.map((g) => {
|
||||
return {
|
||||
glassName: g.glassName,
|
||||
glassLocation: g.glassLocation,
|
||||
colorAnswers: g.parts.reduce((arr, p) => {
|
||||
arr.push({
|
||||
ColorAnswerText: p.color,
|
||||
FeatureAnswers: [
|
||||
{
|
||||
FeatureAnswerText:
|
||||
p.description === "" ? p.color : p.description,
|
||||
PartNumber: p.partNumber,
|
||||
},
|
||||
],
|
||||
});
|
||||
return arr;
|
||||
}, []),
|
||||
};
|
||||
});
|
||||
|
||||
return mappedData;
|
||||
},
|
||||
|
||||
PartsFromApi() {
|
||||
return store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
|
||||
},
|
||||
|
||||
RefPrefix() {
|
||||
return "partQuestion";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Check if isRepair is populated and if the pageData we need is here (Parts data)
|
||||
if (
|
||||
(store.getters.damage.isRepair != null) &&
|
||||
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))
|
||||
.length !== 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
backButtonAction() {
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
forwardButtonAction() {
|
||||
const selectedGlassPartNumbers = [];
|
||||
const matchedParts = [];
|
||||
|
||||
// Compile all selected parts from the page.
|
||||
for (let [key, value] of Object.entries(this.glassParts)) {
|
||||
for (let [glassKey, glassValue] of Object.entries(value)) {
|
||||
selectedGlassPartNumbers.push(glassValue[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Match them to the parts from the API.
|
||||
for (let [key, value] of Object.entries(
|
||||
this.PartsFromApi.partsOrQuestions
|
||||
)) {
|
||||
for (let [partKey, partValue] of Object.entries(value.parts)) {
|
||||
const currentPart =
|
||||
this.PartsFromApi.partsOrQuestions[key].parts[partKey];
|
||||
const isMatched = selectedGlassPartNumbers.some(
|
||||
(p) => p === currentPart.partNumber
|
||||
);
|
||||
|
||||
if (isMatched) {
|
||||
matchedParts.push(currentPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no parts could be matched, throw an error.
|
||||
if (matchedParts.length === 0) {
|
||||
throw new Error("Could not match any parts to the selected parts");
|
||||
}
|
||||
|
||||
// Save parts to the store.
|
||||
store.commit(storeMutations.UPDATE_GLASS_PARTS, matchedParts);
|
||||
|
||||
// Navigate to the next page.
|
||||
this.$router.navigateAfterSave(
|
||||
this.navigationScenarios.SELECTED_PARTS,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
|
||||
resetDependentState() {
|
||||
// Nothing additional to reset here: The page save is already fully resetting all the line-items on the order
|
||||
},
|
||||
|
||||
LoadInitialPartsData() {
|
||||
const partsData = this.PartsFromApi;
|
||||
const alreadyPopulatedPartsData =
|
||||
this.$store.getters.lineItems.glassParts === null
|
||||
? {}
|
||||
: this.$store.getters.lineItems.glassParts;
|
||||
|
||||
partsData.partsOrQuestions.map((g) => {
|
||||
// If the part is already populated, use the value from the store and populate the v-model.
|
||||
Object.keys(alreadyPopulatedPartsData).forEach((key) => {
|
||||
const partNumber = alreadyPopulatedPartsData[key].partNumber;
|
||||
g.parts.forEach((p) => {
|
||||
if (p.partNumber === partNumber) {
|
||||
this.glassParts[g.glassLocation + "-" + g.glassName] = {
|
||||
[g.glassLocation]: [partNumber],
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.LoadInitialPartsData();
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.LoadInitialPartsData();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
maxLength="17"
|
||||
:mask="vinMask"
|
||||
@focus="setVinTouched"
|
||||
@maska="rawVinValue = $event.target.dataset.maskRawValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -188,6 +189,7 @@ export default {
|
|||
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
|
||||
initialVin: this.getVinFromStore(),
|
||||
vinTouched: false,
|
||||
rawVinValue: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -203,10 +205,14 @@ export default {
|
|||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
zip() {
|
||||
this.noServiceZip = false;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
perfectMatchNewVinAlert() {
|
||||
const isVinPerfectMatch = this.vinPopulatedOnPageLoad && this.vin === this.getVinFromStore();
|
||||
const vinToCheck = this.vinTouched ? this.rawVinValue : this.initialVin;
|
||||
const isVinPerfectMatch = this.vinPopulatedOnPageLoad && vinToCheck === this.getVinFromStore();
|
||||
this.updateIsCarIdDifferent(isVinPerfectMatch);
|
||||
return isVinPerfectMatch;
|
||||
},
|
||||
|
|
@ -305,38 +311,56 @@ export default {
|
|||
}
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
let zipValidationResponse;
|
||||
let vehicleLookupResponse;
|
||||
|
||||
const zipValidation = this.validateZip(this.zip);
|
||||
const zipValidationResponse = await zipValidation;
|
||||
|
||||
if (!zipValidationResponse.data.isServiceable) {
|
||||
this.customAlertData.zip = this.zip;
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
this.noServiceZip = true;
|
||||
this.invalidZip = this.zip;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user has clicked on the VIN field, either they are doing a new VIN lookup or changing the VIN previously matched.
|
||||
// Therefore we need to do a VIN Lookup
|
||||
let vehicleLookupResponse;
|
||||
if (this.vinTouched && this.vin != this.initialVin) {
|
||||
if (this.vinTouched && this.vin != this.initialVin) {
|
||||
// If the user has clicked on the VIN field, either they are doing a new VIN lookup or changing the VIN previously matched.
|
||||
// Therefore we need to do a Vehicle Lookup
|
||||
const vinToLookup = this.vinTouched ? this.vin : this.initialVin;
|
||||
const vehicleLookup = this.lookupVehicle(vinToLookup);
|
||||
|
||||
zipValidationResponse = await zipValidation;
|
||||
vehicleLookupResponse = await vehicleLookup.catch((response) => {
|
||||
if (response.status == StatusCodes.NOT_FOUND) {
|
||||
this.vinNotFound = true;
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
this.noServiceZip = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!vehicleLookupResponse) {
|
||||
// Check if Service Zip entered is servicable, if not display an alert
|
||||
if (!zipValidationResponse.data.isServiceable) {
|
||||
this.customAlertData.zip = this.zip;
|
||||
this.noServiceZip = true;
|
||||
this.invalidZip = this.zip;
|
||||
}
|
||||
|
||||
if (!vehicleLookupResponse || !zipValidationResponse.data.isServiceable) {
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.navigateForward();
|
||||
const zipValidationResponse = await zipValidation;
|
||||
|
||||
// Check if Service Zip entered is serviceable, if not display an alert
|
||||
if (!zipValidationResponse.data.isServiceable) {
|
||||
this.customAlertData.zip = this.zip;
|
||||
this.noServiceZip = true;
|
||||
this.invalidZip = this.zip;
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
|
||||
return;
|
||||
|
||||
} else {
|
||||
this.navigateForward();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vehicleLookupResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue