diff --git a/azure-pipelines-automated-testing.yml b/azure-pipelines-automated-testing.yml index 746906a04..2d9c19d74 100644 --- a/azure-pipelines-automated-testing.yml +++ b/azure-pipelines-automated-testing.yml @@ -44,6 +44,28 @@ stages: shardNumber: 4 steps: + - script: | + echo "Starting Docker cleanup to free space..." + + # Remove all stopped containers + echo "Removing stopped containers..." + docker container prune -f + + # Remove dangling images (untagged) + echo "Removing dangling images..." + docker image prune -f + + # Remove unused volumes + echo "Removing unused volumes..." + docker volume prune -f + + # Show disk usage after cleanup + echo "Docker system usage after cleanup:" + docker system df + + echo "Docker cleanup completed!" + displayName: "Docker Cleanup" + - task: Docker@2 displayName: "Build Docker Image" inputs: diff --git a/playwright-tests/business-logic/constants/DefaultTestData.ts b/playwright-tests/business-logic/constants/DefaultTestData.ts index 197297b4b..a5929ce76 100644 --- a/playwright-tests/business-logic/constants/DefaultTestData.ts +++ b/playwright-tests/business-logic/constants/DefaultTestData.ts @@ -97,7 +97,6 @@ export function getDefaultTestData(): Partial { isPolicyDriver: false, isPolicyFound: false, isUseVehicleOnPolicy: true, - isRecalNotification: false, hasOemEndorsement: false, skipEstimatePage: false, isRecalVehicle: false, diff --git a/playwright-tests/business-logic/types/ITestData.ts b/playwright-tests/business-logic/types/ITestData.ts index f9d776224..61ed18128 100644 --- a/playwright-tests/business-logic/types/ITestData.ts +++ b/playwright-tests/business-logic/types/ITestData.ts @@ -25,7 +25,6 @@ export interface ITestData { isPolicyFound: boolean, otherVehiclesOnPolicy: IVehicleDetails[], // IF defined, we validate that the vehicles are present. isUseVehicleOnPolicy: boolean, // Should we use the vehicle on the policy? - isRecalNotification: boolean, // Does Recalibration Information page show up? endorsements: IEndorsementDetails[], hasOemEndorsement: boolean, // OEM Endorsement does not appear on endorsements page, so it has a separate flag.s skipEstimatePage: boolean, diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 709e288ff..213835484 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -300,7 +300,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { } export async function handleInsuranceFlow(testCase: TestCase) { - const { isPolicyFound, isPolicyDriver, endorsements, isRecalNotification } = testCase.testData; + const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle } = testCase.testData; // Check if the insurance policy has endorsements const hasEndorsements = endorsements && endorsements.length > 0; @@ -344,7 +344,7 @@ export async function handleInsuranceFlow(testCase: TestCase) { let policyInfoSubmittedPage = testCase.pages.policyInfoSubmittedPage; await policyInfoSubmittedPage.handlePolicyInfoSubmittedPage(); - if(isRecalNotification) + if(isRecalVehicle) { let recalibrationInfoPage = testCase.pages.recalibrationInfoPage; await recalibrationInfoPage.handleRecalibrationInfoPage(); diff --git a/playwright-tests/tests/InsuranceITAC21stCentury.ts b/playwright-tests/tests/InsuranceITAC21stCentury.ts index 70ce6b528..b97efd31c 100644 --- a/playwright-tests/tests/InsuranceITAC21stCentury.ts +++ b/playwright-tests/tests/InsuranceITAC21stCentury.ts @@ -19,7 +19,7 @@ const insuranceITAC21stCenturyData: Partial = { isDuplicateClaim: true, isPolicyFound: true, isUseVehicleOnPolicy: true, - isRecalNotification: true, // Special flag for recalibration notification + isRecalVehicle: true, // Special flag for recalibration notification // Override customer details for California location customerDetails: { diff --git a/src/digital-components/sierra-webchat/sierra-webchat.vue b/src/digital-components/sierra-webchat/sierra-webchat.vue index 2a114616a..8210e9075 100644 --- a/src/digital-components/sierra-webchat/sierra-webchat.vue +++ b/src/digital-components/sierra-webchat/sierra-webchat.vue @@ -94,8 +94,16 @@ export default { FirstName: transfer.data.first_name, LastName: transfer.data.last_name, Email: transfer.data.email, - Subject: transfer.data.chat_summary, // for now passing in chat_summery in Subject. But this will be changed in future. + Subject: transfer.data.chat_summary, }; + window.embedded_svc.settings.extraPrechatFormDetails = [ + { + label: "Scarlett AI Summary", + value: transfer.data.chat_summary, + transcriptFields: ["Scarlett_AI_Summary__c"], + displayToAgent: true, + }, + ]; if ( window.embedded_svc.liveAgentAPI && typeof window.embedded_svc.liveAgentAPI.startChat === "function" diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 2a715bc93..ef3e0300c 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -117,6 +117,10 @@ {{ amountPaidText }} {{ getLineItemAmount(amountPaid) }} +
+ {{ donationCartItemName }} + {{ getLineItemAmount(donationCartItem.subTotal) }} +
{{ amountDueText }} {{ getLineItemAmount(amountDue, showCoverageAsPending) }} @@ -472,10 +476,6 @@ export default { }); } - if (this.donationCartItem) { - cartItems.push(this.donationCartItem); - } - return cartItems; }, }, @@ -1309,12 +1309,15 @@ export default { .sub-total, .sales-tax, .amount-due, - .amount-paid { - font-weight: 500; + .amount-paid, + .donation-amount { + font-family: + UrbanistSemibold, AvertaSemibold; //Okay to remove AvertaSemibold after 2025.06.19 merge/release color: $black; } .sub-total { border-top: 1px solid $green; + background-color: $green-100; } .service-type, .deductible { diff --git a/src/layouts/estimate/estimate.vue b/src/layouts/estimate/estimate.vue index 811cd1a86..2818d2ecb 100644 --- a/src/layouts/estimate/estimate.vue +++ b/src/layouts/estimate/estimate.vue @@ -62,7 +62,7 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js"; import baseMixin from "@/mixins/base-mixin.js"; import { queryStrings } from "@/constants/query-strings"; import { nextTick } from "vue"; -import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash"; +import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash"; // Define Validation Rules defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); @@ -100,7 +100,7 @@ export default { } const zip = - consumeQueryFromStash(queryStrings.ZIP_CODE) ?? + peekQueryFromStash(queryStrings.ZIP_CODE) ?? store.getters.order.serviceLocation.zipCode; var vinByAddressPromise; diff --git a/src/router/methods/helpers/querystring-stash.js b/src/router/methods/helpers/querystring-stash.js index 96b206572..b000f8feb 100644 --- a/src/router/methods/helpers/querystring-stash.js +++ b/src/router/methods/helpers/querystring-stash.js @@ -1,4 +1,5 @@ import { reactive } from "vue"; +import { debugLog } from "@/helpers/debug-log-helper"; const queryStash = reactive({ queries: [], @@ -27,10 +28,18 @@ export function stashAllQueries(toRoute) { } function getStashedQuery(key) { + debugLog(`Fetching querystring with key:`, key); const match = queryStash.queries.find( (entry) => entry?.key?.toLowerCase() === key?.toLowerCase() ); + if (match) { + debugLog(`Found result:`, match.value); + debugLog(`Already consumed:`, match.used); + } else { + debugLog(`Found no result`); + } + return match; } diff --git a/src/router/methods/route-logic/payment.js b/src/router/methods/route-logic/payment.js new file mode 100644 index 000000000..73f2b5cac --- /dev/null +++ b/src/router/methods/route-logic/payment.js @@ -0,0 +1,14 @@ +import store from "@/store"; +import { storeActions } from "@/constants/store-actions"; +import { paymentMethods } from "@/constants/payment-method-constants"; +import { debugLog } from "@/helpers/debug-log-helper"; + +export async function paymentBeforeEnter(to, from) { + const piaType = store.getters.order?.payment?.piaType; + debugLog(`Entering payment with type =`, piaType); + + if (piaType === paymentMethods.PAYPAL) { + debugLog(`Changing to payment type =`, paymentMethods.CREDIT_CARD); + await store.dispatch(storeActions.SAVE_PAYMENT_METHOD_CHOICE, paymentMethods.CREDIT_CARD); + } +} diff --git a/src/router/methods/routes.js b/src/router/methods/routes.js index 19c802aae..c029ce2cb 100644 --- a/src/router/methods/routes.js +++ b/src/router/methods/routes.js @@ -11,6 +11,7 @@ import { autoRouteBeforeEnter } from "@/router/methods/route-logic/auto-route"; import { errorBeforeEnter } from "@/router/methods/route-logic/error"; import { restartBeforeEnter } from "@/router/methods/route-logic/restart"; import { paymentMethodBeforeEnter } from "@/router/methods/route-logic/payment-method"; +import { paymentBeforeEnter } from "@/router/methods/route-logic/payment"; export const routes = [ // Non-virtual pages. @@ -32,7 +33,7 @@ export const routes = [ createRoute(routeData.SCHEDULE), createRoute(routeData.CUSTOMER_DETAILS), createRoute(routeData.PAYMENT_METHOD, paymentMethodBeforeEnter), - createRoute(routeData.PAYMENT), + createRoute(routeData.PAYMENT, paymentBeforeEnter), createRoute(routeData.PAYMENT_PIA_RETURN), createRoute(routeData.CONFIRMATION), createRoute(routeData.RETURN_USER), diff --git a/src/store/index.js b/src/store/index.js index c3ad4996e..073a50e23 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1072,33 +1072,6 @@ export const getters = { }, }; -function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { - return (array ?? []).map((x) => x[propertyName]).filter((x) => x); -} - -function getTimeSlotsAdditionalEventData( - provisionalTriggers, - zipCode, - firstAvailableAppointmentDateString, - shopAppointmentType -) { - var numberOfDays = null; - if (firstAvailableAppointmentDateString) - numberOfDays = getDateDifferenceInDays( - new Date().toISOString().split("T")[0], - firstAvailableAppointmentDateString - ); - - if (shopAppointmentType) - return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join( - "," - )}`; - else - return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join( - "," - )}`; -} - // Export Actions export const actions = { // Vehicle API Actions @@ -2059,20 +2032,31 @@ export const actions = { }, }; - return globalMethods.callHttpClient({ + let hasCalled = timeSlotCallFlags.shop; + if (!hasCalled) { + timeSlotCallFlags.shop = true; + } + + const options = { method: endpoints.GetShopTimeSlots.method, endpoint: endpoints.GetShopTimeSlots.url, payload: payload, logApiCall: true, pageNameToLog: pageNameToLog, - additionalSuccessEventDataHandler: (response) => + }; + + // Only set handler if this is the very first call in this session + if (!hasCalled) { + options.additionalSuccessEventDataHandler = (response) => getTimeSlotsAdditionalEventData( response.data.provisionalTriggers, order.serviceLocation.zipCode, response.data.days?.[0]?.date, shopAppointmentType - ), - }); + ); + } + + return globalMethods.callHttpClient(options); }, getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) { @@ -2116,19 +2100,30 @@ export const actions = { zipCode: order.serviceLocation.zipCode, }; - return globalMethods.callHttpClient({ + let hasCalled = timeSlotCallFlags.mobile; + if (!hasCalled) { + timeSlotCallFlags.mobile = true; + } + + const options = { method: endpoints.GetMobileTimeSlots.method, endpoint: endpoints.GetMobileTimeSlots.url, payload: payload, logApiCall: true, pageNameToLog: pageNameToLog, - additionalSuccessEventDataHandler: (response) => + }; + + // Only set handler if this is the very first call in this session + if (!hasCalled) { + options.additionalSuccessEventDataHandler = (response) => getTimeSlotsAdditionalEventData( response.data.provisionalTriggers, order.serviceLocation.zipCode, response.data.days?.[0]?.date - ), - }); + ); + } + + return globalMethods.callHttpClient(options); }, getMobilePremiumFee(context, { pageNameToLog }) { @@ -3457,100 +3452,6 @@ export default createStore({ actions, }); -// Private Functions - -function getHasRecalibrationPart(state) { - return containsRecalParts(state.order.lineItems); -} - -function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) { - if (!arrayOfObjects) return null; - - return arrayOfObjects.sort((a, b) => { - if (a[propertyName] < b[propertyName]) return -1; - else if (a[propertyName] > b[propertyName]) return 1; - else return 0; - }); -} - -function convertGlassPieceNamingForApi(glassArray) { - if (!glassArray || glassArray.length === 0) return []; - - // check if array already converted. (likely when a session has been saved previously and then reloaded) - if (glassArray[0].location !== undefined) { - return glassArray; - } - - const converted = []; - glassArray.forEach((glass) => { - converted.push({ - location: glass.glassLocation, - name: glass.glassName, - }); - }); - return converted; -} - -function convertResultsForApi(resultsArray) { - if (!resultsArray) return []; - const converted = []; - resultsArray.forEach((answer) => { - converted.push({ - location: answer.glassLocation, - name: answer.glassName, - result: answer.result, - }); - }); - return converted; -} - -function convertGlassPieceNamingFromApi(glassArray) { - glassArray.forEach((glass) => { - glass.glassLocation = glass.glassPiece.location; - glass.glassName = glass.glassPiece.name; - delete glass.glassPiece; - return glass; - }); - return glassArray; -} - -function addPricesToLineItems(lineItems, pricingLineItems) { - lineItems.forEach((lineItem) => { - const lineItemIndex = pricingLineItems.findIndex( - (pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber - ); - - if (lineItem.childParts) { - addPricesToLineItems(lineItem.childParts, pricingLineItems); - } - - const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0]; - lineItem.laborAmount = pricedLineItem.laborAmount; - lineItem.sellingPrice = pricedLineItem.sellingPrice; - lineItem.kitPrice = pricedLineItem.kitPrice; - lineItem.salesTax = pricedLineItem.salesTax; - }); - - return lineItems; -} - -function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) { - pricedLineItems.forEach((pricedLineItem) => { - const lineItemIndex = taxingLineItems.findIndex( - (taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber - ); - - if (pricedLineItem.childParts) { - addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems); - } - - const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0]; - pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0; - }); - - return pricedLineItems; -} - export function mapTaxedLineItemsToStoreFormat(availableLineItems, storeLineItems) { // clone the lineItems array because what we're passing in is referencing the store directly const lineItems = deepClone(storeLineItems); @@ -3658,6 +3559,127 @@ export function getArrayOfAllLineItemsAndChildParts(lineItems) { return consolidatedLineItemsArray; } +// Private Functions + +function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { + return (array ?? []).map((x) => x[propertyName]).filter((x) => x); +} + +function getTimeSlotsAdditionalEventData( + provisionalTriggers, + zipCode, + firstAvailableAppointmentDateString, + shopAppointmentType +) { + var numberOfDays = null; + if (firstAvailableAppointmentDateString) + numberOfDays = getDateDifferenceInDays( + new Date().toISOString().split("T")[0], + firstAvailableAppointmentDateString + ); + + if (shopAppointmentType) + return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join( + "," + )}`; + else + return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join( + "," + )}`; +} + +function getHasRecalibrationPart(state) { + return containsRecalParts(state.order.lineItems); +} + +function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) { + if (!arrayOfObjects) return null; + + return arrayOfObjects.sort((a, b) => { + if (a[propertyName] < b[propertyName]) return -1; + else if (a[propertyName] > b[propertyName]) return 1; + else return 0; + }); +} + +function convertGlassPieceNamingForApi(glassArray) { + if (!glassArray || glassArray.length === 0) return []; + + // check if array already converted. (likely when a session has been saved previously and then reloaded) + if (glassArray[0].location !== undefined) { + return glassArray; + } + + const converted = []; + glassArray.forEach((glass) => { + converted.push({ + location: glass.glassLocation, + name: glass.glassName, + }); + }); + return converted; +} + +function convertResultsForApi(resultsArray) { + if (!resultsArray) return []; + const converted = []; + resultsArray.forEach((answer) => { + converted.push({ + location: answer.glassLocation, + name: answer.glassName, + result: answer.result, + }); + }); + return converted; +} + +function convertGlassPieceNamingFromApi(glassArray) { + glassArray.forEach((glass) => { + glass.glassLocation = glass.glassPiece.location; + glass.glassName = glass.glassPiece.name; + delete glass.glassPiece; + return glass; + }); + return glassArray; +} + +function addPricesToLineItems(lineItems, pricingLineItems) { + lineItems.forEach((lineItem) => { + const lineItemIndex = pricingLineItems.findIndex( + (pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber + ); + + if (lineItem.childParts) { + addPricesToLineItems(lineItem.childParts, pricingLineItems); + } + + const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0]; + lineItem.laborAmount = pricedLineItem.laborAmount; + lineItem.sellingPrice = pricedLineItem.sellingPrice; + lineItem.kitPrice = pricedLineItem.kitPrice; + lineItem.salesTax = pricedLineItem.salesTax; + }); + + return lineItems; +} + +function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) { + pricedLineItems.forEach((pricedLineItem) => { + const lineItemIndex = taxingLineItems.findIndex( + (taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber + ); + + if (pricedLineItem.childParts) { + addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems); + } + + const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0]; + pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0; + }); + + return pricedLineItems; +} + function getFlattenedArrayOfLineItemsWithChildParts(lineItems, childPartRecursiveCall = false) { let flattenedArray = []; lineItems?.forEach((lineItem) => { @@ -3966,3 +3988,8 @@ function getExternalParameterDefaultState() { function saveExternalParameterState(externalParameterState) { window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState)); } + +const timeSlotCallFlags = { + shop: false, + mobile: false, +}; diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss index 1f17a6cf1..9acab9499 100644 --- a/src/styles/ux-variables.scss +++ b/src/styles/ux-variables.scss @@ -121,8 +121,8 @@ $body-color: $gray-600; //Fonts $font-family-sans-serif: AvertaRegular, Arial, Helvetica, sans-serif; -$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", - monospace; +$font-family-monospace: + SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; // stylelint-enable value-keyword-case $font-family-base: $font-family-sans-serif; $font-family-code: $font-family-monospace; @@ -170,8 +170,7 @@ $spacers: ( /* 16px */ 5: $spacer * 1.5, /* 24px */ 6: $spacer * 2, /* 32px */ 7: $spacer * 2.5, - /* 40px */ 8: $spacer * 3, - /* 48px */ + /* 40px */ 8: $spacer * 3 /* 48px */, ); //Enable negative spacing (does NOT work on padding)