Files
bigcapital/temp/CashFlow/CashFlowTable.ts
2025-01-18 22:32:45 +02:00

375 lines
9.8 KiB
TypeScript

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()),
)([]);
};
}