diff --git a/src/helpers/cart-helper.js b/src/helpers/cart-helper.js
new file mode 100644
index 00000000..0875c5b6
--- /dev/null
+++ b/src/helpers/cart-helper.js
@@ -0,0 +1,143 @@
+import { useMainStore } from '@/store';
+import { getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculator';
+import partNumberStrings from '@/constants/part-number-strings';
+import partTypeStrings from '@/constants/part-type-strings';
+
+/**
+ * Returns the lineItems object from the order
+ * @param {object} order order object
+ * @returns {object} lineItems object from the order
+ */
+export function getLineItems(order) {
+ return order.lineItems;
+}
+
+/**
+ * Returns an array containing only the service line items
+ * @param {object} order order object
+ * @returns {[]} array of service line items
+ */
+export function getServiceLineItems(order) {
+ const { supportingItems, glassParts, otherParts } = getLineItems(order);
+ return [
+ ...(supportingItems ?? []),
+ ...(glassParts ?? []),
+ ...(otherParts ?? [])
+ ];
+}
+
+/**
+ * Returns an array containing only the non-service line items (Vaps and Fees)
+ * @param {object} order order object
+ * @returns {[]} array of non-service line items
+ */
+export function getNonServiceLineItems(order) {
+ const { vaps, feeItems } = getLineItems(order);
+ return [
+ ...(vaps ?? []),
+ ...(feeItems ?? [])
+ ];
+}
+
+/**
+ * Returns an array containing all line items
+ * @param {object} order order object
+ * @returns {[]} array of all line items
+ */
+export function getAllLineItems(order) {
+ return [
+ ...getServiceLineItems(order),
+ ...getNonServiceLineItems(order)
+ ];
+}
+
+/**
+ * Returns the recycle fee line item if it exists on the order
+ * @param {object} order order object
+ * @returns {{}|undefined} recycle fee line item or undefined
+ */
+export function getRecycleFee(order) {
+ return getLineItems(order).feeItems?.find((lineItem) => lineItem.partNumber === partNumberStrings.RECYCLE_FEE);
+}
+
+/**
+ * Returns the mobile fee line item if it exists on the order
+ * @param {object} order order object
+ * @returns {{}|undefined} mobile fee line item or undefined
+ */
+export function getMobileFee(order) {
+ return getLineItems(order).feeItems?.find((lineItem) => lineItem.partType === partTypeStrings.MOBILE_FEE);
+}
+
+/**
+ * Returns the current deductible on the order
+ * @param {object} order order object
+ * @returns {number} current deductible
+ */
+export function getDeductible(order) {
+ return order.currentDeductible ?? 0;
+}
+
+/**
+ * Returns if the order is unverified
+ * @param {object} order order object
+ * @returns {boolean} order unverified state
+ */
+export function isOrderUnverified(order) {
+ return order.isUnverified ?? useMainStore().isUnverified;
+}
+
+/**
+ * Returns if the order is NO COMP
+ * @param {object} order order object
+ * @returns {boolean} order NO COMP flag
+ */
+export function isOrderNoComp(order) {
+ return order.policy?.noCoverage ?? false;
+}
+
+/**
+ * Returns if the order is ITAC
+ * @param {object} order order object
+ * @returns {boolean} order ITAC flag
+ */
+export function isOrderITAC(order) {
+ return order.policy?.isITAC ?? false;
+}
+
+/**
+ * Returns the subtotal for the order
+ * @param {object} order order object
+ * @returns {number} sub total for the order
+ */
+export function getSubtotal(order) {
+ if (!isOrderNoComp(order) && !isOrderITAC(order)) {
+ return getDeductible(order) + getPriceOfLineItems(getNonServiceLineItems(order), false);
+ }
+
+ return getPriceOfLineItems(getAllLineItems(order), false);
+}
+
+/**
+ * Returns the sales tax for the order
+ * @param {object} order order object
+ * @returns {number} sales tax for the order
+ */
+export function getSalesTax(order) {
+ let tax = 0;
+ if (isOrderITAC(order) || isOrderNoComp(order)) {
+ tax += getTaxOfLineItems(getServiceLineItems(order));
+ }
+
+ tax += getTaxOfLineItems(getNonServiceLineItems(order));
+ return tax;
+}
+
+/**
+ * Returns the cart total rounded to two decimal places
+ * @param {object} order order object
+ * @returns {number} cart total for the order
+ */
+export function getCartTotal(order) {
+ return Math.round((getSubtotal(order) + getSalesTax(order)) * 100) / 100;
+}
diff --git a/src/helpers/cart-helper.spec.js b/src/helpers/cart-helper.spec.js
new file mode 100644
index 00000000..b978f5ec
--- /dev/null
+++ b/src/helpers/cart-helper.spec.js
@@ -0,0 +1,630 @@
+import {
+ getAllLineItems,
+ getCartTotal,
+ getDeductible,
+ getLineItems,
+ getMobileFee, getNonServiceLineItems,
+ getRecycleFee, getSalesTax,
+ getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp,
+ isOrderUnverified
+} from '@/helpers/cart-helper';
+import partTypeStrings from '@/constants/part-type-strings';
+import partNumberStrings from '@/constants/part-number-strings';
+
+describe('cart-helper', () => {
+ function createDummyItem(partNumber, price, salesTax) {
+ return {
+ partNumber,
+ sellingPrice: price,
+ kitPrice: 0,
+ laborAmount: 0,
+ salesTax
+ };
+ }
+
+ function createDummyFeeItem(partNumber, partType, price, salesTax) {
+ return {
+ partNumber,
+ partType,
+ sellingPrice: price,
+ kitPrice: 0,
+ laborAmount: 0,
+ salesTax
+ };
+ }
+
+ const nullLineItems = {
+ supportingItems: null,
+ glassParts: null,
+ otherParts: null,
+ vaps: null,
+ feeItems: null
+ };
+
+ const emptyLineItems = {
+ supportingItems: [],
+ glassParts: [],
+ otherParts: [],
+ vaps: [],
+ feeItems: []
+ };
+
+ const supportingLineItem = createDummyItem('supporting', 30, 1);
+ const glassLineItem = createDummyItem('glass', 40, 2);
+ const otherLineItem = createDummyItem('other', 50, 3);
+ const vapsLineItem = createDummyItem('vaps', 60, 4);
+ const mobileFeeLineItem = createDummyFeeItem(null, partTypeStrings.MOBILE_FEE, 70, 5);
+ const recycleFeeLineItem = createDummyFeeItem(partNumberStrings.RECYCLE_FEE, null, 80, 6);
+
+ const defaultLineItems = {
+ supportingItems: [supportingLineItem],
+ glassParts: [glassLineItem],
+ otherParts: [otherLineItem],
+ vaps: [vapsLineItem],
+ feeItems: [mobileFeeLineItem, recycleFeeLineItem]
+ };
+
+ describe('getLineItems', () => {
+ test('Returns line items', () => {
+ // Arrange
+ const order = {
+ lineItems: emptyLineItems
+ };
+
+ // Act
+ const result = getLineItems(order);
+
+ // Assert
+ expect(result).toMatchObject(order.lineItems);
+ });
+ });
+
+ describe('getServiceLineItems', () => {
+ test('Returns only service line items', () => {
+ // Arrange
+ const order = {
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getServiceLineItems(order);
+
+ // Assert
+ expect(result).toHaveLength(3);
+ expect(result).toContain(supportingLineItem);
+ expect(result).toContain(glassLineItem);
+ expect(result).toContain(otherLineItem);
+ });
+
+ test('Returns empty array when line items are null', () => {
+ // Arrange
+ const order = {
+ lineItems: nullLineItems
+ };
+
+ // Act
+ const result = getServiceLineItems(order);
+
+ // Assert
+ expect(result).toHaveLength(0);
+ });
+ });
+
+ describe('getNonServiceLineItems', () => {
+ test('Returns only service line items', () => {
+ // Arrange
+ const order = {
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getNonServiceLineItems(order);
+
+ // Assert
+ expect(result).toHaveLength(3);
+ expect(result).toContain(vapsLineItem);
+ expect(result).toContain(mobileFeeLineItem);
+ expect(result).toContain(recycleFeeLineItem);
+ });
+
+ test('Returns empty array when line items are null', () => {
+ // Arrange
+ const order = {
+ lineItems: nullLineItems
+ };
+
+ // Act
+ const result = getNonServiceLineItems(order);
+
+ // Assert
+ expect(result).toHaveLength(0);
+ });
+ });
+
+ describe('getAllLineItems', () => {
+ test('Returns all line items', () => {
+ // Arrange
+ const order = {
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getAllLineItems(order);
+
+ // Assert
+ expect(result).toHaveLength(6);
+ expect(result).toContain(supportingLineItem);
+ expect(result).toContain(glassLineItem);
+ expect(result).toContain(otherLineItem);
+ expect(result).toContain(vapsLineItem);
+ expect(result).toContain(mobileFeeLineItem);
+ expect(result).toContain(recycleFeeLineItem);
+ });
+
+ test('Returns empty array when line items are null', () => {
+ // Arrange
+ const order = {
+ lineItems: nullLineItems
+ };
+
+ // Act
+ const result = getAllLineItems(order);
+
+ // Assert
+ expect(result).toHaveLength(0);
+ });
+ });
+
+ describe('getRecycleFee', () => {
+ test('Returns recycle fee when present', () => {
+ // Arrange
+ const order = {
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getRecycleFee(order);
+
+ // Assert
+ expect(result).not.toBeNull();
+ expect(result).toMatchObject(recycleFeeLineItem);
+ });
+
+ test('Returns undefined recycle fee when not present', () => {
+ // Arrange
+ const order = {
+ lineItems: emptyLineItems
+ };
+
+ // Act
+ const result = getRecycleFee(order);
+
+ // Assert
+ expect(result).toBeUndefined();
+ });
+ });
+
+ describe('getMobileFee', () => {
+ test('Returns mobile fee when present', () => {
+ // Arrange
+ const order = {
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getMobileFee(order);
+
+ // Assert
+ expect(result).not.toBeNull();
+ expect(result).toMatchObject(mobileFeeLineItem);
+ });
+
+ test('Returns undefined mobile fee when not present', () => {
+ // Arrange
+ const order = {
+ lineItems: emptyLineItems
+ };
+
+ // Act
+ const result = getMobileFee(order);
+
+ // Assert
+ expect(result).toBeUndefined();
+ });
+ });
+
+ describe('getDeductible', () => {
+ test('Returns deductible on the order', () => {
+ // Arrange
+ const deductible = 100;
+ const order = {
+ currentDeductible: deductible
+ };
+
+ // Act
+ const result = getDeductible(order);
+
+ // Assert
+ expect(result).not.toBeNull();
+ expect(result).toBe(deductible);
+ });
+ });
+
+ describe('isOrderUnverified', () => {
+ test('Returns true if order has isUnverified field and is true', () => {
+ // Arrange
+ const order = {
+ isUnverified: true
+ };
+
+ // Act
+ const result = isOrderUnverified(order);
+
+ // Assert
+ expect(result).toBeTruthy();
+ });
+
+ test('Returns false if order has isUnverified field and is false', () => {
+ // Arrange
+ const order = {
+ isUnverified: true
+ };
+
+ // Act
+ const result = isOrderUnverified(order);
+
+ // Assert
+ expect(result).toBeTruthy();
+ });
+ });
+
+ describe('isOrderNoComp', () => {
+ test('Returns null when policy is null', () => {
+ // Arrange
+ const order = {};
+
+ // Act
+ const result = isOrderNoComp(order);
+
+ // Assert
+ expect(result).toBeFalsy();
+ });
+
+ test('Returns true when policy.NoCoverage is true', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true
+ }
+ };
+
+ // Act
+ const result = isOrderNoComp(order);
+
+ // Assert
+ expect(result).toBeTruthy();
+ });
+
+ test('Returns false when policy.NoCoverage is false', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: false
+ }
+ };
+
+ // Act
+ const result = isOrderNoComp(order);
+
+ // Assert
+ expect(result).toBeFalsy();
+ });
+ });
+
+ describe('isOrderITAC', () => {
+ test('Returns null when policy is null', () => {
+ // Arrange
+ const order = {};
+
+ // Act
+ const result = isOrderITAC(order);
+
+ // Assert
+ expect(result).toBeFalsy();
+ });
+
+ test('Returns true when policy.isITAC is true', () => {
+ // Arrange
+ const order = {
+ policy: {
+ isITAC: true
+ }
+ };
+
+ // Act
+ const result = isOrderITAC(order);
+
+ // Assert
+ expect(result).toBeTruthy();
+ });
+
+ test('Returns false when policy.isITAC is false', () => {
+ // Arrange
+ const order = {
+ policy: {
+ isITAC: false
+ }
+ };
+
+ // Act
+ const result = isOrderITAC(order);
+
+ // Assert
+ expect(result).toBeFalsy();
+ });
+ });
+
+ describe('getSubtotal', () => {
+ test('Returns 0 when there are null items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true
+ },
+ lineItems: nullLineItems
+ };
+
+ // Act
+ const result = getSubtotal(order);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns 0 when there are no items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true
+ },
+ lineItems: emptyLineItems
+ };
+
+ // Act
+ const result = getSubtotal(order);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns sub total of deductible + vaps + fee line items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: false,
+ isITAC: false
+ },
+ currentDeductible: 100,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getSubtotal(order);
+
+ // Assert
+ expect(result).toBe(310);
+ });
+
+ test('Returns sub total of all line items when no comp', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true,
+ isITAC: false
+ },
+ currentDeductible: 0,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getSubtotal(order);
+
+ // Assert
+ expect(result).toBe(330);
+ });
+
+ test('Returns sub total of all line items when ITAC', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: false,
+ isITAC: true
+ },
+ currentDeductible: 0,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getSubtotal(order);
+
+ // Assert
+ expect(result).toBe(330);
+ });
+ });
+
+ describe('getSalesTax', () => {
+ test('Returns 0 when there are null items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true
+ },
+ lineItems: nullLineItems
+ };
+
+ // Act
+ const result = getSalesTax(order);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns 0 when there are no items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true
+ },
+ lineItems: emptyLineItems
+ };
+
+ // Act
+ const result = getSalesTax(order);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns sales tax vaps + fee line items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: false,
+ isITAC: false
+ },
+ currentDeductible: 100,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getSalesTax(order);
+
+ // Assert
+ expect(result).toBe(15);
+ });
+
+ test('Returns sales tax of all line items when no comp', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true,
+ isITAC: false
+ },
+ currentDeductible: 0,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getSalesTax(order);
+
+ // Assert
+ expect(result).toBe(21);
+ });
+
+ test('Returns sales tax of all line items when ITAC', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: false,
+ isITAC: true
+ },
+ currentDeductible: 0,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getSalesTax(order);
+
+ // Assert
+ expect(result).toBe(21);
+ });
+ });
+
+ describe('getCartTotal', () => {
+ test('Returns 0 when there are null items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true
+ },
+ lineItems: nullLineItems
+ };
+
+ // Act
+ const result = getCartTotal(order);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns 0 when there are no items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true
+ },
+ lineItems: emptyLineItems
+ };
+
+ // Act
+ const result = getCartTotal(order);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns total of vaps + fee line items', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: false,
+ isITAC: false
+ },
+ currentDeductible: 100,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getCartTotal(order);
+
+ // Assert
+ expect(result).toBe(325);
+ });
+
+ test('Returns total of all line items when no comp', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: true,
+ isITAC: false
+ },
+ currentDeductible: 0,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getCartTotal(order);
+
+ // Assert
+ expect(result).toBe(351);
+ });
+
+ test('Returns total of all line items when ITAC', () => {
+ // Arrange
+ const order = {
+ policy: {
+ noCoverage: false,
+ isITAC: true
+ },
+ currentDeductible: 0,
+ lineItems: defaultLineItems
+ };
+
+ // Act
+ const result = getCartTotal(order);
+
+ // Assert
+ expect(result).toBe(351);
+ });
+ });
+});
diff --git a/src/helpers/price-calculator.js b/src/helpers/price-calculator.js
index b44abc5a..72634df7 100644
--- a/src/helpers/price-calculator.js
+++ b/src/helpers/price-calculator.js
@@ -1,12 +1,36 @@
-function getPriceOfLineItem(lineItem) {
- return (lineItem?.kitPrice ?? 0)
- + (lineItem?.laborAmount ?? 0)
- + (lineItem?.sellingPrice ?? 0);
+/**
+ * Returns the price of a single line item. Will include sales tax if includeTax is true
+ * @param {object} lineItem line item to price
+ * @param {boolean} includeTax if sales tax should be included
+ * @returns {number} price of the line item
+ */
+export function getPriceOfLineItem(lineItem, includeTax = false) {
+ let price = (lineItem.kitPrice ?? 0) + (lineItem.laborAmount ?? 0) + (lineItem.sellingPrice ?? 0);
+ if (includeTax) {
+ price += lineItem.salesTax ?? 0;
+ }
+ if (lineItem.childParts && lineItem.childParts.length !== 0) {
+ // eslint-disable-next-line no-use-before-define
+ price += getPriceOfLineItems(lineItem.childParts, includeTax);
+ }
+ return price;
}
-export default function getPriceOfLineItems(lineItems) {
- return lineItems.reduce(
- (accumulator, lineItem) => accumulator + getPriceOfLineItem(lineItem),
- 0
- );
+/**
+ * Returns the price for the given array of line items. Will include sales tax if includeTax is true
+ * @param {Array} lineItems array of line items to be priced
+ * @param {boolean} includeTax if sales tax should be included
+ * @returns {number} price of the lines items
+ */
+export function getPriceOfLineItems(lineItems, includeTax = false) {
+ return lineItems?.reduce((accumulator, lineItem) => accumulator + getPriceOfLineItem(lineItem, includeTax), 0) ?? 0;
+}
+
+/**
+ * Returns the sales tax for the given array of line items
+ * @param {Array} lineItems array of line items to get the sales tax for
+ * @returns {number} sales tax of the line items
+ */
+export function getTaxOfLineItems(lineItems) {
+ return lineItems?.reduce((accumulator, lineItem) => accumulator + (lineItem.salesTax ?? 0), 0) ?? 0;
}
diff --git a/src/helpers/price-calculator.spec.js b/src/helpers/price-calculator.spec.js
index 5ef5669d..b415d0ab 100644
--- a/src/helpers/price-calculator.spec.js
+++ b/src/helpers/price-calculator.spec.js
@@ -1,56 +1,226 @@
-import getPriceOfLineItems from '@/helpers/price-calculator.js';
+import {
+ getPriceOfLineItem,
+ getPriceOfLineItems,
+ getTaxOfLineItems
+} from '@/helpers/price-calculator.js';
-describe('getPriceOfLineItems', () => {
- test('Returns zero when no line items', () => {
- // Arrange
- const lineItems = [];
+describe('price-calculator', () => {
+ describe('getPriceOfLineItem', () => {
+ test('Empty lineItem returns 0', () => {
+ // Arrange
+ const lineItem = {};
- // Act
- const result = getPriceOfLineItems(lineItems);
+ // Act
+ const result = getPriceOfLineItem(lineItem, false);
- // Assert
- expect(result).toBe(0);
- });
- test('Returns expected when one line item', () => {
- // Arrange
- const lineItems = [
- {
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Empty lineItem with salesTax returns 0', () => {
+ // Arrange
+ const lineItem = {};
+
+ // Act
+ const result = getPriceOfLineItem(lineItem, true);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns kit, labor, sellingPrice total when salesTax is false', () => {
+ // Arrange
+ const lineItem = {
kitPrice: 1,
laborAmount: 2,
- sellingPrice: 3
- }
- ];
- const expected = 6;
+ sellingPrice: 3,
+ salesTax: 4
+ };
- // Act
- const result = getPriceOfLineItems(lineItems);
+ // Act
+ const result = getPriceOfLineItem(lineItem, false);
- // Assert
- expect(result).toBe(expected);
- });
- test('Returns expected when multiple line items', () => {
- // Arrange
- const lineItems = [
- {
+ // Assert
+ expect(result).toBe(6);
+ });
+
+ test('Returns kit, labor, sellingPrice, salesTax total when salesTax is true', () => {
+ // Arrange
+ const lineItem = {
kitPrice: 1,
laborAmount: 2,
- sellingPrice: 3
- },
- {
- kitPrice: 1
- },
- {
- kitPrice: 10,
- laborAmount: 100,
- sellingPrice: 1000
- }
- ];
- const expected = 1117;
+ sellingPrice: 3,
+ salesTax: 4
+ };
- // Act
- const result = getPriceOfLineItems(lineItems);
+ // Act
+ const result = getPriceOfLineItem(lineItem, true);
- // Assert
- expect(result).toBe(expected);
+ // Assert
+ expect(result).toBe(10);
+ });
+ });
+
+ describe('getPriceOfLineItems', () => {
+ test('Returns zero when null', () => {
+ // Arrange
+ const lineItems = null;
+
+ // Act
+ const result = getPriceOfLineItems(lineItems, true);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+
+ test('Returns zero when no line items', () => {
+ // Arrange
+ const lineItems = [];
+
+ // Act
+ const result = getPriceOfLineItems(lineItems, true);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+ test('Returns expected when one line item', () => {
+ // Arrange
+ const lineItems = [
+ {
+ kitPrice: 1,
+ laborAmount: 2,
+ sellingPrice: 3
+ }
+ ];
+ const expected = 6;
+
+ // Act
+ const result = getPriceOfLineItems(lineItems, false);
+
+ // Assert
+ expect(result).toBe(expected);
+ });
+ test('Returns expected when multiple line items', () => {
+ // Arrange
+ const lineItems = [
+ {
+ kitPrice: 1,
+ laborAmount: 2,
+ sellingPrice: 3
+ },
+ {
+ kitPrice: 1
+ },
+ {
+ kitPrice: 10,
+ laborAmount: 100,
+ sellingPrice: 1000
+ }
+ ];
+ const expected = 1117;
+
+ // Act
+ const result = getPriceOfLineItems(lineItems, false);
+
+ // Assert
+ expect(result).toBe(expected);
+ });
+ test('Returns expected when multiple line items with sales tax', () => {
+ // Arrange
+ const lineItems = [
+ {
+ kitPrice: 1,
+ laborAmount: 2,
+ sellingPrice: 3,
+ salesTax: 4
+ },
+ {
+ kitPrice: 1,
+ salesTax: 2
+ },
+ {
+ kitPrice: 10,
+ laborAmount: 100,
+ sellingPrice: 1000,
+ salesTax: 50
+ }
+ ];
+ const expected = 1173;
+
+ // Act
+ const result = getPriceOfLineItems(lineItems, true);
+
+ // Assert
+ expect(result).toBe(expected);
+ });
+ });
+
+ describe('getTaxOfLineItems', () => {
+ test('Returns zero when null line items', () => {
+ // Arrange
+ const lineItems = null;
+
+ // Act
+ const result = getTaxOfLineItems(lineItems);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+ test('Returns zero when no line items', () => {
+ // Arrange
+ const lineItems = [];
+
+ // Act
+ const result = getTaxOfLineItems(lineItems);
+
+ // Assert
+ expect(result).toBe(0);
+ });
+ test('Returns expected when one line item', () => {
+ // Arrange
+ const lineItems = [
+ {
+ kitPrice: 1,
+ laborAmount: 2,
+ sellingPrice: 3,
+ salesTax: 4
+ }
+ ];
+ const expected = 4;
+
+ // Act
+ const result = getTaxOfLineItems(lineItems);
+
+ // Assert
+ expect(result).toBe(expected);
+ });
+ test('Returns expected when multiple line items', () => {
+ // Arrange
+ const lineItems = [
+ {
+ kitPrice: 1,
+ laborAmount: 2,
+ sellingPrice: 3,
+ salesTax: 4
+ },
+ {
+ kitPrice: 1,
+ salesTax: 2
+ },
+ {
+ kitPrice: 10,
+ laborAmount: 100,
+ sellingPrice: 1000,
+ salesTax: 50
+ }
+ ];
+ const expected = 56;
+
+ // Act
+ const result = getTaxOfLineItems(lineItems);
+
+ // Assert
+ expect(result).toBe(expected);
+ });
});
});
diff --git a/src/iss-components/cart-dropdown/cart-dropdown.spec.js b/src/iss-components/cart-dropdown/cart-dropdown.spec.js
index 8fdc9898..1427ea02 100644
--- a/src/iss-components/cart-dropdown/cart-dropdown.spec.js
+++ b/src/iss-components/cart-dropdown/cart-dropdown.spec.js
@@ -5,19 +5,22 @@ import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
// Supporting Files
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore, getDefaultState } from '@/store';
-import coverageStatuses from '@/constants/coverage-statuses';
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import partTypeStrings from '@/constants/part-type-strings';
import partNumberStrings from '@/constants/part-number-strings';
import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/service-package-helper.js';
-import getPriceOfLineItems from '@/helpers/price-calculator.js';
+import { getPriceOfLineItems } from '@/helpers/price-calculator.js';
const VERIFYING_COVERAGE = 'Verifying coverage';
jest.mock('@/helpers/text-helper', () => ({
formatAmountInDollars: jest.fn()
}));
-jest.mock('@/helpers/price-calculator.js', () => jest.fn());
+jest.mock('@/helpers/price-calculator.js', () => ({
+ getPriceOfLineItems: jest.fn(),
+ getTaxOfLineItems: jest.fn(),
+ getPriceOfLineItem: jest.fn()
+}));
jest.mock('@/helpers/service-package-helper', () => ({
getHighestFullySatisfiedTier: jest.fn(),
@@ -201,7 +204,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
lineItems: {
- supportingItems: [
+ feeItems: [
{ partNumber: partNumberStrings.RECYCLE_FEE }
]
}
@@ -384,399 +387,6 @@ describe('cart-dropdown component', () => {
// TODO when subTotal and salesTax are finished
});
});
- describe('subTotal', () => {
- const supportingItemPartNumber = 'supporter';
- const glassPartNumber = 'glass part';
- const otherPartNumber = 'other';
- const vapPartNumber = 'vap';
- const mobileFeePartNumber = 'mobile';
- const supportingItems = [{ partNumber: supportingItemPartNumber }];
- const glassParts = [{ partNumber: glassPartNumber }];
- const otherParts = [{ partNumber: otherPartNumber }];
- const vaps = [{ partNumber: vapPartNumber }];
- const mobileFee = { partNumber: mobileFeePartNumber };
- const includesPartNumber = (lineItems, partNumber) => lineItems.some((item) => item?.partNumber === partNumber);
- const deductiblePrice = 100;
- const supportingAndGlassAndOtherPrice = 30;
- const feesAndVapsPrice = 50;
- const allLineItemsPrice = 70;
- getPriceOfLineItems.mockImplementation((lineItems) => {
- const allPartNumbers = [
- supportingItemPartNumber,
- glassPartNumber,
- otherPartNumber,
- mobileFeePartNumber,
- vapPartNumber
- ];
- if (allPartNumbers.every((partNumber) => includesPartNumber(lineItems, partNumber))) {
- return allLineItemsPrice;
- }
- const supportingAndGlassAndOtherPartNumbers = [
- supportingItemPartNumber,
- glassPartNumber,
- otherPartNumber
- ];
- if (supportingAndGlassAndOtherPartNumbers.every((partNumber) => includesPartNumber(lineItems, partNumber))) {
- return supportingAndGlassAndOtherPrice;
- }
- if (includesPartNumber(lineItems, mobileFeePartNumber) && includesPartNumber(lineItems, vapPartNumber)) {
- return feesAndVapsPrice;
- }
- return 1;
- });
- const lineItems = {
- supportingItems,
- glassParts,
- otherParts,
- vaps,
- mobileFee
- };
- test('when not no comp and not itac, includes deductible price plus fee and vap price', () => {
- // Arrange
- const storeData = {
- order: {
- lineItems,
- policy: {
- noCoverage: false,
- isITAC: false
- },
- currentDeductible: deductiblePrice
- }
- };
- const { wrapper } = getMountedComponent(storeData);
- const expected = deductiblePrice + feesAndVapsPrice;
-
- // Act
- const result = wrapper.vm.subTotal;
-
- // Assert
- expect(result).toBe(expected);
- });
- test('when no comp, returns all line items price', () => {
- // Arrange
- const storeData = {
- order: {
- lineItems,
- policy: {
- noCoverage: true,
- isITAC: false
- },
- currentDeductible: deductiblePrice
- }
- };
- const { wrapper } = getMountedComponent(storeData);
- const expected = allLineItemsPrice;
-
- // Act
- const result = wrapper.vm.subTotal;
-
- // Assert
- expect(result).toBe(expected);
- });
- test('when ITAC, returns all line items price', () => {
- // Arrange
- const storeData = {
- order: {
- lineItems,
- policy: {
- noCoverage: false,
- isITAC: true
- },
- currentDeductible: deductiblePrice
- }
- };
- const { wrapper } = getMountedComponent(storeData);
- const expected = allLineItemsPrice;
-
- // Act
- const result = wrapper.vm.subTotal;
-
- // Assert
- expect(result).toBe(expected);
- });
- });
- describe('salesTax', () => {
- test('salesTax is treated as 0 when line items are null', () => {
- // Arrange
- const storeData = {
- order: {
- policy: {
- policyLookupSuccessful: true
- },
- currentDeductible: 250,
- lineItems: {
- glassParts: null,
- otherParts: null,
- supportingItems: null,
- vaps: null
- },
- payment: {
- insuranceCoverage: { isVerified: true }
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true
- }
- };
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(0);
- });
-
- test("salesTax is treated as 0 when null or undefined is set as the line item's sales tax.", () => {
- // Arrange
- const storeData = {
- order: {
- policy: {
- policyLookupSuccessful: true
- },
- currentDeductible: 250,
- lineItems: {
- glassParts: [
- { partType: 'mock', salesTax: null },
- { partType: 'mock', salesTax: undefined }
- ],
- supportingItems: [
- { partType: 'mock', salesTax: null },
- { partType: 'mock', salesTax: undefined }
- ],
- vaps: [
- { partType: 'mock', salesTax: null },
- { partType: 'mock', salesTax: undefined }
- ]
- },
- payment: {
- insuranceCoverage: { isVerified: true }
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true
- }
- };
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(0);
- });
- test('Returns 0 as sales tax when coverage is unverified and order does not contain vaps.', () => {
- // Arrange
- const storeData = {
- order: {
- policy: {
- policyLookupSuccessful: true
- },
- currentDeductible: 250,
- lineItems: {
- glassParts: [{ partType: 'mock', salesTax: 10 }],
- supportingItems: [
- {
- partNumber: partNumberStrings.RECYCLE_FEE,
- partType: 'mock',
- salesTax: 10,
- sellingPrice: 39.99
- }
- ],
- vaps: []
- },
- payment: {
- insuranceCoverage: { isVerified: false }
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true
- }
- };
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(0);
- });
-
- test('Returns sum of vaps sales tax when coverage is unverified and order has vaps.', () => {
- // Arrange
- const storeData = {
- order: {
- policy: {
- policyLookupSuccessful: true
- },
- currentDeductible: 250,
- lineItems: {
- glassParts: [{ partType: 'mock', salesTax: 10 }],
- vaps: [
- { partType: 'mock', salesTax: 1 },
- { partType: 'mock', salesTax: 2 }
- ]
- },
- payment: {
- insuranceCoverage: { isVerified: false }
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true
- }
- };
-
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(3);
- });
-
- test('Returns Recycle Fee tax when coverage is Verified-Deductible, replace service, and no vaps.', () => {
- // Arrange
- const storeData = {
- order: {
- policy: {
- policyLookupSuccessful: true,
- noCoverage: false,
- isITAC: false
- },
- currentDeductible: 250,
- lineItems: {
- glassParts: [{ partType: 'mock', salesTax: 10, sellingPrice: 150 }],
- supportingItems: [
- {
- partNumber: partNumberStrings.RECYCLE_FEE,
- partType: 'mock',
- salesTax: 10,
- sellingPrice: 39.99
- }
- ],
- vaps: null
- },
- payment: {
- insuranceCoverage: { isVerified: true }
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true
- }
- };
-
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(10);
- });
-
- test('Returns sum of vaps + recycle fee sales tax when Verified-Deductible, replace service, and has vaps.', () => {
- // Arrange
- const storeData = {
- order: {
- policy: {
- policyLookupSuccessful: true
- },
- currentDeductible: 250,
- lineItems: {
- glassParts: [{ partType: 'mock', salesTax: 10, sellingPrice: 100 }],
- otherParts: [{ partType: 'mock', salesTax: 10, kitPrice: 100 }],
- supportingItems: [
- { partNumber: partNumberStrings.RECYCLE_FEE, partType: 'mock', salesTax: 10, sellingPrice: 39.99 },
- { partType: 'mock', salesTax: 10, kitPrice: 100 }
- ],
- vaps: [
- { partType: 'mock', salesTax: 2 },
- { partType: 'mock', salesTax: 3 }
- ]
- },
- payment: {
- insuranceCoverage: { isVerified: true }
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true
- }
- };
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(15);
- });
-
- test('Returns sum of sales tax when Verified-ITAC.', () => {
- // Arrange
- const storeData = {
- order: {
- currentDeductible: 0,
- lineItems: {
- glassParts: [{ partType: 'mock', salesTax: 10, sellingPrice: 100 }],
- otherParts: [{ partType: 'mock', salesTax: 10, kitPrice: 100 }],
- supportingItems: [{ partType: 'mock', salesTax: 10, kitPrice: 100 }],
- vaps: [{ partType: 'mock', salesTax: 5 }]
- },
- payment: {
- insuranceCoverage: { isVerified: true }
- },
- policy: {
- isITAC: true,
- policyLookupSuccessful: true
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true
- }
- };
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(35);
- });
-
- test('Returns sum of sales tax when Verified-NoComp.', () => {
- // Arrange
- const storeData = {
- order: {
- currentDeductible: 0,
- lineItems: {
- glassParts: [{ partType: 'mock', salesTax: 10, sellingPrice: 100 }],
- otherParts: [{ partType: 'mock', salesTax: 10, kitPrice: 100 }],
- supportingItems: [{ partType: 'mock', salesTax: 10, kitPrice: 100 }],
- vaps: [{ partType: 'mock', salesTax: 5 }]
- },
- payment: {
- insuranceCoverage: { isVerified: true }
- },
- policy: {
- noCoverage: true,
- policyLookupSuccessful: true
- }
- },
- issConfig: {
- isClaimRegistrationRequired: true,
- enableNoCompQuote: true
- }
- };
- const { wrapper } = getMountedComponent(storeData);
-
- // Act
- const result = wrapper.vm.salesTax;
-
- // Assert
- expect(result).toBe(35);
- });
- });
describe('availableLineItems', () => {
test('returns expected when all null', () => {
// Arrange
@@ -848,7 +458,9 @@ describe('cart-dropdown component', () => {
const mobileFee = { partNumber: 'mobile' };
const storeData = {
order: {
- lineItems: { mobileFee }
+ lineItems: {
+ feeItems: [mobileFee]
+ }
}
};
const propsData = { availableVaps: null };
@@ -888,7 +500,7 @@ describe('cart-dropdown component', () => {
glassParts: [part1],
supportingItems: [part2, part3],
otherParts: [],
- mobileFee
+ feeItems: [mobileFee]
}
}
};
@@ -1147,7 +759,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
lineItems: {
- supportingItems: [
+ feeItems: [
{
partNumber: partNumberStrings.RECYCLE_FEE
}
@@ -1170,9 +782,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
lineItems: {
- mobileFee: {
- partType: partTypeStrings.MOBILE_FEE
- }
+ feeItems: [{ partType: partTypeStrings.MOBILE_FEE }]
}
}
};
@@ -1368,14 +978,10 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
lineItems: {
- supportingItems: [
- {
- partNumber: partNumberStrings.RECYCLE_FEE
- }
+ feeItems: [
+ { partType: partTypeStrings.MOBILE_FEE },
+ { partNumber: partNumberStrings.RECYCLE_FEE }
],
- mobileFee: {
- partType: partTypeStrings.MOBILE_FEE
- },
vaps: [
{ partType: partTypeStrings.RECALIBRATION },
{ partType: partTypeStrings.FRONT_WIPER },
@@ -1533,7 +1139,7 @@ describe('cart-dropdown component', () => {
// Assert
expect(result).toStrictEqual(expected);
});
- test('returns expected when recycle fee in supporting items', () => {
+ test('returns expected when recycle fee in fee items', () => {
// Arrange
const mountOptions = getMountOptions({
router: { navigate: jest.fn() }
@@ -1549,7 +1155,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
lineItems: {
- supportingItems: [{ partNumber: partNumberStrings.RECYCLE_FEE }]
+ feeItems: [{ partNumber: partNumberStrings.RECYCLE_FEE }]
}
}
};
@@ -1587,7 +1193,7 @@ describe('cart-dropdown component', () => {
// Assert
expect(result).toStrictEqual(expected);
});
- test('returns expected when mobile fee in supporting items', () => {
+ test('returns expected when mobile fee in fee items', () => {
// Arrange
const mountOptions = getMountOptions({
router: { navigate: jest.fn() }
@@ -1603,7 +1209,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
lineItems: {
- mobileFee: { partType: partTypeStrings.MOBILE_FEE }
+ feeItems: [{ partType: partTypeStrings.MOBILE_FEE }]
}
}
};
diff --git a/src/iss-components/cart-dropdown/cart-dropdown.vue b/src/iss-components/cart-dropdown/cart-dropdown.vue
index 5aba7f8d..a6c6e726 100644
--- a/src/iss-components/cart-dropdown/cart-dropdown.vue
+++ b/src/iss-components/cart-dropdown/cart-dropdown.vue
@@ -40,7 +40,7 @@
id="cart-base-price"
class="d-flex justify-content-between align-items-center">
{{ basePriceLabel }}
- {{ getDisplayed(baseServicePrice) }}
+ {{ getDisplayed(servicePrice) }}
@@ -159,15 +159,22 @@ import { useMainStore } from '@/store';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
-import getPriceOfLineItems from '@/helpers/price-calculator.js';
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/service-package-helper.js';
// Constants
import partTypeStrings from '@/constants/part-type-strings.js';
import cartItemType from '@/constants/cart-item-type.js';
-import partNumberStrings from '@/constants/part-number-strings.js';
import widgetFields from '@/constants/cms-widget-fields.js';
+import {
+ getCartTotal,
+ getDeductible,
+ getLineItems, getMobileFee,
+ getRecycleFee, getSalesTax,
+ getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp,
+ isOrderUnverified
+} from '@/helpers/cart-helper';
+import {getPriceOfLineItems, getTaxOfLineItems} from '@/helpers/price-calculator';
const VERIFYING_COVERAGE = 'Verifying coverage';
@@ -206,106 +213,58 @@ export default {
};
},
computed: {
+ order() {
+ return this.submittedOrder ?? useMainStore().order;
+ },
deductible() {
- // Note: added as computed so it can be used in the template.
- return this.submittedOrder ? this.submittedOrder.currentDeductible : useMainStore().order.currentDeductible;
+ return getDeductible(this.order);
},
showDeductibleCartItem() {
- const { isUnverified } = useMainStore();
- return isUnverified || (!this.isNoComp && !this.isITAC);
+ return this.isUnverified || (!this.isNoComp && !this.isITAC);
},
lineItems() {
- return this.submittedOrder ? this.submittedOrder.lineItems : useMainStore().lineItems;
+ return getLineItems(this.order);
},
recycleFeeLineItem() {
- return this.lineItems.supportingItems
- ?.find((lineItem) => lineItem.partNumber === partNumberStrings.RECYCLE_FEE);
+ return getRecycleFee(this.order);
},
- baseServiceLineItems() {
- const { supportingItems, glassParts, otherParts } = this.lineItems;
- const parts = [
- ...(supportingItems ?? []),
- ...(glassParts ?? []),
- ...(otherParts ?? [])
- ];
- return parts.filter((part) => part !== this.recycleFeeLineItem);
+ servicePrice() {
+ return getPriceOfLineItems(getServiceLineItems(this.order), false) ?? 0;
},
- baseServicePrice() {
- return getPriceOfLineItems(this.baseServiceLineItems) ?? 0;
+ isUnverified() {
+ return isOrderUnverified(this.order);
},
isITAC() {
- return this.submittedOrder ? this.submittedOrder.policy.isITAC : useMainStore().isITAC;
+ return isOrderITAC(this.order);
},
isNoComp() {
- return this.submittedOrder ? this.submittedOrder.policy.noCoverage : useMainStore().isNoComp;
+ return isOrderNoComp(this.order);
},
// TODO fix rounding
subTotal() {
- const { supportingItems, glassParts, otherParts, vaps, mobileFee } = this.lineItems;
- const allLineItems = [
- ...(supportingItems ?? []),
- ...(glassParts ?? []),
- ...(otherParts ?? []),
- ...(vaps ?? []),
- mobileFee
- ];
- return !this.isNoComp && !this.isITAC
- ? this.deductible + getPriceOfLineItems([...this.feeLineItems, ...(vaps ?? [])])
- : getPriceOfLineItems(allLineItems);
- },
- feeLineItems() {
- const { mobileFee } = this.lineItems;
- const result = [];
- if (mobileFee) {
- result.push(mobileFee);
- }
- if (this.recycleFeeLineItem) {
- result.push(this.recycleFeeLineItem);
- }
- return result;
+ return getSubtotal(this.order);
},
salesTax() {
- const { isUnverified } = useMainStore();
- function sumTax(lineItems) {
- return lineItems?.reduce((accumulator, lineItem) => accumulator + (lineItem.salesTax ?? 0), 0) ?? 0;
- }
-
- let result = 0;
-
- if (!isUnverified) {
- if (this.isITAC || this.isNoComp) {
- result += sumTax(this.baseServiceLineItems);
- } else {
- // deductible-case need to show tax for Recycle Fee
- result += this.recycleFeeLineItem?.salesTax ?? 0;
- }
- }
-
- result += sumTax(this.lineItems.vaps ?? []);
-
- return result;
+ return getSalesTax(this.order);
+ },
+ total() {
+ return getCartTotal(this.order);
},
amountDue() {
- return this.showAsPaid
- ? 0
- : this.subTotal + this.salesTax;
+ return this.showAsPaid ? 0 : this.total;
},
amountPaid() {
- return !this.showAsPaid
- ? 0
- : this.subTotal + this.salesTax;
+ return !this.showAsPaid ? 0 : this.total;
},
availableLineItems() {
- const { supportingItems, glassParts, otherParts, mobileFee } = this.lineItems;
+ const { supportingItems, glassParts, otherParts, feeItems } = this.lineItems;
const result = [
...(supportingItems ?? []),
...(glassParts ?? []),
...(otherParts ?? []),
- ...(this.availableVaps ?? [])
+ ...(this.availableVaps ?? []),
+ ...(feeItems ?? [])
];
- if (mobileFee) {
- result.push(mobileFee);
- }
return result;
},
vehicleDamage() {
@@ -428,7 +387,7 @@ export default {
: null;
},
mobileFeeCartItem() {
- const mobileFeeLineItem = this.lineItems.mobileFee;
+ const mobileFeeLineItem = getMobileFee(this.order);
return mobileFeeLineItem
? this.getCartItem(
this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
@@ -447,8 +406,7 @@ export default {
this.isExpanded = !this.isExpanded;
},
getDisplayed(amount) {
- const { isUnverified } = useMainStore();
- return isUnverified
+ return this.isUnverified
? VERIFYING_COVERAGE
: formatAmountInDollars(amount);
},
@@ -460,11 +418,8 @@ export default {
name: label,
cartItemType: cartItemTypeString,
partType,
- subTotal: getPriceOfLineItems(lineItems),
- salesTax: lineItems?.reduce(
- (accumulator, lineItem) => accumulator + lineItem.salesTax,
- 0
- ) ?? 0
+ subTotal: getPriceOfLineItems(lineItems, false),
+ salesTax: getTaxOfLineItems(lineItems)
};
}
diff --git a/src/layouts/coverage-statement/coverage-statement.spec.js b/src/layouts/coverage-statement/coverage-statement.spec.js
index e47df660..e90a3804 100644
--- a/src/layouts/coverage-statement/coverage-statement.spec.js
+++ b/src/layouts/coverage-statement/coverage-statement.spec.js
@@ -11,10 +11,12 @@ import { getRandomString, getRandomInt } from '@/helpers/data-generation.js';
import settleAllPromises from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { useMainStore, getDefaultState } from '@/store';
-import getPriceOfLineItems from '@/helpers/price-calculator.js';
+import { getPriceOfLineItems } from '@/helpers/price-calculator.js';
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
-jest.mock('@/helpers/price-calculator.js', () => jest.fn());
+jest.mock('@/helpers/price-calculator.js', () => ({
+ getPriceOfLineItems: jest.fn()
+}));
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(),
diff --git a/src/layouts/coverage-statement/coverage-statement.vue b/src/layouts/coverage-statement/coverage-statement.vue
index d618aecf..0bc42c12 100644
--- a/src/layouts/coverage-statement/coverage-statement.vue
+++ b/src/layouts/coverage-statement/coverage-statement.vue
@@ -123,9 +123,9 @@ import issPageValues from '@/router/router-constants/issPage-values';
import bailoutMessage from '@/constants/bailoutMessage';
import coverageStatuses from '@/constants/coverage-statuses';
import widgetFields from '@/constants/cms-widget-fields.js';
-import getPriceOfLineItems from '@/helpers/price-calculator.js';
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import showIssLoadingModal from '@/helpers/loading-modal-helper';
+import { getPriceOfLineItems } from '@/helpers/price-calculator';
const SAFELITE_PROVIDER = 'Safelite';
@@ -344,7 +344,7 @@ export default {
);
},
totalServicePrice() {
- return getPriceOfLineItems(this.baseServiceLineItems);
+ return getPriceOfLineItems(this.baseServiceLineItems, false);
},
serviceProviderQuestionText() {
return this.getCmsContent(
diff --git a/src/layouts/payment-page/payment-page.spec.js b/src/layouts/payment-page/payment-page.spec.js
index f12f5461..b2bdc2e4 100644
--- a/src/layouts/payment-page/payment-page.spec.js
+++ b/src/layouts/payment-page/payment-page.spec.js
@@ -107,13 +107,6 @@ const taxedParts = {
// Setup global mocks
let mockCmsContent = {};
-jest.mock('@/mixins/base-mixin.js', () => ({
- methods: {
- getAmountDue: jest.fn().mockImplementation(() => 5),
- getDisplayAmountDue: jest.fn().mockImplementation(() => '$5.00')
- }
-}));
-
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: () => Promise.resolve('content')
}));
diff --git a/src/layouts/payment-page/payment-page.vue b/src/layouts/payment-page/payment-page.vue
index 726bc1f9..34ccc2a1 100644
--- a/src/layouts/payment-page/payment-page.vue
+++ b/src/layouts/payment-page/payment-page.vue
@@ -396,8 +396,8 @@ import issPageValues from '@/router/router-constants/issPage-values.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
-import baseMixin from '@/mixins/base-mixin.js';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
+import { getCartTotal } from '@/helpers/cart-helper';
import { useMainStore } from '@/store';
import iframeResize from 'iframe-resizer/js/iframeResizer.js';
import externalUrls from '@/router/router-constants/externalUrl-values.js';
@@ -410,6 +410,7 @@ import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
import queryStrings from '@/constants/query-strings';
import alert from '@/ux-components/alert/alert.vue';
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
+import { formatAmountInDollars } from '@/helpers/text-helper';
export default {
name: 'payment-page',
@@ -715,10 +716,10 @@ export default {
this.payInAdvanceLineItems = lineItems.join('||');
},
getAmountDue() {
- return baseMixin.methods.getAmountDue(useMainStore().lineItems);
+ return getCartTotal(useMainStore().order);
},
getDisplayAmountDue() {
- return baseMixin.methods.getDisplayAmountDue(useMainStore().lineItems);
+ return formatAmountInDollars(this.getAmountDue());
},
fetchSignatureInfo(signatureInfo) {
this.authToken = signatureInfo.token;
diff --git a/src/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue
index 35226055..c26e739b 100644
--- a/src/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue
+++ b/src/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue
@@ -47,6 +47,8 @@ import buttonQuestion from '@/digital-components/button-question/button-question
// Helpers
import { deepClone } from '@/helpers/object-helper';
+import { formatAmountInDollars } from '@/helpers/text-helper';
+import { getPriceOfLineItem } from '@/helpers/price-calculator';
// Validation
import { defineRule, useField } from 'vee-validate';
@@ -421,7 +423,7 @@ export default {
},
getPremiumAppointmentTimeSlot(timeSlotData) {
const formattedPrice =
- `+$${this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2)}`;
+ `+${formatAmountInDollars(getPriceOfLineItem(this.premiumAppointmentFee, false))}`;
return {
// Unique value is required for each and the premium appoinment shares an id
diff --git a/src/layouts/service-packages/service-package-question/service-package-question.vue b/src/layouts/service-packages/service-package-question/service-package-question.vue
index 9c87d796..5092d581 100644
--- a/src/layouts/service-packages/service-package-question/service-package-question.vue
+++ b/src/layouts/service-packages/service-package-question/service-package-question.vue
@@ -18,6 +18,7 @@ import servicePackageRadio from '@/layouts/service-packages/service-package-ques
import partTypeStrings from '@/constants/part-type-strings';
import { useMainStore } from '@/store';
import allGlassPartsAndItemsHavePrices from '@/layouts/service-packages/service-package-helper/service-package-helper';
+import { getPriceOfLineItem } from '@/helpers/price-calculator';
const glassLocations = damageLocationsSelected;
@@ -186,7 +187,7 @@ export default {
&& item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER)
|| (priceRearWipers && item.partType.toUpperCase() === partTypeStrings.REAR_WIPER)
) {
- vapsPrice += this.getTotalLineItemPrice(item);
+ vapsPrice += getPriceOfLineItem(item, false);
}
});
return vapsPrice;
@@ -205,7 +206,7 @@ export default {
|| (priceRainDefense
&& item.partType.toUpperCase() === partTypeStrings.RAIN_DEFENSE)
) {
- vapsPrice += this.getTotalLineItemPrice(item);
+ vapsPrice += getPriceOfLineItem(item, false);
}
});
return vapsPrice;
@@ -309,9 +310,6 @@ export default {
const glassLocationMatches =
store.order.damage.glassToReplace?.filter((glassToReplace) => glassToReplace.glassLocation === glassLocation) ?? [];
return !!glassLocationMatches.length;
- },
- getTotalLineItemPrice(lineItem) {
- return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
}
}
};
diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js
index f4a3028d..ac36cbf0 100644
--- a/src/mixins/base-mixin.js
+++ b/src/mixins/base-mixin.js
@@ -36,62 +36,6 @@ export default {
savePageDataToStore(page, data) {
useMainStore().updatePageData({ page, data });
},
- getTotalPriceOfAllLineItemsAndChildParts(lineItems, includeTax) {
- let totalPrice = 0;
- lineItems.forEach((lineItem) => {
- totalPrice += this.getTotalLineItemPrice(lineItem, includeTax);
- if (lineItem.childParts) {
- totalPrice += this.getTotalPriceOfAllLineItemsAndChildParts(
- lineItem.childParts,
- includeTax
- );
- }
- });
- return totalPrice;
- },
- getTotalLineItemPrice(lineItem, includeTax) {
- if (includeTax) {
- return (
- lineItem.kitPrice
- + lineItem.laborAmount
- + lineItem.sellingPrice
- + lineItem.salesTax
- );
- }
- return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
- },
- getDisplayAmountDue(lineItems) {
- return this.getAmountDue(lineItems).toLocaleString('en-US', {
- style: 'currency',
- currency: 'USD'
- });
- },
- getAmountDue(lineItems) {
- if (!useMainStore().isNoComp && !useMainStore().isITAC) {
- return useMainStore().order.currentDeductible;
- }
-
- let amountDue = 0;
- if (lineItems.glassParts) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
- lineItems.glassParts,
- false
- );
- }
- if (lineItems.supportingItems) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
- lineItems.supportingItems,
- false
- );
- }
- if (lineItems.vaps) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(lineItems.vaps, false);
- }
- if (lineItems.promos) {
- amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(lineItems.promos, false);
- }
- return ((amountDue * 100) / 100).toFixed(2);
- },
scrollToPageTop() {
const container = document.getElementsByClassName('fade-on-route-transition')[0];
container.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
diff --git a/src/router/index.js b/src/router/index.js
index 96a905b9..25a72e96 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -170,16 +170,18 @@ router.afterEach(async (to, from) => {
store.clearSaveSessionPromise();
}
- const saveSessionSynchronous = !!from.params[routerParams.SAVE_SESSION_SYNCHRONOUS];
- await saveSession({ shouldAwaitSaveSessionQueue: saveSessionSynchronous }).catch((error) => {
- if (from.name === issPageValues.WELCOME_PAGE) {
- store.setBailout(bailoutMessage.saveSessionError(error.data));
- router.navigate(
- navigationScenarios.SAVE_SESSION_FAILED,
- { query: { issPage: issPageValues.WELCOME_PAGE } }
- );
- }
- });
+ if (to.query.issPage !== issPageValues.ORDER_CONFIRMATION) {
+ const saveSessionSynchronous = !!from.params[routerParams.SAVE_SESSION_SYNCHRONOUS];
+ await saveSession({ shouldAwaitSaveSessionQueue: saveSessionSynchronous }).catch((error) => {
+ if (from.name === issPageValues.WELCOME_PAGE) {
+ store.setBailout(bailoutMessage.saveSessionError(error.data));
+ router.navigate(
+ navigationScenarios.SAVE_SESSION_FAILED,
+ { query: { issPage: issPageValues.WELCOME_PAGE } }
+ );
+ }
+ });
+ }
if (to.query.issPage !== issPageValues.ENTRY_PAGE) {
// Push page view to GA
diff --git a/src/store/index.js b/src/store/index.js
index 391e4f85..98f0a690 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -24,6 +24,7 @@ import {
import partTypeStrings from '@/constants/part-type-strings';
import bailoutMessage from '@/constants/bailoutMessage';
import bailoutCode from '@/constants/bailoutCode';
+import partNumberStrings from "@/constants/part-number-strings";
const storeId = 'main';
@@ -149,8 +150,9 @@ export const getDefaultState = () => ({
glassParts: null,
otherParts: null,
supportingItems: null,
+ feeItems: [],
vaps: null,
- mobileFee: null
+ serverData: null
},
payment: {
insuranceCoverage: {
@@ -273,6 +275,19 @@ export const useMainStore = defineStore({
policy: (state) => state.order.policy,
hasExactlyOneChip: () => state.order.damage.numberOfChips === 1,
isPolicyVehicle: () => state.order.vehicle.policyVehicleId != null,
+ hasWindshieldReplacement: (s) => {
+ const { damage } = s.order;
+ if (damage.isRepair) {
+ return false;
+ }
+
+ const { glassParts } = s.order.lineItems;
+ if (!glassParts || glassParts.length === 0) {
+ return false;
+ }
+
+ return glassParts.findIndex((gp) => gp.partType === 'WINDSHIELD') !== -1;
+ },
hasAnyNonWindshieldGlassParts: (s) => !s.order.policy.isDamageGlassOnly,
isMobileAppointment: (state) => state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|| state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP,
@@ -848,8 +863,9 @@ export const useMainStore = defineStore({
let lineItems = [
...(order.lineItems.supportingItems ?? []),
...(order.lineItems.vaps ?? []),
- ...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts)
+ ...(order.lineItems.glassParts)
];
+ lineItems = getLineItemsFlattened(lineItems);
lineItems = lineItems.map((lineItem) => ({
partNumber: lineItem.partNumber,
partType: lineItem.partType
@@ -909,8 +925,9 @@ export const useMainStore = defineStore({
let lineItems = [
...(order.lineItems.supportingItems ?? []),
...(order.lineItems.vaps ?? []),
- ...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts)
+ ...(order.lineItems.glassParts)
];
+ lineItems = getLineItemsFlattened(lineItems);
lineItems = lineItems.map((lineItem) => ({
partNumber: lineItem.partNumber,
partType: lineItem.partType
@@ -1044,16 +1061,17 @@ export const useMainStore = defineStore({
this.order.serviceLocation.zipCodeCtu = zipInfo.data.zipCodeCtu;
}
const ctuToUse = this.order.serviceLocation.zipCodeCtu;
- const availableLineItemsFormattedForRequest = getLineItemQueryStringForPricing(availableLineItems);
const deductibleToUse = this.order.currentDeductible ?? 0;
+ const lineItemsQueryString = getLineItemQueryString(availableLineItems, 'lineItems');
+
let queryString =
`ParentAccountNumber=${this.order.accountNumber}`
+ `&BillToAccountNumber=${this.billToNumberToUse}`
+ `&CTU=${ctuToUse}`
+ `&Deductible=${deductibleToUse}`
+ `&ZipCode=${zipCodeToUse}`
- + `${availableLineItemsFormattedForRequest}`;
+ + `&${lineItemsQueryString}`;
const lineItemServerData = this.order.lineItems?.serverData;
if (lineItemServerData) {
@@ -1069,8 +1087,13 @@ export const useMainStore = defineStore({
throw error;
});
- if (response.data?.lineItems) {
- const retAvailableLineItems = addPricesToLineItems(availableLineItems, response.data.lineItems);
+ const { lineItems, serverData } = response.data;
+ if (serverData) {
+ this.order.lineItems.serverData = serverData;
+ }
+
+ if (lineItems) {
+ const retAvailableLineItems = addPricesToLineItems(availableLineItems, lineItems);
return retAvailableLineItems;
}
return availableLineItems;
@@ -1086,10 +1109,7 @@ export const useMainStore = defineStore({
},
getServiceabilityDetails({ serviceZipCode }) {
- const lineItemsWithOnlyPartNumbers = this.order.lineItems.supportingItems.map((lineItem) => ({
- partNumber: lineItem.partNumber
- }));
- const lineItems = buildQueryStringParameterFromArrayOfComplexObjects(lineItemsWithOnlyPartNumbers, 'lineItems');
+ const lineItems = getLineItemQueryString(this.order.lineItems.supportingItems, 'lineItems');
const { vehicle } = this.order;
const { carId } = vehicle;
@@ -1209,6 +1229,10 @@ export const useMainStore = defineStore({
return Promise.resolve(null);
}
+ if (this.hasSubmittedOrder()) {
+ return Promise.resolve(null);
+ }
+
const loadedFromDupeCheck = !!(this.order.loadedFromDupeCheck && !this.order.loadedSessionClearedPreviousData);
const newGlassToReplace = convertGlassPieceNamingForApi(damage.glassToReplace);
@@ -1593,20 +1617,50 @@ export const useMainStore = defineStore({
return;
}
- let parts = partsData;
- if (!this.isNoComp && !this.isITAC) {
- parts = parts.filter((i) => i.partNumber !== partTypeStrings.RECYCLE_FEE);
- }
+ // Process Recycle Fee
+ this.updateRecycleFee(partsData.find((rf) => rf.partNumber === partNumberStrings.RECYCLE_FEE));
- this.order.lineItems.supportingItems = parts;
+ // Remove RecycleFee from supported items as it's saved in feeItems;
+ this.order.lineItems.supportingItems = partsData.filter((i) => i.partNumber !== partNumberStrings.RECYCLE_FEE);
},
updateVaps(partsData) {
this.order.lineItems.vaps = partsData;
},
- updateMobileFee(mobileFee) {
- this.order.lineItems.mobileFee = mobileFee;
+ updateMobileFee(fee) {
+ // Mobile Fee is only added for NO COMP or ITAC
+ if (fee && (this.isNoComp || this.isITAC)) {
+ this.addPartTypeFeeItem(fee, partTypeStrings.MOBILE_FEE);
+ } else {
+ this.addPartTypeFeeItem(null, partTypeStrings.MOBILE_FEE);
+ }
+ },
+
+ updateRecycleFee(fee) {
+ // Recycle Fee is only added for NO COMP or ITAC and has Windshield Replacement
+ if (fee && (this.isNoComp || this.isITAC) && this.hasWindshieldReplacement) {
+ this.addPartNumberFeeItem(fee, partNumberStrings.RECYCLE_FEE);
+ } else {
+ this.addPartNumberFeeItem(null, partNumberStrings.RECYCLE_FEE);
+ }
+ },
+
+ addPartNumberFeeItem(feeItem, partNumber) {
+ this.addFeeItem(feeItem, (fi) => fi.partNumber !== partNumber);
+ },
+
+ addPartTypeFeeItem(feeItem, partType) {
+ this.addFeeItem(feeItem, (fi) => fi.partType !== partType);
+ },
+
+ addFeeItem(feeItem, filter) {
+ let { feeItems } = this.order.lineItems;
+ // Remove all fee items with the given filter
+ feeItems = feeItems.filter(filter);
+ if (feeItem) {
+ feeItems.push(feeItem);
+ }
},
async setPriceAndSalesTaxForOrderLineItems() {
@@ -1614,16 +1668,13 @@ export const useMainStore = defineStore({
...(this.order.lineItems.supportingItems ?? []),
...(this.order.lineItems.glassParts ?? []),
...(this.order.lineItems.otherParts ?? []),
- ...(this.order.lineItems.vaps ?? [])
+ ...(this.order.lineItems.vaps ?? []),
+ ...(this.order.lineItems.feeItems ?? []
+ )
];
- if (this.order.lineItems.mobileFee != null) {
- lineItemsToTax.push(this.order.lineItems.mobileFee);
- }
-
- const pricedLineItemsToTax = await this.getPriceOrderItems(lineItemsToTax);
-
- this.taxOrderItemsAndSaveServerData(pricedLineItemsToTax);
+ await this.getPriceOrderItems(lineItemsToTax);
+ await this.getTaxOrderItems(lineItemsToTax);
},
updateVehicle(vehicle) {
@@ -1731,6 +1782,8 @@ export const useMainStore = defineStore({
resetGlassPartsState() {
this.order.lineItems.glassParts = null;
this.order.lineItems.supportingItems = null;
+ this.order.lineItems.feeItems = [];
+ this.order.lineItems.serverData = null;
this.order.damage.partQuestionAnswers = null;
this.order.damage.moldingQuestionAnswers = null;
this.order.damage.capabilityQuestionAnswers = null;
@@ -1960,7 +2013,7 @@ export const useMainStore = defineStore({
},
// Tax order actions
- async taxOrderItemsAndSaveServerData(pricedLineItems) {
+ async getTaxOrderItems(pricedLineItems) {
const { order } = this;
const { serviceLocation } = order;
const { providerNumber } = serviceLocation.provider;
@@ -1969,39 +2022,27 @@ export const useMainStore = defineStore({
const serviceLocationState = serviceLocation.state;
const serviceLocationZipCode = serviceLocation.zipCode;
- const flattenedLineItemsWithChildParts = getFlattenedArrayOfLineItemsWithChildParts(pricedLineItems);
-
- const lineItemsWithOnlyPriceInfo = flattenedLineItemsWithChildParts.map((lineItem) => ({
- partNumber: lineItem.partNumber,
- laborAmount: lineItem.laborAmount ?? 0,
- kitPrice: lineItem.kitPrice ?? 0,
- sellingPrice: lineItem.sellingPrice ?? 0
- }));
-
- const pricedLineItemsFormattedForRequest = buildQueryStringParameterFromArrayOfComplexObjects(
- lineItemsWithOnlyPriceInfo,
- 'lineItems'
- );
+ const lineItemsQueryString = getLineItemQueryString(pricedLineItems, 'lineItems');
let queryString = '';
if (appointmentType === AppointmentTypeStrings.MOBILE
|| appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
queryString =
- `ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}`
+ `ParentAccountNumber=${this.order.accountNumber}`
+ `&BillToAccountNumber=${this.billToNumberToUse}`
+ `&ProviderNumber=${providerNumber}`
+ `&AppointmentType=${appointmentType}`
+ `&ServiceLocation.City=${serviceLocationCity}`
+ `&ServiceLocation.State=${serviceLocationState}`
+ `&ServiceLocation.ZipCode=${serviceLocationZipCode}`
- + `&${pricedLineItemsFormattedForRequest}`;
+ + `&${lineItemsQueryString}`;
} else {
queryString =
- `ParentAccountNumber=${applicationConfig.CASH_PARENT_ACCOUNT_NUMBER}`
+ `ParentAccountNumber=${this.order.accountNumber}`
+ `&BillToAccountNumber=${this.billToNumberToUse}`
+ `&ProviderNumber=${providerNumber}`
+ `&AppointmentType=${appointmentType}`
- + `&${pricedLineItemsFormattedForRequest}`;
+ + `&${lineItemsQueryString}`;
}
const lineItemServerData = this.order.lineItems.serverData;
@@ -2370,7 +2411,6 @@ export const useMainStore = defineStore({
resetPartsAndDependencies() {
this.resetGlassPartsState();
this.updateVaps(null);
- this.updateMobileFee(null);
this.resetServiceLocationAndDependencies();
},
@@ -2412,6 +2452,8 @@ export const useMainStore = defineStore({
const { experiments } = this.applicationUser;
const { issConfig } = this;
+ submittedOrder.isUnverified = this.isUnverified;
+
// set to local storage
window.sessionStorage.setItem(webStorageConstants.SUBMITTED_ORDER, JSON.stringify(submittedOrder));
@@ -2543,29 +2585,13 @@ function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) {
return pricedLineItems;
}
-function getLineItemQueryStringForPricing(lineItems) {
- return lineItems.map((lineItem, index) => {
- let queryStringSnippet = `&LineItems[${index}].partNumber=${lineItem.partNumber}`;
- if (lineItem.childParts) {
- queryStringSnippet += getLineItemQueryStringForPricing(lineItem.childParts);
- }
-
- return queryStringSnippet;
- }).join('');
+function getLineItemQueryString(lineItems, parameterName) {
+ return getLineItemsFlattened(lineItems).map((lineItem, index) =>
+ `${parameterName}[${index}].partNumber=${lineItem.partNumber}`).join('&');
}
-function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {
- let flattenedArray = [];
- lineItems?.forEach((lineItem) => {
- flattenedArray.push(lineItem);
- if (lineItem.childParts) {
- flattenedArray = [
- ...flattenedArray,
- ...getFlattenedArrayOfLineItemsWithChildParts(lineItem.childParts)
- ];
- }
- });
- return flattenedArray;
+function getLineItemsFlattened(lineItems) {
+ return lineItems?.flatMap((li) => [li, ...(getLineItemsFlattened(li.childParts) ?? [])]) ?? [];
}
function buildQueryStringParameterFromArrayOfComplexObjects(arrayOfObjects, parameterName) {