import * as R from 'ramda'; import { isEmpty, } from 'lodash'; import moment from 'moment'; import { ICashFlowStatementSection, ICashFlowStatementSectionType, IDateRange, ICashFlowStatementDOO, } from './Cashflow.types'; import { ITableRow, ITableColumn } from '../../types/Table.types'; import { dateRangeFromToCollection } from '@/utils/date-range-collection'; import { tableRowMapper } from '../../utils/Table.utils'; import { mapValuesDeep } from '@/utils/deepdash'; enum IROW_TYPE { AGGREGATE = 'AGGREGATE', NET_INCOME = 'NET_INCOME', ACCOUNTS = 'ACCOUNTS', ACCOUNT = 'ACCOUNT', TOTAL = 'TOTAL', } const DEEP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; const DISPLAY_COLUMNS_BY = { DATE_PERIODS: 'date_periods', TOTAL: 'total', }; export class CashFlowTable { private report: ICashFlowStatementDOO; private i18n; private dateRangeSet: IDateRange[]; /** * Constructor method. * @param {ICashFlowStatement} reportStatement */ constructor(reportStatement: ICashFlowStatementDOO, i18n) { this.report = reportStatement; this.i18n = i18n; this.dateRangeSet = []; this.initDateRangeCollection(); } /** * Initialize date range set. */ private initDateRangeCollection() { this.dateRangeSet = dateRangeFromToCollection( this.report.query.fromDate, this.report.query.toDate, this.report.query.displayColumnsBy, ); } /** * Retrieve the date periods columns accessors. */ private datePeriodsColumnsAccessors = () => { return this.dateRangeSet.map((dateRange: IDateRange, index) => ({ key: `date-range-${index}`, accessor: `periods[${index}].total.formattedAmount`, })); }; /** * Retrieve the total column accessor. */ private totalColumnAccessor = () => { return [{ key: 'total', accessor: 'total.formattedAmount' }]; }; /** * Retrieve the common columns for all report nodes. */ private commonColumns = () => { return R.compose( R.concat([{ key: 'name', accessor: 'label' }]), R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), R.concat(this.datePeriodsColumnsAccessors()), ), R.concat(this.totalColumnAccessor()), )([]); }; /** * Retrieve the table rows of regular section. * @param {ICashFlowStatementSection} section * @returns {ITableRow[]} */ private regularSectionMapper = ( section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); return tableRowMapper(section, columns, { rowTypes: [IROW_TYPE.AGGREGATE], id: section.id, }); }; /** * Retrieve the net income table rows of the section. * @param {ICashFlowStatementSection} section * @returns {ITableRow} */ private netIncomeSectionMapper = ( section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); return tableRowMapper(section, columns, { rowTypes: [IROW_TYPE.NET_INCOME, IROW_TYPE.TOTAL], id: section.id, }); }; /** * Retrieve the accounts table rows of the section. * @param {ICashFlowStatementSection} section * @returns {ITableRow} */ private accountsSectionMapper = ( section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); return tableRowMapper(section, columns, { rowTypes: [IROW_TYPE.ACCOUNTS], id: section.id, }); }; /** * Retrieve the account table row of account section. * @param {ICashFlowStatementSection} section * @returns {ITableRow} */ private accountSectionMapper = ( section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); return tableRowMapper(section, columns, { rowTypes: [IROW_TYPE.ACCOUNT], id: `account-${section.id}`, }); }; /** * Retrieve the total table rows from the given total section. * @param {ICashFlowStatementSection} section * @returns {ITableRow} */ private totalSectionMapper = ( section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); return tableRowMapper(section, columns, { rowTypes: [IROW_TYPE.TOTAL], id: section.id, }); }; /** * Detarmines the schema section type. * @param {string} type * @param {ICashFlowSchemaSection} section * @returns {boolean} */ private isSectionHasType = ( type: string, section: ICashFlowStatementSection, ): boolean => { return type === section.sectionType; }; /** * The report section mapper. * @param {ICashFlowStatementSection} section * @returns {ITableRow} */ private sectionMapper = ( section: ICashFlowStatementSection, key: string, parentSection: ICashFlowStatementSection, ): ITableRow => { const isSectionHasType = R.curry(this.isSectionHasType); return R.pipe( R.when( isSectionHasType(ICashFlowStatementSectionType.AGGREGATE), this.regularSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.CASH_AT_BEGINNING), this.regularSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.NET_INCOME), this.netIncomeSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.ACCOUNTS), this.accountsSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.ACCOUNT), this.accountSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.TOTAL), this.totalSectionMapper, ), )(section); }; /** * Mappes the sections to the table rows. * @param {ICashFlowStatementSection[]} sections * @returns {ITableRow[]} */ private mapSectionsToTableRows = ( sections: ICashFlowStatementSection[], ): ITableRow[] => { return mapValuesDeep(sections, this.sectionMapper.bind(this), DEEP_CONFIG); }; /** * Appends the total to section's children. * @param {ICashFlowStatementSection} section * @returns {ICashFlowStatementSection} */ private appendTotalToSectionChildren = ( section: ICashFlowStatementSection, ): ICashFlowStatementSection => { const label = section.footerLabel ? section.footerLabel : this.i18n.__('Total {{accountName}}', { accountName: section.label }); section.children.push({ sectionType: ICashFlowStatementSectionType.TOTAL, label, periods: section.periods, total: section.total, }); return section; }; /** * * @param {ICashFlowStatementSection} section * @returns {ICashFlowStatementSection} */ private mapSectionsToAppendTotalChildren = ( section: ICashFlowStatementSection, ): ICashFlowStatementSection => { const isSectionHasChildren = (section) => !isEmpty(section.children); return R.compose( R.when( isSectionHasChildren, this.appendTotalToSectionChildren.bind(this), ), )(section); }; /** * Appends total node to children section. * @param {ICashFlowStatementSection[]} sections * @returns {ICashFlowStatementSection[]} */ private appendTotalToChildren = (sections: ICashFlowStatementSection[]) => { return mapValuesDeep( sections, this.mapSectionsToAppendTotalChildren.bind(this), DEEP_CONFIG, ); }; /** * Retrieve the table rows of cash flow statement. * @param {ICashFlowStatementSection[]} sections * @returns {ITableRow[]} */ public tableRows = (): ITableRow[] => { const sections = this.report.data; return R.pipe( this.appendTotalToChildren, this.mapSectionsToTableRows, )(sections); }; /** * Retrieve the total columns. * @returns {ITableColumn} */ private totalColumns = (): ITableColumn[] => { return [{ key: 'total', label: this.i18n.__('Total') }]; }; /** * Retrieve the formatted column label from the given date range. * @param {ICashFlowDateRange} dateRange - * @return {string} */ private formatColumnLabel = (dateRange: ICashFlowDateRange) => { const monthFormat = (range) => moment(range.toDate).format('YYYY-MM'); const yearFormat = (range) => moment(range.toDate).format('YYYY'); const dayFormat = (range) => moment(range.toDate).format('YYYY-MM-DD'); const conditions = [ ['month', monthFormat], ['year', yearFormat], ['day', dayFormat], ['quarter', monthFormat], ['week', dayFormat], ]; const conditionsPairs = R.map( ([type, formatFn]) => [ R.always(this.isDisplayColumnsType(type)), formatFn, ], conditions, ); return R.compose(R.cond(conditionsPairs))(dateRange); }; /** * Date periods columns. * @returns {ITableColumn[]} */ private datePeriodsColumns = (): ITableColumn[] => { return this.dateRangeSet.map((dateRange, index) => ({ key: `date-range-${index}`, label: this.formatColumnLabel(dateRange), })); }; /** * Detarmines the given column type is the current. * @reutrns {boolean} */ private isDisplayColumnsBy = (displayColumnsType: string): Boolean => { return this.report.query.displayColumnsType === displayColumnsType; }; /** * Detarmines whether the given display columns type is the current. * @param {string} displayColumnsBy * @returns {boolean} */ private isDisplayColumnsType = (displayColumnsBy: string): Boolean => { return this.report.query.displayColumnsBy === displayColumnsBy; }; /** * Retrieve the table columns. * @return {ITableColumn[]} */ public tableColumns = (): ITableColumn[] => { return R.compose( R.concat([{ key: 'name', label: this.i18n.__('Account name') }]), R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), R.concat(this.datePeriodsColumns()), ), R.concat(this.totalColumns()), )([]); }; }