Files
bigcapital/server/src/services/FinancialStatements/CashFlow/CashFlowTable.ts
2021-06-10 12:51:00 +02:00

366 lines
9.7 KiB
TypeScript

import * as R from 'ramda';
import { isEmpty, times } from 'lodash';
import moment from 'moment';
import {
ICashFlowStatementSection,
ICashFlowStatementSectionType,
ICashFlowStatement,
ITableRow,
ITableColumn,
ICashFlowStatementQuery,
IDateRange,
ICashFlowStatementDOO,
} from 'interfaces';
import { dateRangeFromToCollection, tableRowMapper } from '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 default class CashFlowTable implements ICashFlowTable {
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: 'label', 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.REGULAR),
this.regularSectionMapper.bind(this)
),
R.when(
isSectionHasType(ICashFlowStatementSectionType.CASH_AT_BEGINNING),
this.regularSectionMapper.bind(this)
),
R.when(
isSectionHasType(ICashFlowStatementSectionType.NET_INCOME),
this.netIncomeSectionMapper.bind(this)
),
R.when(
isSectionHasType(ICashFlowStatementSectionType.ACCOUNTS),
this.accountsSectionMapper.bind(this)
),
R.when(
isSectionHasType(ICashFlowStatementSectionType.ACCOUNT),
this.accountSectionMapper.bind(this)
),
R.when(
isSectionHasType(ICashFlowStatementSectionType.TOTAL),
this.totalSectionMapper.bind(this)
)
)(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.bind(this),
this.mapSectionsToTableRows.bind(this)
)(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())
)([]);
}
}