hisptz/function-analytics

View on GitHub
src/utilities/period-instance.js

Summary

Maintainability
F
1 wk
Test Coverage
import { chunk, head, last, range } from 'lodash';

import { Calendar } from './calendars/calendar';

export class PeriodInstance {
  constructor(calendarId, type, preferences, year) {
    this._calendarId = calendarId || 'gregorian';
    this._type = type;
    this._preferences = preferences;
    this._periods = [];

    this._calendar = new Calendar(calendarId);

    if (!this._calendar) {
      throw new Error('Calendar could not be set');
    }

    this._year = year || this._calendar.getCurrentYear();
    this._month = this._calendar.getCurrentMonth();
    this._quarter = this._calendar.getCurrentQuarter();
    this._biMonth = this._calendar.getCurrentBiMonth();
    this._sixMonth = this._calendar.getCurrentSixMonth();
    this._sixMonthApril = this._calendar.getCurrentSixMonthApril();
    this._sixMonthNovember = this._calendar.getCurrentSixMonthNovember();
    this._monthNames = this._calendar.getMonths();
    this._quarterMonthOffset = this._calendar.getQuarterMonthOffset();
  }

  get() {
    this._periods = this.getPeriods(this._type, this._year);

    if (
      (this._preferences && this._preferences.allowFuturePeriods) ||
      this._type.indexOf('Relative') !== -1
    ) {
      return this._periods.reverse();
    }

    return this.omitFuturePeriods(
      this.includeLastPeriods(this._periods, this._type, this._year),
      this._type
    ).reverse();
  }

  year() {
    return this._year;
  }

  currentYear() {
    return this._calendar.getCurrentYear();
  }

  getPeriods(type, year, offset = 0) {
    let periods;

    switch (type) {
      case 'Monthly': {
        periods = this.getMonthlyPeriods(year, offset);
        break;
      }

      case 'Quarterly': {
        periods = this.getQuarterlyPeriods(year);
        break;
      }

      case 'BiMonthly': {
        periods = this.getBiMonthlyPeriods(year);
        break;
      }

      case 'SixMonthly': {
        periods = this.getSixMonthlyPeriods(year);
        break;
      }

      case 'SixMonthlyApril': {
        periods = this.getSixMonthlyAprilPeriods(year);
        break;
      }

      case 'SixMonthlyNovember': {
        periods = this.getSixMonthlyNovemberPeriods(year);
        break;
      }

      case 'Yearly': {
        periods = this.getYearlyPeriods(year, 'Yearly');
        break;
      }

      case 'FinancialApril': {
        periods = this.getYearlyPeriods(year, 'FinancialApril', 'April', 3);
        break;
      }

      case 'FinancialJuly': {
        periods = this.getYearlyPeriods(year, 'FinancialJuly', 'July', 6);
        break;
      }

      case 'FinancialOctober': {
        periods = this.getYearlyPeriods(year, 'FinancialOctober', 'Oct', 9);
        break;
      }

      case 'FinancialNovember': {
        periods = this.getYearlyPeriods(year, 'FinancialNovember', 'Nov', 10);
        break;
      }

      case 'RelativeMonth':
      case 'RelativeBiMonth':
      case 'RelativeQuarter':
      case 'RelativeSixMonth':
      case 'RelativeYear':
      case 'RelativeFinancialYear':
      case 'RelativeWeek': {
        periods = this.getRelativePeriods(type);
        break;
      }

      default:
        periods = [];
        break;
    }

    return periods;
  }

  includeLastPeriods(periods, type, year) {
    const lastYearPeriods = this.getPeriods(type, year - 1);
    const currentPeriods = periods;

    return (periods || []).map((period, periodIndex) => {
      const lastPeriod =
        currentPeriods[periodIndex - 1] || last(lastYearPeriods);

      const newLastPeriod = {
        id: lastPeriod.id,
        name: lastPeriod.name
      };

      period.lastPeriod = newLastPeriod;

      return period;
    });
  }

  getRelativePeriods(type) {
    switch (type) {
      case 'RelativeBiMonth': {
        return [
          { id: 'THIS_BIMONTH', type, name: 'This Bi-month' },
          {
            id: 'LAST_BIMONTH',
            type,
            name: 'Last Bi-month'
          },
          { id: 'LAST_6_BIMONTHS', type, name: 'Last 6 bi-month' }
        ];
      }

      case 'RelativeMonth': {
        return [
          { id: 'THIS_MONTH', type, name: 'This Month' },
          { id: 'LAST_MONTH', type, name: 'Last Month' },
          {
            id: 'LAST_3_MONTHS',
            type,
            name: 'Last 3 Months'
          },
          { id: 'LAST_6_MONTHS', type, name: 'Last 6 Months' },
          { id: 'LAST_12_MONTHS', type, name: 'Last 12 Months' }
        ];
      }

      case 'RelativeQuarter': {
        return [
          { id: 'THIS_QUARTER', type, name: 'This Quarter' },
          {
            id: 'LAST_QUARTER',
            type,
            name: 'Last Quarter'
          },
          { id: 'LAST_4_QUARTERS', type, name: 'Last 4 Quarters' }
        ];
      }

      case 'RelativeSixMonth': {
        return [
          { id: 'THIS_SIX_MONTH', type, name: 'This Six-month' },
          {
            id: 'LAST_SIX_MONTH',
            type,
            name: 'Last Six-month'
          },
          { id: 'LAST_2_SIXMONTHS', type, name: 'Last 2 Six-month' }
        ];
      }

      case 'RelativeYear': {
        return [
          { id: 'THIS_YEAR', type, name: 'This Year' },
          {
            id: 'LAST_YEAR',
            type,
            name: 'Last Year'
          },
          { id: 'LAST_5_YEARS', type, name: 'Last 5 Years' }
        ];
      }

      case 'RelativeFinancialYear': {
        return [
          { id: 'THIS_FINANCIAL_YEAR', type, name: 'This Financial Year' },
          {
            id: 'LAST_FINANCIAL_YEAR',
            type,
            name: 'Last Financial Year'
          },
          { id: 'LAST_5_FINANCIAL_YEARS', type, name: 'Last 5 Financial Years' }
        ];
      }

      case 'RelativeWeek': {
        return [
          { id: 'THIS_WEEK', type, name: 'This Week' },
          { id: 'LAST_WEEK', type, name: 'Last Week' },
          {
            id: 'LAST_4_WEEKS',
            type,
            name: 'Last 4 Weeks'
          },
          { id: 'LAST_12_WEEKS', type, name: 'last 12 Weeks' },
          { id: 'LAST_52_WEEKS', type, name: 'Last 52 weeks' }
        ];
      }

      default:
        return [];
    }
  }
  getMonthlyPeriods(year, offset = 0) {
    const monthPeriods = (this._monthNames || []).map(
      (monthName, monthIndex) => {
        const monthOffset = monthIndex + 1 - offset;
        const monthYear = monthOffset > 12 ? year - 1 : year;
        const id = this.getMonthPeriodId(monthYear, monthIndex + 1);

        return {
          id,
          type: 'Monthly',
          name: `${monthName} ${monthYear}`,
          daily: this.getChildrenPeriods(
            id,
            'Monthly',
            'Daily',
            this._preferences
          ),
          weekly: this.getChildrenPeriods(
            id,
            'Monthly',
            'Weekly',
            this._preferences
          )
        };
      }
    );

    return this.getMonthsByOffset(monthPeriods, offset);
  }

  getQuarterlyPeriods(year) {
    return chunk(
      this.getMonthsByOffset(
        this.getMonthWithYears(
          this._monthNames,
          year,
          this._quarterMonthOffset
        ),
        this._quarterMonthOffset
      ),
      3
    ).map((quarterMonths, quarterIndex) => {
      const id = this.getQuarterPeriodId(year, quarterIndex + 1);
      const startMonth = head(quarterMonths || []);
      const endMonth = last(quarterMonths || []);

      return {
        id,
        type: 'Quarterly',
        name: this.getPeriodNameByRange(startMonth, endMonth, year),
        daily: this.getChildrenPeriods(
          id,
          'Quarterly',
          'Daily',
          this._preferences
        ),
        weekly: this.getChildrenPeriods(
          id,
          'Quarterly',
          'Weekly',
          this._preferences
        ),
        monthly: this.getChildrenPeriods(
          id,
          'Quarterly',
          'Monthly',
          this._preferences
        )
      };
    });
  }

  getPeriodNameByRange(startMonth, endMonth, year) {
    return `${[startMonth.name + ` ${startMonth.year}`, endMonth.name].join(
      ' - '
    )} ${endMonth.year}`;
  }

  getMonthsByOffset(months, offset) {
    if (offset === 0) {
      return months;
    }

    return [
      ...months.slice(offset),
      ...months.slice(0, months.length + offset)
    ];
  }

  getMonthWithYears(monthNames, year, offset) {
    return (monthNames || []).map((name, index) => {
      const monthOffset = index + 1 - offset;

      return { name, index, year: monthOffset > 12 ? year - 1 : year };
    });
  }

  getBiMonthlyPeriods(year) {
    return (chunk(this._monthNames || [], 2) || []).map(
      (biMonths, biMonthIndex) => {
        const id = this.getBiMonthlyPeriodId(year, biMonthIndex + 1);

        return {
          id,
          type: 'BiMonthly',
          name: `${[head(biMonths || []), last(biMonths || [])].join(
            ' - '
          )} ${year}`,
          daily: this.getChildrenPeriods(
            id,
            'BiMonthly',
            'Daily',
            this._preferences
          ),
          weekly: this.getChildrenPeriods(
            id,
            'BiMonthly',
            'Weekly',
            this._preferences
          ),
          monthly: this.getChildrenPeriods(
            id,
            'BiMonthly',
            'Monthly',
            this._preferences
          )
        };
      }
    );
  }

  getSixMonthlyPeriods(year) {
    return (chunk(this._monthNames || [], 6) || []).map(
      (sixMonths, sixMonthIndex) => {
        const id = this.getSixMonthlyPeriodId(year, sixMonthIndex + 1);

        return {
          id,
          type: 'SixMonthly',
          name: `${[head(sixMonths || []), last(sixMonths || [])].join(
            ' - '
          )} ${year}`,
          daily: this.getChildrenPeriods(
            id,
            'SixMonthly',
            'Daily',
            this._preferences
          ),
          weekly: this.getChildrenPeriods(
            id,
            'SixMonthly',
            'Weekly',
            this._preferences
          ),
          monthly: this.getChildrenPeriods(
            id,
            'SixMonthly',
            'Monthly',
            this._preferences
          )
        };
      }
    );
  }

  getSixMonthlyAprilPeriods(year) {
    const months = this.getMonthWithYears(this._monthNames, year + 1, -9);

    return (
      chunk([...months.slice(3), ...months.slice(0, 3)] || [], 6) || []
    ).map((sixMonthApril, sixMonthAprilIndex) => {
      const id = this.getSixMonthlyPeriodId(
        year,
        sixMonthAprilIndex + 1,
        'April'
      );

      return {
        id,
        type: 'SixMonthlyApril',
        name: this.getPeriodNameByRange(
          head(sixMonthApril || []),
          last(sixMonthApril || []),
          year
        ),
        daily: this.getChildrenPeriods(
          id,
          'SixMonthlyApril',
          'Daily',
          this._preferences
        ),
        weekly: this.getChildrenPeriods(
          id,
          'SixMonthlyApril',
          'Weekly',
          this._preferences
        ),
        monthly: this.getChildrenPeriods(
          id,
          'SixMonthlyApril',
          'Monthly',
          this._preferences
        )
      };
    });
  }

  getSixMonthlyNovemberPeriods(year) {
    return chunk(
      this.getMonthsByOffset(
        this.getMonthWithYears(this._monthNames, year, -2),
        this._quarterMonthOffset
      ),
      6
    ).map((sixMonthNovember, sixMonthNovemberIndex) => {
      const id = this.getSixMonthlyPeriodId(
        year,
        sixMonthNovemberIndex + 1,
        'Nov'
      );

      return {
        id,
        type: 'SixMonthlyNovember',
        name: this.getPeriodNameByRange(
          head(sixMonthNovember || []),
          last(sixMonthNovember || []),
          year
        ),
        daily: this.getChildrenPeriods(
          id,
          'SixMonthlyNovember',
          'Daily',
          this._preferences
        ),
        weekly: this.getChildrenPeriods(
          id,
          'SixMonthlyNovember',
          'Weekly',
          this._preferences
        ),
        monthly: this.getChildrenPeriods(
          id,
          'SixMonthlyNovember',
          'Monthly',
          this._preferences
        )
      };
    });
  }

  getYearlyPeriods(year, type, idSuffix = '', monthIndex = -1) {
    return range(10)
      .map(yearIndex => {
        const periodYear = parseInt(year, 10) - yearIndex;
        const id = this.getYearlyPeriodId(periodYear, idSuffix);
        const name = this.getYearlyPeriodName(periodYear, monthIndex);

        return {
          id,
          type,
          name,
          daily: this.getChildrenPeriods(id, type, 'Daily', this._preferences),
          weekly: this.getChildrenPeriods(
            id,
            type,
            'Weekly',
            this._preferences
          ),
          monthly: this.getChildrenPeriods(
            id,
            type,
            'Monthly',
            this._preferences
          ),
          quarterly: this.getChildrenPeriods(
            id,
            type,
            'Quarterly',
            this._preferences
          )
        };
      })
      .reverse();
  }

  omitFuturePeriods(periods, type) {
    return periods.filter(period => period.id < this.getCurrentPeriodId(type));
  }

  getCurrentPeriodId(type) {
    switch (type) {
      case 'Monthly': {
        return this.getMonthPeriodId(
          this._calendar.getCurrentYear(),
          this._month
        );
      }
      case 'Quarterly': {
        return this.getQuarterPeriodId(
          this._calendar.getCurrentYear(),
          this._quarter
        );
      }

      case 'BiMonthly': {
        return this.getBiMonthlyPeriodId(
          this._calendar.getCurrentYear(),
          this._biMonth
        );
      }

      case 'SixMonthly': {
        return this.getSixMonthlyPeriodId(
          this._calendar.getCurrentYear(),
          this._sixMonth
        );
      }

      case 'SixMonthlyApril': {
        return this.getSixMonthlyPeriodId(
          this._calendar.getCurrentYear(),
          this._sixmonthApril,
          'April'
        );
      }

      case 'SixMonthlyNovember': {
        return this.getSixMonthlyPeriodId(
          this._calendar.getCurrentYear(),
          this._sixMonthNovember,
          'Nov'
        );
      }

      case 'Yearly': {
        return this._calendar.getCurrentYear();
      }

      case 'FinancialApril': {
        const currentYear = this._calendar.getCurrentYear();

        return this.getYearlyPeriodId(
          this._month >= 4 ? currentYear : currentYear - 1,
          'FinancialApril'
        );
      }

      case 'FinancialJuly': {
        const currentYear = this._calendar.getCurrentYear();

        return this.getYearlyPeriodId(
          this._month >= 7 ? currentYear : currentYear - 1,
          'FinancialJuly'
        );
      }

      case 'FinancialOctober': {
        const currentYear = this._calendar.getCurrentYear();

        return this.getYearlyPeriodId(
          this._month >= 10 ? currentYear : currentYear - 1,
          'FinancialOctober'
        );
      }

      case 'FinancialNovember': {
        const currentYear = this._calendar.getCurrentYear();

        return this.getYearlyPeriodId(
          this._month >= 11 ? currentYear : currentYear - 1,
          'FinancialNovember'
        );
      }

      default:
        return undefined;
    }
  }

  getMonthPeriodId(year, monthNumber) {
    return (
      year + (monthNumber < 10 ? `0${monthNumber}` : monthNumber).toString()
    );
  }

  getQuarterPeriodId(year, quarterNumber) {
    return `${year}Q${quarterNumber}`;
  }

  getBiMonthlyPeriodId(year, biMonthNumber) {
    return `${year}0${biMonthNumber}B`;
  }

  getSixMonthlyPeriodId(year, sixMonthNumber, sixMonthType = '') {
    return `${year}${sixMonthType}S${sixMonthNumber}`;
  }

  getYearlyPeriodId(year, suffix = '') {
    return `${year}${suffix}`;
  }

  getYearlyPeriodName(year, monthIndex = -1) {
    if (monthIndex === -1) {
      return year.toString();
    }

    return `${this._monthNames[monthIndex]} ${year} - ${
      this._monthNames[monthIndex - 1]
    } ${year + 1}`;
  }

  getYearlyMonthIndex(type) {}

  getChildrenPeriods(parentId, parentType, childrenType, preferences) {
    let periods = [];

    switch (parentType) {
      case 'Yearly': {
        const year = parseInt(parentId.slice(0, 4), 10);

        if (!isNaN(year)) {
          periods = this.getPeriods(childrenType, year).reverse();
        }
        break;
      }

      // TODO Have a common method to handle children generation for combined periods
      case 'Quarterly': {
        const year = parseInt(parentId.slice(0, 4), 10);

        if (!isNaN(year)) {
          switch (childrenType) {
            case 'Monthly': {
              const quarterNumber = parseInt(parentId.slice(-1), 10);

              const monthPeriods = this.getPeriods(
                childrenType,
                year,
                this._quarterMonthOffset
              );

              periods = (monthPeriods || [])
                .filter(({}, periodIndex) => {
                  const max = quarterNumber * 3;
                  const min = max - 3;

                  return periodIndex >= min && periodIndex < max;
                })
                .reverse();
              break;
            }

            default:
              break;
          }
        }

        break;
      }

      case 'BiMonthly': {
        const year = parseInt(parentId.slice(0, 4), 10);

        if (!isNaN(year)) {
          switch (childrenType) {
            case 'Monthly': {
              const biMonthlyNumber = parseInt(parentId.slice(4), 10);

              const monthPeriods = this.getPeriods(childrenType, year, 0);

              periods = (monthPeriods || [])
                .filter(({}, periodIndex) => {
                  const max = biMonthlyNumber * 2;
                  const min = max - 2;

                  return periodIndex >= min && periodIndex < max;
                })
                .reverse();
              break;
            }

            default:
              break;
          }
        }

        break;
      }

      case 'SixMonthly': {
        const year = parseInt(parentId.slice(0, 4), 10);

        if (!isNaN(year)) {
          switch (childrenType) {
            case 'Monthly': {
              const sixMonthlyNumber = parseInt(parentId.slice(-1), 10);

              const monthPeriods = this.getPeriods(childrenType, year, 0);

              periods = (monthPeriods || [])
                .filter(({}, periodIndex) => {
                  const max = sixMonthlyNumber * 6;
                  const min = max - 6;

                  return periodIndex >= min && periodIndex < max;
                })
                .reverse();
              break;
            }

            default:
              break;
          }
        }

        break;
      }

      default:
        break;
    }

    if (preferences && preferences.childrenPeriodSortOrder === 'ASC') {
      return periods.reverse();
    }

    return periods;
  }
}