mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
refactor: nestjs
This commit is contained in:
@@ -17,7 +17,7 @@ export class ExpenseGLEntriesService {
|
|||||||
/**
|
/**
|
||||||
* Retrieves the expense G/L of the given id.
|
* Retrieves the expense G/L of the given id.
|
||||||
* @param {number} expenseId
|
* @param {number} expenseId
|
||||||
* @param {Knex.Transaction} trx
|
* @param {Knex.Transaction} trx - Knex transaction.
|
||||||
* @returns {Promise<ILedger>}
|
* @returns {Promise<ILedger>}
|
||||||
*/
|
*/
|
||||||
public getExpenseLedgerById = async (
|
public getExpenseLedgerById = async (
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { InventoryItemDetailsModule } from './modules/InventoryItemDetails/Inven
|
|||||||
import { InventoryValuationSheetModule } from './modules/InventoryValuationSheet/InventoryValuationSheet.module';
|
import { InventoryValuationSheetModule } from './modules/InventoryValuationSheet/InventoryValuationSheet.module';
|
||||||
import { SalesTaxLiabilityModule } from './modules/SalesTaxLiabilitySummary/SalesTaxLiability.module';
|
import { SalesTaxLiabilityModule } from './modules/SalesTaxLiabilitySummary/SalesTaxLiability.module';
|
||||||
import { JournalSheetModule } from './modules/JournalSheet/JournalSheet.module';
|
import { JournalSheetModule } from './modules/JournalSheet/JournalSheet.module';
|
||||||
|
import { ProfitLossSheetModule } from './modules/ProfitLossSheet/ProfitLossSheet.module';
|
||||||
|
import { CashflowStatementModule } from './modules/CashFlowStatement/CashflowStatement.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [],
|
providers: [],
|
||||||
@@ -31,6 +33,8 @@ import { JournalSheetModule } from './modules/JournalSheet/JournalSheet.module';
|
|||||||
InventoryValuationSheetModule,
|
InventoryValuationSheetModule,
|
||||||
SalesTaxLiabilityModule,
|
SalesTaxLiabilityModule,
|
||||||
JournalSheetModule,
|
JournalSheetModule,
|
||||||
|
ProfitLossSheetModule,
|
||||||
|
CashflowStatementModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class FinancialStatementsModule {}
|
export class FinancialStatementsModule {}
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import { dateRangeFromToCollection } from '@/utils/date-range-collection';
|
|||||||
import { FinancialDateRanges } from './FinancialDateRanges';
|
import { FinancialDateRanges } from './FinancialDateRanges';
|
||||||
import { GConstructor } from '@/common/types/Constructor';
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
import { FinancialSheet } from './FinancialSheet';
|
import { FinancialSheet } from './FinancialSheet';
|
||||||
|
import { IFinancialSheetTotalPeriod } from '../modules/BalanceSheet/BalanceSheet.types';
|
||||||
|
|
||||||
export const FinancialDatePeriods = <T extends GConstructor<FinancialSheet>>(
|
export const FinancialDatePeriods = <T extends GConstructor<FinancialSheet>>(
|
||||||
Base: T,
|
Base: T,
|
||||||
) =>
|
) =>
|
||||||
class extends R.compose(FinancialDateRanges)(Base) {
|
class extends R.pipe(FinancialDateRanges)(Base) {
|
||||||
/**
|
/**
|
||||||
* Retrieves the date ranges from the given from date to the given to date.
|
* Retrieves the date ranges from the given from date to the given to date.
|
||||||
* @param {Date} fromDate -
|
* @param {Date} fromDate -
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as moment from 'moment';
|
|||||||
import { IDateRange, IFinancialDatePeriodsUnit } from '../types/Report.types';
|
import { IDateRange, IFinancialDatePeriodsUnit } from '../types/Report.types';
|
||||||
import { GConstructor } from '@/common/types/Constructor';
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
import { FinancialSheet } from './FinancialSheet';
|
import { FinancialSheet } from './FinancialSheet';
|
||||||
|
import { DateInput } from '@/common/types/Date';
|
||||||
|
|
||||||
export const FinancialDateRanges = <T extends GConstructor<FinancialSheet>>(
|
export const FinancialDateRanges = <T extends GConstructor<FinancialSheet>>(
|
||||||
Base: T,
|
Base: T,
|
||||||
@@ -15,7 +16,7 @@ export const FinancialDateRanges = <T extends GConstructor<FinancialSheet>>(
|
|||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
public getPreviousPeriodDate = (
|
public getPreviousPeriodDate = (
|
||||||
date: Date,
|
date: DateInput,
|
||||||
value: number = 1,
|
value: number = 1,
|
||||||
unit: IFinancialDatePeriodsUnit = IFinancialDatePeriodsUnit.Day,
|
unit: IFinancialDatePeriodsUnit = IFinancialDatePeriodsUnit.Day,
|
||||||
): Date => {
|
): Date => {
|
||||||
@@ -28,21 +29,21 @@ export const FinancialDateRanges = <T extends GConstructor<FinancialSheet>>(
|
|||||||
* @param {Date} toDate
|
* @param {Date} toDate
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
public getPreviousPeriodDiff = (fromDate: Date, toDate: Date) => {
|
public getPreviousPeriodDiff = (fromDate: DateInput, toDate: DateInput) => {
|
||||||
return moment(toDate).diff(fromDate, 'days') + 1;
|
return moment(toDate).diff(fromDate, 'days') + 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the periods period dates.
|
* Retrieves the periods period dates.
|
||||||
* @param {Date} fromDate - From date.
|
* @param {DateInput} fromDate - From date.
|
||||||
* @param {Date} toDate - To date.
|
* @param {DateInput} toDate - To date.
|
||||||
* @param {IFinancialDatePeriodsUnit} unit - Unit of time.
|
* @param {IFinancialDatePeriodsUnit} unit - Unit of time.
|
||||||
* @param {number} amount - Amount of time.
|
* @param {number} amount - Amount of time.
|
||||||
* @returns {IDateRange}
|
* @returns {IDateRange}
|
||||||
*/
|
*/
|
||||||
public getPreviousPeriodDateRange = (
|
public getPreviousPeriodDateRange = (
|
||||||
fromDate: Date,
|
fromDate: DateInput,
|
||||||
toDate: Date,
|
toDate: DateInput,
|
||||||
unit: IFinancialDatePeriodsUnit,
|
unit: IFinancialDatePeriodsUnit,
|
||||||
amount: number = 1,
|
amount: number = 1,
|
||||||
): IDateRange => {
|
): IDateRange => {
|
||||||
@@ -54,11 +55,14 @@ export const FinancialDateRanges = <T extends GConstructor<FinancialSheet>>(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the previous period (PP) date range of total column.
|
* Retrieves the previous period (PP) date range of total column.
|
||||||
* @param {Date} fromDate
|
* @param {DateInput} fromDate - From date.
|
||||||
* @param {Date} toDate
|
* @param {DateInput} toDate - To date.
|
||||||
* @returns {IDateRange}
|
* @returns {IDateRange}
|
||||||
*/
|
*/
|
||||||
public getPPTotalDateRange = (fromDate: Date, toDate: Date): IDateRange => {
|
public getPPTotalDateRange = (
|
||||||
|
fromDate: DateInput,
|
||||||
|
toDate: DateInput,
|
||||||
|
): IDateRange => {
|
||||||
const unit = this.getPreviousPeriodDiff(fromDate, toDate);
|
const unit = this.getPreviousPeriodDiff(fromDate, toDate);
|
||||||
|
|
||||||
return this.getPreviousPeriodDateRange(
|
return this.getPreviousPeriodDateRange(
|
||||||
@@ -77,8 +81,8 @@ export const FinancialDateRanges = <T extends GConstructor<FinancialSheet>>(
|
|||||||
* @returns {IDateRange}
|
* @returns {IDateRange}
|
||||||
*/
|
*/
|
||||||
public getPPDatePeriodDateRange = (
|
public getPPDatePeriodDateRange = (
|
||||||
fromDate: Date,
|
fromDate: DateInput,
|
||||||
toDate: Date,
|
toDate: DateInput,
|
||||||
unit: IFinancialDatePeriodsUnit,
|
unit: IFinancialDatePeriodsUnit,
|
||||||
): IDateRange => {
|
): IDateRange => {
|
||||||
return this.getPreviousPeriodDateRange(fromDate, toDate, unit, 1);
|
return this.getPreviousPeriodDateRange(fromDate, toDate, unit, 1);
|
||||||
@@ -89,22 +93,22 @@ export const FinancialDateRanges = <T extends GConstructor<FinancialSheet>>(
|
|||||||
// ------------------------
|
// ------------------------
|
||||||
/**
|
/**
|
||||||
* Retrieve the previous year of the given date.
|
* Retrieve the previous year of the given date.
|
||||||
* @params {Date} date
|
* @param {DateInput} date
|
||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
getPreviousYearDate = (date: Date) => {
|
getPreviousYearDate = (date: DateInput) => {
|
||||||
return moment(date).subtract(1, 'years').toDate();
|
return moment(date).subtract(1, 'years').toDate();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves previous year date range.
|
* Retrieves previous year date range.
|
||||||
* @param {Date} fromDate
|
* @param {DateInput} fromDate - From date.
|
||||||
* @param {Date} toDate
|
* @param {DateInput} toDate - To date.
|
||||||
* @returns {IDateRange}
|
* @returns {IDateRange}
|
||||||
*/
|
*/
|
||||||
public getPreviousYearDateRange = (
|
public getPreviousYearDateRange = (
|
||||||
fromDate: Date,
|
fromDate: DateInput,
|
||||||
toDate: Date,
|
toDate: DateInput,
|
||||||
): IDateRange => {
|
): IDateRange => {
|
||||||
const PYFromDate = this.getPreviousYearDate(fromDate);
|
const PYFromDate = this.getPreviousYearDate(fromDate);
|
||||||
const PYToDate = this.getPreviousYearDate(toDate);
|
const PYToDate = this.getPreviousYearDate(toDate);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const FinancialEvaluateEquation = <
|
|||||||
>(
|
>(
|
||||||
Base: T
|
Base: T
|
||||||
) =>
|
) =>
|
||||||
class FinancialEvaluateEquation extends R.compose(FinancialSheetStructure)(Base) {
|
class FinancialEvaluateEquation extends R.pipe(FinancialSheetStructure)(Base) {
|
||||||
/**
|
/**
|
||||||
* Evauluate equaation string with the given scope table.
|
* Evauluate equaation string with the given scope table.
|
||||||
* @param {string} equation -
|
* @param {string} equation -
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
import { Constructor } from '@/common/types/Constructor';
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
import { FinancialSheet } from './FinancialSheet';
|
||||||
|
import { IFinancialCommonNode } from '../types/Report.types';
|
||||||
|
|
||||||
export const FinancialFilter = <T extends Constructor>(Base: T) =>
|
export const FinancialFilter = <T extends GConstructor<FinancialSheet>>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
class extends Base {
|
class extends Base {
|
||||||
/**
|
/**
|
||||||
* Detarmines whether the given node has children.
|
* Detarmines whether the given node has children.
|
||||||
* @param {IBalanceSheetCommonNode} node
|
* @param {IBalanceSheetCommonNode} node
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
public isNodeHasChildren = (node: IFinancialCommonNode): boolean =>
|
public isNodeHasChildren = (node: IFinancialCommonNode): boolean =>
|
||||||
@@ -17,7 +20,7 @@ export const FinancialFilter = <T extends Constructor>(Base: T) =>
|
|||||||
* @param {IBalanceSheetCommonNode} node
|
* @param {IBalanceSheetCommonNode} node
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
public isNodeNoneZero = (node) =>{
|
public isNodeNoneZero = (node) => {
|
||||||
return node.total.amount !== 0;
|
return node.total.amount !== 0;
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const FinancialHorizTotals = <T extends GConstructor<FinancialSheet>>(
|
|||||||
) =>
|
) =>
|
||||||
class FinancialHorizTotals extends Base {
|
class FinancialHorizTotals extends Base {
|
||||||
/**
|
/**
|
||||||
*
|
* Associate percentage to the given node.
|
||||||
*/
|
*/
|
||||||
public assocNodePercentage = R.curry(
|
public assocNodePercentage = R.curry(
|
||||||
(assocPath, parentTotal: number, node: any) => {
|
(assocPath, parentTotal: number, node: any) => {
|
||||||
@@ -25,7 +25,7 @@ export const FinancialHorizTotals = <T extends GConstructor<FinancialSheet>>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Associate horizontal percentage total to the given node.
|
||||||
* @param {} parentNode -
|
* @param {} parentNode -
|
||||||
* @param {} horTotalNode -
|
* @param {} horTotalNode -
|
||||||
* @param {number} index -
|
* @param {number} index -
|
||||||
|
|||||||
@@ -4,20 +4,21 @@ import {
|
|||||||
IFinancialNodeWithPreviousPeriod,
|
IFinancialNodeWithPreviousPeriod,
|
||||||
} from '../types/Report.types';
|
} from '../types/Report.types';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Constructor, GConstructor } from '@/common/types/Constructor';
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
import { FinancialSheet } from './FinancialSheet';
|
import { FinancialSheet } from './FinancialSheet';
|
||||||
import { FinancialDatePeriods } from './FinancialDatePeriods';
|
import { FinancialDatePeriods } from './FinancialDatePeriods';
|
||||||
|
import { IProfitLossSheetAccountNode } from '../modules/ProfitLossSheet/ProfitLossSheet.types';
|
||||||
|
|
||||||
export const FinancialPreviousPeriod = <T extends GConstructor<FinancialSheet>>(
|
export const FinancialPreviousPeriod = <T extends GConstructor<FinancialSheet>>(
|
||||||
Base: T,
|
Base: T,
|
||||||
) =>
|
) =>
|
||||||
class extends R.compose(FinancialDatePeriods)(Base) {
|
class extends R.pipe(FinancialDatePeriods)(Base) {
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// # Common Node.
|
// # Common Node.
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
/**
|
/**
|
||||||
* Assoc previous period percentage attribute to account node.
|
* Assoc previous period percentage attribute to account node.
|
||||||
* @param {IProfitLossSheetAccountNode} accountNode
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
* @returns {IFinancialNodeWithPreviousPeriod}
|
* @returns {IFinancialNodeWithPreviousPeriod}
|
||||||
*/
|
*/
|
||||||
public assocPreviousPeriodPercentageNode = (
|
public assocPreviousPeriodPercentageNode = (
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { GConstructor } from '@/common/types/Constructor';
|
|||||||
import { FinancialSheet } from './FinancialSheet';
|
import { FinancialSheet } from './FinancialSheet';
|
||||||
|
|
||||||
export const FinancialSchema = <T extends GConstructor<FinancialSheet>>(
|
export const FinancialSchema = <T extends GConstructor<FinancialSheet>>(
|
||||||
Base: T
|
Base: T,
|
||||||
) =>
|
) =>
|
||||||
class FinancialSchema extends R.compose(FinancialSheetStructure)(Base) {
|
class FinancialSchema extends FinancialSheetStructure(Base) {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ import {
|
|||||||
import { GConstructor } from '@/common/types/Constructor';
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
import { FinancialSheet } from './FinancialSheet';
|
import { FinancialSheet } from './FinancialSheet';
|
||||||
|
|
||||||
export const FinancialSheetStructure = <
|
export const FinancialSheetStructure = <T extends GConstructor<FinancialSheet>>(
|
||||||
T extends GConstructor<FinancialSheet>,
|
Base: T,
|
||||||
>(
|
|
||||||
Base: T
|
|
||||||
) =>
|
) =>
|
||||||
class FinancialSheetStructure extends Base {
|
class FinancialSheetStructure extends Base {
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +28,6 @@ export const FinancialSheetStructure = <
|
|||||||
pathFormat: 'array',
|
pathFormat: 'array',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param nodes
|
* @param nodes
|
||||||
@@ -43,18 +40,15 @@ export const FinancialSheetStructure = <
|
|||||||
pathFormat: 'array',
|
pathFormat: 'array',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public mapNodes = (nodes, callback) => {
|
public mapNodes = (nodes, callback) => {
|
||||||
return mapValues(nodes, callback, {
|
return mapValues(nodes, callback, {
|
||||||
childrenPath: 'children',
|
childrenPath: 'children',
|
||||||
pathFormat: 'array',
|
pathFormat: 'array',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public filterNodesDeep2 = R.curry((predicate, nodes) => {
|
public filterNodesDeep2 = R.curry((predicate, nodes) => {
|
||||||
return filterNodesDeep(predicate, nodes);
|
return filterNodesDeep(predicate, nodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param
|
* @param
|
||||||
@@ -65,14 +59,12 @@ export const FinancialSheetStructure = <
|
|||||||
pathFormat: 'array',
|
pathFormat: 'array',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public findNodeDeep = (nodes, callback) => {
|
public findNodeDeep = (nodes, callback) => {
|
||||||
return findValueDeep(nodes, callback, {
|
return findValueDeep(nodes, callback, {
|
||||||
childrenPath: 'children',
|
childrenPath: 'children',
|
||||||
pathFormat: 'array',
|
pathFormat: 'array',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public mapAccNodesDeep = (nodes, callback) => {
|
public mapAccNodesDeep = (nodes, callback) => {
|
||||||
return reduceDeep(
|
return reduceDeep(
|
||||||
nodes,
|
nodes,
|
||||||
@@ -80,7 +72,7 @@ export const FinancialSheetStructure = <
|
|||||||
set(
|
set(
|
||||||
acc,
|
acc,
|
||||||
context.path,
|
context.path,
|
||||||
callback(value, key, parentValue, acc, context)
|
callback(value, key, parentValue, acc, context),
|
||||||
);
|
);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
@@ -88,10 +80,9 @@ export const FinancialSheetStructure = <
|
|||||||
{
|
{
|
||||||
childrenPath: 'children',
|
childrenPath: 'children',
|
||||||
pathFormat: 'array',
|
pathFormat: 'array',
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -101,11 +92,9 @@ export const FinancialSheetStructure = <
|
|||||||
pathFormat: 'array',
|
pathFormat: 'array',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public getTotalOfChildrenNodes = (node) => {
|
public getTotalOfChildrenNodes = (node) => {
|
||||||
return this.getTotalOfNodes(node.children);
|
return this.getTotalOfNodes(node.children);
|
||||||
};
|
};
|
||||||
|
|
||||||
public getTotalOfNodes = (nodes) => {
|
public getTotalOfNodes = (nodes) => {
|
||||||
return sumBy(nodes, 'total.amount');
|
return sumBy(nodes, 'total.amount');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export const BalanceSheetAggregators = <T extends GConstructor<FinancialSheet>>(
|
|||||||
BalanceSheetComparsionPreviousYear,
|
BalanceSheetComparsionPreviousYear,
|
||||||
BalanceSheetPercentage,
|
BalanceSheetPercentage,
|
||||||
BalanceSheetSchema,
|
BalanceSheetSchema,
|
||||||
BalanceSheetBase,
|
|
||||||
FinancialSheetStructure,
|
FinancialSheetStructure,
|
||||||
|
BalanceSheetBase,
|
||||||
)(Base) {
|
)(Base) {
|
||||||
/**
|
/**
|
||||||
* Balance sheet query.
|
* Balance sheet query.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const BalanceSheetBase = <T extends GConstructor<FinancialSheet>>(
|
|||||||
) =>
|
) =>
|
||||||
class BalanceSheetBase extends Base {
|
class BalanceSheetBase extends Base {
|
||||||
/**
|
/**
|
||||||
* Detarmines the node type of the given schema node.
|
* Determines the node type of the given schema node.
|
||||||
* @param {IBalanceSheetStructureSection} node -
|
* @param {IBalanceSheetStructureSection} node -
|
||||||
* @param {string} type -
|
* @param {string} type -
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
@@ -21,9 +21,8 @@ export const BalanceSheetBase = <T extends GConstructor<FinancialSheet>>(
|
|||||||
return node.type === type;
|
return node.type === type;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detarmines the node type of the given schema node.
|
* Determines the node type of the given schema node.
|
||||||
* @param {IBalanceSheetStructureSection} node -
|
* @param {IBalanceSheetStructureSection} node -
|
||||||
* @param {string} type -
|
* @param {string} type -
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
@@ -33,9 +32,8 @@ export const BalanceSheetBase = <T extends GConstructor<FinancialSheet>>(
|
|||||||
return node.nodeType === type;
|
return node.nodeType === type;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detarmines the given display columns by type.
|
* Determines the given display columns by type.
|
||||||
* @param {string} displayColumnsBy
|
* @param {string} displayColumnsBy
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ export const BalanceSheetNetIncome = <T extends GConstructor<FinancialSheet>>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mappes the aggregate schema node type.
|
* Maps the aggregate schema node type.
|
||||||
* @param {IBalanceSheetSchemaNetIncomeNode} node - Schema node.
|
* @param {IBalanceSheetSchemaNetIncomeNode} node - Schema node.
|
||||||
* @return {IBalanceSheetAggregateNode}
|
* @return {IBalanceSheetAggregateNode}
|
||||||
*/
|
*/
|
||||||
public schemaNetIncomeNodeMapper = (
|
public schemaNetIncomeNodeMapper = (
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
import * as R from 'ramda';
|
|
||||||
import {
|
import {
|
||||||
BALANCE_SHEET_SCHEMA_NODE_ID,
|
BALANCE_SHEET_SCHEMA_NODE_ID,
|
||||||
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
||||||
@@ -12,7 +11,7 @@ import { FinancialSheet } from '../../common/FinancialSheet';
|
|||||||
export const BalanceSheetSchema = <T extends GConstructor<FinancialSheet>>(
|
export const BalanceSheetSchema = <T extends GConstructor<FinancialSheet>>(
|
||||||
Base: T,
|
Base: T,
|
||||||
) =>
|
) =>
|
||||||
class extends R.pipe(FinancialSchema)(Base) {
|
class extends FinancialSchema(Base) {
|
||||||
/**
|
/**
|
||||||
* Retrieves the balance sheet schema.
|
* Retrieves the balance sheet schema.
|
||||||
* @returns
|
* @returns
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { ITableColumn } from '../../types/Table.types';
|
|
||||||
import { Constructor } from '@/common/types/Constructor';
|
|
||||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
|
||||||
import { I18nService } from 'nestjs-i18n';
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { ITableColumn } from '../../types/Table.types';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
|
||||||
export const BalanceSheetTablePercentage = <T extends Constructor>(Base: T) =>
|
export const BalanceSheetTablePercentage = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
class BalanceSheetComparsionPreviousYear extends Base {
|
class BalanceSheetComparsionPreviousYear extends Base {
|
||||||
public readonly query: BalanceSheetQuery;
|
public readonly query: BalanceSheetQuery;
|
||||||
public readonly i18n: I18nService;
|
public readonly i18n: I18nService;
|
||||||
@@ -23,15 +28,15 @@ export const BalanceSheetTablePercentage = <T extends Constructor>(Base: T) =>
|
|||||||
R.append({
|
R.append({
|
||||||
key: 'percentage_of_column',
|
key: 'percentage_of_column',
|
||||||
label: this.i18n.t('balance_sheet.percentage_of_column'),
|
label: this.i18n.t('balance_sheet.percentage_of_column'),
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
R.when(
|
R.when(
|
||||||
this.query.isRowsPercentageActive,
|
this.query.isRowsPercentageActive,
|
||||||
R.append({
|
R.append({
|
||||||
key: 'percentage_of_row',
|
key: 'percentage_of_row',
|
||||||
label: this.i18n.t('balance_sheet.percentage_of_row'),
|
label: this.i18n.t('balance_sheet.percentage_of_row'),
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)([]);
|
)([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,15 +54,15 @@ export const BalanceSheetTablePercentage = <T extends Constructor>(Base: T) =>
|
|||||||
R.append({
|
R.append({
|
||||||
key: 'percentage_of_column',
|
key: 'percentage_of_column',
|
||||||
accessor: 'percentageColumn.formattedAmount',
|
accessor: 'percentageColumn.formattedAmount',
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
R.when(
|
R.when(
|
||||||
this.query.isRowsPercentageActive,
|
this.query.isRowsPercentageActive,
|
||||||
R.append({
|
R.append({
|
||||||
key: 'percentage_of_row',
|
key: 'percentage_of_row',
|
||||||
accessor: 'percentageRow.formattedAmount',
|
accessor: 'percentageRow.formattedAmount',
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)([]);
|
)([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,7 +72,7 @@ export const BalanceSheetTablePercentage = <T extends Constructor>(Base: T) =>
|
|||||||
* @returns {ITableColumn[]}
|
* @returns {ITableColumn[]}
|
||||||
*/
|
*/
|
||||||
public percetangeDatePeriodColumnsAccessor = (
|
public percetangeDatePeriodColumnsAccessor = (
|
||||||
index: number
|
index: number,
|
||||||
): ITableColumn[] => {
|
): ITableColumn[] => {
|
||||||
return R.pipe(
|
return R.pipe(
|
||||||
R.when(
|
R.when(
|
||||||
@@ -75,15 +80,15 @@ export const BalanceSheetTablePercentage = <T extends Constructor>(Base: T) =>
|
|||||||
R.append({
|
R.append({
|
||||||
key: `percentage_of_column-${index}`,
|
key: `percentage_of_column-${index}`,
|
||||||
accessor: `horizontalTotals[${index}].percentageColumn.formattedAmount`,
|
accessor: `horizontalTotals[${index}].percentageColumn.formattedAmount`,
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
R.when(
|
R.when(
|
||||||
this.query.isRowsPercentageActive,
|
this.query.isRowsPercentageActive,
|
||||||
R.append({
|
R.append({
|
||||||
key: `percentage_of_row-${index}`,
|
key: `percentage_of_row-${index}`,
|
||||||
accessor: `horizontalTotals[${index}].percentageRow.formattedAmount`,
|
accessor: `horizontalTotals[${index}].percentageRow.formattedAmount`,
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)([]);
|
)([]);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import { IDateRange } from '../../types/Report.types';
|
|||||||
import { ITableColumn } from '../../types/Table.types';
|
import { ITableColumn } from '../../types/Table.types';
|
||||||
import { FinancialTablePreviousYear } from '../../common/FinancialTablePreviousYear';
|
import { FinancialTablePreviousYear } from '../../common/FinancialTablePreviousYear';
|
||||||
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
|
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
|
||||||
import { Constructor } from '@/common/types/Constructor';
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
|
||||||
export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) =>
|
export const BalanceSheetTablePreviousYear = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
class extends R.pipe(FinancialTablePreviousYear, FinancialDateRanges)(Base) {
|
class extends R.pipe(FinancialTablePreviousYear, FinancialDateRanges)(Base) {
|
||||||
// --------------------
|
// --------------------
|
||||||
// # Columns.
|
// # Columns.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as R from 'ramda';
|
|||||||
import { defaultTo, map, set, sumBy, isEmpty, mapValues, get } from 'lodash';
|
import { defaultTo, map, set, sumBy, isEmpty, mapValues, get } from 'lodash';
|
||||||
import * as mathjs from 'mathjs';
|
import * as mathjs from 'mathjs';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { compose } from 'lodash/fp';
|
|
||||||
import { I18nService } from 'nestjs-i18n';
|
import { I18nService } from 'nestjs-i18n';
|
||||||
import {
|
import {
|
||||||
ICashFlowSchemaSection,
|
ICashFlowSchemaSection,
|
||||||
@@ -21,7 +20,6 @@ import {
|
|||||||
ICashFlowStatementAggregateSection,
|
ICashFlowStatementAggregateSection,
|
||||||
} from './Cashflow.types';
|
} from './Cashflow.types';
|
||||||
import { CASH_FLOW_SCHEMA } from './schema';
|
import { CASH_FLOW_SCHEMA } from './schema';
|
||||||
import { FinancialSheet } from '../../common/FinancialSheet';
|
|
||||||
import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts';
|
import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts';
|
||||||
import { CashFlowStatementDatePeriods } from './CashFlowDatePeriods';
|
import { CashFlowStatementDatePeriods } from './CashFlowDatePeriods';
|
||||||
import { DISPLAY_COLUMNS_BY } from './constants';
|
import { DISPLAY_COLUMNS_BY } from './constants';
|
||||||
@@ -32,11 +30,12 @@ import { INumberFormatQuery } from '../../types/Report.types';
|
|||||||
import { transformToMapBy } from '@/utils/transform-to-map-by';
|
import { transformToMapBy } from '@/utils/transform-to-map-by';
|
||||||
import { accumSum } from '@/utils/accum-sum';
|
import { accumSum } from '@/utils/accum-sum';
|
||||||
import { ModelObject } from 'objection';
|
import { ModelObject } from 'objection';
|
||||||
|
import { CashflowStatementBase } from './CashflowStatementBase';
|
||||||
|
|
||||||
export default class CashFlowStatement extends compose(
|
export class CashFlowStatement extends R.pipe(
|
||||||
CashFlowStatementDatePeriods,
|
CashFlowStatementDatePeriods,
|
||||||
FinancialSheetStructure
|
FinancialSheetStructure,
|
||||||
)(FinancialSheet) {
|
)(CashflowStatementBase) {
|
||||||
readonly baseCurrency: string;
|
readonly baseCurrency: string;
|
||||||
readonly i18n: I18nService;
|
readonly i18n: I18nService;
|
||||||
readonly sectionsByIds = {};
|
readonly sectionsByIds = {};
|
||||||
@@ -63,7 +62,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
netIncomeLedger: ILedger,
|
netIncomeLedger: ILedger,
|
||||||
query: ICashFlowStatementQuery,
|
query: ICashFlowStatementQuery,
|
||||||
baseCurrency: string,
|
baseCurrency: string,
|
||||||
i18n
|
i18n: I18nService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -83,41 +82,6 @@ export default class CashFlowStatement extends compose(
|
|||||||
this.initDateRangeCollection();
|
this.initDateRangeCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
// # GENERAL UTILITIES
|
|
||||||
// --------------------------------------------
|
|
||||||
/**
|
|
||||||
* Retrieve the expense accounts ids.
|
|
||||||
* @return {number[]}
|
|
||||||
*/
|
|
||||||
private getAccountsIdsByType = (accountType: string): number[] => {
|
|
||||||
const expenseAccounts = this.accountsByRootType.get(accountType);
|
|
||||||
const expenseAccountsIds = map(expenseAccounts, 'id');
|
|
||||||
|
|
||||||
return expenseAccountsIds;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines the given display columns by type.
|
|
||||||
* @param {string} displayColumnsBy
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isDisplayColumnsBy = (displayColumnsBy: string): boolean => {
|
|
||||||
return this.query.displayColumnsType === displayColumnsBy;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjustments the given amount.
|
|
||||||
* @param {string} direction
|
|
||||||
* @param {number} amount -
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
private amountAdjustment = (direction: 'mines' | 'plus', amount): number => {
|
|
||||||
return R.when(
|
|
||||||
R.always(R.equals(direction, 'mines')),
|
|
||||||
R.multiply(-1)
|
|
||||||
)(amount);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------
|
// --------------------------------------------
|
||||||
// # NET INCOME NODE
|
// # NET INCOME NODE
|
||||||
@@ -129,18 +93,18 @@ export default class CashFlowStatement extends compose(
|
|||||||
private getAccountsNetIncome(): number {
|
private getAccountsNetIncome(): number {
|
||||||
// Mapping income/expense accounts ids.
|
// Mapping income/expense accounts ids.
|
||||||
const incomeAccountsIds = this.getAccountsIdsByType(
|
const incomeAccountsIds = this.getAccountsIdsByType(
|
||||||
ACCOUNT_ROOT_TYPE.INCOME
|
ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
);
|
);
|
||||||
const expenseAccountsIds = this.getAccountsIdsByType(
|
const expenseAccountsIds = this.getAccountsIdsByType(
|
||||||
ACCOUNT_ROOT_TYPE.EXPENSE
|
ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
);
|
);
|
||||||
// Income closing balance.
|
// Income closing balance.
|
||||||
const incomeClosingBalance = accumSum(incomeAccountsIds, (id) =>
|
const incomeClosingBalance = accumSum(incomeAccountsIds, (id) =>
|
||||||
this.netIncomeLedger.whereAccountId(id).getClosingBalance()
|
this.netIncomeLedger.whereAccountId(id).getClosingBalance(),
|
||||||
);
|
);
|
||||||
// Expense closing balance.
|
// Expense closing balance.
|
||||||
const expenseClosingBalance = accumSum(expenseAccountsIds, (id) =>
|
const expenseClosingBalance = accumSum(expenseAccountsIds, (id) =>
|
||||||
this.netIncomeLedger.whereAccountId(id).getClosingBalance()
|
this.netIncomeLedger.whereAccountId(id).getClosingBalance(),
|
||||||
);
|
);
|
||||||
// Net income = income - expenses.
|
// Net income = income - expenses.
|
||||||
const netIncome = incomeClosingBalance - expenseClosingBalance;
|
const netIncome = incomeClosingBalance - expenseClosingBalance;
|
||||||
@@ -154,21 +118,21 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementNetIncomeSection}
|
* @returns {ICashFlowStatementNetIncomeSection}
|
||||||
*/
|
*/
|
||||||
private netIncomeSectionMapper = (
|
private netIncomeSectionMapper = (
|
||||||
nodeSchema: ICashFlowSchemaSection
|
nodeSchema: ICashFlowSchemaSection,
|
||||||
): ICashFlowStatementNetIncomeSection => {
|
): ICashFlowStatementNetIncomeSection => {
|
||||||
const netIncome = this.getAccountsNetIncome();
|
const netIncome = this.getAccountsNetIncome();
|
||||||
|
|
||||||
const node = {
|
const node = {
|
||||||
id: nodeSchema.id,
|
id: nodeSchema.id,
|
||||||
label: this.i18n.__(nodeSchema.label),
|
label: this.i18n.t(nodeSchema.label),
|
||||||
total: this.getAmountMeta(netIncome),
|
total: this.getAmountMeta(netIncome),
|
||||||
sectionType: ICashFlowStatementSectionType.NET_INCOME,
|
sectionType: ICashFlowStatementSectionType.NET_INCOME,
|
||||||
};
|
};
|
||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
this.assocPeriodsToNetIncomeNode
|
this.assocPeriodsToNetIncomeNode,
|
||||||
)
|
),
|
||||||
)(node);
|
)(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,7 +147,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
*/
|
*/
|
||||||
private accountMetaMapper = (
|
private accountMetaMapper = (
|
||||||
relation: ICashFlowSchemaAccountRelation,
|
relation: ICashFlowSchemaAccountRelation,
|
||||||
account: ModelObject<Account>
|
account: ModelObject<Account>,
|
||||||
): ICashFlowStatementAccountMeta => {
|
): ICashFlowStatementAccountMeta => {
|
||||||
// Retrieve the closing balance of the given account.
|
// Retrieve the closing balance of the given account.
|
||||||
const getClosingBalance = (id) =>
|
const getClosingBalance = (id) =>
|
||||||
@@ -191,7 +155,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
|
|
||||||
const closingBalance = R.compose(
|
const closingBalance = R.compose(
|
||||||
// Multiplies the amount by -1 in case the relation in mines.
|
// Multiplies the amount by -1 in case the relation in mines.
|
||||||
R.curry(this.amountAdjustment)(relation.direction)
|
R.curry(this.amountAdjustment)(relation.direction),
|
||||||
)(getClosingBalance(account.id));
|
)(getClosingBalance(account.id));
|
||||||
|
|
||||||
const node = {
|
const node = {
|
||||||
@@ -206,8 +170,8 @@ export default class CashFlowStatement extends compose(
|
|||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
this.assocPeriodsToAccountNode
|
this.assocPeriodsToAccountNode,
|
||||||
)
|
),
|
||||||
)(node);
|
)(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,7 +181,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
* @returns {ICashFlowStatementAccountMeta[]}
|
||||||
*/
|
*/
|
||||||
private getAccountsBySchemaRelation = (
|
private getAccountsBySchemaRelation = (
|
||||||
relation: ICashFlowSchemaAccountRelation
|
relation: ICashFlowSchemaAccountRelation,
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
): ICashFlowStatementAccountMeta[] => {
|
||||||
const accounts = defaultTo(this.accountByTypeMap.get(relation.type), []);
|
const accounts = defaultTo(this.accountByTypeMap.get(relation.type), []);
|
||||||
const accountMetaMapper = R.curry(this.accountMetaMapper)(relation);
|
const accountMetaMapper = R.curry(this.accountMetaMapper)(relation);
|
||||||
@@ -230,11 +194,11 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
* @returns {ICashFlowStatementAccountMeta[]}
|
||||||
*/
|
*/
|
||||||
private getAccountsBySchemaRelations = (
|
private getAccountsBySchemaRelations = (
|
||||||
relations: ICashFlowSchemaAccountRelation[]
|
relations: ICashFlowSchemaAccountRelation[],
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
): ICashFlowStatementAccountMeta[] => {
|
||||||
return R.pipe(
|
return R.pipe(
|
||||||
R.append(R.map(this.getAccountsBySchemaRelation)(relations)),
|
R.append(R.map(this.getAccountsBySchemaRelation)(relations)),
|
||||||
R.flatten
|
R.flatten,
|
||||||
)([]);
|
)([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -244,7 +208,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
private getAccountsMetaTotal = (
|
private getAccountsMetaTotal = (
|
||||||
accounts: ICashFlowStatementAccountMeta[]
|
accounts: ICashFlowStatementAccountMeta[],
|
||||||
): number => {
|
): number => {
|
||||||
return sumBy(accounts, 'total.amount');
|
return sumBy(accounts, 'total.amount');
|
||||||
};
|
};
|
||||||
@@ -255,7 +219,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementAccountSection}
|
* @returns {ICashFlowStatementAccountSection}
|
||||||
*/
|
*/
|
||||||
private accountsSectionParser = (
|
private accountsSectionParser = (
|
||||||
sectionSchema: ICashFlowSchemaSectionAccounts
|
sectionSchema: ICashFlowSchemaSectionAccounts,
|
||||||
): ICashFlowStatementAccountSection => {
|
): ICashFlowStatementAccountSection => {
|
||||||
const { accountsRelations } = sectionSchema;
|
const { accountsRelations } = sectionSchema;
|
||||||
|
|
||||||
@@ -266,16 +230,16 @@ export default class CashFlowStatement extends compose(
|
|||||||
const node = {
|
const node = {
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNTS,
|
sectionType: ICashFlowStatementSectionType.ACCOUNTS,
|
||||||
id: sectionSchema.id,
|
id: sectionSchema.id,
|
||||||
label: this.i18n.__(sectionSchema.label),
|
label: this.i18n.t(sectionSchema.label),
|
||||||
footerLabel: this.i18n.__(sectionSchema.footerLabel),
|
footerLabel: this.i18n.t(sectionSchema.footerLabel),
|
||||||
children: accounts,
|
children: accounts,
|
||||||
total,
|
total,
|
||||||
};
|
};
|
||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
this.assocPeriodsToAggregateNode
|
this.assocPeriodsToAggregateNode,
|
||||||
)
|
),
|
||||||
)(node);
|
)(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -288,8 +252,8 @@ export default class CashFlowStatement extends compose(
|
|||||||
private isSchemaSectionType = R.curry(
|
private isSchemaSectionType = R.curry(
|
||||||
(type: string, section: ICashFlowSchemaSection): boolean => {
|
(type: string, section: ICashFlowSchemaSection): boolean => {
|
||||||
return type === section.sectionType;
|
return type === section.sectionType;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// --------------------------------------------
|
// --------------------------------------------
|
||||||
// # AGGREGATE NODE
|
// # AGGREGATE NODE
|
||||||
@@ -302,29 +266,29 @@ export default class CashFlowStatement extends compose(
|
|||||||
private regularSectionParser = R.curry(
|
private regularSectionParser = R.curry(
|
||||||
(
|
(
|
||||||
children,
|
children,
|
||||||
schemaSection: ICashFlowSchemaSection
|
schemaSection: ICashFlowSchemaSection,
|
||||||
): ICashFlowStatementAggregateSection => {
|
): ICashFlowStatementAggregateSection => {
|
||||||
const node = {
|
const node = {
|
||||||
id: schemaSection.id,
|
id: schemaSection.id,
|
||||||
label: this.i18n.__(schemaSection.label),
|
label: this.i18n.t(schemaSection.label),
|
||||||
footerLabel: this.i18n.__(schemaSection.footerLabel),
|
footerLabel: this.i18n.t(schemaSection.footerLabel),
|
||||||
sectionType: ICashFlowStatementSectionType.AGGREGATE,
|
sectionType: ICashFlowStatementSectionType.AGGREGATE,
|
||||||
children,
|
children,
|
||||||
};
|
};
|
||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
||||||
this.assocRegularSectionTotal
|
this.assocRegularSectionTotal,
|
||||||
),
|
),
|
||||||
R.when(
|
R.when(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
this.assocPeriodsToAggregateNode
|
this.assocPeriodsToAggregateNode,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)(node);
|
)(node);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
private transformSectionsToMap = (sections: ICashFlowSchemaSection[]) => {
|
private transformSectionsToMap = (sections: ICashFlowSchemaSection[]) => {
|
||||||
@@ -336,7 +300,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{}
|
{},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -356,7 +320,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
*/
|
*/
|
||||||
private evaluateEquation = (
|
private evaluateEquation = (
|
||||||
equation: string,
|
equation: string,
|
||||||
scope: { [key: string | number]: number }
|
scope: { [key: string | number]: number },
|
||||||
): number => {
|
): number => {
|
||||||
return mathjs.evaluate(equation, scope);
|
return mathjs.evaluate(equation, scope);
|
||||||
};
|
};
|
||||||
@@ -369,7 +333,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
*/
|
*/
|
||||||
private totalEquationSectionParser = (
|
private totalEquationSectionParser = (
|
||||||
accumulatedSections: ICashFlowSchemaSection[],
|
accumulatedSections: ICashFlowSchemaSection[],
|
||||||
sectionSchema: ICashFlowSchemaTotalSection
|
sectionSchema: ICashFlowSchemaTotalSection,
|
||||||
): ICashFlowStatementTotalSection => {
|
): ICashFlowStatementTotalSection => {
|
||||||
const mappedSectionsById = this.transformSectionsToMap(accumulatedSections);
|
const mappedSectionsById = this.transformSectionsToMap(accumulatedSections);
|
||||||
const nodesTotalById = this.sectionsMapToTotal(mappedSectionsById);
|
const nodesTotalById = this.sectionsMapToTotal(mappedSectionsById);
|
||||||
@@ -381,13 +345,13 @@ export default class CashFlowStatement extends compose(
|
|||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
R.curry(this.assocTotalEquationDatePeriods)(
|
R.curry(this.assocTotalEquationDatePeriods)(
|
||||||
mappedSectionsById,
|
mappedSectionsById,
|
||||||
sectionSchema.equation
|
sectionSchema.equation,
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)({
|
)({
|
||||||
sectionType: ICashFlowStatementSectionType.TOTAL,
|
sectionType: ICashFlowStatementSectionType.TOTAL,
|
||||||
id: sectionSchema.id,
|
id: sectionSchema.id,
|
||||||
label: this.i18n.__(sectionSchema.label),
|
label: this.i18n.t(sectionSchema.label),
|
||||||
total: this.getTotalAmountMeta(total),
|
total: this.getTotalAmountMeta(total),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -409,7 +373,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
*/
|
*/
|
||||||
private cashAccountMetaMapper = (
|
private cashAccountMetaMapper = (
|
||||||
relation: ICashFlowSchemaAccountRelation,
|
relation: ICashFlowSchemaAccountRelation,
|
||||||
account: ModelObject<Account>
|
account: ModelObject<Account>,
|
||||||
): ICashFlowStatementAccountMeta => {
|
): ICashFlowStatementAccountMeta => {
|
||||||
const cashToDate = this.beginningCashFrom(this.query.fromDate);
|
const cashToDate = this.beginningCashFrom(this.query.fromDate);
|
||||||
|
|
||||||
@@ -430,8 +394,8 @@ export default class CashFlowStatement extends compose(
|
|||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
this.assocCashAtBeginningAccountDatePeriods
|
this.assocCashAtBeginningAccountDatePeriods,
|
||||||
)
|
),
|
||||||
)(node);
|
)(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -441,7 +405,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
* @returns {ICashFlowStatementAccountMeta[]}
|
||||||
*/
|
*/
|
||||||
private getCashAccountsBySchemaRelation = (
|
private getCashAccountsBySchemaRelation = (
|
||||||
relation: ICashFlowSchemaAccountRelation
|
relation: ICashFlowSchemaAccountRelation,
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
): ICashFlowStatementAccountMeta[] => {
|
||||||
const accounts = this.accountByTypeMap.get(relation.type) || [];
|
const accounts = this.accountByTypeMap.get(relation.type) || [];
|
||||||
const accountMetaMapper = R.curry(this.cashAccountMetaMapper)(relation);
|
const accountMetaMapper = R.curry(this.cashAccountMetaMapper)(relation);
|
||||||
@@ -454,7 +418,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
* @returns {ICashFlowStatementAccountMeta[]}
|
||||||
*/
|
*/
|
||||||
private getCashAccountsBySchemaRelations = (
|
private getCashAccountsBySchemaRelations = (
|
||||||
relations: ICashFlowSchemaAccountRelation[]
|
relations: ICashFlowSchemaAccountRelation[],
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
): ICashFlowStatementAccountMeta[] => {
|
||||||
return R.concat(...R.map(this.getCashAccountsBySchemaRelation)(relations));
|
return R.concat(...R.map(this.getCashAccountsBySchemaRelation)(relations));
|
||||||
};
|
};
|
||||||
@@ -465,7 +429,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @return {ICashFlowCashBeginningNode}
|
* @return {ICashFlowCashBeginningNode}
|
||||||
*/
|
*/
|
||||||
private cashAtBeginningSectionParser = (
|
private cashAtBeginningSectionParser = (
|
||||||
nodeSchema: ICashFlowSchemaSection
|
nodeSchema: ICashFlowSchemaSection,
|
||||||
): ICashFlowCashBeginningNode => {
|
): ICashFlowCashBeginningNode => {
|
||||||
const { accountsRelations } = nodeSchema;
|
const { accountsRelations } = nodeSchema;
|
||||||
const children = this.getCashAccountsBySchemaRelations(accountsRelations);
|
const children = this.getCashAccountsBySchemaRelations(accountsRelations);
|
||||||
@@ -474,15 +438,15 @@ export default class CashFlowStatement extends compose(
|
|||||||
const node = {
|
const node = {
|
||||||
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING,
|
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING,
|
||||||
id: nodeSchema.id,
|
id: nodeSchema.id,
|
||||||
label: this.i18n.__(nodeSchema.label),
|
label: this.i18n.t(nodeSchema.label),
|
||||||
children,
|
children,
|
||||||
total: this.getTotalAmountMeta(total),
|
total: this.getTotalAmountMeta(total),
|
||||||
};
|
};
|
||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
this.assocCashAtBeginningDatePeriods
|
this.assocCashAtBeginningDatePeriods,
|
||||||
)
|
),
|
||||||
)(node);
|
)(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -493,31 +457,31 @@ export default class CashFlowStatement extends compose(
|
|||||||
*/
|
*/
|
||||||
private schemaSectionParser = (
|
private schemaSectionParser = (
|
||||||
schemaNode: ICashFlowSchemaSection,
|
schemaNode: ICashFlowSchemaSection,
|
||||||
children
|
children,
|
||||||
): ICashFlowSchemaSection | ICashFlowStatementSection => {
|
): ICashFlowSchemaSection | ICashFlowStatementSection => {
|
||||||
return R.compose(
|
return R.compose(
|
||||||
// Accounts node.
|
// Accounts node.
|
||||||
R.when(
|
R.when(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS),
|
||||||
this.accountsSectionParser
|
this.accountsSectionParser,
|
||||||
),
|
),
|
||||||
// Net income node.
|
// Net income node.
|
||||||
R.when(
|
R.when(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.NET_INCOME),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.NET_INCOME),
|
||||||
this.netIncomeSectionMapper
|
this.netIncomeSectionMapper,
|
||||||
),
|
),
|
||||||
// Cash at beginning node.
|
// Cash at beginning node.
|
||||||
R.when(
|
R.when(
|
||||||
this.isSchemaSectionType(
|
this.isSchemaSectionType(
|
||||||
ICashFlowStatementSectionType.CASH_AT_BEGINNING
|
ICashFlowStatementSectionType.CASH_AT_BEGINNING,
|
||||||
),
|
),
|
||||||
this.cashAtBeginningSectionParser
|
this.cashAtBeginningSectionParser,
|
||||||
),
|
),
|
||||||
// Aggregate node. (that has no section type).
|
// Aggregate node. (that has no section type).
|
||||||
R.when(
|
R.when(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
||||||
this.regularSectionParser(children)
|
this.regularSectionParser(children),
|
||||||
)
|
),
|
||||||
)(schemaNode);
|
)(schemaNode);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -534,14 +498,14 @@ export default class CashFlowStatement extends compose(
|
|||||||
key: number,
|
key: number,
|
||||||
parentValue: ICashFlowSchemaSection[],
|
parentValue: ICashFlowSchemaSection[],
|
||||||
context,
|
context,
|
||||||
accumulatedSections: (ICashFlowSchemaSection | ICashFlowStatementSection)[]
|
accumulatedSections: (ICashFlowSchemaSection | ICashFlowStatementSection)[],
|
||||||
): ICashFlowSchemaSection | ICashFlowStatementSection => {
|
): ICashFlowSchemaSection | ICashFlowStatementSection => {
|
||||||
return R.compose(
|
return R.compose(
|
||||||
// Total equation section.
|
// Total equation section.
|
||||||
R.when(
|
R.when(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.TOTAL),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.TOTAL),
|
||||||
R.curry(this.totalEquationSectionParser)(accumulatedSections)
|
R.curry(this.totalEquationSectionParser)(accumulatedSections),
|
||||||
)
|
),
|
||||||
)(section);
|
)(section);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -551,7 +515,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementSection[]}
|
* @returns {ICashFlowStatementSection[]}
|
||||||
*/
|
*/
|
||||||
private schemaSectionsParser = (
|
private schemaSectionsParser = (
|
||||||
schema: ICashFlowSchemaSection[]
|
schema: ICashFlowSchemaSection[],
|
||||||
): ICashFlowStatementSection[] => {
|
): ICashFlowStatementSection[] => {
|
||||||
return this.mapNodesDeepReverse(schema, this.schemaSectionParser);
|
return this.mapNodesDeepReverse(schema, this.schemaSectionParser);
|
||||||
};
|
};
|
||||||
@@ -572,7 +536,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {(ICashFlowSchemaSection | ICashFlowStatementSection)[]}
|
* @returns {(ICashFlowSchemaSection | ICashFlowStatementSection)[]}
|
||||||
*/
|
*/
|
||||||
private totalSectionsParser = (
|
private totalSectionsParser = (
|
||||||
sections: (ICashFlowSchemaSection | ICashFlowStatementSection)[]
|
sections: (ICashFlowSchemaSection | ICashFlowStatementSection)[],
|
||||||
): (ICashFlowSchemaSection | ICashFlowStatementSection)[] => {
|
): (ICashFlowSchemaSection | ICashFlowStatementSection)[] => {
|
||||||
return this.reduceNodesDeep(
|
return this.reduceNodesDeep(
|
||||||
sections,
|
sections,
|
||||||
@@ -580,11 +544,11 @@ export default class CashFlowStatement extends compose(
|
|||||||
set(
|
set(
|
||||||
acc,
|
acc,
|
||||||
context.path,
|
context.path,
|
||||||
this.schemaSectionTotalParser(value, key, parentValue, context, acc)
|
this.schemaSectionTotalParser(value, key, parentValue, context, acc),
|
||||||
);
|
);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -597,7 +561,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
private isSectionHasChildren = (
|
private isSectionHasChildren = (
|
||||||
section: ICashFlowStatementSection
|
section: ICashFlowStatementSection,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
return !isEmpty(section.children);
|
return !isEmpty(section.children);
|
||||||
};
|
};
|
||||||
@@ -617,12 +581,12 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
private isAccountsSectionHasChildren = (
|
private isAccountsSectionHasChildren = (
|
||||||
section: ICashFlowStatementSection[]
|
section: ICashFlowStatementSection[],
|
||||||
): boolean => {
|
): boolean => {
|
||||||
return R.ifElse(
|
return R.ifElse(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS),
|
||||||
this.isSectionHasChildren,
|
this.isSectionHasChildren,
|
||||||
R.always(true)
|
R.always(true),
|
||||||
)(section);
|
)(section);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -632,12 +596,12 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
private isAccountLeafNoneZero = (
|
private isAccountLeafNoneZero = (
|
||||||
section: ICashFlowStatementSection[]
|
section: ICashFlowStatementSection[],
|
||||||
): boolean => {
|
): boolean => {
|
||||||
return R.ifElse(
|
return R.ifElse(
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNT),
|
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNT),
|
||||||
this.isSectionNoneZero,
|
this.isSectionNoneZero,
|
||||||
R.always(true)
|
R.always(true),
|
||||||
)(section);
|
)(section);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -647,7 +611,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementSection[]}
|
* @returns {ICashFlowStatementSection[]}
|
||||||
*/
|
*/
|
||||||
private filterNoneZeroAccountsLeafs = (
|
private filterNoneZeroAccountsLeafs = (
|
||||||
sections: ICashFlowStatementSection[]
|
sections: ICashFlowStatementSection[],
|
||||||
): ICashFlowStatementSection[] => {
|
): ICashFlowStatementSection[] => {
|
||||||
return this.filterNodesDeep(sections, this.isAccountLeafNoneZero);
|
return this.filterNodesDeep(sections, this.isAccountLeafNoneZero);
|
||||||
};
|
};
|
||||||
@@ -658,7 +622,7 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementSection[]}
|
* @returns {ICashFlowStatementSection[]}
|
||||||
*/
|
*/
|
||||||
private filterNoneChildrenSections = (
|
private filterNoneChildrenSections = (
|
||||||
sections: ICashFlowStatementSection[]
|
sections: ICashFlowStatementSection[],
|
||||||
): ICashFlowStatementSection[] => {
|
): ICashFlowStatementSection[] => {
|
||||||
return this.filterNodesDeep(sections, this.isAccountsSectionHasChildren);
|
return this.filterNodesDeep(sections, this.isAccountsSectionHasChildren);
|
||||||
};
|
};
|
||||||
@@ -669,11 +633,11 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowStatementSection[]}
|
* @returns {ICashFlowStatementSection[]}
|
||||||
*/
|
*/
|
||||||
private filterReportData = (
|
private filterReportData = (
|
||||||
sections: ICashFlowStatementSection[]
|
sections: ICashFlowStatementSection[],
|
||||||
): ICashFlowStatementSection[] => {
|
): ICashFlowStatementSection[] => {
|
||||||
return R.compose(
|
return R.compose(
|
||||||
this.filterNoneChildrenSections,
|
this.filterNoneChildrenSections,
|
||||||
this.filterNoneZeroAccountsLeafs
|
this.filterNoneZeroAccountsLeafs,
|
||||||
)(sections);
|
)(sections);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -683,15 +647,15 @@ export default class CashFlowStatement extends compose(
|
|||||||
* @returns {ICashFlowSchemaSection[]}
|
* @returns {ICashFlowSchemaSection[]}
|
||||||
*/
|
*/
|
||||||
private schemaParser = (
|
private schemaParser = (
|
||||||
schema: ICashFlowSchemaSection[]
|
schema: ICashFlowSchemaSection[],
|
||||||
): ICashFlowSchemaSection[] => {
|
): ICashFlowSchemaSection[] => {
|
||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.query.noneTransactions || this.query.noneZero),
|
R.always(this.query.noneTransactions || this.query.noneZero),
|
||||||
this.filterReportData
|
this.filterReportData,
|
||||||
),
|
),
|
||||||
this.totalSectionsParser,
|
this.totalSectionsParser,
|
||||||
this.schemaSectionsParser
|
this.schemaSectionsParser,
|
||||||
)(schema);
|
)(schema);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,11 +14,19 @@ import {
|
|||||||
import { IFormatNumberSettings } from '../../types/Report.types';
|
import { IFormatNumberSettings } from '../../types/Report.types';
|
||||||
import { dateRangeFromToCollection } from '@/utils/date-range-collection';
|
import { dateRangeFromToCollection } from '@/utils/date-range-collection';
|
||||||
import { accumSum } from '@/utils/accum-sum';
|
import { accumSum } from '@/utils/accum-sum';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
|
||||||
export const CashFlowStatementDatePeriods = (Base) =>
|
export const CashFlowStatementDatePeriods = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
class extends Base {
|
class extends Base {
|
||||||
dateRangeSet: IDateRange[];
|
dateRangeSet: IDateRange[];
|
||||||
query: ICashFlowStatementQuery;
|
query: ICashFlowStatementQuery;
|
||||||
|
netIncomeLedger: Ledger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize date range set.
|
* Initialize date range set.
|
||||||
@@ -27,7 +35,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
this.dateRangeSet = dateRangeFromToCollection(
|
this.dateRangeSet = dateRangeFromToCollection(
|
||||||
this.query.fromDate,
|
this.query.fromDate,
|
||||||
this.query.toDate,
|
this.query.toDate,
|
||||||
this.comparatorDateType
|
this.comparatorDateType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +50,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
total: number,
|
total: number,
|
||||||
fromDate: Date,
|
fromDate: Date,
|
||||||
toDate: Date,
|
toDate: Date,
|
||||||
overrideSettings: IFormatNumberSettings = {}
|
overrideSettings: IFormatNumberSettings = {},
|
||||||
): ICashFlowDatePeriod => {
|
): ICashFlowDatePeriod => {
|
||||||
return this.getDatePeriodMeta(total, fromDate, toDate, {
|
return this.getDatePeriodMeta(total, fromDate, toDate, {
|
||||||
money: true,
|
money: true,
|
||||||
@@ -61,7 +69,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
total: number,
|
total: number,
|
||||||
fromDate: Date,
|
fromDate: Date,
|
||||||
toDate: Date,
|
toDate: Date,
|
||||||
overrideSettings?: IFormatNumberSettings
|
overrideSettings?: IFormatNumberSettings,
|
||||||
): ICashFlowDatePeriod => {
|
): ICashFlowDatePeriod => {
|
||||||
return {
|
return {
|
||||||
fromDate: this.getDateMeta(fromDate),
|
fromDate: this.getDateMeta(fromDate),
|
||||||
@@ -80,10 +88,10 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public getNetIncomeDateRange = (fromDate: Date, toDate: Date) => {
|
public getNetIncomeDateRange = (fromDate: Date, toDate: Date) => {
|
||||||
// Mapping income/expense accounts ids.
|
// Mapping income/expense accounts ids.
|
||||||
const incomeAccountsIds = this.getAccountsIdsByType(
|
const incomeAccountsIds = this.getAccountsIdsByType(
|
||||||
ACCOUNT_ROOT_TYPE.INCOME
|
ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
);
|
);
|
||||||
const expenseAccountsIds = this.getAccountsIdsByType(
|
const expenseAccountsIds = this.getAccountsIdsByType(
|
||||||
ACCOUNT_ROOT_TYPE.EXPENSE
|
ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
);
|
);
|
||||||
// Income closing balance.
|
// Income closing balance.
|
||||||
const incomeClosingBalance = accumSum(incomeAccountsIds, (id) =>
|
const incomeClosingBalance = accumSum(incomeAccountsIds, (id) =>
|
||||||
@@ -91,7 +99,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
.whereFromDate(fromDate)
|
.whereFromDate(fromDate)
|
||||||
.whereToDate(toDate)
|
.whereToDate(toDate)
|
||||||
.whereAccountId(id)
|
.whereAccountId(id)
|
||||||
.getClosingBalance()
|
.getClosingBalance(),
|
||||||
);
|
);
|
||||||
// Expense closing balance.
|
// Expense closing balance.
|
||||||
const expenseClosingBalance = accumSum(expenseAccountsIds, (id) =>
|
const expenseClosingBalance = accumSum(expenseAccountsIds, (id) =>
|
||||||
@@ -99,7 +107,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
.whereToDate(toDate)
|
.whereToDate(toDate)
|
||||||
.whereFromDate(fromDate)
|
.whereFromDate(fromDate)
|
||||||
.whereAccountId(id)
|
.whereAccountId(id)
|
||||||
.getClosingBalance()
|
.getClosingBalance(),
|
||||||
);
|
);
|
||||||
// Net income = income - expenses.
|
// Net income = income - expenses.
|
||||||
const netIncome = incomeClosingBalance - expenseClosingBalance;
|
const netIncome = incomeClosingBalance - expenseClosingBalance;
|
||||||
@@ -115,12 +123,12 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public getNetIncomeDatePeriod = (dateRange): ICashFlowDatePeriod => {
|
public getNetIncomeDatePeriod = (dateRange): ICashFlowDatePeriod => {
|
||||||
const total = this.getNetIncomeDateRange(
|
const total = this.getNetIncomeDateRange(
|
||||||
dateRange.fromDate,
|
dateRange.fromDate,
|
||||||
dateRange.toDate
|
dateRange.toDate,
|
||||||
);
|
);
|
||||||
return this.getDatePeriodMeta(
|
return this.getDatePeriodMeta(
|
||||||
total,
|
total,
|
||||||
dateRange.fromDate,
|
dateRange.fromDate,
|
||||||
dateRange.toDate
|
dateRange.toDate,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,7 +139,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @returns {ICashFlowDatePeriod[]}
|
* @returns {ICashFlowDatePeriod[]}
|
||||||
*/
|
*/
|
||||||
public getNetIncomeDatePeriods = (
|
public getNetIncomeDatePeriods = (
|
||||||
section: ICashFlowStatementNetIncomeSection
|
section: ICashFlowStatementNetIncomeSection,
|
||||||
): ICashFlowDatePeriod[] => {
|
): ICashFlowDatePeriod[] => {
|
||||||
return this.dateRangeSet.map(this.getNetIncomeDatePeriod.bind(this));
|
return this.dateRangeSet.map(this.getNetIncomeDatePeriod.bind(this));
|
||||||
};
|
};
|
||||||
@@ -142,7 +150,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @returns {ICashFlowStatementNetIncomeSection}
|
* @returns {ICashFlowStatementNetIncomeSection}
|
||||||
*/
|
*/
|
||||||
public assocPeriodsToNetIncomeNode = (
|
public assocPeriodsToNetIncomeNode = (
|
||||||
section: ICashFlowStatementNetIncomeSection
|
section: ICashFlowStatementNetIncomeSection,
|
||||||
): ICashFlowStatementNetIncomeSection => {
|
): ICashFlowStatementNetIncomeSection => {
|
||||||
const incomeDatePeriods = this.getNetIncomeDatePeriods(section);
|
const incomeDatePeriods = this.getNetIncomeDatePeriods(section);
|
||||||
return R.assoc('periods', incomeDatePeriods, section);
|
return R.assoc('periods', incomeDatePeriods, section);
|
||||||
@@ -158,7 +166,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public getAccountTotalDateRange = (
|
public getAccountTotalDateRange = (
|
||||||
node: ICashFlowStatementAccountSection,
|
node: ICashFlowStatementAccountSection,
|
||||||
fromDate: Date,
|
fromDate: Date,
|
||||||
toDate: Date
|
toDate: Date,
|
||||||
): number => {
|
): number => {
|
||||||
const closingBalance = this.ledger
|
const closingBalance = this.ledger
|
||||||
.whereFromDate(fromDate)
|
.whereFromDate(fromDate)
|
||||||
@@ -179,7 +187,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public getAccountTotalDatePeriod = (
|
public getAccountTotalDatePeriod = (
|
||||||
node: ICashFlowStatementAccountSection,
|
node: ICashFlowStatementAccountSection,
|
||||||
fromDate: Date,
|
fromDate: Date,
|
||||||
toDate: Date
|
toDate: Date,
|
||||||
): ICashFlowDatePeriod => {
|
): ICashFlowDatePeriod => {
|
||||||
const total = this.getAccountTotalDateRange(node, fromDate, toDate);
|
const total = this.getAccountTotalDateRange(node, fromDate, toDate);
|
||||||
return this.getDatePeriodMeta(total, fromDate, toDate);
|
return this.getDatePeriodMeta(total, fromDate, toDate);
|
||||||
@@ -191,13 +199,13 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @return {ICashFlowDatePeriod[]}
|
* @return {ICashFlowDatePeriod[]}
|
||||||
*/
|
*/
|
||||||
public getAccountDatePeriods = (
|
public getAccountDatePeriods = (
|
||||||
node: ICashFlowStatementAccountSection
|
node: ICashFlowStatementAccountSection,
|
||||||
): ICashFlowDatePeriod[] => {
|
): ICashFlowDatePeriod[] => {
|
||||||
return this.getNodeDatePeriods(
|
return this.getNodeDatePeriods(
|
||||||
node,
|
node,
|
||||||
this.getAccountTotalDatePeriod.bind(this)
|
this.getAccountTotalDatePeriod.bind(this),
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes `periods` property to account node.
|
* Writes `periods` property to account node.
|
||||||
@@ -205,11 +213,11 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @return {ICashFlowStatementAccountSection}
|
* @return {ICashFlowStatementAccountSection}
|
||||||
*/
|
*/
|
||||||
public assocPeriodsToAccountNode = (
|
public assocPeriodsToAccountNode = (
|
||||||
node: ICashFlowStatementAccountSection
|
node: ICashFlowStatementAccountSection,
|
||||||
): ICashFlowStatementAccountSection => {
|
): ICashFlowStatementAccountSection => {
|
||||||
const datePeriods = this.getAccountDatePeriods(node);
|
const datePeriods = this.getAccountDatePeriods(node);
|
||||||
return R.assoc('periods', datePeriods, node);
|
return R.assoc('periods', datePeriods, node);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Aggregate node -------------------------
|
// Aggregate node -------------------------
|
||||||
/**
|
/**
|
||||||
@@ -218,10 +226,10 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
*/
|
*/
|
||||||
public getChildrenTotalPeriodByIndex = (
|
public getChildrenTotalPeriodByIndex = (
|
||||||
node: ICashFlowStatementSection,
|
node: ICashFlowStatementSection,
|
||||||
index: number
|
index: number,
|
||||||
): number => {
|
): number => {
|
||||||
return sumBy(node.children, `periods[${index}].total.amount`);
|
return sumBy(node.children, `periods[${index}].total.amount`);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve date period meta of the given node index.
|
* Retrieve date period meta of the given node index.
|
||||||
@@ -234,7 +242,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
node: ICashFlowStatementSection,
|
node: ICashFlowStatementSection,
|
||||||
index: number,
|
index: number,
|
||||||
fromDate: Date,
|
fromDate: Date,
|
||||||
toDate: Date
|
toDate: Date,
|
||||||
) {
|
) {
|
||||||
const total = this.getChildrenTotalPeriodByIndex(node, index);
|
const total = this.getChildrenTotalPeriodByIndex(node, index);
|
||||||
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
||||||
@@ -246,15 +254,15 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
*/
|
*/
|
||||||
public getAggregateNodeDatePeriods(node: ICashFlowStatementSection) {
|
public getAggregateNodeDatePeriods(node: ICashFlowStatementSection) {
|
||||||
const getChildrenTotalPeriodMetaByIndex = R.curry(
|
const getChildrenTotalPeriodMetaByIndex = R.curry(
|
||||||
this.getChildrenTotalPeriodMetaByIndex.bind(this)
|
this.getChildrenTotalPeriodMetaByIndex.bind(this),
|
||||||
)(node);
|
)(node);
|
||||||
|
|
||||||
return this.dateRangeSet.map((dateRange, index) =>
|
return this.dateRangeSet.map((dateRange, index) =>
|
||||||
getChildrenTotalPeriodMetaByIndex(
|
getChildrenTotalPeriodMetaByIndex(
|
||||||
index,
|
index,
|
||||||
dateRange.fromDate,
|
dateRange.fromDate,
|
||||||
dateRange.toDate
|
dateRange.toDate,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +272,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @return {ICashFlowStatementSection}
|
* @return {ICashFlowStatementSection}
|
||||||
*/
|
*/
|
||||||
public assocPeriodsToAggregateNode = (
|
public assocPeriodsToAggregateNode = (
|
||||||
node: ICashFlowStatementSection
|
node: ICashFlowStatementSection,
|
||||||
): ICashFlowStatementSection => {
|
): ICashFlowStatementSection => {
|
||||||
const datePeriods = this.getAggregateNodeDatePeriods(node);
|
const datePeriods = this.getAggregateNodeDatePeriods(node);
|
||||||
return R.assoc('periods', datePeriods, node);
|
return R.assoc('periods', datePeriods, node);
|
||||||
@@ -274,11 +282,11 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
|
|
||||||
public sectionsMapToTotalPeriod = (
|
public sectionsMapToTotalPeriod = (
|
||||||
mappedSections: { [key: number]: any },
|
mappedSections: { [key: number]: any },
|
||||||
index
|
index,
|
||||||
) => {
|
) => {
|
||||||
return mapValues(
|
return mapValues(
|
||||||
mappedSections,
|
mappedSections,
|
||||||
(node) => get(node, `periods[${index}].total.amount`) || 0
|
(node) => get(node, `periods[${index}].total.amount`) || 0,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -291,7 +299,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public getTotalEquationDatePeriods = (
|
public getTotalEquationDatePeriods = (
|
||||||
node: ICashFlowSchemaTotalSection,
|
node: ICashFlowSchemaTotalSection,
|
||||||
equation: string,
|
equation: string,
|
||||||
nodesTable
|
nodesTable,
|
||||||
): ICashFlowDatePeriod[] => {
|
): ICashFlowDatePeriod[] => {
|
||||||
return this.getNodeDatePeriods(node, (node, fromDate, toDate, index) => {
|
return this.getNodeDatePeriods(node, (node, fromDate, toDate, index) => {
|
||||||
const periodScope = this.sectionsMapToTotalPeriod(nodesTable, index);
|
const periodScope = this.sectionsMapToTotalPeriod(nodesTable, index);
|
||||||
@@ -309,12 +317,12 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public assocTotalEquationDatePeriods = (
|
public assocTotalEquationDatePeriods = (
|
||||||
nodesTable: any,
|
nodesTable: any,
|
||||||
equation: string,
|
equation: string,
|
||||||
node: ICashFlowSchemaTotalSection
|
node: ICashFlowSchemaTotalSection,
|
||||||
): ICashFlowStatementTotalSection => {
|
): ICashFlowStatementTotalSection => {
|
||||||
const datePeriods = this.getTotalEquationDatePeriods(
|
const datePeriods = this.getTotalEquationDatePeriods(
|
||||||
node,
|
node,
|
||||||
equation,
|
equation,
|
||||||
nodesTable
|
nodesTable,
|
||||||
);
|
);
|
||||||
|
|
||||||
return R.assoc('periods', datePeriods, node);
|
return R.assoc('periods', datePeriods, node);
|
||||||
@@ -345,7 +353,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public getBeginningCashAccountDateRange = (
|
public getBeginningCashAccountDateRange = (
|
||||||
node: ICashFlowStatementSection,
|
node: ICashFlowStatementSection,
|
||||||
fromDate: Date,
|
fromDate: Date,
|
||||||
toDate: Date
|
toDate: Date,
|
||||||
) => {
|
) => {
|
||||||
const cashToDate = this.beginningCashFrom(fromDate);
|
const cashToDate = this.beginningCashFrom(fromDate);
|
||||||
|
|
||||||
@@ -365,12 +373,12 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
public getBeginningCashDatePeriod = (
|
public getBeginningCashDatePeriod = (
|
||||||
node: ICashFlowStatementSection,
|
node: ICashFlowStatementSection,
|
||||||
fromDate: Date,
|
fromDate: Date,
|
||||||
toDate: Date
|
toDate: Date,
|
||||||
) => {
|
) => {
|
||||||
const total = this.getBeginningCashAccountDateRange(
|
const total = this.getBeginningCashAccountDateRange(
|
||||||
node,
|
node,
|
||||||
fromDate,
|
fromDate,
|
||||||
toDate
|
toDate,
|
||||||
);
|
);
|
||||||
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
||||||
};
|
};
|
||||||
@@ -381,7 +389,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @return {ICashFlowDatePeriod}
|
* @return {ICashFlowDatePeriod}
|
||||||
*/
|
*/
|
||||||
public getBeginningCashAccountPeriods = (
|
public getBeginningCashAccountPeriods = (
|
||||||
node: ICashFlowStatementSection
|
node: ICashFlowStatementSection,
|
||||||
): ICashFlowDatePeriod => {
|
): ICashFlowDatePeriod => {
|
||||||
return this.getNodeDatePeriods(node, this.getBeginningCashDatePeriod);
|
return this.getNodeDatePeriods(node, this.getBeginningCashDatePeriod);
|
||||||
};
|
};
|
||||||
@@ -392,7 +400,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @return {ICashFlowStatementSection}
|
* @return {ICashFlowStatementSection}
|
||||||
*/
|
*/
|
||||||
public assocCashAtBeginningDatePeriods = (
|
public assocCashAtBeginningDatePeriods = (
|
||||||
node: ICashFlowStatementSection
|
node: ICashFlowStatementSection,
|
||||||
): ICashFlowStatementSection => {
|
): ICashFlowStatementSection => {
|
||||||
const datePeriods = this.getAggregateNodeDatePeriods(node);
|
const datePeriods = this.getAggregateNodeDatePeriods(node);
|
||||||
return R.assoc('periods', datePeriods, node);
|
return R.assoc('periods', datePeriods, node);
|
||||||
@@ -404,7 +412,7 @@ export const CashFlowStatementDatePeriods = (Base) =>
|
|||||||
* @return {ICashFlowStatementSection}
|
* @return {ICashFlowStatementSection}
|
||||||
*/
|
*/
|
||||||
public assocCashAtBeginningAccountDatePeriods = (
|
public assocCashAtBeginningAccountDatePeriods = (
|
||||||
node: ICashFlowStatementSection
|
node: ICashFlowStatementSection,
|
||||||
): ICashFlowStatementSection => {
|
): ICashFlowStatementSection => {
|
||||||
const datePeriods = this.getBeginningCashAccountPeriods(node);
|
const datePeriods = this.getBeginningCashAccountPeriods(node);
|
||||||
return R.assoc('periods', datePeriods, node);
|
return R.assoc('periods', datePeriods, node);
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
import { ICashFlowStatementQuery } from './Cashflow.types';
|
import { ICashFlowStatementQuery } from './Cashflow.types';
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
import { ModelObject } from 'objection';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CashFlowRepository {
|
export class CashFlowRepository {
|
||||||
|
/**
|
||||||
|
* @param {typeof Account} accountModel - Account model.
|
||||||
|
* @param {typeof AccountTransaction} accountTransactionModel - Account transaction model.
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(Account.name)
|
@Inject(Account.name)
|
||||||
private readonly accountModel: typeof Account,
|
private readonly accountModel: typeof Account,
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { ModelObject } from 'objection';
|
import { ModelObject } from 'objection';
|
||||||
import moment from 'moment';
|
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
ICashFlowStatementQuery,
|
ICashFlowStatementQuery,
|
||||||
ICashFlowStatementDOO,
|
ICashFlowStatementDOO,
|
||||||
} from './Cashflow.types';
|
} from './Cashflow.types';
|
||||||
import CashFlowStatement from './CashFlow';
|
import { CashFlowStatement } from './CashFlow';
|
||||||
import { CashflowSheetMeta } from './CashflowSheetMeta';
|
import { CashflowSheetMeta } from './CashflowSheetMeta';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { CashFlowRepository } from './CashFlowRepository';
|
import { CashFlowRepository } from './CashFlowRepository';
|
||||||
@@ -58,8 +57,7 @@ export class CashFlowStatementService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the cash flow sheet statement.
|
* Retrieve the cash flow sheet statement.
|
||||||
* @param {number} tenantId
|
* @param {ICashFlowStatementQuery} query - Cashflow query.
|
||||||
* @param {ICashFlowStatementQuery} query
|
|
||||||
* @returns {Promise<ICashFlowStatementDOO>}
|
* @returns {Promise<ICashFlowStatementDOO>}
|
||||||
*/
|
*/
|
||||||
public async cashFlow(
|
public async cashFlow(
|
||||||
@@ -67,7 +65,7 @@ export class CashFlowStatementService {
|
|||||||
): Promise<ICashFlowStatementDOO> {
|
): Promise<ICashFlowStatementDOO> {
|
||||||
// Retrieve all accounts on the storage.
|
// Retrieve all accounts on the storage.
|
||||||
const accounts = await this.cashFlowRepo.cashFlowAccounts();
|
const accounts = await this.cashFlowRepo.cashFlowAccounts();
|
||||||
const tenant = await this.tenancyContext.getTenant();
|
const tenant = await this.tenancyContext.getTenant(true);
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
...getDefaultCashflowQuery(),
|
...getDefaultCashflowQuery(),
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { isEmpty, } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
import {
|
import {
|
||||||
ICashFlowStatementSection,
|
ICashFlowStatementSection,
|
||||||
ICashFlowStatementSectionType,
|
ICashFlowStatementSectionType,
|
||||||
@@ -27,14 +28,15 @@ const DISPLAY_COLUMNS_BY = {
|
|||||||
|
|
||||||
export class CashFlowTable {
|
export class CashFlowTable {
|
||||||
private report: ICashFlowStatementDOO;
|
private report: ICashFlowStatementDOO;
|
||||||
private i18n;
|
private i18n: I18nService;
|
||||||
private dateRangeSet: IDateRange[];
|
private dateRangeSet: IDateRange[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
* @param {ICashFlowStatement} reportStatement
|
* @param {ICashFlowStatement} reportStatement - Statement.
|
||||||
|
* @param {I18nService} i18n - I18n service.
|
||||||
*/
|
*/
|
||||||
constructor(reportStatement: ICashFlowStatementDOO, i18n) {
|
constructor(reportStatement: ICashFlowStatementDOO, i18n: I18nService) {
|
||||||
this.report = reportStatement;
|
this.report = reportStatement;
|
||||||
this.i18n = i18n;
|
this.i18n = i18n;
|
||||||
this.dateRangeSet = [];
|
this.dateRangeSet = [];
|
||||||
@@ -325,7 +327,6 @@ export class CashFlowTable {
|
|||||||
],
|
],
|
||||||
conditions,
|
conditions,
|
||||||
);
|
);
|
||||||
|
|
||||||
return R.compose(R.cond(conditionsPairs))(dateRange);
|
return R.compose(R.cond(conditionsPairs))(dateRange);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -341,7 +342,7 @@ export class CashFlowTable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detarmines the given column type is the current.
|
* Determines the given column type is the current.
|
||||||
* @reutrns {boolean}
|
* @reutrns {boolean}
|
||||||
*/
|
*/
|
||||||
private isDisplayColumnsBy = (displayColumnsType: string): Boolean => {
|
private isDisplayColumnsBy = (displayColumnsType: string): Boolean => {
|
||||||
@@ -349,7 +350,7 @@ export class CashFlowTable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detarmines whether the given display columns type is the current.
|
* Determines whether the given display columns type is the current.
|
||||||
* @param {string} displayColumnsBy
|
* @param {string} displayColumnsBy
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
@@ -363,7 +364,7 @@ export class CashFlowTable {
|
|||||||
*/
|
*/
|
||||||
public tableColumns = (): ITableColumn[] => {
|
public tableColumns = (): ITableColumn[] => {
|
||||||
return R.compose(
|
return R.compose(
|
||||||
R.concat([{ key: 'name', label: this.i18n.__('Account name') }]),
|
R.concat([{ key: 'name', label: this.i18n.t('Account name') }]),
|
||||||
R.when(
|
R.when(
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
||||||
R.concat(this.datePeriodsColumns()),
|
R.concat(this.datePeriodsColumns()),
|
||||||
@@ -3,8 +3,10 @@ import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
|
|||||||
import { ICashFlowStatementQuery } from './Cashflow.types';
|
import { ICashFlowStatementQuery } from './Cashflow.types';
|
||||||
import { AcceptType } from '@/constants/accept-type';
|
import { AcceptType } from '@/constants/accept-type';
|
||||||
import { CashflowSheetApplication } from './CashflowSheetApplication';
|
import { CashflowSheetApplication } from './CashflowSheetApplication';
|
||||||
|
import { PublicRoute } from '@/modules/Auth/Jwt.guard';
|
||||||
|
|
||||||
@Controller('reports/cashflow')
|
@Controller('reports/cashflow-statement')
|
||||||
|
@PublicRoute()
|
||||||
export class CashflowController {
|
export class CashflowController {
|
||||||
constructor(private readonly cashflowSheetApp: CashflowSheetApplication) {}
|
constructor(private readonly cashflowSheetApp: CashflowSheetApplication) {}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
||||||
import {
|
import {
|
||||||
@@ -20,8 +20,8 @@ import { CashflowSheetApplication } from './CashflowSheetApplication';
|
|||||||
CashflowExportInjectable,
|
CashflowExportInjectable,
|
||||||
CashflowTableInjectable,
|
CashflowTableInjectable,
|
||||||
CashflowSheetApplication,
|
CashflowSheetApplication,
|
||||||
TenancyContext
|
TenancyContext,
|
||||||
],
|
],
|
||||||
controllers: [CashflowController],
|
controllers: [CashflowController],
|
||||||
})
|
})
|
||||||
export class CashflowReportModule {}
|
export class CashflowStatementModule {}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { map } from 'lodash';
|
||||||
|
import { Account } from "@/modules/Accounts/models/Account.model";
|
||||||
|
import { ICashFlowStatementQuery } from './Cashflow.types';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
|
||||||
|
export class CashflowStatementBase extends FinancialSheet {
|
||||||
|
readonly accountsByRootType: Map<string, Account[]>;
|
||||||
|
readonly query: ICashFlowStatementQuery;
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
// # GENERAL UTILITIES
|
||||||
|
// --------------------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieve the expense accounts ids.
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
public getAccountsIdsByType = (accountType: string): number[] => {
|
||||||
|
const expenseAccounts = this.accountsByRootType.get(accountType);
|
||||||
|
const expenseAccountsIds = map(expenseAccounts, 'id');
|
||||||
|
|
||||||
|
return expenseAccountsIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines the given display columns by type.
|
||||||
|
* @param {string} displayColumnsBy
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isDisplayColumnsBy = (displayColumnsBy: string): boolean => {
|
||||||
|
return this.query.displayColumnsType === displayColumnsBy;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjustments the given amount.
|
||||||
|
* @param {string} direction
|
||||||
|
* @param {number} amount -
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
public amountAdjustment = (direction: 'mines' | 'plus', amount): number => {
|
||||||
|
return R.when(
|
||||||
|
R.always(R.equals(direction, 'mines')),
|
||||||
|
R.multiply(-1),
|
||||||
|
)(amount);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ export class CashflowTableInjectable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the cash flow table.
|
* Retrieves the cash flow table.
|
||||||
|
* @param {ICashFlowStatementQuery} query -
|
||||||
* @returns {Promise<ICashFlowStatementTable>}
|
* @returns {Promise<ICashFlowStatementTable>}
|
||||||
*/
|
*/
|
||||||
public async table(
|
public async table(
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as moment from 'moment';
|
||||||
import { ICashFlowStatementQuery } from "./Cashflow.types";
|
import { ICashFlowStatementQuery } from "./Cashflow.types";
|
||||||
|
|
||||||
export const DISPLAY_COLUMNS_BY = {
|
export const DISPLAY_COLUMNS_BY = {
|
||||||
@@ -15,7 +15,7 @@ export class JournalSheetApplication {
|
|||||||
/**
|
/**
|
||||||
* Retrieves the journal sheet.
|
* Retrieves the journal sheet.
|
||||||
* @param {IJournalReportQuery} query
|
* @param {IJournalReportQuery} query
|
||||||
* @returns {}
|
* @returns {Promise<IJournalSheet>}
|
||||||
*/
|
*/
|
||||||
public sheet(query: IJournalReportQuery) {
|
public sheet(query: IJournalReportQuery) {
|
||||||
return this.journalSheet.journalSheet(query);
|
return this.journalSheet.journalSheet(query);
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import {
|
||||||
|
ProfitLossAggregateNodeId,
|
||||||
|
ProfitLossNodeType,
|
||||||
|
IProfitLossSchemaNode,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSchema } from '../../common/FinancialSchema';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
|
||||||
|
export const ProfitLossShema = <T extends GConstructor<FinancialSheet>>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialSchema)(Base) {
|
||||||
|
/**
|
||||||
|
* Retrieves the report schema.
|
||||||
|
* @returns {IProfitLossSchemaNode[]}
|
||||||
|
*/
|
||||||
|
getSchema = (): IProfitLossSchemaNode[] => {
|
||||||
|
return getProfitLossSheetSchema();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves P&L sheet schema.
|
||||||
|
* @returns {IProfitLossSchemaNode}
|
||||||
|
*/
|
||||||
|
export const getProfitLossSheetSchema = (): IProfitLossSchemaNode[] => [
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.INCOME,
|
||||||
|
name: 'profit_loss_sheet.income',
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS,
|
||||||
|
accountsTypes: [ACCOUNT_TYPE.INCOME],
|
||||||
|
alwaysShow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.COS,
|
||||||
|
name: 'profit_loss_sheet.cost_of_sales',
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS,
|
||||||
|
accountsTypes: [ACCOUNT_TYPE.COST_OF_GOODS_SOLD],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.GROSS_PROFIT,
|
||||||
|
name: 'profit_loss_sheet.gross_profit',
|
||||||
|
nodeType: ProfitLossNodeType.EQUATION,
|
||||||
|
equation: `${ProfitLossAggregateNodeId.INCOME} - ${ProfitLossAggregateNodeId.COS}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.EXPENSES,
|
||||||
|
name: 'profit_loss_sheet.expenses',
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS,
|
||||||
|
accountsTypes: [ACCOUNT_TYPE.EXPENSE],
|
||||||
|
alwaysShow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.NET_OPERATING_INCOME,
|
||||||
|
name: 'profit_loss_sheet.net_operating_income',
|
||||||
|
nodeType: ProfitLossNodeType.EQUATION,
|
||||||
|
equation: `${ProfitLossAggregateNodeId.GROSS_PROFIT} - ${ProfitLossAggregateNodeId.EXPENSES}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.OTHER_INCOME,
|
||||||
|
name: 'profit_loss_sheet.other_income',
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS,
|
||||||
|
accountsTypes: [ACCOUNT_TYPE.OTHER_INCOME],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.OTHER_EXPENSES,
|
||||||
|
name: 'profit_loss_sheet.other_expenses',
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS,
|
||||||
|
accountsTypes: [ACCOUNT_TYPE.OTHER_EXPENSE],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ProfitLossAggregateNodeId.NET_INCOME,
|
||||||
|
name: 'profit_loss_sheet.net_income',
|
||||||
|
nodeType: ProfitLossNodeType.EQUATION,
|
||||||
|
equation: `${ProfitLossAggregateNodeId.NET_OPERATING_INCOME} + ${ProfitLossAggregateNodeId.OTHER_INCOME} - ${ProfitLossAggregateNodeId.OTHER_EXPENSES}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { Response } from 'express';
|
||||||
|
import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
|
||||||
|
import { IProfitLossSheetQuery } from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossSheetApplication } from './ProfitLossSheetApplication';
|
||||||
|
import { AcceptType } from '@/constants/accept-type';
|
||||||
|
import { PublicRoute } from '@/modules/Auth/Jwt.guard';
|
||||||
|
|
||||||
|
@Controller('/reports/profit-loss-sheet')
|
||||||
|
@PublicRoute()
|
||||||
|
export class ProfitLossSheetController {
|
||||||
|
constructor(
|
||||||
|
private readonly profitLossSheetApp: ProfitLossSheetApplication,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {string} acceptHeader
|
||||||
|
*/
|
||||||
|
@Get('/')
|
||||||
|
async profitLossSheet(
|
||||||
|
@Query() query: IProfitLossSheetQuery,
|
||||||
|
@Res() res: Response,
|
||||||
|
@Headers('accept') acceptHeader: string,
|
||||||
|
) {
|
||||||
|
// Retrieves the csv format.
|
||||||
|
if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
||||||
|
const sheet = await this.profitLossSheetApp.csv(query);
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
||||||
|
res.setHeader('Content-Type', 'text/csv');
|
||||||
|
|
||||||
|
return res.send(sheet);
|
||||||
|
// Retrieves the json table format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
||||||
|
const table = await this.profitLossSheetApp.table(query);
|
||||||
|
|
||||||
|
return res.status(200).send(table);
|
||||||
|
// Retrieves the xlsx format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
||||||
|
const sheet = await this.profitLossSheetApp.xlsx(query);
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Type',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
);
|
||||||
|
return res.send(sheet);
|
||||||
|
// Retrieves the json format.
|
||||||
|
} else if (acceptHeader.includes(AcceptType.ApplicationJson)) {
|
||||||
|
const pdfContent = await this.profitLossSheetApp.pdf(query);
|
||||||
|
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Length': pdfContent.length,
|
||||||
|
});
|
||||||
|
return res.send(pdfContent);
|
||||||
|
} else {
|
||||||
|
const sheet = await this.profitLossSheetApp.sheet(query);
|
||||||
|
|
||||||
|
return res.status(200).send(sheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ProfitLossSheetService } from './ProfitLossSheetService';
|
||||||
|
import { ProfitLossSheetExportInjectable } from './ProfitLossSheetExportInjectable';
|
||||||
|
import { ProfitLossTablePdfInjectable } from './ProfitLossTablePdfInjectable';
|
||||||
|
import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable';
|
||||||
|
import { ProfitLossSheetMeta } from './ProfitLossSheetMeta';
|
||||||
|
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||||
|
import { AccountsModule } from '@/modules/Accounts/Accounts.module';
|
||||||
|
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import { ProfitLossSheetController } from './ProfitLossSheet.controller';
|
||||||
|
import { ProfitLossSheetApplication } from './ProfitLossSheetApplication';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [FinancialSheetCommonModule, AccountsModule],
|
||||||
|
controllers: [ProfitLossSheetController],
|
||||||
|
providers: [
|
||||||
|
ProfitLossSheetApplication,
|
||||||
|
ProfitLossSheetService,
|
||||||
|
ProfitLossSheetExportInjectable,
|
||||||
|
ProfitLossTablePdfInjectable,
|
||||||
|
ProfitLossSheetTableInjectable,
|
||||||
|
ProfitLossSheetMeta,
|
||||||
|
ProfitLossSheetRepository,
|
||||||
|
TenancyContext,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ProfitLossSheetModule {}
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import {
|
||||||
|
ProfitLossNodeType,
|
||||||
|
IProfitLossSheetEquationNode,
|
||||||
|
IProfitLossEquationSchemaNode,
|
||||||
|
IProfitLossSheetAccountsNode,
|
||||||
|
IProfitLossAccountsSchemaNode,
|
||||||
|
IProfitLossSchemaNode,
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
IProfitLossSheetAccountNode,
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossShema } from './ProfitLossSchema';
|
||||||
|
import { ProfitLossSheetPercentage } from './ProfitLossSheetPercentage';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||||
|
import { ProfitLossSheetBase } from './ProfitLossSheetBase';
|
||||||
|
import { ProfitLossSheetDatePeriods } from './ProfitLossSheetDatePeriods';
|
||||||
|
import { ProfitLossSheetPreviousYear } from './ProfitLossSheetPreviousYear';
|
||||||
|
import { ProfitLossSheetPreviousPeriod } from './ProfitLossSheetPreviousPeriod';
|
||||||
|
import { ProfitLossSheetFilter } from './ProfitLossSheetFilter';
|
||||||
|
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
|
||||||
|
import { FinancialEvaluateEquation } from '../../common/FinancialEvaluateEquation';
|
||||||
|
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
|
import { flatToNestedArray } from '@/utils/flat-to-nested-array';
|
||||||
|
|
||||||
|
export default class ProfitLossSheet extends R.pipe(
|
||||||
|
ProfitLossSheetPreviousYear,
|
||||||
|
ProfitLossSheetPreviousPeriod,
|
||||||
|
ProfitLossSheetPercentage,
|
||||||
|
ProfitLossSheetDatePeriods,
|
||||||
|
ProfitLossSheetFilter,
|
||||||
|
ProfitLossShema,
|
||||||
|
ProfitLossSheetBase,
|
||||||
|
FinancialDateRanges,
|
||||||
|
FinancialEvaluateEquation,
|
||||||
|
FinancialSheetStructure,
|
||||||
|
)(FinancialSheet) {
|
||||||
|
/**
|
||||||
|
* Profit/Loss sheet query.
|
||||||
|
* @param {ProfitLossSheetQuery}
|
||||||
|
*/
|
||||||
|
readonly query: ProfitLossSheetQuery;
|
||||||
|
/**
|
||||||
|
* @param {string}
|
||||||
|
*/
|
||||||
|
readonly comparatorDateType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization's base currency.
|
||||||
|
* @param {string}
|
||||||
|
*/
|
||||||
|
readonly baseCurrency: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profit/Loss repository.
|
||||||
|
* @param {ProfitLossSheetRepository}
|
||||||
|
*/
|
||||||
|
readonly repository: ProfitLossSheetRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I18n service.
|
||||||
|
* @param {I18nService}
|
||||||
|
*/
|
||||||
|
readonly i18n: I18nService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {IProfitLossSheetQuery} query -
|
||||||
|
* @param {IAccount[]} accounts -
|
||||||
|
* @param {IJournalPoster} transactionsJournal -
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
repository: ProfitLossSheetRepository,
|
||||||
|
query: IProfitLossSheetQuery,
|
||||||
|
i18n: I18nService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.query = new ProfitLossSheetQuery(query);
|
||||||
|
this.repository = repository;
|
||||||
|
this.numberFormat = this.query.query.numberFormat;
|
||||||
|
this.i18n = i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the sheet account node from the given account.
|
||||||
|
* @param {ModelObject<Account>} account
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private accountNodeMapper = (
|
||||||
|
account: ModelObject<Account>,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
// Retrieves the children account ids of the given account id.
|
||||||
|
const childrenAccountIds = this.repository.accountsGraph.dependenciesOf(
|
||||||
|
account.id,
|
||||||
|
);
|
||||||
|
// Concat the children and the given account id.
|
||||||
|
const accountIds = R.uniq(R.append(account.id, childrenAccountIds));
|
||||||
|
|
||||||
|
// Retrieves the closing balance of the account included children accounts.
|
||||||
|
const total = this.repository.totalAccountsLedger
|
||||||
|
.whereAccountsIds(accountIds)
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: account.id,
|
||||||
|
name: account.name,
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNT,
|
||||||
|
total: this.getAmountMeta(total),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose account node.
|
||||||
|
* @param {ModelObject<Account>} node
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private accountNodeCompose = (
|
||||||
|
account: ModelObject<Account>,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
this.previousPeriodAccountNodeCompose,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
this.previousYearAccountNodeCompose,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isDatePeriodsColumnsType,
|
||||||
|
this.assocAccountNodeDatePeriod,
|
||||||
|
),
|
||||||
|
this.accountNodeMapper,
|
||||||
|
)(account);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves report accounts nodes by the given accounts types.
|
||||||
|
* @param {string[]} types
|
||||||
|
* @returns {IBalanceSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private getAccountsNodesByTypes = (
|
||||||
|
types: string[],
|
||||||
|
): IProfitLossSheetAccountNode[] => {
|
||||||
|
const accounts = this.repository.getAccountsByType(types);
|
||||||
|
const accountsTree = flatToNestedArray(accounts, {
|
||||||
|
id: 'id',
|
||||||
|
parentId: 'parentAccountId',
|
||||||
|
});
|
||||||
|
return this.mapNodesDeep(accountsTree, this.accountNodeCompose);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapps the accounts schema node to report node.
|
||||||
|
* @param {IProfitLossSchemaNode} node
|
||||||
|
* @returns {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private accountsSchemaNodeMapper = (
|
||||||
|
node: IProfitLossAccountsSchemaNode,
|
||||||
|
): IProfitLossSheetNode => {
|
||||||
|
// Retrieve accounts node by the given types.
|
||||||
|
const children = this.getAccountsNodesByTypes(node.accountsTypes);
|
||||||
|
|
||||||
|
// Retrieve the total of the given nodes.
|
||||||
|
const total = this.getTotalOfNodes(children);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
name: this.i18n.t(node.name),
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS,
|
||||||
|
total: this.getTotalAmountMeta(total),
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts schema node composer.
|
||||||
|
* @param {IProfitLossSchemaNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountsNode}
|
||||||
|
*/
|
||||||
|
private accountsSchemaNodeCompose = (
|
||||||
|
node: IProfitLossSchemaNode,
|
||||||
|
): IProfitLossSheetAccountsNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
this.previousPeriodAggregateNodeCompose,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
this.previousYearAggregateNodeCompose,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isDatePeriodsColumnsType,
|
||||||
|
this.assocAggregateDatePeriod,
|
||||||
|
),
|
||||||
|
this.accountsSchemaNodeMapper,
|
||||||
|
)(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equation schema node parser.
|
||||||
|
* @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes -
|
||||||
|
* @param {IProfitLossEquationSchemaNode} node -
|
||||||
|
* @param {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
private equationSchemaNodeParser = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[],
|
||||||
|
node: IProfitLossEquationSchemaNode,
|
||||||
|
): IProfitLossSheetEquationNode => {
|
||||||
|
const tableNodes = this.getNodesTableForEvaluating(
|
||||||
|
'total.amount',
|
||||||
|
accNodes,
|
||||||
|
);
|
||||||
|
// Evaluate the given equation.
|
||||||
|
const total = this.evaluateEquation(node.equation, tableNodes);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
name: this.i18n.t(node.name),
|
||||||
|
nodeType: ProfitLossNodeType.EQUATION,
|
||||||
|
total: this.getTotalAmountMeta(total),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equation schema node composer.
|
||||||
|
* @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes -
|
||||||
|
* @param {IProfitLossSchemaNode} node -
|
||||||
|
* @returns {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
private equationSchemaNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[],
|
||||||
|
node: IProfitLossEquationSchemaNode,
|
||||||
|
): IProfitLossSheetEquationNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
this.previousPeriodEquationNodeCompose(accNodes, node.equation),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
this.previousYearEquationNodeCompose(accNodes, node.equation),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isDatePeriodsColumnsType,
|
||||||
|
this.assocEquationNodeDatePeriod(accNodes, node.equation),
|
||||||
|
),
|
||||||
|
this.equationSchemaNodeParser(accNodes),
|
||||||
|
)(node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses accounts schema node to report node.
|
||||||
|
* @param {IProfitLossSchemaNode} schemaNode
|
||||||
|
* @returns {IProfitLossSheetNode | IProfitLossSchemaNode}
|
||||||
|
*/
|
||||||
|
private accountsSchemaNodeMap = (
|
||||||
|
schemaNode: IProfitLossSchemaNode,
|
||||||
|
): IProfitLossSheetNode | IProfitLossSchemaNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeType(ProfitLossNodeType.ACCOUNTS),
|
||||||
|
this.accountsSchemaNodeCompose,
|
||||||
|
),
|
||||||
|
)(schemaNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composes schema equation node to report node.
|
||||||
|
* @param {IProfitLossSheetNode | IProfitLossSchemaNode} node
|
||||||
|
* @param {number} key
|
||||||
|
* @param {IProfitLossSheetNode | IProfitLossSchemaNode} parentValue
|
||||||
|
* @param {(IProfitLossSheetNode | IProfitLossSchemaNode)[]} accNodes
|
||||||
|
* @param context
|
||||||
|
* @returns {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
private reportSchemaEquationNodeCompose = (
|
||||||
|
node: IProfitLossSheetNode | IProfitLossSchemaNode,
|
||||||
|
key: number,
|
||||||
|
parentValue: IProfitLossSheetNode | IProfitLossSchemaNode,
|
||||||
|
accNodes: (IProfitLossSheetNode | IProfitLossSchemaNode)[],
|
||||||
|
context,
|
||||||
|
): IProfitLossSheetEquationNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeType(ProfitLossNodeType.EQUATION),
|
||||||
|
this.equationSchemaNodeCompose(accNodes),
|
||||||
|
),
|
||||||
|
)(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses schema accounts nodes.
|
||||||
|
* @param {IProfitLossSchemaNode[]}
|
||||||
|
* @returns {(IProfitLossSheetNode | IProfitLossSchemaNode)[]}
|
||||||
|
*/
|
||||||
|
private reportSchemaAccountsNodesCompose = (
|
||||||
|
schemaNodes: IProfitLossSchemaNode[],
|
||||||
|
): (IProfitLossSheetNode | IProfitLossSchemaNode)[] => {
|
||||||
|
return this.mapNodesDeep(schemaNodes, this.accountsSchemaNodeMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses schema equation nodes.
|
||||||
|
* @param {(IProfitLossSheetNode | IProfitLossSchemaNode)[]} nodes
|
||||||
|
* @returns {(IProfitLossSheetNode | IProfitLossSchemaNode)[]}
|
||||||
|
*/
|
||||||
|
private reportSchemaEquationNodesCompose = (
|
||||||
|
nodes: (IProfitLossSheetNode | IProfitLossSchemaNode)[],
|
||||||
|
): (IProfitLossSheetNode | IProfitLossSchemaNode)[] => {
|
||||||
|
return this.mapAccNodesDeep(nodes, this.reportSchemaEquationNodeCompose);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve profit/loss report data.
|
||||||
|
* @return {IProfitLossSheetStatement}
|
||||||
|
*/
|
||||||
|
public reportData = (): Array<IProfitLossSheetNode> => {
|
||||||
|
const schema = this.getSchema();
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
this.reportFilterPlugin,
|
||||||
|
this.reportRowsPercentageCompose,
|
||||||
|
this.reportColumnsPerentageCompose,
|
||||||
|
this.reportSchemaEquationNodesCompose,
|
||||||
|
this.reportSchemaAccountsNodesCompose,
|
||||||
|
)(schema);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
import * as moment from 'moment';
|
||||||
|
import {
|
||||||
|
IFinancialSheetBranchesQuery,
|
||||||
|
IFinancialSheetCommonMeta,
|
||||||
|
INumberFormatQuery,
|
||||||
|
} from '../../types/Report.types';
|
||||||
|
import { IFinancialTable } from '../../types/Table.types';
|
||||||
|
|
||||||
|
export enum ProfitLossAggregateNodeId {
|
||||||
|
INCOME = 'INCOME',
|
||||||
|
COS = 'COST_OF_SALES',
|
||||||
|
GROSS_PROFIT = 'GROSS_PROFIT',
|
||||||
|
EXPENSES = 'EXPENSES',
|
||||||
|
OTHER_INCOME = 'OTHER_INCOME',
|
||||||
|
OTHER_EXPENSES = 'OTHER_EXPENSES',
|
||||||
|
OPERATING_PROFIT = 'OPERATING_PROFIT',
|
||||||
|
NET_OTHER_INCOME = 'NET_OTHER_INCOME',
|
||||||
|
NET_INCOME = 'NET_INCOME',
|
||||||
|
NET_OPERATING_INCOME = 'NET_OPERATING_INCOME',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProfitLossNodeType {
|
||||||
|
EQUATION = 'EQUATION',
|
||||||
|
ACCOUNTS = 'ACCOUNTS',
|
||||||
|
ACCOUNT = 'ACCOUNT',
|
||||||
|
AGGREGATE = 'AGGREGATE',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FinancialDateMeta {
|
||||||
|
date: Date;
|
||||||
|
formattedDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFinancialNodeWithPreviousPeriod {
|
||||||
|
previousPeriodFromDate?: FinancialDateMeta;
|
||||||
|
previousPeriodToDate?: FinancialDateMeta;
|
||||||
|
|
||||||
|
previousPeriod?: IProfitLossSheetTotal;
|
||||||
|
previousPeriodChange?: IProfitLossSheetTotal;
|
||||||
|
previousPeriodPercentage?: IProfitLossSheetPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFinancialNodeWithPreviousYear {
|
||||||
|
previousYearFromDate: FinancialDateMeta;
|
||||||
|
previousYearToDate: FinancialDateMeta;
|
||||||
|
|
||||||
|
previousYear?: IProfitLossSheetTotal;
|
||||||
|
previousYearChange?: IProfitLossSheetTotal;
|
||||||
|
previousYearPercentage?: IProfitLossSheetPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFinancialCommonNode {
|
||||||
|
total: IProfitLossSheetTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFinancialCommonHorizDatePeriodNode {
|
||||||
|
fromDate: FinancialDateMeta;
|
||||||
|
toDate: FinancialDateMeta;
|
||||||
|
total: IProfitLossSheetTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossSheetQuery extends IFinancialSheetBranchesQuery {
|
||||||
|
basis: string;
|
||||||
|
|
||||||
|
fromDate: moment.MomentInput;
|
||||||
|
toDate: moment.MomentInput;
|
||||||
|
|
||||||
|
numberFormat: INumberFormatQuery;
|
||||||
|
noneZero: boolean;
|
||||||
|
noneTransactions: boolean;
|
||||||
|
accountsIds: number[];
|
||||||
|
|
||||||
|
displayColumnsType: 'total' | 'date_periods';
|
||||||
|
displayColumnsBy: string;
|
||||||
|
|
||||||
|
percentageColumn: boolean;
|
||||||
|
percentageRow: boolean;
|
||||||
|
|
||||||
|
percentageIncome: boolean;
|
||||||
|
percentageExpense: boolean;
|
||||||
|
|
||||||
|
previousPeriod: boolean;
|
||||||
|
previousPeriodAmountChange: boolean;
|
||||||
|
previousPeriodPercentageChange: boolean;
|
||||||
|
|
||||||
|
previousYear: boolean;
|
||||||
|
previousYearAmountChange: boolean;
|
||||||
|
previousYearPercentageChange: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossSheetTotal {
|
||||||
|
amount: number;
|
||||||
|
formattedAmount: string;
|
||||||
|
currencyCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossSheetPercentage {
|
||||||
|
amount: number;
|
||||||
|
formattedAmount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossHorizontalDatePeriodNode
|
||||||
|
extends IFinancialNodeWithPreviousYear,
|
||||||
|
IFinancialNodeWithPreviousPeriod {
|
||||||
|
fromDate: FinancialDateMeta;
|
||||||
|
toDate: FinancialDateMeta;
|
||||||
|
|
||||||
|
total: IProfitLossSheetTotal;
|
||||||
|
|
||||||
|
percentageRow?: IProfitLossSheetPercentage;
|
||||||
|
percentageColumn?: IProfitLossSheetPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossSheetCommonNode
|
||||||
|
extends IFinancialNodeWithPreviousYear,
|
||||||
|
IFinancialNodeWithPreviousPeriod {
|
||||||
|
id: ProfitLossAggregateNodeId;
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
children?: IProfitLossSheetNode[];
|
||||||
|
|
||||||
|
total: IProfitLossSheetTotal;
|
||||||
|
horizontalTotals?: IProfitLossHorizontalDatePeriodNode[];
|
||||||
|
|
||||||
|
percentageRow?: IProfitLossSheetPercentage;
|
||||||
|
percentageColumn?: IProfitLossSheetPercentage;
|
||||||
|
}
|
||||||
|
export interface IProfitLossSheetAccountNode
|
||||||
|
extends IProfitLossSheetCommonNode {
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNT;
|
||||||
|
}
|
||||||
|
export interface IProfitLossSheetEquationNode
|
||||||
|
extends IProfitLossSheetCommonNode {
|
||||||
|
nodeType: ProfitLossNodeType.EQUATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossSheetAccountsNode
|
||||||
|
extends IProfitLossSheetCommonNode {
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IProfitLossSheetNode =
|
||||||
|
| IProfitLossSheetAccountsNode
|
||||||
|
| IProfitLossSheetEquationNode
|
||||||
|
| IProfitLossSheetAccountNode;
|
||||||
|
|
||||||
|
export interface IProfitLossSheetMeta extends IFinancialSheetCommonMeta {
|
||||||
|
formattedDateRange: string;
|
||||||
|
formattedFromDate: string;
|
||||||
|
formattedToDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------
|
||||||
|
// # SCHEMA NODES
|
||||||
|
// ------------------------------------------------
|
||||||
|
export interface IProfitLossCommonSchemaNode {
|
||||||
|
id: ProfitLossAggregateNodeId;
|
||||||
|
name: string;
|
||||||
|
nodeType: ProfitLossNodeType;
|
||||||
|
children?: IProfitLossSchemaNode[];
|
||||||
|
alwaysShow?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossEquationSchemaNode
|
||||||
|
extends IProfitLossCommonSchemaNode {
|
||||||
|
nodeType: ProfitLossNodeType.EQUATION;
|
||||||
|
equation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossAccountsSchemaNode
|
||||||
|
extends IProfitLossCommonSchemaNode {
|
||||||
|
nodeType: ProfitLossNodeType.ACCOUNTS;
|
||||||
|
accountsTypes: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IProfitLossSchemaNode =
|
||||||
|
| IProfitLossCommonSchemaNode
|
||||||
|
| IProfitLossAccountsSchemaNode
|
||||||
|
| IProfitLossEquationSchemaNode;
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// # Table
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
export enum ProfitLossSheetRowType {
|
||||||
|
AGGREGATE = 'AGGREGATE',
|
||||||
|
ACCOUNTS = 'ACCOUNTS',
|
||||||
|
ACCOUNT = 'ACCOUNT',
|
||||||
|
TOTAL = 'TOTAL',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfitLossSheetTable extends IFinancialTable {
|
||||||
|
meta: IProfitLossSheetMeta;
|
||||||
|
query: IProfitLossSheetQuery;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ProfitLossSheetExportInjectable } from './ProfitLossSheetExportInjectable';
|
||||||
|
import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable';
|
||||||
|
import {
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
IProfitLossSheetTable,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossTablePdfInjectable } from './ProfitLossTablePdfInjectable';
|
||||||
|
import { ProfitLossSheetService } from './ProfitLossSheetService';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProfitLossSheetApplication {
|
||||||
|
constructor(
|
||||||
|
private readonly profitLossTable: ProfitLossSheetTableInjectable,
|
||||||
|
private readonly profitLossExport: ProfitLossSheetExportInjectable,
|
||||||
|
private readonly profitLossSheet: ProfitLossSheetService,
|
||||||
|
private readonly profitLossPdf: ProfitLossTablePdfInjectable,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
public sheet(query: IProfitLossSheetQuery) {
|
||||||
|
return this.profitLossSheet.profitLossSheet(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet table format.
|
||||||
|
* @param {IProfitLossSheetQuery} query - Profit/loss sheet query.
|
||||||
|
* @returns {Promise<IProfitLossSheetTable>}
|
||||||
|
*/
|
||||||
|
public table(query: IProfitLossSheetQuery): Promise<IProfitLossSheetTable> {
|
||||||
|
return this.profitLossTable.table(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet in csv format.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
public csv(query: IProfitLossSheetQuery): Promise<string> {
|
||||||
|
return this.profitLossExport.csv(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet in xlsx format.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public xlsx(query: IProfitLossSheetQuery): Promise<Buffer> {
|
||||||
|
return this.profitLossExport.xlsx(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet in pdf format.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public pdf(query: IProfitLossSheetQuery): Promise<Buffer> {
|
||||||
|
return this.profitLossPdf.pdf(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { TOTAL_NODE_TYPES } from './constants';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
|
||||||
|
export const ProfitLossSheetBase = <T extends GConstructor<FinancialSheet>>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends Base {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public isNodeType = R.curry((type: string, node) => {
|
||||||
|
return node.nodeType === type;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected isNodeTypeIn = R.curry((types: string[], node) => {
|
||||||
|
return types.indexOf(node.nodeType) !== -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected findNodeById = R.curry((id, nodes) => {
|
||||||
|
return this.findNodeDeep(nodes, (node) => node.id === id);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
isNodeTotal = (node) => {
|
||||||
|
return this.isNodeTypeIn(TOTAL_NODE_TYPES, node);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { sumBy } from 'lodash';
|
||||||
|
import {
|
||||||
|
IProfitLossHorizontalDatePeriodNode,
|
||||||
|
IProfitLossSheetAccountNode,
|
||||||
|
IProfitLossSheetAccountsNode,
|
||||||
|
IProfitLossSheetCommonNode,
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
|
||||||
|
import { IDateRange } from '../../types/Report.types';
|
||||||
|
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
|
||||||
|
export const ProfitLossSheetDatePeriods = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialDatePeriods)(Base) {
|
||||||
|
query: ProfitLossSheetQuery;
|
||||||
|
repository: ProfitLossSheetRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the date periods based on the report query.
|
||||||
|
* @returns {IDateRange[]}
|
||||||
|
*/
|
||||||
|
get datePeriods(): IDateRange[] {
|
||||||
|
return this.getDateRanges(
|
||||||
|
this.query.fromDate,
|
||||||
|
this.query.toDate,
|
||||||
|
this.query.displayColumnsBy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the date periods of the given node based on the report query.
|
||||||
|
* @param {IProfitLossSheetCommonNode} node
|
||||||
|
* @param {Function} callback
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
protected getReportNodeDatePeriods = (
|
||||||
|
node: IProfitLossSheetCommonNode,
|
||||||
|
callback: (
|
||||||
|
node: IProfitLossSheetCommonNode,
|
||||||
|
fromDate: Date,
|
||||||
|
toDate: Date,
|
||||||
|
index: number,
|
||||||
|
) => any,
|
||||||
|
) => {
|
||||||
|
return this.getNodeDatePeriods(
|
||||||
|
this.query.fromDate,
|
||||||
|
this.query.toDate,
|
||||||
|
this.query.displayColumnsBy,
|
||||||
|
node,
|
||||||
|
callback,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// # Account Nodes.
|
||||||
|
// --------------------------
|
||||||
|
/**
|
||||||
|
* Retrieve account node date period total.
|
||||||
|
* @param {IProfitLossSheetAccount} node
|
||||||
|
* @param {Date} fromDate
|
||||||
|
* @param {Date} toDate
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
private getAccountNodeDatePeriodTotal = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
fromDate: Date,
|
||||||
|
toDate: Date,
|
||||||
|
) => {
|
||||||
|
const periodTotal = this.repository.periodsAccountsLedger
|
||||||
|
.whereAccountId(node.id)
|
||||||
|
.whereFromDate(fromDate)
|
||||||
|
.whereToDate(toDate)
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account node date period.
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
public getAccountNodeDatePeriod = (node: IProfitLossSheetAccountNode) => {
|
||||||
|
return this.getReportNodeDatePeriods(
|
||||||
|
node,
|
||||||
|
this.getAccountNodeDatePeriodTotal,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account date periods to the given account node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
public assocAccountNodeDatePeriod = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
const datePeriods = this.getAccountNodeDatePeriod(node);
|
||||||
|
|
||||||
|
return R.assoc('horizontalTotals', datePeriods, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// # Aggregate nodes.
|
||||||
|
// --------------------------
|
||||||
|
/**
|
||||||
|
* Retrieves sumation of the given aggregate node children totals.
|
||||||
|
* @param {IProfitLossSheetAccountsNode} node
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
private getAggregateDatePeriodIndexTotal = (
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
index: number,
|
||||||
|
): number => {
|
||||||
|
return sumBy(node.children, `horizontalTotals[${index}].total.amount`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetAccount} node
|
||||||
|
* @param {Date} fromDate
|
||||||
|
* @param {Date} toDate
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {IProfitLossSheetAccount}
|
||||||
|
*/
|
||||||
|
private getAggregateNodeDatePeriodTotal = R.curry(
|
||||||
|
(
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
fromDate: Date,
|
||||||
|
toDate: Date,
|
||||||
|
index: number,
|
||||||
|
): IProfitLossHorizontalDatePeriodNode => {
|
||||||
|
const periodTotal = this.getAggregateDatePeriodIndexTotal(node, index);
|
||||||
|
|
||||||
|
return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves aggregate horizontal date periods.
|
||||||
|
* @param {IProfitLossSheetAccountsNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountsNode}
|
||||||
|
*/
|
||||||
|
private getAggregateNodeDatePeriod = (
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
): IProfitLossHorizontalDatePeriodNode[] => {
|
||||||
|
return this.getReportNodeDatePeriods(
|
||||||
|
node,
|
||||||
|
this.getAggregateNodeDatePeriodTotal,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc horizontal date periods to aggregate node.
|
||||||
|
* @param {IProfitLossSheetAccountsNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountsNode}
|
||||||
|
*/
|
||||||
|
protected assocAggregateDatePeriod = (
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
): IProfitLossSheetAccountsNode => {
|
||||||
|
const datePeriods = this.getAggregateNodeDatePeriod(node);
|
||||||
|
|
||||||
|
return R.assoc('horizontalTotals', datePeriods, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// # Equation nodes.
|
||||||
|
// --------------------------
|
||||||
|
/**
|
||||||
|
* Retrieves equation date period node.
|
||||||
|
* @param {IProfitLossSheetNode[]} accNodes
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @param {Date} fromDate
|
||||||
|
* @param {Date} toDate
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {IProfitLossHorizontalDatePeriodNode}
|
||||||
|
*/
|
||||||
|
private getEquationNodeDatePeriod = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
fromDate: Date,
|
||||||
|
toDate: Date,
|
||||||
|
index: number,
|
||||||
|
): IProfitLossHorizontalDatePeriodNode => {
|
||||||
|
const tableNodes = this.getNodesTableForEvaluating(
|
||||||
|
`horizontalTotals[${index}].total.amount`,
|
||||||
|
accNodes,
|
||||||
|
);
|
||||||
|
// Evaluate the given equation.
|
||||||
|
const total = this.evaluateEquation(equation, tableNodes);
|
||||||
|
|
||||||
|
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the equation node date periods.
|
||||||
|
* @param {IProfitLossSheetNode[]} node
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @returns {IProfitLossHorizontalDatePeriodNode[]}
|
||||||
|
*/
|
||||||
|
private getEquationNodeDatePeriods = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
): IProfitLossHorizontalDatePeriodNode[] => {
|
||||||
|
return this.getReportNodeDatePeriods(
|
||||||
|
node,
|
||||||
|
this.getEquationNodeDatePeriod(accNodes, equation),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc equation node date period.
|
||||||
|
* @param {IProfitLossSheetNode[]}
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @returns {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
protected assocEquationNodeDatePeriod = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
): IProfitLossSheetNode => {
|
||||||
|
const periods = this.getEquationNodeDatePeriods(
|
||||||
|
accNodes,
|
||||||
|
equation,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', periods, node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { TableSheet } from '../../common/TableSheet';
|
||||||
|
import { IProfitLossSheetQuery } from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProfitLossSheetExportInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly profitLossSheetTable: ProfitLossSheetTableInjectable,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet in XLSX format.
|
||||||
|
* @param {IProfitLossSheetQuery} query - The profit/loss sheet query.
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async xlsx(query: IProfitLossSheetQuery) {
|
||||||
|
const table = await this.profitLossSheetTable.table(query);
|
||||||
|
|
||||||
|
const tableSheet = new TableSheet(table.table);
|
||||||
|
const tableCsv = tableSheet.convertToXLSX();
|
||||||
|
|
||||||
|
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet in CSV format.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
public async csv(query: IProfitLossSheetQuery): Promise<string> {
|
||||||
|
const table = await this.profitLossSheetTable.table(query);
|
||||||
|
|
||||||
|
const tableSheet = new TableSheet(table.table);
|
||||||
|
const tableCsv = tableSheet.convertToCSV();
|
||||||
|
|
||||||
|
return tableCsv;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import { ProfitLossSheetBase } from './ProfitLossSheetBase';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { FinancialFilter } from '../../common/FinancialFilter';
|
||||||
|
import {
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
ProfitLossNodeType,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||||
|
|
||||||
|
export const ProfitLossSheetFilter = <T extends GConstructor<FinancialSheet>>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialFilter, ProfitLossSheetBase)(Base) {
|
||||||
|
query: ProfitLossSheetQuery;
|
||||||
|
repository: ProfitLossSheetRepository;
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// # Account.
|
||||||
|
// ----------------
|
||||||
|
/**
|
||||||
|
* Filter report node detarmine.
|
||||||
|
* @param {IProfitLossSheetNode} node - Balance sheet node.
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
private accountNoneZeroNodesFilterDetarminer = (
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
): boolean => {
|
||||||
|
return R.ifElse(
|
||||||
|
this.isNodeType(ProfitLossNodeType.ACCOUNT),
|
||||||
|
this.isNodeNoneZero,
|
||||||
|
R.always(true),
|
||||||
|
)(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines account none-transactions node.
|
||||||
|
* @param {IBalanceSheetDataNode} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private accountNoneTransFilterDetarminer = (
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
): boolean => {
|
||||||
|
return R.ifElse(
|
||||||
|
this.isNodeType(ProfitLossNodeType.ACCOUNT),
|
||||||
|
this.isNodeNoneZero,
|
||||||
|
R.always(true),
|
||||||
|
)(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report nodes filter.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes -
|
||||||
|
* @return {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private accountsNoneZeroNodesFilter = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
return this.filterNodesDeep(
|
||||||
|
nodes,
|
||||||
|
this.accountNoneZeroNodesFilterDetarminer,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the accounts none-transactions nodes.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private accountsNoneTransactionsNodesFilter = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
) => {
|
||||||
|
return this.filterNodesDeep(nodes, this.accountNoneTransFilterDetarminer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// # Aggregate.
|
||||||
|
// ----------------
|
||||||
|
/**
|
||||||
|
* Determines aggregate none-children filtering.
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
private aggregateNoneChildrenFilterDetarminer = (
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
): boolean => {
|
||||||
|
const schemaNode = this.getSchemaNodeById(node.id);
|
||||||
|
|
||||||
|
// Determines whether the given node is aggregate node.
|
||||||
|
const isAggregateNode = this.isNodeType(
|
||||||
|
ProfitLossNodeType.ACCOUNTS,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
// Determines if the schema node is always should show.
|
||||||
|
const isSchemaAlwaysShow = get(schemaNode, 'alwaysShow', false);
|
||||||
|
|
||||||
|
// Should node has children if aggregate node or not always show.
|
||||||
|
return isAggregateNode && !isSchemaAlwaysShow
|
||||||
|
? this.isNodeHasChildren(node)
|
||||||
|
: true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters aggregate none-children nodes.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private aggregateNoneChildrenFilter = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
return this.filterNodesDeep2(
|
||||||
|
this.aggregateNoneChildrenFilterDetarminer,
|
||||||
|
nodes,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// # Composers.
|
||||||
|
// ----------------
|
||||||
|
/**
|
||||||
|
* Filters none-zero nodes.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private filterNoneZeroNodesCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
return R.compose(
|
||||||
|
this.aggregateNoneChildrenFilter,
|
||||||
|
this.accountsNoneZeroNodesFilter,
|
||||||
|
)(nodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters none-transactions nodes.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private filterNoneTransNodesCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
return R.compose(
|
||||||
|
this.aggregateNoneChildrenFilter,
|
||||||
|
this.accountsNoneTransactionsNodesFilter,
|
||||||
|
)(nodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supress nodes when total accounts range transactions is empty.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private supressNodesWhenRangeTransactionsEmpty = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
) => {
|
||||||
|
return this.repository.totalAccountsLedger.isEmpty() ? [] : nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose report nodes filtering.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
protected reportFilterPlugin = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
return R.compose(
|
||||||
|
this.supressNodesWhenRangeTransactionsEmpty,
|
||||||
|
R.when(() => this.query.noneZero, this.filterNoneZeroNodesCompose),
|
||||||
|
R.when(
|
||||||
|
() => this.query.noneTransactions,
|
||||||
|
this.filterNoneTransNodesCompose,
|
||||||
|
),
|
||||||
|
)(nodes);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import {
|
||||||
|
IProfitLossSheetMeta,
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProfitLossSheetMeta {
|
||||||
|
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the P/L sheet meta.
|
||||||
|
* @param {IProfitLossSheetQuery} query - P/L sheet query.
|
||||||
|
* @returns {Promise<IBalanceSheetMeta>}
|
||||||
|
*/
|
||||||
|
public async meta(
|
||||||
|
query: IProfitLossSheetQuery,
|
||||||
|
): Promise<IProfitLossSheetMeta> {
|
||||||
|
const commonMeta = await this.financialSheetMeta.meta();
|
||||||
|
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD');
|
||||||
|
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD');
|
||||||
|
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
|
||||||
|
|
||||||
|
const sheetName = 'Cashflow Statement';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...commonMeta,
|
||||||
|
sheetName,
|
||||||
|
formattedFromDate,
|
||||||
|
formattedToDate,
|
||||||
|
formattedDateRange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import {
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
ProfitLossAggregateNodeId,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
|
||||||
|
export const ProfitLossSheetPercentage = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialHorizTotals)(Base) {
|
||||||
|
query: ProfitLossSheetQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc column of percentage attribute to the given node.
|
||||||
|
* @param {IProfitLossSheetNode} netIncomeNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
* @return {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private assocColumnPercentage = R.curry(
|
||||||
|
(
|
||||||
|
propertyPath: string,
|
||||||
|
parentNode: IProfitLossSheetNode,
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
) => {
|
||||||
|
const percentage = this.getPercentageBasis(
|
||||||
|
parentNode.total.amount,
|
||||||
|
node.total.amount,
|
||||||
|
);
|
||||||
|
return R.assoc(
|
||||||
|
propertyPath,
|
||||||
|
this.getPercentageAmountMeta(percentage),
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc column of percentage attribute to the given node.
|
||||||
|
* @param {IProfitLossSheetNode} netIncomeNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
* @return {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private assocColumnTotalPercentage = R.curry(
|
||||||
|
(
|
||||||
|
propertyPath: string,
|
||||||
|
parentNode: IProfitLossSheetNode,
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
) => {
|
||||||
|
const percentage = this.getPercentageBasis(
|
||||||
|
parentNode.total.amount,
|
||||||
|
node.total.amount,
|
||||||
|
);
|
||||||
|
return R.assoc(
|
||||||
|
propertyPath,
|
||||||
|
this.getPercentageTotalAmountMeta(percentage),
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose percentage of columns.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private columnPercentageCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
const netIncomeNode = this.findNodeById(
|
||||||
|
ProfitLossAggregateNodeId.NET_INCOME,
|
||||||
|
nodes,
|
||||||
|
);
|
||||||
|
return this.mapNodesDeep(
|
||||||
|
nodes,
|
||||||
|
this.columnPercentageMapper(netIncomeNode),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose percentage of income.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private incomePercetageCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
const incomeNode = this.findNodeById(
|
||||||
|
ProfitLossAggregateNodeId.INCOME,
|
||||||
|
nodes,
|
||||||
|
);
|
||||||
|
return this.mapNodesDeep(nodes, this.incomePercentageMapper(incomeNode));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private rowPercentageCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
return this.mapNodesDeep(nodes, this.rowPercentageMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode} netIncomeNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
* @return {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private columnPercentageMapper = R.curry(
|
||||||
|
(netIncomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => {
|
||||||
|
const path = 'percentageColumn';
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocColumnPercentageHorizTotals(netIncomeNode),
|
||||||
|
),
|
||||||
|
R.ifElse(
|
||||||
|
this.isNodeTotal,
|
||||||
|
this.assocColumnTotalPercentage(path, netIncomeNode),
|
||||||
|
this.assocColumnPercentage(path, netIncomeNode),
|
||||||
|
),
|
||||||
|
)(node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @returns {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private rowPercentageMap = (
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
): IProfitLossSheetNode => {
|
||||||
|
const path = 'percentageRow';
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.when(this.isNodeHasHorizTotals, this.assocRowPercentageHorizTotals),
|
||||||
|
R.ifElse(
|
||||||
|
this.isNodeTotal,
|
||||||
|
this.assocColumnTotalPercentage(path, node),
|
||||||
|
this.assocColumnPercentage(path, node),
|
||||||
|
),
|
||||||
|
)(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode} incomeNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
* @returns {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private incomePercentageMapper = R.curry(
|
||||||
|
(incomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => {
|
||||||
|
const path = 'percentageIncome';
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocIncomePercentageHorizTotals(incomeNode),
|
||||||
|
),
|
||||||
|
R.ifElse(
|
||||||
|
this.isNodeTotal,
|
||||||
|
this.assocColumnTotalPercentage(path, incomeNode),
|
||||||
|
this.assocColumnPercentage(path, incomeNode),
|
||||||
|
),
|
||||||
|
)(node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode} expenseNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
*/
|
||||||
|
private expensePercentageMapper = R.curry(
|
||||||
|
(expenseNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => {
|
||||||
|
const path = 'percentageExpense';
|
||||||
|
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocExpensePercentageHorizTotals(expenseNode),
|
||||||
|
),
|
||||||
|
R.ifElse(
|
||||||
|
this.isNodeTotal,
|
||||||
|
this.assocColumnTotalPercentage(path, expenseNode),
|
||||||
|
this.assocColumnPercentage(path, expenseNode),
|
||||||
|
),
|
||||||
|
)(node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose percentage of expense.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
private expensesPercentageCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
const expenseNode = this.findNodeById(
|
||||||
|
ProfitLossAggregateNodeId.EXPENSES,
|
||||||
|
nodes,
|
||||||
|
);
|
||||||
|
return this.mapNodesDeep(
|
||||||
|
nodes,
|
||||||
|
this.expensePercentageMapper(expenseNode),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose percentage attributes.
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {IProfitLossSheetNode[]}
|
||||||
|
*/
|
||||||
|
protected reportColumnsPerentageCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): IProfitLossSheetNode[] => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(this.query.isIncomePercentage, this.incomePercetageCompose),
|
||||||
|
R.when(this.query.isColumnPercentage, this.columnPercentageCompose),
|
||||||
|
R.when(this.query.isExpensesPercentage, this.expensesPercentageCompose),
|
||||||
|
R.when(this.query.isRowPercentage, this.rowPercentageCompose),
|
||||||
|
)(nodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {} nodes
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
protected reportRowsPercentageCompose = (nodes) => {
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Horizontal Nodes
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
* Assoc incomer percentage to horizontal totals nodes.
|
||||||
|
* @param {IProfitLossSheetNode} incomeNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
* @returns {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private assocIncomePercentageHorizTotals = R.curry(
|
||||||
|
(incomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => {
|
||||||
|
const horTotalsWithIncomePerc = this.assocPercentageHorizTotals(
|
||||||
|
'percentageIncome',
|
||||||
|
incomeNode,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horTotalsWithIncomePerc, node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc expense percentage to horizontal totals nodes.
|
||||||
|
* @param {IProfitLossSheetNode} expenseNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
* @returns {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private assocExpensePercentageHorizTotals = R.curry(
|
||||||
|
(expenseNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => {
|
||||||
|
const horTotalsWithExpensePerc = this.assocPercentageHorizTotals(
|
||||||
|
'percentageExpense',
|
||||||
|
expenseNode,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horTotalsWithExpensePerc, node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc net income percentage to horizontal totals nodes.
|
||||||
|
* @param {IProfitLossSheetNode} expenseNode -
|
||||||
|
* @param {IProfitLossSheetNode} node -
|
||||||
|
* @returns {IProfitLossSheetNode}
|
||||||
|
*/
|
||||||
|
private assocColumnPercentageHorizTotals = R.curry(
|
||||||
|
(netIncomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => {
|
||||||
|
const horTotalsWithExpensePerc = this.assocPercentageHorizTotals(
|
||||||
|
'percentageColumn',
|
||||||
|
netIncomeNode,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horTotalsWithExpensePerc, node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private assocRowPercentageHorizTotals = R.curry((node) => {
|
||||||
|
const horTotalsWithExpensePerc = this.assocHorizontalPercentageTotals(
|
||||||
|
'percentageRow',
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horTotalsWithExpensePerc, node);
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { sumBy } from 'lodash';
|
||||||
|
import {
|
||||||
|
IProfitLossHorizontalDatePeriodNode,
|
||||||
|
IProfitLossSchemaNode,
|
||||||
|
IProfitLossSheetAccountNode,
|
||||||
|
IProfitLossSheetAccountsNode,
|
||||||
|
IProfitLossSheetEquationNode,
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
|
||||||
|
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||||
|
|
||||||
|
export const ProfitLossSheetPreviousPeriod = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialPreviousPeriod)(Base) {
|
||||||
|
query: ProfitLossSheetQuery;
|
||||||
|
repository: ProfitLossSheetRepository;
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// # Account
|
||||||
|
// ---------------------------
|
||||||
|
/**
|
||||||
|
* Assoc previous period change attribute to account node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
protected assocPreviousPeriodTotalAccountNode = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
const total = this.repository.PPTotalAccountsLedger.whereAccountId(
|
||||||
|
node.id,
|
||||||
|
).getClosingBalance();
|
||||||
|
|
||||||
|
return R.assoc('previousPeriod', this.getAmountMeta(total), node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose previous period account node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
protected previousPeriodAccountNodeCompose = (
|
||||||
|
accountNode: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocPreviousPeriodAccountHorizNodeCompose,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
this.assocPreviousPeriodPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
this.assocPreviousPeriodChangeNode,
|
||||||
|
),
|
||||||
|
this.assocPreviousPeriodTotalAccountNode,
|
||||||
|
)(accountNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// # Aggregate
|
||||||
|
// ---------------------------
|
||||||
|
/**
|
||||||
|
* Assoc previous period total attribute to aggregate node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousPeriodTotalAggregateNode = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
) => {
|
||||||
|
const total = sumBy(node.children, 'previousPeriod.amount');
|
||||||
|
|
||||||
|
return R.assoc('previousPeriod', this.getTotalAmountMeta(total), node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose previous period to aggregate node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
protected previousPeriodAggregateNodeCompose = (
|
||||||
|
accountNode: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocPreviousPeriodAggregateHorizNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
this.assocPreviousPeriodTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
this.assocPreviousPeriodTotalChangeNode,
|
||||||
|
),
|
||||||
|
this.assocPreviousPeriodTotalAggregateNode,
|
||||||
|
)(accountNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// # Equation
|
||||||
|
// --------------------------
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @returns {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousPeriodTotalEquationNode = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetEquationNode,
|
||||||
|
): IProfitLossSheetEquationNode => {
|
||||||
|
const previousPeriodNodePath = 'previousPeriod.amount';
|
||||||
|
const tableNodes = this.getNodesTableForEvaluating(
|
||||||
|
previousPeriodNodePath,
|
||||||
|
accNodes,
|
||||||
|
);
|
||||||
|
// Evaluate the given equation.
|
||||||
|
const total = this.evaluateEquation(equation, tableNodes);
|
||||||
|
|
||||||
|
return R.assoc('previousPeriod', this.getTotalAmountMeta(total), node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes -
|
||||||
|
* @param {string} node
|
||||||
|
* @param {IProfitLossSheetEquationNode} node
|
||||||
|
* @returns {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
protected previousPeriodEquationNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetEquationNode,
|
||||||
|
): IProfitLossSheetEquationNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocPreviousPeriodEquationHorizNode(accNodes, equation),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
this.assocPreviousPeriodTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
this.assocPreviousPeriodTotalChangeNode,
|
||||||
|
),
|
||||||
|
this.assocPreviousPeriodTotalEquationNode(accNodes, equation),
|
||||||
|
)(node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// # Horizontal Nodes - Account
|
||||||
|
// --------------------------
|
||||||
|
/**
|
||||||
|
* Assoc previous period to account horizontal node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @param {IProfitLossHorizontalDatePeriodNode} totalNode
|
||||||
|
* @returns {IProfitLossHorizontalDatePeriodNode}
|
||||||
|
*/
|
||||||
|
private assocPerviousPeriodAccountHorizTotal = R.curry(
|
||||||
|
(
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
totalNode: IProfitLossHorizontalDatePeriodNode,
|
||||||
|
): IProfitLossHorizontalDatePeriodNode => {
|
||||||
|
const total = this.repository.PPPeriodsAccountsLedger.whereAccountId(
|
||||||
|
node.id,
|
||||||
|
)
|
||||||
|
.whereFromDate(totalNode.previousPeriodFromDate.date)
|
||||||
|
.whereToDate(totalNode.previousPeriodToDate.date)
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
return R.assoc('previousPeriod', this.getAmountMeta(total), totalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @param {IProfitLossSheetTotal}
|
||||||
|
*/
|
||||||
|
private previousPeriodAccountHorizNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
horizontalTotalNode: IProfitLossHorizontalDatePeriodNode,
|
||||||
|
index: number,
|
||||||
|
): IProfitLossHorizontalDatePeriodNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
this.assocPreviousPeriodPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
this.assocPreviousPeriodChangeNode,
|
||||||
|
),
|
||||||
|
this.assocPerviousPeriodAccountHorizTotal(node),
|
||||||
|
this.assocPreviousPeriodHorizNodeFromToDates(
|
||||||
|
this.query.displayColumnsBy,
|
||||||
|
),
|
||||||
|
)(horizontalTotalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousPeriodAccountHorizNodeCompose = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
const horizontalTotals = R.addIndex(R.map)(
|
||||||
|
this.previousPeriodAccountHorizNodeCompose(node),
|
||||||
|
node.horizontalTotals,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horizontalTotals, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Horizontal Nodes - Aggregate
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
* Assoc previous period total to aggregate horizontal nodes.
|
||||||
|
* @param {IProfitLossSheetAccountsNode} node
|
||||||
|
* @param {number} index
|
||||||
|
* @param {any} totalNode
|
||||||
|
* @return {}
|
||||||
|
*/
|
||||||
|
private assocPreviousPeriodAggregateHorizTotal = R.curry(
|
||||||
|
(
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
index: number,
|
||||||
|
totalNode: IProfitLossHorizontalDatePeriodNode,
|
||||||
|
) => {
|
||||||
|
const total = this.getPPHorizNodesTotalSumation(index, node);
|
||||||
|
|
||||||
|
return R.assoc(
|
||||||
|
'previousPeriod',
|
||||||
|
this.getTotalAmountMeta(total),
|
||||||
|
totalNode,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetAccountsNode} node
|
||||||
|
* @param {IProfitLossHorizontalDatePeriodNode} horizontalTotalNode -
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {IProfitLossHorizontalDatePeriodNode}
|
||||||
|
*/
|
||||||
|
private previousPeriodAggregateHorizNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
horizontalTotalNode: IProfitLossHorizontalDatePeriodNode,
|
||||||
|
index: number,
|
||||||
|
): IProfitLossHorizontalDatePeriodNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
this.assocPreviousPeriodTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
this.assocPreviousPeriodTotalChangeNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
this.assocPreviousPeriodAggregateHorizTotal(node, index),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
this.assocPreviousPeriodHorizNodeFromToDates(
|
||||||
|
this.query.displayColumnsBy,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)(horizontalTotalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc previous period to aggregate horizontal nodes.
|
||||||
|
* @param {IProfitLossSheetAccountsNode} node
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private assocPreviousPeriodAggregateHorizNode = (
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
): IProfitLossSheetAccountsNode => {
|
||||||
|
const horizontalTotals = R.addIndex(R.map)(
|
||||||
|
this.previousPeriodAggregateHorizNodeCompose(node),
|
||||||
|
node.horizontalTotals,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horizontalTotals, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Horizontal Nodes - Equation
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode[]} accNodes -
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {index} number
|
||||||
|
* @param {} totalNode
|
||||||
|
*/
|
||||||
|
private assocPreviousPeriodEquationHorizTotal = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
index: number,
|
||||||
|
totalNode,
|
||||||
|
): IProfitLossSheetNode => {
|
||||||
|
const scopes = this.getNodesTableForEvaluating(
|
||||||
|
`horizontalTotals[${index}].previousPeriod.amount`,
|
||||||
|
accNodes,
|
||||||
|
);
|
||||||
|
const total = this.evaluateEquation(equation, scopes);
|
||||||
|
|
||||||
|
return R.assoc(
|
||||||
|
'previousPeriod',
|
||||||
|
this.getTotalAmountMeta(total),
|
||||||
|
totalNode,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode[]} accNodes -
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {} horizontalTotalNode
|
||||||
|
* @param {number} index
|
||||||
|
*/
|
||||||
|
private previousPeriodEquationHorizNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
horizontalTotalNode,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
const assocHorizTotal = this.assocPreviousPeriodEquationHorizTotal(
|
||||||
|
accNodes,
|
||||||
|
equation,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
this.assocPreviousPeriodTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
this.assocPreviousPeriodTotalChangeNode,
|
||||||
|
),
|
||||||
|
R.when(this.query.isPreviousPeriodActive, assocHorizTotal),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
this.assocPreviousPeriodHorizNodeFromToDates(
|
||||||
|
this.query.displayColumnsBy,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)(horizontalTotalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assoc previous period equation to horizontal nodes.
|
||||||
|
* @parma {IProfitLossSheetNode[]} accNodes -
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {IProfitLossSheetEquationNode} node
|
||||||
|
* @return {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousPeriodEquationHorizNode = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetEquationNode,
|
||||||
|
): IProfitLossSheetEquationNode => {
|
||||||
|
const horizontalTotals = R.addIndex(R.map)(
|
||||||
|
this.previousPeriodEquationHorizNodeCompose(accNodes, equation),
|
||||||
|
node.horizontalTotals,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horizontalTotals, node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { sumBy } from 'lodash';
|
||||||
|
import {
|
||||||
|
IProfitLossSheetEquationNode,
|
||||||
|
IProfitLossSheetAccountNode,
|
||||||
|
IProfitLossSchemaNode,
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
IProfitLossSheetTotal,
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||||
|
import { FinancialPreviousYear } from '../../common/FinancialPreviousYear';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
|
||||||
|
export const ProfitLossSheetPreviousYear = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialPreviousYear)(Base) {
|
||||||
|
repository: ProfitLossSheetRepository;
|
||||||
|
query: ProfitLossSheetQuery;
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// # Account
|
||||||
|
// ---------------------------
|
||||||
|
/**
|
||||||
|
* Assoc previous year total attribute to account node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousYearTotalAccountNode = (
|
||||||
|
accountNode: IProfitLossSheetAccountNode,
|
||||||
|
) => {
|
||||||
|
const total = this.repository.PYTotalAccountsLedger.whereAccountId(
|
||||||
|
accountNode.id,
|
||||||
|
).getClosingBalance();
|
||||||
|
|
||||||
|
return R.assoc('previousYear', this.getAmountMeta(total), accountNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose previous year account node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
protected previousYearAccountNodeCompose = (
|
||||||
|
accountNode: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocPreviousYearAccountHorizNodeCompose,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
this.assocPreviousYearPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
this.assocPreviousYearChangetNode,
|
||||||
|
),
|
||||||
|
this.assocPreviousYearTotalAccountNode,
|
||||||
|
)(accountNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// # Aggregate
|
||||||
|
// ---------------------------
|
||||||
|
/**
|
||||||
|
* Assoc previous year change attribute to aggregate node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousYearTotalAggregateNode = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
const total = sumBy(node.children, 'previousYear.amount');
|
||||||
|
|
||||||
|
return R.assoc('previousYear', this.getTotalAmountMeta(total), node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose previous year to aggregate node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} accountNode
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
protected previousYearAggregateNodeCompose = (
|
||||||
|
accountNode: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocPreviousYearAggregateHorizNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
this.assocPreviousYearTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
this.assocPreviousYearTotalChangeNode,
|
||||||
|
),
|
||||||
|
this.assocPreviousYearTotalAggregateNode,
|
||||||
|
)(accountNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// # Equation
|
||||||
|
// ---------------------------
|
||||||
|
/**
|
||||||
|
* Assoc previous year total to equation node.
|
||||||
|
* @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @returns {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousYearTotalEquationNode = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetNode,
|
||||||
|
) => {
|
||||||
|
const previousPeriodNodePath = 'previousYear.amount';
|
||||||
|
const tableNodes = this.getNodesTableForEvaluating(
|
||||||
|
previousPeriodNodePath,
|
||||||
|
accNodes,
|
||||||
|
);
|
||||||
|
// Evaluate the given equation.
|
||||||
|
const total = this.evaluateEquation(equation, tableNodes);
|
||||||
|
|
||||||
|
return R.assoc('previousYear', this.getTotalAmountMeta(total), node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year equation node.
|
||||||
|
* @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes -
|
||||||
|
* @param {string} node
|
||||||
|
* @param {IProfitLossSheetEquationNode} node
|
||||||
|
* @returns {IProfitLossSheetEquationNode}
|
||||||
|
*/
|
||||||
|
protected previousYearEquationNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetEquationNode,
|
||||||
|
) => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.isNodeHasHorizTotals,
|
||||||
|
this.assocPreviousYearEquationHorizNode(accNodes, equation),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
this.assocPreviousYearTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
this.assocPreviousYearTotalChangeNode,
|
||||||
|
),
|
||||||
|
this.assocPreviousYearTotalEquationNode(accNodes, equation),
|
||||||
|
)(node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Horizontal Nodes - Account
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
* Assoc preivous year to account horizontal total node.
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private assocPreviousYearAccountHorizTotal = R.curry(
|
||||||
|
(node: IProfitLossSheetAccountNode, totalNode) => {
|
||||||
|
const total = this.repository.PYPeriodsAccountsLedger.whereAccountId(
|
||||||
|
node.id,
|
||||||
|
)
|
||||||
|
.whereFromDate(totalNode.previousYearFromDate.date)
|
||||||
|
.whereToDate(totalNode.previousYearToDate.date)
|
||||||
|
.getClosingBalance();
|
||||||
|
|
||||||
|
return R.assoc('previousYear', this.getAmountMeta(total), totalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year account horizontal node composer.
|
||||||
|
* @param {IProfitLossSheetAccountNode} horizontalTotalNode
|
||||||
|
* @param {IProfitLossSheetTotal} horizontalTotalNode -
|
||||||
|
* @returns {IProfitLossSheetTotal}
|
||||||
|
*/
|
||||||
|
private previousYearAccountHorizNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
horizontalTotalNode: IProfitLossSheetTotal,
|
||||||
|
): IProfitLossSheetTotal => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
this.assocPreviousYearPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
this.assocPreviousYearChangetNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
this.assocPreviousYearAccountHorizTotal(node),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
this.assocPreviousYearHorizNodeFromToDates,
|
||||||
|
),
|
||||||
|
)(horizontalTotalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousYearAccountHorizNodeCompose = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
const horizontalTotals = R.map(
|
||||||
|
this.previousYearAccountHorizNodeCompose(node),
|
||||||
|
node.horizontalTotals,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horizontalTotals, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Horizontal Nodes - Aggregate
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private assocPreviousYearAggregateHorizTotal = R.curry(
|
||||||
|
(node, index, totalNode) => {
|
||||||
|
const total = this.getPYHorizNodesTotalSumation(index, node);
|
||||||
|
|
||||||
|
return R.assoc(
|
||||||
|
'previousYear',
|
||||||
|
this.getTotalAmountMeta(total),
|
||||||
|
totalNode,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private previousYearAggregateHorizNodeCompose = R.curry(
|
||||||
|
(node, horizontalTotalNode, index: number) => {
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
this.assocPreviousYearTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
this.assocPreviousYearTotalChangeNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
this.assocPreviousYearAggregateHorizTotal(node, index),
|
||||||
|
),
|
||||||
|
)(horizontalTotalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @returns {IProfitLossSheetAccountNode}
|
||||||
|
*/
|
||||||
|
private assocPreviousYearAggregateHorizNode = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
): IProfitLossSheetAccountNode => {
|
||||||
|
const horizontalTotals = R.addIndex(R.map)(
|
||||||
|
this.previousYearAggregateHorizNodeCompose(node),
|
||||||
|
node.horizontalTotals,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horizontalTotals, node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Horizontal Nodes - Equation
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode[]} accNodes -
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {number} index
|
||||||
|
* @param {} totalNode -
|
||||||
|
*/
|
||||||
|
private assocPreviousYearEquationHorizTotal = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
index: number,
|
||||||
|
totalNode,
|
||||||
|
) => {
|
||||||
|
const scopes = this.getNodesTableForEvaluating(
|
||||||
|
`horizontalTotals[${index}].previousYear.amount`,
|
||||||
|
accNodes,
|
||||||
|
);
|
||||||
|
const total = this.evaluateEquation(equation, scopes);
|
||||||
|
|
||||||
|
return R.assoc(
|
||||||
|
'previousYear',
|
||||||
|
this.getTotalAmountMeta(total),
|
||||||
|
totalNode,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode[]} accNodes -
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {} horizontalTotalNode
|
||||||
|
* @param {number} index
|
||||||
|
*/
|
||||||
|
private previousYearEquationHorizNodeCompose = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
horizontalTotalNode,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
const assocHorizTotal = this.assocPreviousYearEquationHorizTotal(
|
||||||
|
accNodes,
|
||||||
|
equation,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
return R.compose(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
this.assocPreviousYearTotalPercentageNode,
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
this.assocPreviousYearTotalChangeNode,
|
||||||
|
),
|
||||||
|
R.when(this.query.isPreviousYearActive, assocHorizTotal),
|
||||||
|
)(horizontalTotalNode);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode[]} accNodes
|
||||||
|
* @param {string} equation
|
||||||
|
* @param {IProfitLossSheetEquationNode} node
|
||||||
|
*/
|
||||||
|
private assocPreviousYearEquationHorizNode = R.curry(
|
||||||
|
(
|
||||||
|
accNodes: IProfitLossSheetNode[],
|
||||||
|
equation: string,
|
||||||
|
node: IProfitLossSheetEquationNode,
|
||||||
|
) => {
|
||||||
|
const horizontalTotals = R.addIndex(R.map)(
|
||||||
|
this.previousYearEquationHorizNodeCompose(accNodes, equation),
|
||||||
|
node.horizontalTotals,
|
||||||
|
);
|
||||||
|
return R.assoc('horizontalTotals', horizontalTotals, node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import { merge } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { IProfitLossSheetQuery } from './ProfitLossSheet.types';
|
||||||
|
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
|
||||||
|
import { IFinancialDatePeriodsUnit } from '../../types/Report.types';
|
||||||
|
import { DISPLAY_COLUMNS_BY } from './constants';
|
||||||
|
|
||||||
|
export class ProfitLossSheetQuery extends R.compose(FinancialDateRanges)(
|
||||||
|
class {},
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* P&L query.
|
||||||
|
* @param {IProfitLossSheetQuery}
|
||||||
|
*/
|
||||||
|
public readonly query: IProfitLossSheetQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year to date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public readonly PYToDate: Date;
|
||||||
|
/**
|
||||||
|
* Previous year from date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public readonly PYFromDate: Date;
|
||||||
|
/**
|
||||||
|
* Previous period to date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public readonly PPToDate: Date;
|
||||||
|
/**
|
||||||
|
* Previous period from date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public readonly PPFromDate: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
*/
|
||||||
|
constructor(query: IProfitLossSheetQuery) {
|
||||||
|
super();
|
||||||
|
this.query = query;
|
||||||
|
|
||||||
|
// Pervious Year (PY) Dates.
|
||||||
|
this.PYToDate = this.getPreviousYearDate(this.query.toDate);
|
||||||
|
this.PYFromDate = this.getPreviousYearDate(this.query.fromDate);
|
||||||
|
|
||||||
|
// Previous Period (PP) Dates for total column..
|
||||||
|
if (this.isTotalColumnType()) {
|
||||||
|
const { fromDate, toDate } = this.getPPTotalDateRange(
|
||||||
|
this.query.fromDate,
|
||||||
|
this.query.toDate,
|
||||||
|
);
|
||||||
|
this.PPToDate = toDate;
|
||||||
|
this.PPFromDate = fromDate;
|
||||||
|
|
||||||
|
// Previous period (PP) dates for date periods columns type.
|
||||||
|
} else if (this.isDatePeriodsColumnsType()) {
|
||||||
|
const { fromDate, toDate } = this.getPPDatePeriodDateRange(
|
||||||
|
this.query.fromDate,
|
||||||
|
this.query.toDate,
|
||||||
|
this.query.displayColumnsBy as IFinancialDatePeriodsUnit,
|
||||||
|
);
|
||||||
|
this.PPToDate = toDate;
|
||||||
|
this.PPFromDate = fromDate;
|
||||||
|
}
|
||||||
|
return merge(this, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the given display columns type.
|
||||||
|
* @param {string} displayColumnsBy
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isDisplayColumnsBy = (displayColumnsBy: string): boolean => {
|
||||||
|
return this.query.displayColumnsBy === displayColumnsBy;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the given display columns by type.
|
||||||
|
* @param {string} displayColumnsBy
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isDisplayColumnsType = (displayColumnsType: string): boolean => {
|
||||||
|
return this.query.displayColumnsType === displayColumnsType;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the columns type is date periods.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isDatePeriodsColumnsType = (): boolean => {
|
||||||
|
return this.isDisplayColumnsType(DISPLAY_COLUMNS_BY.DATE_PERIODS);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the columns type is total.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isTotalColumnType = (): boolean => {
|
||||||
|
return this.isDisplayColumnsType(DISPLAY_COLUMNS_BY.TOTAL);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// # Previous Year (PY)
|
||||||
|
// --------------------------------------
|
||||||
|
/**
|
||||||
|
* Determines the report query has previous year enabled.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isPreviousYearActive = (): boolean => {
|
||||||
|
return this.query.previousYear;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the report query has previous year percentage change active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isPreviousYearPercentageActive = (): boolean => {
|
||||||
|
return this.query.previousYearPercentageChange;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the report query has previous year change active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isPreviousYearChangeActive = (): boolean => {
|
||||||
|
return this.query.previousYearAmountChange;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves PY date based on the current query.
|
||||||
|
* @returns {Date}
|
||||||
|
*/
|
||||||
|
public getTotalPreviousYear = (): Date => {
|
||||||
|
return this.PYFromDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// # Previous Period (PP)
|
||||||
|
// --------------------------------------
|
||||||
|
/**
|
||||||
|
* Determines the report query has previous period enabled.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isPreviousPeriodActive = (): boolean => {
|
||||||
|
return this.query.previousPeriod;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the report query has previous period percentage change active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isPreviousPeriodPercentageActive = (): boolean => {
|
||||||
|
return this.query.previousPeriodPercentageChange;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the report query has previous period change active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isPreviousPeriodChangeActive = (): boolean => {
|
||||||
|
return this.query.previousPeriodAmountChange;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves previous period date based on the current query.
|
||||||
|
* @returns {Date}
|
||||||
|
*/
|
||||||
|
public getTotalPreviousPeriod = (): Date => {
|
||||||
|
return this.PPFromDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// # Percentage vertical/horizontal.
|
||||||
|
// --------------------------------------
|
||||||
|
/**
|
||||||
|
* Determines whether percentage of expenses is active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isExpensesPercentage = (): boolean => {
|
||||||
|
return this.query.percentageExpense;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether percentage of income is active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isIncomePercentage = (): boolean => {
|
||||||
|
return this.query.percentageIncome;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether percentage of column is active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isColumnPercentage = (): boolean => {
|
||||||
|
return this.query.percentageColumn;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether percentage of row is active.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public isRowPercentage = (): boolean => {
|
||||||
|
return this.query.percentageRow;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { castArray } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { transformToMapBy } from '@/utils/transform-to-map-by';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||||
|
import { IProfitLossSheetQuery } from './ProfitLossSheet.types';
|
||||||
|
import { IAccountTransactionsGroupBy } from '../BalanceSheet/BalanceSheet.types';
|
||||||
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
|
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
|
||||||
|
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
|
||||||
|
@Injectable({ scope: Scope.TRANSIENT })
|
||||||
|
export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
||||||
|
class {},
|
||||||
|
) {
|
||||||
|
@Inject(Account.name)
|
||||||
|
public accountModel: typeof Account;
|
||||||
|
|
||||||
|
@Inject(AccountTransaction.name)
|
||||||
|
public accountTransactionModel: typeof AccountTransaction;
|
||||||
|
|
||||||
|
@Inject(TenancyContext)
|
||||||
|
public tenancyContext: TenancyContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tenancy base currency.
|
||||||
|
* @param {string}
|
||||||
|
*/
|
||||||
|
public baseCurrency: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts by type.
|
||||||
|
*/
|
||||||
|
public accountsByType: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ModelObject<Account>[]}
|
||||||
|
*/
|
||||||
|
public accounts: ModelObject<Account>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts graph.
|
||||||
|
*/
|
||||||
|
public accountsGraph: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transactions group type.
|
||||||
|
* @param {IAccountTransactionsGroupBy}
|
||||||
|
*/
|
||||||
|
public transactionsGroupType: IAccountTransactionsGroupBy =
|
||||||
|
IAccountTransactionsGroupBy.Month;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {IProfitLossSheetQuery}
|
||||||
|
*/
|
||||||
|
public query: ProfitLossSheetQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year to date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public PYToDate: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year from date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public PYFromDate: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year to date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public PPToDate: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year from date.
|
||||||
|
* @param {Date}
|
||||||
|
*/
|
||||||
|
public PPFromDate: Date;
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// # Total
|
||||||
|
// ------------------------
|
||||||
|
/**
|
||||||
|
* Accounts total.
|
||||||
|
* @param {Ledger}
|
||||||
|
*/
|
||||||
|
public totalAccountsLedger: Ledger;
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// # Date Periods.
|
||||||
|
// ------------------------
|
||||||
|
/**
|
||||||
|
* Accounts date periods.
|
||||||
|
* @param {Ledger}
|
||||||
|
*/
|
||||||
|
public periodsAccountsLedger: Ledger;
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// # Previous Year (PY)
|
||||||
|
// ------------------------
|
||||||
|
/**
|
||||||
|
* @param {Ledger}
|
||||||
|
*/
|
||||||
|
public PYTotalAccountsLedger: Ledger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Ledger}
|
||||||
|
*/
|
||||||
|
public PYPeriodsAccountsLedger: Ledger;
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// # Previous Period (PP).
|
||||||
|
// ------------------------
|
||||||
|
/**
|
||||||
|
* PP Accounts Periods.
|
||||||
|
* @param {Ledger}
|
||||||
|
*/
|
||||||
|
public PPPeriodsAccountsLedger: Ledger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PP Accounts Total.
|
||||||
|
* @param {Ledger}
|
||||||
|
*/
|
||||||
|
public PPTotalAccountsLedger: Ledger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the filter of the report.
|
||||||
|
* @param {IBalanceSheetQuery} query
|
||||||
|
*/
|
||||||
|
setFilter(query: IProfitLossSheetQuery) {
|
||||||
|
this.query = new ProfitLossSheetQuery(query);
|
||||||
|
|
||||||
|
this.transactionsGroupType = this.getGroupByFromDisplayColumnsBy(
|
||||||
|
this.query.displayColumnsBy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async report repository.
|
||||||
|
*/
|
||||||
|
public asyncInitialize = async () => {
|
||||||
|
await this.initBaseCurrency();
|
||||||
|
await this.initAccounts();
|
||||||
|
await this.initAccountsGraph();
|
||||||
|
|
||||||
|
await this.initAccountsTotalLedger();
|
||||||
|
|
||||||
|
// Date Periods.
|
||||||
|
if (this.query.isDatePeriodsColumnsType()) {
|
||||||
|
await this.initTotalDatePeriods();
|
||||||
|
}
|
||||||
|
// Previous Period (PP)
|
||||||
|
if (this.query.isPreviousPeriodActive()) {
|
||||||
|
await this.initTotalPreviousPeriod();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.query.isPreviousPeriodActive() &&
|
||||||
|
this.query.isDatePeriodsColumnsType()
|
||||||
|
) {
|
||||||
|
await this.initPeriodsPreviousPeriod();
|
||||||
|
}
|
||||||
|
// Previous Year (PY).
|
||||||
|
if (this.query.isPreviousYearActive()) {
|
||||||
|
await this.initTotalPreviousYear();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.query.isPreviousYearActive() &&
|
||||||
|
this.query.isDatePeriodsColumnsType()
|
||||||
|
) {
|
||||||
|
await this.initPeriodsPreviousYear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the base currency.
|
||||||
|
*/
|
||||||
|
async initBaseCurrency() {
|
||||||
|
const metadata = await this.tenancyContext.getTenantMetadata();
|
||||||
|
|
||||||
|
this.baseCurrency = metadata.baseCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Accounts
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Initialize accounts of the report.
|
||||||
|
*/
|
||||||
|
private initAccounts = async () => {
|
||||||
|
const accounts = await this.getAccounts();
|
||||||
|
|
||||||
|
// Inject to the repository.
|
||||||
|
this.accounts = accounts;
|
||||||
|
this.accountsByType = transformToMapBy(accounts, 'accountType');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize accounts graph.
|
||||||
|
*/
|
||||||
|
private initAccountsGraph = async () => {
|
||||||
|
this.accountsGraph = this.accountModel.toDependencyGraph(this.accounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Closing Total.
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Initialize accounts closing total based on the given query.
|
||||||
|
*/
|
||||||
|
private initAccountsTotalLedger = async (): Promise<void> => {
|
||||||
|
const totalByAccount = await this.accountsTotal(
|
||||||
|
this.query.fromDate,
|
||||||
|
this.query.toDate,
|
||||||
|
);
|
||||||
|
// Inject to the repository.
|
||||||
|
this.totalAccountsLedger = Ledger.fromTransactions(totalByAccount);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Date periods.
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Initialize date periods total of accounts based on the given query.
|
||||||
|
*/
|
||||||
|
private initTotalDatePeriods = async (): Promise<void> => {
|
||||||
|
// Retrieves grouped transactions by given date group.
|
||||||
|
const periodsByAccount = await this.accountsDatePeriods(
|
||||||
|
this.query.query.fromDate,
|
||||||
|
this.query.query.toDate,
|
||||||
|
this.transactionsGroupType,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inject to the repository.
|
||||||
|
this.periodsAccountsLedger = Ledger.fromTransactions(periodsByAccount);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Previous Period (PP).
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Initialize total of previous period (PP).
|
||||||
|
*/
|
||||||
|
private initTotalPreviousPeriod = async (): Promise<void> => {
|
||||||
|
const PPTotalsByAccounts = await this.accountsTotal(
|
||||||
|
this.query.PPFromDate,
|
||||||
|
this.query.PPToDate,
|
||||||
|
);
|
||||||
|
// Inject to the repository.
|
||||||
|
this.PPTotalAccountsLedger = Ledger.fromTransactions(PPTotalsByAccounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize date periods of previous period (PP).
|
||||||
|
*/
|
||||||
|
private initPeriodsPreviousPeriod = async (): Promise<void> => {
|
||||||
|
// Retrieves grouped transactions by given date group.
|
||||||
|
const periodsByAccount = await this.accountsDatePeriods(
|
||||||
|
this.query.PPFromDate,
|
||||||
|
this.query.PPToDate,
|
||||||
|
this.transactionsGroupType,
|
||||||
|
);
|
||||||
|
// Inject to the repository.
|
||||||
|
this.PPPeriodsAccountsLedger = Ledger.fromTransactions(periodsByAccount);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Previous Year (PY).
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Initialize total of previous year (PY).
|
||||||
|
*/
|
||||||
|
private initTotalPreviousYear = async (): Promise<void> => {
|
||||||
|
const PYTotalsByAccounts = await this.accountsTotal(
|
||||||
|
this.query.PYFromDate,
|
||||||
|
this.query.PYToDate,
|
||||||
|
);
|
||||||
|
// Inject to the repository.
|
||||||
|
this.PYTotalAccountsLedger = Ledger.fromTransactions(PYTotalsByAccounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize periods of previous year (PY).
|
||||||
|
*/
|
||||||
|
private initPeriodsPreviousYear = async () => {
|
||||||
|
// Retrieves grouped transactions by given date group.
|
||||||
|
const periodsByAccount = await this.accountsDatePeriods(
|
||||||
|
this.query.PYFromDate,
|
||||||
|
this.query.PYToDate,
|
||||||
|
this.transactionsGroupType,
|
||||||
|
);
|
||||||
|
// Inject to the repository.
|
||||||
|
this.PYPeriodsAccountsLedger = Ledger.fromTransactions(periodsByAccount);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Utils
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Retrieve the opening balance transactions of the report.
|
||||||
|
*/
|
||||||
|
public accountsTotal = async (fromDate: Date, toDate: Date) => {
|
||||||
|
return this.accountTransactionModel.query().onBuild((query) => {
|
||||||
|
query.sum('credit as credit');
|
||||||
|
query.sum('debit as debit');
|
||||||
|
query.groupBy('accountId');
|
||||||
|
query.select(['accountId']);
|
||||||
|
|
||||||
|
query.modify('filterDateRange', fromDate, toDate);
|
||||||
|
query.withGraphFetched('account');
|
||||||
|
|
||||||
|
this.commonFilterBranchesQuery(query);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closing accounts date periods.
|
||||||
|
* @param openingDate
|
||||||
|
* @param datePeriodsType
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public accountsDatePeriods = async (
|
||||||
|
fromDate: moment.MomentInput,
|
||||||
|
toDate: moment.MomentInput,
|
||||||
|
datePeriodsType,
|
||||||
|
) => {
|
||||||
|
return this.accountTransactionModel.query().onBuild((query) => {
|
||||||
|
query.sum('credit as credit');
|
||||||
|
query.sum('debit as debit');
|
||||||
|
query.groupBy('accountId');
|
||||||
|
query.select(['accountId']);
|
||||||
|
|
||||||
|
query.modify('groupByDateFormat', datePeriodsType);
|
||||||
|
query.modify('filterDateRange', fromDate, toDate);
|
||||||
|
query.withGraphFetched('account');
|
||||||
|
|
||||||
|
this.commonFilterBranchesQuery(query);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common branches filter query.
|
||||||
|
* @param {Knex.QueryBuilder} query
|
||||||
|
*/
|
||||||
|
private commonFilterBranchesQuery = (query: Knex.QueryBuilder) => {
|
||||||
|
if (!isEmpty(this.query.query.branchesIds)) {
|
||||||
|
query.modify('filterByBranches', this.query.query.branchesIds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve accounts of the report.
|
||||||
|
* @return {Promise<IAccount[]>}
|
||||||
|
*/
|
||||||
|
private getAccounts = () => {
|
||||||
|
return this.accountModel.query();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getAccountsByType = (type: string[] | string) => {
|
||||||
|
return R.compose(
|
||||||
|
R.flatten,
|
||||||
|
R.map((accountType) =>
|
||||||
|
R.defaultTo([], this.accountsByType.get(accountType)),
|
||||||
|
),
|
||||||
|
castArray,
|
||||||
|
)(type);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
IProfitLossSheetMeta,
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import ProfitLossSheet from './ProfitLossSheet';
|
||||||
|
import { mergeQueryWithDefaults } from './utils';
|
||||||
|
import { ProfitLossSheetRepository } from './ProfitLossSheetRepository';
|
||||||
|
import { ProfitLossSheetMeta } from './ProfitLossSheetMeta';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProfitLossSheetService {
|
||||||
|
constructor(
|
||||||
|
private readonly profitLossSheetMeta: ProfitLossSheetMeta,
|
||||||
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
|
private readonly profitLossRepository: ProfitLossSheetRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve profit/loss sheet statement.
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
* @return { }
|
||||||
|
*/
|
||||||
|
public profitLossSheet = async (
|
||||||
|
query: IProfitLossSheetQuery,
|
||||||
|
): Promise<{
|
||||||
|
data: IProfitLossSheetNode[];
|
||||||
|
query: IProfitLossSheetQuery;
|
||||||
|
meta: IProfitLossSheetMeta;
|
||||||
|
}> => {
|
||||||
|
// Merges the given query with default filter query.
|
||||||
|
const filter = mergeQueryWithDefaults(query);
|
||||||
|
|
||||||
|
// Loads the profit/loss sheet data.
|
||||||
|
this.profitLossRepository.setFilter(filter);
|
||||||
|
await this.profitLossRepository.asyncInitialize();
|
||||||
|
|
||||||
|
// Profit/Loss report instance.
|
||||||
|
const profitLossInstance = new ProfitLossSheet(
|
||||||
|
this.profitLossRepository,
|
||||||
|
filter,
|
||||||
|
this.i18nService,
|
||||||
|
);
|
||||||
|
// Profit/loss report data and columns.
|
||||||
|
const data = profitLossInstance.reportData();
|
||||||
|
|
||||||
|
// Retrieve the profit/loss sheet meta.
|
||||||
|
const meta = await this.profitLossSheetMeta.meta(filter);
|
||||||
|
|
||||||
|
// Triggers `onProfitLossSheetViewed` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.reports.onProfitLossSheetViewed,
|
||||||
|
{ query },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: filter,
|
||||||
|
data,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import {
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
IProfitLossSheetAccountsNode,
|
||||||
|
ProfitLossNodeType,
|
||||||
|
ProfitLossSheetRowType,
|
||||||
|
IProfitLossSheetNode,
|
||||||
|
IProfitLossSheetEquationNode,
|
||||||
|
IProfitLossSheetAccountNode,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import {
|
||||||
|
ITableColumn,
|
||||||
|
ITableColumnAccessor,
|
||||||
|
ITableRow,
|
||||||
|
} from '../../types/Table.types';
|
||||||
|
import { ProfitLossSheetBase } from './ProfitLossSheetBase';
|
||||||
|
import { ProfitLossSheetTablePercentage } from './ProfitLossSheetTablePercentage';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { ProfitLossTablePreviousPeriod } from './ProfitLossTablePreviousPeriod';
|
||||||
|
import { ProfitLossTablePreviousYear } from './ProfitLossTablePreviousYear';
|
||||||
|
import { ProfitLossSheetTableDatePeriods } from './ProfitLossSheetTableDatePeriods';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
|
||||||
|
import { FinancialTable } from '../../common/FinancialTable';
|
||||||
|
import { tableRowMapper } from '../../utils/Table.utils';
|
||||||
|
|
||||||
|
export class ProfitLossSheetTable extends R.pipe(
|
||||||
|
ProfitLossTablePreviousPeriod,
|
||||||
|
ProfitLossTablePreviousYear,
|
||||||
|
ProfitLossSheetTablePercentage,
|
||||||
|
ProfitLossSheetTableDatePeriods,
|
||||||
|
ProfitLossSheetBase,
|
||||||
|
FinancialSheetStructure,
|
||||||
|
FinancialTable,
|
||||||
|
)(class {}) {
|
||||||
|
readonly query: ProfitLossSheetQuery;
|
||||||
|
readonly i18n: I18nService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {} date
|
||||||
|
* @param {IProfitLossSheetQuery} query
|
||||||
|
*/
|
||||||
|
constructor(data: any, query: IProfitLossSheetQuery, i18n: I18nService) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.query = new ProfitLossSheetQuery(query);
|
||||||
|
this.reportData = data;
|
||||||
|
this.i18n = i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Rows
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieve the total column accessor.
|
||||||
|
* @return {ITableColumnAccessor[]}
|
||||||
|
*/
|
||||||
|
private totalColumnAccessor = (): ITableColumnAccessor[] => {
|
||||||
|
return R.pipe(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
R.concat(this.previousPeriodColumnAccessor()),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
R.concat(this.previousYearColumnAccessor()),
|
||||||
|
),
|
||||||
|
R.concat(this.percentageColumnsAccessor()),
|
||||||
|
R.concat([{ key: 'total', accessor: 'total.formattedAmount' }]),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common columns accessors.
|
||||||
|
* @returns {ITableColumnAccessor}
|
||||||
|
*/
|
||||||
|
private commonColumnsAccessors = (): ITableColumnAccessor[] => {
|
||||||
|
return R.compose(
|
||||||
|
R.concat([{ key: 'name', accessor: 'name' }]),
|
||||||
|
R.ifElse(
|
||||||
|
this.query.isDatePeriodsColumnsType,
|
||||||
|
R.concat(this.datePeriodsColumnsAccessors()),
|
||||||
|
R.concat(this.totalColumnAccessor()),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetAccountNode} node
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private accountNodeToTableRow = (
|
||||||
|
node: IProfitLossSheetAccountNode,
|
||||||
|
): ITableRow => {
|
||||||
|
const columns = this.commonColumnsAccessors();
|
||||||
|
const meta = {
|
||||||
|
rowTypes: [ProfitLossSheetRowType.ACCOUNT],
|
||||||
|
id: node.id,
|
||||||
|
};
|
||||||
|
return tableRowMapper(node, columns, meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetAccountsNode} node
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private accountsNodeToTableRow = (
|
||||||
|
node: IProfitLossSheetAccountsNode,
|
||||||
|
): ITableRow => {
|
||||||
|
const columns = this.commonColumnsAccessors();
|
||||||
|
const meta = {
|
||||||
|
rowTypes: [ProfitLossSheetRowType.ACCOUNTS],
|
||||||
|
id: node.id,
|
||||||
|
};
|
||||||
|
return tableRowMapper(node, columns, meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetEquationNode} node
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private equationNodeToTableRow = (
|
||||||
|
node: IProfitLossSheetEquationNode,
|
||||||
|
): ITableRow => {
|
||||||
|
const columns = this.commonColumnsAccessors();
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
rowTypes: [ProfitLossSheetRowType.TOTAL],
|
||||||
|
id: node.id,
|
||||||
|
};
|
||||||
|
return tableRowMapper(node, columns, meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode} node
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private nodeToTableRowCompose = (node: IProfitLossSheetNode): ITableRow => {
|
||||||
|
return R.cond([
|
||||||
|
[
|
||||||
|
this.isNodeType(ProfitLossNodeType.ACCOUNTS),
|
||||||
|
this.accountsNodeToTableRow,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
this.isNodeType(ProfitLossNodeType.EQUATION),
|
||||||
|
this.equationNodeToTableRow,
|
||||||
|
],
|
||||||
|
[this.isNodeType(ProfitLossNodeType.ACCOUNT), this.accountNodeToTableRow],
|
||||||
|
])(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IProfitLossSheetNode[]} nodes
|
||||||
|
* @returns {ITableRow}
|
||||||
|
*/
|
||||||
|
private nodesToTableRowsCompose = (
|
||||||
|
nodes: IProfitLossSheetNode[],
|
||||||
|
): ITableRow[] => {
|
||||||
|
return this.mapNodesDeep(nodes, this.nodeToTableRowCompose);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the table rows.
|
||||||
|
* @returns {ITableRow[]}
|
||||||
|
*/
|
||||||
|
public tableRows = (): ITableRow[] => {
|
||||||
|
return R.compose(
|
||||||
|
this.addTotalRows,
|
||||||
|
this.nodesToTableRowsCompose,
|
||||||
|
)(this.reportData);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Columns.
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieve total column children columns.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
private tableColumnChildren = (): ITableColumn[] => {
|
||||||
|
return R.compose(
|
||||||
|
R.unless(
|
||||||
|
R.isEmpty,
|
||||||
|
R.concat([
|
||||||
|
{ key: 'total', label: this.i18n.t('profit_loss_sheet.total') },
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
R.concat(this.percentageColumns()),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
R.concat(this.getPreviousYearColumns()),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
R.concat(this.getPreviousPeriodColumns()),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the total column.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
private totalColumn = (): ITableColumn[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'total',
|
||||||
|
label: this.i18n.t('profit_loss_sheet.total'),
|
||||||
|
children: this.tableColumnChildren(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the table columns.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
public tableColumns = (): ITableColumn[] => {
|
||||||
|
return R.compose(
|
||||||
|
this.tableColumnsCellIndexing,
|
||||||
|
R.concat([
|
||||||
|
{ key: 'name', label: this.i18n.t('profit_loss_sheet.account_name') },
|
||||||
|
]),
|
||||||
|
R.ifElse(
|
||||||
|
this.query.isDatePeriodsColumnsType,
|
||||||
|
R.concat(this.datePeriodsColumns()),
|
||||||
|
R.concat(this.totalColumn()),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types';
|
||||||
|
import { ProfitLossSheetTablePercentage } from './ProfitLossSheetTablePercentage';
|
||||||
|
import { ProfitLossTablePreviousPeriod } from './ProfitLossTablePreviousPeriod';
|
||||||
|
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { IDateRange } from '../../types/Report.types';
|
||||||
|
|
||||||
|
export const ProfitLossSheetTableDatePeriods = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(
|
||||||
|
ProfitLossSheetTablePercentage,
|
||||||
|
ProfitLossTablePreviousPeriod,
|
||||||
|
FinancialDatePeriods,
|
||||||
|
)(Base) {
|
||||||
|
/**
|
||||||
|
* Retrieves the date periods based on the report query.
|
||||||
|
* @returns {IDateRange[]}
|
||||||
|
*/
|
||||||
|
get datePeriods() {
|
||||||
|
return this.getDateRanges(
|
||||||
|
this.query.query.fromDate,
|
||||||
|
this.query.query.toDate,
|
||||||
|
this.query.query.displayColumnsBy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------
|
||||||
|
// # Accessors
|
||||||
|
// --------------------------------
|
||||||
|
/**
|
||||||
|
* Date period columns accessor.
|
||||||
|
* @param {IDateRange} dateRange -
|
||||||
|
* @param {number} index -
|
||||||
|
*/
|
||||||
|
private datePeriodColumnsAccessor = R.curry(
|
||||||
|
(dateRange: IDateRange, index: number) => {
|
||||||
|
return R.pipe(
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
R.concat(this.previousPeriodHorizontalColumnAccessors(index)),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
R.concat(this.previousYearHorizontalColumnAccessors(index)),
|
||||||
|
),
|
||||||
|
R.concat(this.percetangeHorizontalColumnsAccessor(index)),
|
||||||
|
R.concat([
|
||||||
|
{
|
||||||
|
key: `date-range-${index}`,
|
||||||
|
accessor: `horizontalTotals[${index}].total.formattedAmount`,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)([]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the date periods columns accessors.
|
||||||
|
* @returns {ITableColumnAccessor[]}
|
||||||
|
*/
|
||||||
|
protected datePeriodsColumnsAccessors = (): ITableColumnAccessor[] => {
|
||||||
|
return R.compose(
|
||||||
|
R.flatten,
|
||||||
|
R.addIndex(R.map)(this.datePeriodColumnsAccessor),
|
||||||
|
)(this.datePeriods);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------
|
||||||
|
// # Columns
|
||||||
|
// --------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieve the formatted column label from the given date range.
|
||||||
|
* @param {ICashFlowDateRange} dateRange -
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
private formatColumnLabel = (dateRange) => {
|
||||||
|
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.query.isDisplayColumnsBy(type)),
|
||||||
|
formatFn,
|
||||||
|
],
|
||||||
|
conditions,
|
||||||
|
);
|
||||||
|
return R.compose(R.cond(conditionsPairs))(dateRange);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} index
|
||||||
|
* @param {IDateRange} dateRange
|
||||||
|
* @returns {}
|
||||||
|
*/
|
||||||
|
private datePeriodChildrenColumns = (
|
||||||
|
index: number,
|
||||||
|
dateRange: IDateRange,
|
||||||
|
) => {
|
||||||
|
return R.compose(
|
||||||
|
R.unless(
|
||||||
|
R.isEmpty,
|
||||||
|
R.concat([
|
||||||
|
{ key: `total`, label: this.i18n.t('profit_loss_sheet.total') },
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
R.concat(this.percentageColumns()),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearActive,
|
||||||
|
R.concat(this.getPreviousYearDatePeriodColumnPlugin(dateRange)),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodActive,
|
||||||
|
R.concat(this.getPreviousPeriodDatePeriodsPlugin(dateRange)),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {IDateRange} dateRange
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {ITableColumn}
|
||||||
|
*/
|
||||||
|
private datePeriodColumn = (
|
||||||
|
dateRange: IDateRange,
|
||||||
|
index: number,
|
||||||
|
): ITableColumn => {
|
||||||
|
return {
|
||||||
|
key: `date-range-${index}`,
|
||||||
|
label: this.formatColumnLabel(dateRange),
|
||||||
|
children: this.datePeriodChildrenColumns(index, dateRange),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date periods columns.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected datePeriodsColumns = (): ITableColumn[] => {
|
||||||
|
return this.datePeriods.map(this.datePeriodColumn);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ProfitLossSheetService } from './ProfitLossSheetService';
|
||||||
|
import { ProfitLossSheetTable } from './ProfitLossSheetTable';
|
||||||
|
import {
|
||||||
|
IProfitLossSheetQuery,
|
||||||
|
IProfitLossSheetTable,
|
||||||
|
} from './ProfitLossSheet.types';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProfitLossSheetTableInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly i18n: I18nService,
|
||||||
|
private readonly profitLossSheet: ProfitLossSheetService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet in table format.
|
||||||
|
* @param {IProfitLossSheetQuery} filter - Profit/loss sheet query.
|
||||||
|
* @returns {Promise<IProfitLossSheetTable>}
|
||||||
|
*/
|
||||||
|
public async table(
|
||||||
|
filter: IProfitLossSheetQuery,
|
||||||
|
): Promise<IProfitLossSheetTable> {
|
||||||
|
const { data, query, meta } =
|
||||||
|
await this.profitLossSheet.profitLossSheet(filter);
|
||||||
|
|
||||||
|
const table = new ProfitLossSheetTable(data, query, this.i18n);
|
||||||
|
|
||||||
|
return {
|
||||||
|
table: {
|
||||||
|
rows: table.tableRows(),
|
||||||
|
columns: table.tableColumns(),
|
||||||
|
},
|
||||||
|
query,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types';
|
||||||
|
|
||||||
|
export const ProfitLossSheetTablePercentage = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends Base {
|
||||||
|
i18n: I18nService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ProfitLossSheetQuery}
|
||||||
|
*/
|
||||||
|
readonly query: ProfitLossSheetQuery;
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Columns.
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieve percentage of column/row columns.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected percentageColumns = (): ITableColumn[] => {
|
||||||
|
return R.pipe(
|
||||||
|
R.when(
|
||||||
|
this.query.isIncomePercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_income',
|
||||||
|
label: this.i18n.t('profit_loss_sheet.percentage_of_income'),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isExpensesPercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_expenses',
|
||||||
|
label: this.i18n.t('profit_loss_sheet.percentage_of_expenses'),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isColumnPercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_column',
|
||||||
|
label: this.i18n.t('profit_loss_sheet.percentage_of_column'),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isRowPercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_row',
|
||||||
|
label: this.i18n.t('profit_loss_sheet.percentage_of_row'),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// # Accessors.
|
||||||
|
// ----------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieves percentage of column/row accessors.
|
||||||
|
* @returns {ITableColumnAccessor[]}
|
||||||
|
*/
|
||||||
|
protected percentageColumnsAccessor = (): ITableColumnAccessor[] => {
|
||||||
|
return R.pipe(
|
||||||
|
R.when(
|
||||||
|
this.query.isIncomePercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_income',
|
||||||
|
accessor: 'percentageIncome.formattedAmount',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isExpensesPercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_expense',
|
||||||
|
accessor: 'percentageExpense.formattedAmount',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isColumnPercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_column',
|
||||||
|
accessor: 'percentageColumn.formattedAmount',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isRowPercentage,
|
||||||
|
R.append({
|
||||||
|
key: 'percentage_row',
|
||||||
|
accessor: 'percentageRow.formattedAmount',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves percentage horizontal columns accessors.
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected percetangeHorizontalColumnsAccessor = (
|
||||||
|
index: number,
|
||||||
|
): ITableColumnAccessor[] => {
|
||||||
|
return R.pipe(
|
||||||
|
R.when(
|
||||||
|
this.query.isIncomePercentage,
|
||||||
|
R.append({
|
||||||
|
key: `percentage_income-${index}`,
|
||||||
|
accessor: `horizontalTotals[${index}].percentageIncome.formattedAmount`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isExpensesPercentage,
|
||||||
|
R.append({
|
||||||
|
key: `percentage_expense-${index}`,
|
||||||
|
accessor: `horizontalTotals[${index}].percentageExpense.formattedAmount`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isColumnPercentage,
|
||||||
|
R.append({
|
||||||
|
key: `percentage_of_column-${index}`,
|
||||||
|
accessor: `horizontalTotals[${index}].percentageColumn.formattedAmount`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isRowPercentage,
|
||||||
|
R.append({
|
||||||
|
key: `percentage_of_row-${index}`,
|
||||||
|
accessor: `horizontalTotals[${index}].percentageRow.formattedAmount`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { TableSheetPdf } from '../../common/TableSheetPdf';
|
||||||
|
import { IProfitLossSheetQuery } from './ProfitLossSheet.types';
|
||||||
|
import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable';
|
||||||
|
import { HtmlTableCustomCss } from './constants';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProfitLossTablePdfInjectable {
|
||||||
|
constructor(
|
||||||
|
private readonly profitLossTable: ProfitLossSheetTableInjectable,
|
||||||
|
private readonly tableSheetPdf: TableSheetPdf,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profit/loss sheet in pdf format.
|
||||||
|
* @param {number} query
|
||||||
|
* @returns {Promise<IBalanceSheetTable>}
|
||||||
|
*/
|
||||||
|
public async pdf(query: IProfitLossSheetQuery): Promise<Buffer> {
|
||||||
|
const table = await this.profitLossTable.table(query);
|
||||||
|
|
||||||
|
return this.tableSheetPdf.convertToPdf(
|
||||||
|
table.table,
|
||||||
|
table.meta.sheetName,
|
||||||
|
table.meta.formattedDateRange,
|
||||||
|
HtmlTableCustomCss,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialTablePreviousPeriod } from '../../common/FinancialTablePreviousPeriod';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { IDateRange } from '../../types/Report.types';
|
||||||
|
|
||||||
|
export const ProfitLossTablePreviousPeriod = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialTablePreviousPeriod)(Base) {
|
||||||
|
query: ProfitLossSheetQuery;
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Columns
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Retrieves pervious period comparison columns.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected getPreviousPeriodColumns = (
|
||||||
|
dateRange?: IDateRange,
|
||||||
|
): ITableColumn[] => {
|
||||||
|
return R.pipe(
|
||||||
|
// Previous period columns.
|
||||||
|
R.append(this.getPreviousPeriodTotalColumn(dateRange)),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
R.append(this.getPreviousPeriodChangeColumn()),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
R.append(this.getPreviousPeriodPercentageColumn()),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose the previous period for date periods columns.
|
||||||
|
* @params {IDateRange}
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected getPreviousPeriodDatePeriodsPlugin = (
|
||||||
|
dateRange: IDateRange,
|
||||||
|
): ITableColumn[] => {
|
||||||
|
const PPDateRange = this.getPPDatePeriodDateRange(
|
||||||
|
dateRange.fromDate,
|
||||||
|
dateRange.toDate,
|
||||||
|
this.query.displayColumnsBy,
|
||||||
|
);
|
||||||
|
return this.getPreviousPeriodColumns(PPDateRange);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// # Accessors
|
||||||
|
// ----------------------------
|
||||||
|
/**
|
||||||
|
* Retrieves previous period columns accessors.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected previousPeriodColumnAccessor = (): ITableColumnAccessor[] => {
|
||||||
|
return R.pipe(
|
||||||
|
// Previous period columns.
|
||||||
|
R.append(this.getPreviousPeriodTotalAccessor()),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
R.append(this.getPreviousPeriodChangeAccessor()),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
R.append(this.getPreviousPeriodPercentageAccessor()),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous period period column accessor.
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected previousPeriodHorizontalColumnAccessors = (
|
||||||
|
index: number,
|
||||||
|
): ITableColumnAccessor[] => {
|
||||||
|
return R.pipe(
|
||||||
|
// Previous period columns.
|
||||||
|
R.append(this.getPreviousPeriodTotalHorizAccessor(index)),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodChangeActive,
|
||||||
|
R.append(this.getPreviousPeriodChangeHorizAccessor(index)),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousPeriodPercentageActive,
|
||||||
|
R.append(this.getPreviousPeriodPercentageHorizAccessor(index)),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { ProfitLossSheetQuery } from './ProfitLossSheetQuery';
|
||||||
|
import { GConstructor } from '@/common/types/Constructor';
|
||||||
|
import { FinancialSheet } from '../../common/FinancialSheet';
|
||||||
|
import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types';
|
||||||
|
import { IDateRange } from '../CashFlowStatement/Cashflow.types';
|
||||||
|
import { FinancialTablePreviousYear } from '../../common/FinancialTablePreviousYear';
|
||||||
|
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
|
||||||
|
|
||||||
|
export const ProfitLossTablePreviousYear = <
|
||||||
|
T extends GConstructor<FinancialSheet>,
|
||||||
|
>(
|
||||||
|
Base: T,
|
||||||
|
) =>
|
||||||
|
class extends R.pipe(FinancialTablePreviousYear, FinancialDateRanges)(Base) {
|
||||||
|
query: ProfitLossSheetQuery;
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// # Columns.
|
||||||
|
// ------------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieves pervious year comparison columns.
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected getPreviousYearColumns = (
|
||||||
|
dateRange?: IDateRange,
|
||||||
|
): ITableColumn[] => {
|
||||||
|
return R.pipe(
|
||||||
|
// Previous year columns.
|
||||||
|
R.append(this.getPreviousYearTotalColumn(dateRange)),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
R.append(this.getPreviousYearChangeColumn()),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
R.append(this.getPreviousYearPercentageColumn()),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose the previous year for date periods columns.
|
||||||
|
* @param {IDateRange} dateRange
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
private previousYearDatePeriodColumnCompose = (
|
||||||
|
dateRange: IDateRange,
|
||||||
|
): ITableColumn[] => {
|
||||||
|
const PYDateRange = this.getPreviousYearDateRange(
|
||||||
|
dateRange.fromDate,
|
||||||
|
dateRange.toDate,
|
||||||
|
);
|
||||||
|
return this.getPreviousYearColumns(PYDateRange);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves previous year date periods columns.
|
||||||
|
* @param {IDateRange} dateRange
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected getPreviousYearDatePeriodColumnPlugin = (
|
||||||
|
dateRange: IDateRange,
|
||||||
|
): ITableColumn[] => {
|
||||||
|
return this.previousYearDatePeriodColumnCompose(dateRange);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------
|
||||||
|
// # Accessors.
|
||||||
|
// ---------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Retrieves previous year columns accessors.
|
||||||
|
* @returns {ITableColumnAccessor[]}
|
||||||
|
*/
|
||||||
|
protected previousYearColumnAccessor = (): ITableColumnAccessor[] => {
|
||||||
|
return R.pipe(
|
||||||
|
// Previous year columns.
|
||||||
|
R.append(this.getPreviousYearTotalAccessor()),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
R.append(this.getPreviousYearChangeAccessor()),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
R.append(this.getPreviousYearPercentageAccessor()),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous year period column accessor.
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {ITableColumn[]}
|
||||||
|
*/
|
||||||
|
protected previousYearHorizontalColumnAccessors = (
|
||||||
|
index: number,
|
||||||
|
): ITableColumnAccessor[] => {
|
||||||
|
return R.pipe(
|
||||||
|
// Previous year columns.
|
||||||
|
R.append(this.getPreviousYearTotalHorizAccessor(index)),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearChangeActive,
|
||||||
|
R.append(this.getPreviousYearChangeHorizAccessor(index)),
|
||||||
|
),
|
||||||
|
R.when(
|
||||||
|
this.query.isPreviousYearPercentageActive,
|
||||||
|
R.append(this.getPreviousYearPercentageHorizAccessor(index)),
|
||||||
|
),
|
||||||
|
)([]);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { ProfitLossNodeType } from './ProfitLossSheet.types';
|
||||||
|
|
||||||
|
export const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' };
|
||||||
|
|
||||||
|
export const DISPLAY_COLUMNS_BY = {
|
||||||
|
DATE_PERIODS: 'date_periods',
|
||||||
|
TOTAL: 'total',
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum IROW_TYPE {
|
||||||
|
AGGREGATE = 'AGGREGATE',
|
||||||
|
ACCOUNTS = 'ACCOUNTS',
|
||||||
|
ACCOUNT = 'ACCOUNT',
|
||||||
|
TOTAL = 'TOTAL',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TOTAL_NODE_TYPES = [
|
||||||
|
ProfitLossNodeType.ACCOUNTS,
|
||||||
|
ProfitLossNodeType.AGGREGATE,
|
||||||
|
ProfitLossNodeType.EQUATION,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const HtmlTableCustomCss = `
|
||||||
|
table tr.row-type--total td {
|
||||||
|
font-weight: 600;
|
||||||
|
border-top: 1px solid #bbb;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
table tr.row-id--net-income td{
|
||||||
|
border-bottom: 3px double #000;
|
||||||
|
}
|
||||||
|
table .column--name,
|
||||||
|
table .cell--name {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table .column--total {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
table td.cell--total,
|
||||||
|
table td.cell--previous_year,
|
||||||
|
table td.cell--previous_year_change,
|
||||||
|
table td.cell--previous_year_percentage,
|
||||||
|
|
||||||
|
table td.cell--previous_period,
|
||||||
|
table td.cell--previous_period_change,
|
||||||
|
table td.cell--previous_period_percentage,
|
||||||
|
|
||||||
|
table td.cell--percentage_of_row,
|
||||||
|
table td.cell--percentage_of_column,
|
||||||
|
table td[class*="cell--date-range"] {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table .column--total,
|
||||||
|
table .column--previous_year,
|
||||||
|
table .column--previous_year_change,
|
||||||
|
table .column--previous_year_percentage,
|
||||||
|
|
||||||
|
table .column--previous_period,
|
||||||
|
table .column--previous_period_change,
|
||||||
|
table .column--previous_period_percentage,
|
||||||
|
|
||||||
|
table .column--percentage_of_row,
|
||||||
|
table .column--percentage_of_column,
|
||||||
|
table [class*="column--date-range"] {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import * as moment from 'moment';
|
||||||
|
import { merge } from 'lodash';
|
||||||
|
import { IProfitLossSheetQuery } from './ProfitLossSheet.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default sheet filter query.
|
||||||
|
* @return {IBalanceSheetQuery}
|
||||||
|
*/
|
||||||
|
export const getDefaultPLQuery = (): IProfitLossSheetQuery => ({
|
||||||
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
|
toDate: moment().format('YYYY-MM-DD'),
|
||||||
|
|
||||||
|
numberFormat: {
|
||||||
|
divideOn1000: false,
|
||||||
|
negativeFormat: 'mines',
|
||||||
|
showZero: false,
|
||||||
|
formatMoney: 'total',
|
||||||
|
precision: 2,
|
||||||
|
},
|
||||||
|
basis: 'accrual',
|
||||||
|
|
||||||
|
noneZero: false,
|
||||||
|
noneTransactions: false,
|
||||||
|
|
||||||
|
displayColumnsType: 'total',
|
||||||
|
displayColumnsBy: 'month',
|
||||||
|
|
||||||
|
accountsIds: [],
|
||||||
|
|
||||||
|
percentageColumn: false,
|
||||||
|
percentageRow: false,
|
||||||
|
|
||||||
|
percentageIncome: false,
|
||||||
|
percentageExpense: false,
|
||||||
|
|
||||||
|
previousPeriod: false,
|
||||||
|
previousPeriodAmountChange: false,
|
||||||
|
previousPeriodPercentageChange: false,
|
||||||
|
|
||||||
|
previousYear: false,
|
||||||
|
previousYearAmountChange: false,
|
||||||
|
previousYearPercentageChange: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const mergeQueryWithDefaults = (
|
||||||
|
query: IProfitLossSheetQuery,
|
||||||
|
): IProfitLossSheetQuery => {
|
||||||
|
return merge(getDefaultPLQuery(), query);
|
||||||
|
};
|
||||||
@@ -11,8 +11,9 @@
|
|||||||
"./src/modules/DynamicListing",
|
"./src/modules/DynamicListing",
|
||||||
"./src/modules/DynamicListing/**/*.ts",
|
"./src/modules/DynamicListing/**/*.ts",
|
||||||
"./src/modules/FinancialStatements/**/*.ts",
|
"./src/modules/FinancialStatements/**/*.ts",
|
||||||
// "./src/modules/FinancialStatements/modules/BalanceSheet/**.ts",
|
"./src/modules/StripePayment/**/*.ts",
|
||||||
|
"./src/modules/FinancialStatements/modules/BalanceSheet/**.ts",
|
||||||
|
"./src/modules/FinancialStatements/modules/ProfitLossSheet/**.ts",
|
||||||
"./src/modules/Views",
|
"./src/modules/Views",
|
||||||
"./src/modules/Expenses/subscribers"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,11 @@ import UnitOfWork from '../UnitOfWork';
|
|||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CreatePaymentReceiveStripePayment {
|
export class CreatePaymentReceiveStripePayment {
|
||||||
@Inject()
|
constructor(
|
||||||
private getSaleInvoiceService: GetSaleInvoice;
|
private readonly getSaleInvoiceService: GetSaleInvoice,
|
||||||
|
private readonly createPaymentReceivedService: CreatePaymentReceived,
|
||||||
@Inject()
|
private readonly uow: UnitOfWork
|
||||||
private createPaymentReceivedService: CreatePaymentReceived;
|
) {}
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private uow: UnitOfWork;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { Knex } from 'knex';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { ICashFlowStatementQuery } from './Cashflow.types';
|
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
|
||||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
|
||||||
import { ModelObject } from 'objection';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CashFlowRepository {
|
|
||||||
constructor(
|
|
||||||
@Inject(Account.name)
|
|
||||||
private readonly accountModel: typeof Account,
|
|
||||||
|
|
||||||
@Inject(AccountTransaction.name)
|
|
||||||
private readonly accountTransactionModel: typeof AccountTransaction,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the group type from periods type.
|
|
||||||
* @param {string} displayType
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
protected getGroupTypeFromPeriodsType(displayType: string) {
|
|
||||||
const displayTypes = {
|
|
||||||
year: 'year',
|
|
||||||
day: 'day',
|
|
||||||
month: 'month',
|
|
||||||
quarter: 'month',
|
|
||||||
week: 'day',
|
|
||||||
};
|
|
||||||
return displayTypes[displayType] || 'month';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the cashflow accounts.
|
|
||||||
* @returns {Promise<IAccount[]>}
|
|
||||||
*/
|
|
||||||
public async cashFlowAccounts(): Promise<Account[]> {
|
|
||||||
const accounts = await this.accountModel.query();
|
|
||||||
return accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve total of csah at beginning transactions.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {ICashFlowStatementQuery} filter -
|
|
||||||
* @return {Promise<IAccountTransaction[]>}
|
|
||||||
*/
|
|
||||||
public async cashAtBeginningTotalTransactions(
|
|
||||||
filter: ICashFlowStatementQuery,
|
|
||||||
): Promise<ModelObject<AccountTransaction>[]> {
|
|
||||||
const cashBeginningPeriod = moment(filter.fromDate)
|
|
||||||
.subtract(1, 'day')
|
|
||||||
.toDate();
|
|
||||||
|
|
||||||
const transactions = await this.accountTransactionModel
|
|
||||||
.query()
|
|
||||||
.onBuild((query) => {
|
|
||||||
query.modify('creditDebitSummation');
|
|
||||||
|
|
||||||
query.select('accountId');
|
|
||||||
query.groupBy('accountId');
|
|
||||||
|
|
||||||
query.withGraphFetched('account');
|
|
||||||
query.modify('filterDateRange', null, cashBeginningPeriod);
|
|
||||||
|
|
||||||
this.commonFilterBranchesQuery(filter, query);
|
|
||||||
});
|
|
||||||
return transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve accounts transactions.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {ICashFlowStatementQuery} filter
|
|
||||||
* @return {Promise<IAccountTransaction>}
|
|
||||||
*/
|
|
||||||
public async getAccountsTransactions(
|
|
||||||
filter: ICashFlowStatementQuery,
|
|
||||||
): Promise<ModelObject<AccountTransaction>[]> {
|
|
||||||
const groupByDateType = this.getGroupTypeFromPeriodsType(
|
|
||||||
filter.displayColumnsBy,
|
|
||||||
);
|
|
||||||
return await this.accountTransactionModel.query().onBuild((query) => {
|
|
||||||
query.modify('creditDebitSummation');
|
|
||||||
query.modify('groupByDateFormat', groupByDateType);
|
|
||||||
|
|
||||||
query.select('accountId');
|
|
||||||
|
|
||||||
query.groupBy('accountId');
|
|
||||||
query.withGraphFetched('account');
|
|
||||||
|
|
||||||
query.modify('filterDateRange', filter.fromDate, filter.toDate);
|
|
||||||
|
|
||||||
this.commonFilterBranchesQuery(filter, query);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the net income tranasctions.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {ICashFlowStatementQuery} query -
|
|
||||||
* @return {Promise<IAccountTransaction[]>}
|
|
||||||
*/
|
|
||||||
public async getNetIncomeTransactions(
|
|
||||||
filter: ICashFlowStatementQuery,
|
|
||||||
): Promise<AccountTransaction[]> {
|
|
||||||
const groupByDateType = this.getGroupTypeFromPeriodsType(
|
|
||||||
filter.displayColumnsBy,
|
|
||||||
);
|
|
||||||
return await this.accountTransactionModel.query().onBuild((query) => {
|
|
||||||
query.modify('creditDebitSummation');
|
|
||||||
query.modify('groupByDateFormat', groupByDateType);
|
|
||||||
|
|
||||||
query.select('accountId');
|
|
||||||
query.groupBy('accountId');
|
|
||||||
|
|
||||||
query.withGraphFetched('account');
|
|
||||||
query.modify('filterDateRange', filter.fromDate, filter.toDate);
|
|
||||||
|
|
||||||
this.commonFilterBranchesQuery(filter, query);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve peridos of cash at beginning transactions.
|
|
||||||
* @param {ICashFlowStatementQuery} filter -
|
|
||||||
* @return {Promise<ModelObject<AccountTransaction>[]>}
|
|
||||||
*/
|
|
||||||
public async cashAtBeginningPeriodTransactions(
|
|
||||||
filter: ICashFlowStatementQuery,
|
|
||||||
): Promise<ModelObject<AccountTransaction>[]> {
|
|
||||||
const groupByDateType = this.getGroupTypeFromPeriodsType(
|
|
||||||
filter.displayColumnsBy,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await this.accountTransactionModel.query().onBuild((query) => {
|
|
||||||
query.modify('creditDebitSummation');
|
|
||||||
query.modify('groupByDateFormat', groupByDateType);
|
|
||||||
|
|
||||||
query.select('accountId');
|
|
||||||
query.groupBy('accountId');
|
|
||||||
|
|
||||||
query.withGraphFetched('account');
|
|
||||||
query.modify('filterDateRange', filter.fromDate, filter.toDate);
|
|
||||||
|
|
||||||
this.commonFilterBranchesQuery(filter, query);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common branches filter query.
|
|
||||||
* @param {Knex.QueryBuilder} query
|
|
||||||
*/
|
|
||||||
private commonFilterBranchesQuery = (
|
|
||||||
query: ICashFlowStatementQuery,
|
|
||||||
knexQuery: Knex.QueryBuilder,
|
|
||||||
) => {
|
|
||||||
if (!isEmpty(query.branchesIds)) {
|
|
||||||
knexQuery.modify('filterByBranches', query.branchesIds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import { ModelObject } from 'objection';
|
|
||||||
import moment from 'moment';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import {
|
|
||||||
ICashFlowStatementQuery,
|
|
||||||
ICashFlowStatementDOO,
|
|
||||||
} from './Cashflow.types';
|
|
||||||
import CashFlowStatement from './CashFlow';
|
|
||||||
import { CashflowSheetMeta } from './CashflowSheetMeta';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { CashFlowRepository } from './CashFlowRepository';
|
|
||||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
|
||||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
|
||||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
|
||||||
import { I18nService } from 'nestjs-i18n';
|
|
||||||
import { getDefaultCashflowQuery } from './constants';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CashFlowStatementService {
|
|
||||||
/**
|
|
||||||
* @param {CashFlowRepository} cashFlowRepo - Cash flow repository.
|
|
||||||
* @param {CashflowSheetMeta} cashflowSheetMeta - Cashflow sheet meta.
|
|
||||||
* @param {TenancyContext} tenancyContext - Tenancy context.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
private readonly cashFlowRepo: CashFlowRepository,
|
|
||||||
private readonly cashflowSheetMeta: CashflowSheetMeta,
|
|
||||||
private readonly tenancyContext: TenancyContext,
|
|
||||||
private readonly i18n: I18nService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves cash at beginning transactions.
|
|
||||||
* @param {ICashFlowStatementQuery} filter - Cash flow statement query.
|
|
||||||
* @returns {Promise<ModelObject<AccountTransaction>[]>}
|
|
||||||
*/
|
|
||||||
private async cashAtBeginningTransactions(
|
|
||||||
filter: ICashFlowStatementQuery,
|
|
||||||
): Promise<ModelObject<AccountTransaction>[]> {
|
|
||||||
const appendPeriodsOperToChain = (trans) =>
|
|
||||||
R.append(
|
|
||||||
this.cashFlowRepo.cashAtBeginningPeriodTransactions(filter),
|
|
||||||
trans,
|
|
||||||
);
|
|
||||||
|
|
||||||
const promisesChain = R.pipe(
|
|
||||||
R.append(this.cashFlowRepo.cashAtBeginningTotalTransactions(filter)),
|
|
||||||
R.when(
|
|
||||||
R.always(R.equals(filter.displayColumnsType, 'date_periods')),
|
|
||||||
appendPeriodsOperToChain,
|
|
||||||
),
|
|
||||||
)([]);
|
|
||||||
const promisesResults = await Promise.all(promisesChain);
|
|
||||||
const transactions = R.flatten(promisesResults);
|
|
||||||
|
|
||||||
return transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the cash flow sheet statement.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {ICashFlowStatementQuery} query
|
|
||||||
* @returns {Promise<ICashFlowStatementDOO>}
|
|
||||||
*/
|
|
||||||
public async cashFlow(
|
|
||||||
query: ICashFlowStatementQuery,
|
|
||||||
): Promise<ICashFlowStatementDOO> {
|
|
||||||
// Retrieve all accounts on the storage.
|
|
||||||
const accounts = await this.cashFlowRepo.cashFlowAccounts();
|
|
||||||
const tenant = await this.tenancyContext.getTenant();
|
|
||||||
|
|
||||||
const filter = {
|
|
||||||
...getDefaultCashflowQuery(),
|
|
||||||
...query,
|
|
||||||
};
|
|
||||||
// Retrieve the accounts transactions.
|
|
||||||
const transactions =
|
|
||||||
await this.cashFlowRepo.getAccountsTransactions(filter);
|
|
||||||
// Retrieve the net income transactions.
|
|
||||||
const netIncome = await this.cashFlowRepo.getNetIncomeTransactions(filter);
|
|
||||||
// Retrieve the cash at beginning transactions.
|
|
||||||
const cashAtBeginningTransactions =
|
|
||||||
await this.cashAtBeginningTransactions(filter);
|
|
||||||
|
|
||||||
// Transformes the transactions to ledgers.
|
|
||||||
const ledger = Ledger.fromTransactions(transactions);
|
|
||||||
const cashLedger = Ledger.fromTransactions(cashAtBeginningTransactions);
|
|
||||||
const netIncomeLedger = Ledger.fromTransactions(netIncome);
|
|
||||||
|
|
||||||
// Cash flow statement.
|
|
||||||
const cashFlowInstance = new CashFlowStatement(
|
|
||||||
accounts,
|
|
||||||
ledger,
|
|
||||||
cashLedger,
|
|
||||||
netIncomeLedger,
|
|
||||||
filter,
|
|
||||||
tenant.metadata.baseCurrency,
|
|
||||||
this.i18n,
|
|
||||||
);
|
|
||||||
// Retrieve the cashflow sheet meta.
|
|
||||||
const meta = await this.cashflowSheetMeta.meta(filter);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: cashFlowInstance.reportData(),
|
|
||||||
query: filter,
|
|
||||||
meta,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { Response } from 'express';
|
|
||||||
import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
|
|
||||||
import { ICashFlowStatementQuery } from './Cashflow.types';
|
|
||||||
import { AcceptType } from '@/constants/accept-type';
|
|
||||||
import { CashflowSheetApplication } from './CashflowSheetApplication';
|
|
||||||
|
|
||||||
@Controller('reports/cashflow')
|
|
||||||
export class CashflowController {
|
|
||||||
constructor(private readonly cashflowSheetApp: CashflowSheetApplication) {}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
async getCashflow(
|
|
||||||
@Query() query: ICashFlowStatementQuery,
|
|
||||||
@Res() res: Response,
|
|
||||||
@Headers('accept') acceptHeader: string,
|
|
||||||
) {
|
|
||||||
const filter = {
|
|
||||||
...query,
|
|
||||||
};
|
|
||||||
// Retrieves the json table format.
|
|
||||||
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
|
|
||||||
const table = await this.cashflowSheetApp.table(filter);
|
|
||||||
|
|
||||||
return res.status(200).send(table);
|
|
||||||
// Retrieves the csv format.
|
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
|
|
||||||
const buffer = await this.cashflowSheetApp.csv(filter);
|
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
|
|
||||||
res.setHeader('Content-Type', 'text/csv');
|
|
||||||
|
|
||||||
return res.status(200).send(buffer);
|
|
||||||
// Retrieves the pdf format.
|
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
|
|
||||||
const buffer = await this.cashflowSheetApp.xlsx(filter);
|
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
|
|
||||||
res.setHeader(
|
|
||||||
'Content-Type',
|
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
||||||
);
|
|
||||||
return res.send(buffer);
|
|
||||||
// Retrieves the pdf format.
|
|
||||||
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
|
|
||||||
const pdfContent = await this.cashflowSheetApp.pdf(filter);
|
|
||||||
|
|
||||||
res.set({
|
|
||||||
'Content-Type': 'application/pdf',
|
|
||||||
'Content-Length': pdfContent.length,
|
|
||||||
});
|
|
||||||
return res.send(pdfContent);
|
|
||||||
// Retrieves the json format.
|
|
||||||
} else {
|
|
||||||
const cashflow = await this.cashflowSheetApp.sheet(filter);
|
|
||||||
|
|
||||||
return res.status(200).send(cashflow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { CashflowSheetMeta } from './CashflowSheetMeta';
|
|
||||||
import { CashFlowRepository } from './CashFlowRepository';
|
|
||||||
import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable';
|
|
||||||
import { CashflowExportInjectable } from './CashflowExportInjectable';
|
|
||||||
import { CashflowController } from './Cashflow.controller';
|
|
||||||
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
|
|
||||||
import { CashflowTableInjectable } from './CashflowTableInjectable';
|
|
||||||
import { CashFlowStatementService } from './CashFlowService';
|
|
||||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
|
||||||
import { CashflowSheetApplication } from './CashflowSheetApplication';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [FinancialSheetCommonModule],
|
|
||||||
providers: [
|
|
||||||
CashFlowRepository,
|
|
||||||
CashflowSheetMeta,
|
|
||||||
CashFlowStatementService,
|
|
||||||
CashflowTablePdfInjectable,
|
|
||||||
CashflowExportInjectable,
|
|
||||||
CashflowTableInjectable,
|
|
||||||
CashflowSheetApplication,
|
|
||||||
TenancyContext
|
|
||||||
],
|
|
||||||
controllers: [CashflowController],
|
|
||||||
})
|
|
||||||
export class CashflowReportModule {}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
import { CashflowExportInjectable } from './CashflowExportInjectable';
|
|
||||||
import { ICashFlowStatementQuery } from './Cashflow.types';
|
|
||||||
import { CashFlowStatementService } from './CashFlowService';
|
|
||||||
import { CashflowTableInjectable } from './CashflowTableInjectable';
|
|
||||||
import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CashflowSheetApplication {
|
|
||||||
constructor(
|
|
||||||
private readonly cashflowExport: CashflowExportInjectable,
|
|
||||||
private readonly cashflowSheet: CashFlowStatementService,
|
|
||||||
private readonly cashflowTable: CashflowTableInjectable,
|
|
||||||
private readonly cashflowPdf: CashflowTablePdfInjectable,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow sheet
|
|
||||||
* @param {ICashFlowStatementQuery} query - Cashflow statement query.
|
|
||||||
*/
|
|
||||||
public async sheet(query: ICashFlowStatementQuery) {
|
|
||||||
return this.cashflowSheet.cashFlow(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow sheet in table format.
|
|
||||||
* @param {ICashFlowStatementQuery} query - Cashflow statement query.
|
|
||||||
*/
|
|
||||||
public async table(query: ICashFlowStatementQuery) {
|
|
||||||
return this.cashflowTable.table(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow sheet in XLSX format.
|
|
||||||
* @param {ICashFlowStatementQuery} query - Cashflow statement query.
|
|
||||||
* @returns {Promise<Buffer>}
|
|
||||||
*/
|
|
||||||
public async xlsx(query: ICashFlowStatementQuery) {
|
|
||||||
return this.cashflowExport.xlsx(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow sheet in CSV format.
|
|
||||||
* @param {ICashFlowStatementQuery} query - Cashflow statement query.
|
|
||||||
* @returns {Promise<Buffer>}
|
|
||||||
*/
|
|
||||||
public async csv(query: ICashFlowStatementQuery): Promise<string> {
|
|
||||||
return this.cashflowExport.csv(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow sheet in pdf format.
|
|
||||||
* @param {ICashFlowStatementQuery} query - Cashflow statement query.
|
|
||||||
* @returns {Promise<Buffer>}
|
|
||||||
*/
|
|
||||||
public async pdf(query: ICashFlowStatementQuery): Promise<Buffer> {
|
|
||||||
return this.cashflowPdf.pdf(query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
|
|
||||||
import { ICashFlowStatementMeta, ICashFlowStatementQuery } from './Cashflow.types';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CashflowSheetMeta {
|
|
||||||
constructor(
|
|
||||||
private readonly financialSheetMeta: FinancialSheetMeta,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cashflow sheet meta.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {ICashFlowStatementQuery} query
|
|
||||||
* @returns {Promise<ICashFlowStatementMeta>}
|
|
||||||
*/
|
|
||||||
public async meta(
|
|
||||||
query: ICashFlowStatementQuery
|
|
||||||
): Promise<ICashFlowStatementMeta> {
|
|
||||||
const meta = await this.financialSheetMeta.meta();
|
|
||||||
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD');
|
|
||||||
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD');
|
|
||||||
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
|
|
||||||
|
|
||||||
const sheetName = 'Statement of Cash Flow';
|
|
||||||
|
|
||||||
return {
|
|
||||||
...meta,
|
|
||||||
sheetName,
|
|
||||||
formattedToDate,
|
|
||||||
formattedFromDate,
|
|
||||||
formattedDateRange,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,705 +0,0 @@
|
|||||||
import * as R from 'ramda';
|
|
||||||
import { defaultTo, map, set, sumBy, isEmpty, mapValues, get } from 'lodash';
|
|
||||||
import * as mathjs from 'mathjs';
|
|
||||||
import * as moment from 'moment';
|
|
||||||
import { compose } from 'lodash/fp';
|
|
||||||
import { I18nService } from 'nestjs-i18n';
|
|
||||||
import {
|
|
||||||
ICashFlowSchemaSection,
|
|
||||||
ICashFlowStatementQuery,
|
|
||||||
ICashFlowStatementNetIncomeSection,
|
|
||||||
ICashFlowStatementAccountSection,
|
|
||||||
ICashFlowSchemaSectionAccounts,
|
|
||||||
ICashFlowStatementAccountMeta,
|
|
||||||
ICashFlowSchemaAccountRelation,
|
|
||||||
ICashFlowStatementSectionType,
|
|
||||||
ICashFlowStatementData,
|
|
||||||
ICashFlowSchemaTotalSection,
|
|
||||||
ICashFlowStatementTotalSection,
|
|
||||||
ICashFlowStatementSection,
|
|
||||||
ICashFlowCashBeginningNode,
|
|
||||||
ICashFlowStatementAggregateSection,
|
|
||||||
} from './Cashflow.types';
|
|
||||||
import { CASH_FLOW_SCHEMA } from './schema';
|
|
||||||
import { FinancialSheet } from '../../common/FinancialSheet';
|
|
||||||
import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts';
|
|
||||||
import { CashFlowStatementDatePeriods } from './CashFlowDatePeriods';
|
|
||||||
import { DISPLAY_COLUMNS_BY } from './constants';
|
|
||||||
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
|
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
|
||||||
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
|
|
||||||
import { INumberFormatQuery } from '../../types/Report.types';
|
|
||||||
import { transformToMapBy } from '@/utils/transform-to-map-by';
|
|
||||||
import { accumSum } from '@/utils/accum-sum';
|
|
||||||
import { ModelObject } from 'objection';
|
|
||||||
|
|
||||||
export default class CashFlowStatement extends compose(
|
|
||||||
CashFlowStatementDatePeriods,
|
|
||||||
FinancialSheetStructure
|
|
||||||
)(FinancialSheet) {
|
|
||||||
readonly baseCurrency: string;
|
|
||||||
readonly i18n: I18nService;
|
|
||||||
readonly sectionsByIds = {};
|
|
||||||
readonly cashFlowSchemaMap: Map<string, ICashFlowSchemaSection>;
|
|
||||||
readonly cashFlowSchemaSeq: Array<string>;
|
|
||||||
readonly accountByTypeMap: Map<string, Account[]>;
|
|
||||||
readonly accountsByRootType: Map<string, Account[]>;
|
|
||||||
readonly ledger: ILedger;
|
|
||||||
readonly cashLedger: ILedger;
|
|
||||||
readonly netIncomeLedger: ILedger;
|
|
||||||
readonly query: ICashFlowStatementQuery;
|
|
||||||
readonly numberFormat: INumberFormatQuery;
|
|
||||||
readonly comparatorDateType: string;
|
|
||||||
readonly dateRangeSet: { fromDate: Date; toDate: Date }[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
accounts: Account[],
|
|
||||||
ledger: ILedger,
|
|
||||||
cashLedger: ILedger,
|
|
||||||
netIncomeLedger: ILedger,
|
|
||||||
query: ICashFlowStatementQuery,
|
|
||||||
baseCurrency: string,
|
|
||||||
i18n
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.baseCurrency = baseCurrency;
|
|
||||||
this.i18n = i18n;
|
|
||||||
this.ledger = ledger;
|
|
||||||
this.cashLedger = cashLedger;
|
|
||||||
this.netIncomeLedger = netIncomeLedger;
|
|
||||||
this.accountByTypeMap = transformToMapBy(accounts, 'accountType');
|
|
||||||
this.accountsByRootType = transformToMapBy(accounts, 'accountRootType');
|
|
||||||
this.query = query;
|
|
||||||
this.numberFormat = this.query.numberFormat;
|
|
||||||
this.dateRangeSet = [];
|
|
||||||
this.comparatorDateType =
|
|
||||||
query.displayColumnsType === 'total' ? 'day' : query.displayColumnsBy;
|
|
||||||
|
|
||||||
this.initDateRangeCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
// # GENERAL UTILITIES
|
|
||||||
// --------------------------------------------
|
|
||||||
/**
|
|
||||||
* Retrieve the expense accounts ids.
|
|
||||||
* @return {number[]}
|
|
||||||
*/
|
|
||||||
private getAccountsIdsByType = (accountType: string): number[] => {
|
|
||||||
const expenseAccounts = this.accountsByRootType.get(accountType);
|
|
||||||
const expenseAccountsIds = map(expenseAccounts, 'id');
|
|
||||||
|
|
||||||
return expenseAccountsIds;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines the given display columns by type.
|
|
||||||
* @param {string} displayColumnsBy
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isDisplayColumnsBy = (displayColumnsBy: string): boolean => {
|
|
||||||
return this.query.displayColumnsType === displayColumnsBy;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjustments the given amount.
|
|
||||||
* @param {string} direction
|
|
||||||
* @param {number} amount -
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
private amountAdjustment = (direction: 'mines' | 'plus', amount): number => {
|
|
||||||
return R.when(
|
|
||||||
R.always(R.equals(direction, 'mines')),
|
|
||||||
R.multiply(-1)
|
|
||||||
)(amount);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
// # NET INCOME NODE
|
|
||||||
// --------------------------------------------
|
|
||||||
/**
|
|
||||||
* Retrieve the accounts net income.
|
|
||||||
* @returns {number} - Amount of net income.
|
|
||||||
*/
|
|
||||||
private getAccountsNetIncome(): number {
|
|
||||||
// Mapping income/expense accounts ids.
|
|
||||||
const incomeAccountsIds = this.getAccountsIdsByType(
|
|
||||||
ACCOUNT_ROOT_TYPE.INCOME
|
|
||||||
);
|
|
||||||
const expenseAccountsIds = this.getAccountsIdsByType(
|
|
||||||
ACCOUNT_ROOT_TYPE.EXPENSE
|
|
||||||
);
|
|
||||||
// Income closing balance.
|
|
||||||
const incomeClosingBalance = accumSum(incomeAccountsIds, (id) =>
|
|
||||||
this.netIncomeLedger.whereAccountId(id).getClosingBalance()
|
|
||||||
);
|
|
||||||
// Expense closing balance.
|
|
||||||
const expenseClosingBalance = accumSum(expenseAccountsIds, (id) =>
|
|
||||||
this.netIncomeLedger.whereAccountId(id).getClosingBalance()
|
|
||||||
);
|
|
||||||
// Net income = income - expenses.
|
|
||||||
const netIncome = incomeClosingBalance - expenseClosingBalance;
|
|
||||||
|
|
||||||
return netIncome;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the net income section from the given section schema.
|
|
||||||
* @param {ICashFlowSchemaSection} nodeSchema - Report section schema.
|
|
||||||
* @returns {ICashFlowStatementNetIncomeSection}
|
|
||||||
*/
|
|
||||||
private netIncomeSectionMapper = (
|
|
||||||
nodeSchema: ICashFlowSchemaSection
|
|
||||||
): ICashFlowStatementNetIncomeSection => {
|
|
||||||
const netIncome = this.getAccountsNetIncome();
|
|
||||||
|
|
||||||
const node = {
|
|
||||||
id: nodeSchema.id,
|
|
||||||
label: this.i18n.__(nodeSchema.label),
|
|
||||||
total: this.getAmountMeta(netIncome),
|
|
||||||
sectionType: ICashFlowStatementSectionType.NET_INCOME,
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
|
||||||
this.assocPeriodsToNetIncomeNode
|
|
||||||
)
|
|
||||||
)(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
// # ACCOUNT NODE
|
|
||||||
// --------------------------------------------
|
|
||||||
/**
|
|
||||||
* Retrieve account meta.
|
|
||||||
* @param {ICashFlowSchemaAccountRelation} relation - Account relation.
|
|
||||||
* @param {IAccount} account -
|
|
||||||
* @returns {ICashFlowStatementAccountMeta}
|
|
||||||
*/
|
|
||||||
private accountMetaMapper = (
|
|
||||||
relation: ICashFlowSchemaAccountRelation,
|
|
||||||
account: ModelObject<Account>
|
|
||||||
): ICashFlowStatementAccountMeta => {
|
|
||||||
// Retrieve the closing balance of the given account.
|
|
||||||
const getClosingBalance = (id) =>
|
|
||||||
this.ledger.whereAccountId(id).getClosingBalance();
|
|
||||||
|
|
||||||
const closingBalance = R.compose(
|
|
||||||
// Multiplies the amount by -1 in case the relation in mines.
|
|
||||||
R.curry(this.amountAdjustment)(relation.direction)
|
|
||||||
)(getClosingBalance(account.id));
|
|
||||||
|
|
||||||
const node = {
|
|
||||||
id: account.id,
|
|
||||||
code: account.code,
|
|
||||||
label: account.name,
|
|
||||||
accountType: account.accountType,
|
|
||||||
adjustmentType: relation.direction,
|
|
||||||
total: this.getAmountMeta(closingBalance),
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNT,
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
|
||||||
this.assocPeriodsToAccountNode
|
|
||||||
)
|
|
||||||
)(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve accounts sections by the given schema relation.
|
|
||||||
* @param {ICashFlowSchemaAccountRelation} relation
|
|
||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
|
||||||
*/
|
|
||||||
private getAccountsBySchemaRelation = (
|
|
||||||
relation: ICashFlowSchemaAccountRelation
|
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
|
||||||
const accounts = defaultTo(this.accountByTypeMap.get(relation.type), []);
|
|
||||||
const accountMetaMapper = R.curry(this.accountMetaMapper)(relation);
|
|
||||||
return R.map(accountMetaMapper)(accounts);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the accounts meta.
|
|
||||||
* @param {string[]} types
|
|
||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
|
||||||
*/
|
|
||||||
private getAccountsBySchemaRelations = (
|
|
||||||
relations: ICashFlowSchemaAccountRelation[]
|
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
|
||||||
return R.pipe(
|
|
||||||
R.append(R.map(this.getAccountsBySchemaRelation)(relations)),
|
|
||||||
R.flatten
|
|
||||||
)([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the accounts total
|
|
||||||
* @param {ICashFlowStatementAccountMeta[]} accounts
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
private getAccountsMetaTotal = (
|
|
||||||
accounts: ICashFlowStatementAccountMeta[]
|
|
||||||
): number => {
|
|
||||||
return sumBy(accounts, 'total.amount');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the accounts section from the section schema.
|
|
||||||
* @param {ICashFlowSchemaSectionAccounts} sectionSchema
|
|
||||||
* @returns {ICashFlowStatementAccountSection}
|
|
||||||
*/
|
|
||||||
private accountsSectionParser = (
|
|
||||||
sectionSchema: ICashFlowSchemaSectionAccounts
|
|
||||||
): ICashFlowStatementAccountSection => {
|
|
||||||
const { accountsRelations } = sectionSchema;
|
|
||||||
|
|
||||||
const accounts = this.getAccountsBySchemaRelations(accountsRelations);
|
|
||||||
const accountsTotal = this.getAccountsMetaTotal(accounts);
|
|
||||||
const total = this.getTotalAmountMeta(accountsTotal);
|
|
||||||
|
|
||||||
const node = {
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNTS,
|
|
||||||
id: sectionSchema.id,
|
|
||||||
label: this.i18n.__(sectionSchema.label),
|
|
||||||
footerLabel: this.i18n.__(sectionSchema.footerLabel),
|
|
||||||
children: accounts,
|
|
||||||
total,
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
|
||||||
this.assocPeriodsToAggregateNode
|
|
||||||
)
|
|
||||||
)(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines the schema section type.
|
|
||||||
* @param {string} type
|
|
||||||
* @param {ICashFlowSchemaSection} section
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isSchemaSectionType = R.curry(
|
|
||||||
(type: string, section: ICashFlowSchemaSection): boolean => {
|
|
||||||
return type === section.sectionType;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
// # AGGREGATE NODE
|
|
||||||
// --------------------------------------------
|
|
||||||
/**
|
|
||||||
* Aggregate schema node parser to aggregate report node.
|
|
||||||
* @param {ICashFlowSchemaSection} schemaSection
|
|
||||||
* @returns {ICashFlowStatementAggregateSection}
|
|
||||||
*/
|
|
||||||
private regularSectionParser = R.curry(
|
|
||||||
(
|
|
||||||
children,
|
|
||||||
schemaSection: ICashFlowSchemaSection
|
|
||||||
): ICashFlowStatementAggregateSection => {
|
|
||||||
const node = {
|
|
||||||
id: schemaSection.id,
|
|
||||||
label: this.i18n.__(schemaSection.label),
|
|
||||||
footerLabel: this.i18n.__(schemaSection.footerLabel),
|
|
||||||
sectionType: ICashFlowStatementSectionType.AGGREGATE,
|
|
||||||
children,
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
|
||||||
this.assocRegularSectionTotal
|
|
||||||
),
|
|
||||||
R.when(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
|
||||||
R.when(
|
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
|
||||||
this.assocPeriodsToAggregateNode
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)(node);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
private transformSectionsToMap = (sections: ICashFlowSchemaSection[]) => {
|
|
||||||
return this.reduceNodesDeep(
|
|
||||||
sections,
|
|
||||||
(acc, section) => {
|
|
||||||
if (section.id) {
|
|
||||||
acc[`${section.id}`] = section;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
// # TOTAL EQUATION NODE
|
|
||||||
// --------------------------------------------
|
|
||||||
|
|
||||||
private sectionsMapToTotal = (mappedSections: { [key: number]: any }) => {
|
|
||||||
return mapValues(mappedSections, (node) => get(node, 'total.amount') || 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Evauluate equaation string with the given scope table.
|
|
||||||
* @param {string} equation -
|
|
||||||
* @param {{ [key: string]: number }} scope -
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
private evaluateEquation = (
|
|
||||||
equation: string,
|
|
||||||
scope: { [key: string | number]: number }
|
|
||||||
): number => {
|
|
||||||
return mathjs.evaluate(equation, scope);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the total section from the eqauation parser.
|
|
||||||
* @param {ICashFlowSchemaTotalSection} sectionSchema
|
|
||||||
* @param {ICashFlowSchemaSection[]} accumulatedSections
|
|
||||||
* @returns {ICashFlowStatementTotalSection}
|
|
||||||
*/
|
|
||||||
private totalEquationSectionParser = (
|
|
||||||
accumulatedSections: ICashFlowSchemaSection[],
|
|
||||||
sectionSchema: ICashFlowSchemaTotalSection
|
|
||||||
): ICashFlowStatementTotalSection => {
|
|
||||||
const mappedSectionsById = this.transformSectionsToMap(accumulatedSections);
|
|
||||||
const nodesTotalById = this.sectionsMapToTotal(mappedSectionsById);
|
|
||||||
|
|
||||||
const total = this.evaluateEquation(sectionSchema.equation, nodesTotalById);
|
|
||||||
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
|
||||||
R.curry(this.assocTotalEquationDatePeriods)(
|
|
||||||
mappedSectionsById,
|
|
||||||
sectionSchema.equation
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)({
|
|
||||||
sectionType: ICashFlowStatementSectionType.TOTAL,
|
|
||||||
id: sectionSchema.id,
|
|
||||||
label: this.i18n.__(sectionSchema.label),
|
|
||||||
total: this.getTotalAmountMeta(total),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the beginning cash from date.
|
|
||||||
* @param {Date|string} fromDate -
|
|
||||||
* @return {Date}
|
|
||||||
*/
|
|
||||||
private beginningCashFrom = (fromDate: string | Date): Date => {
|
|
||||||
return moment(fromDate).subtract(1, 'days').toDate();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve account meta.
|
|
||||||
* @param {ICashFlowSchemaAccountRelation} relation
|
|
||||||
* @param {IAccount} account
|
|
||||||
* @returns {ICashFlowStatementAccountMeta}
|
|
||||||
*/
|
|
||||||
private cashAccountMetaMapper = (
|
|
||||||
relation: ICashFlowSchemaAccountRelation,
|
|
||||||
account: ModelObject<Account>
|
|
||||||
): ICashFlowStatementAccountMeta => {
|
|
||||||
const cashToDate = this.beginningCashFrom(this.query.fromDate);
|
|
||||||
|
|
||||||
const closingBalance = this.cashLedger
|
|
||||||
.whereToDate(cashToDate)
|
|
||||||
.whereAccountId(account.id)
|
|
||||||
.getClosingBalance();
|
|
||||||
|
|
||||||
const node = {
|
|
||||||
id: account.id,
|
|
||||||
code: account.code,
|
|
||||||
label: account.name,
|
|
||||||
accountType: account.accountType,
|
|
||||||
adjustmentType: relation.direction,
|
|
||||||
total: this.getAmountMeta(closingBalance),
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNT,
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
|
||||||
this.assocCashAtBeginningAccountDatePeriods
|
|
||||||
)
|
|
||||||
)(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve accounts sections by the given schema relation.
|
|
||||||
* @param {ICashFlowSchemaAccountRelation} relation
|
|
||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
|
||||||
*/
|
|
||||||
private getCashAccountsBySchemaRelation = (
|
|
||||||
relation: ICashFlowSchemaAccountRelation
|
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
|
||||||
const accounts = this.accountByTypeMap.get(relation.type) || [];
|
|
||||||
const accountMetaMapper = R.curry(this.cashAccountMetaMapper)(relation);
|
|
||||||
return accounts.map(accountMetaMapper);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the accounts meta.
|
|
||||||
* @param {string[]} types
|
|
||||||
* @returns {ICashFlowStatementAccountMeta[]}
|
|
||||||
*/
|
|
||||||
private getCashAccountsBySchemaRelations = (
|
|
||||||
relations: ICashFlowSchemaAccountRelation[]
|
|
||||||
): ICashFlowStatementAccountMeta[] => {
|
|
||||||
return R.concat(...R.map(this.getCashAccountsBySchemaRelation)(relations));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the cash at beginning section.
|
|
||||||
* @param {ICashFlowSchemaTotalSection} sectionSchema -
|
|
||||||
* @return {ICashFlowCashBeginningNode}
|
|
||||||
*/
|
|
||||||
private cashAtBeginningSectionParser = (
|
|
||||||
nodeSchema: ICashFlowSchemaSection
|
|
||||||
): ICashFlowCashBeginningNode => {
|
|
||||||
const { accountsRelations } = nodeSchema;
|
|
||||||
const children = this.getCashAccountsBySchemaRelations(accountsRelations);
|
|
||||||
const total = this.getAccountsMetaTotal(children);
|
|
||||||
|
|
||||||
const node = {
|
|
||||||
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING,
|
|
||||||
id: nodeSchema.id,
|
|
||||||
label: this.i18n.__(nodeSchema.label),
|
|
||||||
children,
|
|
||||||
total: this.getTotalAmountMeta(total),
|
|
||||||
};
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
|
|
||||||
this.assocCashAtBeginningDatePeriods
|
|
||||||
)
|
|
||||||
)(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the schema section.
|
|
||||||
* @param {ICashFlowSchemaSection} schemaNode
|
|
||||||
* @returns {ICashFlowSchemaSection}
|
|
||||||
*/
|
|
||||||
private schemaSectionParser = (
|
|
||||||
schemaNode: ICashFlowSchemaSection,
|
|
||||||
children
|
|
||||||
): ICashFlowSchemaSection | ICashFlowStatementSection => {
|
|
||||||
return R.compose(
|
|
||||||
// Accounts node.
|
|
||||||
R.when(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS),
|
|
||||||
this.accountsSectionParser
|
|
||||||
),
|
|
||||||
// Net income node.
|
|
||||||
R.when(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.NET_INCOME),
|
|
||||||
this.netIncomeSectionMapper
|
|
||||||
),
|
|
||||||
// Cash at beginning node.
|
|
||||||
R.when(
|
|
||||||
this.isSchemaSectionType(
|
|
||||||
ICashFlowStatementSectionType.CASH_AT_BEGINNING
|
|
||||||
),
|
|
||||||
this.cashAtBeginningSectionParser
|
|
||||||
),
|
|
||||||
// Aggregate node. (that has no section type).
|
|
||||||
R.when(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE),
|
|
||||||
this.regularSectionParser(children)
|
|
||||||
)
|
|
||||||
)(schemaNode);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the schema section.
|
|
||||||
* @param {ICashFlowSchemaSection | ICashFlowStatementSection} section
|
|
||||||
* @param {number} key
|
|
||||||
* @param {ICashFlowSchemaSection[]} parentValue
|
|
||||||
* @param {(ICashFlowSchemaSection | ICashFlowStatementSection)[]} accumulatedSections
|
|
||||||
* @returns {ICashFlowSchemaSection}
|
|
||||||
*/
|
|
||||||
private schemaSectionTotalParser = (
|
|
||||||
section: ICashFlowSchemaSection | ICashFlowStatementSection,
|
|
||||||
key: number,
|
|
||||||
parentValue: ICashFlowSchemaSection[],
|
|
||||||
context,
|
|
||||||
accumulatedSections: (ICashFlowSchemaSection | ICashFlowStatementSection)[]
|
|
||||||
): ICashFlowSchemaSection | ICashFlowStatementSection => {
|
|
||||||
return R.compose(
|
|
||||||
// Total equation section.
|
|
||||||
R.when(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.TOTAL),
|
|
||||||
R.curry(this.totalEquationSectionParser)(accumulatedSections)
|
|
||||||
)
|
|
||||||
)(section);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schema sections parser.
|
|
||||||
* @param {ICashFlowSchemaSection[]}schema
|
|
||||||
* @returns {ICashFlowStatementSection[]}
|
|
||||||
*/
|
|
||||||
private schemaSectionsParser = (
|
|
||||||
schema: ICashFlowSchemaSection[]
|
|
||||||
): ICashFlowStatementSection[] => {
|
|
||||||
return this.mapNodesDeepReverse(schema, this.schemaSectionParser);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the `total` property to the aggregate node.
|
|
||||||
* @param {ICashFlowStatementSection} section
|
|
||||||
* @return {ICashFlowStatementSection}
|
|
||||||
*/
|
|
||||||
private assocRegularSectionTotal = (section: ICashFlowStatementSection) => {
|
|
||||||
const total = this.getAccountsMetaTotal(section.children);
|
|
||||||
return R.assoc('total', this.getTotalAmountMeta(total), section);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses total schema nodes.
|
|
||||||
* @param {(ICashFlowSchemaSection | ICashFlowStatementSection)[]} sections
|
|
||||||
* @returns {(ICashFlowSchemaSection | ICashFlowStatementSection)[]}
|
|
||||||
*/
|
|
||||||
private totalSectionsParser = (
|
|
||||||
sections: (ICashFlowSchemaSection | ICashFlowStatementSection)[]
|
|
||||||
): (ICashFlowSchemaSection | ICashFlowStatementSection)[] => {
|
|
||||||
return this.reduceNodesDeep(
|
|
||||||
sections,
|
|
||||||
(acc, value, key, parentValue, context) => {
|
|
||||||
set(
|
|
||||||
acc,
|
|
||||||
context.path,
|
|
||||||
this.schemaSectionTotalParser(value, key, parentValue, context, acc)
|
|
||||||
);
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
// REPORT FILTERING
|
|
||||||
// --------------------------------------------
|
|
||||||
/**
|
|
||||||
* Detarmines the given section has children and not empty.
|
|
||||||
* @param {ICashFlowStatementSection} section
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isSectionHasChildren = (
|
|
||||||
section: ICashFlowStatementSection
|
|
||||||
): boolean => {
|
|
||||||
return !isEmpty(section.children);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the section has no zero amount.
|
|
||||||
* @param {ICashFlowStatementSection} section
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isSectionNoneZero = (section: ICashFlowStatementSection): boolean => {
|
|
||||||
return section.total.amount !== 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines whether the parent accounts sections has children.
|
|
||||||
* @param {ICashFlowStatementSection} section
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isAccountsSectionHasChildren = (
|
|
||||||
section: ICashFlowStatementSection[]
|
|
||||||
): boolean => {
|
|
||||||
return R.ifElse(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS),
|
|
||||||
this.isSectionHasChildren,
|
|
||||||
R.always(true)
|
|
||||||
)(section);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detarmines the account section has no zero otherwise returns true.
|
|
||||||
* @param {ICashFlowStatementSection} section
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
private isAccountLeafNoneZero = (
|
|
||||||
section: ICashFlowStatementSection[]
|
|
||||||
): boolean => {
|
|
||||||
return R.ifElse(
|
|
||||||
this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNT),
|
|
||||||
this.isSectionNoneZero,
|
|
||||||
R.always(true)
|
|
||||||
)(section);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deep filters the non-zero accounts leafs of the report sections.
|
|
||||||
* @param {ICashFlowStatementSection[]} sections
|
|
||||||
* @returns {ICashFlowStatementSection[]}
|
|
||||||
*/
|
|
||||||
private filterNoneZeroAccountsLeafs = (
|
|
||||||
sections: ICashFlowStatementSection[]
|
|
||||||
): ICashFlowStatementSection[] => {
|
|
||||||
return this.filterNodesDeep(sections, this.isAccountLeafNoneZero);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deep filter the non-children sections of the report sections.
|
|
||||||
* @param {ICashFlowStatementSection[]} sections
|
|
||||||
* @returns {ICashFlowStatementSection[]}
|
|
||||||
*/
|
|
||||||
private filterNoneChildrenSections = (
|
|
||||||
sections: ICashFlowStatementSection[]
|
|
||||||
): ICashFlowStatementSection[] => {
|
|
||||||
return this.filterNodesDeep(sections, this.isAccountsSectionHasChildren);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters the report data.
|
|
||||||
* @param {ICashFlowStatementSection[]} sections
|
|
||||||
* @returns {ICashFlowStatementSection[]}
|
|
||||||
*/
|
|
||||||
private filterReportData = (
|
|
||||||
sections: ICashFlowStatementSection[]
|
|
||||||
): ICashFlowStatementSection[] => {
|
|
||||||
return R.compose(
|
|
||||||
this.filterNoneChildrenSections,
|
|
||||||
this.filterNoneZeroAccountsLeafs
|
|
||||||
)(sections);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schema parser.
|
|
||||||
* @param {ICashFlowSchemaSection[]} schema
|
|
||||||
* @returns {ICashFlowSchemaSection[]}
|
|
||||||
*/
|
|
||||||
private schemaParser = (
|
|
||||||
schema: ICashFlowSchemaSection[]
|
|
||||||
): ICashFlowSchemaSection[] => {
|
|
||||||
return R.compose(
|
|
||||||
R.when(
|
|
||||||
R.always(this.query.noneTransactions || this.query.noneZero),
|
|
||||||
this.filterReportData
|
|
||||||
),
|
|
||||||
this.totalSectionsParser,
|
|
||||||
this.schemaSectionsParser
|
|
||||||
)(schema);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the cashflow statement data.
|
|
||||||
* @return {ICashFlowStatementData}
|
|
||||||
*/
|
|
||||||
public reportData = (): ICashFlowStatementData => {
|
|
||||||
return this.schemaParser(R.clone(CASH_FLOW_SCHEMA));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,412 +0,0 @@
|
|||||||
import * as R from 'ramda';
|
|
||||||
import { sumBy, mapValues, get } from 'lodash';
|
|
||||||
import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts';
|
|
||||||
import {
|
|
||||||
ICashFlowDatePeriod,
|
|
||||||
ICashFlowStatementNetIncomeSection,
|
|
||||||
ICashFlowStatementAccountSection,
|
|
||||||
ICashFlowStatementSection,
|
|
||||||
ICashFlowSchemaTotalSection,
|
|
||||||
ICashFlowStatementTotalSection,
|
|
||||||
ICashFlowStatementQuery,
|
|
||||||
IDateRange,
|
|
||||||
} from './Cashflow.types';
|
|
||||||
import { IFormatNumberSettings } from '../../types/Report.types';
|
|
||||||
import { dateRangeFromToCollection } from '@/utils/date-range-collection';
|
|
||||||
import { accumSum } from '@/utils/accum-sum';
|
|
||||||
|
|
||||||
export const CashFlowStatementDatePeriods = (Base) =>
|
|
||||||
class extends Base {
|
|
||||||
dateRangeSet: IDateRange[];
|
|
||||||
query: ICashFlowStatementQuery;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize date range set.
|
|
||||||
*/
|
|
||||||
public initDateRangeCollection() {
|
|
||||||
this.dateRangeSet = dateRangeFromToCollection(
|
|
||||||
this.query.fromDate,
|
|
||||||
this.query.toDate,
|
|
||||||
this.comparatorDateType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the date period meta.
|
|
||||||
* @param {number} total - Total amount.
|
|
||||||
* @param {Date} fromDate - From date.
|
|
||||||
* @param {Date} toDate - To date.
|
|
||||||
* @return {ICashFlowDatePeriod}
|
|
||||||
*/
|
|
||||||
public getDatePeriodTotalMeta = (
|
|
||||||
total: number,
|
|
||||||
fromDate: Date,
|
|
||||||
toDate: Date,
|
|
||||||
overrideSettings: IFormatNumberSettings = {}
|
|
||||||
): ICashFlowDatePeriod => {
|
|
||||||
return this.getDatePeriodMeta(total, fromDate, toDate, {
|
|
||||||
money: true,
|
|
||||||
...overrideSettings,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the date period meta.
|
|
||||||
* @param {number} total - Total amount.
|
|
||||||
* @param {Date} fromDate - From date.
|
|
||||||
* @param {Date} toDate - To date.
|
|
||||||
* @return {ICashFlowDatePeriod}
|
|
||||||
*/
|
|
||||||
public getDatePeriodMeta = (
|
|
||||||
total: number,
|
|
||||||
fromDate: Date,
|
|
||||||
toDate: Date,
|
|
||||||
overrideSettings?: IFormatNumberSettings
|
|
||||||
): ICashFlowDatePeriod => {
|
|
||||||
return {
|
|
||||||
fromDate: this.getDateMeta(fromDate),
|
|
||||||
toDate: this.getDateMeta(toDate),
|
|
||||||
total: this.getAmountMeta(total, overrideSettings),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Net income --------------------
|
|
||||||
/**
|
|
||||||
* Retrieve the net income between the given date range.
|
|
||||||
* @param {Date} fromDate
|
|
||||||
* @param {Date} toDate
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
public getNetIncomeDateRange = (fromDate: Date, toDate: Date) => {
|
|
||||||
// Mapping income/expense accounts ids.
|
|
||||||
const incomeAccountsIds = this.getAccountsIdsByType(
|
|
||||||
ACCOUNT_ROOT_TYPE.INCOME
|
|
||||||
);
|
|
||||||
const expenseAccountsIds = this.getAccountsIdsByType(
|
|
||||||
ACCOUNT_ROOT_TYPE.EXPENSE
|
|
||||||
);
|
|
||||||
// Income closing balance.
|
|
||||||
const incomeClosingBalance = accumSum(incomeAccountsIds, (id) =>
|
|
||||||
this.netIncomeLedger
|
|
||||||
.whereFromDate(fromDate)
|
|
||||||
.whereToDate(toDate)
|
|
||||||
.whereAccountId(id)
|
|
||||||
.getClosingBalance()
|
|
||||||
);
|
|
||||||
// Expense closing balance.
|
|
||||||
const expenseClosingBalance = accumSum(expenseAccountsIds, (id) =>
|
|
||||||
this.netIncomeLedger
|
|
||||||
.whereToDate(toDate)
|
|
||||||
.whereFromDate(fromDate)
|
|
||||||
.whereAccountId(id)
|
|
||||||
.getClosingBalance()
|
|
||||||
);
|
|
||||||
// Net income = income - expenses.
|
|
||||||
const netIncome = incomeClosingBalance - expenseClosingBalance;
|
|
||||||
|
|
||||||
return netIncome;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the net income of date period.
|
|
||||||
* @param {IDateRange} dateRange -
|
|
||||||
* @retrun {ICashFlowDatePeriod}
|
|
||||||
*/
|
|
||||||
public getNetIncomeDatePeriod = (dateRange): ICashFlowDatePeriod => {
|
|
||||||
const total = this.getNetIncomeDateRange(
|
|
||||||
dateRange.fromDate,
|
|
||||||
dateRange.toDate
|
|
||||||
);
|
|
||||||
return this.getDatePeriodMeta(
|
|
||||||
total,
|
|
||||||
dateRange.fromDate,
|
|
||||||
dateRange.toDate
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the net income node between the given date ranges.
|
|
||||||
* @param {Date} fromDate
|
|
||||||
* @param {Date} toDate
|
|
||||||
* @returns {ICashFlowDatePeriod[]}
|
|
||||||
*/
|
|
||||||
public getNetIncomeDatePeriods = (
|
|
||||||
section: ICashFlowStatementNetIncomeSection
|
|
||||||
): ICashFlowDatePeriod[] => {
|
|
||||||
return this.dateRangeSet.map(this.getNetIncomeDatePeriod.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes periods property to net income section.
|
|
||||||
* @param {ICashFlowStatementNetIncomeSection} section
|
|
||||||
* @returns {ICashFlowStatementNetIncomeSection}
|
|
||||||
*/
|
|
||||||
public assocPeriodsToNetIncomeNode = (
|
|
||||||
section: ICashFlowStatementNetIncomeSection
|
|
||||||
): ICashFlowStatementNetIncomeSection => {
|
|
||||||
const incomeDatePeriods = this.getNetIncomeDatePeriods(section);
|
|
||||||
return R.assoc('periods', incomeDatePeriods, section);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Account nodes --------------------
|
|
||||||
/**
|
|
||||||
* Retrieve the account total between date range.
|
|
||||||
* @param {Date} fromDate - From date.
|
|
||||||
* @param {Date} toDate - To date.
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
public getAccountTotalDateRange = (
|
|
||||||
node: ICashFlowStatementAccountSection,
|
|
||||||
fromDate: Date,
|
|
||||||
toDate: Date
|
|
||||||
): number => {
|
|
||||||
const closingBalance = this.ledger
|
|
||||||
.whereFromDate(fromDate)
|
|
||||||
.whereToDate(toDate)
|
|
||||||
.whereAccountId(node.id)
|
|
||||||
.getClosingBalance();
|
|
||||||
|
|
||||||
return this.amountAdjustment(node.adjustmentType, closingBalance);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the given account node total date period.
|
|
||||||
* @param {ICashFlowStatementAccountSection} node -
|
|
||||||
* @param {Date} fromDate - From date.
|
|
||||||
* @param {Date} toDate - To date.
|
|
||||||
* @return {ICashFlowDatePeriod}
|
|
||||||
*/
|
|
||||||
public getAccountTotalDatePeriod = (
|
|
||||||
node: ICashFlowStatementAccountSection,
|
|
||||||
fromDate: Date,
|
|
||||||
toDate: Date
|
|
||||||
): ICashFlowDatePeriod => {
|
|
||||||
const total = this.getAccountTotalDateRange(node, fromDate, toDate);
|
|
||||||
return this.getDatePeriodMeta(total, fromDate, toDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the accounts date periods nodes of the give account node.
|
|
||||||
* @param {ICashFlowStatementAccountSection} node -
|
|
||||||
* @return {ICashFlowDatePeriod[]}
|
|
||||||
*/
|
|
||||||
public getAccountDatePeriods = (
|
|
||||||
node: ICashFlowStatementAccountSection
|
|
||||||
): ICashFlowDatePeriod[] => {
|
|
||||||
return this.getNodeDatePeriods(
|
|
||||||
node,
|
|
||||||
this.getAccountTotalDatePeriod.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes `periods` property to account node.
|
|
||||||
* @param {ICashFlowStatementAccountSection} node -
|
|
||||||
* @return {ICashFlowStatementAccountSection}
|
|
||||||
*/
|
|
||||||
public assocPeriodsToAccountNode = (
|
|
||||||
node: ICashFlowStatementAccountSection
|
|
||||||
): ICashFlowStatementAccountSection => {
|
|
||||||
const datePeriods = this.getAccountDatePeriods(node);
|
|
||||||
return R.assoc('periods', datePeriods, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aggregate node -------------------------
|
|
||||||
/**
|
|
||||||
* Retrieve total of the given period index for node that has children nodes.
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
public getChildrenTotalPeriodByIndex = (
|
|
||||||
node: ICashFlowStatementSection,
|
|
||||||
index: number
|
|
||||||
): number => {
|
|
||||||
return sumBy(node.children, `periods[${index}].total.amount`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve date period meta of the given node index.
|
|
||||||
* @param {ICashFlowStatementSection} node -
|
|
||||||
* @param {number} index - Loop index.
|
|
||||||
* @param {Date} fromDate - From date.
|
|
||||||
* @param {Date} toDate - To date.
|
|
||||||
*/
|
|
||||||
public getChildrenTotalPeriodMetaByIndex(
|
|
||||||
node: ICashFlowStatementSection,
|
|
||||||
index: number,
|
|
||||||
fromDate: Date,
|
|
||||||
toDate: Date
|
|
||||||
) {
|
|
||||||
const total = this.getChildrenTotalPeriodByIndex(node, index);
|
|
||||||
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the date periods of aggregate node.
|
|
||||||
* @param {ICashFlowStatementSection} node
|
|
||||||
*/
|
|
||||||
public getAggregateNodeDatePeriods(node: ICashFlowStatementSection) {
|
|
||||||
const getChildrenTotalPeriodMetaByIndex = R.curry(
|
|
||||||
this.getChildrenTotalPeriodMetaByIndex.bind(this)
|
|
||||||
)(node);
|
|
||||||
|
|
||||||
return this.dateRangeSet.map((dateRange, index) =>
|
|
||||||
getChildrenTotalPeriodMetaByIndex(
|
|
||||||
index,
|
|
||||||
dateRange.fromDate,
|
|
||||||
dateRange.toDate
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes `periods` property to aggregate section node.
|
|
||||||
* @param {ICashFlowStatementSection} node -
|
|
||||||
* @return {ICashFlowStatementSection}
|
|
||||||
*/
|
|
||||||
public assocPeriodsToAggregateNode = (
|
|
||||||
node: ICashFlowStatementSection
|
|
||||||
): ICashFlowStatementSection => {
|
|
||||||
const datePeriods = this.getAggregateNodeDatePeriods(node);
|
|
||||||
return R.assoc('periods', datePeriods, node);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Total equation node --------------------
|
|
||||||
|
|
||||||
public sectionsMapToTotalPeriod = (
|
|
||||||
mappedSections: { [key: number]: any },
|
|
||||||
index
|
|
||||||
) => {
|
|
||||||
return mapValues(
|
|
||||||
mappedSections,
|
|
||||||
(node) => get(node, `periods[${index}].total.amount`) || 0
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the date periods of the given total equation.
|
|
||||||
* @param {ICashFlowSchemaTotalSection}
|
|
||||||
* @param {string} equation -
|
|
||||||
* @return {ICashFlowDatePeriod[]}
|
|
||||||
*/
|
|
||||||
public getTotalEquationDatePeriods = (
|
|
||||||
node: ICashFlowSchemaTotalSection,
|
|
||||||
equation: string,
|
|
||||||
nodesTable
|
|
||||||
): ICashFlowDatePeriod[] => {
|
|
||||||
return this.getNodeDatePeriods(node, (node, fromDate, toDate, index) => {
|
|
||||||
const periodScope = this.sectionsMapToTotalPeriod(nodesTable, index);
|
|
||||||
const total = this.evaluateEquation(equation, periodScope);
|
|
||||||
|
|
||||||
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Associates the total periods of total equation to the ginve total node..
|
|
||||||
* @param {ICashFlowSchemaTotalSection} totalSection -
|
|
||||||
* @return {ICashFlowStatementTotalSection}
|
|
||||||
*/
|
|
||||||
public assocTotalEquationDatePeriods = (
|
|
||||||
nodesTable: any,
|
|
||||||
equation: string,
|
|
||||||
node: ICashFlowSchemaTotalSection
|
|
||||||
): ICashFlowStatementTotalSection => {
|
|
||||||
const datePeriods = this.getTotalEquationDatePeriods(
|
|
||||||
node,
|
|
||||||
equation,
|
|
||||||
nodesTable
|
|
||||||
);
|
|
||||||
|
|
||||||
return R.assoc('periods', datePeriods, node);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cash at beginning ----------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the date preioods of the given node and accumulated function.
|
|
||||||
* @param {} node
|
|
||||||
* @param {}
|
|
||||||
* @return {}
|
|
||||||
*/
|
|
||||||
public getNodeDatePeriods = (node, callback) => {
|
|
||||||
const curriedCallback = R.curry(callback)(node);
|
|
||||||
|
|
||||||
return this.dateRangeSet.map((dateRange, index) => {
|
|
||||||
return curriedCallback(dateRange.fromDate, dateRange.toDate, index);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the account total between date range.
|
|
||||||
* @param {Date} fromDate - From date.
|
|
||||||
* @param {Date} toDate - To date.
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
public getBeginningCashAccountDateRange = (
|
|
||||||
node: ICashFlowStatementSection,
|
|
||||||
fromDate: Date,
|
|
||||||
toDate: Date
|
|
||||||
) => {
|
|
||||||
const cashToDate = this.beginningCashFrom(fromDate);
|
|
||||||
|
|
||||||
return this.cashLedger
|
|
||||||
.whereToDate(cashToDate)
|
|
||||||
.whereAccountId(node.id)
|
|
||||||
.getClosingBalance();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the beginning cash date period.
|
|
||||||
* @param {ICashFlowStatementSection} node -
|
|
||||||
* @param {Date} fromDate - From date.
|
|
||||||
* @param {Date} toDate - To date.
|
|
||||||
* @return {ICashFlowDatePeriod}
|
|
||||||
*/
|
|
||||||
public getBeginningCashDatePeriod = (
|
|
||||||
node: ICashFlowStatementSection,
|
|
||||||
fromDate: Date,
|
|
||||||
toDate: Date
|
|
||||||
) => {
|
|
||||||
const total = this.getBeginningCashAccountDateRange(
|
|
||||||
node,
|
|
||||||
fromDate,
|
|
||||||
toDate
|
|
||||||
);
|
|
||||||
return this.getDatePeriodTotalMeta(total, fromDate, toDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the beginning cash account periods.
|
|
||||||
* @param {ICashFlowStatementSection} node
|
|
||||||
* @return {ICashFlowDatePeriod}
|
|
||||||
*/
|
|
||||||
public getBeginningCashAccountPeriods = (
|
|
||||||
node: ICashFlowStatementSection
|
|
||||||
): ICashFlowDatePeriod => {
|
|
||||||
return this.getNodeDatePeriods(node, this.getBeginningCashDatePeriod);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes `periods` property to cash at beginning date periods.
|
|
||||||
* @param {ICashFlowStatementSection} section -
|
|
||||||
* @return {ICashFlowStatementSection}
|
|
||||||
*/
|
|
||||||
public assocCashAtBeginningDatePeriods = (
|
|
||||||
node: ICashFlowStatementSection
|
|
||||||
): ICashFlowStatementSection => {
|
|
||||||
const datePeriods = this.getAggregateNodeDatePeriods(node);
|
|
||||||
return R.assoc('periods', datePeriods, node);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Associates `periods` propery to cash at beginning account node.
|
|
||||||
* @param {ICashFlowStatementSection} node -
|
|
||||||
* @return {ICashFlowStatementSection}
|
|
||||||
*/
|
|
||||||
public assocCashAtBeginningAccountDatePeriods = (
|
|
||||||
node: ICashFlowStatementSection
|
|
||||||
): ICashFlowStatementSection => {
|
|
||||||
const datePeriods = this.getBeginningCashAccountPeriods(node);
|
|
||||||
return R.assoc('periods', datePeriods, node);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
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()),
|
|
||||||
)([]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
import { IFinancialSheetCommonMeta, INumberFormatQuery } from '../../types/Report.types';
|
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
|
||||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
|
||||||
import { IFinancialTable, ITableRow } from '../../types/Table.types';
|
|
||||||
|
|
||||||
|
|
||||||
export interface ICashFlowStatementQuery {
|
|
||||||
fromDate: Date | string;
|
|
||||||
toDate: Date | string;
|
|
||||||
displayColumnsBy: string;
|
|
||||||
displayColumnsType: string;
|
|
||||||
noneZero: boolean;
|
|
||||||
noneTransactions: boolean;
|
|
||||||
numberFormat: INumberFormatQuery;
|
|
||||||
basis: string;
|
|
||||||
|
|
||||||
branchesIds?: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementTotal {
|
|
||||||
amount: number;
|
|
||||||
formattedAmount: string;
|
|
||||||
currencyCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementTotalPeriod {
|
|
||||||
fromDate: Date;
|
|
||||||
toDate: Date;
|
|
||||||
total: ICashFlowStatementTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementCommonSection {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
total: ICashFlowStatementTotal;
|
|
||||||
footerLabel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementAccountMeta {
|
|
||||||
id: number;
|
|
||||||
label: string;
|
|
||||||
code: string;
|
|
||||||
total: ICashFlowStatementTotal;
|
|
||||||
accountType: string;
|
|
||||||
adjustmentType: string;
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ICashFlowStatementSectionType {
|
|
||||||
REGULAR = 'REGULAR',
|
|
||||||
AGGREGATE = 'AGGREGATE',
|
|
||||||
NET_INCOME = 'NET_INCOME',
|
|
||||||
ACCOUNT = 'ACCOUNT',
|
|
||||||
ACCOUNTS = 'ACCOUNTS',
|
|
||||||
TOTAL = 'TOTAL',
|
|
||||||
CASH_AT_BEGINNING = 'CASH_AT_BEGINNING',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementAccountSection
|
|
||||||
extends ICashFlowStatementCommonSection {
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNTS;
|
|
||||||
children: ICashFlowStatementAccountMeta[];
|
|
||||||
total: ICashFlowStatementTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementNetIncomeSection
|
|
||||||
extends ICashFlowStatementCommonSection {
|
|
||||||
sectionType: ICashFlowStatementSectionType.NET_INCOME;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementTotalSection
|
|
||||||
extends ICashFlowStatementCommonSection {
|
|
||||||
sectionType: ICashFlowStatementSectionType.TOTAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementAggregateSection
|
|
||||||
extends ICashFlowStatementCommonSection {
|
|
||||||
sectionType: ICashFlowStatementSectionType.AGGREGATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowCashBeginningNode
|
|
||||||
extends ICashFlowStatementCommonSection {
|
|
||||||
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ICashFlowStatementSection =
|
|
||||||
| ICashFlowStatementAccountSection
|
|
||||||
| ICashFlowStatementNetIncomeSection
|
|
||||||
| ICashFlowStatementTotalSection
|
|
||||||
| ICashFlowStatementCommonSection;
|
|
||||||
|
|
||||||
export interface ICashFlowStatementColumn {}
|
|
||||||
export interface ICashFlowStatementMeta extends IFinancialSheetCommonMeta {
|
|
||||||
formattedToDate: string;
|
|
||||||
formattedFromDate: string;
|
|
||||||
formattedDateRange: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementDOO {
|
|
||||||
data: ICashFlowStatementData;
|
|
||||||
meta: ICashFlowStatementMeta;
|
|
||||||
query: ICashFlowStatementQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementTable extends IFinancialTable {
|
|
||||||
meta: ICashFlowStatementMeta;
|
|
||||||
query: ICashFlowStatementQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatementService {
|
|
||||||
cashFlow(
|
|
||||||
tenantId: number,
|
|
||||||
query: ICashFlowStatementQuery
|
|
||||||
): Promise<ICashFlowStatementDOO>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CASH FLOW SCHEMA TYPES.
|
|
||||||
// -----------------------------
|
|
||||||
export interface ICashFlowSchemaCommonSection {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
children: ICashFlowSchemaSection[];
|
|
||||||
footerLabel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CASH_FLOW_ACCOUNT_RELATION {
|
|
||||||
MINES = 'mines',
|
|
||||||
PLUS = 'plus',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CASH_FLOW_SECTION_ID {
|
|
||||||
NET_INCOME = 'NET_INCOME',
|
|
||||||
OPERATING = 'OPERATING',
|
|
||||||
OPERATING_ACCOUNTS = 'OPERATING_ACCOUNTS',
|
|
||||||
INVESTMENT = 'INVESTMENT',
|
|
||||||
FINANCIAL = 'FINANCIAL',
|
|
||||||
|
|
||||||
NET_OPERATING = 'NET_OPERATING',
|
|
||||||
NET_INVESTMENT = 'NET_INVESTMENT',
|
|
||||||
NET_FINANCIAL = 'NET_FINANCIAL',
|
|
||||||
|
|
||||||
CASH_BEGINNING_PERIOD = 'CASH_BEGINNING_PERIOD',
|
|
||||||
CASH_END_PERIOD = 'CASH_END_PERIOD',
|
|
||||||
NET_CASH_INCREASE = 'NET_CASH_INCREASE',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowSchemaAccountsSection
|
|
||||||
extends ICashFlowSchemaCommonSection {
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNT;
|
|
||||||
accountsRelations: ICashFlowSchemaAccountRelation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowSchemaTotalSection
|
|
||||||
extends ICashFlowStatementCommonSection {
|
|
||||||
sectionType: ICashFlowStatementSectionType.TOTAL;
|
|
||||||
equation: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ICashFlowSchemaSection =
|
|
||||||
| ICashFlowSchemaAccountsSection
|
|
||||||
| ICashFlowSchemaTotalSection
|
|
||||||
| ICashFlowSchemaCommonSection;
|
|
||||||
|
|
||||||
export type ICashFlowStatementData = ICashFlowSchemaSection[];
|
|
||||||
|
|
||||||
export interface ICashFlowSchemaAccountRelation {
|
|
||||||
type: string;
|
|
||||||
direction: CASH_FLOW_ACCOUNT_RELATION.PLUS;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowSchemaSectionAccounts
|
|
||||||
extends ICashFlowStatementCommonSection {
|
|
||||||
type: ICashFlowStatementSectionType.ACCOUNT;
|
|
||||||
accountsRelations: ICashFlowSchemaAccountRelation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowSchemaSectionTotal {
|
|
||||||
type: ICashFlowStatementSectionType.TOTAL;
|
|
||||||
totalEquation: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowDatePeriod {
|
|
||||||
fromDate: ICashFlowDate;
|
|
||||||
toDate: ICashFlowDate;
|
|
||||||
total: ICashFlowStatementTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowDate {
|
|
||||||
formattedDate: string;
|
|
||||||
date: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowStatement {
|
|
||||||
/**
|
|
||||||
* Constructor method.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
accounts: Account[],
|
|
||||||
ledger: Ledger,
|
|
||||||
cashLedger: Ledger,
|
|
||||||
netIncomeLedger: Ledger,
|
|
||||||
query: ICashFlowStatementQuery,
|
|
||||||
baseCurrency: string
|
|
||||||
): void;
|
|
||||||
|
|
||||||
reportData(): ICashFlowStatementData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashFlowTable {
|
|
||||||
constructor(reportStatement: ICashFlowStatement): void;
|
|
||||||
tableRows(): ITableRow[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDateRange {
|
|
||||||
fromDate: Date;
|
|
||||||
toDate: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashflowTransactionSchema {
|
|
||||||
amount: number;
|
|
||||||
date: Date;
|
|
||||||
referenceNo?: string | null;
|
|
||||||
transactionNumber: string;
|
|
||||||
transactionType: string;
|
|
||||||
creditAccountId: number;
|
|
||||||
cashflowAccountId: number;
|
|
||||||
userId: number;
|
|
||||||
publishedAt?: Date | null;
|
|
||||||
branchId?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICashflowTransactionInput extends ICashflowTransactionSchema {}
|
|
||||||
|
|
||||||
export interface ICategorizeCashflowTransactioDTO {
|
|
||||||
date: Date;
|
|
||||||
creditAccountId: number;
|
|
||||||
referenceNo: string;
|
|
||||||
transactionNumber: string;
|
|
||||||
transactionType: string;
|
|
||||||
exchangeRate: number;
|
|
||||||
description: string;
|
|
||||||
branchId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUncategorizedCashflowTransaction {
|
|
||||||
id?: number;
|
|
||||||
amount: number;
|
|
||||||
date: Date;
|
|
||||||
currencyCode: string;
|
|
||||||
accountId: number;
|
|
||||||
description: string;
|
|
||||||
referenceNo: string;
|
|
||||||
categorizeRefType: string;
|
|
||||||
categorizeRefId: number;
|
|
||||||
categorized: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateUncategorizedTransactionDTO {
|
|
||||||
date: Date | string;
|
|
||||||
accountId: number;
|
|
||||||
amount: number;
|
|
||||||
currencyCode: string;
|
|
||||||
payee?: string;
|
|
||||||
description?: string;
|
|
||||||
referenceNo?: string | null;
|
|
||||||
plaidTransactionId?: string | null;
|
|
||||||
pending?: boolean;
|
|
||||||
pendingPlaidTransactionId?: string | null;
|
|
||||||
batch?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUncategorizedTransactionCreatingEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUncategorizedTransactionCreatedEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransaction: any;
|
|
||||||
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO;
|
|
||||||
trx: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPendingTransactionRemovingEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
pendingTransaction: IUncategorizedCashflowTransaction;
|
|
||||||
trx?: Knex.Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPendingTransactionRemovedEventPayload {
|
|
||||||
tenantId: number;
|
|
||||||
uncategorizedTransactionId: number;
|
|
||||||
pendingTransaction: IUncategorizedCashflowTransaction;
|
|
||||||
trx?: Knex.Transaction;
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { CashflowTableInjectable } from './CashflowTableInjectable';
|
|
||||||
import { ICashFlowStatementQuery } from './Cashflow.types';
|
|
||||||
import { TableSheet } from '../../common/TableSheet';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CashflowExportInjectable {
|
|
||||||
constructor(private readonly cashflowSheetTable: CashflowTableInjectable) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow sheet in XLSX format.
|
|
||||||
* @param {ICashFlowStatementQuery} query - Cashflow statement query.
|
|
||||||
* @returns {Promise<Buffer>}
|
|
||||||
*/
|
|
||||||
public async xlsx(query: ICashFlowStatementQuery): Promise<Buffer> {
|
|
||||||
const table = await this.cashflowSheetTable.table(query);
|
|
||||||
|
|
||||||
const tableSheet = new TableSheet(table.table);
|
|
||||||
const tableCsv = tableSheet.convertToXLSX();
|
|
||||||
|
|
||||||
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cashflow sheet in CSV format.
|
|
||||||
* @param {ICashFlowStatementQuery} query - Cashflow statement query.
|
|
||||||
* @returns {Promise<Buffer>}
|
|
||||||
*/
|
|
||||||
public async csv(query: ICashFlowStatementQuery): Promise<string> {
|
|
||||||
const table = await this.cashflowSheetTable.table(query);
|
|
||||||
|
|
||||||
const tableSheet = new TableSheet(table.table);
|
|
||||||
const tableCsv = tableSheet.convertToCSV();
|
|
||||||
|
|
||||||
return tableCsv;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { CashFlowTable } from './CashFlowTable';
|
|
||||||
import { CashFlowStatementService } from './CashFlowService';
|
|
||||||
import { I18nService } from 'nestjs-i18n';
|
|
||||||
import {
|
|
||||||
ICashFlowStatementQuery,
|
|
||||||
ICashFlowStatementTable,
|
|
||||||
} from './Cashflow.types';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CashflowTableInjectable {
|
|
||||||
constructor(
|
|
||||||
private readonly cashflowSheet: CashFlowStatementService,
|
|
||||||
private readonly i18n: I18nService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the cash flow table.
|
|
||||||
* @returns {Promise<ICashFlowStatementTable>}
|
|
||||||
*/
|
|
||||||
public async table(
|
|
||||||
query: ICashFlowStatementQuery,
|
|
||||||
): Promise<ICashFlowStatementTable> {
|
|
||||||
const cashflowDOO = await this.cashflowSheet.cashFlow(query);
|
|
||||||
const cashflowTable = new CashFlowTable(cashflowDOO, this.i18n);
|
|
||||||
|
|
||||||
return {
|
|
||||||
table: {
|
|
||||||
columns: cashflowTable.tableColumns(),
|
|
||||||
rows: cashflowTable.tableRows(),
|
|
||||||
},
|
|
||||||
query: cashflowDOO.query,
|
|
||||||
meta: cashflowDOO.meta,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { TableSheetPdf } from '../../common/TableSheetPdf';
|
|
||||||
import { ICashFlowStatementQuery } from './Cashflow.types';
|
|
||||||
import { CashflowTableInjectable } from './CashflowTableInjectable';
|
|
||||||
import { HtmlTableCustomCss } from './constants';
|
|
||||||
|
|
||||||
export class CashflowTablePdfInjectable {
|
|
||||||
constructor(
|
|
||||||
private readonly cashflowTable: CashflowTableInjectable,
|
|
||||||
private readonly tableSheetPdf: TableSheetPdf,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given cashflow sheet table to pdf.
|
|
||||||
* @param {number} tenantId - Tenant ID.
|
|
||||||
* @param {IBalanceSheetQuery} query - Balance sheet query.
|
|
||||||
* @returns {Promise<Buffer>}
|
|
||||||
*/
|
|
||||||
public async pdf(query: ICashFlowStatementQuery): Promise<Buffer> {
|
|
||||||
const table = await this.cashflowTable.table(query);
|
|
||||||
|
|
||||||
return this.tableSheetPdf.convertToPdf(
|
|
||||||
table.table,
|
|
||||||
table.meta.sheetName,
|
|
||||||
table.meta.formattedDateRange,
|
|
||||||
HtmlTableCustomCss,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { ICashFlowStatementQuery } from "./Cashflow.types";
|
|
||||||
|
|
||||||
export const DISPLAY_COLUMNS_BY = {
|
|
||||||
DATE_PERIODS: 'date_periods',
|
|
||||||
TOTAL: 'total',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' };
|
|
||||||
export const HtmlTableCustomCss = `
|
|
||||||
table tr.row-type--accounts td {
|
|
||||||
border-top: 1px solid #bbb;
|
|
||||||
}
|
|
||||||
table tr.row-id--cash-end-period td {
|
|
||||||
border-bottom: 3px double #333;
|
|
||||||
}
|
|
||||||
table tr.row-type--total {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
table tr.row-type--total td {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
table tr.row-type--total:not(:first-child) td {
|
|
||||||
border-top: 1px solid #bbb;
|
|
||||||
}
|
|
||||||
table .column--name,
|
|
||||||
table .cell--name {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
table .column--total,
|
|
||||||
table .cell--total,
|
|
||||||
table [class*="column--date-range"],
|
|
||||||
table [class*="cell--date-range"] {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const getDefaultCashflowQuery = (): ICashFlowStatementQuery => ({
|
|
||||||
displayColumnsType: 'total',
|
|
||||||
displayColumnsBy: 'day',
|
|
||||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
|
||||||
toDate: moment().format('YYYY-MM-DD'),
|
|
||||||
numberFormat: {
|
|
||||||
precision: 2,
|
|
||||||
divideOn1000: false,
|
|
||||||
showZero: false,
|
|
||||||
formatMoney: 'total',
|
|
||||||
negativeFormat: 'mines',
|
|
||||||
},
|
|
||||||
noneZero: false,
|
|
||||||
noneTransactions: false,
|
|
||||||
basis: 'cash',
|
|
||||||
});
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
|
||||||
import {
|
|
||||||
ICashFlowSchemaSection,
|
|
||||||
CASH_FLOW_SECTION_ID,
|
|
||||||
ICashFlowStatementSectionType,
|
|
||||||
} from './Cashflow.types';
|
|
||||||
|
|
||||||
export const CASH_FLOW_SCHEMA = [
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.OPERATING,
|
|
||||||
label: 'OPERATING ACTIVITIES',
|
|
||||||
sectionType: ICashFlowStatementSectionType.AGGREGATE,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.NET_INCOME,
|
|
||||||
label: 'Net income',
|
|
||||||
sectionType: ICashFlowStatementSectionType.NET_INCOME,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.OPERATING_ACCOUNTS,
|
|
||||||
label: 'Adjustments net income by operating activities.',
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNTS,
|
|
||||||
accountsRelations: [
|
|
||||||
{ type: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE, direction: 'mines' },
|
|
||||||
{ type: ACCOUNT_TYPE.INVENTORY, direction: 'mines' },
|
|
||||||
{ type: ACCOUNT_TYPE.NON_CURRENT_ASSET, direction: 'mines' },
|
|
||||||
{ type: ACCOUNT_TYPE.ACCOUNTS_PAYABLE, direction: 'plus' },
|
|
||||||
{ type: ACCOUNT_TYPE.CREDIT_CARD, direction: 'plus' },
|
|
||||||
{ type: ACCOUNT_TYPE.TAX_PAYABLE, direction: 'plus' },
|
|
||||||
{ type: ACCOUNT_TYPE.OTHER_CURRENT_ASSET, direction: 'mines' },
|
|
||||||
{ type: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY, direction: 'plus' },
|
|
||||||
{ type: ACCOUNT_TYPE.NON_CURRENT_LIABILITY, direction: 'plus' },
|
|
||||||
],
|
|
||||||
showAlways: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
footerLabel: 'Net cash provided by operating activities',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.INVESTMENT,
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNTS,
|
|
||||||
label: 'INVESTMENT ACTIVITIES',
|
|
||||||
accountsRelations: [{ type: ACCOUNT_TYPE.FIXED_ASSET, direction: 'mines' }],
|
|
||||||
footerLabel: 'Net cash provided by investing activities',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.FINANCIAL,
|
|
||||||
label: 'FINANCIAL ACTIVITIES',
|
|
||||||
sectionType: ICashFlowStatementSectionType.ACCOUNTS,
|
|
||||||
accountsRelations: [
|
|
||||||
{ type: ACCOUNT_TYPE.LOGN_TERM_LIABILITY, direction: 'plus' },
|
|
||||||
{ type: ACCOUNT_TYPE.EQUITY, direction: 'plus' },
|
|
||||||
],
|
|
||||||
footerLabel: 'Net cash provided by financing activities',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.CASH_BEGINNING_PERIOD,
|
|
||||||
sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING,
|
|
||||||
label: 'Cash at beginning of period',
|
|
||||||
accountsRelations: [
|
|
||||||
{ type: ACCOUNT_TYPE.CASH, direction: 'plus' },
|
|
||||||
{ type: ACCOUNT_TYPE.BANK, direction: 'plus' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.NET_CASH_INCREASE,
|
|
||||||
sectionType: ICashFlowStatementSectionType.TOTAL,
|
|
||||||
equation: 'OPERATING + INVESTMENT + FINANCIAL',
|
|
||||||
label: 'NET CASH INCREASE FOR PERIOD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: CASH_FLOW_SECTION_ID.CASH_END_PERIOD,
|
|
||||||
label: 'CASH AT END OF PERIOD',
|
|
||||||
sectionType: ICashFlowStatementSectionType.TOTAL,
|
|
||||||
equation: 'NET_CASH_INCREASE + CASH_BEGINNING_PERIOD',
|
|
||||||
},
|
|
||||||
] as ICashFlowSchemaSection[];
|
|
||||||
Reference in New Issue
Block a user