refactor: balance sheet to nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-01-30 01:57:29 +02:00
parent 7b81d0c8e5
commit c4692d1716
51 changed files with 2823 additions and 287 deletions

View File

@@ -1,5 +1,4 @@
import { Response } from 'express'; import { Response } from 'express';
import { castArray } from 'lodash';
import { Controller, Headers, Query, Res } from '@nestjs/common'; import { Controller, Headers, Query, Res } from '@nestjs/common';
import { IBalanceSheetQuery } from './BalanceSheet.types'; import { IBalanceSheetQuery } from './BalanceSheet.types';
import { AcceptType } from '@/constants/accept-type'; import { AcceptType } from '@/constants/accept-type';
@@ -20,18 +19,14 @@ export class BalanceSheetStatementController {
@Res() res: Response, @Res() res: Response,
@Headers('accept') acceptHeader: string, @Headers('accept') acceptHeader: string,
) { ) {
const filter = {
...query,
accountsIds: castArray(query.accountsIds),
};
// Retrieves the json table format. // Retrieves the json table format.
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) { if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
const table = await this.balanceSheetApp.table(filter); const table = await this.balanceSheetApp.table(query);
return res.status(200).send(table); return res.status(200).send(table);
// Retrieves the csv format. // Retrieves the csv format.
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) { } else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
const buffer = await this.balanceSheetApp.csv(filter); const buffer = await this.balanceSheetApp.csv(query);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Type', 'text/csv');
@@ -39,7 +34,7 @@ export class BalanceSheetStatementController {
return res.send(buffer); return res.send(buffer);
// Retrieves the xlsx format. // Retrieves the xlsx format.
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) { } else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
const buffer = await this.balanceSheetApp.xlsx(filter); const buffer = await this.balanceSheetApp.xlsx(query);
res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx'); res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx');
res.setHeader( res.setHeader(
@@ -49,7 +44,7 @@ export class BalanceSheetStatementController {
return res.send(buffer); return res.send(buffer);
// Retrieves the pdf format. // Retrieves the pdf format.
} else if (acceptHeader.includes(AcceptType.ApplicationPdf)) { } else if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
const pdfContent = await this.balanceSheetApp.pdf(filter); const pdfContent = await this.balanceSheetApp.pdf(query);
res.set({ res.set({
'Content-Type': 'application/pdf', 'Content-Type': 'application/pdf',
@@ -57,7 +52,7 @@ export class BalanceSheetStatementController {
}); });
res.send(pdfContent); res.send(pdfContent);
} else { } else {
const sheet = await this.balanceSheetApp.sheet(filter); const sheet = await this.balanceSheetApp.sheet(query);
return res.status(200).send(sheet); return res.status(200).send(sheet);
} }

View File

@@ -43,8 +43,10 @@ export enum BALANCE_SHEET_SCHEMA_NODE_ID {
export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery { export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery {
displayColumnsType: 'total' | 'date_periods'; displayColumnsType: 'total' | 'date_periods';
displayColumnsBy: string; displayColumnsBy: string;
fromDate: string; fromDate: string;
toDate: string; toDate: string;
numberFormat: INumberFormatQuery; numberFormat: INumberFormatQuery;
noneTransactions: boolean; noneTransactions: boolean;
noneZero: boolean; noneZero: boolean;

View File

@@ -19,13 +19,16 @@ import { BalanceSheetSchema } from './BalanceSheetSchema';
import { BalanceSheetBase } from './BalanceSheetBase'; import { BalanceSheetBase } from './BalanceSheetBase';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetRepository } from './BalanceSheetRepository';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery } from '../../types/Report.types';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { flatToNestedArray } from '@/utils/flat-to-nested-array'; import { flatToNestedArray } from '@/utils/flat-to-nested-array';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetAccounts = <T extends Constructor>(Base: T) => export const BalanceSheetAccounts = <T extends GConstructor<FinancialSheet>>(
class extends R.compose( Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncome, BalanceSheetNetIncome,
BalanceSheetFiltering, BalanceSheetFiltering,
BalanceSheetDatePeriods, BalanceSheetDatePeriods,
@@ -34,7 +37,7 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
BalanceSheetPercentage, BalanceSheetPercentage,
BalanceSheetSchema, BalanceSheetSchema,
BalanceSheetBase, BalanceSheetBase,
FinancialSheetStructure FinancialSheetStructure,
)(Base) { )(Base) {
/** /**
* Balance sheet query. * Balance sheet query.
@@ -70,10 +73,10 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
* @returns {IAccount[]} * @returns {IAccount[]}
*/ */
private getAccountsByAccountTypes = ( private getAccountsByAccountTypes = (
accountsTypes: string[] accountsTypes: string[],
): Account[] => { ): Account[] => {
const mapAccountsByTypes = R.map((accountType) => const mapAccountsByTypes = R.map((accountType) =>
defaultTo(this.repository.accountsByType.get(accountType), []) defaultTo(this.repository.accountsByType.get(accountType), []),
); );
return R.compose(R.flatten, mapAccountsByTypes)(accountsTypes); return R.compose(R.flatten, mapAccountsByTypes)(accountsTypes);
}; };
@@ -84,10 +87,10 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
* @returns {IBalanceSheetAccountNode} * @returns {IBalanceSheetAccountNode}
*/ */
private reportSchemaAccountNodeMapper = ( private reportSchemaAccountNodeMapper = (
account: Account account: Account,
): IBalanceSheetAccountNode => { ): IBalanceSheetAccountNode => {
const childrenAccountsIds = this.repository.accountsGraph.dependenciesOf( const childrenAccountsIds = this.repository.accountsGraph.dependenciesOf(
account.id account.id,
); );
const accountIds = R.uniq(R.append(account.id, childrenAccountsIds)); const accountIds = R.uniq(R.append(account.id, childrenAccountsIds));
const total = this.repository.totalAccountsLedger const total = this.repository.totalAccountsLedger
@@ -110,22 +113,22 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
* @returns {IBalanceSheetAccountNode} * @returns {IBalanceSheetAccountNode}
*/ */
private reportSchemaAccountNodeComposer = ( private reportSchemaAccountNodeComposer = (
account: Account account: Account,
): IBalanceSheetAccountNode => { ): IBalanceSheetAccountNode => {
return R.compose( return R.compose(
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
this.previousYearAccountNodeComposer this.previousYearAccountNodeComposer,
), ),
R.when( R.when(
this.query.isPreviousPeriodActive, this.query.isPreviousPeriodActive,
this.previousPeriodAccountNodeComposer this.previousPeriodAccountNodeComposer,
), ),
R.when( R.when(
this.query.isDatePeriodsColumnsType, this.query.isDatePeriodsColumnsType,
this.assocAccountNodeDatePeriods this.assocAccountNodeDatePeriods,
), ),
this.reportSchemaAccountNodeMapper this.reportSchemaAccountNodeMapper,
)(account); )(account);
}; };
@@ -138,7 +141,7 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
* @returns {IBalanceSheetAccountNode[]} * @returns {IBalanceSheetAccountNode[]}
*/ */
private getAccountsNodesByAccountTypes = ( private getAccountsNodesByAccountTypes = (
accountsTypes: string[] accountsTypes: string[],
): IBalanceSheetAccountNode[] => { ): IBalanceSheetAccountNode[] => {
// Retrieves accounts from the given defined node account types. // Retrieves accounts from the given defined node account types.
const accounts = this.getAccountsByAccountTypes(accountsTypes); const accounts = this.getAccountsByAccountTypes(accountsTypes);
@@ -151,7 +154,7 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
// Maps over the accounts tree. // Maps over the accounts tree.
return this.mapNodesDeep( return this.mapNodesDeep(
accountsTree, accountsTree,
this.reportSchemaAccountNodeComposer this.reportSchemaAccountNodeComposer,
); );
}; };
@@ -161,7 +164,7 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
* @returns {IBalanceSheetAccountNode} * @returns {IBalanceSheetAccountNode}
*/ */
private reportSchemaAccountsNodeMapper = ( private reportSchemaAccountsNodeMapper = (
node: IBalanceSheetSchemaAccountNode node: IBalanceSheetSchemaAccountNode,
): IBalanceSheetAccountsNode => { ): IBalanceSheetAccountsNode => {
const accounts = this.getAccountsNodesByAccountTypes(node.accountsTypes); const accounts = this.getAccountsNodesByAccountTypes(node.accountsTypes);
const children = toArray(node?.children); const children = toArray(node?.children);
@@ -182,13 +185,13 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
* @return {IBalanceSheetSchemaNode | IBalanceSheetDataNode} * @return {IBalanceSheetSchemaNode | IBalanceSheetDataNode}
*/ */
private reportAccountSchemaParser = ( private reportAccountSchemaParser = (
node: IBalanceSheetSchemaNode | IBalanceSheetDataNode node: IBalanceSheetSchemaNode | IBalanceSheetDataNode,
): IBalanceSheetSchemaNode | IBalanceSheetDataNode => { ): IBalanceSheetSchemaNode | IBalanceSheetDataNode => {
return R.compose( return R.compose(
R.when( R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS), this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS),
this.reportSchemaAccountsNodeMapper this.reportSchemaAccountsNodeMapper,
) ),
)(node); )(node);
}; };
@@ -198,7 +201,7 @@ export const BalanceSheetAccounts = <T extends Constructor>(Base: T) =>
* @return {IBalanceSheetStructureSection[]} * @return {IBalanceSheetStructureSection[]}
*/ */
public accountsSchemaParser = ( public accountsSchemaParser = (
nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[] nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[],
): (IBalanceSheetDataNode | IBalanceSheetSchemaNode)[] => { ): (IBalanceSheetDataNode | IBalanceSheetSchemaNode)[] => {
return this.mapNodesDeepReverse(nodes, this.reportAccountSchemaParser); return this.mapNodesDeepReverse(nodes, this.reportAccountSchemaParser);
}; };

View File

@@ -14,18 +14,21 @@ import { BalanceSheetSchema } from './BalanceSheetSchema';
import { BalanceSheetBase } from './BalanceSheetBase'; import { BalanceSheetBase } from './BalanceSheetBase';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery } from '../../types/Report.types';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetAggregators = <T extends Constructor>(Base: T) => export const BalanceSheetAggregators = <T extends GConstructor<FinancialSheet>>(
class extends R.compose( Base: T,
) =>
class extends R.pipe(
BalanceSheetDatePeriods, BalanceSheetDatePeriods,
BalanceSheetComparsionPreviousPeriod, BalanceSheetComparsionPreviousPeriod,
BalanceSheetComparsionPreviousYear, BalanceSheetComparsionPreviousYear,
BalanceSheetPercentage, BalanceSheetPercentage,
BalanceSheetSchema, BalanceSheetSchema,
BalanceSheetBase, BalanceSheetBase,
FinancialSheetStructure FinancialSheetStructure,
)(Base) { )(Base) {
/** /**
* Balance sheet query. * Balance sheet query.
@@ -56,21 +59,21 @@ export const BalanceSheetAggregators = <T extends Constructor>(Base: T) =>
* @returns {IBalanceSheetDataNode} * @returns {IBalanceSheetDataNode}
*/ */
public aggregateNodeTotalMapper = ( public aggregateNodeTotalMapper = (
node: IBalanceSheetDataNode node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => { ): IBalanceSheetDataNode => {
return R.compose( return R.compose(
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
this.previousYearAggregateNodeComposer this.previousYearAggregateNodeComposer,
), ),
R.when( R.when(
this.query.isPreviousPeriodActive, this.query.isPreviousPeriodActive,
this.previousPeriodAggregateNodeComposer this.previousPeriodAggregateNodeComposer,
), ),
R.when( R.when(
this.query.isDatePeriodsColumnsType, this.query.isDatePeriodsColumnsType,
this.assocAggregateNodeDatePeriods this.assocAggregateNodeDatePeriods,
) ),
)(node); )(node);
}; };
@@ -80,7 +83,7 @@ export const BalanceSheetAggregators = <T extends Constructor>(Base: T) =>
* @return {IBalanceSheetAggregateNode} * @return {IBalanceSheetAggregateNode}
*/ */
public reportSchemaAggregateNodeMapper = ( public reportSchemaAggregateNodeMapper = (
node: IBalanceSheetSchemaAggregateNode node: IBalanceSheetSchemaAggregateNode,
): IBalanceSheetAggregateNode => { ): IBalanceSheetAggregateNode => {
const total = this.getTotalOfNodes(node.children); const total = this.getTotalOfNodes(node.children);
@@ -100,11 +103,11 @@ export const BalanceSheetAggregators = <T extends Constructor>(Base: T) =>
* @returns {IBalanceSheetSchemaAggregateNode} * @returns {IBalanceSheetSchemaAggregateNode}
*/ */
public schemaAggregateNodeCompose = ( public schemaAggregateNodeCompose = (
node: IBalanceSheetSchemaAggregateNode node: IBalanceSheetSchemaAggregateNode,
) => { ) => {
return R.compose( return R.compose(
this.aggregateNodeTotalMapper, this.aggregateNodeTotalMapper,
this.reportSchemaAggregateNodeMapper this.reportSchemaAggregateNodeMapper,
)(node); )(node);
}; };
@@ -114,17 +117,17 @@ export const BalanceSheetAggregators = <T extends Constructor>(Base: T) =>
* @return {IBalanceSheetDataNode} * @return {IBalanceSheetDataNode}
*/ */
public reportAggregateSchemaParser = ( public reportAggregateSchemaParser = (
node: IBalanceSheetSchemaNode node: IBalanceSheetSchemaNode,
): IBalanceSheetDataNode => { ): IBalanceSheetDataNode => {
return R.compose( return R.compose(
R.when( R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE), this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE),
this.schemaAggregateNodeCompose this.schemaAggregateNodeCompose,
), ),
R.when( R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS), this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS),
this.schemaAggregateNodeCompose this.schemaAggregateNodeCompose,
) ),
)(node); )(node);
}; };
@@ -134,7 +137,7 @@ export const BalanceSheetAggregators = <T extends Constructor>(Base: T) =>
* @return {IBalanceSheetStructureSection[]} * @return {IBalanceSheetStructureSection[]}
*/ */
public aggregatesSchemaParser = ( public aggregatesSchemaParser = (
nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[] nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[],
): (IBalanceSheetDataNode | IBalanceSheetSchemaNode)[] => { ): (IBalanceSheetDataNode | IBalanceSheetSchemaNode)[] => {
return this.mapNodesDeepReverse(nodes, this.reportAggregateSchemaParser); return this.mapNodesDeepReverse(nodes, this.reportAggregateSchemaParser);
}; };

View File

@@ -3,9 +3,12 @@ import {
IBalanceSheetDataNode, IBalanceSheetDataNode,
IBalanceSheetSchemaNode, IBalanceSheetSchemaNode,
} from './BalanceSheet.types'; } from './BalanceSheet.types';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetBase = <T extends Constructor>(Base: T) => export const BalanceSheetBase = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class BalanceSheetBase extends Base { class BalanceSheetBase extends Base {
/** /**
* Detarmines the node type of the given schema node. * Detarmines the node type of the given schema node.

View File

@@ -9,14 +9,17 @@ import {
} from './BalanceSheet.types'; } from './BalanceSheet.types';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetComparsionPreviousPeriod = <T extends Constructor>( export const BalanceSheetComparsionPreviousPeriod = <
T extends GConstructor<FinancialSheet>,
>(
Base: T, Base: T,
) => ) =>
class BalanceSheetComparsionPreviousPeriod extends R.compose( class BalanceSheetComparsionPreviousPeriod extends R.pipe(
FinancialPreviousPeriod,
FinancialHorizTotals, FinancialHorizTotals,
FinancialPreviousPeriod,
)(Base) { )(Base) {
// ------------------------------ // ------------------------------
// # Account // # Account

View File

@@ -7,15 +7,22 @@ import {
IBalanceSheetTotal, IBalanceSheetTotal,
} from './BalanceSheet.types'; } from './BalanceSheet.types';
import { FinancialPreviousYear } from '../../common/FinancialPreviousYear'; import { FinancialPreviousYear } from '../../common/FinancialPreviousYear';
import { IBalanceSheetComparsions } from './BalanceSheet.types'; import { GConstructor } from '@/common/types/Constructor';
import { Constructor } from '@/common/types/Constructor'; import { FinancialSheet } from '../../common/FinancialSheet';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetRepository } from './BalanceSheetRepository';
export const BalanceSheetComparsionPreviousYear = <T extends Constructor>( export const BalanceSheetComparsionPreviousYear = <
T extends GConstructor<FinancialSheet>,
>(
Base: T, Base: T,
) => ) =>
class BalanceSheetComparsionPreviousYear extends R.compose( class BalanceSheetComparsionPreviousYear extends R.pipe(
FinancialPreviousYear, FinancialPreviousYear,
)(Base) { )(Base) {
query: BalanceSheetQuery;
repository: BalanceSheetRepository;
// ------------------------------ // ------------------------------
// # Account // # Account
// ------------------------------ // ------------------------------

View File

@@ -8,17 +8,21 @@ import {
} from './BalanceSheet.types'; } from './BalanceSheet.types';
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
import { IDateRange, IFormatNumberSettings } from '../../types/Report.types'; import { IDateRange, IFormatNumberSettings } from '../../types/Report.types';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
import { BalanceSheetQuery } from './BalanceSheetQuery';
/** /**
* Balance sheet date periods. * Balance sheet date periods.
*/ */
export const BalanceSheetDatePeriods = <T extends Constructor>(Base: T) => export const BalanceSheetDatePeriods = <T extends GConstructor<FinancialSheet>>(
class BalanceSheetDatePeriods extends R.compose(FinancialDatePeriods)(Base) { Base: T,
) =>
class BalanceSheetDatePeriods extends R.pipe(FinancialDatePeriods)(Base) {
/** /**
* @param {IBalanceSheetQuery} * @param {IBalanceSheetQuery}
*/ */
readonly query: IBalanceSheetQuery; public readonly query: BalanceSheetQuery;
/** /**
* Retrieves the date periods based on the report query. * Retrieves the date periods based on the report query.

View File

@@ -4,15 +4,25 @@ import {
IBalanceSheetDataNode, IBalanceSheetDataNode,
BALANCE_SHEET_NODE_TYPE, BALANCE_SHEET_NODE_TYPE,
} from './BalanceSheet.types'; } from './BalanceSheet.types';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialFilter } from '../../common/FinancialFilter'; import { FinancialFilter } from '../../common/FinancialFilter';
import { BalanceSheetBase } from './BalanceSheetBase'; import { BalanceSheetBase } from './BalanceSheetBase';
import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { FinancialSheet } from '../../common/FinancialSheet';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
export const BalanceSheetFiltering = <T extends Constructor>(Base: T) => export const BalanceSheetFiltering = <T extends GConstructor<FinancialSheet>>(
class extends R.pipe(FinancialFilter, BalanceSheetBase)(Base) { Base: T,
public repository: BalanceSheetRepository; ) =>
class extends R.pipe(
FinancialFilter,
FinancialSheetStructure,
BalanceSheetBase,
)(Base) {
/**
* @description Repository.
*/
readonly repository: BalanceSheetRepository;
// ----------------------- // -----------------------
// # Account // # Account

View File

@@ -5,18 +5,13 @@ import { IBalanceSheetMeta, IBalanceSheetQuery } from './BalanceSheet.types';
@Injectable() @Injectable()
export class BalanceSheetMetaInjectable { export class BalanceSheetMetaInjectable {
constructor( constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
private readonly financialSheetMeta: FinancialSheetMeta,
) {}
/** /**
* Retrieve the balance sheet meta. * Retrieves the balance sheet meta.
* @param {number} tenantId -
* @returns {IBalanceSheetMeta} * @returns {IBalanceSheetMeta}
*/ */
public async meta( public async meta(query: IBalanceSheetQuery): Promise<IBalanceSheetMeta> {
query: IBalanceSheetQuery
): Promise<IBalanceSheetMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedAsDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedAsDate = moment(query.toDate).format('YYYY/MM/DD');
const formattedDateRange = `As ${formattedAsDate}`; const formattedDateRange = `As ${formattedAsDate}`;

View File

@@ -15,15 +15,19 @@ import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP'; import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP';
import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY'; import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncome = (Base: any) => export const BalanceSheetNetIncome = <T extends GConstructor<FinancialSheet>>(
class extends R.compose( Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomePP, BalanceSheetNetIncomePP,
BalanceSheetNetIncomePY, BalanceSheetNetIncomePY,
BalanceSheetComparsionPreviousYear, BalanceSheetComparsionPreviousYear,
BalanceSheetComparsionPreviousPeriod, BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod, FinancialPreviousPeriod,
FinancialHorizTotals FinancialHorizTotals,
)(Base) { )(Base) {
public repository: BalanceSheetRepository; public repository: BalanceSheetRepository;
public query: BalanceSheetQuery; public query: BalanceSheetQuery;
@@ -63,7 +67,7 @@ export const BalanceSheetNetIncome = (Base: any) =>
* @return {IBalanceSheetAggregateNode} * @return {IBalanceSheetAggregateNode}
*/ */
public schemaNetIncomeNodeMapper = ( public schemaNetIncomeNodeMapper = (
node: IBalanceSheetSchemaNetIncomeNode node: IBalanceSheetSchemaNetIncomeNode,
): IBalanceSheetNetIncomeNode => { ): IBalanceSheetNetIncomeNode => {
const total = this.getNetIncomeTotal(); const total = this.getNetIncomeTotal();
@@ -81,22 +85,22 @@ export const BalanceSheetNetIncome = (Base: any) =>
* @returns {IBalanceSheetNetIncomeNode} * @returns {IBalanceSheetNetIncomeNode}
*/ */
public schemaNetIncomeNodeCompose = ( public schemaNetIncomeNodeCompose = (
node: IBalanceSheetSchemaNetIncomeNode node: IBalanceSheetSchemaNetIncomeNode,
): IBalanceSheetNetIncomeNode => { ): IBalanceSheetNetIncomeNode => {
return R.compose( return R.compose(
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
this.previousYearNetIncomeNodeCompose this.previousYearNetIncomeNodeCompose,
), ),
R.when( R.when(
this.query.isPreviousPeriodActive, this.query.isPreviousPeriodActive,
this.previousPeriodNetIncomeNodeCompose this.previousPeriodNetIncomeNodeCompose,
), ),
R.when( R.when(
this.query.isDatePeriodsColumnsType, this.query.isDatePeriodsColumnsType,
this.assocNetIncomeDatePeriodsNode this.assocNetIncomeDatePeriodsNode,
), ),
this.schemaNetIncomeNodeMapper this.schemaNetIncomeNodeMapper,
)(node); )(node);
}; };
@@ -160,7 +164,7 @@ export const BalanceSheetNetIncome = (Base: any) =>
public getNetIncomeDatePeriodNode = ( public getNetIncomeDatePeriodNode = (
node: IBalanceSheetNetIncomeNode, node: IBalanceSheetNetIncomeNode,
fromDate: Date, fromDate: Date,
toDate: Date toDate: Date,
): IBalanceSheetTotalPeriod => { ): IBalanceSheetTotalPeriod => {
const periodTotal = this.getNetIncomeDatePeriodTotal(toDate); const periodTotal = this.getNetIncomeDatePeriodTotal(toDate);
@@ -173,11 +177,11 @@ export const BalanceSheetNetIncome = (Base: any) =>
* @returns {IBalanceSheetNetIncomeNode} * @returns {IBalanceSheetNetIncomeNode}
*/ */
public getNetIncomeDatePeriodsNode = ( public getNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode node: IBalanceSheetNetIncomeNode,
): IBalanceSheetTotalPeriod[] => { ): IBalanceSheetTotalPeriod[] => {
return this.getReportNodeDatePeriods( return this.getReportNodeDatePeriods(
node, node,
this.getNetIncomeDatePeriodNode this.getNetIncomeDatePeriodNode,
); );
}; };
@@ -187,7 +191,7 @@ export const BalanceSheetNetIncome = (Base: any) =>
* @returns {IBalanceSheetNetIncomeNode} * @returns {IBalanceSheetNetIncomeNode}
*/ */
public assocNetIncomeDatePeriodsNode = ( public assocNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => { ): IBalanceSheetNetIncomeNode => {
const datePeriods = this.getNetIncomeDatePeriodsNode(node); const datePeriods = this.getNetIncomeDatePeriodsNode(node);
@@ -203,13 +207,13 @@ export const BalanceSheetNetIncome = (Base: any) =>
* @return {IBalanceSheetDataNode} * @return {IBalanceSheetDataNode}
*/ */
public reportNetIncomeNodeSchemaParser = ( public reportNetIncomeNodeSchemaParser = (
schemaNode: IBalanceSheetSchemaNode schemaNode: IBalanceSheetSchemaNode,
): IBalanceSheetDataNode => { ): IBalanceSheetDataNode => {
return R.compose( return R.compose(
R.when( R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME), this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME),
this.schemaNetIncomeNodeCompose this.schemaNetIncomeNodeCompose,
) ),
)(schemaNode); )(schemaNode);
}; };
@@ -219,7 +223,7 @@ export const BalanceSheetNetIncome = (Base: any) =>
* @return {IBalanceSheetDataNode[]} * @return {IBalanceSheetDataNode[]}
*/ */
public netIncomeSchemaParser = ( public netIncomeSchemaParser = (
nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[] nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[],
): IBalanceSheetDataNode[] => { ): IBalanceSheetDataNode[] => {
return this.mapNodesDeep(nodes, this.reportNetIncomeNodeSchemaParser); return this.mapNodesDeep(nodes, this.reportNetIncomeNodeSchemaParser);
}; };

View File

@@ -8,21 +8,25 @@ import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPr
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP'; import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP';
import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY'; import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncomeDatePeriods = (Base: any) => export const BalanceSheetNetIncomeDatePeriods = <
class extends R.compose( T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomePP, BalanceSheetNetIncomePP,
BalanceSheetNetIncomePY, BalanceSheetNetIncomePY,
BalanceSheetComparsionPreviousYear, BalanceSheetComparsionPreviousYear,
BalanceSheetComparsionPreviousPeriod, BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod, FinancialPreviousPeriod,
FinancialHorizTotals FinancialHorizTotals,
)(Base) { )(Base) {
private repository: BalanceSheetRepository; repository: BalanceSheetRepository;
private query: BalanceSheetQuery;
// -------------------------------- // --------------------------------
// # Date Periods // # Date Periods
@@ -84,7 +88,7 @@ export const BalanceSheetNetIncomeDatePeriods = (Base: any) =>
private getNetIncomeDatePeriodNode = ( private getNetIncomeDatePeriodNode = (
node: IBalanceSheetNetIncomeNode, node: IBalanceSheetNetIncomeNode,
fromDate: Date, fromDate: Date,
toDate: Date toDate: Date,
): IBalanceSheetTotalPeriod => { ): IBalanceSheetTotalPeriod => {
const periodTotal = this.getNetIncomeDatePeriodTotal(toDate); const periodTotal = this.getNetIncomeDatePeriodTotal(toDate);
@@ -97,11 +101,11 @@ export const BalanceSheetNetIncomeDatePeriods = (Base: any) =>
* @returns {IBalanceSheetNetIncomeNode} * @returns {IBalanceSheetNetIncomeNode}
*/ */
private getNetIncomeDatePeriodsNode = ( private getNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode node: IBalanceSheetNetIncomeNode,
): IBalanceSheetTotalPeriod[] => { ): IBalanceSheetTotalPeriod[] => {
return this.getReportNodeDatePeriods( return this.getReportNodeDatePeriods(
node, node,
this.getNetIncomeDatePeriodNode this.getNetIncomeDatePeriodNode,
); );
}; };
@@ -111,7 +115,7 @@ export const BalanceSheetNetIncomeDatePeriods = (Base: any) =>
* @returns {IBalanceSheetNetIncomeNode} * @returns {IBalanceSheetNetIncomeNode}
*/ */
public assocNetIncomeDatePeriodsNode = ( public assocNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => { ): IBalanceSheetNetIncomeNode => {
const datePeriods = this.getNetIncomeDatePeriodsNode(node); const datePeriods = this.getNetIncomeDatePeriodsNode(node);

View File

@@ -7,10 +7,16 @@ import {
IBalanceSheetTotal, IBalanceSheetTotal,
} from './BalanceSheet.types'; } from './BalanceSheet.types';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import BalanceSheetRepository from './BalanceSheetRepository'; import { BalanceSheetRepository } from './BalanceSheetRepository';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => export const BalanceSheetNetIncomeDatePeriodsPP = <
class extends R.compose( T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetComparsionPreviousPeriod, BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod, FinancialPreviousPeriod,
FinancialHorizTotals, FinancialHorizTotals,

View File

@@ -2,15 +2,24 @@ import * as R from 'ramda';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { IBalanceSheetNetIncomeNode, IBalanceSheetTotal } from './BalanceSheet.types'; import {
IBalanceSheetNetIncomeNode,
IBalanceSheetTotal,
} from './BalanceSheet.types';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import BalanceSheetRepository from './BalanceSheetRepository'; import { BalanceSheetRepository } from './BalanceSheetRepository';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) => export const BalanceSheetNetIncomeDatePeriodsPY = <
class extends R.compose( T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetComparsionPreviousYear, BalanceSheetComparsionPreviousYear,
FinancialPreviousPeriod, FinancialPreviousPeriod,
FinancialHorizTotals FinancialHorizTotals,
)(Base) { )(Base) {
query: BalanceSheetQuery; query: BalanceSheetQuery;
repository: BalanceSheetRepository; repository: BalanceSheetRepository;
@@ -67,10 +76,10 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) =>
public assocPreviousYearNetIncomeHorizTotal = R.curry( public assocPreviousYearNetIncomeHorizTotal = R.curry(
(node: IBalanceSheetNetIncomeNode, totalNode) => { (node: IBalanceSheetNetIncomeNode, totalNode) => {
const total = this.getPYNetIncomeDatePeriodTotal( const total = this.getPYNetIncomeDatePeriodTotal(
totalNode.previousYearToDate.date totalNode.previousYearToDate.date,
); );
return R.assoc('previousYear', this.getAmountMeta(total), totalNode); return R.assoc('previousYear', this.getAmountMeta(total), totalNode);
} },
); );
/** /**
@@ -81,27 +90,27 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) =>
public previousYearNetIncomeHorizNodeComposer = R.curry( public previousYearNetIncomeHorizNodeComposer = R.curry(
( (
node: IBalanceSheetNetIncomeNode, node: IBalanceSheetNetIncomeNode,
horiontalTotalNode: IBalanceSheetTotal horiontalTotalNode: IBalanceSheetTotal,
): IBalanceSheetTotal => { ): IBalanceSheetTotal => {
return R.compose( return R.compose(
R.when( R.when(
this.query.isPreviousYearPercentageActive, this.query.isPreviousYearPercentageActive,
this.assocPreviousYearTotalPercentageNode this.assocPreviousYearTotalPercentageNode,
), ),
R.when( R.when(
this.query.isPreviousYearChangeActive, this.query.isPreviousYearChangeActive,
this.assocPreviousYearTotalChangeNode this.assocPreviousYearTotalChangeNode,
), ),
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
this.assocPreviousYearNetIncomeHorizTotal(node) this.assocPreviousYearNetIncomeHorizTotal(node),
), ),
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
this.assocPreviousYearHorizNodeFromToDates this.assocPreviousYearHorizNodeFromToDates,
) ),
)(horiontalTotalNode); )(horiontalTotalNode);
} },
); );
/** /**
@@ -110,11 +119,11 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) =>
* @returns {IBalanceSheetCommonNode} * @returns {IBalanceSheetCommonNode}
*/ */
public assocPreviousYearNetIncomeHorizNode = ( public assocPreviousYearNetIncomeHorizNode = (
node: IBalanceSheetNetIncomeNode node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => { ): IBalanceSheetNetIncomeNode => {
const horizontalTotals = R.addIndex(R.map)( const horizontalTotals = R.addIndex(R.map)(
this.previousYearNetIncomeHorizNodeComposer(node), this.previousYearNetIncomeHorizNodeComposer(node),
node.horizontalTotals node.horizontalTotals,
) as IBalanceSheetTotal[]; ) as IBalanceSheetTotal[];
return R.assoc('horizontalTotals', horizontalTotals, node); return R.assoc('horizontalTotals', horizontalTotals, node);

View File

@@ -9,9 +9,13 @@ import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetNetIncomeDatePeriodsPP } from './BalanceSheetNetIncomeDatePeriodsPP'; import { BalanceSheetNetIncomeDatePeriodsPP } from './BalanceSheetNetIncomeDatePeriodsPP';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncomePP = (Base: any) => export const BalanceSheetNetIncomePP = <T extends GConstructor<FinancialSheet>>(
class extends R.compose( Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomeDatePeriodsPP, BalanceSheetNetIncomeDatePeriodsPP,
BalanceSheetComparsionPreviousPeriod, BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod, FinancialPreviousPeriod,

View File

@@ -1,8 +1,5 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { import { IBalanceSheetNetIncomeNode } from './BalanceSheet.types';
IBalanceSheetDataNode,
IBalanceSheetNetIncomeNode,
} from './BalanceSheet.types';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
@@ -10,17 +7,21 @@ import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetNetIncomeDatePeriodsPY } from './BalanceSheetNetIncomeDatePeriodsPY'; import { BalanceSheetNetIncomeDatePeriodsPY } from './BalanceSheetNetIncomeDatePeriodsPY';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncomePY = (Base: any) => export const BalanceSheetNetIncomePY = <T extends GConstructor<FinancialSheet>>(
class extends R.compose( Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomeDatePeriodsPY, BalanceSheetNetIncomeDatePeriodsPY,
BalanceSheetComparsionPreviousYear, BalanceSheetComparsionPreviousYear,
BalanceSheetComparsionPreviousPeriod, BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod, FinancialPreviousPeriod,
FinancialHorizTotals FinancialHorizTotals,
)(Base) { )(Base) {
public repository: BalanceSheetRepository; // public repository: BalanceSheetRepository;
public query: BalanceSheetQuery; // public query: BalanceSheetQuery;
// ------------------------------ // ------------------------------
// # Previous Year (PY) // # Previous Year (PY)
@@ -44,7 +45,7 @@ export const BalanceSheetNetIncomePY = (Base: any) =>
* @returns {IBalanceSheetAccountNode} * @returns {IBalanceSheetAccountNode}
*/ */
public assocPreviousYearNetIncomeNode = ( public assocPreviousYearNetIncomeNode = (
node: IBalanceSheetNetIncomeNode node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => { ): IBalanceSheetNetIncomeNode => {
const total = this.getPreviousYearNetIncome(); const total = this.getPreviousYearNetIncome();
@@ -57,23 +58,23 @@ export const BalanceSheetNetIncomePY = (Base: any) =>
* @returns {IBalanceSheetAccountNode} * @returns {IBalanceSheetAccountNode}
*/ */
public previousYearNetIncomeNodeCompose = ( public previousYearNetIncomeNodeCompose = (
node: IBalanceSheetNetIncomeNode node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => { ): IBalanceSheetNetIncomeNode => {
return R.compose( return R.compose(
R.when( R.when(
this.query.isPreviousYearPercentageActive, this.query.isPreviousYearPercentageActive,
this.assocPreviousYearTotalPercentageNode this.assocPreviousYearTotalPercentageNode,
), ),
R.when( R.when(
this.query.isPreviousYearChangeActive, this.query.isPreviousYearChangeActive,
this.assocPreviousYearTotalChangeNode this.assocPreviousYearTotalChangeNode,
), ),
// Associate the PY to date periods horizontal nodes. // Associate the PY to date periods horizontal nodes.
R.when( R.when(
this.isNodeHasHorizontalTotals, this.isNodeHasHorizontalTotals,
this.assocPreviousYearNetIncomeHorizNode this.assocPreviousYearNetIncomeHorizNode,
), ),
this.assocPreviousYearNetIncomeNode this.assocPreviousYearNetIncomeNode,
)(node); )(node);
}; };
}; };

View File

@@ -13,13 +13,10 @@ export class BalanceSheetPdfInjectable {
/** /**
* Converts the given balance sheet table to pdf. * Converts the given balance sheet table to pdf.
* @param {number} tenantId - Tenant ID.
* @param {IBalanceSheetQuery} query - Balance sheet query. * @param {IBalanceSheetQuery} query - Balance sheet query.
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}
*/ */
public async pdf( public async pdf(query: IBalanceSheetQuery): Promise<Buffer> {
query: IBalanceSheetQuery,
): Promise<Buffer> {
const table = await this.balanceSheetTable.table(query); const table = await this.balanceSheetTable.table(query);
return this.tableSheetPdf.convertToPdf( return this.tableSheetPdf.convertToPdf(

View File

@@ -2,9 +2,12 @@ import * as R from 'ramda';
import { get } from 'lodash'; import { get } from 'lodash';
import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetQuery } from './BalanceSheetQuery';
import { IBalanceSheetDataNode } from './BalanceSheet.types'; import { IBalanceSheetDataNode } from './BalanceSheet.types';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetPercentage = <T extends Constructor>(Base: T) => export const BalanceSheetPercentage = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends Base { class extends Base {
readonly query: BalanceSheetQuery; readonly query: BalanceSheetQuery;
@@ -16,18 +19,18 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
public assocReportNodeColumnPercentage = R.curry( public assocReportNodeColumnPercentage = R.curry(
( (
parentTotal: number, parentTotal: number,
node: IBalanceSheetDataNode node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => { ): IBalanceSheetDataNode => {
const percentage = this.getPercentageBasis( const percentage = this.getPercentageBasis(
parentTotal, parentTotal,
node.total.amount node.total.amount,
); );
return R.assoc( return R.assoc(
'percentageColumn', 'percentageColumn',
this.getPercentageAmountMeta(percentage), this.getPercentageAmountMeta(percentage),
node node,
); );
} },
); );
/** /**
@@ -38,18 +41,18 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
public assocReportNodeRowPercentage = R.curry( public assocReportNodeRowPercentage = R.curry(
( (
parentTotal: number, parentTotal: number,
node: IBalanceSheetDataNode node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => { ): IBalanceSheetDataNode => {
const percenatage = this.getPercentageBasis( const percenatage = this.getPercentageBasis(
parentTotal, parentTotal,
node.total.amount node.total.amount,
); );
return R.assoc( return R.assoc(
'percentageRow', 'percentageRow',
this.getPercentageAmountMeta(percenatage), this.getPercentageAmountMeta(percenatage),
node node,
); );
} },
); );
/** /**
@@ -61,13 +64,13 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
public assocRowPercentageHorizTotals = R.curry( public assocRowPercentageHorizTotals = R.curry(
( (
parentTotal: number, parentTotal: number,
node: IBalanceSheetDataNode node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => { ): IBalanceSheetDataNode => {
const assocRowPercen = this.assocReportNodeRowPercentage(parentTotal); const assocRowPercen = this.assocReportNodeRowPercentage(parentTotal);
const horTotals = R.map(assocRowPercen)(node.horizontalTotals); const horTotals = R.map(assocRowPercen)(node.horizontalTotals);
return R.assoc('horizontalTotals', horTotals, node); return R.assoc('horizontalTotals', horTotals, node);
} },
); );
/** /**
@@ -81,10 +84,10 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
const parentTotal = get( const parentTotal = get(
parentNode, parentNode,
`horizontalTotals[${index}].total.amount`, `horizontalTotals[${index}].total.amount`,
0 0,
); );
return this.assocReportNodeColumnPercentage(parentTotal, horTotalNode); return this.assocReportNodeColumnPercentage(parentTotal, horTotalNode);
} },
); );
/** /**
@@ -95,15 +98,15 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
public assocColumnPercentageHorizTotals = R.curry( public assocColumnPercentageHorizTotals = R.curry(
( (
parentNode: IBalanceSheetDataNode, parentNode: IBalanceSheetDataNode,
node: IBalanceSheetDataNode node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => { ): IBalanceSheetDataNode => {
// Horizontal totals. // Horizontal totals.
const assocColPerc = this.assocColumnPercentageHorizTotal(parentNode); const assocColPerc = this.assocColumnPercentageHorizTotal(parentNode);
const horTotals = R.addIndex(R.map)(assocColPerc)( const horTotals = R.addIndex(R.map)(assocColPerc)(
node.horizontalTotals node.horizontalTotals,
); );
return R.assoc('horizontalTotals', horTotals, node); return R.assoc('horizontalTotals', horTotals, node);
} },
); );
/** /**
@@ -112,19 +115,17 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
* @param {} node * @param {} node
* @returns * @returns
*/ */
public reportNodeColumnPercentageComposer = R.curry( public reportNodeColumnPercentageComposer = R.curry((parentNode, node) => {
(parentNode, node) => {
const parentTotal = parentNode.total.amount; const parentTotal = parentNode.total.amount;
return R.compose( return R.compose(
R.when( R.when(
this.isNodeHasHorizoTotals, this.isNodeHasHorizoTotals,
this.assocColumnPercentageHorizTotals(parentNode) this.assocColumnPercentageHorizTotals(parentNode),
), ),
this.assocReportNodeColumnPercentage(parentTotal) this.assocReportNodeColumnPercentage(parentTotal),
)(node); )(node);
} });
);
/** /**
* *
@@ -137,9 +138,9 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
return R.compose( return R.compose(
R.when( R.when(
this.isNodeHasHorizoTotals, this.isNodeHasHorizoTotals,
this.assocRowPercentageHorizTotals(total) this.assocRowPercentageHorizTotals(total),
), ),
this.assocReportNodeRowPercentage(total) this.assocReportNodeRowPercentage(total),
)(node); )(node);
}; };
@@ -149,7 +150,7 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
private assocNodeColumnPercentageChildren = (node) => { private assocNodeColumnPercentageChildren = (node) => {
const children = this.mapNodesDeep( const children = this.mapNodesDeep(
node.children, node.children,
this.reportNodeColumnPercentageComposer(node) this.reportNodeColumnPercentageComposer(node),
); );
return R.assoc('children', children, node); return R.assoc('children', children, node);
}; };
@@ -166,10 +167,10 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
return R.compose( return R.compose(
R.when( R.when(
this.isNodeHasHorizoTotals, this.isNodeHasHorizoTotals,
this.assocColumnPercentageHorizTotals(parentNode) this.assocColumnPercentageHorizTotals(parentNode),
), ),
this.assocReportNodeColumnPercentage(parentTotal), this.assocReportNodeColumnPercentage(parentTotal),
this.assocNodeColumnPercentageChildren this.assocNodeColumnPercentageChildren,
)(node); )(node);
}; };
@@ -179,7 +180,7 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
* @returns {IBalanceSheetDataNode[]} * @returns {IBalanceSheetDataNode[]}
*/ */
private reportColumnsPercentageMapper = ( private reportColumnsPercentageMapper = (
nodes: IBalanceSheetDataNode[] nodes: IBalanceSheetDataNode[],
): IBalanceSheetDataNode[] => { ): IBalanceSheetDataNode[] => {
return R.map(this.reportNodeColumnPercentageDeepMap, nodes); return R.map(this.reportNodeColumnPercentageDeepMap, nodes);
}; };
@@ -202,12 +203,12 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
return R.compose( return R.compose(
R.when( R.when(
this.query.isColumnsPercentageActive, this.query.isColumnsPercentageActive,
this.reportColumnsPercentageMapper this.reportColumnsPercentageMapper,
), ),
R.when( R.when(
this.query.isRowsPercentageActive, this.query.isRowsPercentageActive,
this.reportRowsPercentageMapper this.reportRowsPercentageMapper,
) ),
)(nodes); )(nodes);
}; };
@@ -216,9 +217,7 @@ export const BalanceSheetPercentage = <T extends Constructor>(Base: T) =>
* @param {IBalanceSheetDataNode} node * @param {IBalanceSheetDataNode} node
* @returns {boolean} * @returns {boolean}
*/ */
public isNodeHasHorizoTotals = ( public isNodeHasHorizoTotals = (node: IBalanceSheetDataNode): boolean => {
node: IBalanceSheetDataNode
): boolean => {
return ( return (
!R.isEmpty(node.horizontalTotals) && !R.isNil(node.horizontalTotals) !R.isEmpty(node.horizontalTotals) && !R.isNil(node.horizontalTotals)
); );

View File

@@ -15,30 +15,28 @@ import { transformToMapBy } from '@/utils/transform-to-map-by';
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';
@Injectable({ scope: Scope.TRANSIENT }) @Injectable({ scope: Scope.TRANSIENT })
export class BalanceSheetRepository extends R.compose( export class BalanceSheetRepository extends R.compose(
BalanceSheetRepositoryNetIncome, BalanceSheetRepositoryNetIncome,
FinancialDatePeriods FinancialDatePeriods,
)(class {}) { )(class {}) {
/** /**
* * Account model.
*/ */
@Inject(Account.name) @Inject(Account.name)
public readonly accountModel: typeof Account; public readonly accountModel: typeof Account;
/**
* Account transaction model.
*/
@Inject(AccountTransaction.name) @Inject(AccountTransaction.name)
public readonly accountTransactionModel: typeof AccountTransaction; public readonly accountTransactionModel: typeof AccountTransaction;
/** /**
* @param {number} * @description Balance sheet query.
*/
public readonly tenantId: number;
/**
* @param {BalanceSheetQuery} * @param {BalanceSheetQuery}
*/ */
public readonly query: BalanceSheetQuery; public query: BalanceSheetQuery;
/** /**
* @param {} * @param {}
@@ -153,14 +151,13 @@ export class BalanceSheetRepository extends R.compose(
/** /**
* Constructor method. * Constructor method.
* @param {number} tenantId
* @param {IBalanceSheetQuery} query * @param {IBalanceSheetQuery} query
*/ */
public setQuery(query: IBalanceSheetQuery) { public setQuery(query: IBalanceSheetQuery) {
this.query = new BalanceSheetQuery(query); this.query = new BalanceSheetQuery(query);
this.transactionsGroupType = this.getGroupByFromDisplayColumnsBy( this.transactionsGroupType = this.getGroupByFromDisplayColumnsBy(
this.query.displayColumnsBy this.query.displayColumnsBy,
); );
} }
@@ -248,16 +245,16 @@ export class BalanceSheetRepository extends R.compose(
const periodsByAccount = await this.accountsDatePeriods( const periodsByAccount = await this.accountsDatePeriods(
this.query.fromDate, this.query.fromDate,
this.query.toDate, this.query.toDate,
this.transactionsGroupType this.transactionsGroupType,
); );
// Retrieves opening balance of grouped transactions. // Retrieves opening balance of grouped transactions.
const periodsOpeningByAccount = await this.closingAccountsTotal( const periodsOpeningByAccount = await this.closingAccountsTotal(
this.query.fromDate this.query.fromDate,
); );
// Inject to the repository. // Inject to the repository.
this.periodsAccountsLedger = Ledger.fromTransactions(periodsByAccount); this.periodsAccountsLedger = Ledger.fromTransactions(periodsByAccount);
this.periodsOpeningAccountLedger = Ledger.fromTransactions( this.periodsOpeningAccountLedger = Ledger.fromTransactions(
periodsOpeningByAccount periodsOpeningByAccount,
); );
}; };
@@ -270,7 +267,7 @@ export class BalanceSheetRepository extends R.compose(
*/ */
public initTotalPreviousYear = async (): Promise<void> => { public initTotalPreviousYear = async (): Promise<void> => {
const PYTotalsByAccounts = await this.closingAccountsTotal( const PYTotalsByAccounts = await this.closingAccountsTotal(
this.query.PYToDate this.query.PYToDate,
); );
// Inject to the repository. // Inject to the repository.
this.PYTotalAccountsLedger = Ledger.fromTransactions(PYTotalsByAccounts); this.PYTotalAccountsLedger = Ledger.fromTransactions(PYTotalsByAccounts);
@@ -284,16 +281,16 @@ export class BalanceSheetRepository extends R.compose(
const PYPeriodsBYAccounts = await this.accountsDatePeriods( const PYPeriodsBYAccounts = await this.accountsDatePeriods(
this.query.PYFromDate, this.query.PYFromDate,
this.query.PYToDate, this.query.PYToDate,
this.transactionsGroupType this.transactionsGroupType,
); );
// Retrieves opening balance of grouped transactions. // Retrieves opening balance of grouped transactions.
const periodsOpeningByAccount = await this.closingAccountsTotal( const periodsOpeningByAccount = await this.closingAccountsTotal(
this.query.PYFromDate this.query.PYFromDate,
); );
// Inject to the repository. // Inject to the repository.
this.PYPeriodsAccountsLedger = Ledger.fromTransactions(PYPeriodsBYAccounts); this.PYPeriodsAccountsLedger = Ledger.fromTransactions(PYPeriodsBYAccounts);
this.PYPeriodsOpeningAccountLedger = Ledger.fromTransactions( this.PYPeriodsOpeningAccountLedger = Ledger.fromTransactions(
periodsOpeningByAccount periodsOpeningByAccount,
); );
}; };
@@ -306,7 +303,7 @@ export class BalanceSheetRepository extends R.compose(
*/ */
public initTotalPreviousPeriod = async (): Promise<void> => { public initTotalPreviousPeriod = async (): Promise<void> => {
const PPTotalsByAccounts = await this.closingAccountsTotal( const PPTotalsByAccounts = await this.closingAccountsTotal(
this.query.PPToDate this.query.PPToDate,
); );
// Inject to the repository. // Inject to the repository.
this.PPTotalAccountsLedger = Ledger.fromTransactions(PPTotalsByAccounts); this.PPTotalAccountsLedger = Ledger.fromTransactions(PPTotalsByAccounts);
@@ -320,16 +317,16 @@ export class BalanceSheetRepository extends R.compose(
const PPPeriodsBYAccounts = await this.accountsDatePeriods( const PPPeriodsBYAccounts = await this.accountsDatePeriods(
this.query.PPFromDate, this.query.PPFromDate,
this.query.PPToDate, this.query.PPToDate,
this.transactionsGroupType this.transactionsGroupType,
); );
// Retrieves opening balance of grouped transactions. // Retrieves opening balance of grouped transactions.
const periodsOpeningByAccount = await this.closingAccountsTotal( const periodsOpeningByAccount = await this.closingAccountsTotal(
this.query.PPFromDate this.query.PPFromDate,
); );
// Inject to the repository. // Inject to the repository.
this.PPPeriodsAccountsLedger = Ledger.fromTransactions(PPPeriodsBYAccounts); this.PPPeriodsAccountsLedger = Ledger.fromTransactions(PPPeriodsBYAccounts);
this.PPPeriodsOpeningAccountLedger = Ledger.fromTransactions( this.PPPeriodsOpeningAccountLedger = Ledger.fromTransactions(
periodsOpeningByAccount periodsOpeningByAccount,
); );
}; };
@@ -354,7 +351,7 @@ export class BalanceSheetRepository extends R.compose(
public accountsDatePeriods = async ( public accountsDatePeriods = async (
fromDate: Date, fromDate: Date,
toDate: Date, toDate: Date,
datePeriodsType: string datePeriodsType: string,
) => { ) => {
return this.accountTransactionModel.query().onBuild((query) => { return this.accountTransactionModel.query().onBuild((query) => {
query.sum('credit as credit'); query.sum('credit as credit');

View File

@@ -5,11 +5,14 @@ import { Account } from '@/modules/Accounts/models/Account.model';
import { ILedger } from '@/modules/Ledger/types/Ledger.types'; import { ILedger } from '@/modules/Ledger/types/Ledger.types';
import { ACCOUNT_PARENT_TYPE } from '@/constants/accounts'; import { ACCOUNT_PARENT_TYPE } from '@/constants/accounts';
import { GConstructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetRepositoryNetIncome = <T extends GConstructor>( export const BalanceSheetRepositoryNetIncome = <
T extends GConstructor<FinancialSheet>,
>(
Base: T, Base: T,
) => ) =>
class extends R.compose(FinancialDatePeriods)(Base) { class extends R.pipe(FinancialDatePeriods)(Base) {
// ----------------------- // -----------------------
// # Net Income // # Net Income
// ----------------------- // -----------------------

View File

@@ -5,11 +5,14 @@ import {
BALANCE_SHEET_SCHEMA_NODE_TYPE, BALANCE_SHEET_SCHEMA_NODE_TYPE,
} from './BalanceSheet.types'; } from './BalanceSheet.types';
import { FinancialSchema } from '../../common/FinancialSchema'; import { FinancialSchema } from '../../common/FinancialSchema';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { ACCOUNT_TYPE } from '@/constants/accounts'; import { ACCOUNT_TYPE } from '@/constants/accounts';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetSchema = <T extends Constructor>(Base: T) => export const BalanceSheetSchema = <T extends GConstructor<FinancialSheet>>(
class extends R.compose(FinancialSchema)(Base) { Base: T,
) =>
class extends R.pipe(FinancialSchema)(Base) {
/** /**
* Retrieves the balance sheet schema. * Retrieves the balance sheet schema.
* @returns * @returns

View File

@@ -1,15 +1,17 @@
import * as R from 'ramda'; import * as R from 'ramda';
import * as moment from 'moment'; import * as moment from 'moment';
import { import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types';
ITableColumn,
ITableColumnAccessor,
} from '../../types/Table.types';
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
import { IDateRange } from '../CashFlow/Cashflow.types'; import { IDateRange } from '../CashFlow/Cashflow.types';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetTableDatePeriods = <T extends Constructor>(Base: T) => export const BalanceSheetTableDatePeriods = <
class extends R.compose(FinancialDatePeriods)(Base) { T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(FinancialDatePeriods)(Base) {
/** /**
* Retrieves the date periods based on the report query. * Retrieves the date periods based on the report query.
* @returns {IDateRange[]} * @returns {IDateRange[]}
@@ -18,7 +20,7 @@ export const BalanceSheetTableDatePeriods = <T extends Constructor>(Base: T) =>
return this.getDateRanges( return this.getDateRanges(
this.query.fromDate, this.query.fromDate,
this.query.toDate, this.query.toDate,
this.query.displayColumnsBy this.query.displayColumnsBy,
); );
} }
@@ -44,7 +46,7 @@ export const BalanceSheetTableDatePeriods = <T extends Constructor>(Base: T) =>
R.always(this.query.isDisplayColumnsBy(type)), R.always(this.query.isDisplayColumnsBy(type)),
formatFn, formatFn,
], ],
conditions conditions,
); );
return R.compose(R.cond(conditionsPairs))(dateRange); return R.compose(R.cond(conditionsPairs))(dateRange);
}; };
@@ -68,9 +70,9 @@ export const BalanceSheetTableDatePeriods = <T extends Constructor>(Base: T) =>
key: `date-range-${index}`, key: `date-range-${index}`,
accessor: `horizontalTotals[${index}].total.formattedAmount`, accessor: `horizontalTotals[${index}].total.formattedAmount`,
}, },
]) ]),
)([]); )([]);
} },
); );
/** /**
@@ -80,7 +82,7 @@ export const BalanceSheetTableDatePeriods = <T extends Constructor>(Base: T) =>
public datePeriodsColumnsAccessors = (): ITableColumnAccessor[] => { public datePeriodsColumnsAccessors = (): ITableColumnAccessor[] => {
return R.compose( return R.compose(
R.flatten, R.flatten,
R.addIndex(R.map)(this.datePeriodColumnsAccessor) R.addIndex(R.map)(this.datePeriodColumnsAccessor),
)(this.datePeriods); )(this.datePeriods);
}; };
@@ -95,18 +97,18 @@ export const BalanceSheetTableDatePeriods = <T extends Constructor>(Base: T) =>
*/ */
public datePeriodChildrenColumns = ( public datePeriodChildrenColumns = (
index: number, index: number,
dateRange: IDateRange dateRange: IDateRange,
) => { ) => {
return R.compose( return R.compose(
R.unless( R.unless(
R.isEmpty, R.isEmpty,
R.concat([ R.concat([
{ key: `total`, label: this.i18n.__('balance_sheet.total') }, { key: `total`, label: this.i18n.__('balance_sheet.total') },
]) ]),
), ),
R.concat(this.percentageColumns()), R.concat(this.percentageColumns()),
R.concat(this.getPreviousYearHorizontalColumns(dateRange)), R.concat(this.getPreviousYearHorizontalColumns(dateRange)),
R.concat(this.previousPeriodHorizontalColumns(dateRange)) R.concat(this.previousPeriodHorizontalColumns(dateRange)),
)([]); )([]);
}; };
@@ -118,7 +120,7 @@ export const BalanceSheetTableDatePeriods = <T extends Constructor>(Base: T) =>
*/ */
public datePeriodColumn = ( public datePeriodColumn = (
dateRange: IDateRange, dateRange: IDateRange,
index: number index: number,
): ITableColumn => { ): ITableColumn => {
return { return {
key: `date-range-${index}`, key: `date-range-${index}`,

View File

@@ -13,8 +13,7 @@ export class BalanceSheetTableInjectable {
/** /**
* Retrieves the balance sheet in table format. * Retrieves the balance sheet in table format.
* @param {number} tenantId * @param {number} query -
* @param {number} query
* @returns {Promise<IBalanceSheetTable>} * @returns {Promise<IBalanceSheetTable>}
*/ */
public async table(filter: IBalanceSheetQuery): Promise<IBalanceSheetTable> { public async table(filter: IBalanceSheetQuery): Promise<IBalanceSheetTable> {

View File

@@ -4,12 +4,17 @@ import { FinancialTablePreviousPeriod } from '../../common/FinancialTablePreviou
import { FinancialDateRanges } from '../../common/FinancialDateRanges'; import { FinancialDateRanges } from '../../common/FinancialDateRanges';
import { IDateRange } from '../../types/Report.types'; import { IDateRange } from '../../types/Report.types';
import { ITableColumn } from '../../types/Table.types'; import { ITableColumn } from '../../types/Table.types';
import { Constructor } from '@/common/types/Constructor'; import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetTablePreviousPeriod = <T extends Constructor>(Base: T) => export const BalanceSheetTablePreviousPeriod = <
class BalanceSheetTablePreviousPeriod extends R.compose( T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class BalanceSheetTablePreviousPeriod extends R.pipe(
FinancialTablePreviousPeriod, FinancialTablePreviousPeriod,
FinancialDateRanges FinancialDateRanges,
)(Base) { )(Base) {
readonly query: BalanceSheetQuery; readonly query: BalanceSheetQuery;
@@ -20,23 +25,21 @@ export const BalanceSheetTablePreviousPeriod = <T extends Constructor>(Base: T)
* Retrieves the previous period columns. * Retrieves the previous period columns.
* @returns {ITableColumn[]} * @returns {ITableColumn[]}
*/ */
public previousPeriodColumns = ( public previousPeriodColumns = (dateRange?: IDateRange): ITableColumn[] => {
dateRange?: IDateRange
): ITableColumn[] => {
return R.pipe( return R.pipe(
// Previous period columns. // Previous period columns.
R.when( R.when(
this.query.isPreviousPeriodActive, this.query.isPreviousPeriodActive,
R.append(this.getPreviousPeriodTotalColumn(dateRange)) R.append(this.getPreviousPeriodTotalColumn(dateRange)),
), ),
R.when( R.when(
this.query.isPreviousPeriodChangeActive, this.query.isPreviousPeriodChangeActive,
R.append(this.getPreviousPeriodChangeColumn()) R.append(this.getPreviousPeriodChangeColumn()),
), ),
R.when( R.when(
this.query.isPreviousPeriodPercentageActive, this.query.isPreviousPeriodPercentageActive,
R.append(this.getPreviousPeriodPercentageColumn()) R.append(this.getPreviousPeriodPercentageColumn()),
) ),
)([]); )([]);
}; };
@@ -46,12 +49,12 @@ export const BalanceSheetTablePreviousPeriod = <T extends Constructor>(Base: T)
* @returns {ITableColumn} * @returns {ITableColumn}
*/ */
public previousPeriodHorizontalColumns = ( public previousPeriodHorizontalColumns = (
dateRange: IDateRange dateRange: IDateRange,
): ITableColumn[] => { ): ITableColumn[] => {
const PPDateRange = this.getPPDatePeriodDateRange( const PPDateRange = this.getPPDatePeriodDateRange(
dateRange.fromDate, dateRange.fromDate,
dateRange.toDate, dateRange.toDate,
this.query.displayColumnsBy this.query.displayColumnsBy,
); );
return this.previousPeriodColumns({ return this.previousPeriodColumns({
fromDate: PPDateRange.fromDate, fromDate: PPDateRange.fromDate,
@@ -71,16 +74,16 @@ export const BalanceSheetTablePreviousPeriod = <T extends Constructor>(Base: T)
// Previous period columns. // Previous period columns.
R.when( R.when(
this.query.isPreviousPeriodActive, this.query.isPreviousPeriodActive,
R.append(this.getPreviousPeriodTotalAccessor()) R.append(this.getPreviousPeriodTotalAccessor()),
), ),
R.when( R.when(
this.query.isPreviousPeriodChangeActive, this.query.isPreviousPeriodChangeActive,
R.append(this.getPreviousPeriodChangeAccessor()) R.append(this.getPreviousPeriodChangeAccessor()),
), ),
R.when( R.when(
this.query.isPreviousPeriodPercentageActive, this.query.isPreviousPeriodPercentageActive,
R.append(this.getPreviousPeriodPercentageAccessor()) R.append(this.getPreviousPeriodPercentageAccessor()),
) ),
)([]); )([]);
}; };
@@ -90,22 +93,22 @@ export const BalanceSheetTablePreviousPeriod = <T extends Constructor>(Base: T)
* @returns * @returns
*/ */
public previousPeriodHorizColumnAccessors = ( public previousPeriodHorizColumnAccessors = (
index: number index: number,
): ITableColumn[] => { ): ITableColumn[] => {
return R.pipe( return R.pipe(
// Previous period columns. // Previous period columns.
R.when( R.when(
this.query.isPreviousPeriodActive, this.query.isPreviousPeriodActive,
R.append(this.getPreviousPeriodTotalHorizAccessor(index)) R.append(this.getPreviousPeriodTotalHorizAccessor(index)),
), ),
R.when( R.when(
this.query.isPreviousPeriodChangeActive, this.query.isPreviousPeriodChangeActive,
R.append(this.getPreviousPeriodChangeHorizAccessor(index)) R.append(this.getPreviousPeriodChangeHorizAccessor(index)),
), ),
R.when( R.when(
this.query.isPreviousPeriodPercentageActive, this.query.isPreviousPeriodPercentageActive,
R.append(this.getPreviousPeriodPercentageHorizAccessor(index)) R.append(this.getPreviousPeriodPercentageHorizAccessor(index)),
) ),
)([]); )([]);
}; };
}; };

View File

@@ -1,12 +1,12 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { IDateRange, } from '../../types/Report.types'; 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 { Constructor } from '@/common/types/Constructor';
export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) => export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) =>
class extends R.compose(FinancialTablePreviousYear, FinancialDateRanges)(Base) { class extends R.pipe(FinancialTablePreviousYear, FinancialDateRanges)(Base) {
// -------------------- // --------------------
// # Columns. // # Columns.
// -------------------- // --------------------
@@ -15,22 +15,22 @@ export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) =>
* @returns {ITableColumn[]} * @returns {ITableColumn[]}
*/ */
public getPreviousYearColumns = ( public getPreviousYearColumns = (
dateRange?: IDateRange dateRange?: IDateRange,
): ITableColumn[] => { ): ITableColumn[] => {
return R.pipe( return R.pipe(
// Previous year columns. // Previous year columns.
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
R.append(this.getPreviousYearTotalColumn(dateRange)) R.append(this.getPreviousYearTotalColumn(dateRange)),
), ),
R.when( R.when(
this.query.isPreviousYearChangeActive, this.query.isPreviousYearChangeActive,
R.append(this.getPreviousYearChangeColumn()) R.append(this.getPreviousYearChangeColumn()),
), ),
R.when( R.when(
this.query.isPreviousYearPercentageActive, this.query.isPreviousYearPercentageActive,
R.append(this.getPreviousYearPercentageColumn()) R.append(this.getPreviousYearPercentageColumn()),
) ),
)([]); )([]);
}; };
@@ -42,7 +42,7 @@ export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) =>
public getPreviousYearHorizontalColumns = (dateRange: IDateRange) => { public getPreviousYearHorizontalColumns = (dateRange: IDateRange) => {
const PYDateRange = this.getPreviousYearDateRange( const PYDateRange = this.getPreviousYearDateRange(
dateRange.fromDate, dateRange.fromDate,
dateRange.toDate dateRange.toDate,
); );
return this.getPreviousYearColumns(PYDateRange); return this.getPreviousYearColumns(PYDateRange);
}; };
@@ -59,16 +59,16 @@ export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) =>
// Previous year columns. // Previous year columns.
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
R.append(this.getPreviousYearTotalAccessor()) R.append(this.getPreviousYearTotalAccessor()),
), ),
R.when( R.when(
this.query.isPreviousYearChangeActive, this.query.isPreviousYearChangeActive,
R.append(this.getPreviousYearChangeAccessor()) R.append(this.getPreviousYearChangeAccessor()),
), ),
R.when( R.when(
this.query.isPreviousYearPercentageActive, this.query.isPreviousYearPercentageActive,
R.append(this.getPreviousYearPercentageAccessor()) R.append(this.getPreviousYearPercentageAccessor()),
) ),
)([]); )([]);
}; };
@@ -78,22 +78,22 @@ export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) =>
* @returns {ITableColumn[]} * @returns {ITableColumn[]}
*/ */
public previousYearHorizontalColumnAccessors = ( public previousYearHorizontalColumnAccessors = (
index: number index: number,
): ITableColumn[] => { ): ITableColumn[] => {
return R.pipe( return R.pipe(
// Previous year columns. // Previous year columns.
R.when( R.when(
this.query.isPreviousYearActive, this.query.isPreviousYearActive,
R.append(this.getPreviousYearTotalHorizAccessor(index)) R.append(this.getPreviousYearTotalHorizAccessor(index)),
), ),
R.when( R.when(
this.query.isPreviousYearChangeActive, this.query.isPreviousYearChangeActive,
R.append(this.getPreviousYearChangeHorizAccessor(index)) R.append(this.getPreviousYearChangeHorizAccessor(index)),
), ),
R.when( R.when(
this.query.isPreviousYearPercentageActive, this.query.isPreviousYearPercentageActive,
R.append(this.getPreviousYearPercentageHorizAccessor(index)) R.append(this.getPreviousYearPercentageHorizAccessor(index)),
) ),
)([]); )([]);
}; };
}; };

13
pnpm-lock.yaml generated
View File

@@ -667,6 +667,9 @@ importers:
strategy: strategy:
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
stripe:
specifier: ^16.10.0
version: 16.10.0
uniqid: uniqid:
specifier: ^5.2.0 specifier: ^5.2.0
version: 5.4.0 version: 5.4.0
@@ -10572,7 +10575,7 @@ packages:
'@storybook/client-logger': 7.6.17 '@storybook/client-logger': 7.6.17
'@storybook/core-events': 7.6.17 '@storybook/core-events': 7.6.17
'@storybook/global': 5.0.0 '@storybook/global': 5.0.0
qs: 6.12.1 qs: 6.13.0
telejson: 7.2.0 telejson: 7.2.0
tiny-invariant: 1.3.3 tiny-invariant: 1.3.3
dev: true dev: true
@@ -10583,7 +10586,7 @@ packages:
'@storybook/client-logger': 7.6.20 '@storybook/client-logger': 7.6.20
'@storybook/core-events': 7.6.20 '@storybook/core-events': 7.6.20
'@storybook/global': 5.0.0 '@storybook/global': 5.0.0
qs: 6.12.1 qs: 6.13.0
telejson: 7.2.0 telejson: 7.2.0
tiny-invariant: 1.3.3 tiny-invariant: 1.3.3
dev: true dev: true
@@ -11169,7 +11172,7 @@ packages:
dependencies: dependencies:
'@storybook/client-logger': 7.6.17 '@storybook/client-logger': 7.6.17
memoizerific: 1.11.3 memoizerific: 1.11.3
qs: 6.12.1 qs: 6.13.0
dev: true dev: true
/@storybook/router@7.6.20: /@storybook/router@7.6.20:
@@ -11177,7 +11180,7 @@ packages:
dependencies: dependencies:
'@storybook/client-logger': 7.6.20 '@storybook/client-logger': 7.6.20
memoizerific: 1.11.3 memoizerific: 1.11.3
qs: 6.12.1 qs: 6.13.0
dev: true dev: true
/@storybook/telemetry@7.2.2: /@storybook/telemetry@7.2.2:
@@ -32929,7 +32932,7 @@ packages:
http-basic: 8.1.3 http-basic: 8.1.3
http-response-object: 3.0.2 http-response-object: 3.0.2
promise: 8.3.0 promise: 8.3.0
qs: 6.12.1 qs: 6.13.0
dev: false dev: false
/thenify-all@1.6.0: /thenify-all@1.6.0:

View File

@@ -0,0 +1,705 @@
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));
};
}

View File

@@ -0,0 +1,412 @@
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);
};
};

View File

@@ -0,0 +1,163 @@
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 {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 {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);
}
};
}

View File

@@ -0,0 +1,109 @@
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,
};
}
}

View File

@@ -0,0 +1,374 @@
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()),
)([]);
};
}

View File

@@ -0,0 +1,56 @@
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,
) {
// Retrieves the json table format.
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
const table = await this.cashflowSheetApp.table(query);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
const buffer = await this.cashflowSheetApp.csv(query);
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(query);
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(query);
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(query);
return res.status(200).send(cashflow);
}
}
}

View File

@@ -0,0 +1,27 @@
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 {}

View File

@@ -0,0 +1,299 @@
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;
}

View File

@@ -0,0 +1,37 @@
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;
}
}

View File

@@ -0,0 +1,66 @@
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 method.
* @param {CashflowExportInjectable} cashflowExport
* @param {} cashflowSheet
* @param cashflowTable
* @param cashflowPdf
*/
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);
}
}

View File

@@ -0,0 +1,36 @@
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 {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,
};
}
}

View File

@@ -0,0 +1,36 @@
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,
};
}
}

View File

@@ -0,0 +1,28 @@
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,
);
}
}

View File

@@ -0,0 +1,52 @@
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',
});

View File

@@ -0,0 +1,77 @@
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[];