department-of-veterans-affairs/vets-website

View on GitHub
src/applications/financial-status-report/utils/helpers.js

Summary

Maintainability
D
2 days
Test Coverage
import moment from 'moment';
import { addDays, format, isValid } from 'date-fns';
import { toggleValues } from '~/platform/site-wide/feature-toggles/selectors';
import FEATURE_FLAG_NAMES from '~/platform/utilities/feature-toggles/featureFlagNames';
import { deductionCodes } from '../constants/deduction-codes';
import { ignoreFields } from '../constants/ignoreFields';

export const fsrWizardFeatureToggle = state => {
  return toggleValues(state)[
    FEATURE_FLAG_NAMES.showFinancialStatusReportWizard
  ];
};

export const fsrFeatureToggle = state => {
  return toggleValues(state)[FEATURE_FLAG_NAMES.showFinancialStatusReport];
};

export const reviewPageNavigationFeatureToggle = state => {
  return toggleValues(state)[
    FEATURE_FLAG_NAMES.financialStatusReportReviewPageNavigation
  ];
};

export const fsrConfirmationEmailToggle = state =>
  toggleValues(state)[FEATURE_FLAG_NAMES.fsrConfirmationEmail];

export const allEqual = arr => arr.every(val => val === arr[0]);

export const isNumber = value => {
  const pattern = /^\d*$/; // This pattern ensures only whole numbers
  return pattern.test(value);
};

export const dateFormatter = date => {
  if (!date) return undefined;
  const formatDate = date?.slice(0, -3);
  return moment(formatDate, 'YYYY-MM').format('MM/YYYY');
};

export const formatDate = date => {
  return format(new Date(date), 'MMMM d, yyyy');
};

export const endDate = (date, days) => {
  return isValid(new Date(date))
    ? formatDate(addDays(new Date(date), days))
    : '';
};

export const currency = amount => {
  const formatter = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
    minimumFractionDigits: 2,
  });
  const value =
    typeof amount === 'number'
      ? amount
      : parseFloat(amount?.replaceAll(/[^0-9.-]/g, '') ?? 0);
  return formatter.format(value);
};

const hasProperty = (arr, key) => {
  return arr.filter(item => item[key]).length > 0 ?? false;
};

export const sumValues = (arr, key) => {
  const isArrValid = Array.isArray(arr) && arr.length && hasProperty(arr, key);
  if (!isArrValid) return 0;
  return arr.reduce(
    (acc, item) =>
      acc + (item[key] ? Number(item[key]?.replaceAll(/[^0-9.-]/g, '')) : 0),
    0,
  );
};

export const filterReduceByName = (deductions, filters) => {
  if (!deductions?.length) return 0;
  return deductions
    .filter(({ name = '' }) => filters.includes(name))
    .reduce(
      (acc, curr) =>
        acc + Number(curr.amount?.replaceAll(/[^0-9.-]/g, '') ?? 0),
      0,
    );
};

export const otherDeductionsName = (deductions, filters) => {
  if (!deductions.length) return '';
  return deductions
    .filter(({ name = '' }) => !filters.includes(name))
    .map(({ name }) => name)
    .join(', ');
};

export const otherDeductionsAmt = (deductions, filters) => {
  if (!deductions.length) return 0;
  return deductions
    .filter(({ name = '' }) => name && !filters.includes(name))
    .reduce(
      (acc, curr) =>
        acc + Number(curr.amount?.replaceAll(/[^0-9.-]/g, '') ?? 0),
      0,
    );
};

export const nameStr = (socialSecurity, compensation, education, addlInc) => {
  const benefitTypes = [];
  if (socialSecurity) {
    benefitTypes.push('Social Security');
  }
  if (compensation) {
    benefitTypes.push('Disability Compensation');
  }
  if (education) {
    benefitTypes.push('Education');
  }
  const vetAddlNames = addlInc?.map(({ name }) => name) ?? [];
  const otherIncNames = [...benefitTypes, ...vetAddlNames];

  return otherIncNames?.map(item => item).join(', ') ?? '';
};

// safeNumber will return 0 if input is null, undefined, or NaN
export const safeNumber = input => {
  if (!input) return 0;
  const num = Number(input.replaceAll(/[^0-9.-]/g, ''));
  return Number.isNaN(num) ? 0 : num;
};

export const fsrReasonDisplay = resolutionOption => {
  switch (resolutionOption) {
    case 'monthly':
      return 'Extended monthly payments';
    case 'waiver':
      return 'Waiver';
    case 'compromise':
      return 'Compromise';
    default:
      return '';
  }
};

export const getFsrReason = debts => {
  const reasons = debts.map(({ resolutionOption }) =>
    fsrReasonDisplay(resolutionOption),
  );
  const uniqReasons = [...new Set(reasons)];

  return uniqReasons.join(', ');
};

export const getAmountCanBePaidTowardDebt = debts => {
  return debts
    .filter(item => item.resolutionComment !== undefined)
    .reduce(
      (acc, debt) =>
        acc + Number(debt.resolutionComment?.replaceAll(/[^0-9.-]/g, '') ?? 0),
      0,
    );
};

export const mergeAdditionalComments = (additionalComments, expenses) => {
  const individualExpenses = expenses
    ?.map(expense => `${expense.name} (${currency(expense.amount)})`)
    .join(', ');

  const individualExpensesStr = `Individual expense amount: ${individualExpenses}`;

  return individualExpenses
    ? `${
        additionalComments !== undefined ? additionalComments : ''
      }\n${individualExpensesStr}`
    : additionalComments;
};

export const getTotalAssets = ({ assets, questions }) => {
  const totOtherAssets = sumValues(assets.otherAssets, 'amount');
  const totRecVehicles = Number(
    assets?.recVehicleAmount?.replaceAll(/[^0-9.-]/g, '') ?? 0,
  );
  const totVehicles = questions?.hasVehicle
    ? sumValues(assets.automobiles, 'resaleValue')
    : 0;
  const realEstate = Number(
    assets.realEstateValue?.replaceAll(/[^0-9.-]/g, '') ?? 0,
  );

  const totAssets = sumValues(assets.monetaryAssets, 'amount');

  return totVehicles + totRecVehicles + totOtherAssets + realEstate + totAssets;
};

export const getEmploymentHistory = ({ questions, personalData }) => {
  const { employmentHistory } = personalData;
  let history = [];

  const defaultObj = {
    veteranOrSpouse: '',
    occupationName: '',
    from: '',
    to: '',
    present: false,
    employerName: '',
    employerAddress: {
      addresslineOne: '',
      addresslineTwo: '',
      addresslineThree: '',
      city: '',
      stateOrProvince: '',
      zipOrPostalCode: '',
      countryName: '',
    },
  };

  if (questions.vetIsEmployed) {
    const { employmentRecords } = employmentHistory.veteran;
    const vetEmploymentHistory = employmentRecords.map(employment => ({
      ...defaultObj,
      veteranOrSpouse: 'VETERAN',
      occupationName: employment.type,
      from: dateFormatter(employment.from),
      to: employment.isCurrent ? '' : dateFormatter(employment.to),
      present: employment.isCurrent ? employment.isCurrent : false,
      employerName: employment.employerName,
    }));
    history = [...history, ...vetEmploymentHistory];
  }

  if (questions.spouseIsEmployed) {
    const { spEmploymentRecords } = employmentHistory.spouse;
    const spouseEmploymentHistory = spEmploymentRecords.map(employment => ({
      ...defaultObj,
      veteranOrSpouse: 'SPOUSE',
      occupationName: employment.type,
      from: dateFormatter(employment.from),
      to: employment.isCurrent ? '' : dateFormatter(employment.to),
      present: employment.isCurrent ? employment.isCurrent : false,
      employerName: employment.employerName,
    }));
    history = [...history, ...spouseEmploymentHistory];
  }

  return history;
};

// receiving formatted date strings in the response
// so we need to convert back to moment before sorting
export const sortStatementsByDate = statements => {
  const dateFormat = 'MM-DD-YYYY';
  return statements.sort(
    (a, b) =>
      moment(b.pSStatementDate, dateFormat) -
      moment(a.pSStatementDate, dateFormat),
  );
};

// Returns name of debt depending on the type
export const getDebtName = debt => {
  return debt.debtType === 'COPAY'
    ? debt.station.facilityName
    : deductionCodes[debt.deductionCode] || debt.benefitType;
};

export const dateTemplate = 'YYYY-MM-DD';

export const maxDate = moment().add(100, 'year');
export const getDate = date => moment(date, dateTemplate);
export const isDateComplete = date => date?.length === dateTemplate.length;
export const isDateInFuture = date => date?.diff(moment()) > 0;
export const isDateBeyondMax = date => moment(date).isAfter(maxDate);

export const isValidFromDate = date => {
  if (date && isDateComplete(date)) {
    const dateObj = getDate(date);
    return !isDateInFuture(dateObj) && !isDateBeyondMax(dateObj);
  }
  return false;
};

export const isValidToDate = (fromDate, toDate) => {
  if (
    fromDate &&
    toDate &&
    isDateComplete(fromDate) &&
    isDateComplete(toDate)
  ) {
    const fromDateObj = getDate(fromDate);
    const toDateObj = getDate(toDate);

    return (
      !isDateInFuture(toDateObj) &&
      !moment(toDateObj).isBefore(fromDateObj) &&
      !isDateBeyondMax(toDateObj)
    );
  }
  return false;
};

/**
 * Generates a unique key based on the given data fields and an optional index.
 * @example
 * const keyFieldsForCreditCard = ['amountDueMonthly', 'amountPastDue', 'unpaidBalance'];
 * key={generateUniqueKey(bills, keyFieldsForCreditCard, index)}
 * Output: "200-50-1000-2"
 */

export const generateUniqueKey = (data, fields, index = null) => {
  if (data === null || !fields.length) {
    return `default-key-${index}`;
  }
  const keyParts = fields.map(field => data[field] ?? 'undefined');
  if (index !== null) {
    keyParts.push(index);
  }
  return keyParts.join('-');
};

export const firstLetterLowerCase = str => {
  if (!str || str.length === 0) return '';
  // Check if the string is in the ignoreFields array
  if (ignoreFields.includes(str)) {
    return str;
  }
  return str.charAt(0).toLowerCase() + str.slice(1);
};

export const setDocumentTitle = title => {
  document.title = `${title} | FSR (VA Form 5655) | Veterans Affairs`;
};