From 2017539032d1c1cd583d853031d390d080a3abba Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 4 Feb 2025 13:17:25 +0200 Subject: [PATCH] refactor: nestjs --- .../subscribers/ExpenseGLEntries.service.ts | 2 +- .../FinancialStatements.module.ts | 4 + .../common/FinancialDatePeriods.ts | 3 +- .../common/FinancialDateRanges.ts | 38 +- .../common/FinancialEvaluateEquation.ts | 2 +- .../common/FinancialFilter.ts | 15 +- .../common/FinancialHorizTotals.ts | 4 +- .../common/FinancialPreviousPeriod.ts | 7 +- .../common/FinancialSchema.ts | 4 +- .../common/FinancialSheetStructure.ts | 19 +- .../BalanceSheet/BalanceSheetAggregators.ts | 2 +- .../modules/BalanceSheet/BalanceSheetBase.ts | 8 +- .../BalanceSheet/BalanceSheetNetIncome.ts | 4 +- .../BalanceSheet/BalanceSheetSchema.ts | 3 +- .../BalanceSheetTablePercentage.ts | 33 +- .../BalanceSheetTablePreviousYear.ts | 9 +- .../modules/CashFlowStatement}/CashFlow.ts | 192 ++--- .../CashFlowStatement}/CashFlowDatePeriods.ts | 82 +- .../CashFlowStatement/CashFlowRepository.ts | 8 +- .../CashFlowStatement/CashFlowService.ts | 8 +- .../CashFlowStatement}/CashFlowTable.ts | 17 +- .../CashFlowStatement/Cashflow.controller.ts | 4 +- .../CashFlowStatement}/Cashflow.types.ts | 0 .../CashflowExportInjectable.ts | 0 .../CashflowSheetApplication.ts | 0 .../CashFlowStatement/CashflowSheetMeta.ts | 2 +- .../CashflowStatement.module.ts | 4 +- .../CashflowStatementBase.ts | 46 ++ .../CashflowTableInjectable.ts | 1 + .../CashflowTablePdfInjectable.ts | 0 .../modules/CashFlowStatement}/constants.ts | 1 + .../modules/CashFlowStatement}/schema.ts | 0 .../JournalSheet/JournalSheetApplication.ts | 2 +- .../ProfitLossSheet/ProfitLossSchema.ts | 80 ++ .../ProfitLossSheet.controller.ts | 65 ++ .../ProfitLossSheet/ProfitLossSheet.module.ts | 28 + .../ProfitLossSheet/ProfitLossSheet.ts | 341 +++++++++ .../ProfitLossSheet/ProfitLossSheet.types.ts | 195 +++++ .../ProfitLossSheetApplication.ts | 64 ++ .../ProfitLossSheet/ProfitLossSheetBase.ts | 42 ++ .../ProfitLossSheetDatePeriods.ts | 249 +++++++ .../ProfitLossSheetExportInjectable.ts | 40 + .../ProfitLossSheet/ProfitLossSheetFilter.ts | 179 +++++ .../ProfitLossSheet/ProfitLossSheetMeta.ts | 36 + .../ProfitLossSheetPercentage.ts | 310 ++++++++ .../ProfitLossSheetPreviousPeriod.ts | 404 ++++++++++ .../ProfitLossSheetPreviousYear.ts | 376 ++++++++++ .../ProfitLossSheet/ProfitLossSheetQuery.ts | 210 ++++++ .../ProfitLossSheetRepository.ts | 380 ++++++++++ .../ProfitLossSheet/ProfitLossSheetService.ts | 67 ++ .../ProfitLossSheet/ProfitLossSheetTable.ts | 238 ++++++ .../ProfitLossSheetTableDatePeriods.ts | 158 ++++ .../ProfitLossSheetTableInjectable.ts | 39 + .../ProfitLossSheetTablePercentage.ts | 141 ++++ .../ProfitLossTablePdfInjectable.ts | 29 + .../ProfitLossTablePreviousPeriod.ts | 101 +++ .../ProfitLossTablePreviousYear.ts | 112 +++ .../modules/ProfitLossSheet/constants.ts | 69 ++ .../modules/ProfitLossSheet/utils.ts | 54 ++ packages/server-nest/tsconfig.build.json | 5 +- .../CreatePaymentReceivedStripePayment.ts | 16 +- temp/CashFlow/CashFlowRepository.ts | 165 ---- temp/CashFlow/CashFlowService.ts | 109 --- temp/CashFlow/Cashflow.controller.ts | 59 -- temp/CashFlow/Cashflow.module.ts | 27 - temp/CashFlow/CashflowSheetApplication.ts | 60 -- temp/CashFlow/CashflowSheetMeta.ts | 36 - temp/CashFlowStatement/CashFlow.ts | 705 ------------------ temp/CashFlowStatement/CashFlowDatePeriods.ts | 412 ---------- temp/CashFlowStatement/CashFlowTable.ts | 374 ---------- temp/CashFlowStatement/Cashflow.types.ts | 299 -------- .../CashflowExportInjectable.ts | 37 - .../CashflowTableInjectable.ts | 36 - .../CashflowTablePdfInjectable.ts | 28 - temp/CashFlowStatement/constants.ts | 52 -- temp/CashFlowStatement/schema.ts | 77 -- 76 files changed, 4294 insertions(+), 2734 deletions(-) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/CashFlow.ts (84%) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/CashFlowDatePeriods.ts (89%) rename {temp => packages/server-nest/src/modules/FinancialStatements/modules}/CashFlowStatement/CashFlowRepository.ts (96%) rename {temp => packages/server-nest/src/modules/FinancialStatements/modules}/CashFlowStatement/CashFlowService.ts (94%) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/CashFlowTable.ts (95%) rename {temp => packages/server-nest/src/modules/FinancialStatements/modules}/CashFlowStatement/Cashflow.controller.ts (94%) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/Cashflow.types.ts (100%) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/CashflowExportInjectable.ts (100%) rename {temp => packages/server-nest/src/modules/FinancialStatements/modules}/CashFlowStatement/CashflowSheetApplication.ts (100%) rename {temp => packages/server-nest/src/modules/FinancialStatements/modules}/CashFlowStatement/CashflowSheetMeta.ts (96%) rename temp/CashFlowStatement/Cashflow.module.ts => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatement.module.ts (94%) create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatementBase.ts rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/CashflowTableInjectable.ts (95%) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/CashflowTablePdfInjectable.ts (100%) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/constants.ts (97%) rename {temp/CashFlow => packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement}/schema.ts (100%) create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSchema.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.module.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.types.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetApplication.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetBase.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetDatePeriods.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetExportInjectable.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetFilter.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetMeta.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPercentage.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousPeriod.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousYear.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetQuery.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetRepository.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetService.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTable.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableDatePeriods.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableInjectable.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTablePercentage.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePdfInjectable.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousPeriod.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousYear.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/constants.ts create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/utils.ts delete mode 100644 temp/CashFlow/CashFlowRepository.ts delete mode 100644 temp/CashFlow/CashFlowService.ts delete mode 100644 temp/CashFlow/Cashflow.controller.ts delete mode 100644 temp/CashFlow/Cashflow.module.ts delete mode 100644 temp/CashFlow/CashflowSheetApplication.ts delete mode 100644 temp/CashFlow/CashflowSheetMeta.ts delete mode 100644 temp/CashFlowStatement/CashFlow.ts delete mode 100644 temp/CashFlowStatement/CashFlowDatePeriods.ts delete mode 100644 temp/CashFlowStatement/CashFlowTable.ts delete mode 100644 temp/CashFlowStatement/Cashflow.types.ts delete mode 100644 temp/CashFlowStatement/CashflowExportInjectable.ts delete mode 100644 temp/CashFlowStatement/CashflowTableInjectable.ts delete mode 100644 temp/CashFlowStatement/CashflowTablePdfInjectable.ts delete mode 100644 temp/CashFlowStatement/constants.ts delete mode 100644 temp/CashFlowStatement/schema.ts diff --git a/packages/server-nest/src/modules/Expenses/subscribers/ExpenseGLEntries.service.ts b/packages/server-nest/src/modules/Expenses/subscribers/ExpenseGLEntries.service.ts index 5cb397241..3048607c6 100644 --- a/packages/server-nest/src/modules/Expenses/subscribers/ExpenseGLEntries.service.ts +++ b/packages/server-nest/src/modules/Expenses/subscribers/ExpenseGLEntries.service.ts @@ -17,7 +17,7 @@ export class ExpenseGLEntriesService { /** * Retrieves the expense G/L of the given id. * @param {number} expenseId - * @param {Knex.Transaction} trx + * @param {Knex.Transaction} trx - Knex transaction. * @returns {Promise} */ public getExpenseLedgerById = async ( diff --git a/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts b/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts index 3303cbb25..9a1c256b5 100644 --- a/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts +++ b/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts @@ -13,6 +13,8 @@ import { InventoryItemDetailsModule } from './modules/InventoryItemDetails/Inven import { InventoryValuationSheetModule } from './modules/InventoryValuationSheet/InventoryValuationSheet.module'; import { SalesTaxLiabilityModule } from './modules/SalesTaxLiabilitySummary/SalesTaxLiability.module'; import { JournalSheetModule } from './modules/JournalSheet/JournalSheet.module'; +import { ProfitLossSheetModule } from './modules/ProfitLossSheet/ProfitLossSheet.module'; +import { CashflowStatementModule } from './modules/CashFlowStatement/CashflowStatement.module'; @Module({ providers: [], @@ -31,6 +33,8 @@ import { JournalSheetModule } from './modules/JournalSheet/JournalSheet.module'; InventoryValuationSheetModule, SalesTaxLiabilityModule, JournalSheetModule, + ProfitLossSheetModule, + CashflowStatementModule, ], }) export class FinancialStatementsModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialDatePeriods.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialDatePeriods.ts index 55cc9b597..5b9358a9e 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialDatePeriods.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialDatePeriods.ts @@ -9,11 +9,12 @@ import { dateRangeFromToCollection } from '@/utils/date-range-collection'; import { FinancialDateRanges } from './FinancialDateRanges'; import { GConstructor } from '@/common/types/Constructor'; import { FinancialSheet } from './FinancialSheet'; +import { IFinancialSheetTotalPeriod } from '../modules/BalanceSheet/BalanceSheet.types'; export const FinancialDatePeriods = >( Base: T, ) => - class extends R.compose(FinancialDateRanges)(Base) { + class extends R.pipe(FinancialDateRanges)(Base) { /** * Retrieves the date ranges from the given from date to the given to date. * @param {Date} fromDate - diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts index 26a0287e6..8715a96b0 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts @@ -2,6 +2,7 @@ import * as moment from 'moment'; import { IDateRange, IFinancialDatePeriodsUnit } from '../types/Report.types'; import { GConstructor } from '@/common/types/Constructor'; import { FinancialSheet } from './FinancialSheet'; +import { DateInput } from '@/common/types/Date'; export const FinancialDateRanges = >( Base: T, @@ -15,7 +16,7 @@ export const FinancialDateRanges = >( * @returns {Date} */ public getPreviousPeriodDate = ( - date: Date, + date: DateInput, value: number = 1, unit: IFinancialDatePeriodsUnit = IFinancialDatePeriodsUnit.Day, ): Date => { @@ -28,21 +29,21 @@ export const FinancialDateRanges = >( * @param {Date} toDate * @returns {number} */ - public getPreviousPeriodDiff = (fromDate: Date, toDate: Date) => { + public getPreviousPeriodDiff = (fromDate: DateInput, toDate: DateInput) => { return moment(toDate).diff(fromDate, 'days') + 1; }; /** * Retrieves the periods period dates. - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. + * @param {DateInput} fromDate - From date. + * @param {DateInput} toDate - To date. * @param {IFinancialDatePeriodsUnit} unit - Unit of time. * @param {number} amount - Amount of time. * @returns {IDateRange} */ public getPreviousPeriodDateRange = ( - fromDate: Date, - toDate: Date, + fromDate: DateInput, + toDate: DateInput, unit: IFinancialDatePeriodsUnit, amount: number = 1, ): IDateRange => { @@ -54,11 +55,14 @@ export const FinancialDateRanges = >( /** * Retrieves the previous period (PP) date range of total column. - * @param {Date} fromDate - * @param {Date} toDate + * @param {DateInput} fromDate - From date. + * @param {DateInput} toDate - To date. * @returns {IDateRange} */ - public getPPTotalDateRange = (fromDate: Date, toDate: Date): IDateRange => { + public getPPTotalDateRange = ( + fromDate: DateInput, + toDate: DateInput, + ): IDateRange => { const unit = this.getPreviousPeriodDiff(fromDate, toDate); return this.getPreviousPeriodDateRange( @@ -77,8 +81,8 @@ export const FinancialDateRanges = >( * @returns {IDateRange} */ public getPPDatePeriodDateRange = ( - fromDate: Date, - toDate: Date, + fromDate: DateInput, + toDate: DateInput, unit: IFinancialDatePeriodsUnit, ): IDateRange => { return this.getPreviousPeriodDateRange(fromDate, toDate, unit, 1); @@ -89,22 +93,22 @@ export const FinancialDateRanges = >( // ------------------------ /** * Retrieve the previous year of the given date. - * @params {Date} date + * @param {DateInput} date * @returns {Date} */ - getPreviousYearDate = (date: Date) => { + getPreviousYearDate = (date: DateInput) => { return moment(date).subtract(1, 'years').toDate(); }; /** * Retrieves previous year date range. - * @param {Date} fromDate - * @param {Date} toDate + * @param {DateInput} fromDate - From date. + * @param {DateInput} toDate - To date. * @returns {IDateRange} */ public getPreviousYearDateRange = ( - fromDate: Date, - toDate: Date, + fromDate: DateInput, + toDate: DateInput, ): IDateRange => { const PYFromDate = this.getPreviousYearDate(fromDate); const PYToDate = this.getPreviousYearDate(toDate); diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts index d8914d610..ea5a863ac 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts @@ -10,7 +10,7 @@ export const FinancialEvaluateEquation = < >( Base: T ) => - class FinancialEvaluateEquation extends R.compose(FinancialSheetStructure)(Base) { + class FinancialEvaluateEquation extends R.pipe(FinancialSheetStructure)(Base) { /** * Evauluate equaation string with the given scope table. * @param {string} equation - diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialFilter.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialFilter.ts index 169356283..f857acc04 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialFilter.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialFilter.ts @@ -1,12 +1,15 @@ - -import { Constructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; import { isEmpty } from 'lodash'; +import { FinancialSheet } from './FinancialSheet'; +import { IFinancialCommonNode } from '../types/Report.types'; -export const FinancialFilter = (Base: T) => +export const FinancialFilter = >( + Base: T, +) => class extends Base { /** * Detarmines whether the given node has children. - * @param {IBalanceSheetCommonNode} node + * @param {IBalanceSheetCommonNode} node * @returns {boolean} */ public isNodeHasChildren = (node: IFinancialCommonNode): boolean => @@ -17,7 +20,7 @@ export const FinancialFilter = (Base: T) => * @param {IBalanceSheetCommonNode} node * @returns {boolean} */ - public isNodeNoneZero = (node) =>{ + public isNodeNoneZero = (node) => { return node.total.amount !== 0; - } + }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts index 4fce2671b..f8c0058a5 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts @@ -8,7 +8,7 @@ export const FinancialHorizTotals = >( ) => class FinancialHorizTotals extends Base { /** - * + * Associate percentage to the given node. */ public assocNodePercentage = R.curry( (assocPath, parentTotal: number, node: any) => { @@ -25,7 +25,7 @@ export const FinancialHorizTotals = >( ); /** - * + * Associate horizontal percentage total to the given node. * @param {} parentNode - * @param {} horTotalNode - * @param {number} index - diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts index 0876ac10f..c7366e7a7 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts @@ -4,20 +4,21 @@ import { IFinancialNodeWithPreviousPeriod, } from '../types/Report.types'; import * as R from 'ramda'; -import { Constructor, GConstructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; import { FinancialSheet } from './FinancialSheet'; import { FinancialDatePeriods } from './FinancialDatePeriods'; +import { IProfitLossSheetAccountNode } from '../modules/ProfitLossSheet/ProfitLossSheet.types'; export const FinancialPreviousPeriod = >( Base: T, ) => - class extends R.compose(FinancialDatePeriods)(Base) { + class extends R.pipe(FinancialDatePeriods)(Base) { // --------------------------- // # Common Node. // --------------------------- /** * Assoc previous period percentage attribute to account node. - * @param {IProfitLossSheetAccountNode} accountNode + * @param {IProfitLossSheetAccountNode} accountNode * @returns {IFinancialNodeWithPreviousPeriod} */ public assocPreviousPeriodPercentageNode = ( diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts index 732819bc2..bc003b40c 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts @@ -4,9 +4,9 @@ import { GConstructor } from '@/common/types/Constructor'; import { FinancialSheet } from './FinancialSheet'; export const FinancialSchema = >( - Base: T + Base: T, ) => - class FinancialSchema extends R.compose(FinancialSheetStructure)(Base) { + class FinancialSchema extends FinancialSheetStructure(Base) { /** * * @returns diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts index 14ceee3d6..fc1ad82f3 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts @@ -12,10 +12,8 @@ import { import { GConstructor } from '@/common/types/Constructor'; import { FinancialSheet } from './FinancialSheet'; -export const FinancialSheetStructure = < - T extends GConstructor, ->( - Base: T +export const FinancialSheetStructure = >( + Base: T, ) => class FinancialSheetStructure extends Base { /** @@ -30,7 +28,6 @@ export const FinancialSheetStructure = < pathFormat: 'array', }); }; - /** * * @param nodes @@ -43,18 +40,15 @@ export const FinancialSheetStructure = < pathFormat: 'array', }); }; - public mapNodes = (nodes, callback) => { return mapValues(nodes, callback, { childrenPath: 'children', pathFormat: 'array', }); }; - public filterNodesDeep2 = R.curry((predicate, nodes) => { return filterNodesDeep(predicate, nodes); }); - /** * * @param @@ -65,14 +59,12 @@ export const FinancialSheetStructure = < pathFormat: 'array', }); }; - public findNodeDeep = (nodes, callback) => { return findValueDeep(nodes, callback, { childrenPath: 'children', pathFormat: 'array', }); }; - public mapAccNodesDeep = (nodes, callback) => { return reduceDeep( nodes, @@ -80,7 +72,7 @@ export const FinancialSheetStructure = < set( acc, context.path, - callback(value, key, parentValue, acc, context) + callback(value, key, parentValue, acc, context), ); return acc; }, @@ -88,10 +80,9 @@ export const FinancialSheetStructure = < { childrenPath: 'children', pathFormat: 'array', - } + }, ); }; - /** * */ @@ -101,11 +92,9 @@ export const FinancialSheetStructure = < pathFormat: 'array', }); }; - public getTotalOfChildrenNodes = (node) => { return this.getTotalOfNodes(node.children); }; - public getTotalOfNodes = (nodes) => { return sumBy(nodes, 'total.amount'); }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAggregators.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAggregators.ts index 5fc0b2f03..98cd0f74c 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAggregators.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAggregators.ts @@ -27,8 +27,8 @@ export const BalanceSheetAggregators = >( BalanceSheetComparsionPreviousYear, BalanceSheetPercentage, BalanceSheetSchema, - BalanceSheetBase, FinancialSheetStructure, + BalanceSheetBase, )(Base) { /** * Balance sheet query. diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetBase.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetBase.ts index 41ae8ce6d..28103c930 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetBase.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetBase.ts @@ -11,7 +11,7 @@ export const BalanceSheetBase = >( ) => class BalanceSheetBase extends Base { /** - * Detarmines the node type of the given schema node. + * Determines the node type of the given schema node. * @param {IBalanceSheetStructureSection} node - * @param {string} type - * @return {boolean} @@ -21,9 +21,8 @@ export const BalanceSheetBase = >( return node.type === type; }, ); - /** - * Detarmines the node type of the given schema node. + * Determines the node type of the given schema node. * @param {IBalanceSheetStructureSection} node - * @param {string} type - * @return {boolean} @@ -33,9 +32,8 @@ export const BalanceSheetBase = >( return node.nodeType === type; }, ); - /** - * Detarmines the given display columns by type. + * Determines the given display columns by type. * @param {string} displayColumnsBy * @returns {boolean} */ diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncome.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncome.ts index 122bbfc34..190453027 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncome.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncome.ts @@ -62,8 +62,8 @@ export const BalanceSheetNetIncome = >( }; /** - * Mappes the aggregate schema node type. - * @param {IBalanceSheetSchemaNetIncomeNode} node - Schema node. + * Maps the aggregate schema node type. + * @param {IBalanceSheetSchemaNetIncomeNode} node - Schema node. * @return {IBalanceSheetAggregateNode} */ public schemaNetIncomeNodeMapper = ( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetSchema.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetSchema.ts index ca9bb4cf6..1056d9e9f 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetSchema.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetSchema.ts @@ -1,5 +1,4 @@ /* eslint-disable import/prefer-default-export */ -import * as R from 'ramda'; import { BALANCE_SHEET_SCHEMA_NODE_ID, BALANCE_SHEET_SCHEMA_NODE_TYPE, @@ -12,7 +11,7 @@ import { FinancialSheet } from '../../common/FinancialSheet'; export const BalanceSheetSchema = >( Base: T, ) => - class extends R.pipe(FinancialSchema)(Base) { + class extends FinancialSchema(Base) { /** * Retrieves the balance sheet schema. * @returns diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePercentage.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePercentage.ts index 66b41a206..57486b9f1 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePercentage.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePercentage.ts @@ -1,10 +1,15 @@ import * as R from 'ramda'; -import { ITableColumn } from '../../types/Table.types'; -import { Constructor } from '@/common/types/Constructor'; -import { BalanceSheetQuery } from './BalanceSheetQuery'; import { I18nService } from 'nestjs-i18n'; +import { ITableColumn } from '../../types/Table.types'; +import { GConstructor } from '@/common/types/Constructor'; +import { BalanceSheetQuery } from './BalanceSheetQuery'; +import { FinancialSheet } from '../../common/FinancialSheet'; -export const BalanceSheetTablePercentage = (Base: T) => +export const BalanceSheetTablePercentage = < + T extends GConstructor, +>( + Base: T, +) => class BalanceSheetComparsionPreviousYear extends Base { public readonly query: BalanceSheetQuery; public readonly i18n: I18nService; @@ -23,15 +28,15 @@ export const BalanceSheetTablePercentage = (Base: T) => 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'), - }) - ) + }), + ), )([]); }; @@ -49,15 +54,15 @@ export const BalanceSheetTablePercentage = (Base: T) => R.append({ key: 'percentage_of_column', accessor: 'percentageColumn.formattedAmount', - }) + }), ), R.when( this.query.isRowsPercentageActive, R.append({ key: 'percentage_of_row', accessor: 'percentageRow.formattedAmount', - }) - ) + }), + ), )([]); }; @@ -67,7 +72,7 @@ export const BalanceSheetTablePercentage = (Base: T) => * @returns {ITableColumn[]} */ public percetangeDatePeriodColumnsAccessor = ( - index: number + index: number, ): ITableColumn[] => { return R.pipe( R.when( @@ -75,15 +80,15 @@ export const BalanceSheetTablePercentage = (Base: T) => 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`, - }) - ) + }), + ), )([]); }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousYear.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousYear.ts index c4c0c718c..c1c75fb26 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousYear.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousYear.ts @@ -3,9 +3,14 @@ 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'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from '../../common/FinancialSheet'; -export const BalanceSheetTablePreviousYear = (Base: T) => +export const BalanceSheetTablePreviousYear = < + T extends GConstructor, +>( + Base: T, +) => class extends R.pipe(FinancialTablePreviousYear, FinancialDateRanges)(Base) { // -------------------- // # Columns. diff --git a/temp/CashFlow/CashFlow.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlow.ts similarity index 84% rename from temp/CashFlow/CashFlow.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlow.ts index 47df3e94f..5921c9661 100644 --- a/temp/CashFlow/CashFlow.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlow.ts @@ -2,7 +2,6 @@ import * as R from 'ramda'; import { defaultTo, map, set, sumBy, isEmpty, mapValues, get } from 'lodash'; import * as mathjs from 'mathjs'; import * as moment from 'moment'; -import { compose } from 'lodash/fp'; import { I18nService } from 'nestjs-i18n'; import { ICashFlowSchemaSection, @@ -21,7 +20,6 @@ import { ICashFlowStatementAggregateSection, } from './Cashflow.types'; import { CASH_FLOW_SCHEMA } from './schema'; -import { FinancialSheet } from '../../common/FinancialSheet'; import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts'; import { CashFlowStatementDatePeriods } from './CashFlowDatePeriods'; import { DISPLAY_COLUMNS_BY } from './constants'; @@ -32,11 +30,12 @@ import { INumberFormatQuery } from '../../types/Report.types'; import { transformToMapBy } from '@/utils/transform-to-map-by'; import { accumSum } from '@/utils/accum-sum'; import { ModelObject } from 'objection'; +import { CashflowStatementBase } from './CashflowStatementBase'; -export default class CashFlowStatement extends compose( +export class CashFlowStatement extends R.pipe( CashFlowStatementDatePeriods, - FinancialSheetStructure -)(FinancialSheet) { + FinancialSheetStructure, +)(CashflowStatementBase) { readonly baseCurrency: string; readonly i18n: I18nService; readonly sectionsByIds = {}; @@ -63,7 +62,7 @@ export default class CashFlowStatement extends compose( netIncomeLedger: ILedger, query: ICashFlowStatementQuery, baseCurrency: string, - i18n + i18n: I18nService, ) { super(); @@ -83,41 +82,6 @@ export default class CashFlowStatement extends compose( this.initDateRangeCollection(); } - // -------------------------------------------- - // # GENERAL UTILITIES - // -------------------------------------------- - /** - * Retrieve the expense accounts ids. - * @return {number[]} - */ - private getAccountsIdsByType = (accountType: string): number[] => { - const expenseAccounts = this.accountsByRootType.get(accountType); - const expenseAccountsIds = map(expenseAccounts, 'id'); - - return expenseAccountsIds; - }; - - /** - * Detarmines the given display columns by type. - * @param {string} displayColumnsBy - * @returns {boolean} - */ - private isDisplayColumnsBy = (displayColumnsBy: string): boolean => { - return this.query.displayColumnsType === displayColumnsBy; - }; - - /** - * Adjustments the given amount. - * @param {string} direction - * @param {number} amount - - * @return {number} - */ - private amountAdjustment = (direction: 'mines' | 'plus', amount): number => { - return R.when( - R.always(R.equals(direction, 'mines')), - R.multiply(-1) - )(amount); - }; // -------------------------------------------- // # NET INCOME NODE @@ -129,18 +93,18 @@ export default class CashFlowStatement extends compose( private getAccountsNetIncome(): number { // Mapping income/expense accounts ids. const incomeAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.INCOME + ACCOUNT_ROOT_TYPE.INCOME, ); const expenseAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.EXPENSE + ACCOUNT_ROOT_TYPE.EXPENSE, ); // Income closing balance. const incomeClosingBalance = accumSum(incomeAccountsIds, (id) => - this.netIncomeLedger.whereAccountId(id).getClosingBalance() + this.netIncomeLedger.whereAccountId(id).getClosingBalance(), ); // Expense closing balance. const expenseClosingBalance = accumSum(expenseAccountsIds, (id) => - this.netIncomeLedger.whereAccountId(id).getClosingBalance() + this.netIncomeLedger.whereAccountId(id).getClosingBalance(), ); // Net income = income - expenses. const netIncome = incomeClosingBalance - expenseClosingBalance; @@ -154,21 +118,21 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementNetIncomeSection} */ private netIncomeSectionMapper = ( - nodeSchema: ICashFlowSchemaSection + nodeSchema: ICashFlowSchemaSection, ): ICashFlowStatementNetIncomeSection => { const netIncome = this.getAccountsNetIncome(); const node = { id: nodeSchema.id, - label: this.i18n.__(nodeSchema.label), + label: this.i18n.t(nodeSchema.label), total: this.getAmountMeta(netIncome), sectionType: ICashFlowStatementSectionType.NET_INCOME, }; return R.compose( R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToNetIncomeNode - ) + this.assocPeriodsToNetIncomeNode, + ), )(node); }; @@ -183,7 +147,7 @@ export default class CashFlowStatement extends compose( */ private accountMetaMapper = ( relation: ICashFlowSchemaAccountRelation, - account: ModelObject + account: ModelObject, ): ICashFlowStatementAccountMeta => { // Retrieve the closing balance of the given account. const getClosingBalance = (id) => @@ -191,7 +155,7 @@ export default class CashFlowStatement extends compose( const closingBalance = R.compose( // Multiplies the amount by -1 in case the relation in mines. - R.curry(this.amountAdjustment)(relation.direction) + R.curry(this.amountAdjustment)(relation.direction), )(getClosingBalance(account.id)); const node = { @@ -206,8 +170,8 @@ export default class CashFlowStatement extends compose( return R.compose( R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToAccountNode - ) + this.assocPeriodsToAccountNode, + ), )(node); }; @@ -217,7 +181,7 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementAccountMeta[]} */ private getAccountsBySchemaRelation = ( - relation: ICashFlowSchemaAccountRelation + relation: ICashFlowSchemaAccountRelation, ): ICashFlowStatementAccountMeta[] => { const accounts = defaultTo(this.accountByTypeMap.get(relation.type), []); const accountMetaMapper = R.curry(this.accountMetaMapper)(relation); @@ -230,11 +194,11 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementAccountMeta[]} */ private getAccountsBySchemaRelations = ( - relations: ICashFlowSchemaAccountRelation[] + relations: ICashFlowSchemaAccountRelation[], ): ICashFlowStatementAccountMeta[] => { return R.pipe( R.append(R.map(this.getAccountsBySchemaRelation)(relations)), - R.flatten + R.flatten, )([]); }; @@ -244,7 +208,7 @@ export default class CashFlowStatement extends compose( * @returns {number} */ private getAccountsMetaTotal = ( - accounts: ICashFlowStatementAccountMeta[] + accounts: ICashFlowStatementAccountMeta[], ): number => { return sumBy(accounts, 'total.amount'); }; @@ -255,7 +219,7 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementAccountSection} */ private accountsSectionParser = ( - sectionSchema: ICashFlowSchemaSectionAccounts + sectionSchema: ICashFlowSchemaSectionAccounts, ): ICashFlowStatementAccountSection => { const { accountsRelations } = sectionSchema; @@ -266,16 +230,16 @@ export default class CashFlowStatement extends compose( const node = { sectionType: ICashFlowStatementSectionType.ACCOUNTS, id: sectionSchema.id, - label: this.i18n.__(sectionSchema.label), - footerLabel: this.i18n.__(sectionSchema.footerLabel), + label: this.i18n.t(sectionSchema.label), + footerLabel: this.i18n.t(sectionSchema.footerLabel), children: accounts, total, }; return R.compose( R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToAggregateNode - ) + this.assocPeriodsToAggregateNode, + ), )(node); }; @@ -288,8 +252,8 @@ export default class CashFlowStatement extends compose( private isSchemaSectionType = R.curry( (type: string, section: ICashFlowSchemaSection): boolean => { return type === section.sectionType; - } - ); + }, +); // -------------------------------------------- // # AGGREGATE NODE @@ -302,29 +266,29 @@ export default class CashFlowStatement extends compose( private regularSectionParser = R.curry( ( children, - schemaSection: ICashFlowSchemaSection + schemaSection: ICashFlowSchemaSection, ): ICashFlowStatementAggregateSection => { const node = { id: schemaSection.id, - label: this.i18n.__(schemaSection.label), - footerLabel: this.i18n.__(schemaSection.footerLabel), + label: this.i18n.t(schemaSection.label), + footerLabel: this.i18n.t(schemaSection.footerLabel), sectionType: ICashFlowStatementSectionType.AGGREGATE, children, }; return R.compose( R.when( this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE), - this.assocRegularSectionTotal + this.assocRegularSectionTotal, ), R.when( this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE), R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToAggregateNode - ) - ) + this.assocPeriodsToAggregateNode, + ), + ), )(node); - } + }, ); private transformSectionsToMap = (sections: ICashFlowSchemaSection[]) => { @@ -336,7 +300,7 @@ export default class CashFlowStatement extends compose( } return acc; }, - {} + {}, ); }; @@ -356,7 +320,7 @@ export default class CashFlowStatement extends compose( */ private evaluateEquation = ( equation: string, - scope: { [key: string | number]: number } + scope: { [key: string | number]: number }, ): number => { return mathjs.evaluate(equation, scope); }; @@ -369,7 +333,7 @@ export default class CashFlowStatement extends compose( */ private totalEquationSectionParser = ( accumulatedSections: ICashFlowSchemaSection[], - sectionSchema: ICashFlowSchemaTotalSection + sectionSchema: ICashFlowSchemaTotalSection, ): ICashFlowStatementTotalSection => { const mappedSectionsById = this.transformSectionsToMap(accumulatedSections); const nodesTotalById = this.sectionsMapToTotal(mappedSectionsById); @@ -381,13 +345,13 @@ export default class CashFlowStatement extends compose( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), R.curry(this.assocTotalEquationDatePeriods)( mappedSectionsById, - sectionSchema.equation - ) - ) + sectionSchema.equation, + ), + ), )({ sectionType: ICashFlowStatementSectionType.TOTAL, id: sectionSchema.id, - label: this.i18n.__(sectionSchema.label), + label: this.i18n.t(sectionSchema.label), total: this.getTotalAmountMeta(total), }); }; @@ -409,7 +373,7 @@ export default class CashFlowStatement extends compose( */ private cashAccountMetaMapper = ( relation: ICashFlowSchemaAccountRelation, - account: ModelObject + account: ModelObject, ): ICashFlowStatementAccountMeta => { const cashToDate = this.beginningCashFrom(this.query.fromDate); @@ -430,8 +394,8 @@ export default class CashFlowStatement extends compose( return R.compose( R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocCashAtBeginningAccountDatePeriods - ) + this.assocCashAtBeginningAccountDatePeriods, + ), )(node); }; @@ -441,7 +405,7 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementAccountMeta[]} */ private getCashAccountsBySchemaRelation = ( - relation: ICashFlowSchemaAccountRelation + relation: ICashFlowSchemaAccountRelation, ): ICashFlowStatementAccountMeta[] => { const accounts = this.accountByTypeMap.get(relation.type) || []; const accountMetaMapper = R.curry(this.cashAccountMetaMapper)(relation); @@ -454,7 +418,7 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementAccountMeta[]} */ private getCashAccountsBySchemaRelations = ( - relations: ICashFlowSchemaAccountRelation[] + relations: ICashFlowSchemaAccountRelation[], ): ICashFlowStatementAccountMeta[] => { return R.concat(...R.map(this.getCashAccountsBySchemaRelation)(relations)); }; @@ -465,7 +429,7 @@ export default class CashFlowStatement extends compose( * @return {ICashFlowCashBeginningNode} */ private cashAtBeginningSectionParser = ( - nodeSchema: ICashFlowSchemaSection + nodeSchema: ICashFlowSchemaSection, ): ICashFlowCashBeginningNode => { const { accountsRelations } = nodeSchema; const children = this.getCashAccountsBySchemaRelations(accountsRelations); @@ -474,15 +438,15 @@ export default class CashFlowStatement extends compose( const node = { sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING, id: nodeSchema.id, - label: this.i18n.__(nodeSchema.label), + label: this.i18n.t(nodeSchema.label), children, total: this.getTotalAmountMeta(total), }; return R.compose( R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocCashAtBeginningDatePeriods - ) + this.assocCashAtBeginningDatePeriods, + ), )(node); }; @@ -493,31 +457,31 @@ export default class CashFlowStatement extends compose( */ private schemaSectionParser = ( schemaNode: ICashFlowSchemaSection, - children + children, ): ICashFlowSchemaSection | ICashFlowStatementSection => { return R.compose( // Accounts node. R.when( this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS), - this.accountsSectionParser + this.accountsSectionParser, ), // Net income node. R.when( this.isSchemaSectionType(ICashFlowStatementSectionType.NET_INCOME), - this.netIncomeSectionMapper + this.netIncomeSectionMapper, ), // Cash at beginning node. R.when( this.isSchemaSectionType( - ICashFlowStatementSectionType.CASH_AT_BEGINNING + ICashFlowStatementSectionType.CASH_AT_BEGINNING, ), - this.cashAtBeginningSectionParser + this.cashAtBeginningSectionParser, ), // Aggregate node. (that has no section type). R.when( this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE), - this.regularSectionParser(children) - ) + this.regularSectionParser(children), + ), )(schemaNode); }; @@ -534,14 +498,14 @@ export default class CashFlowStatement extends compose( key: number, parentValue: ICashFlowSchemaSection[], context, - accumulatedSections: (ICashFlowSchemaSection | ICashFlowStatementSection)[] + accumulatedSections: (ICashFlowSchemaSection | ICashFlowStatementSection)[], ): ICashFlowSchemaSection | ICashFlowStatementSection => { return R.compose( // Total equation section. R.when( this.isSchemaSectionType(ICashFlowStatementSectionType.TOTAL), - R.curry(this.totalEquationSectionParser)(accumulatedSections) - ) + R.curry(this.totalEquationSectionParser)(accumulatedSections), + ), )(section); }; @@ -551,7 +515,7 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementSection[]} */ private schemaSectionsParser = ( - schema: ICashFlowSchemaSection[] + schema: ICashFlowSchemaSection[], ): ICashFlowStatementSection[] => { return this.mapNodesDeepReverse(schema, this.schemaSectionParser); }; @@ -572,7 +536,7 @@ export default class CashFlowStatement extends compose( * @returns {(ICashFlowSchemaSection | ICashFlowStatementSection)[]} */ private totalSectionsParser = ( - sections: (ICashFlowSchemaSection | ICashFlowStatementSection)[] + sections: (ICashFlowSchemaSection | ICashFlowStatementSection)[], ): (ICashFlowSchemaSection | ICashFlowStatementSection)[] => { return this.reduceNodesDeep( sections, @@ -580,11 +544,11 @@ export default class CashFlowStatement extends compose( set( acc, context.path, - this.schemaSectionTotalParser(value, key, parentValue, context, acc) + this.schemaSectionTotalParser(value, key, parentValue, context, acc), ); return acc; }, - [] + [], ); }; @@ -597,7 +561,7 @@ export default class CashFlowStatement extends compose( * @returns {boolean} */ private isSectionHasChildren = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): boolean => { return !isEmpty(section.children); }; @@ -617,12 +581,12 @@ export default class CashFlowStatement extends compose( * @returns {boolean} */ private isAccountsSectionHasChildren = ( - section: ICashFlowStatementSection[] + section: ICashFlowStatementSection[], ): boolean => { return R.ifElse( this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS), this.isSectionHasChildren, - R.always(true) + R.always(true), )(section); }; @@ -632,12 +596,12 @@ export default class CashFlowStatement extends compose( * @returns {boolean} */ private isAccountLeafNoneZero = ( - section: ICashFlowStatementSection[] + section: ICashFlowStatementSection[], ): boolean => { return R.ifElse( this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNT), this.isSectionNoneZero, - R.always(true) + R.always(true), )(section); }; @@ -647,7 +611,7 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementSection[]} */ private filterNoneZeroAccountsLeafs = ( - sections: ICashFlowStatementSection[] + sections: ICashFlowStatementSection[], ): ICashFlowStatementSection[] => { return this.filterNodesDeep(sections, this.isAccountLeafNoneZero); }; @@ -658,7 +622,7 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementSection[]} */ private filterNoneChildrenSections = ( - sections: ICashFlowStatementSection[] + sections: ICashFlowStatementSection[], ): ICashFlowStatementSection[] => { return this.filterNodesDeep(sections, this.isAccountsSectionHasChildren); }; @@ -669,11 +633,11 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowStatementSection[]} */ private filterReportData = ( - sections: ICashFlowStatementSection[] + sections: ICashFlowStatementSection[], ): ICashFlowStatementSection[] => { return R.compose( this.filterNoneChildrenSections, - this.filterNoneZeroAccountsLeafs + this.filterNoneZeroAccountsLeafs, )(sections); }; @@ -683,15 +647,15 @@ export default class CashFlowStatement extends compose( * @returns {ICashFlowSchemaSection[]} */ private schemaParser = ( - schema: ICashFlowSchemaSection[] + schema: ICashFlowSchemaSection[], ): ICashFlowSchemaSection[] => { return R.compose( R.when( R.always(this.query.noneTransactions || this.query.noneZero), - this.filterReportData + this.filterReportData, ), this.totalSectionsParser, - this.schemaSectionsParser + this.schemaSectionsParser, )(schema); }; diff --git a/temp/CashFlow/CashFlowDatePeriods.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowDatePeriods.ts similarity index 89% rename from temp/CashFlow/CashFlowDatePeriods.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowDatePeriods.ts index a6986ab4c..2b5524d80 100644 --- a/temp/CashFlow/CashFlowDatePeriods.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowDatePeriods.ts @@ -14,11 +14,19 @@ import { import { IFormatNumberSettings } from '../../types/Report.types'; import { dateRangeFromToCollection } from '@/utils/date-range-collection'; import { accumSum } from '@/utils/accum-sum'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { GConstructor } from '@/common/types/Constructor'; +import { Ledger } from '@/modules/Ledger/Ledger'; -export const CashFlowStatementDatePeriods = (Base) => +export const CashFlowStatementDatePeriods = < + T extends GConstructor, +>( + Base: T, +) => class extends Base { dateRangeSet: IDateRange[]; query: ICashFlowStatementQuery; + netIncomeLedger: Ledger; /** * Initialize date range set. @@ -27,7 +35,7 @@ export const CashFlowStatementDatePeriods = (Base) => this.dateRangeSet = dateRangeFromToCollection( this.query.fromDate, this.query.toDate, - this.comparatorDateType + this.comparatorDateType, ); } @@ -42,7 +50,7 @@ export const CashFlowStatementDatePeriods = (Base) => total: number, fromDate: Date, toDate: Date, - overrideSettings: IFormatNumberSettings = {} + overrideSettings: IFormatNumberSettings = {}, ): ICashFlowDatePeriod => { return this.getDatePeriodMeta(total, fromDate, toDate, { money: true, @@ -61,7 +69,7 @@ export const CashFlowStatementDatePeriods = (Base) => total: number, fromDate: Date, toDate: Date, - overrideSettings?: IFormatNumberSettings + overrideSettings?: IFormatNumberSettings, ): ICashFlowDatePeriod => { return { fromDate: this.getDateMeta(fromDate), @@ -80,10 +88,10 @@ export const CashFlowStatementDatePeriods = (Base) => public getNetIncomeDateRange = (fromDate: Date, toDate: Date) => { // Mapping income/expense accounts ids. const incomeAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.INCOME + ACCOUNT_ROOT_TYPE.INCOME, ); const expenseAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.EXPENSE + ACCOUNT_ROOT_TYPE.EXPENSE, ); // Income closing balance. const incomeClosingBalance = accumSum(incomeAccountsIds, (id) => @@ -91,7 +99,7 @@ export const CashFlowStatementDatePeriods = (Base) => .whereFromDate(fromDate) .whereToDate(toDate) .whereAccountId(id) - .getClosingBalance() + .getClosingBalance(), ); // Expense closing balance. const expenseClosingBalance = accumSum(expenseAccountsIds, (id) => @@ -99,7 +107,7 @@ export const CashFlowStatementDatePeriods = (Base) => .whereToDate(toDate) .whereFromDate(fromDate) .whereAccountId(id) - .getClosingBalance() + .getClosingBalance(), ); // Net income = income - expenses. const netIncome = incomeClosingBalance - expenseClosingBalance; @@ -115,12 +123,12 @@ export const CashFlowStatementDatePeriods = (Base) => public getNetIncomeDatePeriod = (dateRange): ICashFlowDatePeriod => { const total = this.getNetIncomeDateRange( dateRange.fromDate, - dateRange.toDate + dateRange.toDate, ); return this.getDatePeriodMeta( total, dateRange.fromDate, - dateRange.toDate + dateRange.toDate, ); }; @@ -131,7 +139,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @returns {ICashFlowDatePeriod[]} */ public getNetIncomeDatePeriods = ( - section: ICashFlowStatementNetIncomeSection + section: ICashFlowStatementNetIncomeSection, ): ICashFlowDatePeriod[] => { return this.dateRangeSet.map(this.getNetIncomeDatePeriod.bind(this)); }; @@ -142,7 +150,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @returns {ICashFlowStatementNetIncomeSection} */ public assocPeriodsToNetIncomeNode = ( - section: ICashFlowStatementNetIncomeSection + section: ICashFlowStatementNetIncomeSection, ): ICashFlowStatementNetIncomeSection => { const incomeDatePeriods = this.getNetIncomeDatePeriods(section); return R.assoc('periods', incomeDatePeriods, section); @@ -158,7 +166,7 @@ export const CashFlowStatementDatePeriods = (Base) => public getAccountTotalDateRange = ( node: ICashFlowStatementAccountSection, fromDate: Date, - toDate: Date + toDate: Date, ): number => { const closingBalance = this.ledger .whereFromDate(fromDate) @@ -179,7 +187,7 @@ export const CashFlowStatementDatePeriods = (Base) => public getAccountTotalDatePeriod = ( node: ICashFlowStatementAccountSection, fromDate: Date, - toDate: Date + toDate: Date, ): ICashFlowDatePeriod => { const total = this.getAccountTotalDateRange(node, fromDate, toDate); return this.getDatePeriodMeta(total, fromDate, toDate); @@ -191,13 +199,13 @@ export const CashFlowStatementDatePeriods = (Base) => * @return {ICashFlowDatePeriod[]} */ public getAccountDatePeriods = ( - node: ICashFlowStatementAccountSection + node: ICashFlowStatementAccountSection, ): ICashFlowDatePeriod[] => { return this.getNodeDatePeriods( node, - this.getAccountTotalDatePeriod.bind(this) + this.getAccountTotalDatePeriod.bind(this), ); - } + }; /** * Writes `periods` property to account node. @@ -205,11 +213,11 @@ export const CashFlowStatementDatePeriods = (Base) => * @return {ICashFlowStatementAccountSection} */ public assocPeriodsToAccountNode = ( - node: ICashFlowStatementAccountSection + node: ICashFlowStatementAccountSection, ): ICashFlowStatementAccountSection => { const datePeriods = this.getAccountDatePeriods(node); return R.assoc('periods', datePeriods, node); - } + }; // Aggregate node ------------------------- /** @@ -218,10 +226,10 @@ export const CashFlowStatementDatePeriods = (Base) => */ public getChildrenTotalPeriodByIndex = ( node: ICashFlowStatementSection, - index: number + index: number, ): number => { return sumBy(node.children, `periods[${index}].total.amount`); - } + }; /** * Retrieve date period meta of the given node index. @@ -234,7 +242,7 @@ export const CashFlowStatementDatePeriods = (Base) => node: ICashFlowStatementSection, index: number, fromDate: Date, - toDate: Date + toDate: Date, ) { const total = this.getChildrenTotalPeriodByIndex(node, index); return this.getDatePeriodTotalMeta(total, fromDate, toDate); @@ -246,15 +254,15 @@ export const CashFlowStatementDatePeriods = (Base) => */ public getAggregateNodeDatePeriods(node: ICashFlowStatementSection) { const getChildrenTotalPeriodMetaByIndex = R.curry( - this.getChildrenTotalPeriodMetaByIndex.bind(this) + this.getChildrenTotalPeriodMetaByIndex.bind(this), )(node); return this.dateRangeSet.map((dateRange, index) => getChildrenTotalPeriodMetaByIndex( index, dateRange.fromDate, - dateRange.toDate - ) + dateRange.toDate, + ), ); } @@ -264,7 +272,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @return {ICashFlowStatementSection} */ public assocPeriodsToAggregateNode = ( - node: ICashFlowStatementSection + node: ICashFlowStatementSection, ): ICashFlowStatementSection => { const datePeriods = this.getAggregateNodeDatePeriods(node); return R.assoc('periods', datePeriods, node); @@ -274,11 +282,11 @@ export const CashFlowStatementDatePeriods = (Base) => public sectionsMapToTotalPeriod = ( mappedSections: { [key: number]: any }, - index + index, ) => { return mapValues( mappedSections, - (node) => get(node, `periods[${index}].total.amount`) || 0 + (node) => get(node, `periods[${index}].total.amount`) || 0, ); }; @@ -291,7 +299,7 @@ export const CashFlowStatementDatePeriods = (Base) => public getTotalEquationDatePeriods = ( node: ICashFlowSchemaTotalSection, equation: string, - nodesTable + nodesTable, ): ICashFlowDatePeriod[] => { return this.getNodeDatePeriods(node, (node, fromDate, toDate, index) => { const periodScope = this.sectionsMapToTotalPeriod(nodesTable, index); @@ -309,12 +317,12 @@ export const CashFlowStatementDatePeriods = (Base) => public assocTotalEquationDatePeriods = ( nodesTable: any, equation: string, - node: ICashFlowSchemaTotalSection + node: ICashFlowSchemaTotalSection, ): ICashFlowStatementTotalSection => { const datePeriods = this.getTotalEquationDatePeriods( node, equation, - nodesTable + nodesTable, ); return R.assoc('periods', datePeriods, node); @@ -345,7 +353,7 @@ export const CashFlowStatementDatePeriods = (Base) => public getBeginningCashAccountDateRange = ( node: ICashFlowStatementSection, fromDate: Date, - toDate: Date + toDate: Date, ) => { const cashToDate = this.beginningCashFrom(fromDate); @@ -365,12 +373,12 @@ export const CashFlowStatementDatePeriods = (Base) => public getBeginningCashDatePeriod = ( node: ICashFlowStatementSection, fromDate: Date, - toDate: Date + toDate: Date, ) => { const total = this.getBeginningCashAccountDateRange( node, fromDate, - toDate + toDate, ); return this.getDatePeriodTotalMeta(total, fromDate, toDate); }; @@ -381,7 +389,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @return {ICashFlowDatePeriod} */ public getBeginningCashAccountPeriods = ( - node: ICashFlowStatementSection + node: ICashFlowStatementSection, ): ICashFlowDatePeriod => { return this.getNodeDatePeriods(node, this.getBeginningCashDatePeriod); }; @@ -392,7 +400,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @return {ICashFlowStatementSection} */ public assocCashAtBeginningDatePeriods = ( - node: ICashFlowStatementSection + node: ICashFlowStatementSection, ): ICashFlowStatementSection => { const datePeriods = this.getAggregateNodeDatePeriods(node); return R.assoc('periods', datePeriods, node); @@ -404,7 +412,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @return {ICashFlowStatementSection} */ public assocCashAtBeginningAccountDatePeriods = ( - node: ICashFlowStatementSection + node: ICashFlowStatementSection, ): ICashFlowStatementSection => { const datePeriods = this.getBeginningCashAccountPeriods(node); return R.assoc('periods', datePeriods, node); diff --git a/temp/CashFlowStatement/CashFlowRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowRepository.ts similarity index 96% rename from temp/CashFlowStatement/CashFlowRepository.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowRepository.ts index 87661dfe6..6a54ca980 100644 --- a/temp/CashFlowStatement/CashFlowRepository.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowRepository.ts @@ -1,14 +1,18 @@ import { Inject, Injectable } from '@nestjs/common'; -import moment from 'moment'; +import * as moment from 'moment'; import { Knex } from 'knex'; import { isEmpty } from 'lodash'; +import { ModelObject } from 'objection'; import { ICashFlowStatementQuery } from './Cashflow.types'; import { Account } from '@/modules/Accounts/models/Account.model'; import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; -import { ModelObject } from 'objection'; @Injectable() export class CashFlowRepository { + /** + * @param {typeof Account} accountModel - Account model. + * @param {typeof AccountTransaction} accountTransactionModel - Account transaction model. + */ constructor( @Inject(Account.name) private readonly accountModel: typeof Account, diff --git a/temp/CashFlowStatement/CashFlowService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowService.ts similarity index 94% rename from temp/CashFlowStatement/CashFlowService.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowService.ts index 4dc334330..a4c515457 100644 --- a/temp/CashFlowStatement/CashFlowService.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowService.ts @@ -1,11 +1,10 @@ import { ModelObject } from 'objection'; -import moment from 'moment'; import * as R from 'ramda'; import { ICashFlowStatementQuery, ICashFlowStatementDOO, } from './Cashflow.types'; -import CashFlowStatement from './CashFlow'; +import { CashFlowStatement } from './CashFlow'; import { CashflowSheetMeta } from './CashflowSheetMeta'; import { Injectable } from '@nestjs/common'; import { CashFlowRepository } from './CashFlowRepository'; @@ -58,8 +57,7 @@ export class CashFlowStatementService { /** * Retrieve the cash flow sheet statement. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query + * @param {ICashFlowStatementQuery} query - Cashflow query. * @returns {Promise} */ public async cashFlow( @@ -67,7 +65,7 @@ export class CashFlowStatementService { ): Promise { // Retrieve all accounts on the storage. const accounts = await this.cashFlowRepo.cashFlowAccounts(); - const tenant = await this.tenancyContext.getTenant(); + const tenant = await this.tenancyContext.getTenant(true); const filter = { ...getDefaultCashflowQuery(), diff --git a/temp/CashFlow/CashFlowTable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowTable.ts similarity index 95% rename from temp/CashFlow/CashFlowTable.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowTable.ts index d54504254..36c75845d 100644 --- a/temp/CashFlow/CashFlowTable.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashFlowTable.ts @@ -1,6 +1,7 @@ import * as R from 'ramda'; -import { isEmpty, } from 'lodash'; +import { isEmpty } from 'lodash'; import moment from 'moment'; +import { I18nService } from 'nestjs-i18n'; import { ICashFlowStatementSection, ICashFlowStatementSectionType, @@ -27,14 +28,15 @@ const DISPLAY_COLUMNS_BY = { export class CashFlowTable { private report: ICashFlowStatementDOO; - private i18n; + private i18n: I18nService; private dateRangeSet: IDateRange[]; /** * Constructor method. - * @param {ICashFlowStatement} reportStatement + * @param {ICashFlowStatement} reportStatement - Statement. + * @param {I18nService} i18n - I18n service. */ - constructor(reportStatement: ICashFlowStatementDOO, i18n) { + constructor(reportStatement: ICashFlowStatementDOO, i18n: I18nService) { this.report = reportStatement; this.i18n = i18n; this.dateRangeSet = []; @@ -325,7 +327,6 @@ export class CashFlowTable { ], conditions, ); - return R.compose(R.cond(conditionsPairs))(dateRange); }; @@ -341,7 +342,7 @@ export class CashFlowTable { }; /** - * Detarmines the given column type is the current. + * Determines the given column type is the current. * @reutrns {boolean} */ private isDisplayColumnsBy = (displayColumnsType: string): Boolean => { @@ -349,7 +350,7 @@ export class CashFlowTable { }; /** - * Detarmines whether the given display columns type is the current. + * Determines whether the given display columns type is the current. * @param {string} displayColumnsBy * @returns {boolean} */ @@ -363,7 +364,7 @@ export class CashFlowTable { */ public tableColumns = (): ITableColumn[] => { return R.compose( - R.concat([{ key: 'name', label: this.i18n.__('Account name') }]), + R.concat([{ key: 'name', label: this.i18n.t('Account name') }]), R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), R.concat(this.datePeriodsColumns()), diff --git a/temp/CashFlowStatement/Cashflow.controller.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.controller.ts similarity index 94% rename from temp/CashFlowStatement/Cashflow.controller.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.controller.ts index be0df3fc7..b98703c60 100644 --- a/temp/CashFlowStatement/Cashflow.controller.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.controller.ts @@ -3,8 +3,10 @@ import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; import { ICashFlowStatementQuery } from './Cashflow.types'; import { AcceptType } from '@/constants/accept-type'; import { CashflowSheetApplication } from './CashflowSheetApplication'; +import { PublicRoute } from '@/modules/Auth/Jwt.guard'; -@Controller('reports/cashflow') +@Controller('reports/cashflow-statement') +@PublicRoute() export class CashflowController { constructor(private readonly cashflowSheetApp: CashflowSheetApplication) {} diff --git a/temp/CashFlow/Cashflow.types.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.types.ts similarity index 100% rename from temp/CashFlow/Cashflow.types.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.types.ts diff --git a/temp/CashFlow/CashflowExportInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowExportInjectable.ts similarity index 100% rename from temp/CashFlow/CashflowExportInjectable.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowExportInjectable.ts diff --git a/temp/CashFlowStatement/CashflowSheetApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowSheetApplication.ts similarity index 100% rename from temp/CashFlowStatement/CashflowSheetApplication.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowSheetApplication.ts diff --git a/temp/CashFlowStatement/CashflowSheetMeta.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowSheetMeta.ts similarity index 96% rename from temp/CashFlowStatement/CashflowSheetMeta.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowSheetMeta.ts index 3d19612eb..1b8f3d796 100644 --- a/temp/CashFlowStatement/CashflowSheetMeta.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowSheetMeta.ts @@ -1,4 +1,4 @@ -import moment from 'moment'; +import * as moment from 'moment'; import { Injectable } from '@nestjs/common'; import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; import { diff --git a/temp/CashFlowStatement/Cashflow.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatement.module.ts similarity index 94% rename from temp/CashFlowStatement/Cashflow.module.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatement.module.ts index 1646def94..25f9de669 100644 --- a/temp/CashFlowStatement/Cashflow.module.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatement.module.ts @@ -20,8 +20,8 @@ import { CashflowSheetApplication } from './CashflowSheetApplication'; CashflowExportInjectable, CashflowTableInjectable, CashflowSheetApplication, - TenancyContext + TenancyContext, ], controllers: [CashflowController], }) -export class CashflowReportModule {} +export class CashflowStatementModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatementBase.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatementBase.ts new file mode 100644 index 000000000..9f2fbc8e3 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowStatementBase.ts @@ -0,0 +1,46 @@ +import * as R from 'ramda'; +import { map } from 'lodash'; +import { Account } from "@/modules/Accounts/models/Account.model"; +import { ICashFlowStatementQuery } from './Cashflow.types'; +import { FinancialSheet } from '../../common/FinancialSheet'; + +export class CashflowStatementBase extends FinancialSheet { + readonly accountsByRootType: Map; + readonly query: ICashFlowStatementQuery; + + // -------------------------------------------- + // # GENERAL UTILITIES + // -------------------------------------------- + /** + * Retrieve the expense accounts ids. + * @return {number[]} + */ + public getAccountsIdsByType = (accountType: string): number[] => { + const expenseAccounts = this.accountsByRootType.get(accountType); + const expenseAccountsIds = map(expenseAccounts, 'id'); + + return expenseAccountsIds; + }; + + /** + * Detarmines the given display columns by type. + * @param {string} displayColumnsBy + * @returns {boolean} + */ + public isDisplayColumnsBy = (displayColumnsBy: string): boolean => { + return this.query.displayColumnsType === displayColumnsBy; + }; + + /** + * Adjustments the given amount. + * @param {string} direction + * @param {number} amount - + * @return {number} + */ + public amountAdjustment = (direction: 'mines' | 'plus', amount): number => { + return R.when( + R.always(R.equals(direction, 'mines')), + R.multiply(-1), + )(amount); + }; +} diff --git a/temp/CashFlow/CashflowTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowTableInjectable.ts similarity index 95% rename from temp/CashFlow/CashflowTableInjectable.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowTableInjectable.ts index 3440b5c3c..362cd5ab1 100644 --- a/temp/CashFlow/CashflowTableInjectable.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowTableInjectable.ts @@ -16,6 +16,7 @@ export class CashflowTableInjectable { /** * Retrieves the cash flow table. + * @param {ICashFlowStatementQuery} query - * @returns {Promise} */ public async table( diff --git a/temp/CashFlow/CashflowTablePdfInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowTablePdfInjectable.ts similarity index 100% rename from temp/CashFlow/CashflowTablePdfInjectable.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/CashflowTablePdfInjectable.ts diff --git a/temp/CashFlow/constants.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/constants.ts similarity index 97% rename from temp/CashFlow/constants.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/constants.ts index 6aaa74486..6152c73ff 100644 --- a/temp/CashFlow/constants.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/constants.ts @@ -1,3 +1,4 @@ +import * as moment from 'moment'; import { ICashFlowStatementQuery } from "./Cashflow.types"; export const DISPLAY_COLUMNS_BY = { diff --git a/temp/CashFlow/schema.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/schema.ts similarity index 100% rename from temp/CashFlow/schema.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/CashFlowStatement/schema.ts diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/JournalSheet/JournalSheetApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/JournalSheet/JournalSheetApplication.ts index dd82aeef6..ea1cdb21c 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/JournalSheet/JournalSheetApplication.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/JournalSheet/JournalSheetApplication.ts @@ -15,7 +15,7 @@ export class JournalSheetApplication { /** * Retrieves the journal sheet. * @param {IJournalReportQuery} query - * @returns {} + * @returns {Promise} */ public sheet(query: IJournalReportQuery) { return this.journalSheet.journalSheet(query); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSchema.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSchema.ts new file mode 100644 index 000000000..21611b1a4 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSchema.ts @@ -0,0 +1,80 @@ +import * as R from 'ramda'; +import { + ProfitLossAggregateNodeId, + ProfitLossNodeType, + IProfitLossSchemaNode, +} from './ProfitLossSheet.types'; +import { ACCOUNT_TYPE } from '@/constants/accounts'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSchema } from '../../common/FinancialSchema'; +import { FinancialSheet } from '../../common/FinancialSheet'; + +export const ProfitLossShema = >( + Base: T, +) => + class extends R.pipe(FinancialSchema)(Base) { + /** + * Retrieves the report schema. + * @returns {IProfitLossSchemaNode[]} + */ + getSchema = (): IProfitLossSchemaNode[] => { + return getProfitLossSheetSchema(); + }; + }; + +/** + * Retrieves P&L sheet schema. + * @returns {IProfitLossSchemaNode} + */ +export const getProfitLossSheetSchema = (): IProfitLossSchemaNode[] => [ + { + id: ProfitLossAggregateNodeId.INCOME, + name: 'profit_loss_sheet.income', + nodeType: ProfitLossNodeType.ACCOUNTS, + accountsTypes: [ACCOUNT_TYPE.INCOME], + alwaysShow: true, + }, + { + id: ProfitLossAggregateNodeId.COS, + name: 'profit_loss_sheet.cost_of_sales', + nodeType: ProfitLossNodeType.ACCOUNTS, + accountsTypes: [ACCOUNT_TYPE.COST_OF_GOODS_SOLD], + }, + { + id: ProfitLossAggregateNodeId.GROSS_PROFIT, + name: 'profit_loss_sheet.gross_profit', + nodeType: ProfitLossNodeType.EQUATION, + equation: `${ProfitLossAggregateNodeId.INCOME} - ${ProfitLossAggregateNodeId.COS}`, + }, + { + id: ProfitLossAggregateNodeId.EXPENSES, + name: 'profit_loss_sheet.expenses', + nodeType: ProfitLossNodeType.ACCOUNTS, + accountsTypes: [ACCOUNT_TYPE.EXPENSE], + alwaysShow: true, + }, + { + id: ProfitLossAggregateNodeId.NET_OPERATING_INCOME, + name: 'profit_loss_sheet.net_operating_income', + nodeType: ProfitLossNodeType.EQUATION, + equation: `${ProfitLossAggregateNodeId.GROSS_PROFIT} - ${ProfitLossAggregateNodeId.EXPENSES}`, + }, + { + id: ProfitLossAggregateNodeId.OTHER_INCOME, + name: 'profit_loss_sheet.other_income', + nodeType: ProfitLossNodeType.ACCOUNTS, + accountsTypes: [ACCOUNT_TYPE.OTHER_INCOME], + }, + { + id: ProfitLossAggregateNodeId.OTHER_EXPENSES, + name: 'profit_loss_sheet.other_expenses', + nodeType: ProfitLossNodeType.ACCOUNTS, + accountsTypes: [ACCOUNT_TYPE.OTHER_EXPENSE], + }, + { + id: ProfitLossAggregateNodeId.NET_INCOME, + name: 'profit_loss_sheet.net_income', + nodeType: ProfitLossNodeType.EQUATION, + equation: `${ProfitLossAggregateNodeId.NET_OPERATING_INCOME} + ${ProfitLossAggregateNodeId.OTHER_INCOME} - ${ProfitLossAggregateNodeId.OTHER_EXPENSES}`, + }, +]; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts new file mode 100644 index 000000000..66e833c65 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts @@ -0,0 +1,65 @@ +import { Response } from 'express'; +import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { IProfitLossSheetQuery } from './ProfitLossSheet.types'; +import { ProfitLossSheetApplication } from './ProfitLossSheetApplication'; +import { AcceptType } from '@/constants/accept-type'; +import { PublicRoute } from '@/modules/Auth/Jwt.guard'; + +@Controller('/reports/profit-loss-sheet') +@PublicRoute() +export class ProfitLossSheetController { + constructor( + private readonly profitLossSheetApp: ProfitLossSheetApplication, + ) {} + + /** + * Retrieves the profit/loss sheet. + * @param {IProfitLossSheetQuery} query + * @param {Response} res + * @param {string} acceptHeader + */ + @Get('/') + async profitLossSheet( + @Query() query: IProfitLossSheetQuery, + @Res() res: Response, + @Headers('accept') acceptHeader: string, + ) { + // Retrieves the csv format. + if (acceptHeader.includes(AcceptType.ApplicationCsv)) { + const sheet = await this.profitLossSheetApp.csv(query); + + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); + res.setHeader('Content-Type', 'text/csv'); + + return res.send(sheet); + // Retrieves the json table format. + } else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) { + const table = await this.profitLossSheetApp.table(query); + + return res.status(200).send(table); + // Retrieves the xlsx format. + } else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) { + const sheet = await this.profitLossSheetApp.xlsx(query); + + res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx'); + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + return res.send(sheet); + // Retrieves the json format. + } else if (acceptHeader.includes(AcceptType.ApplicationJson)) { + const pdfContent = await this.profitLossSheetApp.pdf(query); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); + } else { + const sheet = await this.profitLossSheetApp.sheet(query); + + return res.status(200).send(sheet); + } + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.module.ts new file mode 100644 index 000000000..e5d472625 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; +import { ProfitLossSheetService } from './ProfitLossSheetService'; +import { ProfitLossSheetExportInjectable } from './ProfitLossSheetExportInjectable'; +import { ProfitLossTablePdfInjectable } from './ProfitLossTablePdfInjectable'; +import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; +import { ProfitLossSheetMeta } from './ProfitLossSheetMeta'; +import { ProfitLossSheetRepository } from './ProfitLossSheetRepository'; +import { AccountsModule } from '@/modules/Accounts/Accounts.module'; +import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { ProfitLossSheetController } from './ProfitLossSheet.controller'; +import { ProfitLossSheetApplication } from './ProfitLossSheetApplication'; + +@Module({ + imports: [FinancialSheetCommonModule, AccountsModule], + controllers: [ProfitLossSheetController], + providers: [ + ProfitLossSheetApplication, + ProfitLossSheetService, + ProfitLossSheetExportInjectable, + ProfitLossTablePdfInjectable, + ProfitLossSheetTableInjectable, + ProfitLossSheetMeta, + ProfitLossSheetRepository, + TenancyContext, + ], +}) +export class ProfitLossSheetModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.ts new file mode 100644 index 000000000..628a25113 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.ts @@ -0,0 +1,341 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { ModelObject } from 'objection'; +import { I18nService } from 'nestjs-i18n'; +import { + ProfitLossNodeType, + IProfitLossSheetEquationNode, + IProfitLossEquationSchemaNode, + IProfitLossSheetAccountsNode, + IProfitLossAccountsSchemaNode, + IProfitLossSchemaNode, + IProfitLossSheetNode, + IProfitLossSheetAccountNode, + IProfitLossSheetQuery, +} from './ProfitLossSheet.types'; +import { ProfitLossShema } from './ProfitLossSchema'; +import { ProfitLossSheetPercentage } from './ProfitLossSheetPercentage'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { ProfitLossSheetRepository } from './ProfitLossSheetRepository'; +import { ProfitLossSheetBase } from './ProfitLossSheetBase'; +import { ProfitLossSheetDatePeriods } from './ProfitLossSheetDatePeriods'; +import { ProfitLossSheetPreviousYear } from './ProfitLossSheetPreviousYear'; +import { ProfitLossSheetPreviousPeriod } from './ProfitLossSheetPreviousPeriod'; +import { ProfitLossSheetFilter } from './ProfitLossSheetFilter'; +import { FinancialDateRanges } from '../../common/FinancialDateRanges'; +import { FinancialEvaluateEquation } from '../../common/FinancialEvaluateEquation'; +import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { flatToNestedArray } from '@/utils/flat-to-nested-array'; + +export default class ProfitLossSheet extends R.pipe( + ProfitLossSheetPreviousYear, + ProfitLossSheetPreviousPeriod, + ProfitLossSheetPercentage, + ProfitLossSheetDatePeriods, + ProfitLossSheetFilter, + ProfitLossShema, + ProfitLossSheetBase, + FinancialDateRanges, + FinancialEvaluateEquation, + FinancialSheetStructure, +)(FinancialSheet) { + /** + * Profit/Loss sheet query. + * @param {ProfitLossSheetQuery} + */ + readonly query: ProfitLossSheetQuery; + /** + * @param {string} + */ + readonly comparatorDateType: string; + + /** + * Organization's base currency. + * @param {string} + */ + readonly baseCurrency: string; + + /** + * Profit/Loss repository. + * @param {ProfitLossSheetRepository} + */ + readonly repository: ProfitLossSheetRepository; + + /** + * I18n service. + * @param {I18nService} + */ + readonly i18n: I18nService; + + /** + * Constructor method. + * @param {IProfitLossSheetQuery} query - + * @param {IAccount[]} accounts - + * @param {IJournalPoster} transactionsJournal - + */ + constructor( + repository: ProfitLossSheetRepository, + query: IProfitLossSheetQuery, + i18n: I18nService, + ) { + super(); + + this.query = new ProfitLossSheetQuery(query); + this.repository = repository; + this.numberFormat = this.query.query.numberFormat; + this.i18n = i18n; + } + + /** + * Retrieve the sheet account node from the given account. + * @param {ModelObject} account + * @returns {IProfitLossSheetAccountNode} + */ + private accountNodeMapper = ( + account: ModelObject, + ): IProfitLossSheetAccountNode => { + // Retrieves the children account ids of the given account id. + const childrenAccountIds = this.repository.accountsGraph.dependenciesOf( + account.id, + ); + // Concat the children and the given account id. + const accountIds = R.uniq(R.append(account.id, childrenAccountIds)); + + // Retrieves the closing balance of the account included children accounts. + const total = this.repository.totalAccountsLedger + .whereAccountsIds(accountIds) + .getClosingBalance(); + + return { + id: account.id, + name: account.name, + nodeType: ProfitLossNodeType.ACCOUNT, + total: this.getAmountMeta(total), + }; + }; + + /** + * Compose account node. + * @param {ModelObject} node + * @returns {IProfitLossSheetAccountNode} + */ + private accountNodeCompose = ( + account: ModelObject, + ): IProfitLossSheetAccountNode => { + return R.compose( + R.when( + this.query.isPreviousPeriodActive, + this.previousPeriodAccountNodeCompose, + ), + R.when( + this.query.isPreviousYearActive, + this.previousYearAccountNodeCompose, + ), + R.when( + this.query.isDatePeriodsColumnsType, + this.assocAccountNodeDatePeriod, + ), + this.accountNodeMapper, + )(account); + }; + + /** + * Retrieves report accounts nodes by the given accounts types. + * @param {string[]} types + * @returns {IBalanceSheetAccountNode} + */ + private getAccountsNodesByTypes = ( + types: string[], + ): IProfitLossSheetAccountNode[] => { + const accounts = this.repository.getAccountsByType(types); + const accountsTree = flatToNestedArray(accounts, { + id: 'id', + parentId: 'parentAccountId', + }); + return this.mapNodesDeep(accountsTree, this.accountNodeCompose); + }; + + /** + * Mapps the accounts schema node to report node. + * @param {IProfitLossSchemaNode} node + * @returns {IProfitLossSheetNode} + */ + private accountsSchemaNodeMapper = ( + node: IProfitLossAccountsSchemaNode, + ): IProfitLossSheetNode => { + // Retrieve accounts node by the given types. + const children = this.getAccountsNodesByTypes(node.accountsTypes); + + // Retrieve the total of the given nodes. + const total = this.getTotalOfNodes(children); + + return { + id: node.id, + name: this.i18n.t(node.name), + nodeType: ProfitLossNodeType.ACCOUNTS, + total: this.getTotalAmountMeta(total), + children, + }; + }; + + /** + * Accounts schema node composer. + * @param {IProfitLossSchemaNode} node + * @returns {IProfitLossSheetAccountsNode} + */ + private accountsSchemaNodeCompose = ( + node: IProfitLossSchemaNode, + ): IProfitLossSheetAccountsNode => { + return R.compose( + R.when( + this.query.isPreviousPeriodActive, + this.previousPeriodAggregateNodeCompose, + ), + R.when( + this.query.isPreviousYearActive, + this.previousYearAggregateNodeCompose, + ), + R.when( + this.query.isDatePeriodsColumnsType, + this.assocAggregateDatePeriod, + ), + this.accountsSchemaNodeMapper, + )(node); + }; + + /** + * Equation schema node parser. + * @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes - + * @param {IProfitLossEquationSchemaNode} node - + * @param {IProfitLossSheetEquationNode} + */ + private equationSchemaNodeParser = R.curry( + ( + accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[], + node: IProfitLossEquationSchemaNode, + ): IProfitLossSheetEquationNode => { + const tableNodes = this.getNodesTableForEvaluating( + 'total.amount', + accNodes, + ); + // Evaluate the given equation. + const total = this.evaluateEquation(node.equation, tableNodes); + + return { + id: node.id, + name: this.i18n.t(node.name), + nodeType: ProfitLossNodeType.EQUATION, + total: this.getTotalAmountMeta(total), + }; + }, + ); + + /** + * Equation schema node composer. + * @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes - + * @param {IProfitLossSchemaNode} node - + * @returns {IProfitLossSheetEquationNode} + */ + private equationSchemaNodeCompose = R.curry( + ( + accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[], + node: IProfitLossEquationSchemaNode, + ): IProfitLossSheetEquationNode => { + return R.compose( + R.when( + this.query.isPreviousPeriodActive, + this.previousPeriodEquationNodeCompose(accNodes, node.equation), + ), + R.when( + this.query.isPreviousYearActive, + this.previousYearEquationNodeCompose(accNodes, node.equation), + ), + R.when( + this.query.isDatePeriodsColumnsType, + this.assocEquationNodeDatePeriod(accNodes, node.equation), + ), + this.equationSchemaNodeParser(accNodes), + )(node); + }, + ); + + /** + * Parses accounts schema node to report node. + * @param {IProfitLossSchemaNode} schemaNode + * @returns {IProfitLossSheetNode | IProfitLossSchemaNode} + */ + private accountsSchemaNodeMap = ( + schemaNode: IProfitLossSchemaNode, + ): IProfitLossSheetNode | IProfitLossSchemaNode => { + return R.compose( + R.when( + this.isNodeType(ProfitLossNodeType.ACCOUNTS), + this.accountsSchemaNodeCompose, + ), + )(schemaNode); + }; + + /** + * Composes schema equation node to report node. + * @param {IProfitLossSheetNode | IProfitLossSchemaNode} node + * @param {number} key + * @param {IProfitLossSheetNode | IProfitLossSchemaNode} parentValue + * @param {(IProfitLossSheetNode | IProfitLossSchemaNode)[]} accNodes + * @param context + * @returns {IProfitLossSheetEquationNode} + */ + private reportSchemaEquationNodeCompose = ( + node: IProfitLossSheetNode | IProfitLossSchemaNode, + key: number, + parentValue: IProfitLossSheetNode | IProfitLossSchemaNode, + accNodes: (IProfitLossSheetNode | IProfitLossSchemaNode)[], + context, + ): IProfitLossSheetEquationNode => { + return R.compose( + R.when( + this.isNodeType(ProfitLossNodeType.EQUATION), + this.equationSchemaNodeCompose(accNodes), + ), + )(node); + }; + + /** + * Parses schema accounts nodes. + * @param {IProfitLossSchemaNode[]} + * @returns {(IProfitLossSheetNode | IProfitLossSchemaNode)[]} + */ + private reportSchemaAccountsNodesCompose = ( + schemaNodes: IProfitLossSchemaNode[], + ): (IProfitLossSheetNode | IProfitLossSchemaNode)[] => { + return this.mapNodesDeep(schemaNodes, this.accountsSchemaNodeMap); + }; + + /** + * Parses schema equation nodes. + * @param {(IProfitLossSheetNode | IProfitLossSchemaNode)[]} nodes + * @returns {(IProfitLossSheetNode | IProfitLossSchemaNode)[]} + */ + private reportSchemaEquationNodesCompose = ( + nodes: (IProfitLossSheetNode | IProfitLossSchemaNode)[], + ): (IProfitLossSheetNode | IProfitLossSchemaNode)[] => { + return this.mapAccNodesDeep(nodes, this.reportSchemaEquationNodeCompose); + }; + + /** + * Retrieve profit/loss report data. + * @return {IProfitLossSheetStatement} + */ + public reportData = (): Array => { + const schema = this.getSchema(); + + return R.compose( + this.reportFilterPlugin, + this.reportRowsPercentageCompose, + this.reportColumnsPerentageCompose, + this.reportSchemaEquationNodesCompose, + this.reportSchemaAccountsNodesCompose, + )(schema); + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.types.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.types.ts new file mode 100644 index 000000000..9eada0d4b --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.types.ts @@ -0,0 +1,195 @@ +import * as moment from 'moment'; +import { + IFinancialSheetBranchesQuery, + IFinancialSheetCommonMeta, + INumberFormatQuery, +} from '../../types/Report.types'; +import { IFinancialTable } from '../../types/Table.types'; + +export enum ProfitLossAggregateNodeId { + INCOME = 'INCOME', + COS = 'COST_OF_SALES', + GROSS_PROFIT = 'GROSS_PROFIT', + EXPENSES = 'EXPENSES', + OTHER_INCOME = 'OTHER_INCOME', + OTHER_EXPENSES = 'OTHER_EXPENSES', + OPERATING_PROFIT = 'OPERATING_PROFIT', + NET_OTHER_INCOME = 'NET_OTHER_INCOME', + NET_INCOME = 'NET_INCOME', + NET_OPERATING_INCOME = 'NET_OPERATING_INCOME', +} + +export enum ProfitLossNodeType { + EQUATION = 'EQUATION', + ACCOUNTS = 'ACCOUNTS', + ACCOUNT = 'ACCOUNT', + AGGREGATE = 'AGGREGATE', +} + +interface FinancialDateMeta { + date: Date; + formattedDate: string; +} + +export interface IFinancialNodeWithPreviousPeriod { + previousPeriodFromDate?: FinancialDateMeta; + previousPeriodToDate?: FinancialDateMeta; + + previousPeriod?: IProfitLossSheetTotal; + previousPeriodChange?: IProfitLossSheetTotal; + previousPeriodPercentage?: IProfitLossSheetPercentage; +} + +export interface IFinancialNodeWithPreviousYear { + previousYearFromDate: FinancialDateMeta; + previousYearToDate: FinancialDateMeta; + + previousYear?: IProfitLossSheetTotal; + previousYearChange?: IProfitLossSheetTotal; + previousYearPercentage?: IProfitLossSheetPercentage; +} + +export interface IFinancialCommonNode { + total: IProfitLossSheetTotal; +} + +export interface IFinancialCommonHorizDatePeriodNode { + fromDate: FinancialDateMeta; + toDate: FinancialDateMeta; + total: IProfitLossSheetTotal; +} + +export interface IProfitLossSheetQuery extends IFinancialSheetBranchesQuery { + basis: string; + + fromDate: moment.MomentInput; + toDate: moment.MomentInput; + + numberFormat: INumberFormatQuery; + noneZero: boolean; + noneTransactions: boolean; + accountsIds: number[]; + + displayColumnsType: 'total' | 'date_periods'; + displayColumnsBy: string; + + percentageColumn: boolean; + percentageRow: boolean; + + percentageIncome: boolean; + percentageExpense: boolean; + + previousPeriod: boolean; + previousPeriodAmountChange: boolean; + previousPeriodPercentageChange: boolean; + + previousYear: boolean; + previousYearAmountChange: boolean; + previousYearPercentageChange: boolean; +} + +export interface IProfitLossSheetTotal { + amount: number; + formattedAmount: string; + currencyCode: string; +} + +export interface IProfitLossSheetPercentage { + amount: number; + formattedAmount: string; +} + +export interface IProfitLossHorizontalDatePeriodNode + extends IFinancialNodeWithPreviousYear, + IFinancialNodeWithPreviousPeriod { + fromDate: FinancialDateMeta; + toDate: FinancialDateMeta; + + total: IProfitLossSheetTotal; + + percentageRow?: IProfitLossSheetPercentage; + percentageColumn?: IProfitLossSheetPercentage; +} + +export interface IProfitLossSheetCommonNode + extends IFinancialNodeWithPreviousYear, + IFinancialNodeWithPreviousPeriod { + id: ProfitLossAggregateNodeId; + name: string; + + children?: IProfitLossSheetNode[]; + + total: IProfitLossSheetTotal; + horizontalTotals?: IProfitLossHorizontalDatePeriodNode[]; + + percentageRow?: IProfitLossSheetPercentage; + percentageColumn?: IProfitLossSheetPercentage; +} +export interface IProfitLossSheetAccountNode + extends IProfitLossSheetCommonNode { + nodeType: ProfitLossNodeType.ACCOUNT; +} +export interface IProfitLossSheetEquationNode + extends IProfitLossSheetCommonNode { + nodeType: ProfitLossNodeType.EQUATION; +} + +export interface IProfitLossSheetAccountsNode + extends IProfitLossSheetCommonNode { + nodeType: ProfitLossNodeType.ACCOUNTS; +} + +export type IProfitLossSheetNode = + | IProfitLossSheetAccountsNode + | IProfitLossSheetEquationNode + | IProfitLossSheetAccountNode; + +export interface IProfitLossSheetMeta extends IFinancialSheetCommonMeta { + formattedDateRange: string; + formattedFromDate: string; + formattedToDate: string; +} + +// ------------------------------------------------ +// # SCHEMA NODES +// ------------------------------------------------ +export interface IProfitLossCommonSchemaNode { + id: ProfitLossAggregateNodeId; + name: string; + nodeType: ProfitLossNodeType; + children?: IProfitLossSchemaNode[]; + alwaysShow?: boolean; +} + +export interface IProfitLossEquationSchemaNode + extends IProfitLossCommonSchemaNode { + nodeType: ProfitLossNodeType.EQUATION; + equation: string; +} + +export interface IProfitLossAccountsSchemaNode + extends IProfitLossCommonSchemaNode { + nodeType: ProfitLossNodeType.ACCOUNTS; + accountsTypes: string[]; +} + +export type IProfitLossSchemaNode = + | IProfitLossCommonSchemaNode + | IProfitLossAccountsSchemaNode + | IProfitLossEquationSchemaNode; + +// ------------------------------ +// # Table +// ------------------------------ + +export enum ProfitLossSheetRowType { + AGGREGATE = 'AGGREGATE', + ACCOUNTS = 'ACCOUNTS', + ACCOUNT = 'ACCOUNT', + TOTAL = 'TOTAL', +} + +export interface IProfitLossSheetTable extends IFinancialTable { + meta: IProfitLossSheetMeta; + query: IProfitLossSheetQuery; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetApplication.ts new file mode 100644 index 000000000..79ff78b4f --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetApplication.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@nestjs/common'; +import { ProfitLossSheetExportInjectable } from './ProfitLossSheetExportInjectable'; +import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; +import { + IProfitLossSheetQuery, + IProfitLossSheetTable, +} from './ProfitLossSheet.types'; +import { ProfitLossTablePdfInjectable } from './ProfitLossTablePdfInjectable'; +import { ProfitLossSheetService } from './ProfitLossSheetService'; + +@Injectable() +export class ProfitLossSheetApplication { + constructor( + private readonly profitLossTable: ProfitLossSheetTableInjectable, + private readonly profitLossExport: ProfitLossSheetExportInjectable, + private readonly profitLossSheet: ProfitLossSheetService, + private readonly profitLossPdf: ProfitLossTablePdfInjectable, + ) {} + + /** + * Retrieves the profit/loss sheet. + * @param {IProfitLossSheetQuery} query + * @returns {} + */ + public sheet(query: IProfitLossSheetQuery) { + return this.profitLossSheet.profitLossSheet(query); + } + + /** + * Retrieves the profit/loss sheet table format. + * @param {IProfitLossSheetQuery} query - Profit/loss sheet query. + * @returns {Promise} + */ + public table(query: IProfitLossSheetQuery): Promise { + return this.profitLossTable.table(query); + } + + /** + * Retrieves the profit/loss sheet in csv format. + * @param {IProfitLossSheetQuery} query + * @returns {Promise} + */ + public csv(query: IProfitLossSheetQuery): Promise { + return this.profitLossExport.csv(query); + } + + /** + * Retrieves the profit/loss sheet in xlsx format. + * @param {IProfitLossSheetQuery} query + * @returns {Promise} + */ + public xlsx(query: IProfitLossSheetQuery): Promise { + return this.profitLossExport.xlsx(query); + } + + /** + * Retrieves the profit/loss sheet in pdf format. + * @param {IProfitLossSheetQuery} query + * @returns {Promise} + */ + public pdf(query: IProfitLossSheetQuery): Promise { + return this.profitLossPdf.pdf(query); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetBase.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetBase.ts new file mode 100644 index 000000000..534f71717 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetBase.ts @@ -0,0 +1,42 @@ +import * as R from 'ramda'; +import { TOTAL_NODE_TYPES } from './constants'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { GConstructor } from '@/common/types/Constructor'; + +export const ProfitLossSheetBase = >( + Base: T, +) => + class extends Base { + /** + * + * @param type + * @param node + * @returns + */ + public isNodeType = R.curry((type: string, node) => { + return node.nodeType === type; + }); + + /** + * + */ + protected isNodeTypeIn = R.curry((types: string[], node) => { + return types.indexOf(node.nodeType) !== -1; + }); + + /** + * + */ + protected findNodeById = R.curry((id, nodes) => { + return this.findNodeDeep(nodes, (node) => node.id === id); + }); + + /** + * + * @param node + * @returns + */ + isNodeTotal = (node) => { + return this.isNodeTypeIn(TOTAL_NODE_TYPES, node); + }; + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetDatePeriods.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetDatePeriods.ts new file mode 100644 index 000000000..a99a71946 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetDatePeriods.ts @@ -0,0 +1,249 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { sumBy } from 'lodash'; +import { + IProfitLossHorizontalDatePeriodNode, + IProfitLossSheetAccountNode, + IProfitLossSheetAccountsNode, + IProfitLossSheetCommonNode, + IProfitLossSheetNode, + IProfitLossSheetQuery, +} from './ProfitLossSheet.types'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; +import { IDateRange } from '../../types/Report.types'; +import { ProfitLossSheetRepository } from './ProfitLossSheetRepository'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; + +export const ProfitLossSheetDatePeriods = < + T extends GConstructor, +>( + Base: T, +) => + class extends R.pipe(FinancialDatePeriods)(Base) { + query: ProfitLossSheetQuery; + repository: ProfitLossSheetRepository; + + /** + * Retrieves the date periods based on the report query. + * @returns {IDateRange[]} + */ + get datePeriods(): IDateRange[] { + return this.getDateRanges( + this.query.fromDate, + this.query.toDate, + this.query.displayColumnsBy, + ); + } + + /** + * Retrieves the date periods of the given node based on the report query. + * @param {IProfitLossSheetCommonNode} node + * @param {Function} callback + * @returns {} + */ + protected getReportNodeDatePeriods = ( + node: IProfitLossSheetCommonNode, + callback: ( + node: IProfitLossSheetCommonNode, + fromDate: Date, + toDate: Date, + index: number, + ) => any, + ) => { + return this.getNodeDatePeriods( + this.query.fromDate, + this.query.toDate, + this.query.displayColumnsBy, + node, + callback, + ); + }; + + // -------------------------- + // # Account Nodes. + // -------------------------- + /** + * Retrieve account node date period total. + * @param {IProfitLossSheetAccount} node + * @param {Date} fromDate + * @param {Date} toDate + * @returns {} + */ + private getAccountNodeDatePeriodTotal = ( + node: IProfitLossSheetAccountNode, + fromDate: Date, + toDate: Date, + ) => { + const periodTotal = this.repository.periodsAccountsLedger + .whereAccountId(node.id) + .whereFromDate(fromDate) + .whereToDate(toDate) + .getClosingBalance(); + + return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate); + }; + + /** + * Retrieve account node date period. + * @param {IProfitLossSheetAccountNode} node + * @returns {IProfitLossSheetAccountNode} + */ + public getAccountNodeDatePeriod = (node: IProfitLossSheetAccountNode) => { + return this.getReportNodeDatePeriods( + node, + this.getAccountNodeDatePeriodTotal, + ); + }; + + /** + * Account date periods to the given account node. + * @param {IProfitLossSheetAccountNode} node + * @returns {IProfitLossSheetAccountNode} + */ + public assocAccountNodeDatePeriod = ( + node: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + const datePeriods = this.getAccountNodeDatePeriod(node); + + return R.assoc('horizontalTotals', datePeriods, node); + }; + + // -------------------------- + // # Aggregate nodes. + // -------------------------- + /** + * Retrieves sumation of the given aggregate node children totals. + * @param {IProfitLossSheetAccountsNode} node + * @param {number} index + * @returns {number} + */ + private getAggregateDatePeriodIndexTotal = ( + node: IProfitLossSheetAccountsNode, + index: number, + ): number => { + return sumBy(node.children, `horizontalTotals[${index}].total.amount`); + }; + + /** + * + * @param {IProfitLossSheetAccount} node + * @param {Date} fromDate + * @param {Date} toDate + * @param {number} index + * @returns {IProfitLossSheetAccount} + */ + private getAggregateNodeDatePeriodTotal = R.curry( + ( + node: IProfitLossSheetAccountsNode, + fromDate: Date, + toDate: Date, + index: number, + ): IProfitLossHorizontalDatePeriodNode => { + const periodTotal = this.getAggregateDatePeriodIndexTotal(node, index); + + return this.getDatePeriodTotalMeta(periodTotal, fromDate, toDate); + }, + ); + + /** + * Retrieves aggregate horizontal date periods. + * @param {IProfitLossSheetAccountsNode} node + * @returns {IProfitLossSheetAccountsNode} + */ + private getAggregateNodeDatePeriod = ( + node: IProfitLossSheetAccountsNode, + ): IProfitLossHorizontalDatePeriodNode[] => { + return this.getReportNodeDatePeriods( + node, + this.getAggregateNodeDatePeriodTotal, + ); + }; + + /** + * Assoc horizontal date periods to aggregate node. + * @param {IProfitLossSheetAccountsNode} node + * @returns {IProfitLossSheetAccountsNode} + */ + protected assocAggregateDatePeriod = ( + node: IProfitLossSheetAccountsNode, + ): IProfitLossSheetAccountsNode => { + const datePeriods = this.getAggregateNodeDatePeriod(node); + + return R.assoc('horizontalTotals', datePeriods, node); + }; + + // -------------------------- + // # Equation nodes. + // -------------------------- + /** + * Retrieves equation date period node. + * @param {IProfitLossSheetNode[]} accNodes + * @param {IProfitLossSheetNode} node + * @param {Date} fromDate + * @param {Date} toDate + * @param {number} index + * @returns {IProfitLossHorizontalDatePeriodNode} + */ + private getEquationNodeDatePeriod = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + node: IProfitLossSheetNode, + fromDate: Date, + toDate: Date, + index: number, + ): IProfitLossHorizontalDatePeriodNode => { + const tableNodes = this.getNodesTableForEvaluating( + `horizontalTotals[${index}].total.amount`, + accNodes, + ); + // Evaluate the given equation. + const total = this.evaluateEquation(equation, tableNodes); + + return this.getDatePeriodTotalMeta(total, fromDate, toDate); + }, + ); + + /** + * Retrieves the equation node date periods. + * @param {IProfitLossSheetNode[]} node + * @param {string} equation + * @param {IProfitLossSheetNode} node + * @returns {IProfitLossHorizontalDatePeriodNode[]} + */ + private getEquationNodeDatePeriods = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + node: IProfitLossSheetNode, + ): IProfitLossHorizontalDatePeriodNode[] => { + return this.getReportNodeDatePeriods( + node, + this.getEquationNodeDatePeriod(accNodes, equation), + ); + }, + ); + + /** + * Assoc equation node date period. + * @param {IProfitLossSheetNode[]} + * @param {IProfitLossSheetNode} node + * @returns {IProfitLossSheetNode} + */ + protected assocEquationNodeDatePeriod = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + node: IProfitLossSheetNode, + ): IProfitLossSheetNode => { + const periods = this.getEquationNodeDatePeriods( + accNodes, + equation, + node, + ); + return R.assoc('horizontalTotals', periods, node); + }, + ); + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetExportInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetExportInjectable.ts new file mode 100644 index 000000000..13f26d0b0 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetExportInjectable.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; + +import { TableSheet } from '../../common/TableSheet'; +import { IProfitLossSheetQuery } from './ProfitLossSheet.types'; +import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; + +@Injectable() +export class ProfitLossSheetExportInjectable { + constructor( + private readonly profitLossSheetTable: ProfitLossSheetTableInjectable, + ) {} + + /** + * Retrieves the profit/loss sheet in XLSX format. + * @param {IProfitLossSheetQuery} query - The profit/loss sheet query. + * @returns {Promise} + */ + public async xlsx(query: IProfitLossSheetQuery) { + const table = await this.profitLossSheetTable.table(query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToXLSX(); + + return tableSheet.convertToBuffer(tableCsv, 'xlsx'); + } + + /** + * Retrieves the profit/loss sheet in CSV format. + * @param {IProfitLossSheetQuery} query + * @returns {Promise} + */ + public async csv(query: IProfitLossSheetQuery): Promise { + const table = await this.profitLossSheetTable.table(query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToCSV(); + + return tableCsv; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetFilter.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetFilter.ts new file mode 100644 index 000000000..eeead8058 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetFilter.ts @@ -0,0 +1,179 @@ +import * as R from 'ramda'; +import { get } from 'lodash'; +import { ProfitLossSheetBase } from './ProfitLossSheetBase'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { FinancialFilter } from '../../common/FinancialFilter'; +import { + IProfitLossSheetNode, + ProfitLossNodeType, +} from './ProfitLossSheet.types'; +import { ProfitLossSheetRepository } from './ProfitLossSheetRepository'; + +export const ProfitLossSheetFilter = >( + Base: T, +) => + class extends R.pipe(FinancialFilter, ProfitLossSheetBase)(Base) { + query: ProfitLossSheetQuery; + repository: ProfitLossSheetRepository; + + // ---------------- + // # Account. + // ---------------- + /** + * Filter report node detarmine. + * @param {IProfitLossSheetNode} node - Balance sheet node. + * @return {boolean} + */ + private accountNoneZeroNodesFilterDetarminer = ( + node: IProfitLossSheetNode, + ): boolean => { + return R.ifElse( + this.isNodeType(ProfitLossNodeType.ACCOUNT), + this.isNodeNoneZero, + R.always(true), + )(node); + }; + + /** + * Determines account none-transactions node. + * @param {IBalanceSheetDataNode} node + * @returns {boolean} + */ + private accountNoneTransFilterDetarminer = ( + node: IProfitLossSheetNode, + ): boolean => { + return R.ifElse( + this.isNodeType(ProfitLossNodeType.ACCOUNT), + this.isNodeNoneZero, + R.always(true), + )(node); + }; + + /** + * Report nodes filter. + * @param {IProfitLossSheetNode[]} nodes - + * @return {IProfitLossSheetNode[]} + */ + private accountsNoneZeroNodesFilter = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + return this.filterNodesDeep( + nodes, + this.accountNoneZeroNodesFilterDetarminer, + ); + }; + + /** + * Filters the accounts none-transactions nodes. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private accountsNoneTransactionsNodesFilter = ( + nodes: IProfitLossSheetNode[], + ) => { + return this.filterNodesDeep(nodes, this.accountNoneTransFilterDetarminer); + }; + + // ---------------- + // # Aggregate. + // ---------------- + /** + * Determines aggregate none-children filtering. + * @param {IProfitLossSheetNode} node + * @returns {boolean} + */ + private aggregateNoneChildrenFilterDetarminer = ( + node: IProfitLossSheetNode, + ): boolean => { + const schemaNode = this.getSchemaNodeById(node.id); + + // Determines whether the given node is aggregate node. + const isAggregateNode = this.isNodeType( + ProfitLossNodeType.ACCOUNTS, + node, + ); + // Determines if the schema node is always should show. + const isSchemaAlwaysShow = get(schemaNode, 'alwaysShow', false); + + // Should node has children if aggregate node or not always show. + return isAggregateNode && !isSchemaAlwaysShow + ? this.isNodeHasChildren(node) + : true; + }; + + /** + * Filters aggregate none-children nodes. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private aggregateNoneChildrenFilter = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + return this.filterNodesDeep2( + this.aggregateNoneChildrenFilterDetarminer, + nodes, + ); + }; + + // ---------------- + // # Composers. + // ---------------- + /** + * Filters none-zero nodes. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private filterNoneZeroNodesCompose = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + return R.compose( + this.aggregateNoneChildrenFilter, + this.accountsNoneZeroNodesFilter, + )(nodes); + }; + + /** + * Filters none-transactions nodes. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private filterNoneTransNodesCompose = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + return R.compose( + this.aggregateNoneChildrenFilter, + this.accountsNoneTransactionsNodesFilter, + )(nodes); + }; + + /** + * Supress nodes when total accounts range transactions is empty. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private supressNodesWhenRangeTransactionsEmpty = ( + nodes: IProfitLossSheetNode[], + ) => { + return this.repository.totalAccountsLedger.isEmpty() ? [] : nodes; + }; + + /** + * Compose report nodes filtering. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + protected reportFilterPlugin = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + return R.compose( + this.supressNodesWhenRangeTransactionsEmpty, + R.when(() => this.query.noneZero, this.filterNoneZeroNodesCompose), + R.when( + () => this.query.noneTransactions, + this.filterNoneTransNodesCompose, + ), + )(nodes); + }; + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetMeta.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetMeta.ts new file mode 100644 index 000000000..8a37f21d8 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetMeta.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import * as moment from 'moment'; +import { + IProfitLossSheetMeta, + IProfitLossSheetQuery, +} from './ProfitLossSheet.types'; +import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; + +@Injectable() +export class ProfitLossSheetMeta { + constructor(private readonly financialSheetMeta: FinancialSheetMeta) {} + + /** + * Retrieve the P/L sheet meta. + * @param {IProfitLossSheetQuery} query - P/L sheet query. + * @returns {Promise} + */ + public async meta( + query: IProfitLossSheetQuery, + ): Promise { + const commonMeta = await this.financialSheetMeta.meta(); + const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); + const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); + const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; + + const sheetName = 'Cashflow Statement'; + + return { + ...commonMeta, + sheetName, + formattedFromDate, + formattedToDate, + formattedDateRange, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPercentage.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPercentage.ts new file mode 100644 index 000000000..eb246a277 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPercentage.ts @@ -0,0 +1,310 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { GConstructor } from '@/common/types/Constructor'; +import { + IProfitLossSheetNode, + ProfitLossAggregateNodeId, +} from './ProfitLossSheet.types'; +import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; + +export const ProfitLossSheetPercentage = < + T extends GConstructor, +>( + Base: T, +) => + class extends R.pipe(FinancialHorizTotals)(Base) { + query: ProfitLossSheetQuery; + + /** + * Assoc column of percentage attribute to the given node. + * @param {IProfitLossSheetNode} netIncomeNode - + * @param {IProfitLossSheetNode} node - + * @return {IProfitLossSheetNode} + */ + private assocColumnPercentage = R.curry( + ( + propertyPath: string, + parentNode: IProfitLossSheetNode, + node: IProfitLossSheetNode, + ) => { + const percentage = this.getPercentageBasis( + parentNode.total.amount, + node.total.amount, + ); + return R.assoc( + propertyPath, + this.getPercentageAmountMeta(percentage), + node, + ); + }, + ); + + /** + * Assoc column of percentage attribute to the given node. + * @param {IProfitLossSheetNode} netIncomeNode - + * @param {IProfitLossSheetNode} node - + * @return {IProfitLossSheetNode} + */ + private assocColumnTotalPercentage = R.curry( + ( + propertyPath: string, + parentNode: IProfitLossSheetNode, + node: IProfitLossSheetNode, + ) => { + const percentage = this.getPercentageBasis( + parentNode.total.amount, + node.total.amount, + ); + return R.assoc( + propertyPath, + this.getPercentageTotalAmountMeta(percentage), + node, + ); + }, + ); + + /** + * Compose percentage of columns. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private columnPercentageCompose = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + const netIncomeNode = this.findNodeById( + ProfitLossAggregateNodeId.NET_INCOME, + nodes, + ); + return this.mapNodesDeep( + nodes, + this.columnPercentageMapper(netIncomeNode), + ); + }; + + /** + * Compose percentage of income. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private incomePercetageCompose = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + const incomeNode = this.findNodeById( + ProfitLossAggregateNodeId.INCOME, + nodes, + ); + return this.mapNodesDeep(nodes, this.incomePercentageMapper(incomeNode)); + }; + + /** + * + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private rowPercentageCompose = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + return this.mapNodesDeep(nodes, this.rowPercentageMap); + }; + + /** + * + * @param {IProfitLossSheetNode} netIncomeNode - + * @param {IProfitLossSheetNode} node - + * @return {IProfitLossSheetNode} + */ + private columnPercentageMapper = R.curry( + (netIncomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => { + const path = 'percentageColumn'; + + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocColumnPercentageHorizTotals(netIncomeNode), + ), + R.ifElse( + this.isNodeTotal, + this.assocColumnTotalPercentage(path, netIncomeNode), + this.assocColumnPercentage(path, netIncomeNode), + ), + )(node); + }, + ); + + /** + * + * @param {IProfitLossSheetNode} node + * @returns {IProfitLossSheetNode} + */ + private rowPercentageMap = ( + node: IProfitLossSheetNode, + ): IProfitLossSheetNode => { + const path = 'percentageRow'; + + return R.compose( + R.when(this.isNodeHasHorizTotals, this.assocRowPercentageHorizTotals), + R.ifElse( + this.isNodeTotal, + this.assocColumnTotalPercentage(path, node), + this.assocColumnPercentage(path, node), + ), + )(node); + }; + + /** + * + * @param {IProfitLossSheetNode} incomeNode - + * @param {IProfitLossSheetNode} node - + * @returns {IProfitLossSheetNode} + */ + private incomePercentageMapper = R.curry( + (incomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => { + const path = 'percentageIncome'; + + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocIncomePercentageHorizTotals(incomeNode), + ), + R.ifElse( + this.isNodeTotal, + this.assocColumnTotalPercentage(path, incomeNode), + this.assocColumnPercentage(path, incomeNode), + ), + )(node); + }, + ); + + /** + * + * @param {IProfitLossSheetNode} expenseNode - + * @param {IProfitLossSheetNode} node - + */ + private expensePercentageMapper = R.curry( + (expenseNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => { + const path = 'percentageExpense'; + + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocExpensePercentageHorizTotals(expenseNode), + ), + R.ifElse( + this.isNodeTotal, + this.assocColumnTotalPercentage(path, expenseNode), + this.assocColumnPercentage(path, expenseNode), + ), + )(node); + }, + ); + + /** + * Compose percentage of expense. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + private expensesPercentageCompose = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + const expenseNode = this.findNodeById( + ProfitLossAggregateNodeId.EXPENSES, + nodes, + ); + return this.mapNodesDeep( + nodes, + this.expensePercentageMapper(expenseNode), + ); + }; + + /** + * Compose percentage attributes. + * @param {IProfitLossSheetNode[]} nodes + * @returns {IProfitLossSheetNode[]} + */ + protected reportColumnsPerentageCompose = ( + nodes: IProfitLossSheetNode[], + ): IProfitLossSheetNode[] => { + return R.compose( + R.when(this.query.isIncomePercentage, this.incomePercetageCompose), + R.when(this.query.isColumnPercentage, this.columnPercentageCompose), + R.when(this.query.isExpensesPercentage, this.expensesPercentageCompose), + R.when(this.query.isRowPercentage, this.rowPercentageCompose), + )(nodes); + }; + + /** + * + * @param {} nodes + * @returns {} + */ + protected reportRowsPercentageCompose = (nodes) => { + return nodes; + }; + + // ---------------------------------- + // # Horizontal Nodes + // ---------------------------------- + /** + * Assoc incomer percentage to horizontal totals nodes. + * @param {IProfitLossSheetNode} incomeNode - + * @param {IProfitLossSheetNode} node - + * @returns {IProfitLossSheetNode} + */ + private assocIncomePercentageHorizTotals = R.curry( + (incomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => { + const horTotalsWithIncomePerc = this.assocPercentageHorizTotals( + 'percentageIncome', + incomeNode, + node, + ); + return R.assoc('horizontalTotals', horTotalsWithIncomePerc, node); + }, + ); + + /** + * Assoc expense percentage to horizontal totals nodes. + * @param {IProfitLossSheetNode} expenseNode - + * @param {IProfitLossSheetNode} node - + * @returns {IProfitLossSheetNode} + */ + private assocExpensePercentageHorizTotals = R.curry( + (expenseNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => { + const horTotalsWithExpensePerc = this.assocPercentageHorizTotals( + 'percentageExpense', + expenseNode, + node, + ); + return R.assoc('horizontalTotals', horTotalsWithExpensePerc, node); + }, + ); + + /** + * Assoc net income percentage to horizontal totals nodes. + * @param {IProfitLossSheetNode} expenseNode - + * @param {IProfitLossSheetNode} node - + * @returns {IProfitLossSheetNode} + */ + private assocColumnPercentageHorizTotals = R.curry( + (netIncomeNode: IProfitLossSheetNode, node: IProfitLossSheetNode) => { + const horTotalsWithExpensePerc = this.assocPercentageHorizTotals( + 'percentageColumn', + netIncomeNode, + node, + ); + return R.assoc('horizontalTotals', horTotalsWithExpensePerc, node); + }, + ); + + /** + * + */ + private assocRowPercentageHorizTotals = R.curry((node) => { + const horTotalsWithExpensePerc = this.assocHorizontalPercentageTotals( + 'percentageRow', + node, + ); + return R.assoc('horizontalTotals', horTotalsWithExpensePerc, node); + }); + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousPeriod.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousPeriod.ts new file mode 100644 index 000000000..582a8b107 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousPeriod.ts @@ -0,0 +1,404 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { sumBy } from 'lodash'; +import { + IProfitLossHorizontalDatePeriodNode, + IProfitLossSchemaNode, + IProfitLossSheetAccountNode, + IProfitLossSheetAccountsNode, + IProfitLossSheetEquationNode, + IProfitLossSheetNode, +} from './ProfitLossSheet.types'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; +import { ProfitLossSheetRepository } from './ProfitLossSheetRepository'; + +export const ProfitLossSheetPreviousPeriod = < + T extends GConstructor, +>( + Base: T, +) => + class extends R.pipe(FinancialPreviousPeriod)(Base) { + query: ProfitLossSheetQuery; + repository: ProfitLossSheetRepository; + + // --------------------------- + // # Account + // --------------------------- + /** + * Assoc previous period change attribute to account node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + protected assocPreviousPeriodTotalAccountNode = ( + node: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + const total = this.repository.PPTotalAccountsLedger.whereAccountId( + node.id, + ).getClosingBalance(); + + return R.assoc('previousPeriod', this.getAmountMeta(total), node); + }; + + /** + * Compose previous period account node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + protected previousPeriodAccountNodeCompose = ( + accountNode: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocPreviousPeriodAccountHorizNodeCompose, + ), + R.when( + this.query.isPreviousPeriodPercentageActive, + this.assocPreviousPeriodPercentageNode, + ), + R.when( + this.query.isPreviousPeriodChangeActive, + this.assocPreviousPeriodChangeNode, + ), + this.assocPreviousPeriodTotalAccountNode, + )(accountNode); + }; + + // --------------------------- + // # Aggregate + // --------------------------- + /** + * Assoc previous period total attribute to aggregate node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + private assocPreviousPeriodTotalAggregateNode = ( + node: IProfitLossSheetAccountNode, + ) => { + const total = sumBy(node.children, 'previousPeriod.amount'); + + return R.assoc('previousPeriod', this.getTotalAmountMeta(total), node); + }; + + /** + * Compose previous period to aggregate node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + protected previousPeriodAggregateNodeCompose = ( + accountNode: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocPreviousPeriodAggregateHorizNode, + ), + R.when( + this.query.isPreviousPeriodPercentageActive, + this.assocPreviousPeriodTotalPercentageNode, + ), + R.when( + this.query.isPreviousPeriodChangeActive, + this.assocPreviousPeriodTotalChangeNode, + ), + this.assocPreviousPeriodTotalAggregateNode, + )(accountNode); + }; + + // --------------------------- + // # Equation + // -------------------------- + /** + * + * @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes + * @param {string} equation + * @param {IProfitLossSheetNode} node + * @returns {IProfitLossSheetEquationNode} + */ + private assocPreviousPeriodTotalEquationNode = R.curry( + ( + accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[], + equation: string, + node: IProfitLossSheetEquationNode, + ): IProfitLossSheetEquationNode => { + const previousPeriodNodePath = 'previousPeriod.amount'; + const tableNodes = this.getNodesTableForEvaluating( + previousPeriodNodePath, + accNodes, + ); + // Evaluate the given equation. + const total = this.evaluateEquation(equation, tableNodes); + + return R.assoc('previousPeriod', this.getTotalAmountMeta(total), node); + }, + ); + + /** + * + * @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes - + * @param {string} node + * @param {IProfitLossSheetEquationNode} node + * @returns {IProfitLossSheetEquationNode} + */ + protected previousPeriodEquationNodeCompose = R.curry( + ( + accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[], + equation: string, + node: IProfitLossSheetEquationNode, + ): IProfitLossSheetEquationNode => { + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocPreviousPeriodEquationHorizNode(accNodes, equation), + ), + R.when( + this.query.isPreviousPeriodPercentageActive, + this.assocPreviousPeriodTotalPercentageNode, + ), + R.when( + this.query.isPreviousPeriodChangeActive, + this.assocPreviousPeriodTotalChangeNode, + ), + this.assocPreviousPeriodTotalEquationNode(accNodes, equation), + )(node); + }, + ); + + // --------------------------- + // # Horizontal Nodes - Account + // -------------------------- + /** + * Assoc previous period to account horizontal node. + * @param {IProfitLossSheetAccountNode} node + * @param {IProfitLossHorizontalDatePeriodNode} totalNode + * @returns {IProfitLossHorizontalDatePeriodNode} + */ + private assocPerviousPeriodAccountHorizTotal = R.curry( + ( + node: IProfitLossSheetAccountNode, + totalNode: IProfitLossHorizontalDatePeriodNode, + ): IProfitLossHorizontalDatePeriodNode => { + const total = this.repository.PPPeriodsAccountsLedger.whereAccountId( + node.id, + ) + .whereFromDate(totalNode.previousPeriodFromDate.date) + .whereToDate(totalNode.previousPeriodToDate.date) + .getClosingBalance(); + + return R.assoc('previousPeriod', this.getAmountMeta(total), totalNode); + }, + ); + + /** + * @param {IProfitLossSheetAccountNode} node + * @param {IProfitLossSheetTotal} + */ + private previousPeriodAccountHorizNodeCompose = R.curry( + ( + node: IProfitLossSheetAccountNode, + horizontalTotalNode: IProfitLossHorizontalDatePeriodNode, + index: number, + ): IProfitLossHorizontalDatePeriodNode => { + return R.compose( + R.when( + this.query.isPreviousPeriodPercentageActive, + this.assocPreviousPeriodPercentageNode, + ), + R.when( + this.query.isPreviousPeriodChangeActive, + this.assocPreviousPeriodChangeNode, + ), + this.assocPerviousPeriodAccountHorizTotal(node), + this.assocPreviousPeriodHorizNodeFromToDates( + this.query.displayColumnsBy, + ), + )(horizontalTotalNode); + }, + ); + + /** + * + * @param {IProfitLossSheetAccountNode} node + * @returns {IProfitLossSheetAccountNode} + */ + private assocPreviousPeriodAccountHorizNodeCompose = ( + node: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + const horizontalTotals = R.addIndex(R.map)( + this.previousPeriodAccountHorizNodeCompose(node), + node.horizontalTotals, + ); + return R.assoc('horizontalTotals', horizontalTotals, node); + }; + + // ---------------------------------- + // # Horizontal Nodes - Aggregate + // ---------------------------------- + /** + * Assoc previous period total to aggregate horizontal nodes. + * @param {IProfitLossSheetAccountsNode} node + * @param {number} index + * @param {any} totalNode + * @return {} + */ + private assocPreviousPeriodAggregateHorizTotal = R.curry( + ( + node: IProfitLossSheetAccountsNode, + index: number, + totalNode: IProfitLossHorizontalDatePeriodNode, + ) => { + const total = this.getPPHorizNodesTotalSumation(index, node); + + return R.assoc( + 'previousPeriod', + this.getTotalAmountMeta(total), + totalNode, + ); + }, + ); + + /** + * + * @param {IProfitLossSheetAccountsNode} node + * @param {IProfitLossHorizontalDatePeriodNode} horizontalTotalNode - + * @param {number} index + * @returns {IProfitLossHorizontalDatePeriodNode} + */ + private previousPeriodAggregateHorizNodeCompose = R.curry( + ( + node: IProfitLossSheetAccountsNode, + horizontalTotalNode: IProfitLossHorizontalDatePeriodNode, + index: number, + ): IProfitLossHorizontalDatePeriodNode => { + return R.compose( + R.when( + this.query.isPreviousPeriodPercentageActive, + this.assocPreviousPeriodTotalPercentageNode, + ), + R.when( + this.query.isPreviousPeriodChangeActive, + this.assocPreviousPeriodTotalChangeNode, + ), + R.when( + this.query.isPreviousPeriodActive, + this.assocPreviousPeriodAggregateHorizTotal(node, index), + ), + R.when( + this.query.isPreviousPeriodActive, + this.assocPreviousPeriodHorizNodeFromToDates( + this.query.displayColumnsBy, + ), + ), + )(horizontalTotalNode); + }, + ); + + /** + * Assoc previous period to aggregate horizontal nodes. + * @param {IProfitLossSheetAccountsNode} node + * @returns + */ + private assocPreviousPeriodAggregateHorizNode = ( + node: IProfitLossSheetAccountsNode, + ): IProfitLossSheetAccountsNode => { + const horizontalTotals = R.addIndex(R.map)( + this.previousPeriodAggregateHorizNodeCompose(node), + node.horizontalTotals, + ); + return R.assoc('horizontalTotals', horizontalTotals, node); + }; + + // ---------------------------------- + // # Horizontal Nodes - Equation + // ---------------------------------- + /** + * + * @param {IProfitLossSheetNode[]} accNodes - + * @param {string} equation + * @param {index} number + * @param {} totalNode + */ + private assocPreviousPeriodEquationHorizTotal = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + index: number, + totalNode, + ): IProfitLossSheetNode => { + const scopes = this.getNodesTableForEvaluating( + `horizontalTotals[${index}].previousPeriod.amount`, + accNodes, + ); + const total = this.evaluateEquation(equation, scopes); + + return R.assoc( + 'previousPeriod', + this.getTotalAmountMeta(total), + totalNode, + ); + }, + ); + + /** + * + * @param {IProfitLossSheetNode[]} accNodes - + * @param {string} equation + * @param {} horizontalTotalNode + * @param {number} index + */ + private previousPeriodEquationHorizNodeCompose = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + horizontalTotalNode, + index: number, + ) => { + const assocHorizTotal = this.assocPreviousPeriodEquationHorizTotal( + accNodes, + equation, + index, + ); + return R.compose( + R.when( + this.query.isPreviousPeriodPercentageActive, + this.assocPreviousPeriodTotalPercentageNode, + ), + R.when( + this.query.isPreviousPeriodChangeActive, + this.assocPreviousPeriodTotalChangeNode, + ), + R.when(this.query.isPreviousPeriodActive, assocHorizTotal), + R.when( + this.query.isPreviousPeriodActive, + this.assocPreviousPeriodHorizNodeFromToDates( + this.query.displayColumnsBy, + ), + ), + )(horizontalTotalNode); + }, + ); + + /** + * Assoc previous period equation to horizontal nodes. + * @parma {IProfitLossSheetNode[]} accNodes - + * @param {string} equation + * @param {IProfitLossSheetEquationNode} node + * @return {IProfitLossSheetEquationNode} + */ + private assocPreviousPeriodEquationHorizNode = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + node: IProfitLossSheetEquationNode, + ): IProfitLossSheetEquationNode => { + const horizontalTotals = R.addIndex(R.map)( + this.previousPeriodEquationHorizNodeCompose(accNodes, equation), + node.horizontalTotals, + ); + return R.assoc('horizontalTotals', horizontalTotals, node); + }, + ); + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousYear.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousYear.ts new file mode 100644 index 000000000..2b4ca2000 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetPreviousYear.ts @@ -0,0 +1,376 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { sumBy } from 'lodash'; +import { + IProfitLossSheetEquationNode, + IProfitLossSheetAccountNode, + IProfitLossSchemaNode, + IProfitLossSheetNode, + IProfitLossSheetTotal, + IProfitLossSheetQuery, +} from './ProfitLossSheet.types'; +import { ProfitLossSheetRepository } from './ProfitLossSheetRepository'; +import { FinancialPreviousYear } from '../../common/FinancialPreviousYear'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; + +export const ProfitLossSheetPreviousYear = < + T extends GConstructor, +>( + Base: T, +) => + class extends R.pipe(FinancialPreviousYear)(Base) { + repository: ProfitLossSheetRepository; + query: ProfitLossSheetQuery; + + // --------------------------- + // # Account + // --------------------------- + /** + * Assoc previous year total attribute to account node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + private assocPreviousYearTotalAccountNode = ( + accountNode: IProfitLossSheetAccountNode, + ) => { + const total = this.repository.PYTotalAccountsLedger.whereAccountId( + accountNode.id, + ).getClosingBalance(); + + return R.assoc('previousYear', this.getAmountMeta(total), accountNode); + }; + + /** + * Compose previous year account node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + protected previousYearAccountNodeCompose = ( + accountNode: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocPreviousYearAccountHorizNodeCompose, + ), + R.when( + this.query.isPreviousYearPercentageActive, + this.assocPreviousYearPercentageNode, + ), + R.when( + this.query.isPreviousYearChangeActive, + this.assocPreviousYearChangetNode, + ), + this.assocPreviousYearTotalAccountNode, + )(accountNode); + }; + + // --------------------------- + // # Aggregate + // --------------------------- + /** + * Assoc previous year change attribute to aggregate node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + private assocPreviousYearTotalAggregateNode = ( + node: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + const total = sumBy(node.children, 'previousYear.amount'); + + return R.assoc('previousYear', this.getTotalAmountMeta(total), node); + }; + + /** + * Compose previous year to aggregate node. + * @param {IProfitLossSheetAccountNode} accountNode + * @returns {IProfitLossSheetAccountNode} + */ + protected previousYearAggregateNodeCompose = ( + accountNode: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocPreviousYearAggregateHorizNode, + ), + R.when( + this.query.isPreviousYearPercentageActive, + this.assocPreviousYearTotalPercentageNode, + ), + R.when( + this.query.isPreviousYearChangeActive, + this.assocPreviousYearTotalChangeNode, + ), + this.assocPreviousYearTotalAggregateNode, + )(accountNode); + }; + + // --------------------------- + // # Equation + // --------------------------- + /** + * Assoc previous year total to equation node. + * @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes + * @param {string} equation + * @param {IProfitLossSheetNode} node + * @returns {IProfitLossSheetEquationNode} + */ + private assocPreviousYearTotalEquationNode = R.curry( + ( + accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[], + equation: string, + node: IProfitLossSheetNode, + ) => { + const previousPeriodNodePath = 'previousYear.amount'; + const tableNodes = this.getNodesTableForEvaluating( + previousPeriodNodePath, + accNodes, + ); + // Evaluate the given equation. + const total = this.evaluateEquation(equation, tableNodes); + + return R.assoc('previousYear', this.getTotalAmountMeta(total), node); + }, + ); + + /** + * Previous year equation node. + * @param {(IProfitLossSchemaNode | IProfitLossSheetNode)[]} accNodes - + * @param {string} node + * @param {IProfitLossSheetEquationNode} node + * @returns {IProfitLossSheetEquationNode} + */ + protected previousYearEquationNodeCompose = R.curry( + ( + accNodes: (IProfitLossSchemaNode | IProfitLossSheetNode)[], + equation: string, + node: IProfitLossSheetEquationNode, + ) => { + return R.compose( + R.when( + this.isNodeHasHorizTotals, + this.assocPreviousYearEquationHorizNode(accNodes, equation), + ), + R.when( + this.query.isPreviousYearPercentageActive, + this.assocPreviousYearTotalPercentageNode, + ), + R.when( + this.query.isPreviousYearChangeActive, + this.assocPreviousYearTotalChangeNode, + ), + this.assocPreviousYearTotalEquationNode(accNodes, equation), + )(node); + }, + ); + + // ---------------------------------- + // # Horizontal Nodes - Account + // ---------------------------------- + /** + * Assoc preivous year to account horizontal total node. + * @param {IProfitLossSheetAccountNode} node + * @returns + */ + private assocPreviousYearAccountHorizTotal = R.curry( + (node: IProfitLossSheetAccountNode, totalNode) => { + const total = this.repository.PYPeriodsAccountsLedger.whereAccountId( + node.id, + ) + .whereFromDate(totalNode.previousYearFromDate.date) + .whereToDate(totalNode.previousYearToDate.date) + .getClosingBalance(); + + return R.assoc('previousYear', this.getAmountMeta(total), totalNode); + }, + ); + + /** + * Previous year account horizontal node composer. + * @param {IProfitLossSheetAccountNode} horizontalTotalNode + * @param {IProfitLossSheetTotal} horizontalTotalNode - + * @returns {IProfitLossSheetTotal} + */ + private previousYearAccountHorizNodeCompose = R.curry( + ( + node: IProfitLossSheetAccountNode, + horizontalTotalNode: IProfitLossSheetTotal, + ): IProfitLossSheetTotal => { + return R.compose( + R.when( + this.query.isPreviousYearPercentageActive, + this.assocPreviousYearPercentageNode, + ), + R.when( + this.query.isPreviousYearChangeActive, + this.assocPreviousYearChangetNode, + ), + R.when( + this.query.isPreviousYearActive, + this.assocPreviousYearAccountHorizTotal(node), + ), + R.when( + this.query.isPreviousYearActive, + this.assocPreviousYearHorizNodeFromToDates, + ), + )(horizontalTotalNode); + }, + ); + + /** + * + * @param {IProfitLossSheetAccountNode} node + * @returns {IProfitLossSheetAccountNode} + */ + private assocPreviousYearAccountHorizNodeCompose = ( + node: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + const horizontalTotals = R.map( + this.previousYearAccountHorizNodeCompose(node), + node.horizontalTotals, + ); + return R.assoc('horizontalTotals', horizontalTotals, node); + }; + + // ---------------------------------- + // # Horizontal Nodes - Aggregate + // ---------------------------------- + /** + * + */ + private assocPreviousYearAggregateHorizTotal = R.curry( + (node, index, totalNode) => { + const total = this.getPYHorizNodesTotalSumation(index, node); + + return R.assoc( + 'previousYear', + this.getTotalAmountMeta(total), + totalNode, + ); + }, + ); + + /** + * + */ + private previousYearAggregateHorizNodeCompose = R.curry( + (node, horizontalTotalNode, index: number) => { + return R.compose( + R.when( + this.query.isPreviousYearPercentageActive, + this.assocPreviousYearTotalPercentageNode, + ), + R.when( + this.query.isPreviousYearChangeActive, + this.assocPreviousYearTotalChangeNode, + ), + R.when( + this.query.isPreviousYearActive, + this.assocPreviousYearAggregateHorizTotal(node, index), + ), + )(horizontalTotalNode); + }, + ); + + /** + * + * @param {IProfitLossSheetAccountNode} node + * @returns {IProfitLossSheetAccountNode} + */ + private assocPreviousYearAggregateHorizNode = ( + node: IProfitLossSheetAccountNode, + ): IProfitLossSheetAccountNode => { + const horizontalTotals = R.addIndex(R.map)( + this.previousYearAggregateHorizNodeCompose(node), + node.horizontalTotals, + ); + return R.assoc('horizontalTotals', horizontalTotals, node); + }; + + // ---------------------------------- + // # Horizontal Nodes - Equation + // ---------------------------------- + /** + * + * @param {IProfitLossSheetNode[]} accNodes - + * @param {string} equation + * @param {number} index + * @param {} totalNode - + */ + private assocPreviousYearEquationHorizTotal = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + index: number, + totalNode, + ) => { + const scopes = this.getNodesTableForEvaluating( + `horizontalTotals[${index}].previousYear.amount`, + accNodes, + ); + const total = this.evaluateEquation(equation, scopes); + + return R.assoc( + 'previousYear', + this.getTotalAmountMeta(total), + totalNode, + ); + }, + ); + + /** + * + * @param {IProfitLossSheetNode[]} accNodes - + * @param {string} equation + * @param {} horizontalTotalNode + * @param {number} index + */ + private previousYearEquationHorizNodeCompose = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + horizontalTotalNode, + index: number, + ) => { + const assocHorizTotal = this.assocPreviousYearEquationHorizTotal( + accNodes, + equation, + index, + ); + return R.compose( + R.when( + this.query.isPreviousYearPercentageActive, + this.assocPreviousYearTotalPercentageNode, + ), + R.when( + this.query.isPreviousYearChangeActive, + this.assocPreviousYearTotalChangeNode, + ), + R.when(this.query.isPreviousYearActive, assocHorizTotal), + )(horizontalTotalNode); + }, + ); + + /** + * + * @param {IProfitLossSheetNode[]} accNodes + * @param {string} equation + * @param {IProfitLossSheetEquationNode} node + */ + private assocPreviousYearEquationHorizNode = R.curry( + ( + accNodes: IProfitLossSheetNode[], + equation: string, + node: IProfitLossSheetEquationNode, + ) => { + const horizontalTotals = R.addIndex(R.map)( + this.previousYearEquationHorizNodeCompose(accNodes, equation), + node.horizontalTotals, + ); + return R.assoc('horizontalTotals', horizontalTotals, node); + }, + ); + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetQuery.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetQuery.ts new file mode 100644 index 000000000..0a94e2798 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetQuery.ts @@ -0,0 +1,210 @@ +import { merge } from 'lodash'; +import * as R from 'ramda'; +import { IProfitLossSheetQuery } from './ProfitLossSheet.types'; +import { FinancialDateRanges } from '../../common/FinancialDateRanges'; +import { IFinancialDatePeriodsUnit } from '../../types/Report.types'; +import { DISPLAY_COLUMNS_BY } from './constants'; + +export class ProfitLossSheetQuery extends R.compose(FinancialDateRanges)( + class {}, +) { + /** + * P&L query. + * @param {IProfitLossSheetQuery} + */ + public readonly query: IProfitLossSheetQuery; + + /** + * Previous year to date. + * @param {Date} + */ + public readonly PYToDate: Date; + /** + * Previous year from date. + * @param {Date} + */ + public readonly PYFromDate: Date; + /** + * Previous period to date. + * @param {Date} + */ + public readonly PPToDate: Date; + /** + * Previous period from date. + * @param {Date} + */ + public readonly PPFromDate: Date; + + /** + * Constructor method. + * @param {IProfitLossSheetQuery} query + */ + constructor(query: IProfitLossSheetQuery) { + super(); + this.query = query; + + // Pervious Year (PY) Dates. + this.PYToDate = this.getPreviousYearDate(this.query.toDate); + this.PYFromDate = this.getPreviousYearDate(this.query.fromDate); + + // Previous Period (PP) Dates for total column.. + if (this.isTotalColumnType()) { + const { fromDate, toDate } = this.getPPTotalDateRange( + this.query.fromDate, + this.query.toDate, + ); + this.PPToDate = toDate; + this.PPFromDate = fromDate; + + // Previous period (PP) dates for date periods columns type. + } else if (this.isDatePeriodsColumnsType()) { + const { fromDate, toDate } = this.getPPDatePeriodDateRange( + this.query.fromDate, + this.query.toDate, + this.query.displayColumnsBy as IFinancialDatePeriodsUnit, + ); + this.PPToDate = toDate; + this.PPFromDate = fromDate; + } + return merge(this, query); + } + + /** + * Determines the given display columns type. + * @param {string} displayColumnsBy + * @returns {boolean} + */ + public isDisplayColumnsBy = (displayColumnsBy: string): boolean => { + return this.query.displayColumnsBy === displayColumnsBy; + }; + + /** + * Determines the given display columns by type. + * @param {string} displayColumnsBy + * @returns {boolean} + */ + public isDisplayColumnsType = (displayColumnsType: string): boolean => { + return this.query.displayColumnsType === displayColumnsType; + }; + + /** + * Determines whether the columns type is date periods. + * @returns {boolean} + */ + public isDatePeriodsColumnsType = (): boolean => { + return this.isDisplayColumnsType(DISPLAY_COLUMNS_BY.DATE_PERIODS); + }; + + /** + * Determines whether the columns type is total. + * @returns {boolean} + */ + public isTotalColumnType = (): boolean => { + return this.isDisplayColumnsType(DISPLAY_COLUMNS_BY.TOTAL); + }; + + // -------------------------------------- + // # Previous Year (PY) + // -------------------------------------- + /** + * Determines the report query has previous year enabled. + * @returns {boolean} + */ + public isPreviousYearActive = (): boolean => { + return this.query.previousYear; + }; + + /** + * Determines the report query has previous year percentage change active. + * @returns {boolean} + */ + public isPreviousYearPercentageActive = (): boolean => { + return this.query.previousYearPercentageChange; + }; + + /** + * Determines the report query has previous year change active. + * @returns {boolean} + */ + public isPreviousYearChangeActive = (): boolean => { + return this.query.previousYearAmountChange; + }; + + /** + * Retrieves PY date based on the current query. + * @returns {Date} + */ + public getTotalPreviousYear = (): Date => { + return this.PYFromDate; + }; + + // -------------------------------------- + // # Previous Period (PP) + // -------------------------------------- + /** + * Determines the report query has previous period enabled. + * @returns {boolean} + */ + public isPreviousPeriodActive = (): boolean => { + return this.query.previousPeriod; + }; + + /** + * Determines the report query has previous period percentage change active. + * @returns {boolean} + */ + public isPreviousPeriodPercentageActive = (): boolean => { + return this.query.previousPeriodPercentageChange; + }; + + /** + * Determines the report query has previous period change active. + * @returns {boolean} + */ + public isPreviousPeriodChangeActive = (): boolean => { + return this.query.previousPeriodAmountChange; + }; + + /** + * Retrieves previous period date based on the current query. + * @returns {Date} + */ + public getTotalPreviousPeriod = (): Date => { + return this.PPFromDate; + }; + + // -------------------------------------- + // # Percentage vertical/horizontal. + // -------------------------------------- + /** + * Determines whether percentage of expenses is active. + * @returns {boolean} + */ + public isExpensesPercentage = (): boolean => { + return this.query.percentageExpense; + }; + + /** + * Determines whether percentage of income is active. + * @returns {boolean} + */ + public isIncomePercentage = (): boolean => { + return this.query.percentageIncome; + }; + + /** + * Determines whether percentage of column is active. + * @returns {boolean} + */ + public isColumnPercentage = (): boolean => { + return this.query.percentageColumn; + }; + + /** + * Determines whether percentage of row is active. + * @returns {boolean} + */ + public isRowPercentage = (): boolean => { + return this.query.percentageRow; + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetRepository.ts new file mode 100644 index 000000000..bb4efd3b0 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetRepository.ts @@ -0,0 +1,380 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; +import { ModelObject } from 'objection'; +import { castArray } from 'lodash'; +import * as R from 'ramda'; +import { Knex } from 'knex'; +import { isEmpty } from 'lodash'; +import { transformToMapBy } from '@/utils/transform-to-map-by'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { IProfitLossSheetQuery } from './ProfitLossSheet.types'; +import { IAccountTransactionsGroupBy } from '../BalanceSheet/BalanceSheet.types'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; +import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)( + class {}, +) { + @Inject(Account.name) + public accountModel: typeof Account; + + @Inject(AccountTransaction.name) + public accountTransactionModel: typeof AccountTransaction; + + @Inject(TenancyContext) + public tenancyContext: TenancyContext; + + /** + * Tenancy base currency. + * @param {string} + */ + public baseCurrency: string; + + /** + * Accounts by type. + */ + public accountsByType: any; + + /** + * @param {ModelObject[]} + */ + public accounts: ModelObject[]; + + /** + * Accounts graph. + */ + public accountsGraph: any; + + /** + * Transactions group type. + * @param {IAccountTransactionsGroupBy} + */ + public transactionsGroupType: IAccountTransactionsGroupBy = + IAccountTransactionsGroupBy.Month; + + /** + * @param {IProfitLossSheetQuery} + */ + public query: ProfitLossSheetQuery; + + /** + * Previous year to date. + * @param {Date} + */ + public PYToDate: Date; + + /** + * Previous year from date. + * @param {Date} + */ + public PYFromDate: Date; + + /** + * Previous year to date. + * @param {Date} + */ + public PPToDate: Date; + + /** + * Previous year from date. + * @param {Date} + */ + public PPFromDate: Date; + + // ------------------------ + // # Total + // ------------------------ + /** + * Accounts total. + * @param {Ledger} + */ + public totalAccountsLedger: Ledger; + + // ------------------------ + // # Date Periods. + // ------------------------ + /** + * Accounts date periods. + * @param {Ledger} + */ + public periodsAccountsLedger: Ledger; + + // ------------------------ + // # Previous Year (PY) + // ------------------------ + /** + * @param {Ledger} + */ + public PYTotalAccountsLedger: Ledger; + + /** + * + * @param {Ledger} + */ + public PYPeriodsAccountsLedger: Ledger; + + // ------------------------ + // # Previous Period (PP). + // ------------------------ + /** + * PP Accounts Periods. + * @param {Ledger} + */ + public PPPeriodsAccountsLedger: Ledger; + + /** + * PP Accounts Total. + * @param {Ledger} + */ + public PPTotalAccountsLedger: Ledger; + + /** + * Set the filter of the report. + * @param {IBalanceSheetQuery} query + */ + setFilter(query: IProfitLossSheetQuery) { + this.query = new ProfitLossSheetQuery(query); + + this.transactionsGroupType = this.getGroupByFromDisplayColumnsBy( + this.query.displayColumnsBy, + ); + } + + /** + * Async report repository. + */ + public asyncInitialize = async () => { + await this.initBaseCurrency(); + await this.initAccounts(); + await this.initAccountsGraph(); + + await this.initAccountsTotalLedger(); + + // Date Periods. + if (this.query.isDatePeriodsColumnsType()) { + await this.initTotalDatePeriods(); + } + // Previous Period (PP) + if (this.query.isPreviousPeriodActive()) { + await this.initTotalPreviousPeriod(); + } + if ( + this.query.isPreviousPeriodActive() && + this.query.isDatePeriodsColumnsType() + ) { + await this.initPeriodsPreviousPeriod(); + } + // Previous Year (PY). + if (this.query.isPreviousYearActive()) { + await this.initTotalPreviousYear(); + } + if ( + this.query.isPreviousYearActive() && + this.query.isDatePeriodsColumnsType() + ) { + await this.initPeriodsPreviousYear(); + } + }; + + /** + * Initialize the base currency. + */ + async initBaseCurrency() { + const metadata = await this.tenancyContext.getTenantMetadata(); + + this.baseCurrency = metadata.baseCurrency; + } + + // ---------------------------- + // # Accounts + // ---------------------------- + /** + * Initialize accounts of the report. + */ + private initAccounts = async () => { + const accounts = await this.getAccounts(); + + // Inject to the repository. + this.accounts = accounts; + this.accountsByType = transformToMapBy(accounts, 'accountType'); + }; + + /** + * Initialize accounts graph. + */ + private initAccountsGraph = async () => { + this.accountsGraph = this.accountModel.toDependencyGraph(this.accounts); + }; + + // ---------------------------- + // # Closing Total. + // ---------------------------- + /** + * Initialize accounts closing total based on the given query. + */ + private initAccountsTotalLedger = async (): Promise => { + const totalByAccount = await this.accountsTotal( + this.query.fromDate, + this.query.toDate, + ); + // Inject to the repository. + this.totalAccountsLedger = Ledger.fromTransactions(totalByAccount); + }; + + // ---------------------------- + // # Date periods. + // ---------------------------- + /** + * Initialize date periods total of accounts based on the given query. + */ + private initTotalDatePeriods = async (): Promise => { + // Retrieves grouped transactions by given date group. + const periodsByAccount = await this.accountsDatePeriods( + this.query.query.fromDate, + this.query.query.toDate, + this.transactionsGroupType, + ); + + // Inject to the repository. + this.periodsAccountsLedger = Ledger.fromTransactions(periodsByAccount); + }; + + // ---------------------------- + // # Previous Period (PP). + // ---------------------------- + /** + * Initialize total of previous period (PP). + */ + private initTotalPreviousPeriod = async (): Promise => { + const PPTotalsByAccounts = await this.accountsTotal( + this.query.PPFromDate, + this.query.PPToDate, + ); + // Inject to the repository. + this.PPTotalAccountsLedger = Ledger.fromTransactions(PPTotalsByAccounts); + }; + + /** + * Initialize date periods of previous period (PP). + */ + private initPeriodsPreviousPeriod = async (): Promise => { + // Retrieves grouped transactions by given date group. + const periodsByAccount = await this.accountsDatePeriods( + this.query.PPFromDate, + this.query.PPToDate, + this.transactionsGroupType, + ); + // Inject to the repository. + this.PPPeriodsAccountsLedger = Ledger.fromTransactions(periodsByAccount); + }; + + // ---------------------------- + // # Previous Year (PY). + // ---------------------------- + /** + * Initialize total of previous year (PY). + */ + private initTotalPreviousYear = async (): Promise => { + const PYTotalsByAccounts = await this.accountsTotal( + this.query.PYFromDate, + this.query.PYToDate, + ); + // Inject to the repository. + this.PYTotalAccountsLedger = Ledger.fromTransactions(PYTotalsByAccounts); + }; + + /** + * Initialize periods of previous year (PY). + */ + private initPeriodsPreviousYear = async () => { + // Retrieves grouped transactions by given date group. + const periodsByAccount = await this.accountsDatePeriods( + this.query.PYFromDate, + this.query.PYToDate, + this.transactionsGroupType, + ); + // Inject to the repository. + this.PYPeriodsAccountsLedger = Ledger.fromTransactions(periodsByAccount); + }; + + // ---------------------------- + // # Utils + // ---------------------------- + /** + * Retrieve the opening balance transactions of the report. + */ + public accountsTotal = async (fromDate: Date, toDate: Date) => { + return this.accountTransactionModel.query().onBuild((query) => { + query.sum('credit as credit'); + query.sum('debit as debit'); + query.groupBy('accountId'); + query.select(['accountId']); + + query.modify('filterDateRange', fromDate, toDate); + query.withGraphFetched('account'); + + this.commonFilterBranchesQuery(query); + }); + }; + + /** + * Closing accounts date periods. + * @param openingDate + * @param datePeriodsType + * @returns + */ + public accountsDatePeriods = async ( + fromDate: moment.MomentInput, + toDate: moment.MomentInput, + datePeriodsType, + ) => { + return this.accountTransactionModel.query().onBuild((query) => { + query.sum('credit as credit'); + query.sum('debit as debit'); + query.groupBy('accountId'); + query.select(['accountId']); + + query.modify('groupByDateFormat', datePeriodsType); + query.modify('filterDateRange', fromDate, toDate); + query.withGraphFetched('account'); + + this.commonFilterBranchesQuery(query); + }); + }; + + /** + * Common branches filter query. + * @param {Knex.QueryBuilder} query + */ + private commonFilterBranchesQuery = (query: Knex.QueryBuilder) => { + if (!isEmpty(this.query.query.branchesIds)) { + query.modify('filterByBranches', this.query.query.branchesIds); + } + }; + + /** + * Retrieve accounts of the report. + * @return {Promise} + */ + private getAccounts = () => { + return this.accountModel.query(); + }; + + /** + * + * @param type + * @returns + */ + public getAccountsByType = (type: string[] | string) => { + return R.compose( + R.flatten, + R.map((accountType) => + R.defaultTo([], this.accountsByType.get(accountType)), + ), + castArray, + )(type); + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetService.ts new file mode 100644 index 000000000..8a819217a --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetService.ts @@ -0,0 +1,67 @@ +import { + IProfitLossSheetQuery, + IProfitLossSheetMeta, + IProfitLossSheetNode, +} from './ProfitLossSheet.types'; +import ProfitLossSheet from './ProfitLossSheet'; +import { mergeQueryWithDefaults } from './utils'; +import { ProfitLossSheetRepository } from './ProfitLossSheetRepository'; +import { ProfitLossSheetMeta } from './ProfitLossSheetMeta'; +import { events } from '@/common/events/events'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; +import { I18nService } from 'nestjs-i18n'; + +@Injectable() +export class ProfitLossSheetService { + constructor( + private readonly profitLossSheetMeta: ProfitLossSheetMeta, + private readonly eventPublisher: EventEmitter2, + private readonly i18nService: I18nService, + private readonly profitLossRepository: ProfitLossSheetRepository, + ) {} + + /** + * Retrieve profit/loss sheet statement. + * @param {IProfitLossSheetQuery} query + * @return { } + */ + public profitLossSheet = async ( + query: IProfitLossSheetQuery, + ): Promise<{ + data: IProfitLossSheetNode[]; + query: IProfitLossSheetQuery; + meta: IProfitLossSheetMeta; + }> => { + // Merges the given query with default filter query. + const filter = mergeQueryWithDefaults(query); + + // Loads the profit/loss sheet data. + this.profitLossRepository.setFilter(filter); + await this.profitLossRepository.asyncInitialize(); + + // Profit/Loss report instance. + const profitLossInstance = new ProfitLossSheet( + this.profitLossRepository, + filter, + this.i18nService, + ); + // Profit/loss report data and columns. + const data = profitLossInstance.reportData(); + + // Retrieve the profit/loss sheet meta. + const meta = await this.profitLossSheetMeta.meta(filter); + + // Triggers `onProfitLossSheetViewed` event. + await this.eventPublisher.emitAsync( + events.reports.onProfitLossSheetViewed, + { query }, + ); + + return { + query: filter, + data, + meta, + }; + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTable.ts new file mode 100644 index 000000000..5bb84c105 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTable.ts @@ -0,0 +1,238 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { + IProfitLossSheetQuery, + IProfitLossSheetAccountsNode, + ProfitLossNodeType, + ProfitLossSheetRowType, + IProfitLossSheetNode, + IProfitLossSheetEquationNode, + IProfitLossSheetAccountNode, +} from './ProfitLossSheet.types'; +import { + ITableColumn, + ITableColumnAccessor, + ITableRow, +} from '../../types/Table.types'; +import { ProfitLossSheetBase } from './ProfitLossSheetBase'; +import { ProfitLossSheetTablePercentage } from './ProfitLossSheetTablePercentage'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { ProfitLossTablePreviousPeriod } from './ProfitLossTablePreviousPeriod'; +import { ProfitLossTablePreviousYear } from './ProfitLossTablePreviousYear'; +import { ProfitLossSheetTableDatePeriods } from './ProfitLossSheetTableDatePeriods'; +import { I18nService } from 'nestjs-i18n'; +import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; +import { FinancialTable } from '../../common/FinancialTable'; +import { tableRowMapper } from '../../utils/Table.utils'; + +export class ProfitLossSheetTable extends R.pipe( + ProfitLossTablePreviousPeriod, + ProfitLossTablePreviousYear, + ProfitLossSheetTablePercentage, + ProfitLossSheetTableDatePeriods, + ProfitLossSheetBase, + FinancialSheetStructure, + FinancialTable, +)(class {}) { + readonly query: ProfitLossSheetQuery; + readonly i18n: I18nService; + + /** + * Constructor method. + * @param {} date + * @param {IProfitLossSheetQuery} query + */ + constructor(data: any, query: IProfitLossSheetQuery, i18n: I18nService) { + super(); + + this.query = new ProfitLossSheetQuery(query); + this.reportData = data; + this.i18n = i18n; + } + + // ---------------------------------- + // # Rows + // ---------------------------------- + /** + * Retrieve the total column accessor. + * @return {ITableColumnAccessor[]} + */ + private totalColumnAccessor = (): ITableColumnAccessor[] => { + return R.pipe( + R.when( + this.query.isPreviousPeriodActive, + R.concat(this.previousPeriodColumnAccessor()), + ), + R.when( + this.query.isPreviousYearActive, + R.concat(this.previousYearColumnAccessor()), + ), + R.concat(this.percentageColumnsAccessor()), + R.concat([{ key: 'total', accessor: 'total.formattedAmount' }]), + )([]); + }; + + /** + * Common columns accessors. + * @returns {ITableColumnAccessor} + */ + private commonColumnsAccessors = (): ITableColumnAccessor[] => { + return R.compose( + R.concat([{ key: 'name', accessor: 'name' }]), + R.ifElse( + this.query.isDatePeriodsColumnsType, + R.concat(this.datePeriodsColumnsAccessors()), + R.concat(this.totalColumnAccessor()), + ), + )([]); + }; + + /** + * + * @param {IProfitLossSheetAccountNode} node + * @returns {ITableRow} + */ + private accountNodeToTableRow = ( + node: IProfitLossSheetAccountNode, + ): ITableRow => { + const columns = this.commonColumnsAccessors(); + const meta = { + rowTypes: [ProfitLossSheetRowType.ACCOUNT], + id: node.id, + }; + return tableRowMapper(node, columns, meta); + }; + + /** + * + * @param {IProfitLossSheetAccountsNode} node + * @returns {ITableRow} + */ + private accountsNodeToTableRow = ( + node: IProfitLossSheetAccountsNode, + ): ITableRow => { + const columns = this.commonColumnsAccessors(); + const meta = { + rowTypes: [ProfitLossSheetRowType.ACCOUNTS], + id: node.id, + }; + return tableRowMapper(node, columns, meta); + }; + + /** + * + * @param {IProfitLossSheetEquationNode} node + * @returns {ITableRow} + */ + private equationNodeToTableRow = ( + node: IProfitLossSheetEquationNode, + ): ITableRow => { + const columns = this.commonColumnsAccessors(); + + const meta = { + rowTypes: [ProfitLossSheetRowType.TOTAL], + id: node.id, + }; + return tableRowMapper(node, columns, meta); + }; + + /** + * + * @param {IProfitLossSheetNode} node + * @returns {ITableRow} + */ + private nodeToTableRowCompose = (node: IProfitLossSheetNode): ITableRow => { + return R.cond([ + [ + this.isNodeType(ProfitLossNodeType.ACCOUNTS), + this.accountsNodeToTableRow, + ], + [ + this.isNodeType(ProfitLossNodeType.EQUATION), + this.equationNodeToTableRow, + ], + [this.isNodeType(ProfitLossNodeType.ACCOUNT), this.accountNodeToTableRow], + ])(node); + }; + + /** + * + * @param {IProfitLossSheetNode[]} nodes + * @returns {ITableRow} + */ + private nodesToTableRowsCompose = ( + nodes: IProfitLossSheetNode[], + ): ITableRow[] => { + return this.mapNodesDeep(nodes, this.nodeToTableRowCompose); + }; + + /** + * Retrieves the table rows. + * @returns {ITableRow[]} + */ + public tableRows = (): ITableRow[] => { + return R.compose( + this.addTotalRows, + this.nodesToTableRowsCompose, + )(this.reportData); + }; + + // ---------------------------------- + // # Columns. + // ---------------------------------- + /** + * Retrieve total column children columns. + * @returns {ITableColumn[]} + */ + private tableColumnChildren = (): ITableColumn[] => { + return R.compose( + R.unless( + R.isEmpty, + R.concat([ + { key: 'total', label: this.i18n.t('profit_loss_sheet.total') }, + ]), + ), + R.concat(this.percentageColumns()), + R.when( + this.query.isPreviousYearActive, + R.concat(this.getPreviousYearColumns()), + ), + R.when( + this.query.isPreviousPeriodActive, + R.concat(this.getPreviousPeriodColumns()), + ), + )([]); + }; + + /** + * Retrieves the total column. + * @returns {ITableColumn[]} + */ + private totalColumn = (): ITableColumn[] => { + return [ + { + key: 'total', + label: this.i18n.t('profit_loss_sheet.total'), + children: this.tableColumnChildren(), + }, + ]; + }; + + /** + * Retrieves the table columns. + * @returns {ITableColumn[]} + */ + public tableColumns = (): ITableColumn[] => { + return R.compose( + this.tableColumnsCellIndexing, + R.concat([ + { key: 'name', label: this.i18n.t('profit_loss_sheet.account_name') }, + ]), + R.ifElse( + this.query.isDatePeriodsColumnsType, + R.concat(this.datePeriodsColumns()), + R.concat(this.totalColumn()), + ), + )([]); + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableDatePeriods.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableDatePeriods.ts new file mode 100644 index 000000000..f03bdf412 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableDatePeriods.ts @@ -0,0 +1,158 @@ +// @ts-nocheck +import * as R from 'ramda'; +import moment from 'moment'; +import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types'; +import { ProfitLossSheetTablePercentage } from './ProfitLossSheetTablePercentage'; +import { ProfitLossTablePreviousPeriod } from './ProfitLossTablePreviousPeriod'; +import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { IDateRange } from '../../types/Report.types'; + +export const ProfitLossSheetTableDatePeriods = < + T extends GConstructor, +>( + Base: T, +) => + class extends R.pipe( + ProfitLossSheetTablePercentage, + ProfitLossTablePreviousPeriod, + FinancialDatePeriods, + )(Base) { + /** + * Retrieves the date periods based on the report query. + * @returns {IDateRange[]} + */ + get datePeriods() { + return this.getDateRanges( + this.query.query.fromDate, + this.query.query.toDate, + this.query.query.displayColumnsBy, + ); + } + + // -------------------------------- + // # Accessors + // -------------------------------- + /** + * Date period columns accessor. + * @param {IDateRange} dateRange - + * @param {number} index - + */ + private datePeriodColumnsAccessor = R.curry( + (dateRange: IDateRange, index: number) => { + return R.pipe( + R.when( + this.query.isPreviousPeriodActive, + R.concat(this.previousPeriodHorizontalColumnAccessors(index)), + ), + R.when( + this.query.isPreviousYearActive, + R.concat(this.previousYearHorizontalColumnAccessors(index)), + ), + R.concat(this.percetangeHorizontalColumnsAccessor(index)), + R.concat([ + { + key: `date-range-${index}`, + accessor: `horizontalTotals[${index}].total.formattedAmount`, + }, + ]), + )([]); + }, + ); + + /** + * Retrieve the date periods columns accessors. + * @returns {ITableColumnAccessor[]} + */ + protected datePeriodsColumnsAccessors = (): ITableColumnAccessor[] => { + return R.compose( + R.flatten, + R.addIndex(R.map)(this.datePeriodColumnsAccessor), + )(this.datePeriods); + }; + + // -------------------------------- + // # Columns + // -------------------------------- + /** + * Retrieve the formatted column label from the given date range. + * @param {ICashFlowDateRange} dateRange - + * @return {string} + */ + private formatColumnLabel = (dateRange) => { + const monthFormat = (range) => moment(range.toDate).format('YYYY-MM'); + const yearFormat = (range) => moment(range.toDate).format('YYYY'); + const dayFormat = (range) => moment(range.toDate).format('YYYY-MM-DD'); + + const conditions = [ + ['month', monthFormat], + ['year', yearFormat], + ['day', dayFormat], + ['quarter', monthFormat], + ['week', dayFormat], + ]; + const conditionsPairs = R.map( + ([type, formatFn]) => [ + R.always(this.query.isDisplayColumnsBy(type)), + formatFn, + ], + conditions, + ); + return R.compose(R.cond(conditionsPairs))(dateRange); + }; + + /** + * + * @param {number} index + * @param {IDateRange} dateRange + * @returns {} + */ + private datePeriodChildrenColumns = ( + index: number, + dateRange: IDateRange, + ) => { + return R.compose( + R.unless( + R.isEmpty, + R.concat([ + { key: `total`, label: this.i18n.t('profit_loss_sheet.total') }, + ]), + ), + R.concat(this.percentageColumns()), + R.when( + this.query.isPreviousYearActive, + R.concat(this.getPreviousYearDatePeriodColumnPlugin(dateRange)), + ), + R.when( + this.query.isPreviousPeriodActive, + R.concat(this.getPreviousPeriodDatePeriodsPlugin(dateRange)), + ), + )([]); + }; + + /** + * + * @param {IDateRange} dateRange + * @param {number} index + * @returns {ITableColumn} + */ + private datePeriodColumn = ( + dateRange: IDateRange, + index: number, + ): ITableColumn => { + return { + key: `date-range-${index}`, + label: this.formatColumnLabel(dateRange), + children: this.datePeriodChildrenColumns(index, dateRange), + }; + }; + + /** + * Date periods columns. + * @returns {ITableColumn[]} + */ + protected datePeriodsColumns = (): ITableColumn[] => { + return this.datePeriods.map(this.datePeriodColumn); + }; + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableInjectable.ts new file mode 100644 index 000000000..62e0d02e6 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTableInjectable.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { ProfitLossSheetService } from './ProfitLossSheetService'; +import { ProfitLossSheetTable } from './ProfitLossSheetTable'; +import { + IProfitLossSheetQuery, + IProfitLossSheetTable, +} from './ProfitLossSheet.types'; +import { I18nService } from 'nestjs-i18n'; + +@Injectable() +export class ProfitLossSheetTableInjectable { + constructor( + private readonly i18n: I18nService, + private readonly profitLossSheet: ProfitLossSheetService, + ) {} + + /** + * Retrieves the profit/loss sheet in table format. + * @param {IProfitLossSheetQuery} filter - Profit/loss sheet query. + * @returns {Promise} + */ + public async table( + filter: IProfitLossSheetQuery, + ): Promise { + const { data, query, meta } = + await this.profitLossSheet.profitLossSheet(filter); + + const table = new ProfitLossSheetTable(data, query, this.i18n); + + return { + table: { + rows: table.tableRows(), + columns: table.tableColumns(), + }, + query, + meta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTablePercentage.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTablePercentage.ts new file mode 100644 index 000000000..d03793225 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheetTablePercentage.ts @@ -0,0 +1,141 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { I18nService } from 'nestjs-i18n'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types'; + +export const ProfitLossSheetTablePercentage = < + T extends GConstructor, +>( + Base: T, +) => + class extends Base { + i18n: I18nService; + + /** + * @param {ProfitLossSheetQuery} + */ + readonly query: ProfitLossSheetQuery; + + // ---------------------------------- + // # Columns. + // ---------------------------------- + /** + * Retrieve percentage of column/row columns. + * @returns {ITableColumn[]} + */ + protected percentageColumns = (): ITableColumn[] => { + return R.pipe( + R.when( + this.query.isIncomePercentage, + R.append({ + key: 'percentage_income', + label: this.i18n.t('profit_loss_sheet.percentage_of_income'), + }), + ), + R.when( + this.query.isExpensesPercentage, + R.append({ + key: 'percentage_expenses', + label: this.i18n.t('profit_loss_sheet.percentage_of_expenses'), + }), + ), + R.when( + this.query.isColumnPercentage, + R.append({ + key: 'percentage_column', + label: this.i18n.t('profit_loss_sheet.percentage_of_column'), + }), + ), + R.when( + this.query.isRowPercentage, + R.append({ + key: 'percentage_row', + label: this.i18n.t('profit_loss_sheet.percentage_of_row'), + }), + ), + )([]); + }; + + // ---------------------------------- + // # Accessors. + // ---------------------------------- + /** + * Retrieves percentage of column/row accessors. + * @returns {ITableColumnAccessor[]} + */ + protected percentageColumnsAccessor = (): ITableColumnAccessor[] => { + return R.pipe( + R.when( + this.query.isIncomePercentage, + R.append({ + key: 'percentage_income', + accessor: 'percentageIncome.formattedAmount', + }), + ), + R.when( + this.query.isExpensesPercentage, + R.append({ + key: 'percentage_expense', + accessor: 'percentageExpense.formattedAmount', + }), + ), + R.when( + this.query.isColumnPercentage, + R.append({ + key: 'percentage_column', + accessor: 'percentageColumn.formattedAmount', + }), + ), + R.when( + this.query.isRowPercentage, + R.append({ + key: 'percentage_row', + accessor: 'percentageRow.formattedAmount', + }), + ), + )([]); + }; + + /** + * Retrieves percentage horizontal columns accessors. + * @param {number} index + * @returns {ITableColumn[]} + */ + protected percetangeHorizontalColumnsAccessor = ( + index: number, + ): ITableColumnAccessor[] => { + return R.pipe( + R.when( + this.query.isIncomePercentage, + R.append({ + key: `percentage_income-${index}`, + accessor: `horizontalTotals[${index}].percentageIncome.formattedAmount`, + }), + ), + R.when( + this.query.isExpensesPercentage, + R.append({ + key: `percentage_expense-${index}`, + accessor: `horizontalTotals[${index}].percentageExpense.formattedAmount`, + }), + ), + R.when( + this.query.isColumnPercentage, + R.append({ + key: `percentage_of_column-${index}`, + accessor: `horizontalTotals[${index}].percentageColumn.formattedAmount`, + }), + ), + R.when( + this.query.isRowPercentage, + R.append({ + key: `percentage_of_row-${index}`, + accessor: `horizontalTotals[${index}].percentageRow.formattedAmount`, + }), + ), + )([]); + }; + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePdfInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePdfInjectable.ts new file mode 100644 index 000000000..776f1783a --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePdfInjectable.ts @@ -0,0 +1,29 @@ +import { TableSheetPdf } from '../../common/TableSheetPdf'; +import { IProfitLossSheetQuery } from './ProfitLossSheet.types'; +import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; +import { HtmlTableCustomCss } from './constants'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ProfitLossTablePdfInjectable { + constructor( + private readonly profitLossTable: ProfitLossSheetTableInjectable, + private readonly tableSheetPdf: TableSheetPdf, + ) {} + + /** + * Retrieves the profit/loss sheet in pdf format. + * @param {number} query + * @returns {Promise} + */ + public async pdf(query: IProfitLossSheetQuery): Promise { + const table = await this.profitLossTable.table(query); + + return this.tableSheetPdf.convertToPdf( + table.table, + table.meta.sheetName, + table.meta.formattedDateRange, + HtmlTableCustomCss, + ); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousPeriod.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousPeriod.ts new file mode 100644 index 000000000..22d338f98 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousPeriod.ts @@ -0,0 +1,101 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialTablePreviousPeriod } from '../../common/FinancialTablePreviousPeriod'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { IDateRange } from '../../types/Report.types'; + +export const ProfitLossTablePreviousPeriod = < + T extends GConstructor, +>( + Base: T, +) => + class extends R.pipe(FinancialTablePreviousPeriod)(Base) { + query: ProfitLossSheetQuery; + + // ---------------------------- + // # Columns + // ---------------------------- + /** + * Retrieves pervious period comparison columns. + * @returns {ITableColumn[]} + */ + protected getPreviousPeriodColumns = ( + dateRange?: IDateRange, + ): ITableColumn[] => { + return R.pipe( + // Previous period columns. + R.append(this.getPreviousPeriodTotalColumn(dateRange)), + R.when( + this.query.isPreviousPeriodChangeActive, + R.append(this.getPreviousPeriodChangeColumn()), + ), + R.when( + this.query.isPreviousPeriodPercentageActive, + R.append(this.getPreviousPeriodPercentageColumn()), + ), + )([]); + }; + + /** + * Compose the previous period for date periods columns. + * @params {IDateRange} + * @returns {ITableColumn[]} + */ + protected getPreviousPeriodDatePeriodsPlugin = ( + dateRange: IDateRange, + ): ITableColumn[] => { + const PPDateRange = this.getPPDatePeriodDateRange( + dateRange.fromDate, + dateRange.toDate, + this.query.displayColumnsBy, + ); + return this.getPreviousPeriodColumns(PPDateRange); + }; + + // ---------------------------- + // # Accessors + // ---------------------------- + /** + * Retrieves previous period columns accessors. + * @returns {ITableColumn[]} + */ + protected previousPeriodColumnAccessor = (): ITableColumnAccessor[] => { + return R.pipe( + // Previous period columns. + R.append(this.getPreviousPeriodTotalAccessor()), + R.when( + this.query.isPreviousPeriodChangeActive, + R.append(this.getPreviousPeriodChangeAccessor()), + ), + R.when( + this.query.isPreviousPeriodPercentageActive, + R.append(this.getPreviousPeriodPercentageAccessor()), + ), + )([]); + }; + + /** + * Previous period period column accessor. + * @param {number} index + * @returns {ITableColumn[]} + */ + protected previousPeriodHorizontalColumnAccessors = ( + index: number, + ): ITableColumnAccessor[] => { + return R.pipe( + // Previous period columns. + R.append(this.getPreviousPeriodTotalHorizAccessor(index)), + R.when( + this.query.isPreviousPeriodChangeActive, + R.append(this.getPreviousPeriodChangeHorizAccessor(index)), + ), + R.when( + this.query.isPreviousPeriodPercentageActive, + R.append(this.getPreviousPeriodPercentageHorizAccessor(index)), + ), + )([]); + }; + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousYear.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousYear.ts new file mode 100644 index 000000000..ca431bc7f --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossTablePreviousYear.ts @@ -0,0 +1,112 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { ProfitLossSheetQuery } from './ProfitLossSheetQuery'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { ITableColumn, ITableColumnAccessor } from '../../types/Table.types'; +import { IDateRange } from '../CashFlowStatement/Cashflow.types'; +import { FinancialTablePreviousYear } from '../../common/FinancialTablePreviousYear'; +import { FinancialDateRanges } from '../../common/FinancialDateRanges'; + +export const ProfitLossTablePreviousYear = < + T extends GConstructor, +>( + Base: T, +) => + class extends R.pipe(FinancialTablePreviousYear, FinancialDateRanges)(Base) { + query: ProfitLossSheetQuery; + + // ------------------------------------ + // # Columns. + // ------------------------------------ + /** + * Retrieves pervious year comparison columns. + * @returns {ITableColumn[]} + */ + protected getPreviousYearColumns = ( + dateRange?: IDateRange, + ): ITableColumn[] => { + return R.pipe( + // Previous year columns. + R.append(this.getPreviousYearTotalColumn(dateRange)), + R.when( + this.query.isPreviousYearChangeActive, + R.append(this.getPreviousYearChangeColumn()), + ), + R.when( + this.query.isPreviousYearPercentageActive, + R.append(this.getPreviousYearPercentageColumn()), + ), + )([]); + }; + + /** + * Compose the previous year for date periods columns. + * @param {IDateRange} dateRange + * @returns {ITableColumn[]} + */ + private previousYearDatePeriodColumnCompose = ( + dateRange: IDateRange, + ): ITableColumn[] => { + const PYDateRange = this.getPreviousYearDateRange( + dateRange.fromDate, + dateRange.toDate, + ); + return this.getPreviousYearColumns(PYDateRange); + }; + + /** + * Retrieves previous year date periods columns. + * @param {IDateRange} dateRange + * @returns {ITableColumn[]} + */ + protected getPreviousYearDatePeriodColumnPlugin = ( + dateRange: IDateRange, + ): ITableColumn[] => { + return this.previousYearDatePeriodColumnCompose(dateRange); + }; + + // --------------------------------------------------- + // # Accessors. + // --------------------------------------------------- + /** + * Retrieves previous year columns accessors. + * @returns {ITableColumnAccessor[]} + */ + protected previousYearColumnAccessor = (): ITableColumnAccessor[] => { + return R.pipe( + // Previous year columns. + R.append(this.getPreviousYearTotalAccessor()), + R.when( + this.query.isPreviousYearChangeActive, + R.append(this.getPreviousYearChangeAccessor()), + ), + R.when( + this.query.isPreviousYearPercentageActive, + R.append(this.getPreviousYearPercentageAccessor()), + ), + )([]); + }; + + /** + * Previous year period column accessor. + * @param {number} index + * @returns {ITableColumn[]} + */ + protected previousYearHorizontalColumnAccessors = ( + index: number, + ): ITableColumnAccessor[] => { + return R.pipe( + // Previous year columns. + R.append(this.getPreviousYearTotalHorizAccessor(index)), + R.when( + this.query.isPreviousYearChangeActive, + R.append(this.getPreviousYearChangeHorizAccessor(index)), + ), + R.when( + this.query.isPreviousYearPercentageActive, + R.append(this.getPreviousYearPercentageHorizAccessor(index)), + ), + )([]); + }; + }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/constants.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/constants.ts new file mode 100644 index 000000000..1f80c14db --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/constants.ts @@ -0,0 +1,69 @@ +import { ProfitLossNodeType } from './ProfitLossSheet.types'; + +export const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; + +export const DISPLAY_COLUMNS_BY = { + DATE_PERIODS: 'date_periods', + TOTAL: 'total', +}; + +export enum IROW_TYPE { + AGGREGATE = 'AGGREGATE', + ACCOUNTS = 'ACCOUNTS', + ACCOUNT = 'ACCOUNT', + TOTAL = 'TOTAL', +} + +export const TOTAL_NODE_TYPES = [ + ProfitLossNodeType.ACCOUNTS, + ProfitLossNodeType.AGGREGATE, + ProfitLossNodeType.EQUATION, +]; + +export const HtmlTableCustomCss = ` +table tr.row-type--total td { + font-weight: 600; + border-top: 1px solid #bbb; + color: #000; +} +table tr.row-id--net-income td{ + border-bottom: 3px double #000; +} +table .column--name, +table .cell--name { + width: 400px; +} + +table .column--total { + width: 25%; +} +table td.cell--total, +table td.cell--previous_year, +table td.cell--previous_year_change, +table td.cell--previous_year_percentage, + +table td.cell--previous_period, +table td.cell--previous_period_change, +table td.cell--previous_period_percentage, + +table td.cell--percentage_of_row, +table td.cell--percentage_of_column, +table td[class*="cell--date-range"] { + text-align: right; +} + +table .column--total, +table .column--previous_year, +table .column--previous_year_change, +table .column--previous_year_percentage, + +table .column--previous_period, +table .column--previous_period_change, +table .column--previous_period_percentage, + +table .column--percentage_of_row, +table .column--percentage_of_column, +table [class*="column--date-range"] { + text-align: right; +} +`; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/utils.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/utils.ts new file mode 100644 index 000000000..7d29a405e --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ProfitLossSheet/utils.ts @@ -0,0 +1,54 @@ +import * as moment from 'moment'; +import { merge } from 'lodash'; +import { IProfitLossSheetQuery } from './ProfitLossSheet.types'; + +/** + * Default sheet filter query. + * @return {IBalanceSheetQuery} + */ +export const getDefaultPLQuery = (): IProfitLossSheetQuery => ({ + fromDate: moment().startOf('year').format('YYYY-MM-DD'), + toDate: moment().format('YYYY-MM-DD'), + + numberFormat: { + divideOn1000: false, + negativeFormat: 'mines', + showZero: false, + formatMoney: 'total', + precision: 2, + }, + basis: 'accrual', + + noneZero: false, + noneTransactions: false, + + displayColumnsType: 'total', + displayColumnsBy: 'month', + + accountsIds: [], + + percentageColumn: false, + percentageRow: false, + + percentageIncome: false, + percentageExpense: false, + + previousPeriod: false, + previousPeriodAmountChange: false, + previousPeriodPercentageChange: false, + + previousYear: false, + previousYearAmountChange: false, + previousYearPercentageChange: false, +}); + +/** + * + * @param query + * @returns + */ +export const mergeQueryWithDefaults = ( + query: IProfitLossSheetQuery, +): IProfitLossSheetQuery => { + return merge(getDefaultPLQuery(), query); +}; diff --git a/packages/server-nest/tsconfig.build.json b/packages/server-nest/tsconfig.build.json index 83d7ca36f..c18e810dd 100644 --- a/packages/server-nest/tsconfig.build.json +++ b/packages/server-nest/tsconfig.build.json @@ -11,8 +11,9 @@ "./src/modules/DynamicListing", "./src/modules/DynamicListing/**/*.ts", "./src/modules/FinancialStatements/**/*.ts", - // "./src/modules/FinancialStatements/modules/BalanceSheet/**.ts", + "./src/modules/StripePayment/**/*.ts", + "./src/modules/FinancialStatements/modules/BalanceSheet/**.ts", + "./src/modules/FinancialStatements/modules/ProfitLossSheet/**.ts", "./src/modules/Views", - "./src/modules/Expenses/subscribers" ] } diff --git a/packages/server/src/services/StripePayment/CreatePaymentReceivedStripePayment.ts b/packages/server/src/services/StripePayment/CreatePaymentReceivedStripePayment.ts index 29d1c8d18..d6ae6eb72 100644 --- a/packages/server/src/services/StripePayment/CreatePaymentReceivedStripePayment.ts +++ b/packages/server/src/services/StripePayment/CreatePaymentReceivedStripePayment.ts @@ -7,17 +7,11 @@ import UnitOfWork from '../UnitOfWork'; @Service() export class CreatePaymentReceiveStripePayment { - @Inject() - private getSaleInvoiceService: GetSaleInvoice; - - @Inject() - private createPaymentReceivedService: CreatePaymentReceived; - - @Inject() - private tenancy: HasTenancyService; - - @Inject() - private uow: UnitOfWork; + constructor( + private readonly getSaleInvoiceService: GetSaleInvoice, + private readonly createPaymentReceivedService: CreatePaymentReceived, + private readonly uow: UnitOfWork + ) {} /** * diff --git a/temp/CashFlow/CashFlowRepository.ts b/temp/CashFlow/CashFlowRepository.ts deleted file mode 100644 index 3bdf3a42a..000000000 --- a/temp/CashFlow/CashFlowRepository.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import moment from 'moment'; -import { Knex } from 'knex'; -import { isEmpty } from 'lodash'; -import { ICashFlowStatementQuery } from './Cashflow.types'; -import { Account } from '@/modules/Accounts/models/Account.model'; -import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; -import { ModelObject } from 'objection'; - -@Injectable() -export class CashFlowRepository { - constructor( - @Inject(Account.name) - private readonly accountModel: typeof Account, - - @Inject(AccountTransaction.name) - private readonly accountTransactionModel: typeof AccountTransaction, - ) {} - - /** - * Retrieve the group type from periods type. - * @param {string} displayType - * @returns {string} - */ - protected getGroupTypeFromPeriodsType(displayType: string) { - const displayTypes = { - year: 'year', - day: 'day', - month: 'month', - quarter: 'month', - week: 'day', - }; - return displayTypes[displayType] || 'month'; - } - - /** - * Retrieve the cashflow accounts. - * @returns {Promise} - */ - public async cashFlowAccounts(): Promise { - const accounts = await this.accountModel.query(); - return accounts; - } - - /** - * Retrieve total of csah at beginning transactions. - * @param {number} tenantId - - * @param {ICashFlowStatementQuery} filter - - * @return {Promise} - */ - public async cashAtBeginningTotalTransactions( - filter: ICashFlowStatementQuery, - ): Promise[]> { - const cashBeginningPeriod = moment(filter.fromDate) - .subtract(1, 'day') - .toDate(); - - const transactions = await this.accountTransactionModel - .query() - .onBuild((query) => { - query.modify('creditDebitSummation'); - - query.select('accountId'); - query.groupBy('accountId'); - - query.withGraphFetched('account'); - query.modify('filterDateRange', null, cashBeginningPeriod); - - this.commonFilterBranchesQuery(filter, query); - }); - return transactions; - } - - /** - * Retrieve accounts transactions. - * @param {number} tenantId - - * @param {ICashFlowStatementQuery} filter - * @return {Promise} - */ - public async getAccountsTransactions( - filter: ICashFlowStatementQuery, - ): Promise[]> { - const groupByDateType = this.getGroupTypeFromPeriodsType( - filter.displayColumnsBy, - ); - return await this.accountTransactionModel.query().onBuild((query) => { - query.modify('creditDebitSummation'); - query.modify('groupByDateFormat', groupByDateType); - - query.select('accountId'); - - query.groupBy('accountId'); - query.withGraphFetched('account'); - - query.modify('filterDateRange', filter.fromDate, filter.toDate); - - this.commonFilterBranchesQuery(filter, query); - }); - } - - /** - * Retrieve the net income tranasctions. - * @param {number} tenantId - - * @param {ICashFlowStatementQuery} query - - * @return {Promise} - */ - public async getNetIncomeTransactions( - filter: ICashFlowStatementQuery, - ): Promise { - const groupByDateType = this.getGroupTypeFromPeriodsType( - filter.displayColumnsBy, - ); - return await this.accountTransactionModel.query().onBuild((query) => { - query.modify('creditDebitSummation'); - query.modify('groupByDateFormat', groupByDateType); - - query.select('accountId'); - query.groupBy('accountId'); - - query.withGraphFetched('account'); - query.modify('filterDateRange', filter.fromDate, filter.toDate); - - this.commonFilterBranchesQuery(filter, query); - }); - } - - /** - * Retrieve peridos of cash at beginning transactions. - * @param {ICashFlowStatementQuery} filter - - * @return {Promise[]>} - */ - public async cashAtBeginningPeriodTransactions( - filter: ICashFlowStatementQuery, - ): Promise[]> { - const groupByDateType = this.getGroupTypeFromPeriodsType( - filter.displayColumnsBy, - ); - - return await this.accountTransactionModel.query().onBuild((query) => { - query.modify('creditDebitSummation'); - query.modify('groupByDateFormat', groupByDateType); - - query.select('accountId'); - query.groupBy('accountId'); - - query.withGraphFetched('account'); - query.modify('filterDateRange', filter.fromDate, filter.toDate); - - this.commonFilterBranchesQuery(filter, query); - }); - } - - /** - * Common branches filter query. - * @param {Knex.QueryBuilder} query - */ - private commonFilterBranchesQuery = ( - query: ICashFlowStatementQuery, - knexQuery: Knex.QueryBuilder, - ) => { - if (!isEmpty(query.branchesIds)) { - knexQuery.modify('filterByBranches', query.branchesIds); - } - }; -} diff --git a/temp/CashFlow/CashFlowService.ts b/temp/CashFlow/CashFlowService.ts deleted file mode 100644 index 4dc334330..000000000 --- a/temp/CashFlow/CashFlowService.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { ModelObject } from 'objection'; -import moment from 'moment'; -import * as R from 'ramda'; -import { - ICashFlowStatementQuery, - ICashFlowStatementDOO, -} from './Cashflow.types'; -import CashFlowStatement from './CashFlow'; -import { CashflowSheetMeta } from './CashflowSheetMeta'; -import { Injectable } from '@nestjs/common'; -import { CashFlowRepository } from './CashFlowRepository'; -import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; -import { Ledger } from '@/modules/Ledger/Ledger'; -import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; -import { I18nService } from 'nestjs-i18n'; -import { getDefaultCashflowQuery } from './constants'; - -@Injectable() -export class CashFlowStatementService { - /** - * @param {CashFlowRepository} cashFlowRepo - Cash flow repository. - * @param {CashflowSheetMeta} cashflowSheetMeta - Cashflow sheet meta. - * @param {TenancyContext} tenancyContext - Tenancy context. - */ - constructor( - private readonly cashFlowRepo: CashFlowRepository, - private readonly cashflowSheetMeta: CashflowSheetMeta, - private readonly tenancyContext: TenancyContext, - private readonly i18n: I18nService, - ) {} - - /** - * Retrieves cash at beginning transactions. - * @param {ICashFlowStatementQuery} filter - Cash flow statement query. - * @returns {Promise[]>} - */ - private async cashAtBeginningTransactions( - filter: ICashFlowStatementQuery, - ): Promise[]> { - const appendPeriodsOperToChain = (trans) => - R.append( - this.cashFlowRepo.cashAtBeginningPeriodTransactions(filter), - trans, - ); - - const promisesChain = R.pipe( - R.append(this.cashFlowRepo.cashAtBeginningTotalTransactions(filter)), - R.when( - R.always(R.equals(filter.displayColumnsType, 'date_periods')), - appendPeriodsOperToChain, - ), - )([]); - const promisesResults = await Promise.all(promisesChain); - const transactions = R.flatten(promisesResults); - - return transactions; - } - - /** - * Retrieve the cash flow sheet statement. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - * @returns {Promise} - */ - public async cashFlow( - query: ICashFlowStatementQuery, - ): Promise { - // Retrieve all accounts on the storage. - const accounts = await this.cashFlowRepo.cashFlowAccounts(); - const tenant = await this.tenancyContext.getTenant(); - - const filter = { - ...getDefaultCashflowQuery(), - ...query, - }; - // Retrieve the accounts transactions. - const transactions = - await this.cashFlowRepo.getAccountsTransactions(filter); - // Retrieve the net income transactions. - const netIncome = await this.cashFlowRepo.getNetIncomeTransactions(filter); - // Retrieve the cash at beginning transactions. - const cashAtBeginningTransactions = - await this.cashAtBeginningTransactions(filter); - - // Transformes the transactions to ledgers. - const ledger = Ledger.fromTransactions(transactions); - const cashLedger = Ledger.fromTransactions(cashAtBeginningTransactions); - const netIncomeLedger = Ledger.fromTransactions(netIncome); - - // Cash flow statement. - const cashFlowInstance = new CashFlowStatement( - accounts, - ledger, - cashLedger, - netIncomeLedger, - filter, - tenant.metadata.baseCurrency, - this.i18n, - ); - // Retrieve the cashflow sheet meta. - const meta = await this.cashflowSheetMeta.meta(filter); - - return { - data: cashFlowInstance.reportData(), - query: filter, - meta, - }; - } -} diff --git a/temp/CashFlow/Cashflow.controller.ts b/temp/CashFlow/Cashflow.controller.ts deleted file mode 100644 index 0227145ed..000000000 --- a/temp/CashFlow/Cashflow.controller.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Response } from 'express'; -import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; -import { ICashFlowStatementQuery } from './Cashflow.types'; -import { AcceptType } from '@/constants/accept-type'; -import { CashflowSheetApplication } from './CashflowSheetApplication'; - -@Controller('reports/cashflow') -export class CashflowController { - constructor(private readonly cashflowSheetApp: CashflowSheetApplication) {} - - @Get() - async getCashflow( - @Query() query: ICashFlowStatementQuery, - @Res() res: Response, - @Headers('accept') acceptHeader: string, - ) { - const filter = { - ...query, - }; - // Retrieves the json table format. - if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) { - const table = await this.cashflowSheetApp.table(filter); - - return res.status(200).send(table); - // Retrieves the csv format. - } else if (acceptHeader.includes(AcceptType.ApplicationCsv)) { - const buffer = await this.cashflowSheetApp.csv(filter); - - res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); - res.setHeader('Content-Type', 'text/csv'); - - return res.status(200).send(buffer); - // Retrieves the pdf format. - } else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) { - const buffer = await this.cashflowSheetApp.xlsx(filter); - - res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx'); - res.setHeader( - 'Content-Type', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - ); - return res.send(buffer); - // Retrieves the pdf format. - } else if (acceptHeader.includes(AcceptType.ApplicationPdf)) { - const pdfContent = await this.cashflowSheetApp.pdf(filter); - - res.set({ - 'Content-Type': 'application/pdf', - 'Content-Length': pdfContent.length, - }); - return res.send(pdfContent); - // Retrieves the json format. - } else { - const cashflow = await this.cashflowSheetApp.sheet(filter); - - return res.status(200).send(cashflow); - } - } -} diff --git a/temp/CashFlow/Cashflow.module.ts b/temp/CashFlow/Cashflow.module.ts deleted file mode 100644 index 1646def94..000000000 --- a/temp/CashFlow/Cashflow.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Module } from '@nestjs/common'; -import { CashflowSheetMeta } from './CashflowSheetMeta'; -import { CashFlowRepository } from './CashFlowRepository'; -import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable'; -import { CashflowExportInjectable } from './CashflowExportInjectable'; -import { CashflowController } from './Cashflow.controller'; -import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; -import { CashflowTableInjectable } from './CashflowTableInjectable'; -import { CashFlowStatementService } from './CashFlowService'; -import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; -import { CashflowSheetApplication } from './CashflowSheetApplication'; - -@Module({ - imports: [FinancialSheetCommonModule], - providers: [ - CashFlowRepository, - CashflowSheetMeta, - CashFlowStatementService, - CashflowTablePdfInjectable, - CashflowExportInjectable, - CashflowTableInjectable, - CashflowSheetApplication, - TenancyContext - ], - controllers: [CashflowController], -}) -export class CashflowReportModule {} diff --git a/temp/CashFlow/CashflowSheetApplication.ts b/temp/CashFlow/CashflowSheetApplication.ts deleted file mode 100644 index 7d04b298c..000000000 --- a/temp/CashFlow/CashflowSheetApplication.ts +++ /dev/null @@ -1,60 +0,0 @@ - -import { CashflowExportInjectable } from './CashflowExportInjectable'; -import { ICashFlowStatementQuery } from './Cashflow.types'; -import { CashFlowStatementService } from './CashFlowService'; -import { CashflowTableInjectable } from './CashflowTableInjectable'; -import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable'; -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class CashflowSheetApplication { - constructor( - private readonly cashflowExport: CashflowExportInjectable, - private readonly cashflowSheet: CashFlowStatementService, - private readonly cashflowTable: CashflowTableInjectable, - private readonly cashflowPdf: CashflowTablePdfInjectable, - ) {} - - /** - * Retrieves the cashflow sheet - * @param {ICashFlowStatementQuery} query - Cashflow statement query. - */ - public async sheet(query: ICashFlowStatementQuery) { - return this.cashflowSheet.cashFlow(query); - } - - /** - * Retrieves the cashflow sheet in table format. - * @param {ICashFlowStatementQuery} query - Cashflow statement query. - */ - public async table(query: ICashFlowStatementQuery) { - return this.cashflowTable.table(query); - } - - /** - * Retrieves the cashflow sheet in XLSX format. - * @param {ICashFlowStatementQuery} query - Cashflow statement query. - * @returns {Promise} - */ - public async xlsx(query: ICashFlowStatementQuery) { - return this.cashflowExport.xlsx(query); - } - - /** - * Retrieves the cashflow sheet in CSV format. - * @param {ICashFlowStatementQuery} query - Cashflow statement query. - * @returns {Promise} - */ - public async csv(query: ICashFlowStatementQuery): Promise { - return this.cashflowExport.csv(query); - } - - /** - * Retrieves the cashflow sheet in pdf format. - * @param {ICashFlowStatementQuery} query - Cashflow statement query. - * @returns {Promise} - */ - public async pdf(query: ICashFlowStatementQuery): Promise { - return this.cashflowPdf.pdf(query); - } -} diff --git a/temp/CashFlow/CashflowSheetMeta.ts b/temp/CashFlow/CashflowSheetMeta.ts deleted file mode 100644 index 094745e41..000000000 --- a/temp/CashFlow/CashflowSheetMeta.ts +++ /dev/null @@ -1,36 +0,0 @@ -import moment from 'moment'; -import { Injectable } from '@nestjs/common'; -import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; -import { ICashFlowStatementMeta, ICashFlowStatementQuery } from './Cashflow.types'; - -@Injectable() -export class CashflowSheetMeta { - constructor( - private readonly financialSheetMeta: FinancialSheetMeta, - ) {} - - /** - * Cashflow sheet meta. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - * @returns {Promise} - */ - public async meta( - query: ICashFlowStatementQuery - ): Promise { - const meta = await this.financialSheetMeta.meta(); - const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); - const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); - const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; - - const sheetName = 'Statement of Cash Flow'; - - return { - ...meta, - sheetName, - formattedToDate, - formattedFromDate, - formattedDateRange, - }; - } -} diff --git a/temp/CashFlowStatement/CashFlow.ts b/temp/CashFlowStatement/CashFlow.ts deleted file mode 100644 index 47df3e94f..000000000 --- a/temp/CashFlowStatement/CashFlow.ts +++ /dev/null @@ -1,705 +0,0 @@ -import * as R from 'ramda'; -import { defaultTo, map, set, sumBy, isEmpty, mapValues, get } from 'lodash'; -import * as mathjs from 'mathjs'; -import * as moment from 'moment'; -import { compose } from 'lodash/fp'; -import { I18nService } from 'nestjs-i18n'; -import { - ICashFlowSchemaSection, - ICashFlowStatementQuery, - ICashFlowStatementNetIncomeSection, - ICashFlowStatementAccountSection, - ICashFlowSchemaSectionAccounts, - ICashFlowStatementAccountMeta, - ICashFlowSchemaAccountRelation, - ICashFlowStatementSectionType, - ICashFlowStatementData, - ICashFlowSchemaTotalSection, - ICashFlowStatementTotalSection, - ICashFlowStatementSection, - ICashFlowCashBeginningNode, - ICashFlowStatementAggregateSection, -} from './Cashflow.types'; -import { CASH_FLOW_SCHEMA } from './schema'; -import { FinancialSheet } from '../../common/FinancialSheet'; -import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts'; -import { CashFlowStatementDatePeriods } from './CashFlowDatePeriods'; -import { DISPLAY_COLUMNS_BY } from './constants'; -import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; -import { Account } from '@/modules/Accounts/models/Account.model'; -import { ILedger } from '@/modules/Ledger/types/Ledger.types'; -import { INumberFormatQuery } from '../../types/Report.types'; -import { transformToMapBy } from '@/utils/transform-to-map-by'; -import { accumSum } from '@/utils/accum-sum'; -import { ModelObject } from 'objection'; - -export default class CashFlowStatement extends compose( - CashFlowStatementDatePeriods, - FinancialSheetStructure -)(FinancialSheet) { - readonly baseCurrency: string; - readonly i18n: I18nService; - readonly sectionsByIds = {}; - readonly cashFlowSchemaMap: Map; - readonly cashFlowSchemaSeq: Array; - readonly accountByTypeMap: Map; - readonly accountsByRootType: Map; - readonly ledger: ILedger; - readonly cashLedger: ILedger; - readonly netIncomeLedger: ILedger; - readonly query: ICashFlowStatementQuery; - readonly numberFormat: INumberFormatQuery; - readonly comparatorDateType: string; - readonly dateRangeSet: { fromDate: Date; toDate: Date }[]; - - /** - * Constructor method. - * @constructor - */ - constructor( - accounts: Account[], - ledger: ILedger, - cashLedger: ILedger, - netIncomeLedger: ILedger, - query: ICashFlowStatementQuery, - baseCurrency: string, - i18n - ) { - super(); - - this.baseCurrency = baseCurrency; - this.i18n = i18n; - this.ledger = ledger; - this.cashLedger = cashLedger; - this.netIncomeLedger = netIncomeLedger; - this.accountByTypeMap = transformToMapBy(accounts, 'accountType'); - this.accountsByRootType = transformToMapBy(accounts, 'accountRootType'); - this.query = query; - this.numberFormat = this.query.numberFormat; - this.dateRangeSet = []; - this.comparatorDateType = - query.displayColumnsType === 'total' ? 'day' : query.displayColumnsBy; - - this.initDateRangeCollection(); - } - - // -------------------------------------------- - // # GENERAL UTILITIES - // -------------------------------------------- - /** - * Retrieve the expense accounts ids. - * @return {number[]} - */ - private getAccountsIdsByType = (accountType: string): number[] => { - const expenseAccounts = this.accountsByRootType.get(accountType); - const expenseAccountsIds = map(expenseAccounts, 'id'); - - return expenseAccountsIds; - }; - - /** - * Detarmines the given display columns by type. - * @param {string} displayColumnsBy - * @returns {boolean} - */ - private isDisplayColumnsBy = (displayColumnsBy: string): boolean => { - return this.query.displayColumnsType === displayColumnsBy; - }; - - /** - * Adjustments the given amount. - * @param {string} direction - * @param {number} amount - - * @return {number} - */ - private amountAdjustment = (direction: 'mines' | 'plus', amount): number => { - return R.when( - R.always(R.equals(direction, 'mines')), - R.multiply(-1) - )(amount); - }; - - // -------------------------------------------- - // # NET INCOME NODE - // -------------------------------------------- - /** - * Retrieve the accounts net income. - * @returns {number} - Amount of net income. - */ - private getAccountsNetIncome(): number { - // Mapping income/expense accounts ids. - const incomeAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.INCOME - ); - const expenseAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.EXPENSE - ); - // Income closing balance. - const incomeClosingBalance = accumSum(incomeAccountsIds, (id) => - this.netIncomeLedger.whereAccountId(id).getClosingBalance() - ); - // Expense closing balance. - const expenseClosingBalance = accumSum(expenseAccountsIds, (id) => - this.netIncomeLedger.whereAccountId(id).getClosingBalance() - ); - // Net income = income - expenses. - const netIncome = incomeClosingBalance - expenseClosingBalance; - - return netIncome; - } - - /** - * Parses the net income section from the given section schema. - * @param {ICashFlowSchemaSection} nodeSchema - Report section schema. - * @returns {ICashFlowStatementNetIncomeSection} - */ - private netIncomeSectionMapper = ( - nodeSchema: ICashFlowSchemaSection - ): ICashFlowStatementNetIncomeSection => { - const netIncome = this.getAccountsNetIncome(); - - const node = { - id: nodeSchema.id, - label: this.i18n.__(nodeSchema.label), - total: this.getAmountMeta(netIncome), - sectionType: ICashFlowStatementSectionType.NET_INCOME, - }; - return R.compose( - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToNetIncomeNode - ) - )(node); - }; - - // -------------------------------------------- - // # ACCOUNT NODE - // -------------------------------------------- - /** - * Retrieve account meta. - * @param {ICashFlowSchemaAccountRelation} relation - Account relation. - * @param {IAccount} account - - * @returns {ICashFlowStatementAccountMeta} - */ - private accountMetaMapper = ( - relation: ICashFlowSchemaAccountRelation, - account: ModelObject - ): ICashFlowStatementAccountMeta => { - // Retrieve the closing balance of the given account. - const getClosingBalance = (id) => - this.ledger.whereAccountId(id).getClosingBalance(); - - const closingBalance = R.compose( - // Multiplies the amount by -1 in case the relation in mines. - R.curry(this.amountAdjustment)(relation.direction) - )(getClosingBalance(account.id)); - - const node = { - id: account.id, - code: account.code, - label: account.name, - accountType: account.accountType, - adjustmentType: relation.direction, - total: this.getAmountMeta(closingBalance), - sectionType: ICashFlowStatementSectionType.ACCOUNT, - }; - return R.compose( - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToAccountNode - ) - )(node); - }; - - /** - * Retrieve accounts sections by the given schema relation. - * @param {ICashFlowSchemaAccountRelation} relation - * @returns {ICashFlowStatementAccountMeta[]} - */ - private getAccountsBySchemaRelation = ( - relation: ICashFlowSchemaAccountRelation - ): ICashFlowStatementAccountMeta[] => { - const accounts = defaultTo(this.accountByTypeMap.get(relation.type), []); - const accountMetaMapper = R.curry(this.accountMetaMapper)(relation); - return R.map(accountMetaMapper)(accounts); - }; - - /** - * Retrieve the accounts meta. - * @param {string[]} types - * @returns {ICashFlowStatementAccountMeta[]} - */ - private getAccountsBySchemaRelations = ( - relations: ICashFlowSchemaAccountRelation[] - ): ICashFlowStatementAccountMeta[] => { - return R.pipe( - R.append(R.map(this.getAccountsBySchemaRelation)(relations)), - R.flatten - )([]); - }; - - /** - * Calculates the accounts total - * @param {ICashFlowStatementAccountMeta[]} accounts - * @returns {number} - */ - private getAccountsMetaTotal = ( - accounts: ICashFlowStatementAccountMeta[] - ): number => { - return sumBy(accounts, 'total.amount'); - }; - - /** - * Retrieve the accounts section from the section schema. - * @param {ICashFlowSchemaSectionAccounts} sectionSchema - * @returns {ICashFlowStatementAccountSection} - */ - private accountsSectionParser = ( - sectionSchema: ICashFlowSchemaSectionAccounts - ): ICashFlowStatementAccountSection => { - const { accountsRelations } = sectionSchema; - - const accounts = this.getAccountsBySchemaRelations(accountsRelations); - const accountsTotal = this.getAccountsMetaTotal(accounts); - const total = this.getTotalAmountMeta(accountsTotal); - - const node = { - sectionType: ICashFlowStatementSectionType.ACCOUNTS, - id: sectionSchema.id, - label: this.i18n.__(sectionSchema.label), - footerLabel: this.i18n.__(sectionSchema.footerLabel), - children: accounts, - total, - }; - return R.compose( - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToAggregateNode - ) - )(node); - }; - - /** - * Detarmines the schema section type. - * @param {string} type - * @param {ICashFlowSchemaSection} section - * @returns {boolean} - */ - private isSchemaSectionType = R.curry( - (type: string, section: ICashFlowSchemaSection): boolean => { - return type === section.sectionType; - } - ); - - // -------------------------------------------- - // # AGGREGATE NODE - // -------------------------------------------- - /** - * Aggregate schema node parser to aggregate report node. - * @param {ICashFlowSchemaSection} schemaSection - * @returns {ICashFlowStatementAggregateSection} - */ - private regularSectionParser = R.curry( - ( - children, - schemaSection: ICashFlowSchemaSection - ): ICashFlowStatementAggregateSection => { - const node = { - id: schemaSection.id, - label: this.i18n.__(schemaSection.label), - footerLabel: this.i18n.__(schemaSection.footerLabel), - sectionType: ICashFlowStatementSectionType.AGGREGATE, - children, - }; - return R.compose( - R.when( - this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE), - this.assocRegularSectionTotal - ), - R.when( - this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE), - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocPeriodsToAggregateNode - ) - ) - )(node); - } - ); - - private transformSectionsToMap = (sections: ICashFlowSchemaSection[]) => { - return this.reduceNodesDeep( - sections, - (acc, section) => { - if (section.id) { - acc[`${section.id}`] = section; - } - return acc; - }, - {} - ); - }; - - // -------------------------------------------- - // # TOTAL EQUATION NODE - // -------------------------------------------- - - private sectionsMapToTotal = (mappedSections: { [key: number]: any }) => { - return mapValues(mappedSections, (node) => get(node, 'total.amount') || 0); - }; - - /** - * Evauluate equaation string with the given scope table. - * @param {string} equation - - * @param {{ [key: string]: number }} scope - - * @return {number} - */ - private evaluateEquation = ( - equation: string, - scope: { [key: string | number]: number } - ): number => { - return mathjs.evaluate(equation, scope); - }; - - /** - * Retrieve the total section from the eqauation parser. - * @param {ICashFlowSchemaTotalSection} sectionSchema - * @param {ICashFlowSchemaSection[]} accumulatedSections - * @returns {ICashFlowStatementTotalSection} - */ - private totalEquationSectionParser = ( - accumulatedSections: ICashFlowSchemaSection[], - sectionSchema: ICashFlowSchemaTotalSection - ): ICashFlowStatementTotalSection => { - const mappedSectionsById = this.transformSectionsToMap(accumulatedSections); - const nodesTotalById = this.sectionsMapToTotal(mappedSectionsById); - - const total = this.evaluateEquation(sectionSchema.equation, nodesTotalById); - - return R.compose( - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - R.curry(this.assocTotalEquationDatePeriods)( - mappedSectionsById, - sectionSchema.equation - ) - ) - )({ - sectionType: ICashFlowStatementSectionType.TOTAL, - id: sectionSchema.id, - label: this.i18n.__(sectionSchema.label), - total: this.getTotalAmountMeta(total), - }); - }; - - /** - * Retrieve the beginning cash from date. - * @param {Date|string} fromDate - - * @return {Date} - */ - private beginningCashFrom = (fromDate: string | Date): Date => { - return moment(fromDate).subtract(1, 'days').toDate(); - }; - - /** - * Retrieve account meta. - * @param {ICashFlowSchemaAccountRelation} relation - * @param {IAccount} account - * @returns {ICashFlowStatementAccountMeta} - */ - private cashAccountMetaMapper = ( - relation: ICashFlowSchemaAccountRelation, - account: ModelObject - ): ICashFlowStatementAccountMeta => { - const cashToDate = this.beginningCashFrom(this.query.fromDate); - - const closingBalance = this.cashLedger - .whereToDate(cashToDate) - .whereAccountId(account.id) - .getClosingBalance(); - - const node = { - id: account.id, - code: account.code, - label: account.name, - accountType: account.accountType, - adjustmentType: relation.direction, - total: this.getAmountMeta(closingBalance), - sectionType: ICashFlowStatementSectionType.ACCOUNT, - }; - return R.compose( - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocCashAtBeginningAccountDatePeriods - ) - )(node); - }; - - /** - * Retrieve accounts sections by the given schema relation. - * @param {ICashFlowSchemaAccountRelation} relation - * @returns {ICashFlowStatementAccountMeta[]} - */ - private getCashAccountsBySchemaRelation = ( - relation: ICashFlowSchemaAccountRelation - ): ICashFlowStatementAccountMeta[] => { - const accounts = this.accountByTypeMap.get(relation.type) || []; - const accountMetaMapper = R.curry(this.cashAccountMetaMapper)(relation); - return accounts.map(accountMetaMapper); - }; - - /** - * Retrieve the accounts meta. - * @param {string[]} types - * @returns {ICashFlowStatementAccountMeta[]} - */ - private getCashAccountsBySchemaRelations = ( - relations: ICashFlowSchemaAccountRelation[] - ): ICashFlowStatementAccountMeta[] => { - return R.concat(...R.map(this.getCashAccountsBySchemaRelation)(relations)); - }; - - /** - * Parses the cash at beginning section. - * @param {ICashFlowSchemaTotalSection} sectionSchema - - * @return {ICashFlowCashBeginningNode} - */ - private cashAtBeginningSectionParser = ( - nodeSchema: ICashFlowSchemaSection - ): ICashFlowCashBeginningNode => { - const { accountsRelations } = nodeSchema; - const children = this.getCashAccountsBySchemaRelations(accountsRelations); - const total = this.getAccountsMetaTotal(children); - - const node = { - sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING, - id: nodeSchema.id, - label: this.i18n.__(nodeSchema.label), - children, - total: this.getTotalAmountMeta(total), - }; - return R.compose( - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - this.assocCashAtBeginningDatePeriods - ) - )(node); - }; - - /** - * Parses the schema section. - * @param {ICashFlowSchemaSection} schemaNode - * @returns {ICashFlowSchemaSection} - */ - private schemaSectionParser = ( - schemaNode: ICashFlowSchemaSection, - children - ): ICashFlowSchemaSection | ICashFlowStatementSection => { - return R.compose( - // Accounts node. - R.when( - this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS), - this.accountsSectionParser - ), - // Net income node. - R.when( - this.isSchemaSectionType(ICashFlowStatementSectionType.NET_INCOME), - this.netIncomeSectionMapper - ), - // Cash at beginning node. - R.when( - this.isSchemaSectionType( - ICashFlowStatementSectionType.CASH_AT_BEGINNING - ), - this.cashAtBeginningSectionParser - ), - // Aggregate node. (that has no section type). - R.when( - this.isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE), - this.regularSectionParser(children) - ) - )(schemaNode); - }; - - /** - * Parses the schema section. - * @param {ICashFlowSchemaSection | ICashFlowStatementSection} section - * @param {number} key - * @param {ICashFlowSchemaSection[]} parentValue - * @param {(ICashFlowSchemaSection | ICashFlowStatementSection)[]} accumulatedSections - * @returns {ICashFlowSchemaSection} - */ - private schemaSectionTotalParser = ( - section: ICashFlowSchemaSection | ICashFlowStatementSection, - key: number, - parentValue: ICashFlowSchemaSection[], - context, - accumulatedSections: (ICashFlowSchemaSection | ICashFlowStatementSection)[] - ): ICashFlowSchemaSection | ICashFlowStatementSection => { - return R.compose( - // Total equation section. - R.when( - this.isSchemaSectionType(ICashFlowStatementSectionType.TOTAL), - R.curry(this.totalEquationSectionParser)(accumulatedSections) - ) - )(section); - }; - - /** - * Schema sections parser. - * @param {ICashFlowSchemaSection[]}schema - * @returns {ICashFlowStatementSection[]} - */ - private schemaSectionsParser = ( - schema: ICashFlowSchemaSection[] - ): ICashFlowStatementSection[] => { - return this.mapNodesDeepReverse(schema, this.schemaSectionParser); - }; - - /** - * Writes the `total` property to the aggregate node. - * @param {ICashFlowStatementSection} section - * @return {ICashFlowStatementSection} - */ - private assocRegularSectionTotal = (section: ICashFlowStatementSection) => { - const total = this.getAccountsMetaTotal(section.children); - return R.assoc('total', this.getTotalAmountMeta(total), section); - }; - - /** - * Parses total schema nodes. - * @param {(ICashFlowSchemaSection | ICashFlowStatementSection)[]} sections - * @returns {(ICashFlowSchemaSection | ICashFlowStatementSection)[]} - */ - private totalSectionsParser = ( - sections: (ICashFlowSchemaSection | ICashFlowStatementSection)[] - ): (ICashFlowSchemaSection | ICashFlowStatementSection)[] => { - return this.reduceNodesDeep( - sections, - (acc, value, key, parentValue, context) => { - set( - acc, - context.path, - this.schemaSectionTotalParser(value, key, parentValue, context, acc) - ); - return acc; - }, - [] - ); - }; - - // -------------------------------------------- - // REPORT FILTERING - // -------------------------------------------- - /** - * Detarmines the given section has children and not empty. - * @param {ICashFlowStatementSection} section - * @returns {boolean} - */ - private isSectionHasChildren = ( - section: ICashFlowStatementSection - ): boolean => { - return !isEmpty(section.children); - }; - - /** - * Detarmines whether the section has no zero amount. - * @param {ICashFlowStatementSection} section - * @returns {boolean} - */ - private isSectionNoneZero = (section: ICashFlowStatementSection): boolean => { - return section.total.amount !== 0; - }; - - /** - * Detarmines whether the parent accounts sections has children. - * @param {ICashFlowStatementSection} section - * @returns {boolean} - */ - private isAccountsSectionHasChildren = ( - section: ICashFlowStatementSection[] - ): boolean => { - return R.ifElse( - this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS), - this.isSectionHasChildren, - R.always(true) - )(section); - }; - - /** - * Detarmines the account section has no zero otherwise returns true. - * @param {ICashFlowStatementSection} section - * @returns {boolean} - */ - private isAccountLeafNoneZero = ( - section: ICashFlowStatementSection[] - ): boolean => { - return R.ifElse( - this.isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNT), - this.isSectionNoneZero, - R.always(true) - )(section); - }; - - /** - * Deep filters the non-zero accounts leafs of the report sections. - * @param {ICashFlowStatementSection[]} sections - * @returns {ICashFlowStatementSection[]} - */ - private filterNoneZeroAccountsLeafs = ( - sections: ICashFlowStatementSection[] - ): ICashFlowStatementSection[] => { - return this.filterNodesDeep(sections, this.isAccountLeafNoneZero); - }; - - /** - * Deep filter the non-children sections of the report sections. - * @param {ICashFlowStatementSection[]} sections - * @returns {ICashFlowStatementSection[]} - */ - private filterNoneChildrenSections = ( - sections: ICashFlowStatementSection[] - ): ICashFlowStatementSection[] => { - return this.filterNodesDeep(sections, this.isAccountsSectionHasChildren); - }; - - /** - * Filters the report data. - * @param {ICashFlowStatementSection[]} sections - * @returns {ICashFlowStatementSection[]} - */ - private filterReportData = ( - sections: ICashFlowStatementSection[] - ): ICashFlowStatementSection[] => { - return R.compose( - this.filterNoneChildrenSections, - this.filterNoneZeroAccountsLeafs - )(sections); - }; - - /** - * Schema parser. - * @param {ICashFlowSchemaSection[]} schema - * @returns {ICashFlowSchemaSection[]} - */ - private schemaParser = ( - schema: ICashFlowSchemaSection[] - ): ICashFlowSchemaSection[] => { - return R.compose( - R.when( - R.always(this.query.noneTransactions || this.query.noneZero), - this.filterReportData - ), - this.totalSectionsParser, - this.schemaSectionsParser - )(schema); - }; - - /** - * Retrieve the cashflow statement data. - * @return {ICashFlowStatementData} - */ - public reportData = (): ICashFlowStatementData => { - return this.schemaParser(R.clone(CASH_FLOW_SCHEMA)); - }; -} diff --git a/temp/CashFlowStatement/CashFlowDatePeriods.ts b/temp/CashFlowStatement/CashFlowDatePeriods.ts deleted file mode 100644 index a6986ab4c..000000000 --- a/temp/CashFlowStatement/CashFlowDatePeriods.ts +++ /dev/null @@ -1,412 +0,0 @@ -import * as R from 'ramda'; -import { sumBy, mapValues, get } from 'lodash'; -import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts'; -import { - ICashFlowDatePeriod, - ICashFlowStatementNetIncomeSection, - ICashFlowStatementAccountSection, - ICashFlowStatementSection, - ICashFlowSchemaTotalSection, - ICashFlowStatementTotalSection, - ICashFlowStatementQuery, - IDateRange, -} from './Cashflow.types'; -import { IFormatNumberSettings } from '../../types/Report.types'; -import { dateRangeFromToCollection } from '@/utils/date-range-collection'; -import { accumSum } from '@/utils/accum-sum'; - -export const CashFlowStatementDatePeriods = (Base) => - class extends Base { - dateRangeSet: IDateRange[]; - query: ICashFlowStatementQuery; - - /** - * Initialize date range set. - */ - public initDateRangeCollection() { - this.dateRangeSet = dateRangeFromToCollection( - this.query.fromDate, - this.query.toDate, - this.comparatorDateType - ); - } - - /** - * Retrieve the date period meta. - * @param {number} total - Total amount. - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. - * @return {ICashFlowDatePeriod} - */ - public getDatePeriodTotalMeta = ( - total: number, - fromDate: Date, - toDate: Date, - overrideSettings: IFormatNumberSettings = {} - ): ICashFlowDatePeriod => { - return this.getDatePeriodMeta(total, fromDate, toDate, { - money: true, - ...overrideSettings, - }); - }; - - /** - * Retrieve the date period meta. - * @param {number} total - Total amount. - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. - * @return {ICashFlowDatePeriod} - */ - public getDatePeriodMeta = ( - total: number, - fromDate: Date, - toDate: Date, - overrideSettings?: IFormatNumberSettings - ): ICashFlowDatePeriod => { - return { - fromDate: this.getDateMeta(fromDate), - toDate: this.getDateMeta(toDate), - total: this.getAmountMeta(total, overrideSettings), - }; - }; - - // Net income -------------------- - /** - * Retrieve the net income between the given date range. - * @param {Date} fromDate - * @param {Date} toDate - * @returns {number} - */ - public getNetIncomeDateRange = (fromDate: Date, toDate: Date) => { - // Mapping income/expense accounts ids. - const incomeAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.INCOME - ); - const expenseAccountsIds = this.getAccountsIdsByType( - ACCOUNT_ROOT_TYPE.EXPENSE - ); - // Income closing balance. - const incomeClosingBalance = accumSum(incomeAccountsIds, (id) => - this.netIncomeLedger - .whereFromDate(fromDate) - .whereToDate(toDate) - .whereAccountId(id) - .getClosingBalance() - ); - // Expense closing balance. - const expenseClosingBalance = accumSum(expenseAccountsIds, (id) => - this.netIncomeLedger - .whereToDate(toDate) - .whereFromDate(fromDate) - .whereAccountId(id) - .getClosingBalance() - ); - // Net income = income - expenses. - const netIncome = incomeClosingBalance - expenseClosingBalance; - - return netIncome; - }; - - /** - * Retrieve the net income of date period. - * @param {IDateRange} dateRange - - * @retrun {ICashFlowDatePeriod} - */ - public getNetIncomeDatePeriod = (dateRange): ICashFlowDatePeriod => { - const total = this.getNetIncomeDateRange( - dateRange.fromDate, - dateRange.toDate - ); - return this.getDatePeriodMeta( - total, - dateRange.fromDate, - dateRange.toDate - ); - }; - - /** - * Retrieve the net income node between the given date ranges. - * @param {Date} fromDate - * @param {Date} toDate - * @returns {ICashFlowDatePeriod[]} - */ - public getNetIncomeDatePeriods = ( - section: ICashFlowStatementNetIncomeSection - ): ICashFlowDatePeriod[] => { - return this.dateRangeSet.map(this.getNetIncomeDatePeriod.bind(this)); - }; - - /** - * Writes periods property to net income section. - * @param {ICashFlowStatementNetIncomeSection} section - * @returns {ICashFlowStatementNetIncomeSection} - */ - public assocPeriodsToNetIncomeNode = ( - section: ICashFlowStatementNetIncomeSection - ): ICashFlowStatementNetIncomeSection => { - const incomeDatePeriods = this.getNetIncomeDatePeriods(section); - return R.assoc('periods', incomeDatePeriods, section); - }; - - // Account nodes -------------------- - /** - * Retrieve the account total between date range. - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. - * @return {number} - */ - public getAccountTotalDateRange = ( - node: ICashFlowStatementAccountSection, - fromDate: Date, - toDate: Date - ): number => { - const closingBalance = this.ledger - .whereFromDate(fromDate) - .whereToDate(toDate) - .whereAccountId(node.id) - .getClosingBalance(); - - return this.amountAdjustment(node.adjustmentType, closingBalance); - }; - - /** - * Retrieve the given account node total date period. - * @param {ICashFlowStatementAccountSection} node - - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. - * @return {ICashFlowDatePeriod} - */ - public getAccountTotalDatePeriod = ( - node: ICashFlowStatementAccountSection, - fromDate: Date, - toDate: Date - ): ICashFlowDatePeriod => { - const total = this.getAccountTotalDateRange(node, fromDate, toDate); - return this.getDatePeriodMeta(total, fromDate, toDate); - }; - - /** - * Retrieve the accounts date periods nodes of the give account node. - * @param {ICashFlowStatementAccountSection} node - - * @return {ICashFlowDatePeriod[]} - */ - public getAccountDatePeriods = ( - node: ICashFlowStatementAccountSection - ): ICashFlowDatePeriod[] => { - return this.getNodeDatePeriods( - node, - this.getAccountTotalDatePeriod.bind(this) - ); - } - - /** - * Writes `periods` property to account node. - * @param {ICashFlowStatementAccountSection} node - - * @return {ICashFlowStatementAccountSection} - */ - public assocPeriodsToAccountNode = ( - node: ICashFlowStatementAccountSection - ): ICashFlowStatementAccountSection => { - const datePeriods = this.getAccountDatePeriods(node); - return R.assoc('periods', datePeriods, node); - } - - // Aggregate node ------------------------- - /** - * Retrieve total of the given period index for node that has children nodes. - * @return {number} - */ - public getChildrenTotalPeriodByIndex = ( - node: ICashFlowStatementSection, - index: number - ): number => { - return sumBy(node.children, `periods[${index}].total.amount`); - } - - /** - * Retrieve date period meta of the given node index. - * @param {ICashFlowStatementSection} node - - * @param {number} index - Loop index. - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. - */ - public getChildrenTotalPeriodMetaByIndex( - node: ICashFlowStatementSection, - index: number, - fromDate: Date, - toDate: Date - ) { - const total = this.getChildrenTotalPeriodByIndex(node, index); - return this.getDatePeriodTotalMeta(total, fromDate, toDate); - } - - /** - * Retrieve the date periods of aggregate node. - * @param {ICashFlowStatementSection} node - */ - public getAggregateNodeDatePeriods(node: ICashFlowStatementSection) { - const getChildrenTotalPeriodMetaByIndex = R.curry( - this.getChildrenTotalPeriodMetaByIndex.bind(this) - )(node); - - return this.dateRangeSet.map((dateRange, index) => - getChildrenTotalPeriodMetaByIndex( - index, - dateRange.fromDate, - dateRange.toDate - ) - ); - } - - /** - * Writes `periods` property to aggregate section node. - * @param {ICashFlowStatementSection} node - - * @return {ICashFlowStatementSection} - */ - public assocPeriodsToAggregateNode = ( - node: ICashFlowStatementSection - ): ICashFlowStatementSection => { - const datePeriods = this.getAggregateNodeDatePeriods(node); - return R.assoc('periods', datePeriods, node); - }; - - // Total equation node -------------------- - - public sectionsMapToTotalPeriod = ( - mappedSections: { [key: number]: any }, - index - ) => { - return mapValues( - mappedSections, - (node) => get(node, `periods[${index}].total.amount`) || 0 - ); - }; - - /** - * Retrieve the date periods of the given total equation. - * @param {ICashFlowSchemaTotalSection} - * @param {string} equation - - * @return {ICashFlowDatePeriod[]} - */ - public getTotalEquationDatePeriods = ( - node: ICashFlowSchemaTotalSection, - equation: string, - nodesTable - ): ICashFlowDatePeriod[] => { - return this.getNodeDatePeriods(node, (node, fromDate, toDate, index) => { - const periodScope = this.sectionsMapToTotalPeriod(nodesTable, index); - const total = this.evaluateEquation(equation, periodScope); - - return this.getDatePeriodTotalMeta(total, fromDate, toDate); - }); - }; - - /** - * Associates the total periods of total equation to the ginve total node.. - * @param {ICashFlowSchemaTotalSection} totalSection - - * @return {ICashFlowStatementTotalSection} - */ - public assocTotalEquationDatePeriods = ( - nodesTable: any, - equation: string, - node: ICashFlowSchemaTotalSection - ): ICashFlowStatementTotalSection => { - const datePeriods = this.getTotalEquationDatePeriods( - node, - equation, - nodesTable - ); - - return R.assoc('periods', datePeriods, node); - }; - - // Cash at beginning ---------------------- - - /** - * Retrieve the date preioods of the given node and accumulated function. - * @param {} node - * @param {} - * @return {} - */ - public getNodeDatePeriods = (node, callback) => { - const curriedCallback = R.curry(callback)(node); - - return this.dateRangeSet.map((dateRange, index) => { - return curriedCallback(dateRange.fromDate, dateRange.toDate, index); - }); - }; - - /** - * Retrieve the account total between date range. - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. - * @return {number} - */ - public getBeginningCashAccountDateRange = ( - node: ICashFlowStatementSection, - fromDate: Date, - toDate: Date - ) => { - const cashToDate = this.beginningCashFrom(fromDate); - - return this.cashLedger - .whereToDate(cashToDate) - .whereAccountId(node.id) - .getClosingBalance(); - }; - - /** - * Retrieve the beginning cash date period. - * @param {ICashFlowStatementSection} node - - * @param {Date} fromDate - From date. - * @param {Date} toDate - To date. - * @return {ICashFlowDatePeriod} - */ - public getBeginningCashDatePeriod = ( - node: ICashFlowStatementSection, - fromDate: Date, - toDate: Date - ) => { - const total = this.getBeginningCashAccountDateRange( - node, - fromDate, - toDate - ); - return this.getDatePeriodTotalMeta(total, fromDate, toDate); - }; - - /** - * Retrieve the beginning cash account periods. - * @param {ICashFlowStatementSection} node - * @return {ICashFlowDatePeriod} - */ - public getBeginningCashAccountPeriods = ( - node: ICashFlowStatementSection - ): ICashFlowDatePeriod => { - return this.getNodeDatePeriods(node, this.getBeginningCashDatePeriod); - }; - - /** - * Writes `periods` property to cash at beginning date periods. - * @param {ICashFlowStatementSection} section - - * @return {ICashFlowStatementSection} - */ - public assocCashAtBeginningDatePeriods = ( - node: ICashFlowStatementSection - ): ICashFlowStatementSection => { - const datePeriods = this.getAggregateNodeDatePeriods(node); - return R.assoc('periods', datePeriods, node); - }; - - /** - * Associates `periods` propery to cash at beginning account node. - * @param {ICashFlowStatementSection} node - - * @return {ICashFlowStatementSection} - */ - public assocCashAtBeginningAccountDatePeriods = ( - node: ICashFlowStatementSection - ): ICashFlowStatementSection => { - const datePeriods = this.getBeginningCashAccountPeriods(node); - return R.assoc('periods', datePeriods, node); - }; - }; diff --git a/temp/CashFlowStatement/CashFlowTable.ts b/temp/CashFlowStatement/CashFlowTable.ts deleted file mode 100644 index d54504254..000000000 --- a/temp/CashFlowStatement/CashFlowTable.ts +++ /dev/null @@ -1,374 +0,0 @@ -import * as R from 'ramda'; -import { isEmpty, } from 'lodash'; -import moment from 'moment'; -import { - ICashFlowStatementSection, - ICashFlowStatementSectionType, - IDateRange, - ICashFlowStatementDOO, -} from './Cashflow.types'; -import { ITableRow, ITableColumn } from '../../types/Table.types'; -import { dateRangeFromToCollection } from '@/utils/date-range-collection'; -import { tableRowMapper } from '../../utils/Table.utils'; -import { mapValuesDeep } from '@/utils/deepdash'; - -enum IROW_TYPE { - AGGREGATE = 'AGGREGATE', - NET_INCOME = 'NET_INCOME', - ACCOUNTS = 'ACCOUNTS', - ACCOUNT = 'ACCOUNT', - TOTAL = 'TOTAL', -} -const DEEP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; -const DISPLAY_COLUMNS_BY = { - DATE_PERIODS: 'date_periods', - TOTAL: 'total', -}; - -export class CashFlowTable { - private report: ICashFlowStatementDOO; - private i18n; - private dateRangeSet: IDateRange[]; - - /** - * Constructor method. - * @param {ICashFlowStatement} reportStatement - */ - constructor(reportStatement: ICashFlowStatementDOO, i18n) { - this.report = reportStatement; - this.i18n = i18n; - this.dateRangeSet = []; - this.initDateRangeCollection(); - } - - /** - * Initialize date range set. - */ - private initDateRangeCollection() { - this.dateRangeSet = dateRangeFromToCollection( - this.report.query.fromDate, - this.report.query.toDate, - this.report.query.displayColumnsBy, - ); - } - - /** - * Retrieve the date periods columns accessors. - */ - private datePeriodsColumnsAccessors = () => { - return this.dateRangeSet.map((dateRange: IDateRange, index) => ({ - key: `date-range-${index}`, - accessor: `periods[${index}].total.formattedAmount`, - })); - }; - - /** - * Retrieve the total column accessor. - */ - private totalColumnAccessor = () => { - return [{ key: 'total', accessor: 'total.formattedAmount' }]; - }; - - /** - * Retrieve the common columns for all report nodes. - */ - private commonColumns = () => { - return R.compose( - R.concat([{ key: 'name', accessor: 'label' }]), - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - R.concat(this.datePeriodsColumnsAccessors()), - ), - R.concat(this.totalColumnAccessor()), - )([]); - }; - - /** - * Retrieve the table rows of regular section. - * @param {ICashFlowStatementSection} section - * @returns {ITableRow[]} - */ - private regularSectionMapper = ( - section: ICashFlowStatementSection, - ): ITableRow => { - const columns = this.commonColumns(); - - return tableRowMapper(section, columns, { - rowTypes: [IROW_TYPE.AGGREGATE], - id: section.id, - }); - }; - - /** - * Retrieve the net income table rows of the section. - * @param {ICashFlowStatementSection} section - * @returns {ITableRow} - */ - private netIncomeSectionMapper = ( - section: ICashFlowStatementSection, - ): ITableRow => { - const columns = this.commonColumns(); - - return tableRowMapper(section, columns, { - rowTypes: [IROW_TYPE.NET_INCOME, IROW_TYPE.TOTAL], - id: section.id, - }); - }; - - /** - * Retrieve the accounts table rows of the section. - * @param {ICashFlowStatementSection} section - * @returns {ITableRow} - */ - private accountsSectionMapper = ( - section: ICashFlowStatementSection, - ): ITableRow => { - const columns = this.commonColumns(); - - return tableRowMapper(section, columns, { - rowTypes: [IROW_TYPE.ACCOUNTS], - id: section.id, - }); - }; - - /** - * Retrieve the account table row of account section. - * @param {ICashFlowStatementSection} section - * @returns {ITableRow} - */ - private accountSectionMapper = ( - section: ICashFlowStatementSection, - ): ITableRow => { - const columns = this.commonColumns(); - - return tableRowMapper(section, columns, { - rowTypes: [IROW_TYPE.ACCOUNT], - id: `account-${section.id}`, - }); - }; - - /** - * Retrieve the total table rows from the given total section. - * @param {ICashFlowStatementSection} section - * @returns {ITableRow} - */ - private totalSectionMapper = ( - section: ICashFlowStatementSection, - ): ITableRow => { - const columns = this.commonColumns(); - - return tableRowMapper(section, columns, { - rowTypes: [IROW_TYPE.TOTAL], - id: section.id, - }); - }; - - /** - * Detarmines the schema section type. - * @param {string} type - * @param {ICashFlowSchemaSection} section - * @returns {boolean} - */ - private isSectionHasType = ( - type: string, - section: ICashFlowStatementSection, - ): boolean => { - return type === section.sectionType; - }; - - /** - * The report section mapper. - * @param {ICashFlowStatementSection} section - * @returns {ITableRow} - */ - private sectionMapper = ( - section: ICashFlowStatementSection, - key: string, - parentSection: ICashFlowStatementSection, - ): ITableRow => { - const isSectionHasType = R.curry(this.isSectionHasType); - - return R.pipe( - R.when( - isSectionHasType(ICashFlowStatementSectionType.AGGREGATE), - this.regularSectionMapper, - ), - R.when( - isSectionHasType(ICashFlowStatementSectionType.CASH_AT_BEGINNING), - this.regularSectionMapper, - ), - R.when( - isSectionHasType(ICashFlowStatementSectionType.NET_INCOME), - this.netIncomeSectionMapper, - ), - R.when( - isSectionHasType(ICashFlowStatementSectionType.ACCOUNTS), - this.accountsSectionMapper, - ), - R.when( - isSectionHasType(ICashFlowStatementSectionType.ACCOUNT), - this.accountSectionMapper, - ), - R.when( - isSectionHasType(ICashFlowStatementSectionType.TOTAL), - this.totalSectionMapper, - ), - )(section); - }; - - /** - * Mappes the sections to the table rows. - * @param {ICashFlowStatementSection[]} sections - * @returns {ITableRow[]} - */ - private mapSectionsToTableRows = ( - sections: ICashFlowStatementSection[], - ): ITableRow[] => { - return mapValuesDeep(sections, this.sectionMapper.bind(this), DEEP_CONFIG); - }; - - /** - * Appends the total to section's children. - * @param {ICashFlowStatementSection} section - * @returns {ICashFlowStatementSection} - */ - private appendTotalToSectionChildren = ( - section: ICashFlowStatementSection, - ): ICashFlowStatementSection => { - const label = section.footerLabel - ? section.footerLabel - : this.i18n.__('Total {{accountName}}', { accountName: section.label }); - - section.children.push({ - sectionType: ICashFlowStatementSectionType.TOTAL, - label, - periods: section.periods, - total: section.total, - }); - return section; - }; - - /** - * - * @param {ICashFlowStatementSection} section - * @returns {ICashFlowStatementSection} - */ - private mapSectionsToAppendTotalChildren = ( - section: ICashFlowStatementSection, - ): ICashFlowStatementSection => { - const isSectionHasChildren = (section) => !isEmpty(section.children); - - return R.compose( - R.when( - isSectionHasChildren, - this.appendTotalToSectionChildren.bind(this), - ), - )(section); - }; - - /** - * Appends total node to children section. - * @param {ICashFlowStatementSection[]} sections - * @returns {ICashFlowStatementSection[]} - */ - private appendTotalToChildren = (sections: ICashFlowStatementSection[]) => { - return mapValuesDeep( - sections, - this.mapSectionsToAppendTotalChildren.bind(this), - DEEP_CONFIG, - ); - }; - - /** - * Retrieve the table rows of cash flow statement. - * @param {ICashFlowStatementSection[]} sections - * @returns {ITableRow[]} - */ - public tableRows = (): ITableRow[] => { - const sections = this.report.data; - - return R.pipe( - this.appendTotalToChildren, - this.mapSectionsToTableRows, - )(sections); - }; - - /** - * Retrieve the total columns. - * @returns {ITableColumn} - */ - private totalColumns = (): ITableColumn[] => { - return [{ key: 'total', label: this.i18n.__('Total') }]; - }; - - /** - * Retrieve the formatted column label from the given date range. - * @param {ICashFlowDateRange} dateRange - - * @return {string} - */ - private formatColumnLabel = (dateRange: ICashFlowDateRange) => { - const monthFormat = (range) => moment(range.toDate).format('YYYY-MM'); - const yearFormat = (range) => moment(range.toDate).format('YYYY'); - const dayFormat = (range) => moment(range.toDate).format('YYYY-MM-DD'); - - const conditions = [ - ['month', monthFormat], - ['year', yearFormat], - ['day', dayFormat], - ['quarter', monthFormat], - ['week', dayFormat], - ]; - const conditionsPairs = R.map( - ([type, formatFn]) => [ - R.always(this.isDisplayColumnsType(type)), - formatFn, - ], - conditions, - ); - - return R.compose(R.cond(conditionsPairs))(dateRange); - }; - - /** - * Date periods columns. - * @returns {ITableColumn[]} - */ - private datePeriodsColumns = (): ITableColumn[] => { - return this.dateRangeSet.map((dateRange, index) => ({ - key: `date-range-${index}`, - label: this.formatColumnLabel(dateRange), - })); - }; - - /** - * Detarmines the given column type is the current. - * @reutrns {boolean} - */ - private isDisplayColumnsBy = (displayColumnsType: string): Boolean => { - return this.report.query.displayColumnsType === displayColumnsType; - }; - - /** - * Detarmines whether the given display columns type is the current. - * @param {string} displayColumnsBy - * @returns {boolean} - */ - private isDisplayColumnsType = (displayColumnsBy: string): Boolean => { - return this.report.query.displayColumnsBy === displayColumnsBy; - }; - - /** - * Retrieve the table columns. - * @return {ITableColumn[]} - */ - public tableColumns = (): ITableColumn[] => { - return R.compose( - R.concat([{ key: 'name', label: this.i18n.__('Account name') }]), - R.when( - R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - R.concat(this.datePeriodsColumns()), - ), - R.concat(this.totalColumns()), - )([]); - }; -} diff --git a/temp/CashFlowStatement/Cashflow.types.ts b/temp/CashFlowStatement/Cashflow.types.ts deleted file mode 100644 index 6d21ca118..000000000 --- a/temp/CashFlowStatement/Cashflow.types.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { Knex } from 'knex'; -import { IFinancialSheetCommonMeta, INumberFormatQuery } from '../../types/Report.types'; -import { Account } from '@/modules/Accounts/models/Account.model'; -import { Ledger } from '@/modules/Ledger/Ledger'; -import { IFinancialTable, ITableRow } from '../../types/Table.types'; - - -export interface ICashFlowStatementQuery { - fromDate: Date | string; - toDate: Date | string; - displayColumnsBy: string; - displayColumnsType: string; - noneZero: boolean; - noneTransactions: boolean; - numberFormat: INumberFormatQuery; - basis: string; - - branchesIds?: number[]; -} - -export interface ICashFlowStatementTotal { - amount: number; - formattedAmount: string; - currencyCode: string; -} - -export interface ICashFlowStatementTotalPeriod { - fromDate: Date; - toDate: Date; - total: ICashFlowStatementTotal; -} - -export interface ICashFlowStatementCommonSection { - id: string; - label: string; - total: ICashFlowStatementTotal; - footerLabel?: string; -} - -export interface ICashFlowStatementAccountMeta { - id: number; - label: string; - code: string; - total: ICashFlowStatementTotal; - accountType: string; - adjustmentType: string; - sectionType: ICashFlowStatementSectionType.ACCOUNT; -} - -export enum ICashFlowStatementSectionType { - REGULAR = 'REGULAR', - AGGREGATE = 'AGGREGATE', - NET_INCOME = 'NET_INCOME', - ACCOUNT = 'ACCOUNT', - ACCOUNTS = 'ACCOUNTS', - TOTAL = 'TOTAL', - CASH_AT_BEGINNING = 'CASH_AT_BEGINNING', -} - -export interface ICashFlowStatementAccountSection - extends ICashFlowStatementCommonSection { - sectionType: ICashFlowStatementSectionType.ACCOUNTS; - children: ICashFlowStatementAccountMeta[]; - total: ICashFlowStatementTotal; -} - -export interface ICashFlowStatementNetIncomeSection - extends ICashFlowStatementCommonSection { - sectionType: ICashFlowStatementSectionType.NET_INCOME; -} - -export interface ICashFlowStatementTotalSection - extends ICashFlowStatementCommonSection { - sectionType: ICashFlowStatementSectionType.TOTAL; -} - -export interface ICashFlowStatementAggregateSection - extends ICashFlowStatementCommonSection { - sectionType: ICashFlowStatementSectionType.AGGREGATE; -} - -export interface ICashFlowCashBeginningNode - extends ICashFlowStatementCommonSection { - sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING; -} - -export type ICashFlowStatementSection = - | ICashFlowStatementAccountSection - | ICashFlowStatementNetIncomeSection - | ICashFlowStatementTotalSection - | ICashFlowStatementCommonSection; - -export interface ICashFlowStatementColumn {} -export interface ICashFlowStatementMeta extends IFinancialSheetCommonMeta { - formattedToDate: string; - formattedFromDate: string; - formattedDateRange: string; -} - -export interface ICashFlowStatementDOO { - data: ICashFlowStatementData; - meta: ICashFlowStatementMeta; - query: ICashFlowStatementQuery; -} - -export interface ICashFlowStatementTable extends IFinancialTable { - meta: ICashFlowStatementMeta; - query: ICashFlowStatementQuery; -} - -export interface ICashFlowStatementService { - cashFlow( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise; -} - -// CASH FLOW SCHEMA TYPES. -// ----------------------------- -export interface ICashFlowSchemaCommonSection { - id: string; - label: string; - children: ICashFlowSchemaSection[]; - footerLabel?: string; -} - -export enum CASH_FLOW_ACCOUNT_RELATION { - MINES = 'mines', - PLUS = 'plus', -} - -export enum CASH_FLOW_SECTION_ID { - NET_INCOME = 'NET_INCOME', - OPERATING = 'OPERATING', - OPERATING_ACCOUNTS = 'OPERATING_ACCOUNTS', - INVESTMENT = 'INVESTMENT', - FINANCIAL = 'FINANCIAL', - - NET_OPERATING = 'NET_OPERATING', - NET_INVESTMENT = 'NET_INVESTMENT', - NET_FINANCIAL = 'NET_FINANCIAL', - - CASH_BEGINNING_PERIOD = 'CASH_BEGINNING_PERIOD', - CASH_END_PERIOD = 'CASH_END_PERIOD', - NET_CASH_INCREASE = 'NET_CASH_INCREASE', -} - -export interface ICashFlowSchemaAccountsSection - extends ICashFlowSchemaCommonSection { - sectionType: ICashFlowStatementSectionType.ACCOUNT; - accountsRelations: ICashFlowSchemaAccountRelation[]; -} - -export interface ICashFlowSchemaTotalSection - extends ICashFlowStatementCommonSection { - sectionType: ICashFlowStatementSectionType.TOTAL; - equation: string; -} - -export type ICashFlowSchemaSection = - | ICashFlowSchemaAccountsSection - | ICashFlowSchemaTotalSection - | ICashFlowSchemaCommonSection; - -export type ICashFlowStatementData = ICashFlowSchemaSection[]; - -export interface ICashFlowSchemaAccountRelation { - type: string; - direction: CASH_FLOW_ACCOUNT_RELATION.PLUS; -} - -export interface ICashFlowSchemaSectionAccounts - extends ICashFlowStatementCommonSection { - type: ICashFlowStatementSectionType.ACCOUNT; - accountsRelations: ICashFlowSchemaAccountRelation[]; -} - -export interface ICashFlowSchemaSectionTotal { - type: ICashFlowStatementSectionType.TOTAL; - totalEquation: string; -} - -export interface ICashFlowDatePeriod { - fromDate: ICashFlowDate; - toDate: ICashFlowDate; - total: ICashFlowStatementTotal; -} - -export interface ICashFlowDate { - formattedDate: string; - date: Date; -} - -export interface ICashFlowStatement { - /** - * Constructor method. - * @constructor - */ - constructor( - accounts: Account[], - ledger: Ledger, - cashLedger: Ledger, - netIncomeLedger: Ledger, - query: ICashFlowStatementQuery, - baseCurrency: string - ): void; - - reportData(): ICashFlowStatementData; -} - -export interface ICashFlowTable { - constructor(reportStatement: ICashFlowStatement): void; - tableRows(): ITableRow[]; -} - -export interface IDateRange { - fromDate: Date; - toDate: Date; -} - -export interface ICashflowTransactionSchema { - amount: number; - date: Date; - referenceNo?: string | null; - transactionNumber: string; - transactionType: string; - creditAccountId: number; - cashflowAccountId: number; - userId: number; - publishedAt?: Date | null; - branchId?: number; -} - -export interface ICashflowTransactionInput extends ICashflowTransactionSchema {} - -export interface ICategorizeCashflowTransactioDTO { - date: Date; - creditAccountId: number; - referenceNo: string; - transactionNumber: string; - transactionType: string; - exchangeRate: number; - description: string; - branchId: number; -} - -export interface IUncategorizedCashflowTransaction { - id?: number; - amount: number; - date: Date; - currencyCode: string; - accountId: number; - description: string; - referenceNo: string; - categorizeRefType: string; - categorizeRefId: number; - categorized: boolean; -} - -export interface CreateUncategorizedTransactionDTO { - date: Date | string; - accountId: number; - amount: number; - currencyCode: string; - payee?: string; - description?: string; - referenceNo?: string | null; - plaidTransactionId?: string | null; - pending?: boolean; - pendingPlaidTransactionId?: string | null; - batch?: string; -} - -export interface IUncategorizedTransactionCreatingEventPayload { - tenantId: number; - createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO; - trx: Knex.Transaction; -} - -export interface IUncategorizedTransactionCreatedEventPayload { - tenantId: number; - uncategorizedTransaction: any; - createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO; - trx: Knex.Transaction; -} - -export interface IPendingTransactionRemovingEventPayload { - tenantId: number; - uncategorizedTransactionId: number; - pendingTransaction: IUncategorizedCashflowTransaction; - trx?: Knex.Transaction; -} - -export interface IPendingTransactionRemovedEventPayload { - tenantId: number; - uncategorizedTransactionId: number; - pendingTransaction: IUncategorizedCashflowTransaction; - trx?: Knex.Transaction; -} diff --git a/temp/CashFlowStatement/CashflowExportInjectable.ts b/temp/CashFlowStatement/CashflowExportInjectable.ts deleted file mode 100644 index 75f4bcae2..000000000 --- a/temp/CashFlowStatement/CashflowExportInjectable.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { CashflowTableInjectable } from './CashflowTableInjectable'; -import { ICashFlowStatementQuery } from './Cashflow.types'; -import { TableSheet } from '../../common/TableSheet'; - -@Injectable() -export class CashflowExportInjectable { - constructor(private readonly cashflowSheetTable: CashflowTableInjectable) {} - - /** - * Retrieves the cashflow sheet in XLSX format. - * @param {ICashFlowStatementQuery} query - Cashflow statement query. - * @returns {Promise} - */ - public async xlsx(query: ICashFlowStatementQuery): Promise { - const table = await this.cashflowSheetTable.table(query); - - const tableSheet = new TableSheet(table.table); - const tableCsv = tableSheet.convertToXLSX(); - - return tableSheet.convertToBuffer(tableCsv, 'xlsx'); - } - - /** - * Retrieves the cashflow sheet in CSV format. - * @param {ICashFlowStatementQuery} query - Cashflow statement query. - * @returns {Promise} - */ - public async csv(query: ICashFlowStatementQuery): Promise { - const table = await this.cashflowSheetTable.table(query); - - const tableSheet = new TableSheet(table.table); - const tableCsv = tableSheet.convertToCSV(); - - return tableCsv; - } -} diff --git a/temp/CashFlowStatement/CashflowTableInjectable.ts b/temp/CashFlowStatement/CashflowTableInjectable.ts deleted file mode 100644 index 3440b5c3c..000000000 --- a/temp/CashFlowStatement/CashflowTableInjectable.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { CashFlowTable } from './CashFlowTable'; -import { CashFlowStatementService } from './CashFlowService'; -import { I18nService } from 'nestjs-i18n'; -import { - ICashFlowStatementQuery, - ICashFlowStatementTable, -} from './Cashflow.types'; - -@Injectable() -export class CashflowTableInjectable { - constructor( - private readonly cashflowSheet: CashFlowStatementService, - private readonly i18n: I18nService, - ) {} - - /** - * Retrieves the cash flow table. - * @returns {Promise} - */ - public async table( - query: ICashFlowStatementQuery, - ): Promise { - const cashflowDOO = await this.cashflowSheet.cashFlow(query); - const cashflowTable = new CashFlowTable(cashflowDOO, this.i18n); - - return { - table: { - columns: cashflowTable.tableColumns(), - rows: cashflowTable.tableRows(), - }, - query: cashflowDOO.query, - meta: cashflowDOO.meta, - }; - } -} diff --git a/temp/CashFlowStatement/CashflowTablePdfInjectable.ts b/temp/CashFlowStatement/CashflowTablePdfInjectable.ts deleted file mode 100644 index 6033a9df2..000000000 --- a/temp/CashFlowStatement/CashflowTablePdfInjectable.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TableSheetPdf } from '../../common/TableSheetPdf'; -import { ICashFlowStatementQuery } from './Cashflow.types'; -import { CashflowTableInjectable } from './CashflowTableInjectable'; -import { HtmlTableCustomCss } from './constants'; - -export class CashflowTablePdfInjectable { - constructor( - private readonly cashflowTable: CashflowTableInjectable, - private readonly tableSheetPdf: TableSheetPdf, - ) {} - - /** - * Converts the given cashflow sheet table to pdf. - * @param {number} tenantId - Tenant ID. - * @param {IBalanceSheetQuery} query - Balance sheet query. - * @returns {Promise} - */ - public async pdf(query: ICashFlowStatementQuery): Promise { - const table = await this.cashflowTable.table(query); - - return this.tableSheetPdf.convertToPdf( - table.table, - table.meta.sheetName, - table.meta.formattedDateRange, - HtmlTableCustomCss, - ); - } -} diff --git a/temp/CashFlowStatement/constants.ts b/temp/CashFlowStatement/constants.ts deleted file mode 100644 index 6aaa74486..000000000 --- a/temp/CashFlowStatement/constants.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ICashFlowStatementQuery } from "./Cashflow.types"; - -export const DISPLAY_COLUMNS_BY = { - DATE_PERIODS: 'date_periods', - TOTAL: 'total', -}; - -export const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; -export const HtmlTableCustomCss = ` -table tr.row-type--accounts td { - border-top: 1px solid #bbb; -} -table tr.row-id--cash-end-period td { - border-bottom: 3px double #333; -} -table tr.row-type--total { - font-weight: 600; -} -table tr.row-type--total td { - color: #000; -} -table tr.row-type--total:not(:first-child) td { - border-top: 1px solid #bbb; -} -table .column--name, -table .cell--name { - width: 400px; -} -table .column--total, -table .cell--total, -table [class*="column--date-range"], -table [class*="cell--date-range"] { - text-align: right; -} -`; - -export const getDefaultCashflowQuery = (): ICashFlowStatementQuery => ({ - displayColumnsType: 'total', - displayColumnsBy: 'day', - fromDate: moment().startOf('year').format('YYYY-MM-DD'), - toDate: moment().format('YYYY-MM-DD'), - numberFormat: { - precision: 2, - divideOn1000: false, - showZero: false, - formatMoney: 'total', - negativeFormat: 'mines', - }, - noneZero: false, - noneTransactions: false, - basis: 'cash', -}); diff --git a/temp/CashFlowStatement/schema.ts b/temp/CashFlowStatement/schema.ts deleted file mode 100644 index 67be33743..000000000 --- a/temp/CashFlowStatement/schema.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { ACCOUNT_TYPE } from '@/constants/accounts'; -import { - ICashFlowSchemaSection, - CASH_FLOW_SECTION_ID, - ICashFlowStatementSectionType, -} from './Cashflow.types'; - -export const CASH_FLOW_SCHEMA = [ - { - id: CASH_FLOW_SECTION_ID.OPERATING, - label: 'OPERATING ACTIVITIES', - sectionType: ICashFlowStatementSectionType.AGGREGATE, - children: [ - { - id: CASH_FLOW_SECTION_ID.NET_INCOME, - label: 'Net income', - sectionType: ICashFlowStatementSectionType.NET_INCOME, - }, - { - id: CASH_FLOW_SECTION_ID.OPERATING_ACCOUNTS, - label: 'Adjustments net income by operating activities.', - sectionType: ICashFlowStatementSectionType.ACCOUNTS, - accountsRelations: [ - { type: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE, direction: 'mines' }, - { type: ACCOUNT_TYPE.INVENTORY, direction: 'mines' }, - { type: ACCOUNT_TYPE.NON_CURRENT_ASSET, direction: 'mines' }, - { type: ACCOUNT_TYPE.ACCOUNTS_PAYABLE, direction: 'plus' }, - { type: ACCOUNT_TYPE.CREDIT_CARD, direction: 'plus' }, - { type: ACCOUNT_TYPE.TAX_PAYABLE, direction: 'plus' }, - { type: ACCOUNT_TYPE.OTHER_CURRENT_ASSET, direction: 'mines' }, - { type: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY, direction: 'plus' }, - { type: ACCOUNT_TYPE.NON_CURRENT_LIABILITY, direction: 'plus' }, - ], - showAlways: true, - }, - ], - footerLabel: 'Net cash provided by operating activities', - }, - { - id: CASH_FLOW_SECTION_ID.INVESTMENT, - sectionType: ICashFlowStatementSectionType.ACCOUNTS, - label: 'INVESTMENT ACTIVITIES', - accountsRelations: [{ type: ACCOUNT_TYPE.FIXED_ASSET, direction: 'mines' }], - footerLabel: 'Net cash provided by investing activities', - }, - { - id: CASH_FLOW_SECTION_ID.FINANCIAL, - label: 'FINANCIAL ACTIVITIES', - sectionType: ICashFlowStatementSectionType.ACCOUNTS, - accountsRelations: [ - { type: ACCOUNT_TYPE.LOGN_TERM_LIABILITY, direction: 'plus' }, - { type: ACCOUNT_TYPE.EQUITY, direction: 'plus' }, - ], - footerLabel: 'Net cash provided by financing activities', - }, - { - id: CASH_FLOW_SECTION_ID.CASH_BEGINNING_PERIOD, - sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING, - label: 'Cash at beginning of period', - accountsRelations: [ - { type: ACCOUNT_TYPE.CASH, direction: 'plus' }, - { type: ACCOUNT_TYPE.BANK, direction: 'plus' }, - ], - }, - { - id: CASH_FLOW_SECTION_ID.NET_CASH_INCREASE, - sectionType: ICashFlowStatementSectionType.TOTAL, - equation: 'OPERATING + INVESTMENT + FINANCIAL', - label: 'NET CASH INCREASE FOR PERIOD', - }, - { - id: CASH_FLOW_SECTION_ID.CASH_END_PERIOD, - label: 'CASH AT END OF PERIOD', - sectionType: ICashFlowStatementSectionType.TOTAL, - equation: 'NET_CASH_INCREASE + CASH_BEGINNING_PERIOD', - }, -] as ICashFlowSchemaSection[];