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

@@ -0,0 +1,60 @@
import { Response } from 'express';
import { Controller, Headers, Query, Res } from '@nestjs/common';
import { IBalanceSheetQuery } from './BalanceSheet.types';
import { AcceptType } from '@/constants/accept-type';
import { BalanceSheetApplication } from './BalanceSheetApplication';
@Controller('/reports/balance-sheet')
export class BalanceSheetStatementController {
constructor(private readonly balanceSheetApp: BalanceSheetApplication) {}
/**
* Retrieve the balance sheet.
* @param {IBalanceSheetQuery} query - Balance sheet query.
* @param {Response} res - Response.
* @param {string} acceptHeader - Accept header.
*/
public async balanceSheet(
@Query() query: IBalanceSheetQuery,
@Res() res: Response,
@Headers('accept') acceptHeader: string,
) {
// Retrieves the json table format.
if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) {
const table = await this.balanceSheetApp.table(query);
return res.status(200).send(table);
// Retrieves the csv format.
} else if (acceptHeader.includes(AcceptType.ApplicationCsv)) {
const buffer = await this.balanceSheetApp.csv(query);
res.setHeader('Content-Disposition', 'attachment; filename=output.csv');
res.setHeader('Content-Type', 'text/csv');
return res.send(buffer);
// Retrieves the xlsx format.
} else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) {
const buffer = await this.balanceSheetApp.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.balanceSheetApp.pdf(query);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
});
res.send(pdfContent);
} else {
const sheet = await this.balanceSheetApp.sheet(query);
return res.status(200).send(sheet);
}
}
}

View File

@@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { BalanceSheetInjectable } from './BalanceSheetInjectable';
import { BalanceSheetApplication } from './BalanceSheetApplication';
import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable';
import { BalanceSheetExportInjectable } from './BalanceSheetExportInjectable';
import { BalanceSheetMetaInjectable } from './BalanceSheetMeta';
import { BalanceSheetPdfInjectable } from './BalanceSheetPdfInjectable';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
import { BalanceSheetStatementController } from './BalanceSheet.controller';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { AccountsModule } from '@/modules/Accounts/Accounts.module';
@Module({
imports: [FinancialSheetCommonModule, AccountsModule],
controllers: [BalanceSheetStatementController],
providers: [
BalanceSheetRepository,
BalanceSheetInjectable,
BalanceSheetTableInjectable,
BalanceSheetExportInjectable,
BalanceSheetMetaInjectable,
BalanceSheetApplication,
BalanceSheetPdfInjectable,
TenancyContext,
],
exports: [BalanceSheetInjectable],
})
export class BalanceSheetModule {}

View File

@@ -0,0 +1,114 @@
import * as R from 'ramda';
import { I18nService } from 'nestjs-i18n';
import {
IBalanceSheetQuery,
IBalanceSheetSchemaNode,
IBalanceSheetDataNode,
} from './BalanceSheet.types';
import { BalanceSheetSchema } from './BalanceSheetSchema';
import { BalanceSheetPercentage } from './BalanceSheetPercentage';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { BalanceSheetDatePeriods } from './BalanceSheetDatePeriods';
import { BalanceSheetBase } from './BalanceSheetBase';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetFiltering } from './BalanceSheetFiltering';
import { BalanceSheetNetIncome } from './BalanceSheetNetIncome';
import { BalanceSheetAggregators } from './BalanceSheetAggregators';
import { BalanceSheetAccounts } from './BalanceSheetAccounts';
import { INumberFormatQuery } from '../../types/Report.types';
import { FinancialSheet } from '../../common/FinancialSheet';
export class BalanceSheet extends R.pipe(
BalanceSheetAggregators,
BalanceSheetAccounts,
BalanceSheetNetIncome,
BalanceSheetFiltering,
BalanceSheetDatePeriods,
BalanceSheetComparsionPreviousPeriod,
BalanceSheetComparsionPreviousYear,
BalanceSheetPercentage,
BalanceSheetSchema,
BalanceSheetBase,
FinancialSheetStructure,
)(FinancialSheet) {
/**
* Balance sheet query.
* @param {BalanceSheetQuery}
*/
readonly query: BalanceSheetQuery;
/**
* Balance sheet number format query.
* @param {INumberFormatQuery}
*/
readonly numberFormat: INumberFormatQuery;
/**
* Base currency of the organization.
* @param {string}
*/
readonly baseCurrency: string;
/**
* Localization.
*/
readonly i18n: I18nService;
/**
* Balance sheet repository.
*/
readonly repository: BalanceSheetRepository;
/**
* Constructor method.
* @param {IBalanceSheetQuery} query -
* @param {IAccount[]} accounts -
* @param {string} baseCurrency -
*/
constructor(
query: IBalanceSheetQuery,
repository: BalanceSheetRepository,
baseCurrency: string,
i18n: I18nService,
) {
super();
this.query = new BalanceSheetQuery(query);
this.repository = repository;
this.baseCurrency = baseCurrency;
this.numberFormat = this.query.query.numberFormat;
this.i18n = i18n;
}
/**
* Parses report schema nodes.
* @param {IBalanceSheetSchemaNode[]} schema
* @returns {IBalanceSheetDataNode[]}
*/
public parseSchemaNodes = (
schema: IBalanceSheetSchemaNode[],
): IBalanceSheetDataNode[] => {
return R.compose(
this.aggregatesSchemaParser,
this.netIncomeSchemaParser,
this.accountsSchemaParser,
)(schema) as IBalanceSheetDataNode[];
};
/**
* Retrieve the report statement data.
* @returns {IBalanceSheetDataNode[]}
*/
public reportData = () => {
const balanceSheetSchema = this.getSchema();
return R.compose(
this.reportFilterPlugin,
this.reportPercentageCompose,
this.parseSchemaNodes,
)(balanceSheetSchema);
};
}

View File

@@ -0,0 +1,225 @@
import {
IFinancialSheetBranchesQuery,
IFinancialSheetCommonMeta,
IFormatNumberSettings,
INumberFormatQuery,
} from '../../types/Report.types';
import { IFinancialTable } from '../../types/Table.types';
// Balance sheet schema nodes types.
export enum BALANCE_SHEET_SCHEMA_NODE_TYPE {
AGGREGATE = 'AGGREGATE',
ACCOUNTS = 'ACCOUNTS',
ACCOUNT = 'ACCOUNT',
NET_INCOME = 'NET_INCOME',
}
export enum BALANCE_SHEET_NODE_TYPE {
AGGREGATE = 'AGGREGATE',
ACCOUNTS = 'ACCOUNTS',
ACCOUNT = 'ACCOUNT',
}
// Balance sheet schema nodes ids.
export enum BALANCE_SHEET_SCHEMA_NODE_ID {
ASSETS = 'ASSETS',
CURRENT_ASSETS = 'CURRENT_ASSETS',
CASH_EQUIVALENTS = 'CASH_EQUIVALENTS',
ACCOUNTS_RECEIVABLE = 'ACCOUNTS_RECEIVABLE',
NON_CURRENT_ASSET = 'NON_CURRENT_ASSET',
FIXED_ASSET = 'FIXED_ASSET',
OTHER_CURRENT_ASSET = 'OTHER_CURRENT_ASSET',
INVENTORY = 'INVENTORY',
LIABILITY_EQUITY = 'LIABILITY_EQUITY',
LIABILITY = 'LIABILITY',
CURRENT_LIABILITY = 'CURRENT_LIABILITY',
LOGN_TERM_LIABILITY = 'LOGN_TERM_LIABILITY',
NON_CURRENT_LIABILITY = 'NON_CURRENT_LIABILITY',
EQUITY = 'EQUITY',
NET_INCOME = 'NET_INCOME',
}
// Balance sheet query.
export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery {
displayColumnsType: 'total' | 'date_periods';
displayColumnsBy: string;
fromDate: string;
toDate: string;
numberFormat: INumberFormatQuery;
noneTransactions: boolean;
noneZero: boolean;
basis: 'cash' | 'accrual';
accountIds: number[];
percentageOfColumn: boolean;
percentageOfRow: boolean;
previousPeriod: boolean;
previousPeriodAmountChange: boolean;
previousPeriodPercentageChange: boolean;
previousYear: boolean;
previousYearAmountChange: boolean;
previousYearPercentageChange: boolean;
}
// Balance sheet meta.
export interface IBalanceSheetMeta extends IFinancialSheetCommonMeta {
formattedAsDate: string;
formattedDateRange: string;
}
export interface IBalanceSheetFormatNumberSettings
extends IFormatNumberSettings {
type: string;
}
// Balance sheet service.
export interface IBalanceSheetStatementService {
balanceSheet(
tenantId: number,
query: IBalanceSheetQuery,
): Promise<IBalanceSheetDOO>;
}
export type IBalanceSheetStatementData = IBalanceSheetDataNode[];
export interface IBalanceSheetDOO {
query: IBalanceSheetQuery;
data: IBalanceSheetStatementData;
meta: IBalanceSheetMeta;
}
export interface IBalanceSheetCommonNode {
total: IBalanceSheetTotal;
horizontalTotals?: IBalanceSheetTotal[];
percentageRow?: IBalanceSheetPercentageAmount;
percentageColumn?: IBalanceSheetPercentageAmount;
previousPeriod?: IBalanceSheetTotal;
previousPeriodChange?: IBalanceSheetTotal;
previousPeriodPercentage?: IBalanceSheetPercentageAmount;
previousYear?: IBalanceSheetTotal;
previousYearChange?: IBalanceSheetTotal;
previousYearPercentage?: IBalanceSheetPercentageAmount;
}
export interface IBalanceSheetAggregateNode extends IBalanceSheetCommonNode {
id: string;
name: string;
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE;
children?: IBalanceSheetDataNode[];
}
export interface IBalanceSheetTotal {
amount: number;
formattedAmount: string;
currencyCode: string;
date?: string | Date;
}
export interface IBalanceSheetAccountsNode extends IBalanceSheetCommonNode {
id: number | string;
name: string;
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS;
children: IBalanceSheetAccountNode[];
}
export interface IBalanceSheetAccountNode extends IBalanceSheetCommonNode {
id: number;
index: number;
name: string;
code: string;
parentAccountId?: number;
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNT;
children?: IBalanceSheetAccountNode[];
}
export interface IBalanceSheetNetIncomeNode extends IBalanceSheetCommonNode {
id: number;
name: string;
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME;
}
export type IBalanceSheetDataNode =
| IBalanceSheetAggregateNode
| IBalanceSheetAccountNode
| IBalanceSheetAccountsNode
| IBalanceSheetNetIncomeNode;
export interface IBalanceSheetPercentageAmount {
amount: number;
formattedAmount: string;
}
export interface IBalanceSheetSchemaAggregateNode {
name: string;
id: string;
type: BALANCE_SHEET_SCHEMA_NODE_TYPE;
children: IBalanceSheetSchemaNode[];
alwaysShow: boolean;
}
export interface IBalanceSheetSchemaAccountNode {
name: string;
id: string;
type: BALANCE_SHEET_SCHEMA_NODE_TYPE;
accountsTypes: string[];
}
export interface IBalanceSheetSchemaNetIncomeNode {
id: string;
name: string;
type: BALANCE_SHEET_SCHEMA_NODE_TYPE;
}
export type IBalanceSheetSchemaNode =
| IBalanceSheetSchemaAccountNode
| IBalanceSheetSchemaAggregateNode
| IBalanceSheetSchemaNetIncomeNode;
export interface IBalanceSheetDatePeriods {
assocAccountNodeDatePeriods(node): any;
initDateRangeCollection(): void;
}
export interface IBalanceSheetComparsions {
assocPreviousYearAccountNode(node);
hasPreviousPeriod(): boolean;
hasPreviousYear(): boolean;
assocPreviousPeriodAccountNode(node);
}
export interface IBalanceSheetTotalPeriod extends IFinancialSheetTotalPeriod {
percentageRow?: IBalanceSheetPercentageAmount;
percentageColumn?: IBalanceSheetPercentageAmount;
}
export interface IFinancialSheetTotalPeriod {
fromDate: any;
toDate: any;
total: any;
}
export enum IFinancialDatePeriodsUnit {
Day = 'day',
Month = 'month',
Year = 'year',
}
export enum IAccountTransactionsGroupBy {
Quarter = 'quarter',
Year = 'year',
Day = 'day',
Month = 'month',
Week = 'week',
}
export interface IBalanceSheetTable extends IFinancialTable {
meta: IBalanceSheetMeta;
query: IBalanceSheetQuery;
}

View File

@@ -0,0 +1,208 @@
import * as R from 'ramda';
import { defaultTo, toArray } from 'lodash';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
import {
BALANCE_SHEET_SCHEMA_NODE_TYPE,
IBalanceSheetAccountNode,
IBalanceSheetAccountsNode,
IBalanceSheetDataNode,
IBalanceSheetSchemaAccountNode,
IBalanceSheetSchemaNode,
} from './BalanceSheet.types';
import { BalanceSheetNetIncome } from './BalanceSheetNetIncome';
import { BalanceSheetFiltering } from './BalanceSheetFiltering';
import { BalanceSheetDatePeriods } from './BalanceSheetDatePeriods';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { BalanceSheetPercentage } from './BalanceSheetPercentage';
import { BalanceSheetSchema } from './BalanceSheetSchema';
import { BalanceSheetBase } from './BalanceSheetBase';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { GConstructor } from '@/common/types/Constructor';
import { INumberFormatQuery } from '../../types/Report.types';
import { Account } from '@/modules/Accounts/models/Account.model';
import { flatToNestedArray } from '@/utils/flat-to-nested-array';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetAccounts = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncome,
BalanceSheetFiltering,
BalanceSheetDatePeriods,
BalanceSheetComparsionPreviousPeriod,
BalanceSheetComparsionPreviousYear,
BalanceSheetPercentage,
BalanceSheetSchema,
BalanceSheetBase,
FinancialSheetStructure,
)(Base) {
/**
* Balance sheet query.
* @param {BalanceSheetQuery}
*/
readonly query: BalanceSheetQuery;
/**
* Balance sheet number format query.
* @param {INumberFormatQuery}
*/
readonly numberFormat: INumberFormatQuery;
/**
* Base currency of the organization.
* @param {string}
*/
readonly baseCurrency: string;
/**
* Localization.
*/
readonly i18n: any;
/**
* Balance sheet repository.
*/
readonly repository: BalanceSheetRepository;
/**
* Retrieve the accounts node of accounts types.
* @param {string} accountsTypes
* @returns {IAccount[]}
*/
private getAccountsByAccountTypes = (
accountsTypes: string[],
): Account[] => {
const mapAccountsByTypes = R.map((accountType) =>
defaultTo(this.repository.accountsByType.get(accountType), []),
);
return R.compose(R.flatten, mapAccountsByTypes)(accountsTypes);
};
/**
* Mappes the account model to report account node.
* @param {Account} account
* @returns {IBalanceSheetAccountNode}
*/
private reportSchemaAccountNodeMapper = (
account: Account,
): IBalanceSheetAccountNode => {
const childrenAccountsIds = this.repository.accountsGraph.dependenciesOf(
account.id,
);
const accountIds = R.uniq(R.append(account.id, childrenAccountsIds));
const total = this.repository.totalAccountsLedger
.whereAccountsIds(accountIds)
.getClosingBalance();
return {
id: account.id,
index: account.index,
name: account.name,
code: account.code,
total: this.getAmountMeta(total),
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNT,
};
};
/**
* Mappes the given account model to the balance sheet account node.
* @param {IAccount} account
* @returns {IBalanceSheetAccountNode}
*/
private reportSchemaAccountNodeComposer = (
account: Account,
): IBalanceSheetAccountNode => {
return R.compose(
R.when(
this.query.isPreviousYearActive,
this.previousYearAccountNodeComposer,
),
R.when(
this.query.isPreviousPeriodActive,
this.previousPeriodAccountNodeComposer,
),
R.when(
this.query.isDatePeriodsColumnsType,
this.assocAccountNodeDatePeriods,
),
this.reportSchemaAccountNodeMapper,
)(account);
};
// -----------------------------
// - Accounts Node Praser
// -----------------------------
/**
* Retrieve the report accounts node by the given accounts types.
* @param {string[]} accountsTypes
* @returns {IBalanceSheetAccountNode[]}
*/
private getAccountsNodesByAccountTypes = (
accountsTypes: string[],
): IBalanceSheetAccountNode[] => {
// Retrieves accounts from the given defined node account types.
const accounts = this.getAccountsByAccountTypes(accountsTypes);
// Converts the flatten accounts to tree.
const accountsTree = flatToNestedArray(accounts, {
id: 'id',
parentId: 'parentAccountId',
});
// Maps over the accounts tree.
return this.mapNodesDeep(
accountsTree,
this.reportSchemaAccountNodeComposer,
);
};
/**
* Mappes the accounts schema node type.
* @param {IBalanceSheetSchemaNode} node - Schema node.
* @returns {IBalanceSheetAccountNode}
*/
private reportSchemaAccountsNodeMapper = (
node: IBalanceSheetSchemaAccountNode,
): IBalanceSheetAccountsNode => {
const accounts = this.getAccountsNodesByAccountTypes(node.accountsTypes);
const children = toArray(node?.children);
return {
id: node.id,
name: this.i18n.__(node.name),
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
children: [...accounts, ...children],
total: this.getTotalAmountMeta(0),
};
};
/**
* Mappes the given report schema node.
* @param {IBalanceSheetSchemaNode | IBalanceSheetDataNode} node - Schema node.
* @return {IBalanceSheetSchemaNode | IBalanceSheetDataNode}
*/
private reportAccountSchemaParser = (
node: IBalanceSheetSchemaNode | IBalanceSheetDataNode,
): IBalanceSheetSchemaNode | IBalanceSheetDataNode => {
return R.compose(
R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS),
this.reportSchemaAccountsNodeMapper,
),
)(node);
};
/**
* Parses the report accounts schema nodes.
* @param {IBalanceSheetSchemaNode[]} nodes -
* @return {IBalanceSheetStructureSection[]}
*/
public accountsSchemaParser = (
nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[],
): (IBalanceSheetDataNode | IBalanceSheetSchemaNode)[] => {
return this.mapNodesDeepReverse(nodes, this.reportAccountSchemaParser);
};
};

View File

@@ -0,0 +1,144 @@
import * as R from 'ramda';
import {
BALANCE_SHEET_SCHEMA_NODE_TYPE,
IBalanceSheetAggregateNode,
IBalanceSheetDataNode,
IBalanceSheetSchemaAggregateNode,
IBalanceSheetSchemaNode,
} from './BalanceSheet.types';
import { BalanceSheetDatePeriods } from './BalanceSheetDatePeriods';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { BalanceSheetPercentage } from './BalanceSheetPercentage';
import { BalanceSheetSchema } from './BalanceSheetSchema';
import { BalanceSheetBase } from './BalanceSheetBase';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
import { GConstructor } from '@/common/types/Constructor';
import { INumberFormatQuery } from '../../types/Report.types';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetAggregators = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetDatePeriods,
BalanceSheetComparsionPreviousPeriod,
BalanceSheetComparsionPreviousYear,
BalanceSheetPercentage,
BalanceSheetSchema,
BalanceSheetBase,
FinancialSheetStructure,
)(Base) {
/**
* Balance sheet query.
* @param {BalanceSheetQuery}
*/
public readonly query: BalanceSheetQuery;
/**
* Balance sheet number format query.
* @param {INumberFormatQuery}
*/
readonly numberFormat: INumberFormatQuery;
/**
* Base currency of the organization.
* @param {string}
*/
readonly baseCurrency: string;
/**
* Localization.
*/
readonly i18n: any;
/**
* Sets total amount that calculated from node children.
* @param {IBalanceSheetSection} node
* @returns {IBalanceSheetDataNode}
*/
public aggregateNodeTotalMapper = (
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
return R.compose(
R.when(
this.query.isPreviousYearActive,
this.previousYearAggregateNodeComposer,
),
R.when(
this.query.isPreviousPeriodActive,
this.previousPeriodAggregateNodeComposer,
),
R.when(
this.query.isDatePeriodsColumnsType,
this.assocAggregateNodeDatePeriods,
),
)(node);
};
/**
* Mappes the aggregate schema node type.
* @param {IBalanceSheetSchemaAggregateNode} node - Schema node.
* @return {IBalanceSheetAggregateNode}
*/
public reportSchemaAggregateNodeMapper = (
node: IBalanceSheetSchemaAggregateNode,
): IBalanceSheetAggregateNode => {
const total = this.getTotalOfNodes(node.children);
return {
name: this.i18n.__(node.name),
id: node.id,
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
total: this.getTotalAmountMeta(total),
children: node.children,
};
};
/**
* Compose shema aggregate node of balance sheet schema.
* @param {IBalanceSheetSchemaAggregateNode} node
* @returns {IBalanceSheetSchemaAggregateNode}
*/
public schemaAggregateNodeCompose = (
node: IBalanceSheetSchemaAggregateNode,
) => {
return R.compose(
this.aggregateNodeTotalMapper,
this.reportSchemaAggregateNodeMapper,
)(node);
};
/**
* Mappes the given report schema node.
* @param {IBalanceSheetSchemaNode} node - Schema node.
* @return {IBalanceSheetDataNode}
*/
public reportAggregateSchemaParser = (
node: IBalanceSheetSchemaNode,
): IBalanceSheetDataNode => {
return R.compose(
R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE),
this.schemaAggregateNodeCompose,
),
R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS),
this.schemaAggregateNodeCompose,
),
)(node);
};
/**
* Mappes the report schema nodes.
* @param {IBalanceSheetSchemaNode[]} nodes -
* @return {IBalanceSheetStructureSection[]}
*/
public aggregatesSchemaParser = (
nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[],
): (IBalanceSheetDataNode | IBalanceSheetSchemaNode)[] => {
return this.mapNodesDeepReverse(nodes, this.reportAggregateSchemaParser);
};
};

View File

@@ -0,0 +1,64 @@
import { Injectable } from '@nestjs/common';
import { IBalanceSheetQuery } from './BalanceSheet.types';
import { BalanceSheetExportInjectable } from './BalanceSheetExportInjectable';
import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable';
import { BalanceSheetInjectable } from './BalanceSheetInjectable';
@Injectable()
export class BalanceSheetApplication {
/**
* @param {BalanceSheetExportInjectable} balanceSheetExportService - The balance sheet export service.
* @param {BalanceSheetTableInjectable} balanceSheetTableService - The balance sheet table service.
* @param {BalanceSheetInjectable} balanceSheetService - The balance sheet service.
*/
constructor(
private readonly balanceSheetExportService: BalanceSheetExportInjectable,
private readonly balanceSheetTableService: BalanceSheetTableInjectable,
private readonly balanceSheetService: BalanceSheetInjectable,
) {}
/**
* Retrieves the balnace sheet in json format.
* @param {IBalanceSheetQuery} query
* @returns {Promise<IBalanceSheetStatement>}
*/
public sheet(query: IBalanceSheetQuery) {
return this.balanceSheetService.balanceSheet(query);
}
/**
* Retrieves the balance sheet in table format.
* @param {IBalanceSheetQuery} query
* @returns {Promise<IBalanceSheetTable>}
*/
public table(query: IBalanceSheetQuery) {
return this.balanceSheetTableService.table(query);
}
/**
* Retrieves the balance sheet in XLSX format.
* @param {IBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public xlsx(query: IBalanceSheetQuery) {
return this.balanceSheetExportService.xlsx(query);
}
/**
* Retrieves the balance sheet in CSV format.
* @param {IBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public csv(query: IBalanceSheetQuery): Promise<string> {
return this.balanceSheetExportService.csv(query);
}
/**
* Retrieves the balance sheet in pdf format.
* @param {IBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public pdf(query: IBalanceSheetQuery) {
return this.balanceSheetExportService.pdf(query);
}
}

View File

@@ -0,0 +1,45 @@
import * as R from 'ramda';
import {
IBalanceSheetDataNode,
IBalanceSheetSchemaNode,
} from './BalanceSheet.types';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetBase = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class BalanceSheetBase extends Base {
/**
* Detarmines the node type of the given schema node.
* @param {IBalanceSheetStructureSection} node -
* @param {string} type -
* @return {boolean}
*/
public isSchemaNodeType = R.curry(
(type: string, node: IBalanceSheetSchemaNode): boolean => {
return node.type === type;
},
);
/**
* Detarmines the node type of the given schema node.
* @param {IBalanceSheetStructureSection} node -
* @param {string} type -
* @return {boolean}
*/
public isNodeType = R.curry(
(type: string, node: IBalanceSheetDataNode): boolean => {
return node.nodeType === type;
},
);
/**
* Detarmines the given display columns by type.
* @param {string} displayColumnsBy
* @returns {boolean}
*/
public isDisplayColumnsBy = (displayColumnsBy: string): boolean => {
return this.query.displayColumnsType === displayColumnsBy;
};
};

View File

@@ -0,0 +1,273 @@
import * as R from 'ramda';
import { sumBy } from 'lodash';
import {
IBalanceSheetAccountNode,
IBalanceSheetDataNode,
IBalanceSheetAggregateNode,
IBalanceSheetTotal,
IBalanceSheetCommonNode,
} from './BalanceSheet.types';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetComparsionPreviousPeriod = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class BalanceSheetComparsionPreviousPeriod extends R.pipe(
FinancialHorizTotals,
FinancialPreviousPeriod,
)(Base) {
// ------------------------------
// # Account
// ------------------------------
/**
* Associates the previous period to account node.
* @param {IBalanceSheetDataNode} node
* @returns {IBalanceSheetDataNode}
*/
public assocPreviousPeriodAccountNode = (
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
const total = this.repository.PPTotalAccountsLedger.whereAccountId(
node.id,
).getClosingBalance();
return R.assoc('previousPeriod', this.getAmountMeta(total), node);
};
/**
* Previous period account node composer.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
public previousPeriodAccountNodeComposer = (
node: IBalanceSheetAccountNode,
): IBalanceSheetAccountNode => {
return R.compose(
R.when(
this.isNodeHasHorizTotals,
this.assocPreivousPeriodAccountHorizNodeComposer,
),
R.when(
this.query.isPreviousPeriodPercentageActive,
this.assocPreviousPeriodPercentageNode,
),
R.when(
this.query.isPreviousPeriodChangeActive,
this.assocPreviousPeriodChangeNode,
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodAccountNode,
),
)(node);
};
// ------------------------------
// # Aggregate
// ------------------------------
/**
* Assoc previous period total to aggregate node.
* @param {IBalanceSheetAggregateNode} node
* @returns {IBalanceSheetAggregateNode}
*/
public assocPreviousPeriodAggregateNode = (
node: IBalanceSheetAggregateNode,
): IBalanceSheetAggregateNode => {
const total = sumBy(node.children, 'previousYear.amount');
return R.assoc('previousPeriod', this.getTotalAmountMeta(total), node);
};
/**
* Previous period aggregate node composer.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
public previousPeriodAggregateNodeComposer = (
node: IBalanceSheetAccountNode,
): IBalanceSheetAccountNode => {
return R.compose(
R.when(
this.isNodeHasHorizTotals,
this.assocPreviousPeriodAggregateHorizNode,
),
R.when(
this.query.isPreviousPeriodPercentageActive,
this.assocPreviousPeriodTotalPercentageNode,
),
R.when(
this.query.isPreviousPeriodChangeActive,
this.assocPreviousPeriodTotalChangeNode,
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodAggregateNode,
),
)(node);
};
// ------------------------------
// # Horizontal Nodes - Account.
// ------------------------------
/**
* Retrieve the given account total in the given period.
* @param {number} accountId - Account id.
* @param {Date} fromDate - From date.
* @param {Date} toDate - To date.
* @returns {number}
*/
private getAccountPPDatePeriodTotal = R.curry(
(accountId: number, fromDate: Date, toDate: Date): number => {
const PPPeriodsTotal =
this.repository.PPPeriodsAccountsLedger.whereAccountId(accountId)
.whereToDate(toDate)
.getClosingBalance();
const PPPeriodsOpeningTotal =
this.repository.PPPeriodsOpeningAccountLedger.whereAccountId(
accountId,
).getClosingBalance();
return PPPeriodsOpeningTotal + PPPeriodsTotal;
},
);
/**
* Assoc preivous period to account horizontal total node.
* @param {IBalanceSheetAccountNode} node
* @returns {}
*/
private assocPreviousPeriodAccountHorizTotal = R.curry(
(node: IBalanceSheetAccountNode, totalNode) => {
const total = this.getAccountPPDatePeriodTotal(
node.id,
totalNode.previousPeriodFromDate.date,
totalNode.previousPeriodToDate.date,
);
return R.assoc('previousPeriod', this.getAmountMeta(total), totalNode);
},
);
/**
* Previous year account horizontal node composer.
* @param {IBalanceSheetAccountNode} node -
* @param {IBalanceSheetTotal}
* @returns {IBalanceSheetTotal}
*/
private previousPeriodAccountHorizNodeCompose = R.curry(
(
node: IBalanceSheetAccountNode,
horizontalTotalNode: IBalanceSheetTotal,
): IBalanceSheetTotal => {
return R.compose(
R.when(
this.query.isPreviousPeriodPercentageActive,
this.assocPreviousPeriodPercentageNode,
),
R.when(
this.query.isPreviousPeriodChangeActive,
this.assocPreviousPeriodChangeNode,
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodAccountHorizTotal(node),
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodHorizNodeFromToDates(
this.query.displayColumnsBy,
),
),
)(horizontalTotalNode);
},
);
/**
*
* @param {IBalanceSheetAccountNode} node
* @returns
*/
private assocPreivousPeriodAccountHorizNodeComposer = (
node: IBalanceSheetAccountNode,
) => {
const horizontalTotals = R.map(
this.previousPeriodAccountHorizNodeCompose(node),
node.horizontalTotals,
);
return R.assoc('horizontalTotals', horizontalTotals, node);
};
// ------------------------------
// # Horizontal Nodes - Aggregate
// ------------------------------
/**
* Assoc previous year total to horizontal node.
* @param node
* @returns
*/
private assocPreviousPeriodAggregateHorizTotalNode = R.curry(
(node, index: number, totalNode) => {
const total = this.getPPHorizNodesTotalSumation(index, node);
return R.assoc(
'previousPeriod',
this.getTotalAmountMeta(total),
totalNode,
);
},
);
/**
* Compose previous period to aggregate horizontal nodes.
* @param {IBalanceSheetTotal} node
* @returns {IBalanceSheetTotal}
*/
private previousPeriodAggregateHorizNodeComposer = R.curry(
(
node: IBalanceSheetCommonNode,
horiontalTotalNode: IBalanceSheetTotal,
index: number,
): IBalanceSheetTotal => {
return R.compose(
R.when(
this.query.isPreviousPeriodPercentageActive,
this.assocPreviousPeriodTotalPercentageNode,
),
R.when(
this.query.isPreviousPeriodChangeActive,
this.assocPreviousPeriodTotalChangeNode,
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodAggregateHorizTotalNode(node, index),
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodHorizNodeFromToDates(
this.query.displayColumnsBy,
),
),
)(horiontalTotalNode);
},
);
/**
* Assoc
* @param {IBalanceSheetCommonNode} node
* @returns {IBalanceSheetCommonNode}
*/
private assocPreviousPeriodAggregateHorizNode = (
node: IBalanceSheetCommonNode,
) => {
const horizontalTotals = R.addIndex(R.map)(
this.previousPeriodAggregateHorizNodeComposer(node),
node.horizontalTotals,
);
return R.assoc('horizontalTotals', horizontalTotals, node);
};
};

View File

@@ -0,0 +1,278 @@
import * as R from 'ramda';
import { sumBy, isEmpty } from 'lodash';
import {
IBalanceSheetAccountNode,
IBalanceSheetCommonNode,
IBalanceSheetDataNode,
IBalanceSheetTotal,
} from './BalanceSheet.types';
import { FinancialPreviousYear } from '../../common/FinancialPreviousYear';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetRepository } from './BalanceSheetRepository';
export const BalanceSheetComparsionPreviousYear = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class BalanceSheetComparsionPreviousYear extends R.pipe(
FinancialPreviousYear,
)(Base) {
query: BalanceSheetQuery;
repository: BalanceSheetRepository;
// ------------------------------
// # Account
// ------------------------------
/**
* Associates the previous year to account node.
* @param {IBalanceSheetDataNode} node
* @returns {IBalanceSheetDataNode}
*/
protected assocPreviousYearAccountNode = (
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
const closingBalance =
this.repository.PYTotalAccountsLedger.whereAccountId(
node.id,
).getClosingBalance();
return R.assoc('previousYear', this.getAmountMeta(closingBalance), node);
};
/**
* Assoc previous year attributes to account node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
protected previousYearAccountNodeComposer = (
node: IBalanceSheetAccountNode,
): IBalanceSheetAccountNode => {
return R.compose(
R.when(
this.isNodeHasHorizontalTotals,
this.assocPreviousYearAccountHorizNodeComposer,
),
R.when(
this.query.isPreviousYearPercentageActive,
this.assocPreviousYearPercentageNode,
),
R.when(
this.query.isPreviousYearChangeActive,
this.assocPreviousYearChangetNode,
),
this.assocPreviousYearAccountNode,
)(node);
};
// ------------------------------
// # Aggregate
// ------------------------------
/**
* Assoc previous year on aggregate node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
protected assocPreviousYearAggregateNode = (
node: IBalanceSheetAccountNode,
): IBalanceSheetAccountNode => {
const total = sumBy(node.children, 'previousYear.amount');
return R.assoc('previousYear', this.getTotalAmountMeta(total), node);
};
/**
* Assoc previous year attributes to aggregate node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
protected previousYearAggregateNodeComposer = (
node: IBalanceSheetAccountNode,
): IBalanceSheetAccountNode => {
return R.compose(
R.when(
this.query.isPreviousYearPercentageActive,
this.assocPreviousYearTotalPercentageNode,
),
R.when(
this.query.isPreviousYearChangeActive,
this.assocPreviousYearTotalChangeNode,
),
R.when(
this.isNodeHasHorizontalTotals,
this.assocPreviousYearAggregateHorizNode,
),
this.assocPreviousYearAggregateNode,
)(node);
};
// ------------------------------
// # Horizontal Nodes - Aggregate
// ------------------------------
/**
* Assoc previous year total to horizontal node.
* @param node
* @returns
*/
private assocPreviousYearAggregateHorizTotalNode = R.curry(
(node, index, totalNode) => {
const total = this.getPYHorizNodesTotalSumation(index, node);
return R.assoc(
'previousYear',
this.getTotalAmountMeta(total),
totalNode,
);
},
);
/**
* Compose previous year to aggregate horizontal nodes.
* @param {IBalanceSheetTotal} node
* @returns {IBalanceSheetTotal}
*/
private previousYearAggregateHorizNodeComposer = R.curry(
(
node: IBalanceSheetCommonNode,
horiontalTotalNode: IBalanceSheetTotal,
index: number,
): IBalanceSheetTotal => {
return R.compose(
R.when(
this.query.isPreviousYearPercentageActive,
this.assocPreviousYearTotalPercentageNode,
),
R.when(
this.query.isPreviousYearChangeActive,
this.assocPreviousYearTotalChangeNode,
),
R.when(
this.query.isPreviousYearActive,
this.assocPreviousYearAggregateHorizTotalNode(node, index),
),
R.when(
this.query.isPreviousYearActive,
this.assocPreviousYearHorizNodeFromToDates,
),
)(horiontalTotalNode);
},
);
/**
* Assoc
* @param {IBalanceSheetCommonNode} node
* @returns {IBalanceSheetCommonNode}
*/
public assocPreviousYearAggregateHorizNode = (
node: IBalanceSheetCommonNode,
): IBalanceSheetCommonNode => {
const horizontalTotals = R.addIndex(R.map)(
this.previousYearAggregateHorizNodeComposer(node),
node.horizontalTotals,
) as IBalanceSheetTotal[];
return R.assoc('horizontalTotals', horizontalTotals, node);
};
// ------------------------------
// # Horizontal Nodes - Account.
// ------------------------------
/**
* Retrieve the given account total in the given period.
* @param {number} accountId - Account id.
* @param {Date} fromDate - From date.
* @param {Date} toDate - To date.
* @returns {number}
*/
private getAccountPYDatePeriodTotal = R.curry(
(accountId: number, fromDate: Date, toDate: Date): number => {
const PYPeriodsTotal =
this.repository.PYPeriodsAccountsLedger.whereAccountId(accountId)
.whereToDate(toDate)
.getClosingBalance();
const PYPeriodsOpeningTotal =
this.repository.PYPeriodsOpeningAccountLedger.whereAccountId(
accountId,
).getClosingBalance();
return PYPeriodsOpeningTotal + PYPeriodsTotal;
},
);
/**
* Assoc preivous year to account horizontal total node.
* @param {IBalanceSheetAccountNode} node
* @returns {}
*/
private assocPreviousYearAccountHorizTotal = R.curry(
(node: IBalanceSheetAccountNode, totalNode) => {
const total = this.getAccountPYDatePeriodTotal(
node.id,
totalNode.previousYearFromDate.date,
totalNode.previousYearToDate.date,
);
return R.assoc('previousYear', this.getAmountMeta(total), totalNode);
},
);
/**
* Previous year account horizontal node composer.
* @param {IBalanceSheetAccountNode} node -
* @param {IBalanceSheetTotal}
* @returns {IBalanceSheetTotal}
*/
private previousYearAccountHorizNodeCompose = R.curry(
(
node: IBalanceSheetAccountNode,
horizontalTotalNode: IBalanceSheetTotal,
): IBalanceSheetTotal => {
return R.compose(
R.when(
this.query.isPreviousYearPercentageActive,
this.assocPreviousYearPercentageNode,
),
R.when(
this.query.isPreviousYearChangeActive,
this.assocPreviousYearChangetNode,
),
R.when(
this.query.isPreviousYearActive,
this.assocPreviousYearAccountHorizTotal(node),
),
R.when(
this.query.isPreviousYearActive,
this.assocPreviousYearHorizNodeFromToDates,
),
)(horizontalTotalNode);
},
);
/**
* Assoc previous year horizontal nodes to account node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
private assocPreviousYearAccountHorizNodeComposer = (
node: IBalanceSheetAccountNode,
) => {
const horizontalTotals = R.map(
this.previousYearAccountHorizNodeCompose(node),
node.horizontalTotals,
);
return R.assoc('horizontalTotals', horizontalTotals, node);
};
// ------------------------------
// # Horizontal Nodes - Aggregate.
// ------------------------------
/**
* Detarmines whether the given node has horizontal totals.
* @param {IBalanceSheetCommonNode} node
* @returns {boolean}
*/
public isNodeHasHorizontalTotals = (node: IBalanceSheetCommonNode) =>
!isEmpty(node.horizontalTotals);
};

View File

@@ -0,0 +1,210 @@
import * as R from 'ramda';
import { sumBy } from 'lodash';
import {
IBalanceSheetQuery,
IBalanceSheetAccountNode,
IBalanceSheetTotalPeriod,
IBalanceSheetCommonNode,
} from './BalanceSheet.types';
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
import { IDateRange, IFormatNumberSettings } from '../../types/Report.types';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
import { BalanceSheetQuery } from './BalanceSheetQuery';
/**
* Balance sheet date periods.
*/
export const BalanceSheetDatePeriods = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class BalanceSheetDatePeriods extends R.pipe(FinancialDatePeriods)(Base) {
/**
* @param {IBalanceSheetQuery}
*/
public readonly query: BalanceSheetQuery;
/**
* Retrieves the date periods based on the report query.
* @returns {IDateRange[]}
*/
get datePeriods(): IDateRange[] {
return this.getDateRanges(
this.query.fromDate,
this.query.toDate,
this.query.displayColumnsBy,
);
}
/**
* Retrieves the date periods of the given node based on the report query.
* @param {IBalanceSheetCommonNode} node
* @param {Function} callback
* @returns {}
*/
public getReportNodeDatePeriods = (
node: IBalanceSheetCommonNode,
callback: (
node: IBalanceSheetCommonNode,
fromDate: Date,
toDate: Date,
index: number,
) => any,
) => {
return this.getNodeDatePeriods(
this.query.fromDate,
this.query.toDate,
this.query.displayColumnsBy,
node,
callback,
);
};
/**
* 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 = {},
): IBalanceSheetTotalPeriod => {
return this.getDatePeriodMeta(total, fromDate, toDate, {
money: true,
...overrideSettings,
});
};
// --------------------------------
// # Account
// --------------------------------
/**
* Retrieve the given account date period total.
* @param {number} accountId
* @param {Date} toDate
* @returns {number}
*/
public getAccountDatePeriodTotal = (
accountId: number,
toDate: Date,
): number => {
const periodTotalBetween = this.repository.periodsAccountsLedger
.whereAccountId(accountId)
.whereToDate(toDate)
.getClosingBalance();
const periodOpening = this.repository.periodsOpeningAccountLedger
.whereAccountId(accountId)
.getClosingBalance();
return periodOpening + periodTotalBetween;
};
/**
*
* @param {IBalanceSheetAccountNode} node
* @param {Date} fromDate
* @param {Date} toDate
* @returns {IBalanceSheetAccountNode}
*/
public getAccountNodeDatePeriod = (
node: IBalanceSheetAccountNode,
fromDate: Date,
toDate: Date,
): IBalanceSheetTotalPeriod => {
const periodTotal = this.getAccountDatePeriodTotal(node.id, toDate);
return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
};
/**
* Retrieve total date periods of the given account node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
public getAccountsNodeDatePeriods = (
node: IBalanceSheetAccountNode,
): IBalanceSheetTotalPeriod[] => {
return this.getReportNodeDatePeriods(node, this.getAccountNodeDatePeriod);
};
/**
* Assoc total date periods to account node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
public assocAccountNodeDatePeriods = (
node: IBalanceSheetAccountNode,
): IBalanceSheetAccountNode => {
const datePeriods = this.getAccountsNodeDatePeriods(node);
return R.assoc('horizontalTotals', datePeriods, node);
};
// --------------------------------
// # Aggregate
// --------------------------------
/**
*
* @param {} node
* @param {number} index
* @returns {number}
*/
public getAggregateDatePeriodIndexTotal = (node, index) => {
return sumBy(node.children, `horizontalTotals[${index}].total.amount`);
};
/**
*
* @param {IBalanceSheetAccountNode} node
* @param {Date} fromDate
* @param {Date} toDate
* @returns
*/
public getAggregateNodeDatePeriod = (
node: IBalanceSheetAccountNode,
fromDate: Date,
toDate: Date,
index: number,
) => {
const periodTotal = this.getAggregateDatePeriodIndexTotal(node, index);
return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
};
/**
*
* @param node
* @returns
*/
public getAggregateNodeDatePeriods = (node) => {
return this.getReportNodeDatePeriods(
node,
this.getAggregateNodeDatePeriod,
);
};
/**
* Assoc total date periods to aggregate node.
* @param node
* @returns {}
*/
public assocAggregateNodeDatePeriods = (node) => {
const datePeriods = this.getAggregateNodeDatePeriods(node);
return R.assoc('horizontalTotals', datePeriods, node);
};
/**
*
* @param node
* @returns
*/
public assocAccountsNodeDatePeriods = (node) => {
return this.assocAggregateNodeDatePeriods(node);
};
};

View File

@@ -0,0 +1,54 @@
import { Injectable } from '@nestjs/common';
import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable';
import { BalanceSheetPdfInjectable } from './BalanceSheetPdfInjectable';
import { IBalanceSheetQuery } from './BalanceSheet.types';
import { TableSheet } from '../../common/TableSheet';
@Injectable()
export class BalanceSheetExportInjectable {
constructor(
private readonly balanceSheetTable: BalanceSheetTableInjectable,
private readonly balanceSheetPdf: BalanceSheetPdfInjectable,
) {}
/**
* Retrieves the trial balance sheet in XLSX format.
* @param {ITrialBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public async xlsx(query: IBalanceSheetQuery) {
const table = await this.balanceSheetTable.table(query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToXLSX();
return tableSheet.convertToBuffer(tableCsv, 'xlsx');
}
/**
* Retrieves the trial balance sheet in CSV format.
* @param {ITrialBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public async csv(
query: IBalanceSheetQuery,
): Promise<string> {
const table = await this.balanceSheetTable.table(query);
const tableSheet = new TableSheet(table.table);
const tableCsv = tableSheet.convertToCSV();
return tableCsv;
}
/**
* Retrieves the balance sheet in pdf format.
* @param {IBalanceSheetQuery} query
* @returns {Promise<Buffer>}
*/
public async pdf(
query: IBalanceSheetQuery,
): Promise<Buffer> {
return this.balanceSheetPdf.pdf(query);
}
}

View File

@@ -0,0 +1,183 @@
import * as R from 'ramda';
import { get } from 'lodash';
import {
IBalanceSheetDataNode,
BALANCE_SHEET_NODE_TYPE,
} from './BalanceSheet.types';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialFilter } from '../../common/FinancialFilter';
import { BalanceSheetBase } from './BalanceSheetBase';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { FinancialSheet } from '../../common/FinancialSheet';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
export const BalanceSheetFiltering = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends R.pipe(
FinancialFilter,
FinancialSheetStructure,
BalanceSheetBase,
)(Base) {
/**
* @description Repository.
*/
readonly repository: BalanceSheetRepository;
// -----------------------
// # Account
// -----------------------
/**
* Filter report node detarmine.
* @param {IBalanceSheetDataNode} node - Balance sheet node.
* @return {boolean}
*/
private accountNoneZeroNodesFilterDetarminer = (
node: IBalanceSheetDataNode,
): boolean => {
return R.ifElse(
this.isNodeType(BALANCE_SHEET_NODE_TYPE.ACCOUNT),
this.isNodeNoneZero,
R.always(true),
)(node);
};
/**
* Detarmines account none-transactions node.
* @param {IBalanceSheetDataNode} node
* @returns {boolean}
*/
private accountNoneTransFilterDetarminer = (
node: IBalanceSheetDataNode,
): boolean => {
return R.ifElse(
this.isNodeType(BALANCE_SHEET_NODE_TYPE.ACCOUNT),
this.isNodeNoneZero,
R.always(true),
)(node);
};
/**
* Report nodes filter.
* @param {IBalanceSheetSection[]} nodes -
* @return {IBalanceSheetSection[]}
*/
private accountsNoneZeroNodesFilter = (
nodes: IBalanceSheetDataNode[],
): IBalanceSheetDataNode[] => {
return this.filterNodesDeep(
nodes,
this.accountNoneZeroNodesFilterDetarminer,
);
};
/**
* Filters the accounts none-transactions nodes.
* @param {IBalanceSheetDataNode[]} nodes
* @returns {IBalanceSheetDataNode[]}
*/
private accountsNoneTransactionsNodesFilter = (
nodes: IBalanceSheetDataNode[],
) => {
return this.filterNodesDeep(nodes, this.accountNoneTransFilterDetarminer);
};
// -----------------------
// # Aggregate/Accounts.
// -----------------------
/**
* Detearmines aggregate none-children filtering.
* @param {IBalanceSheetDataNode} node
* @returns {boolean}
*/
private aggregateNoneChildrenFilterDetarminer = (
node: IBalanceSheetDataNode,
): boolean => {
// Detarmines whether the given node is aggregate or accounts node.
const isAggregateOrAccounts =
this.isNodeType(BALANCE_SHEET_NODE_TYPE.AGGREGATE, node) ||
this.isNodeType(BALANCE_SHEET_NODE_TYPE.ACCOUNTS, node);
// Retrieve the schema node of the given id.
const schemaNode = this.getSchemaNodeById(node.id);
// Detarmines if the schema node is always should show.
const isSchemaAlwaysShow = get(schemaNode, 'alwaysShow', false);
return isAggregateOrAccounts && !isSchemaAlwaysShow
? this.isNodeHasChildren(node)
: true;
};
/**
* Filters aggregate none-children nodes.
* @param {IBalanceSheetDataNode[]} nodes
* @returns {IBalanceSheetDataNode[]}
*/
private aggregateNoneChildrenFilter = (
nodes: IBalanceSheetDataNode[],
): IBalanceSheetDataNode[] => {
return this.filterNodesDeep2(
this.aggregateNoneChildrenFilterDetarminer,
nodes,
);
};
// -----------------------
// # Composers.
// -----------------------
/**
* Filters none-zero nodes.
* @param {IBalanceSheetDataNode[]} nodes
* @returns {IBalanceSheetDataNode[]}
*/
private filterNoneZeroNodesCompose = (
nodes: IBalanceSheetDataNode[],
): IBalanceSheetDataNode[] => {
return R.compose(
this.aggregateNoneChildrenFilter,
this.accountsNoneZeroNodesFilter,
)(nodes);
};
/**
* Filters none-transactions nodes.
* @param {IBalanceSheetDataNode[]} nodes
* @returns {IBalanceSheetDataNode[]}
*/
private filterNoneTransNodesCompose = (
nodes: IBalanceSheetDataNode[],
): IBalanceSheetDataNode[] => {
return R.compose(
this.aggregateNoneChildrenFilter,
this.accountsNoneTransactionsNodesFilter,
)(nodes);
};
/**
* Supress nodes when accounts transactions ledger is empty.
* @param {IBalanceSheetDataNode[]} nodes
* @returns {IBalanceSheetDataNode[]}
*/
private supressNodesWhenAccountsTransactionsEmpty = (
nodes: IBalanceSheetDataNode[],
): IBalanceSheetDataNode[] => {
return this.repository.totalAccountsLedger.isEmpty() ? [] : nodes;
};
/**
* Compose report nodes filtering.
* @param {IBalanceSheetDataNode[]} nodes
* @returns {IBalanceSheetDataNode[]}
*/
protected reportFilterPlugin = (nodes: IBalanceSheetDataNode[]) => {
return R.compose(
this.supressNodesWhenAccountsTransactionsEmpty,
R.when(R.always(this.query.noneZero), this.filterNoneZeroNodesCompose),
R.when(
R.always(this.query.noneTransactions),
this.filterNoneTransNodesCompose,
),
)(nodes);
};
};

View File

@@ -0,0 +1,67 @@
import {
IBalanceSheetDOO,
IBalanceSheetQuery,
} from './BalanceSheet.types';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetMetaInjectable } from './BalanceSheetMeta';
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { BalanceSheet } from './BalanceSheet';
import { getBalanceSheetDefaultQuery } from './constants';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { I18nService } from 'nestjs-i18n';
@Injectable()
export class BalanceSheetInjectable {
constructor(
private readonly balanceSheetMeta: BalanceSheetMetaInjectable,
private readonly eventPublisher: EventEmitter2,
private readonly tenancyContext: TenancyContext,
private readonly i18n: I18nService,
@Inject(BalanceSheetRepository.name)
private readonly balanceSheetRepository: BalanceSheetRepository,
) {}
/**
* Retrieve balance sheet statement.
* @param {IBalanceSheetQuery} query - Balance sheet query.
* @return {Promise<IBalanceSheetStatement>}
*/
public async balanceSheet(
query: IBalanceSheetQuery,
): Promise<IBalanceSheetDOO> {
const filter = {
...getBalanceSheetDefaultQuery(),
...query,
};
const tenantMetadata = await this.tenancyContext.getTenantMetadata(true);
// Loads all resources.
await this.balanceSheetRepository.asyncInitialize(filter);
// Balance sheet report instance.
const balanceSheetInstanace = new BalanceSheet(
filter,
this.balanceSheetRepository,
tenantMetadata.baseCurrency,
this.i18n,
);
// Balance sheet data.
const data = balanceSheetInstanace.reportData();
// Balance sheet meta.
const meta = await this.balanceSheetMeta.meta(filter);
// Triggers `onBalanceSheetViewed` event.
await this.eventPublisher.emitAsync(events.reports.onBalanceSheetViewed, {
query,
});
return {
query: filter,
data,
meta,
};
}
}

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import * as moment from 'moment';
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
import { IBalanceSheetMeta, IBalanceSheetQuery } from './BalanceSheet.types';
@Injectable()
export class BalanceSheetMetaInjectable {
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
/**
* Retrieves the balance sheet meta.
* @returns {IBalanceSheetMeta}
*/
public async meta(query: IBalanceSheetQuery): Promise<IBalanceSheetMeta> {
const commonMeta = await this.financialSheetMeta.meta();
const formattedAsDate = moment(query.toDate).format('YYYY/MM/DD');
const formattedDateRange = `As ${formattedAsDate}`;
const sheetName = 'Balance Sheet Statement';
return {
...commonMeta,
sheetName,
formattedAsDate,
formattedDateRange,
};
}
}

View File

@@ -0,0 +1,230 @@
import * as R from 'ramda';
import {
BALANCE_SHEET_SCHEMA_NODE_TYPE,
IBalanceSheetDataNode,
IBalanceSheetNetIncomeNode,
IBalanceSheetSchemaNetIncomeNode,
IBalanceSheetSchemaNode,
IBalanceSheetTotalPeriod,
} from './BalanceSheet.types';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP';
import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncome = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomePP,
BalanceSheetNetIncomePY,
BalanceSheetComparsionPreviousYear,
BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod,
FinancialHorizTotals,
)(Base) {
public repository: BalanceSheetRepository;
public query: BalanceSheetQuery;
/**
* Retrieves the closing balance of income accounts.
* @returns {number}
*/
public getIncomeTotal = () => {
const closeingBalance = this.repository.incomeLedger.getClosingBalance();
return closeingBalance;
};
/**
* Retrieves the closing balance of expenses accounts.
* @returns {number}
*/
public getExpensesTotal = () => {
const closingBalance = this.repository.expensesLedger.getClosingBalance();
return closingBalance;
};
/**
* Retrieves the total net income.
* @returns {number}
*/
public getNetIncomeTotal = () => {
const income = this.getIncomeTotal();
const expenses = this.getExpensesTotal();
return income - expenses;
};
/**
* Mappes the aggregate schema node type.
* @param {IBalanceSheetSchemaNetIncomeNode} node - Schema node.
* @return {IBalanceSheetAggregateNode}
*/
public schemaNetIncomeNodeMapper = (
node: IBalanceSheetSchemaNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
const total = this.getNetIncomeTotal();
return {
id: node.id,
name: this.i18n.__(node.name),
nodeType: BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME,
total: this.getTotalAmountMeta(total),
};
};
/**
* Mapps the net income shcema node to report node.
* @param {IBalanceSheetSchemaNetIncomeNode} node
* @returns {IBalanceSheetNetIncomeNode}
*/
public schemaNetIncomeNodeCompose = (
node: IBalanceSheetSchemaNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
return R.compose(
R.when(
this.query.isPreviousYearActive,
this.previousYearNetIncomeNodeCompose,
),
R.when(
this.query.isPreviousPeriodActive,
this.previousPeriodNetIncomeNodeCompose,
),
R.when(
this.query.isDatePeriodsColumnsType,
this.assocNetIncomeDatePeriodsNode,
),
this.schemaNetIncomeNodeMapper,
)(node);
};
// --------------------------------
// # Date Periods
// --------------------------------
/**
* Retreives total income of the given date period.
* @param {number} accountId -
* @param {Date} toDate -
* @returns {number}
*/
public getIncomeDatePeriodTotal = (toDate: Date): number => {
const periodTotalBetween = this.repository.incomePeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const periodOpening =
this.repository.incomePeriodsOpeningAccountsLedger.getClosingBalance();
return periodOpening + periodTotalBetween;
};
/**
* Retrieves total expense of the given date period.
* @param {number} accountId -
* @param {Date} toDate -
* @returns {number}
*/
public getExpensesDatePeriodTotal = (toDate: Date): number => {
const periodTotalBetween = this.repository.expensesPeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const periodOpening =
this.repository.expensesOpeningAccountLedger.getClosingBalance();
return periodOpening + periodTotalBetween;
};
/**
* Retrieve the given net income date period total.
* @param {number} accountId
* @param {Date} toDate
* @returns {number}
*/
public getNetIncomeDatePeriodTotal = (toDate: Date): number => {
const income = this.getIncomeDatePeriodTotal(toDate);
const expense = this.getExpensesDatePeriodTotal(toDate);
return income - expense;
};
/**
* Retrieves the net income date period node.
* @param {IBalanceSheetNetIncomeNode} node
* @param {Date} fromDate
* @param {Date} toDate
* @returns {IBalanceSheetNetIncomeNode}
*/
public getNetIncomeDatePeriodNode = (
node: IBalanceSheetNetIncomeNode,
fromDate: Date,
toDate: Date,
): IBalanceSheetTotalPeriod => {
const periodTotal = this.getNetIncomeDatePeriodTotal(toDate);
return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
};
/**
* Retrieve total date periods of the given net income node.
* @param {IBalanceSheetNetIncomeNode} node
* @returns {IBalanceSheetNetIncomeNode}
*/
public getNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetTotalPeriod[] => {
return this.getReportNodeDatePeriods(
node,
this.getNetIncomeDatePeriodNode,
);
};
/**
* Assoc total date periods to net income node.
* @param {IBalanceSheetNetIncomeNode} node
* @returns {IBalanceSheetNetIncomeNode}
*/
public assocNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
const datePeriods = this.getNetIncomeDatePeriodsNode(node);
return R.assoc('horizontalTotals', datePeriods, node);
};
// -----------------------------
// - Net Income Nodes Praser
// -----------------------------
/**
* Mappes the given report schema node.
* @param {IBalanceSheetSchemaNode} node - Schema node.
* @return {IBalanceSheetDataNode}
*/
public reportNetIncomeNodeSchemaParser = (
schemaNode: IBalanceSheetSchemaNode,
): IBalanceSheetDataNode => {
return R.compose(
R.when(
this.isSchemaNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME),
this.schemaNetIncomeNodeCompose,
),
)(schemaNode);
};
/**
* Parses the report net income schema nodes.
* @param {(IBalanceSheetSchemaNode | IBalanceSheetDataNode)[]} nodes -
* @return {IBalanceSheetDataNode[]}
*/
public netIncomeSchemaParser = (
nodes: (IBalanceSheetSchemaNode | IBalanceSheetDataNode)[],
): IBalanceSheetDataNode[] => {
return this.mapNodesDeep(nodes, this.reportNetIncomeNodeSchemaParser);
};
};

View File

@@ -0,0 +1,124 @@
import * as R from 'ramda';
import {
IBalanceSheetNetIncomeNode,
IBalanceSheetTotalPeriod,
} from '@/interfaces';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP';
import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncomeDatePeriods = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomePP,
BalanceSheetNetIncomePY,
BalanceSheetComparsionPreviousYear,
BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod,
FinancialHorizTotals,
)(Base) {
repository: BalanceSheetRepository;
// --------------------------------
// # Date Periods
// --------------------------------
/**
* Retreives total income of the given date period.
* @param {number} accountId -
* @param {Date} toDate -
* @returns {number}
*/
private getIncomeDatePeriodTotal = (toDate: Date): number => {
const periodTotalBetween = this.repository.incomePeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const periodOpening =
this.repository.incomePeriodsOpeningAccountsLedger.getClosingBalance();
return periodOpening + periodTotalBetween;
};
/**
* Retrieves total expense of the given date period.
* @param {number} accountId -
* @param {Date} toDate -
* @returns {number}
*/
private getExpensesDatePeriodTotal = (toDate: Date): number => {
const periodTotalBetween = this.repository.expensesPeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const periodOpening =
this.repository.expensesOpeningAccountLedger.getClosingBalance();
return periodOpening + periodTotalBetween;
};
/**
* Retrieve the given net income date period total.
* @param {number} accountId
* @param {Date} toDate
* @returns {number}
*/
private getNetIncomeDatePeriodTotal = (toDate: Date): number => {
const income = this.getIncomeDatePeriodTotal(toDate);
const expense = this.getExpensesDatePeriodTotal(toDate);
return income - expense;
};
/**
* Retrieves the net income date period node.
* @param {IBalanceSheetNetIncomeNode} node
* @param {Date} fromDate
* @param {Date} toDate
* @returns {IBalanceSheetNetIncomeNode}
*/
private getNetIncomeDatePeriodNode = (
node: IBalanceSheetNetIncomeNode,
fromDate: Date,
toDate: Date,
): IBalanceSheetTotalPeriod => {
const periodTotal = this.getNetIncomeDatePeriodTotal(toDate);
return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate);
};
/**
* Retrieve total date periods of the given net income node.
* @param {IBalanceSheetNetIncomeNode} node
* @returns {IBalanceSheetNetIncomeNode}
*/
private getNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetTotalPeriod[] => {
return this.getReportNodeDatePeriods(
node,
this.getNetIncomeDatePeriodNode,
);
};
/**
* Assoc total date periods to net income node.
* @param {IBalanceSheetNetIncomeNode} node
* @returns {IBalanceSheetNetIncomeNode}
*/
public assocNetIncomeDatePeriodsNode = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
const datePeriods = this.getNetIncomeDatePeriodsNode(node);
return R.assoc('horizontalTotals', datePeriods, node);
};
};

View File

@@ -0,0 +1,136 @@
import * as R from 'ramda';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import {
IBalanceSheetNetIncomeNode,
IBalanceSheetTotal,
} from './BalanceSheet.types';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetNetIncomeDatePeriodsPP = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod,
FinancialHorizTotals,
)(Base) {
query: BalanceSheetQuery;
repository: BalanceSheetRepository;
/**
* Retrieves the PY total income of the given date period.
* @param {number} accountId -
* @param {Date} toDate -
* @return {number}
*/
public getPPIncomeDatePeriodTotal = R.curry((toDate: Date) => {
const PYPeriodsTotal = this.repository.incomePPPeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const PYPeriodsOpeningTotal =
this.repository.incomePPPeriodsOpeningAccountLedger.getClosingBalance();
return PYPeriodsOpeningTotal + PYPeriodsTotal;
});
/**
* Retrieves the PY total expense of the given date period.
* @param {number} accountId -
* @param {Date} toDate -
* @returns {number}
*/
public getPPExpenseDatePeriodTotal = R.curry((toDate: Date) => {
const PYPeriodsTotal = this.repository.expensePPPeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const PYPeriodsOpeningTotal =
this.repository.expensePPPeriodsOpeningAccountLedger.getClosingBalance();
return PYPeriodsOpeningTotal + PYPeriodsTotal;
});
/**
* Retrieve the given net income total of the given period.
* @param {number} accountId - Account id.
* @param {Date} toDate - To date.
* @returns {number}
*/
public getPPNetIncomeDatePeriodTotal = R.curry((toDate: Date) => {
const income = this.getPPIncomeDatePeriodTotal(toDate);
const expense = this.getPPExpenseDatePeriodTotal(toDate);
return income - expense;
});
/**
* Assoc preivous period to account horizontal total node.
* @param {IBalanceSheetAccountNode} node
* @returns {}
*/
public assocPreviousPeriodNetIncomeHorizTotal = R.curry(
(node: IBalanceSheetNetIncomeNode, totalNode) => {
const total = this.getPPNetIncomeDatePeriodTotal(
totalNode.previousPeriodToDate.date,
);
return R.assoc('previousPeriod', this.getAmountMeta(total), totalNode);
},
);
/**
* Compose previous period to aggregate horizontal nodes.
* @param {IBalanceSheetTotal} node
* @returns {IBalanceSheetTotal}
*/
public previousPeriodNetIncomeHorizNodeComposer = R.curry(
(
node: IBalanceSheetNetIncomeNode,
horiontalTotalNode: IBalanceSheetTotal,
): IBalanceSheetTotal => {
return R.compose(
R.when(
this.query.isPreviousPeriodPercentageActive,
this.assocPreviousPeriodTotalPercentageNode,
),
R.when(
this.query.isPreviousPeriodChangeActive,
this.assocPreviousPeriodTotalChangeNode,
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodNetIncomeHorizTotal(node),
),
R.when(
this.query.isPreviousPeriodActive,
this.assocPreviousPeriodHorizNodeFromToDates(
this.query.displayColumnsBy,
),
),
)(horiontalTotalNode);
},
);
/**
* Associate the PP to net income horizontal nodes.
* @param {IBalanceSheetCommonNode} node
* @returns {IBalanceSheetCommonNode}
*/
public assocPreviousPeriodNetIncomeHorizNode = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
const horizontalTotals = R.addIndex(R.map)(
this.previousPeriodNetIncomeHorizNodeComposer(node),
node.horizontalTotals,
) as IBalanceSheetTotal[];
return R.assoc('horizontalTotals', horizontalTotals, node);
};
};

View File

@@ -0,0 +1,131 @@
import * as R from 'ramda';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import {
IBalanceSheetNetIncomeNode,
IBalanceSheetTotal,
} from './BalanceSheet.types';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetNetIncomeDatePeriodsPY = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetComparsionPreviousYear,
FinancialPreviousPeriod,
FinancialHorizTotals,
)(Base) {
query: BalanceSheetQuery;
repository: BalanceSheetRepository;
/**
* Retrieves the PY total income of the given date period.
* @param {Date} toDate -
* @return {number}
*/
public getPYIncomeDatePeriodTotal = R.curry((toDate: Date) => {
const PYPeriodsTotal = this.repository.incomePYPeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const PYPeriodsOpeningTotal =
this.repository.incomePYPeriodsOpeningAccountLedger.getClosingBalance();
return PYPeriodsOpeningTotal + PYPeriodsTotal;
});
/**
* Retrieves the PY total expense of the given date period.
* @param {Date} toDate -
* @returns {number}
*/
public getPYExpenseDatePeriodTotal = R.curry((toDate: Date) => {
const PYPeriodsTotal = this.repository.expensePYPeriodsAccountsLedger
.whereToDate(toDate)
.getClosingBalance();
const PYPeriodsOpeningTotal =
this.repository.expensePYPeriodsOpeningAccountLedger.getClosingBalance();
return PYPeriodsOpeningTotal + PYPeriodsTotal;
});
/**
* Retrieve the given net income total of the given period.
* @param {Date} toDate - To date.
* @returns {number}
*/
public getPYNetIncomeDatePeriodTotal = R.curry((toDate: Date) => {
const income = this.getPYIncomeDatePeriodTotal(toDate);
const expense = this.getPYExpenseDatePeriodTotal(toDate);
return income - expense;
});
/**
* Assoc preivous year to account horizontal total node.
* @param {IBalanceSheetAccountNode} node
* @returns {}
*/
public assocPreviousYearNetIncomeHorizTotal = R.curry(
(node: IBalanceSheetNetIncomeNode, totalNode) => {
const total = this.getPYNetIncomeDatePeriodTotal(
totalNode.previousYearToDate.date,
);
return R.assoc('previousYear', this.getAmountMeta(total), totalNode);
},
);
/**
* Compose PY to net income horizontal nodes.
* @param {IBalanceSheetTotal} node
* @returns {IBalanceSheetTotal}
*/
public previousYearNetIncomeHorizNodeComposer = R.curry(
(
node: IBalanceSheetNetIncomeNode,
horiontalTotalNode: IBalanceSheetTotal,
): IBalanceSheetTotal => {
return R.compose(
R.when(
this.query.isPreviousYearPercentageActive,
this.assocPreviousYearTotalPercentageNode,
),
R.when(
this.query.isPreviousYearChangeActive,
this.assocPreviousYearTotalChangeNode,
),
R.when(
this.query.isPreviousYearActive,
this.assocPreviousYearNetIncomeHorizTotal(node),
),
R.when(
this.query.isPreviousYearActive,
this.assocPreviousYearHorizNodeFromToDates,
),
)(horiontalTotalNode);
},
);
/**
* Associate the PY to net income horizontal nodes.
* @param {IBalanceSheetCommonNode} node
* @returns {IBalanceSheetCommonNode}
*/
public assocPreviousYearNetIncomeHorizNode = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
const horizontalTotals = R.addIndex(R.map)(
this.previousYearNetIncomeHorizNodeComposer(node),
node.horizontalTotals,
) as IBalanceSheetTotal[];
return R.assoc('horizontalTotals', horizontalTotals, node);
};
};

View File

@@ -0,0 +1,79 @@
import * as R from 'ramda';
import {
IBalanceSheetDataNode,
IBalanceSheetNetIncomeNode,
} from './BalanceSheet.types';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetNetIncomeDatePeriodsPP } from './BalanceSheetNetIncomeDatePeriodsPP';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncomePP = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomeDatePeriodsPP,
BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod,
FinancialHorizTotals,
)(Base) {
public repository: BalanceSheetRepository;
public query: BalanceSheetQuery;
// -------------------------------
// # Previous Period (PP)
// -------------------------------
/**
* Retrieves the PP net income.
* @returns {}
*/
public getPreviousPeriodNetIncome = () => {
const income = this.repository.incomePPAccountsLedger.getClosingBalance();
const expense =
this.repository.expensePPAccountsLedger.getClosingBalance();
return income - expense;
};
/**
* Associates the previous period to account node.
* @param {IBalanceSheetDataNode} node
* @returns {IBalanceSheetDataNode}
*/
public assocPreviousPeriodNetIncomeNode = (
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
const total = this.getPreviousPeriodNetIncome();
return R.assoc('previousPeriod', this.getAmountMeta(total), node);
};
/**
* Previous period account node composer.
* @param {IBalanceSheetNetIncomeNode} node
* @returns {IBalanceSheetNetIncomeNode}
*/
public previousPeriodNetIncomeNodeCompose = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
return R.compose(
R.when(
this.isNodeHasHorizTotals,
this.assocPreviousPeriodNetIncomeHorizNode,
),
R.when(
this.query.isPreviousPeriodPercentageActive,
this.assocPreviousPeriodPercentageNode,
),
R.when(
this.query.isPreviousPeriodChangeActive,
this.assocPreviousPeriodChangeNode,
),
this.assocPreviousPeriodNetIncomeNode,
)(node);
};
};

View File

@@ -0,0 +1,80 @@
import * as R from 'ramda';
import { IBalanceSheetNetIncomeNode } from './BalanceSheet.types';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod';
import { FinancialHorizTotals } from '../../common/FinancialHorizTotals';
import { BalanceSheetRepository } from './BalanceSheetRepository';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetNetIncomeDatePeriodsPY } from './BalanceSheetNetIncomeDatePeriodsPY';
import { FinancialSheet } from '../../common/FinancialSheet';
import { GConstructor } from '@/common/types/Constructor';
export const BalanceSheetNetIncomePY = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends R.pipe(
BalanceSheetNetIncomeDatePeriodsPY,
BalanceSheetComparsionPreviousYear,
BalanceSheetComparsionPreviousPeriod,
FinancialPreviousPeriod,
FinancialHorizTotals,
)(Base) {
// public repository: BalanceSheetRepository;
// public query: BalanceSheetQuery;
// ------------------------------
// # Previous Year (PY)
// ------------------------------
/**
* Retrieves the previous year (PY) net income.
* @returns {number}
*/
public getPreviousYearNetIncome = () => {
const income =
this.repository.incomePYTotalAccountsLedger.getClosingBalance();
const expense =
this.repository.expensePYTotalAccountsLedger.getClosingBalance();
return income - expense;
};
/**
* Assoc previous year on aggregate node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
public assocPreviousYearNetIncomeNode = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
const total = this.getPreviousYearNetIncome();
return R.assoc('previousYear', this.getTotalAmountMeta(total), node);
};
/**
* Assoc previous year attributes to aggregate node.
* @param {IBalanceSheetAccountNode} node
* @returns {IBalanceSheetAccountNode}
*/
public previousYearNetIncomeNodeCompose = (
node: IBalanceSheetNetIncomeNode,
): IBalanceSheetNetIncomeNode => {
return R.compose(
R.when(
this.query.isPreviousYearPercentageActive,
this.assocPreviousYearTotalPercentageNode,
),
R.when(
this.query.isPreviousYearChangeActive,
this.assocPreviousYearTotalChangeNode,
),
// Associate the PY to date periods horizontal nodes.
R.when(
this.isNodeHasHorizontalTotals,
this.assocPreviousYearNetIncomeHorizNode,
),
this.assocPreviousYearNetIncomeNode,
)(node);
};
};

View File

@@ -0,0 +1,29 @@
import { TableSheetPdf } from '../../common/TableSheetPdf';
import { IBalanceSheetQuery } from './BalanceSheet.types';
import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable';
import { HtmlTableCustomCss } from './constants';
import { Injectable } from '@nestjs/common';
@Injectable()
export class BalanceSheetPdfInjectable {
constructor(
private readonly balanceSheetTable: BalanceSheetTableInjectable,
private readonly tableSheetPdf: TableSheetPdf,
) {}
/**
* Converts the given balance sheet table to pdf.
* @param {IBalanceSheetQuery} query - Balance sheet query.
* @returns {Promise<Buffer>}
*/
public async pdf(query: IBalanceSheetQuery): Promise<Buffer> {
const table = await this.balanceSheetTable.table(query);
return this.tableSheetPdf.convertToPdf(
table.table,
table.meta.sheetName,
table.meta.formattedDateRange,
HtmlTableCustomCss,
);
}
}

View File

@@ -0,0 +1,225 @@
import * as R from 'ramda';
import { get } from 'lodash';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { IBalanceSheetDataNode } from './BalanceSheet.types';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetPercentage = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends Base {
readonly query: BalanceSheetQuery;
/**
* Assoc percentage of column to report node.
* @param {IBalanceSheetDataNode} node
* @returns {IBalanceSheetDataNode}
*/
public assocReportNodeColumnPercentage = R.curry(
(
parentTotal: number,
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
const percentage = this.getPercentageBasis(
parentTotal,
node.total.amount,
);
return R.assoc(
'percentageColumn',
this.getPercentageAmountMeta(percentage),
node,
);
},
);
/**
* Assoc percentage of row to report node.
* @param {IBalanceSheetDataNode} node
* @returns {IBalanceSheetDataNode}
*/
public assocReportNodeRowPercentage = R.curry(
(
parentTotal: number,
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
const percenatage = this.getPercentageBasis(
parentTotal,
node.total.amount,
);
return R.assoc(
'percentageRow',
this.getPercentageAmountMeta(percenatage),
node,
);
},
);
/**
* Assoc percentage of row to horizontal total.
* @param {number} parentTotal -
* @param {IBalanceSheetDataNode} node
* @returns {IBalanceSheetDataNode}
*/
public assocRowPercentageHorizTotals = R.curry(
(
parentTotal: number,
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
const assocRowPercen = this.assocReportNodeRowPercentage(parentTotal);
const horTotals = R.map(assocRowPercen)(node.horizontalTotals);
return R.assoc('horizontalTotals', horTotals, node);
},
);
/**
*
* @param {} parentNode -
* @param {} horTotalNode -
* @param {number} index -
*/
private assocColumnPercentageHorizTotal = R.curry(
(parentNode, horTotalNode, index) => {
const parentTotal = get(
parentNode,
`horizontalTotals[${index}].total.amount`,
0,
);
return this.assocReportNodeColumnPercentage(parentTotal, horTotalNode);
},
);
/**
* Assoc column percentage to horizontal totals nodes.
* @param {IBalanceSheetDataNode} node
* @returns {IBalanceSheetDataNode}
*/
public assocColumnPercentageHorizTotals = R.curry(
(
parentNode: IBalanceSheetDataNode,
node: IBalanceSheetDataNode,
): IBalanceSheetDataNode => {
// Horizontal totals.
const assocColPerc = this.assocColumnPercentageHorizTotal(parentNode);
const horTotals = R.addIndex(R.map)(assocColPerc)(
node.horizontalTotals,
);
return R.assoc('horizontalTotals', horTotals, node);
},
);
/**
*
* @param {number} parentTotal -
* @param {} node
* @returns
*/
public reportNodeColumnPercentageComposer = R.curry((parentNode, node) => {
const parentTotal = parentNode.total.amount;
return R.compose(
R.when(
this.isNodeHasHorizoTotals,
this.assocColumnPercentageHorizTotals(parentNode),
),
this.assocReportNodeColumnPercentage(parentTotal),
)(node);
});
/**
*
* @param node
* @returns
*/
private reportNodeRowPercentageComposer = (node) => {
const total = node.total.amount;
return R.compose(
R.when(
this.isNodeHasHorizoTotals,
this.assocRowPercentageHorizTotals(total),
),
this.assocReportNodeRowPercentage(total),
)(node);
};
/**
*
*/
private assocNodeColumnPercentageChildren = (node) => {
const children = this.mapNodesDeep(
node.children,
this.reportNodeColumnPercentageComposer(node),
);
return R.assoc('children', children, node);
};
/**
*
* @param node
* @returns
*/
private reportNodeColumnPercentageDeepMap = (node) => {
const parentTotal = node.total.amount;
const parentNode = node;
return R.compose(
R.when(
this.isNodeHasHorizoTotals,
this.assocColumnPercentageHorizTotals(parentNode),
),
this.assocReportNodeColumnPercentage(parentTotal),
this.assocNodeColumnPercentageChildren,
)(node);
};
/**
*
* @param {IBalanceSheetDataNode[]} node
* @returns {IBalanceSheetDataNode[]}
*/
private reportColumnsPercentageMapper = (
nodes: IBalanceSheetDataNode[],
): IBalanceSheetDataNode[] => {
return R.map(this.reportNodeColumnPercentageDeepMap, nodes);
};
/**
*
* @param nodes
* @returns
*/
private reportRowsPercentageMapper = (nodes) => {
return this.mapNodesDeep(nodes, this.reportNodeRowPercentageComposer);
};
/**
*
* @param nodes
* @returns
*/
public reportPercentageCompose = (nodes) => {
return R.compose(
R.when(
this.query.isColumnsPercentageActive,
this.reportColumnsPercentageMapper,
),
R.when(
this.query.isRowsPercentageActive,
this.reportRowsPercentageMapper,
),
)(nodes);
};
/**
* Detarmines whether the given node has horizontal total.
* @param {IBalanceSheetDataNode} node
* @returns {boolean}
*/
public isNodeHasHorizoTotals = (node: IBalanceSheetDataNode): boolean => {
return (
!R.isEmpty(node.horizontalTotals) && !R.isNil(node.horizontalTotals)
);
};
};

View File

@@ -0,0 +1,185 @@
import { merge } from 'lodash';
import * as R from 'ramda';
import {
IBalanceSheetQuery,
IFinancialDatePeriodsUnit,
} from './BalanceSheet.types';
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
import { DISPLAY_COLUMNS_BY } from './constants';
export class BalanceSheetQuery extends R.compose(FinancialDateRanges)(
class {},
) {
/**
* Balance sheet query.
* @param {IBalanceSheetQuery}
*/
public readonly query: IBalanceSheetQuery;
/**
* Previous year to date.
* @param {Date}
*/
public readonly PYToDate: Date;
/**
* Previous year from date.
* @param {Date}
*/
public readonly PYFromDate: Date;
/**
* Previous period to date.
* @param {Date}
*/
public readonly PPToDate: Date;
/**
* Previous period from date.
* @param {Date}
*/
public readonly PPFromDate: Date;
/**
* Constructor method
* @param {IBalanceSheetQuery} query
*/
constructor(query: IBalanceSheetQuery) {
super();
this.query = query;
// Pervious Year (PY) Dates.
this.PYToDate = this.getPreviousYearDate(this.query.toDate);
this.PYFromDate = this.getPreviousYearDate(this.query.fromDate);
// Previous Period (PP) Dates for Total column.
if (this.isTotalColumnType()) {
const { fromDate, toDate } = this.getPPTotalDateRange(
this.query.fromDate,
this.query.toDate,
);
this.PPToDate = toDate;
this.PPFromDate = fromDate;
// Previous Period (PP) Dates for Date period columns type.
} else if (this.isDatePeriodsColumnsType()) {
const { fromDate, toDate } = this.getPPDatePeriodDateRange(
this.query.fromDate,
this.query.toDate,
this.query.displayColumnsBy as IFinancialDatePeriodsUnit,
);
this.PPToDate = toDate;
this.PPFromDate = fromDate;
}
return merge(this, query);
}
// ---------------------------
// # Columns Type/By.
// ---------------------------
/**
* Detarmines the given display columns type.
* @param {string} displayColumnsBy
* @returns {boolean}
*/
public isDisplayColumnsBy = (displayColumnsBy: string): boolean => {
return this.query.displayColumnsBy === displayColumnsBy;
};
/**
* Detarmines the given display columns by type.
* @param {string} displayColumnsBy
* @returns {boolean}
*/
public isDisplayColumnsType = (displayColumnsType: string): boolean => {
return this.query.displayColumnsType === displayColumnsType;
};
/**
* Detarmines whether the columns type is date periods.
* @returns {boolean}
*/
public isDatePeriodsColumnsType = (): boolean => {
return this.isDisplayColumnsType(DISPLAY_COLUMNS_BY.DATE_PERIODS);
};
/**
* Detarmines whether the columns type is total.
* @returns {boolean}
*/
public isTotalColumnType = (): boolean => {
return this.isDisplayColumnsType(DISPLAY_COLUMNS_BY.TOTAL);
};
// ---------------------------
// # Percentage column/row.
// ---------------------------
/**
* Detarmines whether the percentage of column active.
* @returns {boolean}
*/
public isColumnsPercentageActive = (): boolean => {
return this.query.percentageOfColumn;
};
/**
* Detarmines whether the percentage of row active.
* @returns {boolean}
*/
public isRowsPercentageActive = (): boolean => {
return this.query.percentageOfRow;
};
// ---------------------------
// # Previous Year (PY)
// ---------------------------
/**
* Detarmines the report query has previous year enabled.
* @returns {boolean}
*/
public isPreviousYearActive = (): boolean => {
return this.query.previousYear;
};
/**
* Detarmines the report query has previous year percentage change active.
* @returns {boolean}
*/
public isPreviousYearPercentageActive = (): boolean => {
return this.query.previousYearPercentageChange;
};
/**
* Detarmines the report query has previous year change active.
* @returns {boolean}
*/
public isPreviousYearChangeActive = (): boolean => {
return this.query.previousYearAmountChange;
};
// ---------------------------
// # Previous Period (PP).
// ---------------------------
/**
* Detarmines the report query has previous period enabled.
* @returns {boolean}
*/
public isPreviousPeriodActive = (): boolean => {
return this.query.previousPeriod;
};
/**
* Detarmines wether the preivous period percentage is active.
* @returns {boolean}
*/
public isPreviousPeriodPercentageActive = (): boolean => {
return this.query.previousPeriodPercentageChange;
};
/**
* Detarmines wether the previous period change is active.
* @returns {boolean}
*/
public isPreviousPeriodChangeActive = (): boolean => {
return this.query.previousPeriodAmountChange;
};
}

View File

@@ -0,0 +1,397 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
import * as R from 'ramda';
import { Knex } from 'knex';
import { isEmpty } from 'lodash';
import {
IAccountTransactionsGroupBy,
IBalanceSheetQuery,
} from './BalanceSheet.types';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
import { Ledger } from '@/modules/Ledger/Ledger';
import { transformToMapBy } from '@/utils/transform-to-map-by';
import { Account } from '@/modules/Accounts/models/Account.model';
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
@Injectable({ scope: Scope.TRANSIENT })
export class BalanceSheetRepository extends R.compose(
BalanceSheetRepositoryNetIncome,
FinancialDatePeriods,
)(class {}) {
/**
* Account model.
*/
@Inject(Account.name)
public readonly accountModel: typeof Account;
/**
* Account transaction model.
*/
@Inject(AccountTransaction.name)
public readonly accountTransactionModel: typeof AccountTransaction;
/**
* @description Balance sheet query.
* @param {BalanceSheetQuery}
*/
public query: BalanceSheetQuery;
/**
* @param {}
*/
public accounts: any;
/**
* @param {}
*/
public accountsGraph: any;
/**
*
*/
public accountsByType: any;
/**
* PY from date.
* @param {Date}
*/
public readonly PYFromDate: Date;
/**
* PY to date.
* @param {Date}
*/
public readonly PYToDate: Date;
/**
* PP to date.
* @param {Date}
*/
public readonly PPToDate: Date;
/**
* PP from date.
* @param {Date}
*/
public readonly PPFromDate: Date;
/**
* Total closing accounts ledger.
* @param {Ledger}
*/
public totalAccountsLedger: Ledger;
/**
* Total income accounts ledger.
*/
public incomeLedger: Ledger;
/**
* Total expense accounts ledger.
*/
public expensesLedger: Ledger;
/**
* Transactions group type.
* @param {IAccountTransactionsGroupBy}
*/
public transactionsGroupType: IAccountTransactionsGroupBy =
IAccountTransactionsGroupBy.Month;
// -----------------------
// # Date Periods
// -----------------------
/**
* @param {Ledger}
*/
public periodsAccountsLedger: Ledger;
/**
* @param {Ledger}
*/
public periodsOpeningAccountLedger: Ledger;
// -----------------------
// # Previous Year (PY).
// -----------------------
/**
* @param {Ledger}
*/
public PYPeriodsOpeningAccountLedger: Ledger;
/**
* @param {Ledger}
*/
public PYPeriodsAccountsLedger: Ledger;
/**
* @param {Ledger}
*/
public PYTotalAccountsLedger: ILedger;
// -----------------------
// # Previous Period (PP).
// -----------------------
/**
* @param {Ledger}
*/
public PPTotalAccountsLedger: Ledger;
/**
* @param {Ledger}
*/
public PPPeriodsAccountsLedger: ILedger;
/**
* @param {Ledger}
*/
public PPPeriodsOpeningAccountLedger: ILedger;
/**
* Constructor method.
* @param {IBalanceSheetQuery} query
*/
public setQuery(query: IBalanceSheetQuery) {
this.query = new BalanceSheetQuery(query);
this.transactionsGroupType = this.getGroupByFromDisplayColumnsBy(
this.query.displayColumnsBy,
);
}
/**
* Async initialize.
* @returns {Promise<void>}
*/
public asyncInitialize = async (query: IBalanceSheetQuery) => {
this.setQuery(query);
await this.initAccounts();
await this.initAccountsGraph();
await this.initAccountsTotalLedger();
// Date periods.
if (this.query.isDatePeriodsColumnsType()) {
await this.initTotalDatePeriods();
}
// Previous Year (PY).
if (this.query.isPreviousYearActive()) {
await this.initTotalPreviousYear();
}
if (
this.query.isPreviousYearActive() &&
this.query.isDatePeriodsColumnsType()
) {
await this.initPeriodsPreviousYear();
}
// Previous Period (PP).
if (this.query.isPreviousPeriodActive()) {
await this.initTotalPreviousPeriod();
}
if (
this.query.isPreviousPeriodActive() &&
this.query.isDatePeriodsColumnsType()
) {
await this.initPeriodsPreviousPeriod();
}
//
await this.asyncInitializeNetIncome();
};
// ----------------------------
// # Accounts
// ----------------------------
public initAccounts = async () => {
const accounts = await this.getAccounts();
this.accounts = accounts;
this.accountsByType = transformToMapBy(accounts, 'accountType');
this.accountsByParentType = transformToMapBy(accounts, 'accountParentType');
};
/**
* Initialize accounts graph.
*/
public initAccountsGraph = async () => {
this.accountsGraph = this.accountModel.toDependencyGraph(this.accounts);
};
// ----------------------------
// # Closing Total
// ----------------------------
/**
* Initialize accounts closing total based on the given query.
* @returns {Promise<void>}
*/
public initAccountsTotalLedger = async (): Promise<void> => {
const totalByAccount = await this.closingAccountsTotal(this.query.toDate);
// Inject to the repository.
this.totalAccountsLedger = Ledger.fromTransactions(totalByAccount);
};
// ----------------------------
// # Date periods.
// ----------------------------
/**
* Initialize date periods total.
* @returns {Promise<void>}
*/
public initTotalDatePeriods = async (): Promise<void> => {
// Retrieves grouped transactions by given date group.
const periodsByAccount = await this.accountsDatePeriods(
this.query.fromDate,
this.query.toDate,
this.transactionsGroupType,
);
// Retrieves opening balance of grouped transactions.
const periodsOpeningByAccount = await this.closingAccountsTotal(
this.query.fromDate,
);
// Inject to the repository.
this.periodsAccountsLedger = Ledger.fromTransactions(periodsByAccount);
this.periodsOpeningAccountLedger = Ledger.fromTransactions(
periodsOpeningByAccount,
);
};
// ----------------------------
// # Previous Year (PY).
// ----------------------------
/**
* Initialize total of previous year.
* @returns {Promise<void>}
*/
public initTotalPreviousYear = async (): Promise<void> => {
const PYTotalsByAccounts = await this.closingAccountsTotal(
this.query.PYToDate,
);
// Inject to the repository.
this.PYTotalAccountsLedger = Ledger.fromTransactions(PYTotalsByAccounts);
};
/**
* Initialize date periods of previous year.
* @returns {Promise<void>}
*/
public initPeriodsPreviousYear = async (): Promise<void> => {
const PYPeriodsBYAccounts = await this.accountsDatePeriods(
this.query.PYFromDate,
this.query.PYToDate,
this.transactionsGroupType,
);
// Retrieves opening balance of grouped transactions.
const periodsOpeningByAccount = await this.closingAccountsTotal(
this.query.PYFromDate,
);
// Inject to the repository.
this.PYPeriodsAccountsLedger = Ledger.fromTransactions(PYPeriodsBYAccounts);
this.PYPeriodsOpeningAccountLedger = Ledger.fromTransactions(
periodsOpeningByAccount,
);
};
// ----------------------------
// # Previous Year (PP).
// ----------------------------
/**
* Initialize total of previous year.
* @returns {Promise<void>}
*/
public initTotalPreviousPeriod = async (): Promise<void> => {
const PPTotalsByAccounts = await this.closingAccountsTotal(
this.query.PPToDate,
);
// Inject to the repository.
this.PPTotalAccountsLedger = Ledger.fromTransactions(PPTotalsByAccounts);
};
/**
* Initialize date periods of previous year.
* @returns {Promise<void>}
*/
public initPeriodsPreviousPeriod = async (): Promise<void> => {
const PPPeriodsBYAccounts = await this.accountsDatePeriods(
this.query.PPFromDate,
this.query.PPToDate,
this.transactionsGroupType,
);
// Retrieves opening balance of grouped transactions.
const periodsOpeningByAccount = await this.closingAccountsTotal(
this.query.PPFromDate,
);
// Inject to the repository.
this.PPPeriodsAccountsLedger = Ledger.fromTransactions(PPPeriodsBYAccounts);
this.PPPeriodsOpeningAccountLedger = Ledger.fromTransactions(
periodsOpeningByAccount,
);
};
// ----------------------------
// # Utils
// ----------------------------
/**
* Retrieve accounts of the report.
* @return {Promise<IAccount[]>}
*/
public getAccounts = () => {
return this.accountModel.query();
};
/**
* Closing accounts date periods.
* @param {Date} fromDate
* @param {Date} toDate
* @param {string} datePeriodsType
* @returns
*/
public accountsDatePeriods = async (
fromDate: Date,
toDate: Date,
datePeriodsType: string,
) => {
return this.accountTransactionModel.query().onBuild((query) => {
query.sum('credit as credit');
query.sum('debit as debit');
query.groupBy('accountId');
query.select(['accountId']);
query.modify('groupByDateFormat', datePeriodsType);
query.modify('filterDateRange', fromDate, toDate);
query.withGraphFetched('account');
this.commonFilterBranchesQuery(query);
});
};
/**
* Retrieve the opening balance transactions of the report.
* @param {Date|string} openingDate -
*/
public closingAccountsTotal = async (openingDate: Date | string) => {
return this.accountTransactionModel.query().onBuild((query) => {
query.sum('credit as credit');
query.sum('debit as debit');
query.groupBy('accountId');
query.select(['accountId']);
query.modify('filterDateRange', null, openingDate);
query.withGraphFetched('account');
this.commonFilterBranchesQuery(query);
});
};
/**
* Common branches filter query.
* @param {Knex.QueryBuilder} query
*/
public commonFilterBranchesQuery = (query: Knex.QueryBuilder) => {
if (!isEmpty(this.query.branchesIds)) {
query.modify('filterByBranches', this.query.branchesIds);
}
};
}

View File

@@ -0,0 +1,230 @@
import * as R from 'ramda';
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
import { ModelObject } from 'objection';
import { Account } from '@/modules/Accounts/models/Account.model';
import { ILedger } from '@/modules/Ledger/types/Ledger.types';
import { ACCOUNT_PARENT_TYPE } from '@/constants/accounts';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetRepositoryNetIncome = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(FinancialDatePeriods)(Base) {
// -----------------------
// # Net Income
// -----------------------
public incomeAccounts: ModelObject<Account>[];
public incomeAccountsIds: number[];
public expenseAccounts: ModelObject<Account>[];
public expenseAccountsIds: number[];
public incomePeriodsAccountsLedger: ILedger;
public incomePeriodsOpeningAccountsLedger: ILedger;
public expensesPeriodsAccountsLedger: ILedger;
public expensesOpeningAccountLedger: ILedger;
public incomePPAccountsLedger: ILedger;
public expensePPAccountsLedger: ILedger;
public incomePPPeriodsAccountsLedger: ILedger;
public incomePPPeriodsOpeningAccountLedger: ILedger;
public expensePPPeriodsAccountsLedger: ILedger;
public expensePPPeriodsOpeningAccountLedger: ILedger;
public incomePYTotalAccountsLedger: ILedger;
public expensePYTotalAccountsLedger: ILedger;
public incomePYPeriodsAccountsLedger: ILedger;
public incomePYPeriodsOpeningAccountLedger: ILedger;
public expensePYPeriodsAccountsLedger: ILedger;
public expensePYPeriodsOpeningAccountLedger: ILedger;
/**
* Async initialize.
* @returns {Promise<void>}
*/
public asyncInitializeNetIncome = async () => {
await this.initAccounts();
await this.initAccountsTotalLedger();
// Net Income
this.initIncomeAccounts();
this.initExpenseAccounts();
this.initIncomeTotalLedger();
this.initExpensesTotalLedger();
// Date periods
if (this.query.isDatePeriodsColumnsType()) {
this.initNetIncomeDatePeriods();
}
// Previous Year (PY).
if (this.query.isPreviousYearActive()) {
this.initNetIncomePreviousYear();
}
// Previous Period (PP).
if (this.query.isPreviousPeriodActive()) {
this.initNetIncomePreviousPeriod();
}
// Previous Year (PY) / Date Periods.
if (
this.query.isPreviousYearActive() &&
this.query.isDatePeriodsColumnsType()
) {
this.initNetIncomePeriodsPreviewYear();
}
// Previous Period (PP) / Date Periods.
if (
this.query.isPreviousPeriodActive() &&
this.query.isDatePeriodsColumnsType()
) {
this.initNetIncomePeriodsPreviousPeriod();
}
};
// ----------------------------
// # Net Income
// ----------------------------
/**
* Initialize income accounts.
*/
public initIncomeAccounts = () => {
const incomeAccounts = this.accountsByParentType.get(
ACCOUNT_PARENT_TYPE.INCOME,
);
const incomeAccountsIds = incomeAccounts.map((a) => a.id);
this.incomeAccounts = incomeAccounts;
this.incomeAccountsIds = incomeAccountsIds;
};
/**
* Initialize expense accounts.
*/
public initExpenseAccounts = () => {
const expensesAccounts = this.accountsByParentType.get(
ACCOUNT_PARENT_TYPE.EXPENSE,
);
const expensesAccountsIds = expensesAccounts.map((a) => a.id);
this.expenseAccounts = expensesAccounts;
this.expenseAccountsIds = expensesAccountsIds;
};
/**
* Initialize the income total ledger.
*/
public initIncomeTotalLedger = (): void => {
// Inject to the repository.
this.incomeLedger = this.totalAccountsLedger.whereAccountsIds(
this.incomeAccountsIds,
);
};
/**
* Initialize the expenses total ledger.
*/
public initExpensesTotalLedger = (): void => {
this.expensesLedger = this.totalAccountsLedger.whereAccountsIds(
this.expenseAccountsIds,
);
};
// ----------------------------
// # Net Income - Date Periods
// ----------------------------
/**
* Initialize the net income date periods.
*/
public initNetIncomeDatePeriods = () => {
this.incomePeriodsAccountsLedger =
this.periodsAccountsLedger.whereAccountsIds(this.incomeAccountsIds);
this.incomePeriodsOpeningAccountsLedger =
this.periodsOpeningAccountLedger.whereAccountsIds(
this.incomeAccountsIds,
);
this.expensesPeriodsAccountsLedger =
this.periodsAccountsLedger.whereAccountsIds(this.expenseAccountsIds);
this.expensesOpeningAccountLedger =
this.periodsOpeningAccountLedger.whereAccountsIds(
this.expenseAccountsIds,
);
};
// ----------------------------
// # Net Income - Previous Period
// ----------------------------
/**
* Initialize the total net income PP.
*/
public initNetIncomePreviousPeriod = () => {
this.incomePPAccountsLedger = this.PPTotalAccountsLedger.whereAccountsIds(
this.incomeAccountsIds,
);
this.expensePPAccountsLedger =
this.PPTotalAccountsLedger.whereAccountsIds(this.expenseAccountsIds);
};
/**
* Initialize the net income periods of previous period.
*/
public initNetIncomePeriodsPreviousPeriod = () => {
this.incomePPPeriodsAccountsLedger =
this.PPPeriodsAccountsLedger.whereAccountsIds(this.incomeAccountsIds);
this.incomePPPeriodsOpeningAccountLedger =
this.PPPeriodsOpeningAccountLedger.whereAccountsIds(
this.incomeAccountsIds,
);
this.expensePPPeriodsAccountsLedger =
this.PPPeriodsAccountsLedger.whereAccountsIds(this.expenseAccountsIds);
this.expensePPPeriodsOpeningAccountLedger =
this.PPPeriodsOpeningAccountLedger.whereAccountsIds(
this.expenseAccountsIds,
);
};
// ----------------------------
// # Net Income - Previous Year
// ----------------------------
/**
* Initialize the net income PY total.
*/
public initNetIncomePreviousYear = () => {
this.incomePYTotalAccountsLedger =
this.PYTotalAccountsLedger.whereAccountsIds(this.incomeAccountsIds);
this.expensePYTotalAccountsLedger =
this.PYTotalAccountsLedger.whereAccountsIds(this.expenseAccountsIds);
};
/**
* Initialize the net income PY periods.
*/
public initNetIncomePeriodsPreviewYear = () => {
this.incomePYPeriodsAccountsLedger =
this.PYPeriodsAccountsLedger.whereAccountsIds(this.incomeAccountsIds);
this.incomePYPeriodsOpeningAccountLedger =
this.PYPeriodsOpeningAccountLedger.whereAccountsIds(
this.incomeAccountsIds,
);
this.expensePYPeriodsAccountsLedger =
this.PYPeriodsAccountsLedger.whereAccountsIds(this.expenseAccountsIds);
this.expensePYPeriodsOpeningAccountLedger =
this.PYPeriodsOpeningAccountLedger.whereAccountsIds(
this.expenseAccountsIds,
);
};
};

View File

@@ -0,0 +1,132 @@
/* eslint-disable import/prefer-default-export */
import * as R from 'ramda';
import {
BALANCE_SHEET_SCHEMA_NODE_ID,
BALANCE_SHEET_SCHEMA_NODE_TYPE,
} from './BalanceSheet.types';
import { FinancialSchema } from '../../common/FinancialSchema';
import { GConstructor } from '@/common/types/Constructor';
import { ACCOUNT_TYPE } from '@/constants/accounts';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetSchema = <T extends GConstructor<FinancialSheet>>(
Base: T,
) =>
class extends R.pipe(FinancialSchema)(Base) {
/**
* Retrieves the balance sheet schema.
* @returns
*/
getSchema = () => {
return getBalanceSheetSchema();
};
};
/**
* Retrieve the balance sheet report schema.
*/
export const getBalanceSheetSchema = () => [
{
name: 'balance_sheet.assets',
id: BALANCE_SHEET_SCHEMA_NODE_ID.ASSETS,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
children: [
{
name: 'balance_sheet.current_asset',
id: BALANCE_SHEET_SCHEMA_NODE_ID.CURRENT_ASSETS,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
children: [
{
name: 'balance_sheet.cash_and_cash_equivalents',
id: BALANCE_SHEET_SCHEMA_NODE_ID.CASH_EQUIVALENTS,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.CASH, ACCOUNT_TYPE.BANK],
},
{
name: 'balance_sheet.accounts_receivable',
id: BALANCE_SHEET_SCHEMA_NODE_ID.ACCOUNTS_RECEIVABLE,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE],
},
{
name: 'balance_sheet.inventory',
id: BALANCE_SHEET_SCHEMA_NODE_ID.INVENTORY,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.INVENTORY],
},
{
name: 'balance_sheet.other_current_assets',
id: BALANCE_SHEET_SCHEMA_NODE_ID.OTHER_CURRENT_ASSET,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.OTHER_CURRENT_ASSET],
},
],
alwaysShow: true,
},
{
name: 'balance_sheet.fixed_asset',
id: BALANCE_SHEET_SCHEMA_NODE_ID.FIXED_ASSET,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.FIXED_ASSET],
},
{
name: 'balance_sheet.non_current_assets',
id: BALANCE_SHEET_SCHEMA_NODE_ID.NON_CURRENT_ASSET,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.NON_CURRENT_ASSET],
},
],
alwaysShow: true,
},
{
name: 'balance_sheet.liabilities_and_equity',
id: BALANCE_SHEET_SCHEMA_NODE_ID.LIABILITY_EQUITY,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
children: [
{
name: 'balance_sheet.liabilities',
id: BALANCE_SHEET_SCHEMA_NODE_ID.LIABILITY,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
children: [
{
name: 'balance_sheet.current_liabilties',
id: BALANCE_SHEET_SCHEMA_NODE_ID.CURRENT_LIABILITY,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [
ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
ACCOUNT_TYPE.TAX_PAYABLE,
ACCOUNT_TYPE.CREDIT_CARD,
ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
],
},
{
name: 'balance_sheet.long_term_liabilities',
id: BALANCE_SHEET_SCHEMA_NODE_ID.LOGN_TERM_LIABILITY,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.LOGN_TERM_LIABILITY],
},
{
name: 'balance_sheet.non_current_liabilities',
id: BALANCE_SHEET_SCHEMA_NODE_ID.NON_CURRENT_LIABILITY,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.NON_CURRENT_LIABILITY],
},
],
},
{
name: 'balance_sheet.equity',
id: BALANCE_SHEET_SCHEMA_NODE_ID.EQUITY,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
accountsTypes: [ACCOUNT_TYPE.EQUITY],
children: [
{
name: 'balance_sheet.net_income',
id: BALANCE_SHEET_SCHEMA_NODE_ID.NET_INCOME,
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME,
},
],
},
],
alwaysShow: true,
},
];

View File

@@ -0,0 +1,282 @@
import * as R from 'ramda';
import {
IBalanceSheetStatementData,
IBalanceSheetQuery,
BALANCE_SHEET_SCHEMA_NODE_TYPE,
IBalanceSheetDataNode,
IBalanceSheetSchemaNode,
IBalanceSheetNetIncomeNode,
IBalanceSheetAccountNode,
IBalanceSheetAccountsNode,
IBalanceSheetAggregateNode,
} from './BalanceSheet.types';
import {
ITableColumnAccessor,
ITableColumn,
ITableRow,
} from '../../types/Table.types';
import { tableRowMapper } from '../../utils/Table.utils';
import { FinancialSheet } from '../../common/FinancialSheet';
import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear';
import { IROW_TYPE, DISPLAY_COLUMNS_BY } from './constants';
import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod';
import { BalanceSheetPercentage } from './BalanceSheetPercentage';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
import { BalanceSheetBase } from './BalanceSheetBase';
import { BalanceSheetTablePercentage } from './BalanceSheetTablePercentage';
import { BalanceSheetTablePreviousYear } from './BalanceSheetTablePreviousYear';
import { BalanceSheetTablePreviousPeriod } from './BalanceSheetTablePreviousPeriod';
import { FinancialTable } from '../../common/FinancialTable';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { BalanceSheetTableDatePeriods } from './BalanceSheetTableDatePeriods';
export class BalanceSheetTable extends R.pipe(
BalanceSheetBase,
FinancialTable,
FinancialSheetStructure,
BalanceSheetPercentage,
BalanceSheetComparsionPreviousPeriod,
BalanceSheetComparsionPreviousYear,
BalanceSheetTablePercentage,
BalanceSheetTableDatePeriods,
BalanceSheetTablePreviousYear,
BalanceSheetTablePreviousPeriod,
)(FinancialSheet) {
/**
* Balance sheet data.
* @param {IBalanceSheetStatementData}
*/
public reportData: IBalanceSheetStatementData;
/**
* Balance sheet query.
* @parma {BalanceSheetQuery}
*/
public query: BalanceSheetQuery;
/**
* Constructor method.
* @param {IBalanceSheetStatementData} reportData -
* @param {IBalanceSheetQuery} query -
*/
constructor(
reportData: IBalanceSheetStatementData,
query: IBalanceSheetQuery,
i18n: any,
) {
super();
this.reportData = reportData;
this.query = new BalanceSheetQuery(query);
this.i18n = i18n;
}
/**
* Detarmines the node type of the given schema node.
* @param {IBalanceSheetStructureSection} node -
* @param {string} type -
* @return {boolean}
*/
public isNodeType = R.curry(
(type: string, node: IBalanceSheetSchemaNode): boolean => {
return node.nodeType === type;
},
);
// -------------------------
// # Accessors.
// -------------------------
/**
* Retrieve the common columns for all report nodes.
* @param {ITableColumnAccessor[]}
*/
public commonColumnsAccessors = (): ITableColumnAccessor[] => {
return R.compose(
R.concat([{ key: 'name', accessor: 'name' }]),
R.ifElse(
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
R.concat(this.datePeriodsColumnsAccessors()),
R.concat(this.totalColumnAccessor()),
),
)([]);
};
/**
* Retrieve the total column accessor.
* @return {ITableColumnAccessor[]}
*/
public totalColumnAccessor = (): ITableColumnAccessor[] => {
return R.pipe(
R.concat(this.previousPeriodColumnAccessor()),
R.concat(this.previousYearColumnAccessor()),
R.concat(this.percentageColumnsAccessor()),
R.concat([{ key: 'total', accessor: 'total.formattedAmount' }]),
)([]);
};
/**
* Retrieves the table row from the given report aggregate node.
* @param {IBalanceSheetAggregateNode} node
* @returns {ITableRow}
*/
public aggregateNodeTableRowsMapper = (
node: IBalanceSheetAggregateNode,
): ITableRow => {
const columns = this.commonColumnsAccessors();
const meta = {
rowTypes: [IROW_TYPE.AGGREGATE],
id: node.id,
};
return tableRowMapper(node, columns, meta);
};
/**
* Retrieves the table row from the given report accounts node.
* @param {IBalanceSheetAccountsNode} node
* @returns {ITableRow}
*/
public accountsNodeTableRowsMapper = (
node: IBalanceSheetAccountsNode,
): ITableRow => {
const columns = this.commonColumnsAccessors();
const meta = {
rowTypes: [IROW_TYPE.ACCOUNTS],
id: node.id,
};
return tableRowMapper(node, columns, meta);
};
/**
* Retrieves the table row from the given report account node.
* @param {IBalanceSheetAccountNode} node
* @returns {ITableRow}
*/
public accountNodeTableRowsMapper = (
node: IBalanceSheetAccountNode,
): ITableRow => {
const columns = this.commonColumnsAccessors();
const meta = {
rowTypes: [IROW_TYPE.ACCOUNT],
id: node.id,
};
return tableRowMapper(node, columns, meta);
};
/**
* Retrieves the table row from the given report net income node.
* @param {IBalanceSheetNetIncomeNode} node
* @returns {ITableRow}
*/
public netIncomeNodeTableRowsMapper = (
node: IBalanceSheetNetIncomeNode,
): ITableRow => {
const columns = this.commonColumnsAccessors();
const meta = {
rowTypes: [IROW_TYPE.NET_INCOME],
id: node.id,
};
return tableRowMapper(node, columns, meta);
};
/**
* Mappes the given report node to table rows.
* @param {IBalanceSheetDataNode} node -
* @returns {ITableRow}
*/
public nodeToTableRowsMapper = (node: IBalanceSheetDataNode): ITableRow => {
return R.cond([
[
this.isNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE),
this.aggregateNodeTableRowsMapper,
],
[
this.isNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS),
this.accountsNodeTableRowsMapper,
],
[
this.isNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNT),
this.accountNodeTableRowsMapper,
],
[
this.isNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.NET_INCOME),
this.netIncomeNodeTableRowsMapper,
],
])(node);
};
/**
* Mappes the given report sections to table rows.
* @param {IBalanceSheetDataNode[]} nodes -
* @return {ITableRow}
*/
public nodesToTableRowsMapper = (
nodes: IBalanceSheetDataNode[],
): ITableRow[] => {
return this.mapNodesDeep(nodes, this.nodeToTableRowsMapper);
};
/**
* Retrieves the total children columns.
* @returns {ITableColumn[]}
*/
public totalColumnChildren = (): ITableColumn[] => {
return R.compose(
R.unless(
R.isEmpty,
R.concat([
{ key: 'total', label: this.i18n.__('balance_sheet.total') },
]),
),
R.concat(this.percentageColumns()),
R.concat(this.getPreviousYearColumns()),
R.concat(this.previousPeriodColumns()),
)([]);
};
/**
* Retrieve the total column.
* @returns {ITableColumn[]}
*/
public totalColumn = (): ITableColumn[] => {
return [
{
key: 'total',
label: this.i18n.__('balance_sheet.total'),
children: this.totalColumnChildren(),
},
];
};
/**
* Retrieve the report table rows.
* @returns {ITableRow[]}
*/
public tableRows = (): ITableRow[] => {
return R.compose(
this.addTotalRows,
this.nodesToTableRowsMapper,
)(this.reportData);
};
// -------------------------
// # Columns.
// -------------------------
/**
* Retrieve the report table columns.
* @returns {ITableColumn[]}
*/
public tableColumns = (): ITableColumn[] => {
return R.compose(
this.tableColumnsCellIndexing,
R.concat([
{ key: 'name', label: this.i18n.__('balance_sheet.account_name') },
]),
R.ifElse(
this.query.isDatePeriodsColumnsType,
R.concat(this.datePeriodsColumns()),
R.concat(this.totalColumn()),
),
)([]);
};
}

View File

@@ -0,0 +1,139 @@
import * as R from 'ramda';
import * as moment from 'moment';
import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types';
import { FinancialDatePeriods } from '../../common/FinancialDatePeriods';
import { IDateRange } from '../CashFlow/Cashflow.types';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetTableDatePeriods = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class extends R.pipe(FinancialDatePeriods)(Base) {
/**
* Retrieves the date periods based on the report query.
* @returns {IDateRange[]}
*/
get datePeriods() {
return this.getDateRanges(
this.query.fromDate,
this.query.toDate,
this.query.displayColumnsBy,
);
}
/**
* Retrieve the formatted column label from the given date range.
* @param {ICashFlowDateRange} dateRange -
* @return {string}
*/
public 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.query.isDisplayColumnsBy(type)),
formatFn,
],
conditions,
);
return R.compose(R.cond(conditionsPairs))(dateRange);
};
// -------------------------
// # Accessors.
// -------------------------
/**
* Date period columns accessor.
* @param {IDateRange} dateRange -
* @param {number} index -
*/
public datePeriodColumnsAccessor = R.curry(
(dateRange: IDateRange, index: number) => {
return R.pipe(
R.concat(this.previousPeriodHorizColumnAccessors(index)),
R.concat(this.previousYearHorizontalColumnAccessors(index)),
R.concat(this.percetangeDatePeriodColumnsAccessor(index)),
R.concat([
{
key: `date-range-${index}`,
accessor: `horizontalTotals[${index}].total.formattedAmount`,
},
]),
)([]);
},
);
/**
* Retrieve the date periods columns accessors.
* @returns {ITableColumnAccessor[]}
*/
public datePeriodsColumnsAccessors = (): ITableColumnAccessor[] => {
return R.compose(
R.flatten,
R.addIndex(R.map)(this.datePeriodColumnsAccessor),
)(this.datePeriods);
};
// -------------------------
// # Columns.
// -------------------------
/**
*
* @param {number} index
* @param {} dateRange
* @returns {}
*/
public datePeriodChildrenColumns = (
index: number,
dateRange: IDateRange,
) => {
return R.compose(
R.unless(
R.isEmpty,
R.concat([
{ key: `total`, label: this.i18n.__('balance_sheet.total') },
]),
),
R.concat(this.percentageColumns()),
R.concat(this.getPreviousYearHorizontalColumns(dateRange)),
R.concat(this.previousPeriodHorizontalColumns(dateRange)),
)([]);
};
/**
*
* @param dateRange
* @param index
* @returns
*/
public datePeriodColumn = (
dateRange: IDateRange,
index: number,
): ITableColumn => {
return {
key: `date-range-${index}`,
label: this.formatColumnLabel(dateRange),
children: this.datePeriodChildrenColumns(index, dateRange),
};
};
/**
* Date periods columns.
* @returns {ITableColumn[]}
*/
public datePeriodsColumns = (): ITableColumn[] => {
return this.datePeriods.map(this.datePeriodColumn);
};
};

View File

@@ -0,0 +1,34 @@
import { Injectable } from '@nestjs/common';
import { I18nService } from 'nestjs-i18n';
import { BalanceSheetInjectable } from './BalanceSheetInjectable';
import { BalanceSheetTable } from './BalanceSheetTable';
import { IBalanceSheetQuery, IBalanceSheetTable } from './BalanceSheet.types';
@Injectable()
export class BalanceSheetTableInjectable {
constructor(
private readonly balanceSheetService: BalanceSheetInjectable,
private readonly i18nService: I18nService,
) {}
/**
* Retrieves the balance sheet in table format.
* @param {number} query -
* @returns {Promise<IBalanceSheetTable>}
*/
public async table(filter: IBalanceSheetQuery): Promise<IBalanceSheetTable> {
const { data, query, meta } =
await this.balanceSheetService.balanceSheet(filter);
const table = new BalanceSheetTable(data, query, this.i18nService);
return {
table: {
columns: table.tableColumns(),
rows: table.tableRows(),
},
query,
meta,
};
}
}

View File

@@ -0,0 +1,89 @@
import * as R from 'ramda';
import { ITableColumn } from '../../types/Table.types';
import { Constructor } from '@/common/types/Constructor';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { I18nService } from 'nestjs-i18n';
export const BalanceSheetTablePercentage = <T extends Constructor>(Base: T) =>
class BalanceSheetComparsionPreviousYear extends Base {
public readonly query: BalanceSheetQuery;
public readonly i18n: I18nService;
// --------------------
// # Columns
// --------------------
/**
* Retrieve percentage of column/row columns.
* @returns {ITableColumn[]}
*/
public percentageColumns = (): ITableColumn[] => {
return R.pipe(
R.when(
this.query.isColumnsPercentageActive,
R.append({
key: 'percentage_of_column',
label: this.i18n.t('balance_sheet.percentage_of_column'),
})
),
R.when(
this.query.isRowsPercentageActive,
R.append({
key: 'percentage_of_row',
label: this.i18n.t('balance_sheet.percentage_of_row'),
})
)
)([]);
};
// --------------------
// # Accessors
// --------------------
/**
* Retrieves percentage of column/row accessors.
* @returns {ITableColumn[]}
*/
public percentageColumnsAccessor = (): ITableColumn[] => {
return R.pipe(
R.when(
this.query.isColumnsPercentageActive,
R.append({
key: 'percentage_of_column',
accessor: 'percentageColumn.formattedAmount',
})
),
R.when(
this.query.isRowsPercentageActive,
R.append({
key: 'percentage_of_row',
accessor: 'percentageRow.formattedAmount',
})
)
)([]);
};
/**
* Percentage columns accessors for date period columns.
* @param {number} index
* @returns {ITableColumn[]}
*/
public percetangeDatePeriodColumnsAccessor = (
index: number
): ITableColumn[] => {
return R.pipe(
R.when(
this.query.isColumnsPercentageActive,
R.append({
key: `percentage_of_column-${index}`,
accessor: `horizontalTotals[${index}].percentageColumn.formattedAmount`,
})
),
R.when(
this.query.isRowsPercentageActive,
R.append({
key: `percentage_of_row-${index}`,
accessor: `horizontalTotals[${index}].percentageRow.formattedAmount`,
})
)
)([]);
};
};

View File

@@ -0,0 +1,114 @@
import * as R from 'ramda';
import { BalanceSheetQuery } from './BalanceSheetQuery';
import { FinancialTablePreviousPeriod } from '../../common/FinancialTablePreviousPeriod';
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
import { IDateRange } from '../../types/Report.types';
import { ITableColumn } from '../../types/Table.types';
import { GConstructor } from '@/common/types/Constructor';
import { FinancialSheet } from '../../common/FinancialSheet';
export const BalanceSheetTablePreviousPeriod = <
T extends GConstructor<FinancialSheet>,
>(
Base: T,
) =>
class BalanceSheetTablePreviousPeriod extends R.pipe(
FinancialTablePreviousPeriod,
FinancialDateRanges,
)(Base) {
readonly query: BalanceSheetQuery;
// --------------------
// # Columns
// --------------------
/**
* Retrieves the previous period columns.
* @returns {ITableColumn[]}
*/
public previousPeriodColumns = (dateRange?: IDateRange): ITableColumn[] => {
return R.pipe(
// Previous period columns.
R.when(
this.query.isPreviousPeriodActive,
R.append(this.getPreviousPeriodTotalColumn(dateRange)),
),
R.when(
this.query.isPreviousPeriodChangeActive,
R.append(this.getPreviousPeriodChangeColumn()),
),
R.when(
this.query.isPreviousPeriodPercentageActive,
R.append(this.getPreviousPeriodPercentageColumn()),
),
)([]);
};
/**
* Previous period for date periods
* @param {IDateRange} dateRange
* @returns {ITableColumn}
*/
public previousPeriodHorizontalColumns = (
dateRange: IDateRange,
): ITableColumn[] => {
const PPDateRange = this.getPPDatePeriodDateRange(
dateRange.fromDate,
dateRange.toDate,
this.query.displayColumnsBy,
);
return this.previousPeriodColumns({
fromDate: PPDateRange.fromDate,
toDate: PPDateRange.toDate,
});
};
// --------------------
// # Accessors
// --------------------
/**
* Retrieves previous period columns accessors.
* @returns {ITableColumn[]}
*/
public previousPeriodColumnAccessor = (): ITableColumn[] => {
return R.pipe(
// Previous period columns.
R.when(
this.query.isPreviousPeriodActive,
R.append(this.getPreviousPeriodTotalAccessor()),
),
R.when(
this.query.isPreviousPeriodChangeActive,
R.append(this.getPreviousPeriodChangeAccessor()),
),
R.when(
this.query.isPreviousPeriodPercentageActive,
R.append(this.getPreviousPeriodPercentageAccessor()),
),
)([]);
};
/**
*
* @param {number} index
* @returns
*/
public previousPeriodHorizColumnAccessors = (
index: number,
): ITableColumn[] => {
return R.pipe(
// Previous period columns.
R.when(
this.query.isPreviousPeriodActive,
R.append(this.getPreviousPeriodTotalHorizAccessor(index)),
),
R.when(
this.query.isPreviousPeriodChangeActive,
R.append(this.getPreviousPeriodChangeHorizAccessor(index)),
),
R.when(
this.query.isPreviousPeriodPercentageActive,
R.append(this.getPreviousPeriodPercentageHorizAccessor(index)),
),
)([]);
};
};

View File

@@ -0,0 +1,99 @@
import * as R from 'ramda';
import { IDateRange } from '../../types/Report.types';
import { ITableColumn } from '../../types/Table.types';
import { FinancialTablePreviousYear } from '../../common/FinancialTablePreviousYear';
import { FinancialDateRanges } from '../../common/FinancialDateRanges';
import { Constructor } from '@/common/types/Constructor';
export const BalanceSheetTablePreviousYear = <T extends Constructor>(Base: T) =>
class extends R.pipe(FinancialTablePreviousYear, FinancialDateRanges)(Base) {
// --------------------
// # Columns.
// --------------------
/**
* Retrieves pervious year comparison columns.
* @returns {ITableColumn[]}
*/
public getPreviousYearColumns = (
dateRange?: IDateRange,
): ITableColumn[] => {
return R.pipe(
// Previous year columns.
R.when(
this.query.isPreviousYearActive,
R.append(this.getPreviousYearTotalColumn(dateRange)),
),
R.when(
this.query.isPreviousYearChangeActive,
R.append(this.getPreviousYearChangeColumn()),
),
R.when(
this.query.isPreviousYearPercentageActive,
R.append(this.getPreviousYearPercentageColumn()),
),
)([]);
};
/**
*
* @param {IDateRange} dateRange
* @returns
*/
public getPreviousYearHorizontalColumns = (dateRange: IDateRange) => {
const PYDateRange = this.getPreviousYearDateRange(
dateRange.fromDate,
dateRange.toDate,
);
return this.getPreviousYearColumns(PYDateRange);
};
// --------------------
// # Accessors.
// --------------------
/**
* Retrieves previous year columns accessors.
* @returns {ITableColumn[]}
*/
public previousYearColumnAccessor = (): ITableColumn[] => {
return R.pipe(
// Previous year columns.
R.when(
this.query.isPreviousYearActive,
R.append(this.getPreviousYearTotalAccessor()),
),
R.when(
this.query.isPreviousYearChangeActive,
R.append(this.getPreviousYearChangeAccessor()),
),
R.when(
this.query.isPreviousYearPercentageActive,
R.append(this.getPreviousYearPercentageAccessor()),
),
)([]);
};
/**
* Previous year period column accessor.
* @param {number} index
* @returns {ITableColumn[]}
*/
public previousYearHorizontalColumnAccessors = (
index: number,
): ITableColumn[] => {
return R.pipe(
// Previous year columns.
R.when(
this.query.isPreviousYearActive,
R.append(this.getPreviousYearTotalHorizAccessor(index)),
),
R.when(
this.query.isPreviousYearChangeActive,
R.append(this.getPreviousYearChangeHorizAccessor(index)),
),
R.when(
this.query.isPreviousYearPercentageActive,
R.append(this.getPreviousYearPercentageHorizAccessor(index)),
),
)([]);
};
};

View File

@@ -0,0 +1,3 @@
import * as R from 'ramda';
export const BalanceSheetTotal = (Base: any) => class extends Base {};

View File

@@ -0,0 +1,100 @@
import { IBalanceSheetQuery } from "./BalanceSheet.types";
export const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' };
export const DISPLAY_COLUMNS_BY = {
DATE_PERIODS: 'date_periods',
TOTAL: 'total',
};
export enum IROW_TYPE {
AGGREGATE = 'AGGREGATE',
ACCOUNTS = 'ACCOUNTS',
ACCOUNT = 'ACCOUNT',
NET_INCOME = 'NET_INCOME',
TOTAL = 'TOTAL',
}
export const HtmlTableCustomCss = `
table tr.row-type--total td {
font-weight: 600;
border-top: 1px solid #bbb;
color: #000;
}
table tr.row-type--total.row-id--assets td,
table tr.row-type--total.row-id--liability-equity td {
border-bottom: 3px double #000;
}
table .column--name,
table .cell--name {
width: 400px;
}
table .column--total {
width: 25%;
}
table td.cell--total,
table td.cell--previous_year,
table td.cell--previous_year_change,
table td.cell--previous_year_percentage,
table td.cell--previous_period,
table td.cell--previous_period_change,
table td.cell--previous_period_percentage,
table td.cell--percentage_of_row,
table td.cell--percentage_of_column,
table td[class*="cell--date-range"] {
text-align: right;
}
table .column--total,
table .column--previous_year,
table .column--previous_year_change,
table .column--previous_year_percentage,
table .column--previous_period,
table .column--previous_period_change,
table .column--previous_period_percentage,
table .column--percentage_of_row,
table .column--percentage_of_column,
table [class*="column--date-range"] {
text-align: right;
}
`;
export const getBalanceSheetDefaultQuery = (): IBalanceSheetQuery => {
return {
displayColumnsType: 'total',
displayColumnsBy: 'month',
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',
accountIds: [],
percentageOfColumn: false,
percentageOfRow: false,
previousPeriod: false,
previousPeriodAmountChange: false,
previousPeriodPercentageChange: false,
previousYear: false,
previousYearAmountChange: false,
previousYearPercentageChange: false,
};
}