diff --git a/packages/server-nest/package.json b/packages/server-nest/package.json index c88928ff8..b1e54a507 100644 --- a/packages/server-nest/package.json +++ b/packages/server-nest/package.json @@ -49,6 +49,7 @@ "cache-manager-redis-store": "^3.0.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "deepdash": "^5.3.9", "express-validator": "^7.2.0", "form-data": "^4.0.0", "fp-ts": "^2.16.9", @@ -75,6 +76,7 @@ "ramda": "^0.30.1", "redis": "^4.7.0", "reflect-metadata": "^0.2.0", + "remeda": "^2.19.2", "rxjs": "^7.8.1", "serialize-interceptor": "^1.1.7", "strategy": "^1.1.1", @@ -82,7 +84,8 @@ "uuid": "^10.0.0", "xlsx": "^0.18.5", "yup": "^0.28.1", - "zod": "^3.23.8" + "zod": "^3.23.8", + "mathjs": "^9.4.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -93,12 +96,14 @@ "@types/node": "^20.3.1", "@types/supertest": "^6.0.0", "@types/yup": "^0.29.13", + "@types/mathjs": "^6.0.12", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", + "mustache": "^3.0.3", "prettier": "^3.0.0", "source-map-support": "^0.5.21", "supertest": "^7.0.0", @@ -106,8 +111,7 @@ "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3", - "mustache": "^3.0.3" + "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ diff --git a/packages/server-nest/src/common/events/events.ts b/packages/server-nest/src/common/events/events.ts index 0fdf57638..c7db1e744 100644 --- a/packages/server-nest/src/common/events/events.ts +++ b/packages/server-nest/src/common/events/events.ts @@ -180,6 +180,7 @@ export const events = { * Sales estimates service. */ saleEstimate: { + onViewed: 'onSaleEstimateViewed', onPdfViewed: 'onSaleEstimatePdfViewed', onCreating: 'onSaleEstimateCreating', @@ -212,9 +213,7 @@ export const events = { onPreMailSend: 'onSaleEstimatePreMailSend', onMailSend: 'onSaleEstimateMailSend', - onMailSent: 'onSaleEstimateMailSend', - - onViewed: 'onSaleEstimateViewed', + onMailSent: 'onSaleEstimateMailSent', }, /** @@ -753,4 +752,24 @@ export const events = { onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted', onAccountUpdated: 'onStripeAccountUpdated', }, + + // Reports + reports: { + onBalanceSheetViewed: 'onBalanceSheetViewed', + onTrialBalanceSheetView: 'onTrialBalanceSheetViewed', + onProfitLossSheetViewed: 'onProfitLossSheetViewed', + onCashflowStatementViewed: 'onCashflowStatementViewed', + onGeneralLedgerViewed: 'onGeneralLedgerViewed', + onJournalViewed: 'onJounralViewed', + onReceivableAgingViewed: 'onReceivableAgingViewed', + onPayableAgingViewed: 'onPayableAgingViewed', + onCustomerBalanceSummaryViewed: 'onInventoryValuationViewed', + onVendorBalanceSummaryViewed: 'onVendorBalanceSummaryViewed', + onInventoryValuationViewed: 'onCustomerBalanceSummaryViewed', + onCustomerTransactionsViewed: 'onCustomerTransactionsViewed', + onVendorTransactionsViewed: 'onVendorTransactionsViewed', + onSalesByItemViewed: 'onSalesByItemViewed', + onPurchasesByItemViewed: 'onPurchasesByItemViewed', + }, + }; diff --git a/packages/server-nest/src/common/types/Constructor.ts b/packages/server-nest/src/common/types/Constructor.ts index c9a49e5d2..373c3f94e 100644 --- a/packages/server-nest/src/common/types/Constructor.ts +++ b/packages/server-nest/src/common/types/Constructor.ts @@ -1,2 +1,3 @@ export type Constructor = new (...args: any[]) => {}; +export type GConstructor = new (...args: any[]) => T; \ No newline at end of file diff --git a/packages/server-nest/src/modules/Accounts/models/Account.model.ts b/packages/server-nest/src/modules/Accounts/models/Account.model.ts index f4e194ba7..5f6e6b466 100644 --- a/packages/server-nest/src/modules/Accounts/models/Account.model.ts +++ b/packages/server-nest/src/modules/Accounts/models/Account.model.ts @@ -14,6 +14,7 @@ import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils'; import { Model } from 'objection'; import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; +import { flatToNestedArray } from '@/utils/flat-to-nested-array'; // import AccountSettings from './Account.Settings'; // import { DEFAULT_VIEWS } from '@/modules/Accounts/constants'; // import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder'; @@ -406,10 +407,10 @@ export class Account extends TenantBaseModel { * @param {Object} options */ static toNestedArray(accounts, options = { children: 'children' }) { - // return flatToNestedArray(accounts, { - // id: 'id', - // parentId: 'parentAccountId', - // }); + return flatToNestedArray(accounts, { + id: 'id', + parentId: 'parentAccountId', + }); } /** diff --git a/packages/server-nest/src/modules/BankingAccounts/BankAccounts.controller.ts b/packages/server-nest/src/modules/BankingAccounts/BankAccounts.controller.ts index add105797..8184a65f2 100644 --- a/packages/server-nest/src/modules/BankingAccounts/BankAccounts.controller.ts +++ b/packages/server-nest/src/modules/BankingAccounts/BankAccounts.controller.ts @@ -1,6 +1,6 @@ import { Controller, Param, Post } from '@nestjs/common'; import { BankAccountsApplication } from './BankAccountsApplication.service'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller('banking/accounts') @ApiTags('banking-accounts') @@ -11,6 +11,14 @@ export class BankAccountsController { @ApiOperation({ summary: 'Disconnect the bank connection of the given bank account.', }) + @ApiResponse({ + status: 200, + description: 'Bank account disconnected successfully.', + }) + @ApiResponse({ + status: 404, + description: 'Bank account not found.', + }) async disconnectBankAccount(@Param('id') bankAccountId: number) { return this.bankAccountsApplication.disconnectBankAccount(bankAccountId); } @@ -19,6 +27,14 @@ export class BankAccountsController { @ApiOperation({ summary: 'Refresh the bank account transactions.', }) + @ApiResponse({ + status: 200, + description: 'Bank account transactions refreshed successfully.', + }) + @ApiResponse({ + status: 404, + description: 'Bank account not found.', + }) async refreshBankAccount(@Param('id') bankAccountId: number) { return this.bankAccountsApplication.refreshBankAccount(bankAccountId); } @@ -27,6 +43,14 @@ export class BankAccountsController { @ApiOperation({ summary: 'Pause transactions syncing of the given bank account.', }) + @ApiResponse({ + status: 200, + description: 'Bank account transactions paused successfully.', + }) + @ApiResponse({ + status: 404, + description: 'Bank account not found.', + }) async pauseBankAccount(@Param('id') bankAccountId: number) { return this.bankAccountsApplication.pauseBankAccount(bankAccountId); } @@ -35,6 +59,14 @@ export class BankAccountsController { @ApiOperation({ summary: 'Resume transactions syncing of the given bank account.', }) + @ApiResponse({ + status: 200, + description: 'Bank account transactions resumed successfully.', + }) + @ApiResponse({ + status: 404, + description: 'Bank account not found.', + }) async resumeBankAccount(@Param('id') bankAccountId: number) { return this.bankAccountsApplication.resumeBankAccount(bankAccountId); } diff --git a/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts b/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts index 117caf95b..b035da052 100644 --- a/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts +++ b/packages/server-nest/src/modules/FinancialStatements/FinancialStatements.module.ts @@ -1,9 +1,16 @@ import { Module } from '@nestjs/common'; -import { TableSheetPdf } from './TableSheetPdf'; import { PurchasesByItemsModule } from './modules/PurchasesByItems/PurchasesByItems.module'; - +import { CustomerBalanceSummaryModule } from './modules/CustomerBalanceSummary/CustomerBalanceSummary.module'; +import { SalesByItemsModule } from './modules/SalesByItems/SalesByItems.module'; +import { GeneralLedgerModule } from './modules/GeneralLedger/GeneralLedger.module'; +// @Module({ - providers: [TableSheetPdf], - imports: [PurchasesByItemsModule], + providers: [], + imports: [ + PurchasesByItemsModule, + CustomerBalanceSummaryModule, + SalesByItemsModule, + GeneralLedgerModule + ], }) 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 c848e0f7a..55cc9b597 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialDatePeriods.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialDatePeriods.ts @@ -3,14 +3,16 @@ import { memoize } from 'lodash'; import { IAccountTransactionsGroupBy, IFinancialDatePeriodsUnit, - IFinancialSheetTotalPeriod, IFormatNumberSettings, } from '../types/Report.types'; import { dateRangeFromToCollection } from '@/utils/date-range-collection'; import { FinancialDateRanges } from './FinancialDateRanges'; -import { Constructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialDatePeriods = (Base: T) => +export const FinancialDatePeriods = >( + Base: T, +) => class extends R.compose(FinancialDateRanges)(Base) { /** * Retrieves the date ranges from the given from date to the given to date. @@ -19,9 +21,9 @@ export const FinancialDatePeriods = (Base: T) => * @param {string} unit */ public getDateRanges = memoize( - (fromDate: Date, toDate: Date, unit: string) => { + (fromDate: Date, toDate: Date, unit: moment.unitOfTime.StartOf) => { return dateRangeFromToCollection(fromDate, toDate, unit); - } + }, ); /** @@ -35,7 +37,7 @@ export const FinancialDatePeriods = (Base: T) => total: number, fromDate: Date, toDate: Date, - overrideSettings?: IFormatNumberSettings + overrideSettings?: IFormatNumberSettings, ): IFinancialSheetTotalPeriod => { return { fromDate: this.getDateMeta(fromDate), @@ -55,7 +57,7 @@ export const FinancialDatePeriods = (Base: T) => total: number, fromDate: Date, toDate: Date, - overrideSettings: IFormatNumberSettings = {} + overrideSettings: IFormatNumberSettings = {}, ) => { return this.getDatePeriodMeta(total, fromDate, toDate, { money: true, @@ -79,8 +81,8 @@ export const FinancialDatePeriods = (Base: T) => node: any, fromDate: Date, toDate: Date, - index: number - ) => any + index: number, + ) => any, ) => { const curriedCallback = R.curry(callback)(node); // Retrieves memorized date ranges. @@ -88,7 +90,7 @@ export const FinancialDatePeriods = (Base: T) => return dateRanges.map((dateRange, index) => { return curriedCallback(dateRange.fromDate, dateRange.toDate, index); }); - } + }, ); /** * Retrieve the accounts transactions group type from display columns by. @@ -96,7 +98,7 @@ export const FinancialDatePeriods = (Base: T) => * @returns {IAccountTransactionsGroupBy} */ public getGroupByFromDisplayColumnsBy = ( - columnsBy: IFinancialDatePeriodsUnit + columnsBy: IFinancialDatePeriodsUnit, ): IAccountTransactionsGroupBy => { const paris = { week: IAccountTransactionsGroupBy.Day, diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts index 438337f17..cf568b62b 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialDateRanges.ts @@ -1,14 +1,17 @@ import moment from 'moment'; import { IDateRange, IFinancialDatePeriodsUnit } from '../types/Report.types'; -import { Constructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialDateRanges = (Base: T) => +export const FinancialDateRanges = >( + Base: T, +) => class extends Base { /** * Retrieve previous period (PP) date of the given date. - * @param {Date} fromDate - - * @param {Date} toDate - - * @param {IFinancialDatePeriodsUnit} unit - + * @param {Date} date - Date. + * @param {number} value - Value. + * @param {IFinancialDatePeriodsUnit} unit - Unit of time. * @returns {Date} */ public getPreviousPeriodDate = ( @@ -20,10 +23,10 @@ export const FinancialDateRanges = (Base: T) => }; /** - * Retrieves the different + * Retrieves the different between two dates. * @param {Date} fromDate * @param {Date} toDate - * @returns + * @returns {number} */ public getPreviousPeriodDiff = (fromDate: Date, toDate: Date) => { return moment(toDate).diff(fromDate, 'days') + 1; @@ -31,8 +34,11 @@ export const FinancialDateRanges = (Base: T) => /** * Retrieves the periods period dates. - * @param {Date} fromDate - - * @param {Date} toDate - + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @param {IFinancialDatePeriodsUnit} unit - Unit of time. + * @param {number} amount - Amount of time. + * @returns {IDateRange} */ public getPreviousPeriodDateRange = ( fromDate: Date, @@ -65,9 +71,9 @@ export const FinancialDateRanges = (Base: T) => /** * Retrieves the previous period (PP) date range of date periods columns. - * @param {Date} fromDate - - * @param {Date} toDate - - * @param {IFinancialDatePeriodsUnit} + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @param {IFinancialDatePeriodsUnit} unit - Unit of time. * @returns {IDateRange} */ public getPPDatePeriodDateRange = ( diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts index 253f488c2..d8914d610 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialEvaluateEquation.ts @@ -1,12 +1,16 @@ import * as mathjs from 'mathjs'; import * as R from 'ramda'; -import { compose } from 'lodash/fp'; import { omit, get, mapValues } from 'lodash'; import { FinancialSheetStructure } from './FinancialSheetStructure'; -import { Constructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialEvaluateEquation = (Base: T) => - class extends compose(FinancialSheetStructure)(Base) { +export const FinancialEvaluateEquation = < + T extends GConstructor, +>( + Base: T +) => + class FinancialEvaluateEquation extends R.compose(FinancialSheetStructure)(Base) { /** * Evauluate equaation string with the given scope table. * @param {string} equation - @@ -34,7 +38,6 @@ export const FinancialEvaluateEquation = (Base: T) => } return acc; }, - {} ); }; diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts index 2202a893e..4fce2671b 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialHorizTotals.ts @@ -1,9 +1,12 @@ import * as R from 'ramda'; import { get, isEmpty } from 'lodash'; -import { Constructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialHorizTotals = (Base: T) => - class extends Base { +export const FinancialHorizTotals = >( + Base: T, +) => + class FinancialHorizTotals extends Base { /** * */ diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts index ccad76ccf..0876ac10f 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousPeriod.ts @@ -4,9 +4,14 @@ import { IFinancialNodeWithPreviousPeriod, } from '../types/Report.types'; import * as R from 'ramda'; +import { Constructor, GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; +import { FinancialDatePeriods } from './FinancialDatePeriods'; -export const FinancialPreviousPeriod = (Base) => - class extends Base { +export const FinancialPreviousPeriod = >( + Base: T, +) => + class extends R.compose(FinancialDatePeriods)(Base) { // --------------------------- // # Common Node. // --------------------------- @@ -16,16 +21,16 @@ export const FinancialPreviousPeriod = (Base) => * @returns {IFinancialNodeWithPreviousPeriod} */ public assocPreviousPeriodPercentageNode = ( - accountNode: IProfitLossSheetAccountNode + accountNode: IProfitLossSheetAccountNode, ): IFinancialNodeWithPreviousPeriod => { const percentage = this.getPercentageBasis( accountNode.previousPeriod.amount, - accountNode.previousPeriodChange.amount + accountNode.previousPeriodChange.amount, ); return R.assoc( 'previousPeriodPercentage', this.getPercentageAmountMeta(percentage), - accountNode + accountNode, ); }; @@ -35,16 +40,16 @@ export const FinancialPreviousPeriod = (Base) => * @returns {IFinancialNodeWithPreviousPeriod} */ public assocPreviousPeriodChangeNode = ( - accountNode: IProfitLossSheetAccountNode + accountNode: IProfitLossSheetAccountNode, ): IFinancialNodeWithPreviousPeriod => { const change = this.getAmountChange( accountNode.total.amount, - accountNode.previousPeriod.amount + accountNode.previousPeriod.amount, ); return R.assoc( 'previousPeriodChange', this.getAmountMeta(change), - accountNode + accountNode, ); }; @@ -57,16 +62,16 @@ export const FinancialPreviousPeriod = (Base) => * @returns {IFinancialNodeWithPreviousPeriod} */ public assocPreviousPeriodTotalPercentageNode = ( - accountNode: IProfitLossSheetAccountNode + accountNode: IProfitLossSheetAccountNode, ): IFinancialNodeWithPreviousPeriod => { const percentage = this.getPercentageBasis( accountNode.previousPeriod.amount, - accountNode.previousPeriodChange.amount + accountNode.previousPeriodChange.amount, ); return R.assoc( 'previousPeriodPercentage', this.getPercentageTotalAmountMeta(percentage), - accountNode + accountNode, ); }; @@ -76,16 +81,16 @@ export const FinancialPreviousPeriod = (Base) => * @returns {IFinancialNodeWithPreviousPeriod} */ public assocPreviousPeriodTotalChangeNode = ( - accountNode: any + accountNode: any, ): IFinancialNodeWithPreviousPeriod => { const change = this.getAmountChange( accountNode.total.amount, - accountNode.previousPeriod.amount + accountNode.previousPeriod.amount, ); return R.assoc( 'previousPeriodChange', this.getTotalAmountMeta(change), - accountNode + accountNode, ); }; @@ -97,19 +102,19 @@ export const FinancialPreviousPeriod = (Base) => public assocPreviousPeriodHorizNodeFromToDates = R.curry( ( periodUnit: IFinancialDatePeriodsUnit, - horizNode: any + horizNode: any, ): IFinancialNodeWithPreviousPeriod => { const { fromDate: PPFromDate, toDate: PPToDate } = this.getPreviousPeriodDateRange( horizNode.fromDate.date, horizNode.toDate.date, - periodUnit + periodUnit, ); return R.compose( R.assoc('previousPeriodToDate', this.getDateMeta(PPToDate)), - R.assoc('previousPeriodFromDate', this.getDateMeta(PPFromDate)) + R.assoc('previousPeriodFromDate', this.getDateMeta(PPFromDate)), )(horizNode); - } + }, ); /** @@ -121,7 +126,7 @@ export const FinancialPreviousPeriod = (Base) => public getPPHorizNodesTotalSumation = (index: number, node): number => { return sumBy( node.children, - `horizontalTotals[${index}].previousPeriod.amount` + `horizontalTotals[${index}].previousPeriod.amount`, ); }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousYear.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousYear.ts index 4453112da..e79d9e33c 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousYear.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialPreviousYear.ts @@ -1,50 +1,54 @@ import * as R from 'ramda'; -import { sumBy } from 'lodash' +import { sumBy } from 'lodash'; import { IFinancialCommonHorizDatePeriodNode, IFinancialCommonNode, IFinancialNodeWithPreviousYear, } from '../types/Report.types'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; +import { FinancialDatePeriods } from './FinancialDatePeriods'; -export const FinancialPreviousYear = (Base) => - class extends Base { +export const FinancialPreviousYear = >( + Base: T, +) => + class extends R.compose(FinancialDatePeriods)(Base) { // --------------------------- // # Common Node // --------------------------- /** * Assoc previous year change attribute to account node. - * @param {IProfitLossSheetAccountNode} accountNode - * @returns {IProfitLossSheetAccountNode} + * @param {IFinancialCommonNode & IFinancialNodeWithPreviousYear} accountNode + * @returns {IFinancialNodeWithPreviousYear} */ public assocPreviousYearChangetNode = ( - node: IFinancialCommonNode & IFinancialNodeWithPreviousYear + node: IFinancialCommonNode & IFinancialNodeWithPreviousYear, ): IFinancialNodeWithPreviousYear => { const change = this.getAmountChange( node.total.amount, - node.previousYear.amount + node.previousYear.amount, ); return R.assoc('previousYearChange', this.getAmountMeta(change), node); }; /** * Assoc previous year percentage attribute to account node. - * * % increase = Increase ÷ Original Number × 100. * * @param {IProfitLossSheetAccountNode} accountNode * @returns {IProfitLossSheetAccountNode} */ public assocPreviousYearPercentageNode = ( - node: IFinancialCommonNode & IFinancialNodeWithPreviousYear + node: IFinancialCommonNode & IFinancialNodeWithPreviousYear, ): IFinancialNodeWithPreviousYear => { const percentage = this.getPercentageBasis( node.previousYear.amount, - node.previousYearChange.amount + node.previousYearChange.amount, ); return R.assoc( 'previousYearPercentage', this.getPercentageAmountMeta(percentage), - node + node, ); }; @@ -54,16 +58,16 @@ export const FinancialPreviousYear = (Base) => * @returns {IProfitLossSheetAccountNode} */ public assocPreviousYearTotalChangeNode = ( - node: IFinancialCommonNode & IFinancialNodeWithPreviousYear + node: IFinancialCommonNode & IFinancialNodeWithPreviousYear, ): IFinancialNodeWithPreviousYear => { const change = this.getAmountChange( node.total.amount, - node.previousYear.amount + node.previousYear.amount, ); return R.assoc( 'previousYearChange', this.getTotalAmountMeta(change), - node + node, ); }; @@ -73,16 +77,16 @@ export const FinancialPreviousYear = (Base) => * @returns {IProfitLossSheetAccountNode} */ public assocPreviousYearTotalPercentageNode = ( - node: IFinancialCommonNode & IFinancialNodeWithPreviousYear + node: IFinancialCommonNode & IFinancialNodeWithPreviousYear, ): IFinancialNodeWithPreviousYear => { const percentage = this.getPercentageBasis( node.previousYear.amount, - node.previousYearChange.amount + node.previousYearChange.amount, ); return R.assoc( 'previousYearPercentage', this.getPercentageTotalAmountMeta(percentage), - node + node, ); }; @@ -92,27 +96,27 @@ export const FinancialPreviousYear = (Base) => * @returns */ public assocPreviousYearHorizNodeFromToDates = ( - horizNode: IFinancialCommonHorizDatePeriodNode + horizNode: IFinancialCommonHorizDatePeriodNode, ) => { const PYFromDate = this.getPreviousYearDate(horizNode.fromDate.date); const PYToDate = this.getPreviousYearDate(horizNode.toDate.date); return R.compose( R.assoc('previousYearToDate', this.getDateMeta(PYToDate)), - R.assoc('previousYearFromDate', this.getDateMeta(PYFromDate)) + R.assoc('previousYearFromDate', this.getDateMeta(PYFromDate)), )(horizNode); }; /** - * Retrieves PP total sumation of the given horiz index node. - * @param {number} index - * @param {} node + * Retrieves PP total sumation of the given horiz index node. + * @param {number} index + * @param {} node * @returns {number} */ public getPYHorizNodesTotalSumation = (index: number, node): number => { return sumBy( node.children, - `horizontalTotals[${index}].previousYear.amount` - ) - } + `horizontalTotals[${index}].previousYear.amount`, + ); + }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts index f134b1083..732819bc2 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSchema.ts @@ -1,9 +1,12 @@ import * as R from 'ramda'; import { FinancialSheetStructure } from './FinancialSheetStructure'; -import { Constructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialSchema = (Base: T) => - class extends R.compose(FinancialSheetStructure)(Base) { +export const FinancialSchema = >( + Base: T +) => + class FinancialSchema extends R.compose(FinancialSheetStructure)(Base) { /** * * @returns @@ -17,7 +20,7 @@ export const FinancialSchema = (Base: T) => * @param {string|number} id * @returns */ - publicgetSchemaNodeById = (id: string | number) => { + public getSchemaNodeById = (id: string | number) => { const schema = this.getSchema(); return this.findNodeDeep(schema, (node) => node.id === id); diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheet.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheet.ts index 22f1e98b9..1da16b0c9 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheet.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheet.ts @@ -1,20 +1,21 @@ import moment from 'moment'; import { - ICashFlowStatementTotal, IFormatNumberSettings, INumberFormatQuery, + } from '../types/Report.types'; import { formatNumber } from '@/utils/format-number'; +import { IFinancialTableTotal } from '../types/Table.types'; -export default class FinancialSheet { - readonly numberFormat: INumberFormatQuery = { +export class FinancialSheet { + public numberFormat: INumberFormatQuery = { precision: 2, divideOn1000: false, showZero: false, formatMoney: 'total', negativeFormat: 'mines', }; - readonly baseCurrency: string; + public baseCurrency: string; /** * Transformes the number format query to settings @@ -109,7 +110,7 @@ export default class FinancialSheet { protected getAmountMeta( amount: number, overrideSettings?: IFormatNumberSettings - ): ICashFlowStatementTotal { + ): IFinancialTableTotal { return { amount, formattedAmount: this.formatNumber(amount, overrideSettings), @@ -125,7 +126,7 @@ export default class FinancialSheet { protected getTotalAmountMeta( amount: number, title?: string - ): ICashFlowStatementTotal { + ): IFinancialTableTotal { return { ...(title ? { title } : {}), amount, diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetCommon.module.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetCommon.module.ts new file mode 100644 index 000000000..fbca79cdd --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetCommon.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { FinancialSheetMeta } from './FinancialSheetMeta'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { TableSheetPdf } from './TableSheetPdf'; +import { TemplateInjectableModule } from '@/modules/TemplateInjectable/TemplateInjectable.module'; +import { ChromiumlyTenancyModule } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.module'; + +@Module({ + imports: [TemplateInjectableModule, ChromiumlyTenancyModule], + providers: [ + FinancialSheetMeta, + TenancyContext, + TableSheetPdf, + ], + exports: [FinancialSheetMeta, TableSheetPdf], +}) +export class FinancialSheetCommonModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetMeta.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetMeta.ts index f90dc7568..f4dac4f7c 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetMeta.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetMeta.ts @@ -4,10 +4,7 @@ import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; @Injectable() export class FinancialSheetMeta { - constructor( - private readonly inventoryService: InventoryService, - private readonly tenancyContext: TenancyContext, - ) {} + constructor(private readonly tenancyContext: TenancyContext) {} /** * Retrieves the common meta data of the financial sheet. @@ -20,8 +17,10 @@ export class FinancialSheetMeta { const baseCurrency = tenantMetadata.baseCurrency; const dateFormat = tenantMetadata.dateFormat; - const isCostComputeRunning = - this.inventoryService.isItemsCostComputeRunning(tenantId); + // const isCostComputeRunning = + // this.inventoryService.isItemsCostComputeRunning(); + + const isCostComputeRunning = false; return { organizationName, diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts index fdeb7852b..14ceee3d6 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialSheetStructure.ts @@ -4,16 +4,20 @@ import { mapValuesDeepReverse, mapValuesDeep, mapValues, - condense, filterDeep, reduceDeep, findValueDeep, filterNodesDeep, -} from 'utils/deepdash'; -import { Constructor } from '@/common/types/Constructor'; +} from '@/utils/deepdash'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialSheetStructure = (Base: T) => - class extends Base { +export const FinancialSheetStructure = < + T extends GConstructor, +>( + Base: T +) => + class FinancialSheetStructure extends Base { /** * * @param nodes diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialTable.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialTable.ts index 72f029183..b395a5460 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialTable.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialTable.ts @@ -2,10 +2,21 @@ import * as R from 'ramda'; import { isEmpty, clone, cloneDeep, omit } from 'lodash'; import { increment } from '@/utils/increment'; import { ITableRow, ITableColumn } from '../types/Table.types'; -import { IROW_TYPE } from './BalanceSheet/constants'; +import { GConstructor } from '@/common/types/Constructor'; +import { FinancialSheetStructure } from './FinancialSheetStructure'; +import { I18nService } from 'nestjs-i18n'; +import { FinancialSheet } from './FinancialSheet'; + +enum IROW_TYPE { + TOTAL = 'TOTAL', +} + +export const FinancialTable = >( + Base: T +) => + class extends R.pipe(FinancialSheetStructure)(Base) { + public readonly i18n: I18nService; -export const FinancialTable = (Base) => - class extends Base { /** * Table columns cell indexing. * @param {ITableColumn[]} columns @@ -23,13 +34,15 @@ export const FinancialTable = (Base) => }); }; - addTotalRow = (node: ITableRow) => { + public addTotalRow = (node: ITableRow) => { const clonedNode = clone(node); if (clonedNode.children) { const cells = cloneDeep(node.cells); - cells[0].value = this.i18n.__('financial_sheet.total_row', { - value: cells[0].value, + cells[0].value = this.i18n.t('financial_sheet.total_row', { + args: { + value: cells[0].value, + }, }); clonedNode.children.push({ diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousPeriod.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousPeriod.ts index d2a6200e4..c5cafbf29 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousPeriod.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousPeriod.ts @@ -1,8 +1,18 @@ import moment from 'moment'; -import { ITableColumn, IDateRange, ITableColumnAccessor } from '../types/Table.types'; +import { ITableColumn, ITableColumnAccessor } from '../types/Table.types'; +import { IDateRange } from '../types/Report.types'; +import { Constructor, GConstructor } from '@/common/types/Constructor'; +import { I18nService } from 'nestjs-i18n'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialTablePreviousPeriod = (Base) => +export const FinancialTablePreviousPeriod = < + T extends GConstructor, +>( + Base: T, +) => class extends Base { + public readonly i18n: I18nService; + getTotalPreviousPeriod = () => { return this.query.PPToDate; }; @@ -24,8 +34,8 @@ export const FinancialTablePreviousPeriod = (Base) => return { key: 'previous_period', - label: this.i18n.__(`financial_sheet.previoud_period_date`, { - date: PPFormatted, + label: this.i18n.t(`financial_sheet.previoud_period_date`, { + args: { date: PPFormatted, } }), }; }; @@ -37,7 +47,7 @@ export const FinancialTablePreviousPeriod = (Base) => public getPreviousPeriodChangeColumn = (): ITableColumn => { return { key: 'previous_period_change', - label: this.i18n.__('fianncial_sheet.previous_period_change'), + label: this.i18n.t('fianncial_sheet.previous_period_change'), }; }; @@ -48,7 +58,7 @@ export const FinancialTablePreviousPeriod = (Base) => public getPreviousPeriodPercentageColumn = (): ITableColumn => { return { key: 'previous_period_percentage', - label: this.i18n.__('financial_sheet.previous_period_percentage'), + label: this.i18n.t('financial_sheet.previous_period_percentage'), }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousYear.ts b/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousYear.ts index 59766ac4b..6787b7357 100644 --- a/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousYear.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/FinancialTablePreviousYear.ts @@ -1,18 +1,32 @@ import moment from 'moment'; import { ITableColumn, ITableColumnAccessor } from '../types/Table.types'; import { IDateRange } from '../types/Report.types'; +import { GConstructor } from '@/common/types/Constructor'; +import { I18nService } from 'nestjs-i18n'; +import { FinancialSheet } from './FinancialSheet'; -export const FinancialTablePreviousYear = (Base) => +export const FinancialTablePreviousYear = < + T extends GConstructor, +>( + Base: T, +) => class extends Base { - getTotalPreviousYear = () => { + public readonly i18n: I18nService; + + /** + * Retrieves the total previous year date. + * @returns {Date} + */ + public getTotalPreviousYear = () => { return this.query.PYToDate; }; + // ------------------------------------ // # Columns. // ------------------------------------ /** * Retrive previous year total column. - * @param {DateRange} previousYear - + * @param {DateRange} previousYear - * @returns {ITableColumn} */ public getPreviousYearTotalColumn = ( @@ -23,8 +37,8 @@ export const FinancialTablePreviousYear = (Base) => return { key: 'previous_year', - label: this.i18n.__('financial_sheet.previous_year_date', { - date: PYFormatted, + label: this.i18n.t('financial_sheet.previous_year_date', { + args: { date: PYFormatted }, }), }; }; @@ -36,7 +50,7 @@ export const FinancialTablePreviousYear = (Base) => public getPreviousYearChangeColumn = (): ITableColumn => { return { key: 'previous_year_change', - label: this.i18n.__('financial_sheet.previous_year_change'), + label: this.i18n.t('financial_sheet.previous_year_change'), }; }; @@ -47,7 +61,7 @@ export const FinancialTablePreviousYear = (Base) => public getPreviousYearPercentageColumn = (): ITableColumn => { return { key: 'previous_year_percentage', - label: this.i18n.__('financial_sheet.previous_year_percentage'), + label: this.i18n.t('financial_sheet.previous_year_percentage'), }; }; @@ -89,7 +103,7 @@ export const FinancialTablePreviousYear = (Base) => /** * Retrieves previous year total horizontal column accessor. - * @param {number} index + * @param {number} index - Index. * @returns {ITableColumnAccessor} */ public getPreviousYearTotalHorizAccessor = ( @@ -103,7 +117,7 @@ export const FinancialTablePreviousYear = (Base) => /** * Retrieves previous previous year change horizontal column accessor. - * @param {number} index + * @param {number} index * @returns {ITableColumnAccessor} */ public getPreviousYearChangeHorizAccessor = ( @@ -117,7 +131,7 @@ export const FinancialTablePreviousYear = (Base) => /** * Retrieves previous year percentage horizontal column accessor. - * @param {number} index + * @param {number} index * @returns {ITableColumnAccessor} */ public getPreviousYearPercentageHorizAccessor = ( diff --git a/packages/server-nest/src/modules/FinancialStatements/TableSheetPdf.ts b/packages/server-nest/src/modules/FinancialStatements/common/TableSheetPdf.ts similarity index 74% rename from packages/server-nest/src/modules/FinancialStatements/TableSheetPdf.ts rename to packages/server-nest/src/modules/FinancialStatements/common/TableSheetPdf.ts index 4385c0625..17b96d7b4 100644 --- a/packages/server-nest/src/modules/FinancialStatements/TableSheetPdf.ts +++ b/packages/server-nest/src/modules/FinancialStatements/common/TableSheetPdf.ts @@ -1,13 +1,17 @@ import * as R from 'ramda'; -import { ITableColumn, ITableData, ITableRow } from './types/Table.types'; -import { FinancialTableStructure } from './common/FinancialTableStructure'; -import { tableClassNames } from './utils'; +import { ITableColumn, ITableData, ITableRow } from '../types/Table.types'; +import { FinancialTableStructure } from './FinancialTableStructure'; +import { tableClassNames } from '../utils'; import { Injectable } from '@nestjs/common'; -import { TemplateInjectable } from '../TemplateInjectable/TemplateInjectable.service'; -import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy.service'; +import { TemplateInjectable } from '../../TemplateInjectable/TemplateInjectable.service'; +import { ChromiumlyTenancy } from '../../ChromiumlyTenancy/ChromiumlyTenancy.service'; @Injectable() export class TableSheetPdf { + /** + * @param {TemplateInjectable} templateInjectable - The template injectable service. + * @param {ChromiumlyTenancy} chromiumlyTenancy - The chromiumly tenancy service. + */ constructor( private readonly templateInjectable: TemplateInjectable, private readonly chromiumlyTenancy: ChromiumlyTenancy, @@ -60,8 +64,8 @@ export class TableSheetPdf { /** * Converts the table rows to pdf rows. - * @param {ITableRow[]} rows - - * @returns {ITableRow[]} + * @param {ITableRow[]} rows - The table rows to be converted. + * @returns {ITableRow[]} - The converted table rows. */ private tablePdfRows = (rows: ITableRow[]): ITableRow[] => { const curriedFlatNestedTree = R.curry( @@ -70,6 +74,8 @@ export class TableSheetPdf { const flatNestedTree = curriedFlatNestedTree(R.__, { nestedPrefix: '', }); + + // @ts-ignore return R.compose(tableClassNames, flatNestedTree)(rows); }; } diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.module.ts deleted file mode 100644 index 39c02289e..000000000 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { BalanceSheetInjectable } from './BalanceSheetInjectable'; -import { BalanceSheetApplication } from './BalanceSheetApplication'; - -@Module({ - providers: [BalanceSheetInjectable, BalanceSheetApplication], - exports: [BalanceSheetInjectable], -}) -export class BalanceSheetModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetInjectable.ts deleted file mode 100644 index ca12afd32..000000000 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetInjectable.ts +++ /dev/null @@ -1,101 +0,0 @@ -import moment from 'moment'; -import { - IBalanceSheetStatementService, - IBalanceSheetQuery, - IBalanceSheetStatement, -} from './BalanceSheet.types'; -import BalanceSheetRepository from './BalanceSheetRepository'; -import { BalanceSheetMetaInjectable } from './BalanceSheetMeta'; -import { Injectable } from '@nestjs/common'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { events } from '@/common/events/events'; - -@Injectable() -export class BalanceSheetInjectable { - constructor( - private readonly balanceSheetMeta: BalanceSheetMetaInjectable, - private readonly eventPublisher: EventEmitter2, - ) {} - - /** - * Defaults balance sheet filter query. - * @return {IBalanceSheetQuery} - */ - get defaultQuery(): IBalanceSheetQuery { - return { - displayColumnsType: 'total', - displayColumnsBy: 'month', - - fromDate: moment().startOf('year').format('YYYY-MM-DD'), - toDate: moment().format('YYYY-MM-DD'), - - numberFormat: { - precision: 2, - divideOn1000: false, - showZero: false, - formatMoney: 'total', - negativeFormat: 'mines', - }, - noneZero: false, - noneTransactions: false, - - basis: 'cash', - accountIds: [], - - percentageOfColumn: false, - percentageOfRow: false, - - previousPeriod: false, - previousPeriodAmountChange: false, - previousPeriodPercentageChange: false, - - previousYear: false, - previousYearAmountChange: false, - previousYearPercentageChange: false, - }; - } - - /** - * Retrieve balance sheet statement. - * @param {number} tenantId - * @param {IBalanceSheetQuery} query - * @return {IBalanceSheetStatement} - */ - public async balanceSheet( - tenantId: number, - query: IBalanceSheetQuery, - ): Promise { - - const filter = { - ...this.defaultQuery, - ...query, - }; - const balanceSheetRepo = new BalanceSheetRepository(models, filter); - - // Loads all resources. - await balanceSheetRepo.asyncInitialize(); - - // Balance sheet report instance. - const balanceSheetInstanace = new BalanceSheetStatementService( - filter, - balanceSheetRepo, - tenant.metadata.baseCurrency, - i18n, - ); - // Balance sheet data. - const data = balanceSheetInstanace.reportData(); - - // Balance sheet meta. - const meta = await this.balanceSheetMeta.meta(tenantId, filter); - - // Triggers `onBalanceSheetViewed` event. - await this.eventPublisher.emitAsync(events.reports.onBalanceSheetViewed, { - query, - }); - return { - query: filter, - data, - meta, - }; - } -} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowService.ts deleted file mode 100644 index 201a31be4..000000000 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowService.ts +++ /dev/null @@ -1,154 +0,0 @@ -import moment from 'moment'; -import { Service, Inject } from 'typedi'; -import * as R from 'ramda'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import FinancialSheet from '../FinancialSheet'; -import { - ICashFlowStatementService, - ICashFlowStatementQuery, - ICashFlowStatementDOO, - IAccountTransaction, - ICashFlowStatementMeta, -} from '@/interfaces'; -import CashFlowStatement from './CashFlow'; -import Ledger from '@/services/Accounting/Ledger'; -import CashFlowRepository from './CashFlowRepository'; -import InventoryService from '@/services/Inventory/Inventory'; -import { parseBoolean } from 'utils'; -import { Tenant } from '@/system/models'; -import { CashflowSheetMeta } from './CashflowSheetMeta'; - -@Service() -export default class CashFlowStatementService - extends FinancialSheet - implements ICashFlowStatementService -{ - @Inject() - tenancy: TenancyService; - - @Inject() - cashFlowRepo: CashFlowRepository; - - @Inject() - inventoryService: InventoryService; - - @Inject() - private cashflowSheetMeta: CashflowSheetMeta; - - /** - * Defaults balance sheet filter query. - * @return {IBalanceSheetQuery} - */ - get defaultQuery(): ICashFlowStatementQuery { - return { - 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', - }; - } - - /** - * Retrieve cash at beginning transactions. - * @param {number} tenantId - - * @param {ICashFlowStatementQuery} filter - - * @retrun {Promise} - */ - private async cashAtBeginningTransactions( - tenantId: number, - filter: ICashFlowStatementQuery - ): Promise { - const appendPeriodsOperToChain = (trans) => - R.append( - this.cashFlowRepo.cashAtBeginningPeriodTransactions(tenantId, filter), - trans - ); - - const promisesChain = R.pipe( - R.append( - this.cashFlowRepo.cashAtBeginningTotalTransactions(tenantId, 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( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise { - const i18n = this.tenancy.i18n(tenantId); - - // Retrieve all accounts on the storage. - const accounts = await this.cashFlowRepo.cashFlowAccounts(tenantId); - - const tenant = await Tenant.query() - .findById(tenantId) - .withGraphFetched('metadata'); - - const filter = { - ...this.defaultQuery, - ...query, - }; - // Retrieve the accounts transactions. - const transactions = await this.cashFlowRepo.getAccountsTransactions( - tenantId, - filter - ); - // Retrieve the net income transactions. - const netIncome = await this.cashFlowRepo.getNetIncomeTransactions( - tenantId, - filter - ); - // Retrieve the cash at beginning transactions. - const cashAtBeginningTransactions = await this.cashAtBeginningTransactions( - tenantId, - 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, - i18n - ); - // Retrieve the cashflow sheet meta. - const meta = await this.cashflowSheetMeta.meta(tenantId, filter); - - return { - data: cashFlowInstance.reportData(), - query: filter, - meta, - }; - } -} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowExportInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowExportInjectable.ts deleted file mode 100644 index 8562cfbf5..000000000 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowExportInjectable.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Inject, Service } from 'typedi'; -import { ICashFlowStatementQuery } from '@/interfaces'; -import { TableSheet } from '@/lib/Xlsx/TableSheet'; -import { CashflowTableInjectable } from './CashflowTableInjectable'; - -@Service() -export class CashflowExportInjectable { - @Inject() - private cashflowSheetTable: CashflowTableInjectable; - - /** - * Retrieves the cashflow sheet in XLSX format. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - * @returns {Promise} - */ - public async xlsx( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise { - const table = await this.cashflowSheetTable.table(tenantId, query); - - const tableSheet = new TableSheet(table.table); - const tableCsv = tableSheet.convertToXLSX(); - - return tableSheet.convertToBuffer(tableCsv, 'xlsx'); - } - - /** - * Retrieves the cashflow sheet in CSV format. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - * @returns {Promise} - */ - public async csv( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise { - const table = await this.cashflowSheetTable.table(tenantId, query); - - const tableSheet = new TableSheet(table.table); - const tableCsv = tableSheet.convertToCSV(); - - return tableCsv; - } -} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowSheetApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowSheetApplication.ts deleted file mode 100644 index 72a587f52..000000000 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowSheetApplication.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Inject, Service } from 'typedi'; -import { CashflowExportInjectable } from './CashflowExportInjectable'; -import { ICashFlowStatementQuery } from '@/interfaces'; -import CashFlowStatementService from './CashFlowService'; -import { CashflowTableInjectable } from './CashflowTableInjectable'; -import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable'; - -@Service() -export class CashflowSheetApplication { - @Inject() - private cashflowExport: CashflowExportInjectable; - - @Inject() - private cashflowSheet: CashFlowStatementService; - - @Inject() - private cashflowTable: CashflowTableInjectable; - - @Inject() - private cashflowPdf: CashflowTablePdfInjectable; - - /** - * Retrieves the cashflow sheet - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - */ - public async sheet(tenantId: number, query: ICashFlowStatementQuery) { - return this.cashflowSheet.cashFlow(tenantId, query); - } - - /** - * Retrieves the cashflow sheet in table format. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - */ - public async table(tenantId: number, query: ICashFlowStatementQuery) { - return this.cashflowTable.table(tenantId, query); - } - - /** - * Retrieves the cashflow sheet in XLSX format. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - * @returns {Promise} - */ - public async xlsx(tenantId: number, query: ICashFlowStatementQuery) { - return this.cashflowExport.xlsx(tenantId, query); - } - - /** - * Retrieves the cashflow sheet in CSV format. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - * @returns {Promise} - */ - public async csv( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise { - return this.cashflowExport.csv(tenantId, query); - } - - /** - * Retrieves the cashflow sheet in pdf format. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} query - * @returns {Promise} - */ - public async pdf( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise { - return this.cashflowPdf.pdf(tenantId, query); - } -} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowTableInjectable.ts deleted file mode 100644 index 0a54071f2..000000000 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowTableInjectable.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Inject, Service } from "typedi"; -import { ICashFlowStatementQuery, ICashFlowStatementTable } from "@/interfaces"; -import HasTenancyService from "@/services/Tenancy/TenancyService"; -import CashFlowTable from "./CashFlowTable"; -import CashFlowStatementService from "./CashFlowService"; - -@Service() -export class CashflowTableInjectable { - @Inject() - private tenancy: HasTenancyService; - - @Inject() - private cashflowSheet: CashFlowStatementService; - - /** - * Retrieves the cash flow table. - * @returns {Promise} - */ - public async table( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise { - const i18n = this.tenancy.i18n(tenantId); - - const cashflowDOO = await this.cashflowSheet.cashFlow(tenantId, query); - const cashflowTable = new CashFlowTable(cashflowDOO, i18n); - - return { - table: { - columns: cashflowTable.tableColumns(), - rows: cashflowTable.tableRows(), - }, - query: cashflowDOO.query, - meta: cashflowDOO.meta, - }; - } -} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ContactBalanceSummary/ContactBalanceSummary.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ContactBalanceSummary/ContactBalanceSummary.ts new file mode 100644 index 000000000..af3dd2c2b --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ContactBalanceSummary/ContactBalanceSummary.ts @@ -0,0 +1,204 @@ +import { sumBy, isEmpty } from 'lodash'; +import * as R from 'ramda'; +import { + IContactBalanceSummaryContact, + IContactBalanceSummaryTotal, + IContactBalanceSummaryAmount, + IContactBalanceSummaryPercentage, + IContactBalanceSummaryQuery, +} from './ContactBalanceSummary.types'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; + +export class ContactBalanceSummaryReport extends FinancialSheet { + readonly baseCurrency: string; + readonly ledger: Ledger; + readonly filter: IContactBalanceSummaryQuery; + + /** + * Calculates the contact percentage of column. + * @param {number} customerBalance - Contact balance. + * @param {number} totalBalance - Total contacts balance. + * @returns {number} + */ + protected getContactPercentageOfColumn = ( + customerBalance: number, + totalBalance: number + ): number => { + return totalBalance / customerBalance; + }; + + /** + * Retrieve the contacts total. + * @param {IContactBalanceSummaryContact} contacts + * @returns {number} + */ + protected getContactsTotal = ( + contacts: IContactBalanceSummaryContact[] + ): number => { + return sumBy( + contacts, + (contact: IContactBalanceSummaryContact) => contact.total.amount + ); + }; + + /** + * Assoc total percentage of column. + * @param {IContactBalanceSummaryTotal} node + * @returns {IContactBalanceSummaryTotal} + */ + protected assocTotalPercentageOfColumn = ( + node: IContactBalanceSummaryTotal + ): IContactBalanceSummaryTotal => { + return R.assoc('percentageOfColumn', this.getPercentageMeta(1), node); + }; + + /** + * Retrieve the contacts total section. + * @param {IContactBalanceSummaryContact[]} contacts + * @returns {IContactBalanceSummaryTotal} + */ + protected getContactsTotalSection = ( + contacts: IContactBalanceSummaryContact[] + ): IContactBalanceSummaryTotal => { + const customersTotal = this.getContactsTotal(contacts); + const node = { + total: this.getTotalFormat(customersTotal), + }; + return R.compose( + R.when( + R.always(this.filter.percentageColumn), + this.assocTotalPercentageOfColumn + ) + )(node); + }; + + /** + * Retrieve the contact summary section with percentage of column. + * @param {number} total + * @param {IContactBalanceSummaryContact} contact + * @returns {IContactBalanceSummaryContact} + */ + private contactCamparsionPercentageOfColumnMapper = ( + total: number, + contact: IContactBalanceSummaryContact + ): IContactBalanceSummaryContact => { + const amount = this.getContactPercentageOfColumn( + total, + contact.total.amount + ); + return { + ...contact, + percentageOfColumn: this.getPercentageMeta(amount), + }; + }; + + /** + * Mappes the contacts summary sections with percentage of column. + * @param {IContactBalanceSummaryContact[]} contacts - + * @return {IContactBalanceSummaryContact[]} + */ + protected contactCamparsionPercentageOfColumn = ( + contacts: IContactBalanceSummaryContact[] + ): IContactBalanceSummaryContact[] => { + const customersTotal = this.getContactsTotal(contacts); + const camparsionPercentageOfColummn = R.curry( + this.contactCamparsionPercentageOfColumnMapper + )(customersTotal); + + return contacts.map(camparsionPercentageOfColummn); + }; + + /** + * Retrieve the contact total format. + * @param {number} amount - + * @return {IContactBalanceSummaryAmount} + */ + protected getContactTotalFormat = ( + amount: number + ): IContactBalanceSummaryAmount => { + return { + amount, + formattedAmount: this.formatNumber(amount, { money: true }), + currencyCode: this.baseCurrency, + }; + }; + + /** + * Retrieve the total amount of contacts sections. + * @param {number} amount + * @returns {IContactBalanceSummaryAmount} + */ + protected getTotalFormat = (amount: number): IContactBalanceSummaryAmount => { + return { + amount, + formattedAmount: this.formatTotalNumber(amount, { money: true }), + currencyCode: this.baseCurrency, + }; + }; + + /** + * Retrieve the percentage amount object. + * @param {number} amount + * @returns {IContactBalanceSummaryPercentage} + */ + protected getPercentageMeta = ( + amount: number + ): IContactBalanceSummaryPercentage => { + return { + amount, + formattedAmount: this.formatPercentage(amount), + }; + }; + + /** + * Filters customer has none transactions. + * @param {ICustomerBalanceSummaryCustomer} customer - + * @returns {boolean} + */ + private filterContactNoneTransactions = ( + contact: IContactBalanceSummaryContact + ): boolean => { + const entries = this.ledger.whereContactId(contact.id).getEntries(); + + return !isEmpty(entries); + }; + + /** + * Filters the customer that has zero total amount. + * @param {ICustomerBalanceSummaryCustomer} customer + * @returns {boolean} + */ + private filterContactNoneZero = ( + node: IContactBalanceSummaryContact + ): boolean => { + return node.total.amount !== 0; + }; + + /** + * Filters the given customer node; + * @param {ICustomerBalanceSummaryCustomer} customer + */ + private contactNodeFilter = (contact: IContactBalanceSummaryContact) => { + const { noneTransactions, noneZero } = this.filter; + + // Conditions pair filter detarminer. + const condsPairFilters = [ + [noneTransactions, this.filterContactNoneTransactions], + [noneZero, this.filterContactNoneZero], + ]; + return allPassedConditionsPass(condsPairFilters)(contact); + }; + + /** + * Filters the given customers nodes. + * @param {ICustomerBalanceSummaryCustomer[]} nodes + * @returns {ICustomerBalanceSummaryCustomer[]} + */ + protected contactsFilter = ( + nodes: IContactBalanceSummaryContact[] + ): IContactBalanceSummaryContact[] => { + return nodes.filter(this.contactNodeFilter); + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ContactBalanceSummary/ContactBalanceSummary.types.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ContactBalanceSummary/ContactBalanceSummary.types.ts new file mode 100644 index 000000000..fdee93bf6 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ContactBalanceSummary/ContactBalanceSummary.types.ts @@ -0,0 +1,47 @@ +import { INumberFormatQuery } from "../../types/Report.types"; + +export interface IContactBalanceSummaryQuery { + asDate: Date; + numberFormat: INumberFormatQuery; + percentageColumn: boolean; + noneTransactions: boolean; + noneZero: boolean; +} + +export interface IContactBalanceSummaryAmount { + amount: number; + formattedAmount: string; + currencyCode: string; +} +export interface IContactBalanceSummaryPercentage { + amount: number; + formattedAmount: string; +} + +export interface IContactBalanceSummaryContact { + total: IContactBalanceSummaryAmount; + percentageOfColumn?: IContactBalanceSummaryPercentage; +} + +export interface IContactBalanceSummaryTotal { + total: IContactBalanceSummaryAmount; + percentageOfColumn?: IContactBalanceSummaryPercentage; +} + +export interface ICustomerBalanceSummaryData { + customers: IContactBalanceSummaryContact[]; + total: IContactBalanceSummaryTotal; +} + +export interface ICustomerBalanceSummaryStatement { + data: ICustomerBalanceSummaryData; + columns: {}; + query: IContactBalanceSummaryQuery; +} + +export interface ICustomerBalanceSummaryService { + customerBalanceSummary( + tenantId: number, + query: IContactBalanceSummaryQuery + ): Promise; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.controller.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.controller.ts new file mode 100644 index 000000000..4a646c800 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.controller.ts @@ -0,0 +1,57 @@ +import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { ICustomerBalanceSummaryQuery } from './CustomerBalanceSummary.types'; +import { CustomerBalanceSummaryApplication } from './CustomerBalanceSummaryApplication'; +import { AcceptType } from '@/constants/accept-type'; +import { Response } from 'express'; +import { ApiResponse, ApiTags } from '@nestjs/swagger'; + +@Controller('/reports/customer-balance-summary') +@ApiTags('reports') +export class CustomerBalanceSummaryController { + constructor( + private readonly customerBalanceSummaryApp: CustomerBalanceSummaryApplication, + ) {} + + @Get() + @ApiResponse({ status: 200, description: 'Customer balance summary report' }) + async customerBalanceSummary( + @Query() filter: ICustomerBalanceSummaryQuery, + @Res() res: Response, + @Headers('accept') acceptHeader: string, + ) { + // Retrieves the xlsx format. + if (acceptHeader.includes(AcceptType.ApplicationXlsx)) { + const buffer = await this.customerBalanceSummaryApp.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 csv format. + } else if (acceptHeader.includes(AcceptType.ApplicationCsv)) { + const buffer = await this.customerBalanceSummaryApp.csv(filter); + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); + res.setHeader('Content-Type', 'text/csv'); + + return res.send(buffer); + // Retrieves the json table format. + } else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) { + const table = await this.customerBalanceSummaryApp.table(filter); + return res.status(200).send(table); + // Retrieves the pdf format. + } else if (acceptHeader.includes(AcceptType.ApplicationPdf)) { + const buffer = await this.customerBalanceSummaryApp.pdf(filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': buffer.length, + }); + return res.send(buffer); + // Retrieves the json format. + } else { + const sheet = await this.customerBalanceSummaryApp.sheet(filter); + return res.status(200).send(sheet); + } + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.module.ts new file mode 100644 index 000000000..10c841c0a --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { CustomerBalanceSummaryApplication } from './CustomerBalanceSummaryApplication'; +import { CustomerBalanceSummaryExportInjectable } from './CustomerBalanceSummaryExportInjectable'; +import { CustomerBalanceSummaryMeta } from './CustomerBalanceSummaryMeta'; +import { CustomerBalanceSummaryPdf } from './CustomerBalanceSummaryPdf'; +import { CustomerBalanceSummaryService } from './CustomerBalanceSummaryService'; +import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; +import { CustomerBalanceSummaryController } from './CustomerBalanceSummary.controller'; +import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; +import { CustomerBalanceSummaryRepository } from './CustomerBalanceSummaryRepository'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; + +@Module({ + imports: [ + FinancialSheetCommonModule, + ], + controllers: [CustomerBalanceSummaryController], + providers: [ + CustomerBalanceSummaryApplication, + CustomerBalanceSummaryExportInjectable, + CustomerBalanceSummaryMeta, + CustomerBalanceSummaryPdf, + CustomerBalanceSummaryService, + CustomerBalanceSummaryTableInjectable, + CustomerBalanceSummaryRepository, + TenancyContext + ], +}) +export class CustomerBalanceSummaryModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.ts new file mode 100644 index 000000000..b2672958c --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.ts @@ -0,0 +1,112 @@ +import { isEmpty } from 'lodash'; +import * as R from 'ramda'; +import { + ICustomerBalanceSummaryCustomer, + ICustomerBalanceSummaryQuery, + ICustomerBalanceSummaryData, +} from './CustomerBalanceSummary.types'; +import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary'; +import { ILedger } from '@/modules/Ledger/types/Ledger.types'; +import { ModelObject } from 'objection'; +import { INumberFormatQuery } from '../../types/Report.types'; +import { Customer } from '@/modules/Customers/models/Customer'; + +export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport { + readonly ledger: ILedger; + readonly baseCurrency: string; + readonly customers: ModelObject[]; + readonly filter: ICustomerBalanceSummaryQuery; + readonly numberFormat: INumberFormatQuery; + + /** + * Constructor method. + * @param {IJournalPoster} receivableLedger + * @param {ICustomer[]} customers + * @param {ICustomerBalanceSummaryQuery} filter + * @param {string} baseCurrency + */ + constructor( + ledger: ILedger, + customers: ModelObject[], + filter: ICustomerBalanceSummaryQuery, + baseCurrency: string + ) { + super(); + + this.ledger = ledger; + this.baseCurrency = baseCurrency; + this.customers = customers; + this.filter = filter; + this.numberFormat = this.filter.numberFormat; + } + + /** + * Customer section mapper. + * @param {ModelObject} customer + * @returns {ICustomerBalanceSummaryCustomer} + */ + private customerMapper = ( + customer: ModelObject + ): ICustomerBalanceSummaryCustomer => { + const closingBalance = this.ledger + .whereContactId(customer.id) + .getClosingBalance(); + + return { + id: customer.id, + customerName: customer.displayName, + total: this.getContactTotalFormat(closingBalance), + }; + }; + + /** + * Mappes the customer model object to customer balance summary section. + * @param {ModelObject[]} customers - Customers. + * @returns {ICustomerBalanceSummaryCustomer[]} + */ + private customersMapper = ( + customers: ModelObject[] + ): ICustomerBalanceSummaryCustomer[] => { + return customers.map(this.customerMapper); + }; + + /** + * Detarmines whether the customers post filter is active. + * @returns {boolean} + */ + private isCustomersPostFilter = () => { + return isEmpty(this.filter.customersIds); + }; + + /** + * Retrieve the customers sections of the report. + * @param {ModelObject[]} customers + * @returns {ICustomerBalanceSummaryCustomer[]} + */ + private getCustomersSection = ( + customers: ModelObject[] + ): ICustomerBalanceSummaryCustomer[] => { + return R.compose( + R.when(this.isCustomersPostFilter, this.contactsFilter), + R.when( + R.always(this.filter.percentageColumn), + this.contactCamparsionPercentageOfColumn + ), + this.customersMapper + )(customers); + }; + + /** + * Retrieve the report statement data. + * @returns {ICustomerBalanceSummaryData} + */ + public reportData = (): ICustomerBalanceSummaryData => { + const customersSections = this.getCustomersSection(this.customers); + const customersTotal = this.getContactsTotalSection(customersSections); + + return { + customers: customersSections, + total: customersTotal, + }; + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.types.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.types.ts new file mode 100644 index 000000000..fd285db1a --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummary.types.ts @@ -0,0 +1,60 @@ +import { IFinancialSheetCommonMeta } from '../../types/Report.types'; +import { IFinancialTable } from '../../types/Table.types'; +import { + IContactBalanceSummaryQuery, + IContactBalanceSummaryAmount, + IContactBalanceSummaryPercentage, + IContactBalanceSummaryTotal, +} from '../ContactBalanceSummary/ContactBalanceSummary.types'; + +export interface ICustomerBalanceSummaryQuery + extends IContactBalanceSummaryQuery { + customersIds?: number[]; +} + +export interface ICustomerBalanceSummaryAmount + extends IContactBalanceSummaryAmount {} + +export interface ICustomerBalanceSummaryPercentage + extends IContactBalanceSummaryPercentage {} + +export interface ICustomerBalanceSummaryCustomer { + id: number; + customerName: string; + total: ICustomerBalanceSummaryAmount; + percentageOfColumn?: ICustomerBalanceSummaryPercentage; +} + +export interface ICustomerBalanceSummaryTotal + extends IContactBalanceSummaryTotal { + total: ICustomerBalanceSummaryAmount; + percentageOfColumn?: ICustomerBalanceSummaryPercentage; +} + +export interface ICustomerBalanceSummaryData { + customers: ICustomerBalanceSummaryCustomer[]; + total: ICustomerBalanceSummaryTotal; +} + +export interface ICustomerBalanceSummaryMeta extends IFinancialSheetCommonMeta { + formattedAsDate: string; + formattedDateRange: string; +} + +export interface ICustomerBalanceSummaryStatement { + data: ICustomerBalanceSummaryData; + query: ICustomerBalanceSummaryQuery; + meta: ICustomerBalanceSummaryMeta; +} + +export interface ICustomerBalanceSummaryService { + customerBalanceSummary( + tenantId: number, + query: ICustomerBalanceSummaryQuery + ): Promise; +} + +export interface ICustomerBalanceSummaryTable extends IFinancialTable { + query: ICustomerBalanceSummaryQuery; + meta: ICustomerBalanceSummaryMeta; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts new file mode 100644 index 000000000..c7af96760 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts @@ -0,0 +1,62 @@ +import { CustomerBalanceSummaryExportInjectable } from './CustomerBalanceSummaryExportInjectable'; +import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; +import { ICustomerBalanceSummaryQuery } from './CustomerBalanceSummary.types'; +import { CustomerBalanceSummaryService } from './CustomerBalanceSummaryService'; +import { CustomerBalanceSummaryPdf } from './CustomerBalanceSummaryPdf'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class CustomerBalanceSummaryApplication { + constructor( + private readonly customerBalanceSummaryTable: CustomerBalanceSummaryTableInjectable, + private readonly customerBalanceSummaryExport: CustomerBalanceSummaryExportInjectable, + private readonly customerBalanceSummarySheet: CustomerBalanceSummaryService, + private readonly customerBalanceSummaryPdf: CustomerBalanceSummaryPdf, + ) {} + + /** + * Retrieves the customer balance sheet in json format. + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public sheet(query: ICustomerBalanceSummaryQuery) { + return this.customerBalanceSummarySheet.customerBalanceSummary(query); + } + + /** + * Retrieves the customer balance sheet in json format. + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public table(query: ICustomerBalanceSummaryQuery) { + return this.customerBalanceSummaryTable.table(query); + } + + /** + * Retrieves the customer balance sheet in XLSX format. + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public xlsx(query: ICustomerBalanceSummaryQuery) { + return this.customerBalanceSummaryExport.xlsx(query); + } + + /** + * Retrieves the customer balance sheet in CSV format. + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public csv(query: ICustomerBalanceSummaryQuery) { + return this.customerBalanceSummaryExport.csv(query); + } + + /** + * Retrieves the customer balance sheet in PDF format. + * @param {number} tenantId + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public pdf(query: ICustomerBalanceSummaryQuery) { + return this.customerBalanceSummaryPdf.pdf(query); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryExportInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryExportInjectable.ts new file mode 100644 index 000000000..47a4a28ef --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryExportInjectable.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@nestjs/common'; +import { ICustomerBalanceSummaryQuery } from './CustomerBalanceSummary.types'; +import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; +import { TableSheet } from '../../common/TableSheet'; + +@Injectable() +export class CustomerBalanceSummaryExportInjectable { + /** + * Constructor method. + * @param {CustomerBalanceSummaryTableInjectable} customerBalanceSummaryTable + */ + constructor( + private readonly customerBalanceSummaryTable: CustomerBalanceSummaryTableInjectable, + ) {} + + /** + * Retrieves the cashflow sheet in XLSX format. + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public async xlsx(query: ICustomerBalanceSummaryQuery) { + const table = await this.customerBalanceSummaryTable.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 {ICustomerBalanceSummaryQuery} query - Query. + * @returns {Promise} + */ + public async csv(query: ICustomerBalanceSummaryQuery): Promise { + const table = await this.customerBalanceSummaryTable.table(query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToCSV(); + + return tableCsv; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryMeta.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryMeta.ts new file mode 100644 index 000000000..66ec0b586 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryMeta.ts @@ -0,0 +1,32 @@ +import moment from 'moment'; +import { + ICustomerBalanceSummaryMeta, + ICustomerBalanceSummaryQuery, +} from './CustomerBalanceSummary.types'; +import { Injectable } from '@nestjs/common'; +import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; + +@Injectable() +export class CustomerBalanceSummaryMeta { + constructor(private readonly financialSheetMeta: FinancialSheetMeta) {} + + /** + * Retrieves the customer balance summary meta. + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + async meta( + query: ICustomerBalanceSummaryQuery, + ): Promise { + const commonMeta = await this.financialSheetMeta.meta(); + const formattedAsDate = moment(query.asDate).format('YYYY/MM/DD'); + const formattedDateRange = `As ${formattedAsDate}`; + + return { + ...commonMeta, + sheetName: 'Customer Balance Summary', + formattedAsDate, + formattedDateRange, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts new file mode 100644 index 000000000..50a3fc8ac --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts @@ -0,0 +1,29 @@ +import { TableSheetPdf } from '../../common/TableSheetPdf'; +import { ICustomerBalanceSummaryQuery } from './CustomerBalanceSummary.types'; +import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; +import { HtmlTableCustomCss } from './constants'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class CustomerBalanceSummaryPdf { + constructor( + private readonly customerBalanceSummaryTable: CustomerBalanceSummaryTableInjectable, + private readonly tableSheetPdf: TableSheetPdf, + ) {} + + /** + * Converts the given customer balance summary sheet table to pdf. + * @param {IAPAgingSummaryQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf(query: ICustomerBalanceSummaryQuery): Promise { + const table = await this.customerBalanceSummaryTable.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/CustomerBalanceSummary/CustomerBalanceSummaryRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryRepository.ts new file mode 100644 index 000000000..15682a597 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryRepository.ts @@ -0,0 +1,74 @@ +import { map, isEmpty } from 'lodash'; +import { Inject, Injectable } from '@nestjs/common'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; +import { Customer } from '@/modules/Customers/models/Customer'; +import { ModelObject } from 'objection'; +import { ACCOUNT_TYPE } from '@/constants/accounts'; + +@Injectable() +export class CustomerBalanceSummaryRepository { + constructor( + @Inject(Account.name) + private readonly accountModel: typeof Account, + + @Inject(AccountTransaction.name) + private readonly accountTransactionModel: typeof AccountTransaction, + + @Inject(Customer.name) + private readonly customerModel: typeof Customer, + ) {} + + /** + * Retrieve the report customers. + * @param {number[]} customersIds + * @returns {ICustomer[]} + */ + public async getCustomers( + customersIds: number[], + ): Promise[]> { + return await this.customerModel + .query() + .orderBy('displayName') + .onBuild((query) => { + if (!isEmpty(customersIds)) { + query.whereIn('id', customersIds); + } + }); + } + + /** + * Retrieve the A/R accounts. + * @returns {Promise} + */ + public async getReceivableAccounts(): Promise[]> { + return await this.accountModel + .query() + .where('accountType', ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE); + } + + /** + * Retrieve the customers credit/debit totals + * @returns + */ + public async getCustomersTransactions( + asDate: any, + ): Promise[]> { + // Retrieve the receivable accounts A/R. + const receivableAccounts = await this.getReceivableAccounts(); + const receivableAccountsIds = map(receivableAccounts, 'id'); + + // Retrieve the customers transactions of A/R accounts. + const customersTranasctions = await this.accountTransactionModel + .query() + .onBuild((query) => { + query.whereIn('accountId', receivableAccountsIds); + query.modify('filterDateRange', null, asDate); + query.groupBy('contactId'); + query.sum('credit as credit'); + query.sum('debit as debit'); + query.select('contactId'); + }); + return customersTranasctions; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryService.ts new file mode 100644 index 000000000..29fa53dc2 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryService.ts @@ -0,0 +1,89 @@ +import * as R from 'ramda'; +import { + ICustomerBalanceSummaryQuery, + ICustomerBalanceSummaryStatement, +} from './CustomerBalanceSummary.types'; +import { CustomerBalanceSummaryReport } from './CustomerBalanceSummary'; +import { CustomerBalanceSummaryRepository } from './CustomerBalanceSummaryRepository'; +import { CustomerBalanceSummaryMeta } from './CustomerBalanceSummaryMeta'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; +import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { events } from '@/common/events/events'; +import { getCustomerBalanceSummaryDefaultQuery } from './_utils'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; + +@Injectable() +export class CustomerBalanceSummaryService { + constructor( + private readonly reportRepository: CustomerBalanceSummaryRepository, + private readonly customerBalanceSummaryMeta: CustomerBalanceSummaryMeta, + private readonly eventPublisher: EventEmitter2, + private readonly tenancyContext: TenancyContext, + ) {} + + /** + * Retrieve the customers ledger entries mapped from accounts transactions. + * @param {Date|string} asDate - The date to retrieve the ledger entries. + * @returns {Promise} + */ + private async getReportCustomersEntries( + asDate: Date | string, + ): Promise { + const transactions = + await this.reportRepository.getCustomersTransactions(asDate); + const commonProps = { accountNormal: 'debit', date: asDate }; + + return R.map(R.merge(commonProps))(transactions); + } + + /** + * Retrieve the statment of customer balance summary report. + * @param {ICustomerBalanceSummaryQuery} query - The customer balance summary query. + * @return {Promise} + */ + public async customerBalanceSummary( + query: ICustomerBalanceSummaryQuery, + ): Promise { + const tenantMetadata = await this.tenancyContext.getTenantMetadata(); + + // Merges the default query and request query. + const filter = { ...getCustomerBalanceSummaryDefaultQuery(), ...query }; + + // Retrieve the customers list ordered by the display name. + const customers = await this.reportRepository.getCustomers( + query.customersIds, + ); + // Retrieve the customers debit/credit totals. + const customersEntries = await this.getReportCustomersEntries( + filter.asDate, + ); + // Ledger query. + const ledger = new Ledger(customersEntries); + + // Report instance. + const report = new CustomerBalanceSummaryReport( + ledger, + customers, + filter, + tenantMetadata.baseCurrency, + ); + // Retrieve the customer balance summary meta. + const meta = await this.customerBalanceSummaryMeta.meta(filter); + + // Triggers `onCustomerBalanceSummaryViewed` event. + await this.eventPublisher.emitAsync( + events.reports.onCustomerBalanceSummaryViewed, + { + query, + }, + ); + + return { + data: report.reportData(), + query: filter, + meta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryTableInjectable.ts new file mode 100644 index 000000000..715d8b529 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryTableInjectable.ts @@ -0,0 +1,38 @@ +import { CustomerBalanceSummaryService } from './CustomerBalanceSummaryService'; +import { + ICustomerBalanceSummaryQuery, + ICustomerBalanceSummaryTable, +} from './CustomerBalanceSummary.types'; +import { CustomerBalanceSummaryTable } from './CustomerBalanceSummaryTableRows'; +import { Injectable } from '@nestjs/common'; +import { I18nService } from 'nestjs-i18n'; + +@Injectable() +export class CustomerBalanceSummaryTableInjectable { + constructor( + private readonly customerBalanceSummaryService: CustomerBalanceSummaryService, + private readonly i18n: I18nService, + ) {} + + /** + * Retrieves the customer balance sheet in table format. + * @param {ICustomerBalanceSummaryQuery} filter - The customer balance summary query. + * @returns {Promise} + */ + public async table( + filter: ICustomerBalanceSummaryQuery, + ): Promise { + const { data, query, meta } = + await this.customerBalanceSummaryService.customerBalanceSummary(filter); + const table = new CustomerBalanceSummaryTable(data, filter, this.i18n); + + return { + table: { + columns: table.tableColumns(), + rows: table.tableRows(), + }, + query, + meta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryTableRows.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryTableRows.ts new file mode 100644 index 000000000..c9a3a2ddb --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryTableRows.ts @@ -0,0 +1,156 @@ +import * as R from 'ramda'; +import { I18nService } from 'nestjs-i18n'; +import { + ICustomerBalanceSummaryData, + ICustomerBalanceSummaryCustomer, + ICustomerBalanceSummaryTotal, + ICustomerBalanceSummaryQuery, +} from './CustomerBalanceSummary.types'; +import { + IColumnMapperMeta, + ITableColumn, + ITableRow, +} from '../../types/Table.types'; +import { tableMapper, tableRowMapper } from '../../utils/Table.utils'; + +enum TABLE_ROWS_TYPES { + CUSTOMER = 'CUSTOMER', + TOTAL = 'TOTAL', +} + +export class CustomerBalanceSummaryTable { + public readonly report: ICustomerBalanceSummaryData; + public readonly query: ICustomerBalanceSummaryQuery; + public readonly i18n: I18nService; + + /** + * Constructor method. + * @param {ICustomerBalanceSummaryData} report - The report object. + * @param {ICustomerBalanceSummaryQuery} query - The query object. + * @param {I18nService} i18n - The i18n service. + */ + constructor( + report: ICustomerBalanceSummaryData, + query: ICustomerBalanceSummaryQuery, + i18n: I18nService, + ) { + this.report = report; + this.i18n = i18n; + this.query = query; + } + + /** + * Retrieve percentage columns accessor. + * @returns {IColumnMapperMeta[]} + */ + private getPercentageColumnsAccessor = (): IColumnMapperMeta[] => { + return [ + { + key: 'percentageOfColumn', + accessor: 'percentageOfColumn.formattedAmount', + }, + ]; + }; + + /** + * Retrieve customer node columns accessor. + * @returns {IColumnMapperMeta[]} + */ + private getCustomerColumnsAccessor = (): IColumnMapperMeta[] => { + const columns = [ + { key: 'name', accessor: 'customerName' }, + { key: 'total', accessor: 'total.formattedAmount' }, + ]; + return R.compose( + R.concat(columns), + R.when( + R.always(this.query.percentageColumn), + R.concat(this.getPercentageColumnsAccessor()), + ), + )([]); + }; + + /** + * Transformes the customers to table rows. + * @param {ICustomerBalanceSummaryCustomer[]} customers + * @returns {ITableRow[]} + */ + private customersTransformer( + customers: ICustomerBalanceSummaryCustomer[], + ): ITableRow[] { + const columns = this.getCustomerColumnsAccessor(); + + return tableMapper(customers, columns, { + rowTypes: [TABLE_ROWS_TYPES.CUSTOMER], + }); + } + + /** + * Retrieve total node columns accessor. + * @returns {IColumnMapperMeta[]} + */ + private getTotalColumnsAccessor = (): IColumnMapperMeta[] => { + const columns = [ + { key: 'name', value: this.i18n.t('Total') }, + { key: 'total', accessor: 'total.formattedAmount' }, + ]; + return R.compose( + R.concat(columns), + R.when( + R.always(this.query.percentageColumn), + R.concat(this.getPercentageColumnsAccessor()), + ), + )([]); + }; + + /** + * Transformes the total to table row. + * @param {ICustomerBalanceSummaryTotal} total + * @returns {ITableRow} + */ + private totalTransformer = ( + total: ICustomerBalanceSummaryTotal, + ): ITableRow => { + const columns = this.getTotalColumnsAccessor(); + + return tableRowMapper(total, columns, { + rowTypes: [TABLE_ROWS_TYPES.TOTAL], + }); + }; + + /** + * Transformes the customer balance summary to table rows. + * @param {ICustomerBalanceSummaryData} customerBalanceSummary + * @returns {ITableRow[]} + */ + public tableRows(): ITableRow[] { + const customers = this.customersTransformer(this.report.customers); + const total = this.totalTransformer(this.report.total); + + return customers.length > 0 ? [...customers, total] : []; + } + + /** + * Retrieve the report statement columns + * @returns {ITableColumn[]} + */ + public tableColumns = (): ITableColumn[] => { + const columns = [ + { + key: 'name', + label: this.i18n.t('contact_summary_balance.account_name'), + }, + { key: 'total', label: this.i18n.t('contact_summary_balance.total') }, + ]; + return R.compose( + R.when( + R.always(this.query.percentageColumn), + R.append({ + key: 'percentage_of_column', + label: this.i18n.t('contact_summary_balance.percentage_column'), + }), + ), + R.concat(columns), + )([]); + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/_utils.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/_utils.ts new file mode 100644 index 000000000..98ccc11a3 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/_utils.ts @@ -0,0 +1,16 @@ +export const getCustomerBalanceSummaryDefaultQuery = () => { + return { + asDate: moment().format('YYYY-MM-DD'), + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines', + }, + percentageColumn: false, + + noneZero: false, + noneTransactions: true, + }; +}; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/constants.ts b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/constants.ts new file mode 100644 index 000000000..513a3dcdb --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/CustomerBalanceSummary/constants.ts @@ -0,0 +1,14 @@ +export const HtmlTableCustomCss = ` +table tr.row-type--total td { + font-weight: 600; + border-top: 1px solid #bbb; + border-bottom: 3px double #333; +} +table .column--name { + width: 65%; +} +table .column--total, +table .cell--total { + text-align: right; +} +`; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.controller.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.controller.ts new file mode 100644 index 000000000..77151c26b --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.controller.ts @@ -0,0 +1,61 @@ +import { Controller, Get } from '@nestjs/common'; +import { Headers, Query, Res } from '@nestjs/common'; +import { IGeneralLedgerSheetQuery } from './GeneralLedger.types'; +import { GeneralLedgerApplication } from './GeneralLedgerApplication'; +import { AcceptType } from '@/constants/accept-type'; +import { Response } from 'express'; +import { ApiResponse, ApiTags } from '@nestjs/swagger'; + +@Controller('/reports/general-ledger') +@ApiTags('reports') +export class GeneralLedgerController { + constructor( + private readonly generalLedgerApplication: GeneralLedgerApplication, + ) {} + + @Get() + @ApiResponse({ status: 200, description: 'General ledger report' }) + public async getGeneralLedger( + @Query() query: IGeneralLedgerSheetQuery, + @Res() res: Response, + @Headers('accept') acceptHeader: string, + ) { + // Retrieves the table format. + if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) { + const table = await this.generalLedgerApplication.table(query); + + return res.status(200).send(table); + // Retrieves the csv format. + } else if (acceptHeader.includes(AcceptType.ApplicationCsv)) { + const buffer = await this.generalLedgerApplication.csv(query); + + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); + res.setHeader('Content-Type', 'text/csv'); + + return res.send(buffer); + // Retrieves the xlsx format. + } else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) { + const buffer = await this.generalLedgerApplication.xlsx(query); + + res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx'); + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + return res.send(buffer); + // Retrieves the pdf format. + } else if (acceptHeader.includes(AcceptType.ApplicationPdf)) { + const pdfContent = await this.generalLedgerApplication.pdf(query); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); + // Retrieves the json format. + } else { + const sheet = await this.generalLedgerApplication.sheet(query); + + return res.status(200).send(sheet); + } + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.module.ts new file mode 100644 index 000000000..885d6aaf0 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.module.ts @@ -0,0 +1,31 @@ +import { Module } from '@nestjs/common'; +import { GeneralLedgerRepository } from './GeneralLedgerRepository'; +import { GeneralLedgerApplication } from './GeneralLedgerApplication'; +import { GeneralLedgerPdf } from './GeneralLedgerPdf'; +import { GeneralLedgerExportInjectable } from './GeneralLedgerExport'; +import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; +import { GeneralLedgerService } from './GeneralLedgerService'; +import { GeneralLedgerController } from './GeneralLedger.controller'; +import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; +import { GeneralLedgerMeta } from './GeneralLedgerMeta'; +import { AccountsModule } from '@/modules/Accounts/Accounts.module'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; + +@Module({ + imports: [ + FinancialSheetCommonModule, + AccountsModule + ], + providers: [ + GeneralLedgerRepository, + GeneralLedgerApplication, + GeneralLedgerPdf, + GeneralLedgerExportInjectable, + GeneralLedgerTableInjectable, + GeneralLedgerService, + GeneralLedgerMeta, + TenancyContext + ], + controllers: [GeneralLedgerController], +}) +export class GeneralLedgerModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.ts new file mode 100644 index 000000000..81ac03eb3 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.ts @@ -0,0 +1,390 @@ +import { isEmpty, get, last, head } from 'lodash'; +import * as moment from 'moment'; +import * as R from 'ramda'; +import { + IGeneralLedgerSheetQuery, + IGeneralLedgerSheetAccount, + IGeneralLedgerSheetAccountBalance, + IGeneralLedgerSheetAccountTransaction, +} from './GeneralLedger.types'; +import { GeneralLedgerRepository } from './GeneralLedgerRepository'; +import { calculateRunningBalance } from './_utils'; +import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { I18nService } from 'nestjs-i18n'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { ModelObject } from 'objection'; +import { flatToNestedArray } from '@/utils/flat-to-nested-array'; +import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils'; + +export class GeneralLedgerSheet extends R.compose(FinancialSheetStructure)( + FinancialSheet, +) { + public query: IGeneralLedgerSheetQuery; + public baseCurrency: string; + public i18n: I18nService; + public repository: GeneralLedgerRepository; + + /** + * Constructor method. + * @param {IGeneralLedgerSheetQuery} query - + * @param {GeneralLedgerRepository} repository - + * @param {I18nService} i18n - + */ + constructor( + query: IGeneralLedgerSheetQuery, + repository: GeneralLedgerRepository, + i18n, + ) { + super(); + + this.query = query; + this.numberFormat = this.query.numberFormat; + this.repository = repository; + this.baseCurrency = this.repository.tenant.metadata.currencyCode; + this.i18n = i18n; + } + + /** + * Entry mapper. + * @param {ILedgerEntry} entry - + * @return {IGeneralLedgerSheetAccountTransaction} + */ + private getEntryRunningBalance( + entry: ILedgerEntry, + openingBalance: number, + runningBalance?: number, + ): number { + const lastRunningBalance = runningBalance || openingBalance; + + const amount = Ledger.getAmount( + entry.credit, + entry.debit, + entry.accountNormal, + ); + return calculateRunningBalance(amount, lastRunningBalance); + } + + /** + * Maps the given ledger entry to G/L transaction. + * @param {ILedgerEntry} entry + * @param {number} runningBalance + * @returns {IGeneralLedgerSheetAccountTransaction} + */ + private transactionMapper( + entry: ILedgerEntry, + runningBalance: number, + ): IGeneralLedgerSheetAccountTransaction { + const contact = this.repository.contactsById.get(entry.contactId); + const amount = Ledger.getAmount( + entry.credit, + entry.debit, + entry.accountNormal, + ); + return { + id: entry.id, + date: entry.date, + dateFormatted: moment(entry.date).format('YYYY MMM DD'), + + referenceType: entry.transactionType, + referenceId: entry.transactionId, + + transactionNumber: entry.transactionNumber, + transactionTypeFormatted: this.i18n.t( + getTransactionTypeLabel( + entry.transactionType, + entry.transactionSubType, + ), + ), + contactName: get(contact, 'displayName'), + contactType: get(contact, 'contactService'), + + transactionType: entry.transactionType, + index: entry.index, + note: entry.note, + + credit: entry.credit, + debit: entry.debit, + amount, + runningBalance, + + formattedAmount: this.formatNumber(amount, { excerptZero: false }), + formattedCredit: this.formatNumber(entry.credit, { excerptZero: false }), + formattedDebit: this.formatNumber(entry.debit, { excerptZero: false }), + formattedRunningBalance: this.formatNumber(runningBalance, { + excerptZero: false, + }), + + currencyCode: this.baseCurrency, + } as IGeneralLedgerSheetAccountTransaction; + } + + /** + * Mapping the account transactions to general ledger transactions of the given account. + * @param {IAccount} account + * @return {IGeneralLedgerSheetAccountTransaction[]} + */ + private accountTransactionsMapper( + account: ModelObject, + openingBalance: number, + ): IGeneralLedgerSheetAccountTransaction[] { + const entries = this.repository.transactionsLedger + .whereAccountId(account.id) + .getEntries(); + + return entries + .reduce((prev: Array<[number, ILedgerEntry]>, current: ILedgerEntry) => { + const prevEntry = last(prev); + const prevRunningBalance = head(prevEntry) as number; + const amount = this.getEntryRunningBalance( + current, + openingBalance, + prevRunningBalance, + ); + return [...prev, [amount, current]]; + }, []) + .map((entryPair: [number, ILedgerEntry]) => { + const [runningBalance, entry] = entryPair; + + return this.transactionMapper(entry, runningBalance); + }); + } + + /** + * Retrieves the given account opening balance. + * @param {number} accountId + * @returns {number} + */ + private accountOpeningBalance(accountId: number): number { + return this.repository.openingBalanceTransactionsLedger + .whereAccountId(accountId) + .getClosingBalance(); + } + + /** + * Retrieve the given account opening balance. + * @param {IAccount} account + * @return {IGeneralLedgerSheetAccountBalance} + */ + private accountOpeningBalanceTotal( + accountId: number, + ): IGeneralLedgerSheetAccountBalance { + const amount = this.accountOpeningBalance(accountId); + const formattedAmount = this.formatTotalNumber(amount); + const currencyCode = this.baseCurrency; + const date = this.query.fromDate; + + return { amount, formattedAmount, currencyCode, date }; + } + + /** + * Retrieves the given account closing balance. + * @param {number} accountId + * @returns {number} + */ + private accountClosingBalance(accountId: number): number { + const openingBalance = this.repository.openingBalanceTransactionsLedger + .whereAccountId(accountId) + .getClosingBalance(); + + const transactionsBalance = this.repository.transactionsLedger + .whereAccountId(accountId) + .getClosingBalance(); + + return openingBalance + transactionsBalance; + } + + /** + * Retrieves the given account closing balance. + * @param {IAccount} account + * @return {IGeneralLedgerSheetAccountBalance} + */ + private accountClosingBalanceTotal( + accountId: number, + ): IGeneralLedgerSheetAccountBalance { + const amount = this.accountClosingBalance(accountId); + const formattedAmount = this.formatTotalNumber(amount); + const currencyCode = this.baseCurrency; + const date = this.query.toDate; + + return { amount, formattedAmount, currencyCode, date }; + } + + /** + * Retrieves the given account closing balance with subaccounts. + * @param {number} accountId + * @returns {number} + */ + private accountClosingBalanceWithSubaccounts = ( + accountId: number, + ): number => { + const depsAccountsIds = + this.repository.accountsGraph.dependenciesOf(accountId); + + const openingBalance = this.repository.openingBalanceTransactionsLedger + .whereAccountsIds([...depsAccountsIds, accountId]) + .getClosingBalance(); + + const transactionsBalanceWithSubAccounts = + this.repository.transactionsLedger + .whereAccountsIds([...depsAccountsIds, accountId]) + .getClosingBalance(); + + const closingBalance = openingBalance + transactionsBalanceWithSubAccounts; + + return closingBalance; + }; + + /** + * Retrieves the closing balance with subaccounts total node. + * @param {number} accountId + * @returns {IGeneralLedgerSheetAccountBalance} + */ + private accountClosingBalanceWithSubaccountsTotal = ( + accountId: number, + ): IGeneralLedgerSheetAccountBalance => { + const amount = this.accountClosingBalanceWithSubaccounts(accountId); + const formattedAmount = this.formatTotalNumber(amount); + const currencyCode = this.baseCurrency; + const date = this.query.toDate; + + return { amount, formattedAmount, currencyCode, date }; + }; + + /** + * Detarmines whether the closing balance subaccounts node should be exist. + * @param {number} accountId + * @returns {boolean} + */ + private isAccountNodeIncludesClosingSubaccounts = (accountId: number) => { + // Retrun early if there is no accounts in the filter so + // return closing subaccounts in all cases. + if (isEmpty(this.query.accountsIds)) { + return true; + } + // Returns true if the given account id includes transactions. + return this.repository.accountNodesIncludeTransactions.includes(accountId); + }; + + /** + * Retreive general ledger accounts sections. + * @param {IAccount} account + * @return {IGeneralLedgerSheetAccount} + */ + private accountMapper = ( + account: ModelObject, + ): IGeneralLedgerSheetAccount => { + const openingBalance = this.accountOpeningBalanceTotal(account.id); + const transactions = this.accountTransactionsMapper( + account, + openingBalance.amount, + ); + const closingBalance = this.accountClosingBalanceTotal(account.id); + const closingBalanceSubaccounts = + this.accountClosingBalanceWithSubaccountsTotal(account.id); + + const initialNode = { + id: account.id, + name: account.name, + code: account.code, + index: account.index, + parentAccountId: account.parentAccountId, + openingBalance, + transactions, + closingBalance, + }; + + return R.compose( + R.when( + () => this.isAccountNodeIncludesClosingSubaccounts(account.id), + R.assoc('closingBalanceSubaccounts', closingBalanceSubaccounts), + ), + )(initialNode); + }; + + /** + * Maps over deep nodes to retrieve the G/L account node. + * @param {IAccount[]} accounts + * @returns {IGeneralLedgerSheetAccount[]} + */ + private accountNodesDeepMap = ( + accounts: ModelObject[], + ): IGeneralLedgerSheetAccount[] => { + return this.mapNodesDeep(accounts, this.accountMapper); + }; + + /** + * Transformes the flatten nodes to nested nodes. + * @param {ModelObject[]} flattenAccounts - + * @returns {ModelObject[]} + */ + private nestedAccountsNode = ( + flattenAccounts: ModelObject[], + ): ModelObject[] => { + return flatToNestedArray(flattenAccounts, { + id: 'id', + parentId: 'parentAccountId', + }); + }; + + /** + * Filters account nodes. + * @param {IGeneralLedgerSheetAccount[]} nodes + * @returns {IGeneralLedgerSheetAccount[]} + */ + private filterAccountNodesByTransactionsFilter = ( + nodes: IGeneralLedgerSheetAccount[], + ): IGeneralLedgerSheetAccount[] => { + return this.filterNodesDeep( + nodes, + (account: IGeneralLedgerSheetAccount) => + !(account.transactions.length === 0 && this.query.noneTransactions), + ); + }; + + /** + * Filters account nodes by the acounts filter. + * @param {IAccount[]} nodes + * @returns {IAccount[]} + */ + private filterAccountNodesByAccountsFilter = ( + nodes: ModelObject[], + ): ModelObject[] => { + return this.filterNodesDeep(nodes, (node: IGeneralLedgerSheetAccount) => { + if (R.isEmpty(this.query.accountsIds)) { + return true; + } + // Returns true if the given account id exists in the filter. + return this.repository.accountNodeInclude?.includes(node.id); + }); + }; + + /** + * Retrieves mapped accounts with general ledger transactions and + * opeing/closing balance. + * @param {ModelObject[]} accounts - + * @return {IGeneralLedgerSheetAccount[]} + */ + private accountsWalker( + accounts: ModelObject[], + ): IGeneralLedgerSheetAccount[] { + return R.compose( + R.defaultTo([]), + this.filterAccountNodesByTransactionsFilter, + this.accountNodesDeepMap, + R.defaultTo([]), + this.filterAccountNodesByAccountsFilter, + this.nestedAccountsNode, + )(accounts); + } + + /** + * Retrieves general ledger report data. + * @return {IGeneralLedgerSheetAccount[]} + */ + public reportData(): IGeneralLedgerSheetAccount[] { + return this.accountsWalker(this.repository.accounts); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.types.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.types.ts new file mode 100644 index 000000000..29305b7bf --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.types.ts @@ -0,0 +1,92 @@ +import { IFinancialSheetCommonMeta } from "../../types/Report.types"; +import { IFinancialTable } from "../../types/Table.types"; + +export interface IGeneralLedgerSheetQuery { + fromDate: Date | string; + toDate: Date | string; + basis: string; + numberFormat: { + noCents: boolean; + divideOn1000: boolean; + }; + noneTransactions: boolean; + accountsIds: number[]; + branchesIds?: number[]; +} + +export interface IGeneralLedgerSheetAccountTransaction { + id: number; + + amount: number; + runningBalance: number; + credit: number; + debit: number; + + formattedAmount: string; + formattedCredit: string; + formattedDebit: string; + formattedRunningBalance: string; + + currencyCode: string; + note?: string; + + transactionTypeFormatted: string; + transactionNumber: string; + + referenceId?: number; + referenceType?: string; + + date: Date | string; + dateFormatted: string; +} + +export interface IGeneralLedgerSheetAccountBalance { + date: Date | string; + amount: number; + formattedAmount: string; + currencyCode: string; +} + +export interface IGeneralLedgerSheetAccount { + id: number; + name: string; + code: string; + index: number; + parentAccountId: number; + transactions: IGeneralLedgerSheetAccountTransaction[]; + openingBalance: IGeneralLedgerSheetAccountBalance; + closingBalance: IGeneralLedgerSheetAccountBalance; + closingBalanceSubaccounts?: IGeneralLedgerSheetAccountBalance; + children?: IGeneralLedgerSheetAccount[]; +} + +export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[]; + +export interface IAccountTransaction { + id: number; + index: number; + draft: boolean; + note: string; + accountId: number; + transactionType: string; + referenceType: string; + referenceId: number; + contactId: number; + contactType: string; + credit: number; + debit: number; + date: string | Date; + createdAt: string | Date; + updatedAt: string | Date; +} + +export interface IGeneralLedgerMeta extends IFinancialSheetCommonMeta { + formattedFromDate: string; + formattedToDate: string; + formattedDateRange: string; +} + +export interface IGeneralLedgerTableData extends IFinancialTable { + meta: IGeneralLedgerMeta; + query: IGeneralLedgerSheetQuery; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerApplication.ts new file mode 100644 index 000000000..a056c43c2 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerApplication.ts @@ -0,0 +1,70 @@ +import { + IGeneralLedgerSheetQuery, + IGeneralLedgerTableData, +} from './GeneralLedger.types'; +import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; +import { GeneralLedgerExportInjectable } from './GeneralLedgerExport'; +import { GeneralLedgerService } from './GeneralLedgerService'; +import { GeneralLedgerPdf } from './GeneralLedgerPdf'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class GeneralLedgerApplication { + constructor( + private readonly GLTable: GeneralLedgerTableInjectable, + private readonly GLExport: GeneralLedgerExportInjectable, + private readonly GLSheet: GeneralLedgerService, + private readonly GLPdf: GeneralLedgerPdf, + ) {} + + /** + * Retrieves the G/L sheet in json format. + * @param {IGeneralLedgerSheetQuery} query + */ + public sheet(query: IGeneralLedgerSheetQuery) { + return this.GLSheet.generalLedger(query); + } + + /** + * Retrieves the G/L sheet in table format. + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public table( + query: IGeneralLedgerSheetQuery, + ): Promise { + return this.GLTable.table(query); + } + + /** + * Retrieves the G/L sheet in xlsx format. + * @param {IGeneralLedgerSheetQuery} query + * @returns {} + */ + public xlsx( + query: IGeneralLedgerSheetQuery, + ): Promise { + return this.GLExport.xlsx(query); + } + + /** + * Retrieves the G/L sheet in csv format. + * @param {IGeneralLedgerSheetQuery} query - + */ + public csv( + query: IGeneralLedgerSheetQuery, + ): Promise { + return this.GLExport.csv(query); + } + + /** + * Retrieves the G/L sheet in pdf format. + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public pdf( + query: IGeneralLedgerSheetQuery, + ): Promise { + return this.GLPdf.pdf(query); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerExport.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerExport.ts new file mode 100644 index 000000000..3b07fb287 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerExport.ts @@ -0,0 +1,42 @@ + +import { Injectable } from '@nestjs/common'; +import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; +import { IGeneralLedgerSheetQuery } from './GeneralLedger.types'; +import { TableSheet } from '../../common/TableSheet'; + +@Injectable() +export class GeneralLedgerExportInjectable { + constructor( + private readonly generalLedgerTable: GeneralLedgerTableInjectable + ) {} + + /** + * Retrieves the general ledger sheet in XLSX format. + * @param {IGeneralLedgerSheetQuery} query - General ledger sheet query. + * @returns {Promise} + */ + public async xlsx(query: IGeneralLedgerSheetQuery) { + const table = await this.generalLedgerTable.table(query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToXLSX(); + + return tableSheet.convertToBuffer(tableCsv, 'xlsx'); + } + + /** + * Retrieves the general ledger sheet in CSV format. + * @param {IGeneralLedgerSheetQuery} query - General ledger sheet query. + * @returns {Promise} + */ + public async csv( + query: IGeneralLedgerSheetQuery + ): Promise { + const table = await this.generalLedgerTable.table(query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToCSV(); + + return tableCsv; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerMeta.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerMeta.ts new file mode 100644 index 000000000..93dd8d0a0 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerMeta.ts @@ -0,0 +1,34 @@ +import { + IGeneralLedgerMeta, + IGeneralLedgerSheetQuery, +} from './GeneralLedger.types'; +import moment from 'moment'; +import { Injectable } from '@nestjs/common'; +import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; + +@Injectable() +export class GeneralLedgerMeta { + constructor(private readonly financialSheetMeta: FinancialSheetMeta) {} + + /** + * Retrieve the general ledger meta. + * @returns {IGeneralLedgerMeta} + */ + public async meta( + query: IGeneralLedgerSheetQuery, + ): 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}`; + + return { + ...commonMeta, + sheetName: 'Balance Sheet', + formattedFromDate, + formattedToDate, + formattedDateRange, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerPdf.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerPdf.ts new file mode 100644 index 000000000..b53c50ccb --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerPdf.ts @@ -0,0 +1,29 @@ +import { TableSheetPdf } from '../../common/TableSheetPdf'; +import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; +import { IGeneralLedgerSheetQuery } from './GeneralLedger.types'; +import { HtmlTableCustomCss } from './constants'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class GeneralLedgerPdf { + constructor( + private readonly generalLedgerTable: GeneralLedgerTableInjectable, + private readonly tableSheetPdf: TableSheetPdf, + ) {} + + /** + * Converts the general ledger sheet table to pdf. + * @param {IGeneralLedgerSheetQuery} query - General ledger sheet query. + * @returns {Promise} + */ + public async pdf(query: IGeneralLedgerSheetQuery): Promise { + const table = await this.generalLedgerTable.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/GeneralLedger/GeneralLedgerRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerRepository.ts new file mode 100644 index 000000000..86bf3eb76 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerRepository.ts @@ -0,0 +1,180 @@ +import moment from 'moment'; +import * as R from 'ramda'; +import { + IGeneralLedgerSheetQuery, + +} from './GeneralLedger.types'; +import { flatten, isEmpty, uniq } from 'lodash'; +import { ModelObject } from 'objection'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; +import { Contact } from '@/modules/Contacts/models/Contact'; +import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository'; +import { Inject } from '@nestjs/common'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { transformToMap } from '@/utils/transform-to-key'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { TenantModel } from '@/modules/System/models/TenantModel'; + +export class GeneralLedgerRepository { + public filter: IGeneralLedgerSheetQuery; + public accounts: Account[]; + + public transactions: AccountTransaction[]; + public openingBalanceTransactions: AccountTransaction[]; + + public transactionsLedger: Ledger; + public openingBalanceTransactionsLedger: Ledger; + + public repositories: any; + public models: any; + public accountsGraph: any; + + public contacts: ModelObject[]; + public contactsById: Map>; + + public tenantId: number; + public tenant: TenantModel; + + public accountNodesIncludeTransactions: Array = []; + public accountNodeInclude: Array = []; + + @Inject(AccountRepository) + private readonly accountRepository: AccountRepository; + + @Inject(TenancyContext) + private readonly tenancyContext: TenancyContext; + + /** + * Set the filter. + * @param {IGeneralLedgerSheetQuery} filter - The filter. + */ + setFilter(filter: IGeneralLedgerSheetQuery) { + this.filter = filter; + } + + /** + * Initialize the G/L report. + */ + public async asyncInitialize() { + await this.initTenant(); + await this.initAccounts(); + await this.initAccountsGraph(); + await this.initContacts(); + await this.initAccountsOpeningBalance(); + this.initAccountNodesIncludeTransactions(); + await this.initTransactions(); + this.initAccountNodesIncluded(); + } + + /** + * Initialize the tenant. + */ + public async initTenant() { + this.tenant = await this.tenancyContext.getTenant(true); + } + + /** + * Initialize the accounts. + */ + public async initAccounts() { + this.accounts = await this.accountRepository + .all() + .orderBy('name', 'ASC'); + } + + /** + * Initialize the accounts graph. + */ + public async initAccountsGraph() { + this.accountsGraph = + await this.repositories.accountRepository.getDependencyGraph(); + } + + /** + * Initialize the contacts. + */ + public async initContacts() { + this.contacts = await this.repositories.contactRepository.all(); + this.contactsById = transformToMap(this.contacts, 'id'); + } + + /** + * Initialize the G/L transactions from/to the given date. + */ + public async initTransactions() { + this.transactions = await this.repositories.transactionsRepository + .journal({ + fromDate: this.filter.fromDate, + toDate: this.filter.toDate, + branchesIds: this.filter.branchesIds, + }) + .orderBy('date', 'ASC') + .onBuild((query) => { + if (this.filter.accountsIds?.length > 0) { + query.whereIn('accountId', this.accountNodesIncludeTransactions); + } + }); + // Transform array transactions to journal collection. + this.transactionsLedger = Ledger.fromTransactions(this.transactions); + } + + /** + * Initialize the G/L accounts opening balance. + */ + public async initAccountsOpeningBalance() { + // Retreive opening balance credit/debit sumation. + this.openingBalanceTransactions = + await this.repositories.transactionsRepository.journal({ + toDate: moment(this.filter.fromDate).subtract(1, 'day'), + sumationCreditDebit: true, + branchesIds: this.filter.branchesIds, + }); + + // Accounts opening transactions. + this.openingBalanceTransactionsLedger = Ledger.fromTransactions( + this.openingBalanceTransactions + ); + } + + /** + * Initialize the account nodes that should include transactions. + * @returns {void} + */ + public initAccountNodesIncludeTransactions() { + if (isEmpty(this.filter.accountsIds)) { + return; + } + const childrenNodeIds = this.filter.accountsIds?.map( + (accountId: number) => { + return this.accountsGraph.dependenciesOf(accountId); + } + ); + const nodeIds = R.concat(this.filter.accountsIds, childrenNodeIds); + + this.accountNodesIncludeTransactions = uniq(flatten(nodeIds)); + } + + /** + * Initialize the account node ids should be included, + * if the filter by acounts is presented. + * @returns {void} + */ + public initAccountNodesIncluded() { + if (isEmpty(this.filter.accountsIds)) { + return; + } + const nodeIds = this.filter.accountsIds.map((accountId) => { + const childrenIds = this.accountsGraph.dependenciesOf(accountId); + const parentIds = this.accountsGraph.dependantsOf(accountId); + + return R.concat(childrenIds, parentIds); + }); + + this.accountNodeInclude = R.compose( + R.uniq, + R.flatten, + R.concat(this.filter.accountsIds) + )(nodeIds); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerService.ts new file mode 100644 index 000000000..2fe8c0959 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerService.ts @@ -0,0 +1,64 @@ +import * as moment from 'moment'; +import { GeneralLedgerMeta } from './GeneralLedgerMeta'; +import { GeneralLedgerRepository } from './GeneralLedgerRepository'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; +import { GeneralLedgerSheet } from './GeneralLedger'; +import { events } from '@/common/events/events'; +import { getGeneralLedgerReportQuery } from './_utils'; +import { + IGeneralLedgerMeta, + IGeneralLedgerSheetQuery, +} from './GeneralLedger.types'; +import { I18nService } from 'nestjs-i18n'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; + +@Injectable() +export class GeneralLedgerService { + constructor( + private readonly generalLedgerMeta: GeneralLedgerMeta, + private readonly eventEmitter: EventEmitter2, + private readonly generalLedgerRepository: GeneralLedgerRepository, + private readonly i18n: I18nService, + ) {} + + /** + * Retrieve general ledger report statement. + * @param {IGeneralLedgerSheetQuery} query + * @return {Promise} + */ + public async generalLedger(query: IGeneralLedgerSheetQuery): Promise<{ + data: any; + query: IGeneralLedgerSheetQuery; + meta: IGeneralLedgerMeta; + }> { + const filter = { + ...getGeneralLedgerReportQuery(), + ...query, + }; + this.generalLedgerRepository.setFilter(filter); + await this.generalLedgerRepository.asyncInitialize(); + + // General ledger report instance. + const generalLedgerInstance = new GeneralLedgerSheet( + filter, + this.generalLedgerRepository, + this.i18n, + ); + // Retrieve general ledger report data. + const reportData = generalLedgerInstance.reportData(); + + // Retrieve general ledger report metadata. + const meta = await this.generalLedgerMeta.meta(filter); + + // Triggers `onGeneralLedgerViewed` event. + await this.eventEmitter.emitAsync(events.reports.onGeneralLedgerViewed, { + }); + + return { + data: reportData, + query: filter, + meta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerTable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerTable.ts new file mode 100644 index 000000000..3353bfca9 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerTable.ts @@ -0,0 +1,324 @@ +import * as R from 'ramda'; +import { + IGeneralLedgerMeta, + IGeneralLedgerSheetAccount, + IGeneralLedgerSheetAccountTransaction, + IGeneralLedgerSheetData, + IGeneralLedgerSheetQuery, +} from './GeneralLedger.types'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; +import { FinancialTable } from '../../common/FinancialTable'; +import { ROW_TYPE } from './utils'; +import { + IColumnMapperMeta, + ITableColumn, + ITableColumnAccessor, + ITableRow, +} from '../../types/Table.types'; +import { tableRowMapper } from '../../utils/Table.utils'; + +export class GeneralLedgerTable extends R.compose( + FinancialTable, + FinancialSheetStructure, +)(FinancialSheet) { + private data: IGeneralLedgerSheetData; + private query: IGeneralLedgerSheetQuery; + private meta: IGeneralLedgerMeta; + + /** + * Creates an instance of `GeneralLedgerTable`. + * @param {IGeneralLedgerSheetData} data + * @param {IGeneralLedgerSheetQuery} query + */ + constructor( + data: IGeneralLedgerSheetData, + query: IGeneralLedgerSheetQuery, + meta: IGeneralLedgerMeta, + ) { + super(); + + this.data = data; + this.query = query; + this.meta = meta; + } + + /** + * Retrieves the common table accessors. + * @returns {ITableColumnAccessor[]} + */ + private accountColumnsAccessors(): ITableColumnAccessor[] { + return [ + { key: 'date', accessor: 'name' }, + { key: 'account_name', accessor: '_empty_' }, + { key: 'reference_type', accessor: '_empty_' }, + { key: 'reference_number', accessor: '_empty_' }, + { key: 'description', accessor: 'description' }, + { key: 'credit', accessor: '_empty_' }, + { key: 'debit', accessor: '_empty_' }, + { key: 'amount', accessor: 'amount.formattedAmount' }, + { key: 'running_balance', accessor: 'closingBalance.formattedAmount' }, + ]; + } + + /** + * Retrieves the transaction column accessors. + * @returns {ITableColumnAccessor[]} + */ + private transactionColumnAccessors(): ITableColumnAccessor[] { + return [ + { key: 'date', accessor: 'dateFormatted' }, + { key: 'account_name', accessor: 'account.name' }, + { key: 'reference_type', accessor: 'transactionTypeFormatted' }, + { key: 'reference_number', accessor: 'transactionNumber' }, + { key: 'description', accessor: 'note' }, + { key: 'credit', accessor: 'formattedCredit' }, + { key: 'debit', accessor: 'formattedDebit' }, + { key: 'amount', accessor: 'formattedAmount' }, + { key: 'running_balance', accessor: 'formattedRunningBalance' }, + ]; + } + + /** + * Retrieves the opening row column accessors. + * @returns {ITableRowIColumnMapperMeta[]} + */ + private openingBalanceColumnsAccessors(): IColumnMapperMeta[] { + return [ + { key: 'date', value: 'Opening Balance' }, + { key: 'account_name', value: '' }, + { key: 'reference_type', accessor: '_empty_' }, + { key: 'reference_number', accessor: '_empty_' }, + { key: 'description', accessor: 'description' }, + { key: 'credit', accessor: '_empty_' }, + { key: 'debit', accessor: '_empty_' }, + { key: 'amount', accessor: 'openingBalance.formattedAmount' }, + { key: 'running_balance', accessor: 'openingBalance.formattedAmount' }, + ]; + } + + /** + * Closing balance row column accessors. + * @param {IGeneralLedgerSheetAccount} account - + * @returns {ITableColumnAccessor[]} + */ + private closingBalanceColumnAccessors( + account: IGeneralLedgerSheetAccount, + ): IColumnMapperMeta[] { + return [ + { key: 'date', value: `Closing balance for ${account.name}` }, + { key: 'account_name', value: `` }, + { key: 'reference_type', accessor: '_empty_' }, + { key: 'reference_number', accessor: '_empty_' }, + { key: 'description', accessor: '_empty_' }, + { key: 'credit', accessor: '_empty_' }, + { key: 'debit', accessor: '_empty_' }, + { key: 'amount', accessor: 'closingBalance.formattedAmount' }, + { key: 'running_balance', accessor: 'closingBalance.formattedAmount' }, + ]; + } + + /** + * Closing balance row column accessors. + * @param {IGeneralLedgerSheetAccount} account - + * @returns {ITableColumnAccessor[]} + */ + private closingBalanceWithSubaccountsColumnAccessors( + account: IGeneralLedgerSheetAccount, + ): IColumnMapperMeta[] { + return [ + { + key: 'date', + value: `Closing Balance for ${account.name} with sub-accounts`, + }, + { + key: 'account_name', + value: ``, + }, + { key: 'reference_type', accessor: '_empty_' }, + { key: 'reference_number', accessor: '_empty_' }, + { key: 'description', accessor: '_empty_' }, + { key: 'credit', accessor: '_empty_' }, + { key: 'debit', accessor: '_empty_' }, + { key: 'amount', accessor: 'closingBalanceSubaccounts.formattedAmount' }, + { + key: 'running_balance', + accessor: 'closingBalanceSubaccounts.formattedAmount', + }, + ]; + } + + /** + * Retrieves the common table columns. + * @returns {ITableColumn[]} + */ + private commonColumns(): ITableColumn[] { + return [ + { key: 'date', label: 'Date' }, + { key: 'account_name', label: 'Account Name' }, + { key: 'reference_type', label: 'Transaction Type' }, + { key: 'reference_number', label: 'Transaction #' }, + { key: 'description', label: 'Description' }, + { key: 'credit', label: 'Credit' }, + { key: 'debit', label: 'Debit' }, + { key: 'amount', label: 'Amount' }, + { key: 'running_balance', label: 'Running Balance' }, + ]; + } + + /** + * Maps the given transaction node to table row. + * @param {IGeneralLedgerSheetAccountTransaction} transaction + * @returns {ITableRow} + */ + private transactionMapper = R.curry( + ( + account: IGeneralLedgerSheetAccount, + transaction: IGeneralLedgerSheetAccountTransaction, + ): ITableRow => { + const columns = this.transactionColumnAccessors(); + const data = { ...transaction, account }; + const meta = { + rowTypes: [ROW_TYPE.TRANSACTION], + }; + return tableRowMapper(data, columns, meta); + }, + ); + + /** + * Maps the given transactions nodes to table rows. + * @param {IGeneralLedgerSheetAccountTransaction[]} transactions + * @returns {ITableRow[]} + */ + private transactionsMapper = ( + account: IGeneralLedgerSheetAccount, + ): ITableRow[] => { + const transactionMapper = this.transactionMapper(account); + + return R.map(transactionMapper)(account.transactions); + }; + + /** + * Maps the given account node to opening balance table row. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow} + */ + private openingBalanceMapper = ( + account: IGeneralLedgerSheetAccount, + ): ITableRow => { + const columns = this.openingBalanceColumnsAccessors(); + const meta = { + rowTypes: [ROW_TYPE.OPENING_BALANCE], + }; + return tableRowMapper(account, columns, meta); + }; + + /** + * Maps the given account node to closing balance table row. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow} + */ + private closingBalanceMapper = (account: IGeneralLedgerSheetAccount) => { + const columns = this.closingBalanceColumnAccessors(account); + const meta = { + rowTypes: [ROW_TYPE.CLOSING_BALANCE], + }; + return tableRowMapper(account, columns, meta); + }; + + /** + * Maps the given account node to opening balance table row. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow} + */ + private closingBalanceWithSubaccountsMapper = ( + account: IGeneralLedgerSheetAccount, + ): ITableRow => { + const columns = this.closingBalanceWithSubaccountsColumnAccessors(account); + const meta = { + rowTypes: [ROW_TYPE.CLOSING_BALANCE], + }; + return tableRowMapper(account, columns, meta); + }; + + /** + * Maps the given account node to transactions table rows. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow[]} + */ + private transactionsNode = ( + account: IGeneralLedgerSheetAccount, + ): ITableRow[] => { + const openingBalance = this.openingBalanceMapper(account); + const transactions = this.transactionsMapper(account); + const closingBalance = this.closingBalanceMapper(account); + + return R.when( + R.always(R.not(R.isEmpty(transactions))), + R.prepend(openingBalance), + )([...transactions, closingBalance]) as ITableRow[]; + }; + + /** + * Maps the given account node to the table rows. + * @param {IGeneralLedgerSheetAccount} account + * @returns {ITableRow} + */ + private accountMapper = (account: IGeneralLedgerSheetAccount): ITableRow => { + const columns = this.accountColumnsAccessors(); + const transactions = this.transactionsNode(account); + const meta = { + rowTypes: [ROW_TYPE.ACCOUNT], + }; + const row = tableRowMapper(account, columns, meta); + const closingBalanceWithSubaccounts = + this.closingBalanceWithSubaccountsMapper(account); + + // Appends the closing balance with sub-accounts row if the account + // has children accounts and the node is define. + const isAppendClosingSubaccounts = () => + account.children?.length > 0 && !!account.closingBalanceSubaccounts; + + const children = R.compose( + R.when( + isAppendClosingSubaccounts, + R.append(closingBalanceWithSubaccounts), + ), + R.concat(R.defaultTo([], transactions)), + R.when( + () => account?.children?.length > 0, + R.concat(R.defaultTo([], account.children)), + ), + )([]); + + return R.assoc('children', children)(row); + }; + + /** + * Maps the given account node to table rows. + * @param {IGeneralLedgerSheetAccount[]} accounts + * @returns {ITableRow[]} + */ + private accountsMapper = ( + accounts: IGeneralLedgerSheetAccount[], + ): ITableRow[] => { + return this.mapNodesDeepReverse(accounts, this.accountMapper); + }; + + /** + * Retrieves the table rows. + * @returns {ITableRow[]} + */ + public tableRows(): ITableRow[] { + return R.compose(this.accountsMapper)(this.data); + } + + /** + * Retrieves the table columns. + * @returns {ITableColumn[]} + */ + public tableColumns(): ITableColumn[] { + const columns = this.commonColumns(); + return R.compose(this.tableColumnsCellIndexing)(columns); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerTableInjectable.ts new file mode 100644 index 000000000..382f8e3e2 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedgerTableInjectable.ts @@ -0,0 +1,38 @@ +import { + IGeneralLedgerSheetQuery, + IGeneralLedgerTableData, +} from './GeneralLedger.types'; +import { GeneralLedgerService } from './GeneralLedgerService'; +import { GeneralLedgerTable } from './GeneralLedgerTable'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class GeneralLedgerTableInjectable { + constructor(private readonly GLSheet: GeneralLedgerService) {} + + /** + * Retrieves the G/L table. + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public async table( + query: IGeneralLedgerSheetQuery, + ): Promise { + const { + data: sheetData, + query: sheetQuery, + meta: sheetMeta, + } = await this.GLSheet.generalLedger(query); + + const table = new GeneralLedgerTable(sheetData, sheetQuery, sheetMeta); + + return { + table: { + columns: table.tableColumns(), + rows: table.tableRows(), + }, + query: sheetQuery, + meta: sheetMeta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/_utils.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/_utils.ts new file mode 100644 index 000000000..b77182e9c --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/_utils.ts @@ -0,0 +1,28 @@ +/** + * Calculate the running balance. + * @param {number} amount - Transaction amount. + * @param {number} lastRunningBalance - Last running balance. + * @param {number} openingBalance - Opening balance. + * @return {number} Running balance. + */ +export function calculateRunningBalance( + amount: number, + lastRunningBalance: number, +): number { + return amount + lastRunningBalance; +} + +export const getGeneralLedgerReportQuery = ( +) => { + return { + fromDate: moment().startOf('month').format('YYYY-MM-DD'), + toDate: moment().format('YYYY-MM-DD'), + basis: 'cash', + numberFormat: { + noCents: false, + divideOn1000: false, + }, + noneZero: false, + accountsIds: [], + }; +}; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/constants.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/constants.ts new file mode 100644 index 000000000..9e79a81da --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/constants.ts @@ -0,0 +1,29 @@ +export const HtmlTableCustomCss = ` +table tr:last-child td { + border-bottom: 1px solid #ececec; +} +table tr.row-type--account td, +table tr.row-type--opening-balance td, +table tr.row-type--closing-balance td{ + font-weight: 600; +} +table tr.row-type--closing-balance td { + border-bottom: 1px solid #ececec; +} + +table .column--debit, +table .column--credit, +table .column--amount, +table .column--running_balance, +table .cell--debit, +table .cell--credit, +table .cell--amount, +table .cell--running_balance{ + text-align: right; +} +table tr.row-type--account .cell--date span, +table tr.row-type--opening-balance .cell--account_name span, +table tr.row-type--closing-balance .cell--account_name span{ + white-space: nowrap; +} +`; \ No newline at end of file diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/utils.ts b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/utils.ts new file mode 100644 index 000000000..07418ae37 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/GeneralLedger/utils.ts @@ -0,0 +1,6 @@ +export enum ROW_TYPE { + ACCOUNT = 'ACCOUNT', + OPENING_BALANCE = 'OPENING_BALANCE', + TRANSACTION = 'TRANSACTION', + CLOSING_BALANCE = 'CLOSING_BALANCE', +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.controller.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.controller.ts index 217e65fe2..e59c18cb6 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.controller.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.controller.ts @@ -3,14 +3,19 @@ import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; import { PurchasesByItemsApplication } from './PurchasesByItemsApplication'; import { IPurchasesByItemsReportQuery } from './types/PurchasesByItems.types'; import { AcceptType } from '@/constants/accept-type'; +import { PublicRoute } from '@/modules/Auth/Jwt.guard'; +import { ApiResponse, ApiTags } from '@nestjs/swagger'; @Controller('/reports/purchases-by-items') +@PublicRoute() +@ApiTags('reports') export class PurchasesByItemReportController { constructor( private readonly purchasesByItemsApp: PurchasesByItemsApplication, ) {} @Get() + @ApiResponse({ status: 200, description: 'Purchases by items report' }) async purchasesByItems( @Query() filter: IPurchasesByItemsReportQuery, @Res() res: Response, diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.module.ts index 10aceed8a..ea5e95ada 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.module.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.module.ts @@ -1,18 +1,25 @@ import { Module } from '@nestjs/common'; import { PurchasesByItemsTableInjectable } from './PurchasesByItemsTableInjectable'; -import { PurchasesByItemsService } from './PurchasesByItemsService'; +import { PurchasesByItemsService } from './PurchasesByItems.service'; import { PurchasesByItemsPdf } from './PurchasesByItemsPdf'; import { PurchasesByItemsExport } from './PurchasesByItemsExport'; import { PurchasesByItemsApplication } from './PurchasesByItemsApplication'; import { PurchasesByItemReportController } from './PurchasesByItems.controller'; +import { PurchasesByItemsMeta } from './PurchasesByItemsMeta'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { InventoryCostModule } from '@/modules/InventoryCost/InventoryCost.module'; +import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; @Module({ + imports: [InventoryCostModule, FinancialSheetCommonModule], providers: [ PurchasesByItemsTableInjectable, PurchasesByItemsService, PurchasesByItemsExport, PurchasesByItemsPdf, + PurchasesByItemsMeta, PurchasesByItemsApplication, + TenancyContext, ], exports: [PurchasesByItemsApplication], controllers: [PurchasesByItemReportController], diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.service.ts similarity index 74% rename from packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsService.ts rename to packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.service.ts index f2d0e7185..82959a597 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsService.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.service.ts @@ -1,4 +1,3 @@ -import moment from 'moment'; import { Inject, Injectable } from '@nestjs/common'; import { PurchasesByItems } from './PurchasesByItems'; import { @@ -11,9 +10,17 @@ import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTr import { Item } from '@/modules/Items/models/Item'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { events } from '@/common/events/events'; +import { getPurchasesByItemsDefaultQuery } from './utils'; @Injectable() export class PurchasesByItemsService { + /** + * @param {PurchasesByItemsMeta} purchasesByItemsMeta - The purchases by items meta. + * @param {EventEmitter2} eventPublisher - The event emitter. + * @param {TenancyContext} tenancyContext - The tenancy context. + * @param {typeof InventoryTransaction} inventoryTransactionModel - The inventory transaction model. + * @param {typeof Item} itemModel - The item model. + */ constructor( private readonly purchasesByItemsMeta: PurchasesByItemsMeta, private readonly eventPublisher: EventEmitter2, @@ -27,39 +34,16 @@ export class PurchasesByItemsService { ) {} /** - * Defaults purchases by items filter query. - * @return {IPurchasesByItemsReportQuery} - */ - private get defaultQuery(): IPurchasesByItemsReportQuery { - return { - fromDate: moment().startOf('month').format('YYYY-MM-DD'), - toDate: moment().format('YYYY-MM-DD'), - itemsIds: [], - numberFormat: { - precision: 2, - divideOn1000: false, - showZero: false, - formatMoney: 'always', - negativeFormat: 'mines', - }, - noneTransactions: true, - onlyActive: false, - }; - } - - /** - * Retrieve balance sheet statement. - * ------------- - * @param {number} tenantId - * @param {IPurchasesByItemsReportQuery} query - * @return {Promise} + * Retrieve purchases by items statement. + * @param {IPurchasesByItemsReportQuery} query - Purchases by items report query. + * @return {Promise} - Purchases by items sheet. */ public async purchasesByItems( query: IPurchasesByItemsReportQuery, ): Promise { - const tenant = await this.tenancyContext.getTenant(); + const tenantMetadata = await this.tenancyContext.getTenantMetadata(); const filter = { - ...this.defaultQuery, + ...getPurchasesByItemsDefaultQuery(), ...query, }; const inventoryItems = await this.itemModel.query().onBuild((q) => { @@ -88,7 +72,7 @@ export class PurchasesByItemsService { filter, inventoryItems, inventoryTransactions, - tenant.metadata.baseCurrency, + tenantMetadata.baseCurrency, ); const purchasesByItemsData = purchasesByItemsInstance.reportData(); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.ts index 5df07077e..91f4b1544 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItems.ts @@ -7,7 +7,7 @@ import { IPurchasesByItemsSheetData, IPurchasesByItemsTotal, } from './types/PurchasesByItems.types'; -import FinancialSheet from '../../common/FinancialSheet'; +import { FinancialSheet } from '../../common/FinancialSheet'; import { transformToMapBy } from '@/utils/transform-to-map-by'; import { Item } from '@/modules/Items/models/Item'; import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; @@ -48,7 +48,7 @@ export class PurchasesByItems extends FinancialSheet{ cost: number; average: number; } { - const transaction = this.itemsTransactions.get(itemId); + const transaction = this.itemsTransactions.get(itemId.toString()); const quantity = get(transaction, 'quantity', 0); const cost = get(transaction, 'cost', 0); @@ -105,14 +105,17 @@ export class PurchasesByItems extends FinancialSheet{ id: item.id, name: item.name, code: item.code, + quantityPurchased: meta.quantity, purchaseCost: meta.cost, averageCostPrice: meta.average, + quantityPurchasedFormatted: this.formatNumber(meta.quantity, { money: false, }), purchaseCostFormatted: this.formatNumber(meta.cost), averageCostPriceFormatted: this.formatNumber(meta.average), + currencyCode: this.baseCurrency, }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsApplication.ts index db4224aa6..53526ad91 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsApplication.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsApplication.ts @@ -6,11 +6,17 @@ import { IPurchasesByItemsTable, } from './types/PurchasesByItems.types'; import { PurchasesByItemsTableInjectable } from './PurchasesByItemsTableInjectable'; -import { PurchasesByItemsService } from './PurchasesByItemsService'; +import { PurchasesByItemsService } from './PurchasesByItems.service'; import { PurchasesByItemsPdf } from './PurchasesByItemsPdf'; @Injectable() export class PurchasesByItemsApplication { + /** + * @param {PurchasesByItemsService} purchasesByItemsSheetService - Purchases by items sheet service. + * @param {PurchasesByItemsTableInjectable} purchasesByItemsTableService - Purchases by items table service. + * @param {PurchasesByItemsExport} purchasesByItemsExportService - Purchases by items export service. + * @param {PurchasesByItemsPdf} purchasesByItemsPdfService - Purchases by items pdf service. + */ constructor( private readonly purchasesByItemsSheetService: PurchasesByItemsService, private readonly purchasesByItemsTableService: PurchasesByItemsTableInjectable, @@ -21,7 +27,7 @@ export class PurchasesByItemsApplication { /** * Retrieves the purchases by items in json format. * @param {IPurchasesByItemsReportQuery} query - * @returns + * @returns {Promise} */ public sheet( query: IPurchasesByItemsReportQuery, diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsPdf.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsPdf.ts index 7dbbd95d6..e26539454 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsPdf.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsPdf.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { TableSheetPdf } from '../../TableSheetPdf'; +import { TableSheetPdf } from '../../common/TableSheetPdf'; import { PurchasesByItemsTableInjectable } from './PurchasesByItemsTableInjectable'; import { IPurchasesByItemsReportQuery } from './types/PurchasesByItems.types'; import { HtmlTableCustomCss } from './_types'; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTable.ts index 2aa31092b..696c883e5 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTable.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTable.ts @@ -8,7 +8,7 @@ import { import { ITableColumn, ITableColumnAccessor, ITableRow } from '../../types/Table.types'; import { FinancialTable } from '../../common/FinancialTable'; import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; -import FinancialSheet from '../../common/FinancialSheet'; +import { FinancialSheet } from '../../common/FinancialSheet'; import { tableRowMapper } from '../../utils/Table.utils'; export class PurchasesByItemsTable extends R.compose( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTableInjectable.ts index 07b705a66..1d123f49d 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTableInjectable.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/PurchasesByItemsTableInjectable.ts @@ -2,7 +2,7 @@ import { IPurchasesByItemsReportQuery, IPurchasesByItemsTable, } from './types/PurchasesByItems.types'; -import { PurchasesByItemsService } from './PurchasesByItemsService'; +import { PurchasesByItemsService } from './PurchasesByItems.service'; import { PurchasesByItemsTable } from './PurchasesByItemsTable'; import { Injectable } from '@nestjs/common'; @@ -14,9 +14,8 @@ export class PurchasesByItemsTableInjectable { /** * Retrieves the purchases by items table format. - * @param {number} tenantId - * @param {IPurchasesByItemsReportQuery} filter - * @returns {Promise} + * @param {IPurchasesByItemsReportQuery} filter - The filter to be used. + * @returns {Promise} - The purchases by items table. */ public async table( filter: IPurchasesByItemsReportQuery, diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/types/PurchasesByItems.types.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/types/PurchasesByItems.types.ts index 648758d03..c2ced6cdf 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/types/PurchasesByItems.types.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/types/PurchasesByItems.types.ts @@ -23,15 +23,16 @@ export interface IPurchasesByItemsItem { id: number; name: string; code: string; - soldCost: number; - averageSellPrice: number; - averageSellPriceFormatted: string; + purchaseCost: number; + purchaseCostFormatted: string; + + averageCostPrice: number; + averageCostPriceFormatted: string; quantityPurchased: number; quantityPurchasedFormatted: string; - soldCostFormatted: string; currencyCode: string; } diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/utils.ts b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/utils.ts new file mode 100644 index 000000000..004373572 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/PurchasesByItems/utils.ts @@ -0,0 +1,15 @@ +import * as moment from 'moment'; +export const getPurchasesByItemsDefaultQuery = () => ({ + fromDate: moment().startOf('month').format('YYYY-MM-DD'), + toDate: moment().format('YYYY-MM-DD'), + itemsIds: [], + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'always', + negativeFormat: 'mines', + }, + noneTransactions: true, + onlyActive: false, +}); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.controller.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.controller.ts new file mode 100644 index 000000000..c9329dba4 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.controller.ts @@ -0,0 +1,57 @@ +import { Body, Controller, Get, Headers, Query, Req, Res } from '@nestjs/common'; +import { ISalesByItemsReportQuery } from './SalesByItems.types'; +import { AcceptType } from '@/constants/accept-type'; +import { SalesByItemsApplication } from './SalesByItemsApplication'; +import { Response } from 'express'; +import { ApiResponse, ApiTags } from '@nestjs/swagger'; + +@Controller('/reports/sales-by-items') +@ApiTags('reports') +export class SalesByItemsController { + constructor(private readonly salesByItemsApp: SalesByItemsApplication) {} + + @Get() + @ApiResponse({ status: 200, description: 'Sales by items report' }) + public async salesByitems( + @Query() filter: ISalesByItemsReportQuery, + @Res() res: Response, + @Headers('accept') acceptHeader: string, + ) { + // Retrieves the csv format. + if (acceptHeader.includes(AcceptType.ApplicationCsv)) { + const buffer = await this.salesByItemsApp.csv(filter); + + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); + res.setHeader('Content-Type', 'text/csv'); + + return res.send(buffer); + // Retrieves the json table format. + } else if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) { + const table = await this.salesByItemsApp.table(filter); + + return res.status(200).send(table); + // Retrieves the xlsx format. + } else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) { + const buffer = this.salesByItemsApp.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 json format. + } else if (acceptHeader.includes(AcceptType.ApplicationPdf)) { + const pdfContent = await this.salesByItemsApp.pdf(filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); + } else { + const sheet = await this.salesByItemsApp.sheet(filter); + return res.status(200).send(sheet); + } + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.module.ts new file mode 100644 index 000000000..e1e769b0d --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { SalesByItemsApplication } from './SalesByItemsApplication'; +import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; +import { SalesByItemsPdfInjectable } from './SalesByItemsPdfInjectable'; +import { SalesByItemsReportService } from './SalesByItemsService'; +import { SalesByItemsExport } from './SalesByItemsExport'; +import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; +import { SalesByItemsMeta } from './SalesByItemsMeta'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { InventoryCostModule } from '@/modules/InventoryCost/InventoryCost.module'; +import { SalesByItemsController } from './SalesByItems.controller'; + +@Module({ + providers: [ + SalesByItemsApplication, + SalesByItemsTableInjectable, + SalesByItemsPdfInjectable, + SalesByItemsReportService, + SalesByItemsExport, + SalesByItemsMeta, + TenancyContext + ], + controllers: [SalesByItemsController], + imports: [ + FinancialSheetCommonModule, + InventoryCostModule + ], +}) +export class SalesByItemsModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.ts new file mode 100644 index 000000000..8022efd34 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.ts @@ -0,0 +1,176 @@ +import { get, sumBy } from 'lodash'; +import * as R from 'ramda'; +import { + ISalesByItemsReportQuery, + ISalesByItemsItem, + ISalesByItemsTotal, + ISalesByItemsSheetData, +} from './SalesByItems.types'; +import { ModelObject } from 'objection'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { Item } from '@/modules/Items/models/Item'; +import { transformToMap } from '@/utils/transform-to-key'; +import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; +import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; + +export class SalesByItemsReport extends FinancialSheet { + readonly baseCurrency: string; + readonly items: Item[]; + readonly itemsTransactions: Map[]>; + readonly query: ISalesByItemsReportQuery; + + /** + * Constructor method. + * @param {ISalesByItemsReportQuery} query + * @param {IItem[]} items + * @param {IAccountTransaction[]} itemsTransactions + * @param {string} baseCurrency + */ + constructor( + query: ISalesByItemsReportQuery, + items: Item[], + itemsTransactions: ModelObject[], + baseCurrency: string, + ) { + super(); + + this.baseCurrency = baseCurrency; + this.items = items; + this.itemsTransactions = transformToMap(itemsTransactions, 'itemId'); + this.query = query; + this.numberFormat = this.query.numberFormat; + } + + /** + * Retrieve the item purchase item, cost and average cost price. + * @param {number} itemId - Item id. + */ + getItemTransaction(itemId: number): { + quantity: number; + cost: number; + average: number; + } { + const transaction = this.itemsTransactions.get(itemId); + + const quantity = get(transaction, 'quantity', 0); + const cost = get(transaction, 'cost', 0); + + const average = cost / quantity; + + return { quantity, cost, average }; + } + + /** + * Mapping the given item section. + * @param {ISalesByItemsItem} item + * @returns + */ + private itemSectionMapper = (item: Item): ISalesByItemsItem => { + const meta = this.getItemTransaction(item.id); + + return { + id: item.id, + name: item.name, + code: item.code, + quantitySold: meta.quantity, + soldCost: meta.cost, + averageSellPrice: meta.average, + quantitySoldFormatted: this.formatNumber(meta.quantity, { + money: false, + }), + soldCostFormatted: this.formatNumber(meta.cost), + averageSellPriceFormatted: this.formatNumber(meta.average), + currencyCode: this.baseCurrency, + }; + }; + + /** + * Detarmines whether the given sale node is has transactions. + * @param {ISalesByItemsItem} node - + * @returns {boolean} + */ + private filterSaleNoneTransactions = (node: ISalesByItemsItem) => { + return this.itemsTransactions.get(node.id); + }; + + /** + * Detarmines whether the given sale by item node is active. + * @param {ISalesByItemsItem} node + * @returns {boolean} + */ + private filterSaleOnlyActive = (node: ISalesByItemsItem): boolean => { + return node.quantitySold !== 0 || node.soldCost !== 0; + }; + + /** + * Filters sales by items nodes based on the report query. + * @param {ISalesByItemsItem} saleItem - + * @return {boolean} + */ + private itemSaleFilter = (saleItem: ISalesByItemsItem): boolean => { + const { noneTransactions, onlyActive } = this.query; + + const conditions = [ + [noneTransactions, this.filterSaleNoneTransactions], + [onlyActive, this.filterSaleOnlyActive], + ]; + return allPassedConditionsPass(conditions)(saleItem); + }; + + /** + * Mappes the given items to sales by items nodes. + * @param {IItem[]} items - + * @returns {ISalesByItemsItem[]} + */ + private itemsMapper = (items: Item[]): ISalesByItemsItem[] => { + return items.map(this.itemSectionMapper); + }; + + /** + * Filters sales by items sections. + * @param items + * @returns + */ + private itemsFilters = (nodes: ISalesByItemsItem[]): ISalesByItemsItem[] => { + return nodes.filter(this.itemSaleFilter); + }; + + /** + * Retrieve the items sections. + * @returns {ISalesByItemsItem[]} + */ + private itemsSection(): ISalesByItemsItem[] { + return R.compose(this.itemsFilters, this.itemsMapper)(this.items); + } + + /** + * Retrieve the total section of the sheet. + * @param {IInventoryValuationItem[]} items + * @returns {IInventoryValuationTotal} + */ + private totalSection(items: ISalesByItemsItem[]): ISalesByItemsTotal { + const quantitySold = sumBy(items, (item) => item.quantitySold); + const soldCost = sumBy(items, (item) => item.soldCost); + + return { + quantitySold, + soldCost, + quantitySoldFormatted: this.formatTotalNumber(quantitySold, { + money: false, + }), + soldCostFormatted: this.formatTotalNumber(soldCost), + currencyCode: this.baseCurrency, + }; + } + + /** + * Retrieve the sheet data. + * @returns {ISalesByItemsSheetData} + */ + public reportData(): ISalesByItemsSheetData { + const items = this.itemsSection(); + const total = this.totalSection(items); + + return { items, total }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.types.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.types.ts new file mode 100644 index 000000000..93163a3f3 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItems.types.ts @@ -0,0 +1,56 @@ +import { IFinancialSheetCommonMeta } from "../../types/Report.types"; +import { INumberFormatQuery } from "../../types/Report.types"; +import { IFinancialTable } from "../../types/Table.types"; + +export interface ISalesByItemsReportQuery { + fromDate: Date | string; + toDate: Date | string; + itemsIds: number[]; + numberFormat: INumberFormatQuery; + noneTransactions: boolean; + onlyActive: boolean; +} + +export interface ISalesByItemsSheetMeta extends IFinancialSheetCommonMeta { + formattedFromDate: string; + formattedToDate: string; + formattedDateRange: string; +} + +export interface ISalesByItemsItem { + id: number; + name: string; + code: string; + quantitySold: number; + soldCost: number; + averageSellPrice: number; + + quantitySoldFormatted: string; + soldCostFormatted: string; + averageSellPriceFormatted: string; + currencyCode: string; +} + +export interface ISalesByItemsTotal { + quantitySold: number; + soldCost: number; + quantitySoldFormatted: string; + soldCostFormatted: string; + currencyCode: string; +} + +export type ISalesByItemsSheetData = { + items: ISalesByItemsItem[]; + total: ISalesByItemsTotal; +}; + +export interface ISalesByItemsSheet { + data: ISalesByItemsSheetData; + query: ISalesByItemsReportQuery; + meta: ISalesByItemsSheetMeta; +} + +export interface ISalesByItemsTable extends IFinancialTable { + query: ISalesByItemsReportQuery; + meta: ISalesByItemsSheetMeta; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsApplication.ts new file mode 100644 index 000000000..c47ccfebf --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsApplication.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { + ISalesByItemsReportQuery, + ISalesByItemsSheet, + ISalesByItemsTable, +} from './SalesByItems.types'; +import { SalesByItemsReportService } from './SalesByItemsService'; +import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; +import { SalesByItemsExport } from './SalesByItemsExport'; +import { SalesByItemsPdfInjectable } from './SalesByItemsPdfInjectable'; + +@Injectable() +export class SalesByItemsApplication { + constructor( + private readonly salesByItemsSheet: SalesByItemsReportService, + private readonly salesByItemsTable: SalesByItemsTableInjectable, + private readonly salesByItemsExport: SalesByItemsExport, + private readonly salesByItemsPdf: SalesByItemsPdfInjectable, + ) {} + + /** + * Retrieves the sales by items report in json format. + * @param {ISalesByItemsReportQuery} filter - Sales by items report query. + * @returns {Promise} + */ + public sheet( + filter: ISalesByItemsReportQuery, + ): Promise { + return this.salesByItemsSheet.salesByItems(filter); + } + + /** + * Retrieves the sales by items report in table format. + * @param {ISalesByItemsReportQuery} filter - Sales by items report query. + * @returns {Promise} + */ + public table( + filter: ISalesByItemsReportQuery, + ): Promise { + return this.salesByItemsTable.table(filter); + } + + /** + * Retrieves the sales by items report in csv format. + * @param {ISalesByItemsReportQuery} filter - Sales by items report query. + * @returns {Promise} + */ + public csv( + filter: ISalesByItemsReportQuery, + ): Promise { + return this.salesByItemsExport.csv(filter); + } + + /** + * Retrieves the sales by items report in xlsx format. + * @param {ISalesByItemsReportQuery} filter - Sales by items report query. + * @returns {Promise} + */ + public xlsx( + filter: ISalesByItemsReportQuery, + ): Promise { + return this.salesByItemsExport.xlsx(filter); + } + + /** + * Retrieves the sales by items in pdf format. + * @param {ISalesByItemsReportQuery} filter - Sales by items report query. + * @returns {Promise} + */ + public pdf( + query: ISalesByItemsReportQuery, + ): Promise { + return this.salesByItemsPdf.pdf(query); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsExport.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsExport.ts new file mode 100644 index 000000000..a9637befd --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsExport.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { TableSheet } from '../../common/TableSheet'; +import { ISalesByItemsReportQuery } from './SalesByItems.types'; +import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; + +@Injectable() +export class SalesByItemsExport { + constructor( + private readonly salesByItemsTable: SalesByItemsTableInjectable, + ) {} + + /** + * Retrieves the trial balance sheet in XLSX format. + * @param {ISalesByItemsReportQuery} query - Sales by items report query. + * @returns {Promise} + */ + public async xlsx(query: ISalesByItemsReportQuery) { + const table = await this.salesByItemsTable.table(query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToXLSX(); + + return tableSheet.convertToBuffer(tableCsv, 'xlsx'); + } + + /** + * Retrieves the trial balance sheet in CSV format. + * @param {ISalesByItemsReportQuery} query - Sales by items report query. + * @returns {Promise} + */ + public async csv(query: ISalesByItemsReportQuery): Promise { + const table = await this.salesByItemsTable.table(query); + + const tableSheet = new TableSheet(table.table); + const tableCsv = tableSheet.convertToCSV(); + + return tableCsv; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsMeta.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsMeta.ts new file mode 100644 index 000000000..40a8827c1 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsMeta.ts @@ -0,0 +1,36 @@ +import moment from 'moment'; +import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; +import { ISalesByItemsReportQuery, ISalesByItemsSheetMeta } from './SalesByItems.types'; +import { Injectable } from '@nestjs/common'; +import { I18nService } from 'nestjs-i18n'; + +@Injectable() +export class SalesByItemsMeta { + constructor( + private financialSheetMeta: FinancialSheetMeta, + private i18n: I18nService, + ) {} + + /** + * Retrieve the sales by items meta. + * @returns {IBalanceSheetMeta} + */ + public async meta( + query: ISalesByItemsReportQuery + ): 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 = 'Sales By Items'; + + return { + ...commonMeta, + sheetName, + formattedFromDate, + formattedToDate, + formattedDateRange, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsPdfInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsPdfInjectable.ts new file mode 100644 index 000000000..eb8dc4845 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsPdfInjectable.ts @@ -0,0 +1,31 @@ +import { ISalesByItemsReportQuery } from './SalesByItems.types'; +import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; +import { TableSheetPdf } from '../../common/TableSheetPdf'; +import { HtmlTableCustomCss } from './constants'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SalesByItemsPdfInjectable { + constructor( + private readonly salesByItemsTable: SalesByItemsTableInjectable, + private readonly tableSheetPdf: TableSheetPdf, + ) {} + + /** + * Retrieves the sales by items sheet in pdf format. + * @param {ISalesByItemsReportQuery} query - The query to apply to the report. + * @returns {Promise} + */ + public async pdf( + query: ISalesByItemsReportQuery, + ): Promise { + const table = await this.salesByItemsTable.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/SalesByItems/SalesByItemsService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsService.ts new file mode 100644 index 000000000..7efc64f39 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsService.ts @@ -0,0 +1,88 @@ +import { SalesByItemsMeta } from './SalesByItemsMeta'; +import { getSalesByItemsDefaultQuery } from './utils'; +import { Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { + ISalesByItemsReportQuery, + ISalesByItemsSheet, +} from './SalesByItems.types'; +import { Item } from '@/modules/Items/models/Item'; +import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; +import { events } from '@/common/events/events'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { SalesByItemsReport } from './SalesByItems'; + +@Injectable() +export class SalesByItemsReportService { + constructor( + private readonly salesByItemsMeta: SalesByItemsMeta, + private readonly eventPublisher: EventEmitter2, + private readonly tenancyContext: TenancyContext, + + @Inject(Item.name) + private readonly itemModel: typeof Item, + + @Inject(InventoryTransaction.name) + private readonly inventoryTransactionModel: typeof InventoryTransaction, + ) {} + + /** + * Retrieve balance sheet statement. + * @param {ISalesByItemsReportQuery} query - The sales by items report query. + * @return {Promise} + */ + public async salesByItems( + query: ISalesByItemsReportQuery, + ): Promise { + const filter = { + ...getSalesByItemsDefaultQuery(), + ...query, + }; + const tenantMetadata = await this.tenancyContext.getTenantMetadata(); + + // Inventory items for sales report. + const inventoryItems = await this.itemModel.query().onBuild((q) => { + q.where('type', 'inventory'); + + if (filter.itemsIds.length > 0) { + q.whereIn('id', filter.itemsIds); + } + }); + const inventoryItemsIds = inventoryItems.map((item) => item.id); + + // Calculates the total inventory total quantity and rate `IN` transactions. + const inventoryTransactions = await this.inventoryTransactionModel.query().onBuild( + (builder: any) => { + builder.modify('itemsTotals'); + builder.modify('OUTDirection'); + + // Filter the inventory items only. + builder.whereIn('itemId', inventoryItemsIds); + + // Filter the date range of the sheet. + builder.modify('filterDateRange', filter.fromDate, filter.toDate); + }, + ); + const sheet = new SalesByItemsReport( + filter, + inventoryItems, + inventoryTransactions, + tenantMetadata.baseCurrency, + ); + const salesByItemsData = sheet.reportData(); + + // Retrieve the sales by items meta. + const meta = await this.salesByItemsMeta.meta(query); + + // Triggers `onSalesByItemViewed` event. + await this.eventPublisher.emitAsync(events.reports.onSalesByItemViewed, { + query, + }); + + return { + data: salesByItemsData, + query: filter, + meta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsTable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsTable.ts new file mode 100644 index 000000000..6691873d5 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsTable.ts @@ -0,0 +1,102 @@ +import * as R from 'ramda'; +import { + ISalesByItemsItem, + ISalesByItemsTotal, +} from './SalesByItems.types'; +import { ROW_TYPE } from './constants'; +import { FinancialTable } from '../../common/FinancialTable'; +import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { ITableColumn, ITableRow } from '../../types/Table.types'; +import { tableRowMapper } from '../../utils/Table.utils'; + +export class SalesByItemsTable extends R.compose( + FinancialTable, + FinancialSheetStructure +)(FinancialSheet) { + private readonly data: ISalesByItemsSheetStatement; + + /** + * Constructor method. + * @param {ISalesByItemsSheetStatement} data + */ + constructor(data: ISalesByItemsSheetStatement) { + super(); + this.data = data; + } + + /** + * Retrieves the common table accessors. + * @returns {ITableColumn[]} + */ + private commonTableAccessors() { + return [ + { key: 'item_name', accessor: 'name' }, + { key: 'sold_quantity', accessor: 'quantitySoldFormatted' }, + { key: 'sold_amount', accessor: 'soldCostFormatted' }, + { key: 'average_price', accessor: 'averageSellPriceFormatted' }, + ]; + } + + /** + * Maps the given item node to table row. + * @param {ISalesByItemsItem} item + * @returns {ITableRow} + */ + private itemMap = (item: ISalesByItemsItem): ITableRow => { + const columns = this.commonTableAccessors(); + const meta = { + rowTypes: [ROW_TYPE.ITEM], + }; + return tableRowMapper(item, columns, meta); + }; + + /** + * Maps the given items nodes to table rows. + * @param {ISalesByItemsItem[]} items + * @returns {ITableRow[]} + */ + private itemsMap = (items: ISalesByItemsItem[]): ITableRow[] => { + return R.map(this.itemMap, items); + }; + + /** + * Maps the given total node to table row. + * @param {ISalesByItemsTotal} total + * @returns {ITableRow[]} + */ + private totalMap = (total: ISalesByItemsTotal) => { + const columns = this.commonTableAccessors(); + const meta = { + rowTypes: [ROW_TYPE.TOTAL], + }; + return tableRowMapper(total, columns, meta); + }; + + /** + * Retrieves the table rows. + * @returns {ITableRow[]} + */ + public tableData(): ITableRow[] { + const itemsRows = this.itemsMap(this.data.items); + const totalRow = this.totalMap(this.data.total); + + return R.compose( + R.when(R.always(R.not(R.isEmpty(itemsRows))), R.append(totalRow)) + )([...itemsRows]) as ITableRow[]; + } + + /** + * Retrieves the table columns. + * @returns {ITableColumn[]} + */ + public tableColumns(): ITableColumn[] { + const columns = [ + { key: 'item_name', label: 'Item name' }, + { key: 'sold_quantity', label: 'Sold quantity' }, + { key: 'sold_amount', label: 'Sold amount' }, + { key: 'average_price', label: 'Average price' }, + ]; + return R.compose(this.tableColumnsCellIndexing)(columns); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsTableInjectable.ts new file mode 100644 index 000000000..eee83aadb --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/SalesByItemsTableInjectable.ts @@ -0,0 +1,29 @@ +import { ISalesByItemsReportQuery } from './SalesByItems.types'; +import { SalesByItemsReportService } from './SalesByItemsService'; +import { SalesByItemsTable } from './SalesByItemsTable'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SalesByItemsTableInjectable { + constructor(private readonly salesByItemSheet: SalesByItemsReportService) {} + + /** + * Retrieves the sales by items report in table format. + * @param {ISalesByItemsReportQuery} filter - The filter to apply to the report. + * @returns {Promise} + */ + public async table(filter: ISalesByItemsReportQuery) { + const { data, query, meta } = + await this.salesByItemSheet.salesByItems(filter); + const table = new SalesByItemsTable(data); + + return { + table: { + columns: table.tableColumns(), + rows: table.tableData(), + }, + meta, + query, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/constants.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/constants.ts new file mode 100644 index 000000000..761e37ec5 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/constants.ts @@ -0,0 +1,23 @@ +export enum ROW_TYPE { + ITEM = 'ITEM', + TOTAL = 'TOTAL', +} + +export const HtmlTableCustomCss = ` +table tr.row-type--total td { + border-top: 1px solid #bbb; + border-bottom: 3px double #000; + font-weight: 600; +} +table .column--item_name{ + width: 300px; +} +table .column--average_price, +table .column--sold_quantity, +table .column--sold_amount, +table .cell--average_price, +table .cell--sold_quantity, +table .cell--sold_amount{ + text-align: right; +} +`; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/utils.ts b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/utils.ts new file mode 100644 index 000000000..b041c5d27 --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/SalesByItems/utils.ts @@ -0,0 +1,16 @@ +export const getSalesByItemsDefaultQuery = () => { + return { + fromDate: moment().startOf('month').format('YYYY-MM-DD'), + toDate: moment().format('YYYY-MM-DD'), + itemsIds: [], + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'always', + negativeFormat: 'mines', + }, + noneTransactions: true, + onlyActive: false, + }; +}; diff --git a/packages/server-nest/src/modules/FinancialStatements/types/Report.types.ts b/packages/server-nest/src/modules/FinancialStatements/types/Report.types.ts index 232aa7e3b..33f3d2d56 100644 --- a/packages/server-nest/src/modules/FinancialStatements/types/Report.types.ts +++ b/packages/server-nest/src/modules/FinancialStatements/types/Report.types.ts @@ -70,3 +70,44 @@ export interface IDateRange { fromDate: Date; toDate: Date; } + +interface FinancialDateMeta { + date: Date; + formattedDate: string; +} + +interface IFinancialSheetTotal { + amount: number; + formattedAmount: string; + currencyCode: string; +} + +interface IFinancialSheetPercentage { + amount: number; + formattedAmount: string; +} + +export interface IFinancialNodeWithPreviousPeriod { + previousPeriodFromDate?: FinancialDateMeta; + previousPeriodToDate?: FinancialDateMeta; + + previousPeriod?: IFinancialSheetTotal; + previousPeriodChange?: IFinancialSheetTotal; + previousPeriodPercentage?: IFinancialSheetPercentage; +} +export interface IFinancialNodeWithPreviousYear { + previousYearFromDate: FinancialDateMeta; + previousYearToDate: FinancialDateMeta; + + previousYear?: IFinancialSheetTotal; + previousYearChange?: IFinancialSheetTotal; + previousYearPercentage?: IFinancialSheetPercentage; +} +export interface IFinancialCommonNode { + total: IFinancialSheetTotal; +} +export interface IFinancialCommonHorizDatePeriodNode { + fromDate: FinancialDateMeta; + toDate: FinancialDateMeta; + total: IFinancialSheetTotal; +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/FinancialStatements/types/Table.types.ts b/packages/server-nest/src/modules/FinancialStatements/types/Table.types.ts index a567d2d92..99fef5dbd 100644 --- a/packages/server-nest/src/modules/FinancialStatements/types/Table.types.ts +++ b/packages/server-nest/src/modules/FinancialStatements/types/Table.types.ts @@ -11,6 +11,8 @@ export interface ITableCell { export type ITableRow = { cells: ITableCell[]; + rowTypes?: Array + id?: string; }; export interface ITableColumn { @@ -38,3 +40,9 @@ export interface ITableData { export interface IFinancialTable { table: ITableData; } + +export interface IFinancialTableTotal { + amount: number; + formattedAmount: string; + currencyCode: string; +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/FinancialStatements/utils.ts b/packages/server-nest/src/modules/FinancialStatements/utils.ts index 5d304ca6f..9a5d15c72 100644 --- a/packages/server-nest/src/modules/FinancialStatements/utils.ts +++ b/packages/server-nest/src/modules/FinancialStatements/utils.ts @@ -1,19 +1,19 @@ import { kebabCase } from 'lodash'; -import { ITableRow } from '@/interfaces'; +import { ITableRow } from './types/Table.types'; export const formatNumber = (balance, { noCents, divideOn1000 }): string => { let formattedBalance: number = parseFloat(balance); if (noCents) { - formattedBalance = parseInt(formattedBalance, 10); + formattedBalance = parseInt(formattedBalance.toString(), 10); } if (divideOn1000) { formattedBalance /= 1000; } - return formattedBalance; + return formattedBalance.toString(); }; -export const tableClassNames = (rows: ITableRow[]) => { +export const tableClassNames = (rows: ITableRow[]): ITableRow[] => { return rows.map((row) => { const classNames = row?.rowTypes?.map((rowType) => `row-type--${kebabCase(rowType)}`) || []; diff --git a/packages/server-nest/src/modules/Import/ImportFileDataTransformer.ts b/packages/server-nest/src/modules/Import/ImportFileDataTransformer.ts index 797d0aea3..fe8505c22 100644 --- a/packages/server-nest/src/modules/Import/ImportFileDataTransformer.ts +++ b/packages/server-nest/src/modules/Import/ImportFileDataTransformer.ts @@ -110,8 +110,8 @@ export class ImportFileDataTransformer { valueDTOs: Record[], trx?: Knex.Transaction ): Promise[]> { - const tenantModels = this.tenancy.models(tenantId); - const _valueParser = valueParser(fields, tenantModels, trx); + // const tenantModels = this.tenancy.models(tenantId); + const _valueParser = valueParser(fields, {}, trx); const _keyParser = parseKey(fields); const parseAsync = async (valueDTO) => { diff --git a/packages/server-nest/src/modules/Import/ImportableResources.ts b/packages/server-nest/src/modules/Import/ImportableResources.ts index b6b61423b..2a9bf0d35 100644 --- a/packages/server-nest/src/modules/Import/ImportableResources.ts +++ b/packages/server-nest/src/modules/Import/ImportableResources.ts @@ -1,24 +1,24 @@ -import Container, { Service } from 'typedi'; -import { AccountsImportable } from '../Accounts/AccountsImportable'; +// import { AccountsImportable } from '../Accounts/AccountsImportable'; +import { Injectable } from '@nestjs/common'; import { ImportableRegistry } from './ImportableRegistry'; -import { UncategorizedTransactionsImportable } from '../BankingCategorize/commands/UncategorizedTransactionsImportable'; -import { CustomersImportable } from '../Contacts/Customers/CustomersImportable'; -import { VendorsImportable } from '../Contacts/Vendors/VendorsImportable'; -import { ItemsImportable } from '../Items/ItemsImportable'; -import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable'; -import { ManualJournalImportable } from '../ManualJournals/commands/ManualJournalsImport'; -import { BillsImportable } from '../Purchases/Bills/BillsImportable'; -import { ExpensesImportable } from '../Expenses/ExpensesImportable'; -import { SaleInvoicesImportable } from '../Sales/Invoices/SaleInvoicesImportable'; -import { SaleEstimatesImportable } from '../Sales/Estimates/SaleEstimatesImportable'; -import { BillPaymentsImportable } from '../Purchases/BillPayments/BillPaymentsImportable'; -import { VendorCreditsImportable } from '../Purchases/VendorCredits/VendorCreditsImportable'; -import { PaymentsReceivedImportable } from '../Sales/PaymentReceived/PaymentsReceivedImportable'; -import { CreditNotesImportable } from '../CreditNotes/commands/CreditNotesImportable'; -import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable'; -import { TaxRatesImportable } from '../TaxRates/TaxRatesImportable'; +// import { UncategorizedTransactionsImportable } from '../BankingCategorize/commands/UncategorizedTransactionsImportable'; +// import { CustomersImportable } from '../Contacts/Customers/CustomersImportable'; +// import { VendorsImportable } from '../Contacts/Vendors/VendorsImportable'; +// import { ItemsImportable } from '../Items/ItemsImportable'; +// import { ItemCategoriesImportable } from '../ItemCategories/ItemCategoriesImportable'; +// import { ManualJournalImportable } from '../ManualJournals/commands/ManualJournalsImport'; +// import { BillsImportable } from '../Purchases/Bills/BillsImportable'; +// import { ExpensesImportable } from '../Expenses/ExpensesImportable'; +// import { SaleInvoicesImportable } from '../Sales/Invoices/SaleInvoicesImportable'; +// import { SaleEstimatesImportable } from '../Sales/Estimates/SaleEstimatesImportable'; +// import { BillPaymentsImportable } from '../Purchases/BillPayments/BillPaymentsImportable'; +// import { VendorCreditsImportable } from '../Purchases/VendorCredits/VendorCreditsImportable'; +// import { PaymentsReceivedImportable } from '../Sales/PaymentReceived/PaymentsReceivedImportable'; +// import { CreditNotesImportable } from '../CreditNotes/commands/CreditNotesImportable'; +// import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable'; +// import { TaxRatesImportable } from '../TaxRates/TaxRatesImportable'; -@Service() +@Injectable() export class ImportableResources { private static registry: ImportableRegistry; @@ -30,26 +30,26 @@ export class ImportableResources { * Importable instances. */ private importables = [ - { resource: 'Account', importable: AccountsImportable }, - { - resource: 'UncategorizedCashflowTransaction', - importable: UncategorizedTransactionsImportable, - }, - { resource: 'Customer', importable: CustomersImportable }, - { resource: 'Vendor', importable: VendorsImportable }, - { resource: 'Item', importable: ItemsImportable }, - { resource: 'ItemCategory', importable: ItemCategoriesImportable }, - { resource: 'ManualJournal', importable: ManualJournalImportable }, - { resource: 'Bill', importable: BillsImportable }, - { resource: 'Expense', importable: ExpensesImportable }, - { resource: 'SaleInvoice', importable: SaleInvoicesImportable }, - { resource: 'SaleEstimate', importable: SaleEstimatesImportable }, - { resource: 'BillPayment', importable: BillPaymentsImportable }, - { resource: 'PaymentReceive', importable: PaymentsReceivedImportable }, - { resource: 'VendorCredit', importable: VendorCreditsImportable }, - { resource: 'CreditNote', importable: CreditNotesImportable }, - { resource: 'SaleReceipt', importable: SaleReceiptsImportable }, - { resource: 'TaxRate', importable: TaxRatesImportable }, + // { resource: 'Account', importable: AccountsImportable }, + // { + // resource: 'UncategorizedCashflowTransaction', + // importable: UncategorizedTransactionsImportable, + // }, + // { resource: 'Customer', importable: CustomersImportable }, + // { resource: 'Vendor', importable: VendorsImportable }, + // { resource: 'Item', importable: ItemsImportable }, + // { resource: 'ItemCategory', importable: ItemCategoriesImportable }, + // { resource: 'ManualJournal', importable: ManualJournalImportable }, + // { resource: 'Bill', importable: BillsImportable }, + // { resource: 'Expense', importable: ExpensesImportable }, + // { resource: 'SaleInvoice', importable: SaleInvoicesImportable }, + // { resource: 'SaleEstimate', importable: SaleEstimatesImportable }, + // { resource: 'BillPayment', importable: BillPaymentsImportable }, + // { resource: 'PaymentReceive', importable: PaymentsReceivedImportable }, + // { resource: 'VendorCredit', importable: VendorCreditsImportable }, + // { resource: 'CreditNote', importable: CreditNotesImportable }, + // { resource: 'SaleReceipt', importable: SaleReceiptsImportable }, + // { resource: 'TaxRate', importable: TaxRatesImportable }, ]; public get registry() { @@ -64,8 +64,8 @@ export class ImportableResources { const instance = ImportableRegistry.getInstance(); this.importables.forEach((importable) => { - const importableInstance = Container.get(importable.importable); - instance.registerImportable(importable.resource, importableInstance); + // const importableInstance = Container.get(importable.importable); + // instance.registerImportable(importable.resource, importableInstance); }); ImportableResources.registry = instance; } diff --git a/packages/server-nest/src/modules/Import/_utils.ts b/packages/server-nest/src/modules/Import/_utils.ts index c3127f110..df3ff1a15 100644 --- a/packages/server-nest/src/modules/Import/_utils.ts +++ b/packages/server-nest/src/modules/Import/_utils.ts @@ -336,7 +336,7 @@ export const valueParser = * @param {string} key - Mapped key path. formats: `group.key` or `key`. * @returns {string} */ -export const parseKey = R.curry( +export const parseKey: R.Curry = R.curry( (fields: { [key: string]: IModelMetaField2 }, key: string) => { const fieldKey = getFieldKey(key); const field = fields[fieldKey]; diff --git a/packages/server-nest/src/modules/Import/jobs/ImportDeleteExpiredFilesJob.ts b/packages/server-nest/src/modules/Import/jobs/ImportDeleteExpiredFilesJob.ts index 74ce6a7c8..849703546 100644 --- a/packages/server-nest/src/modules/Import/jobs/ImportDeleteExpiredFilesJob.ts +++ b/packages/server-nest/src/modules/Import/jobs/ImportDeleteExpiredFilesJob.ts @@ -1,28 +1,28 @@ -import Container, { Service } from 'typedi'; -import { ImportDeleteExpiredFiles } from '../ImportRemoveExpiredFiles'; +// import Container, { Service } from 'typedi'; +// import { ImportDeleteExpiredFiles } from '../ImportRemoveExpiredFiles'; -@Service() -export class ImportDeleteExpiredFilesJobs { - /** - * Constructor method. - */ - constructor(agenda) { - agenda.define('delete-expired-imported-files', this.handler); - } +// @Service() +// export class ImportDeleteExpiredFilesJobs { +// /** +// * Constructor method. +// */ +// constructor(agenda) { +// agenda.define('delete-expired-imported-files', this.handler); +// } - /** - * Triggers sending invoice mail. - */ - private handler = async (job, done: Function) => { - const importDeleteExpiredFiles = Container.get(ImportDeleteExpiredFiles); +// /** +// * Triggers sending invoice mail. +// */ +// private handler = async (job, done: Function) => { +// const importDeleteExpiredFiles = Container.get(ImportDeleteExpiredFiles); - try { - console.log('Delete expired import files has started.'); - await importDeleteExpiredFiles.deleteExpiredFiles(); - done(); - } catch (error) { - console.log(error); - done(error); - } - }; -} +// try { +// console.log('Delete expired import files has started.'); +// await importDeleteExpiredFiles.deleteExpiredFiles(); +// done(); +// } catch (error) { +// console.log(error); +// done(error); +// } +// }; +// } diff --git a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts index 80a097e86..ddd592db1 100644 --- a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts +++ b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts @@ -1,7 +1,6 @@ import { Model, raw } from 'objection'; import { castArray } from 'lodash'; -import moment, { unitOfTime } from 'moment'; -import { BaseModel } from '@/models/Model'; +import * as moment from 'moment'; import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils'; import { TInventoryTransactionDirection } from '../types/InventoryCost.types'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; @@ -53,7 +52,7 @@ export class InventoryTransaction extends TenantBaseModel { query, startDate, endDate, - type: unitOfTime.StartOf = 'day', + type: moment.unitOfTime.StartOf = 'day', ) { const dateFormat = 'YYYY-MM-DD'; const fromDate = moment(startDate).startOf(type).format(dateFormat); diff --git a/packages/server-nest/src/modules/Ledger/Ledger.ts b/packages/server-nest/src/modules/Ledger/Ledger.ts index 8824ac143..0fe2a173f 100644 --- a/packages/server-nest/src/modules/Ledger/Ledger.ts +++ b/packages/server-nest/src/modules/Ledger/Ledger.ts @@ -4,6 +4,7 @@ import { ILedger } from './types/Ledger.types'; import { ILedgerEntry } from './types/Ledger.types'; import { AccountTransaction } from '../Accounts/models/AccountTransaction.model'; import { IAccountTransaction } from '@/interfaces/Account'; +import { ModelObject } from 'objection'; export class Ledger implements ILedger { readonly entries: ILedgerEntry[]; @@ -71,7 +72,8 @@ export class Ledger implements ILedger { return this.filter( (entry) => - fromDateParsed.isBefore(entry.date) || fromDateParsed.isSame(entry.date) + fromDateParsed.isBefore(entry.date) || + fromDateParsed.isSame(entry.date), ); } @@ -85,7 +87,7 @@ export class Ledger implements ILedger { return this.filter( (entry) => - toDateParsed.isAfter(entry.date) || toDateParsed.isSame(entry.date) + toDateParsed.isAfter(entry.date) || toDateParsed.isSame(entry.date), ); } @@ -191,7 +193,7 @@ export class Ledger implements ILedger { */ public getAccountsIds = (): number[] => { return uniqBy(this.entries, 'accountId').map( - (e: ILedgerEntry) => e.accountId + (e: ILedgerEntry) => e.accountId, ); }; @@ -222,22 +224,21 @@ export class Ledger implements ILedger { // --------------------------------- // # STATIC METHODS. // ---------------------------------- - /** * Mappes the account transactions to ledger entries. * @param {IAccountTransaction[]} entries * @returns {ILedgerEntry[]} */ - static mappingTransactions(entries: AccountTransaction[]): ILedgerEntry[] { + static mappingTransactions(entries: ModelObject[]): ILedgerEntry[] { return entries.map(this.mapTransaction); } /** * Mappes the account transaction to ledger entry. - * @param {IAccountTransaction} entry + * @param {IAccountTransaction} entry - Account transaction. * @returns {ILedgerEntry} */ - static mapTransaction(entry: AccountTransaction): ILedgerEntry { + static mapTransaction(entry: ModelObject): ILedgerEntry { return { credit: defaultTo(entry.credit, 0), debit: defaultTo(entry.debit, 0), @@ -277,7 +278,9 @@ export class Ledger implements ILedger { * @param {IAccountTransaction[]} transactions * @returns {ILedger} */ - static fromTransactions(transactions: AccountTransaction[]): Ledger { + static fromTransactions( + transactions: Array>, + ): Ledger { const entries = Ledger.mappingTransactions(transactions); return new Ledger(entries); } diff --git a/packages/server-nest/src/utils/accum-sum.ts b/packages/server-nest/src/utils/accum-sum.ts new file mode 100644 index 000000000..c5eb0f62d --- /dev/null +++ b/packages/server-nest/src/utils/accum-sum.ts @@ -0,0 +1,7 @@ + +export const accumSum = (data: any[], callback: (data: any) => number): number => { + return data.reduce((acc, _data) => { + const amount = callback(_data); + return acc + amount; + }, 0); +}; diff --git a/packages/server-nest/src/utils/date-range-collection.ts b/packages/server-nest/src/utils/date-range-collection.ts index 3e0d3908d..e78163f79 100644 --- a/packages/server-nest/src/utils/date-range-collection.ts +++ b/packages/server-nest/src/utils/date-range-collection.ts @@ -4,7 +4,7 @@ export const dateRangeCollection = ( fromDate, toDate, addType: moment.unitOfTime.StartOf = 'day', - increment = 1, + increment: number = 1, ) => { const collection = []; const momentFromDate = moment(fromDate); @@ -26,7 +26,7 @@ export const dateRangeCollection = ( for ( let i = momentFromDate; i.isBefore(toDate, addType) || i.isSame(toDate, addType); - i.add(increment, `${addType}s`) + i.add(increment, `${addType}s` as moment.unitOfTime.DurationConstructor) ) { collection.push(i.endOf(addType).format(dateFormat)); } @@ -37,7 +37,7 @@ export const dateRangeFromToCollection = ( fromDate: moment.MomentInput, toDate: moment.MomentInput, addType: moment.unitOfTime.StartOf = 'day', - increment = 1, + increment: number = 1, ) => { const collection = []; const momentFromDate = moment(fromDate); @@ -46,7 +46,7 @@ export const dateRangeFromToCollection = ( for ( let i = momentFromDate; i.isBefore(toDate, addType) || i.isSame(toDate, addType); - i.add(increment, `${addType}s`) + i.add(increment, `${addType}s` as moment.unitOfTime.DurationConstructor) ) { collection.push({ fromDate: i.startOf(addType).format(dateFormat), diff --git a/packages/server-nest/src/utils/deepdash.ts b/packages/server-nest/src/utils/deepdash.ts new file mode 100644 index 000000000..7c44d298a --- /dev/null +++ b/packages/server-nest/src/utils/deepdash.ts @@ -0,0 +1,131 @@ +// @ts-nocheck +import * as _ from 'lodash'; +import * as addDeepdash from 'deepdash'; + +const { + condense, + condenseDeep, + eachDeep, + exists, + filterDeep, + findDeep, + findPathDeep, + findValueDeep, + forEachDeep, + index, + keysDeep, + mapDeep, + mapKeysDeep, + mapValuesDeep, + mapValues, + omitDeep, + pathMatches, + pathToString, + paths, + pickDeep, + reduceDeep, + someDeep, + iteratee, +} = addDeepdash(_); + +const mapValuesDeepReverse = (nodes, callback, config?) => { + const clonedNodes = _.clone(nodes); + const nodesPaths = paths(nodes, config); + const reversedPaths = _.reverse(nodesPaths); + + reversedPaths.forEach((pathStack: string[], i) => { + const node = _.get(clonedNodes, pathStack); + const pathString = pathToString(pathStack); + const children = _.get( + clonedNodes, + `${pathString}.${config.childrenPath}`, + [] + ); + const mappedNode = callback(node, children); + + if (!mappedNode.children && children) { + mappedNode.children = children; + } + _.set(clonedNodes, pathString, mappedNode); + }); + return clonedNodes; +}; + +const filterNodesDeep = (predicate, nodes) => { + return condense( + reduceDeep( + nodes, + (accumulator, value, key, parent, context) => { + const newValue = { ...value }; + + if (newValue.children) { + _.set(newValue, 'children', condense(value.children)); + } + const isTrue = predicate(newValue, key, parent, context); + + if (isTrue === true) { + _.set(accumulator, context.path, newValue); + } else if (isTrue === false) { + _.unset(accumulator, context.path); + } + return accumulator; + }, + [], + { + childrenPath: 'children', + pathFormat: 'array', + callbackAfterIterate: true, + } + ) + ); +}; + +const flatNestedTree = (obj, mapper, options) => { + return reduceDeep( + obj, + (accumulator, value, key, parentValue, context) => { + const computedValue = _.omit(value, ['children']); + const mappedValue = mapper + ? mapper(computedValue, key, context) + : computedValue; + + accumulator.push(mappedValue); + return accumulator; + }, + [], + { + childrenPath: 'children', + pathFormat: 'array', + ...options, + } + ); +}; + +export { + iteratee, + condense, + condenseDeep, + eachDeep, + exists, + filterDeep, + findDeep, + findPathDeep, + findValueDeep, + forEachDeep, + index, + keysDeep, + mapDeep, + mapKeysDeep, + mapValuesDeep, + mapValues, + omitDeep, + pathMatches, + pathToString, + paths, + pickDeep, + reduceDeep, + someDeep, + mapValuesDeepReverse, + filterNodesDeep, + flatNestedTree, +}; diff --git a/packages/server-nest/src/utils/format-number.ts b/packages/server-nest/src/utils/format-number.ts index 00767a827..13bee2579 100644 --- a/packages/server-nest/src/utils/format-number.ts +++ b/packages/server-nest/src/utils/format-number.ts @@ -15,6 +15,19 @@ const getCurrencySign = (currencyCode) => { return get(Currencies, `${currencyCode}.symbol`); }; +export interface IFormatNumberSettings { + precision?: number; + divideOn1000?: boolean; + excerptZero?: boolean; + negativeFormat?: string; + thousand?: string; + decimal?: string; + zeroSign?: string; + money?: boolean; + currencyCode?: string; + symbol?: string; +} + export const formatNumber = ( balance, { @@ -28,7 +41,7 @@ export const formatNumber = ( money = true, currencyCode, symbol = '', - }, + }: IFormatNumberSettings, ) => { const formattedSymbol = getCurrencySign(currencyCode); const negForamt = getNegativeFormat(negativeFormat); diff --git a/packages/server-nest/tsconfig.build.json b/packages/server-nest/tsconfig.build.json index 78721cbbb..83d7ca36f 100644 --- a/packages/server-nest/tsconfig.build.json +++ b/packages/server-nest/tsconfig.build.json @@ -5,11 +5,13 @@ "test", "dist", "**/*spec.ts", - // "./src/modules/DynamicListing/**/*.ts", + "./src/modules/DynamicListing/**/*.ts", "./src/modules/Import/**/*.ts", "./src/modules/Export/**/*.ts", "./src/modules/DynamicListing", "./src/modules/DynamicListing/**/*.ts", + "./src/modules/FinancialStatements/**/*.ts", + // "./src/modules/FinancialStatements/modules/BalanceSheet/**.ts", "./src/modules/Views", "./src/modules/Expenses/subscribers" ] diff --git a/packages/server-nest/tsconfig.json b/packages/server-nest/tsconfig.json index 8f7407b02..b32c9b439 100644 --- a/packages/server-nest/tsconfig.json +++ b/packages/server-nest/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "declaration": true, + "declaration": false, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetComparsionPreviousYear.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetComparsionPreviousYear.ts index d5d048039..d48d44a6d 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetComparsionPreviousYear.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetComparsionPreviousYear.ts @@ -10,10 +10,7 @@ import { import { FinancialPreviousYear } from '../FinancialPreviousYear'; export const BalanceSheetComparsionPreviousYear = (Base: any) => - class - extends R.compose(FinancialPreviousYear)(Base) - implements IBalanceSheetComparsions - { + class extends R.compose(FinancialPreviousYear)(Base) { // ------------------------------ // # Account // ------------------------------ diff --git a/packages/server/src/services/Import/jobs/ImportDeleteExpiredFilesJob.ts b/packages/server/src/services/Import/jobs/ImportDeleteExpiredFilesJob.ts index 74ce6a7c8..849703546 100644 --- a/packages/server/src/services/Import/jobs/ImportDeleteExpiredFilesJob.ts +++ b/packages/server/src/services/Import/jobs/ImportDeleteExpiredFilesJob.ts @@ -1,28 +1,28 @@ -import Container, { Service } from 'typedi'; -import { ImportDeleteExpiredFiles } from '../ImportRemoveExpiredFiles'; +// import Container, { Service } from 'typedi'; +// import { ImportDeleteExpiredFiles } from '../ImportRemoveExpiredFiles'; -@Service() -export class ImportDeleteExpiredFilesJobs { - /** - * Constructor method. - */ - constructor(agenda) { - agenda.define('delete-expired-imported-files', this.handler); - } +// @Service() +// export class ImportDeleteExpiredFilesJobs { +// /** +// * Constructor method. +// */ +// constructor(agenda) { +// agenda.define('delete-expired-imported-files', this.handler); +// } - /** - * Triggers sending invoice mail. - */ - private handler = async (job, done: Function) => { - const importDeleteExpiredFiles = Container.get(ImportDeleteExpiredFiles); +// /** +// * Triggers sending invoice mail. +// */ +// private handler = async (job, done: Function) => { +// const importDeleteExpiredFiles = Container.get(ImportDeleteExpiredFiles); - try { - console.log('Delete expired import files has started.'); - await importDeleteExpiredFiles.deleteExpiredFiles(); - done(); - } catch (error) { - console.log(error); - done(error); - } - }; -} +// try { +// console.log('Delete expired import files has started.'); +// await importDeleteExpiredFiles.deleteExpiredFiles(); +// done(); +// } catch (error) { +// console.log(error); +// done(error); +// } +// }; +// } diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index cd1044500..d1ee2d6d1 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -771,4 +771,5 @@ export default { onSalesByItemViewed: 'onSalesByItemViewed', onPurchasesByItemViewed: 'onPurchasesByItemViewed', }, + }; diff --git a/packages/server/src/utils/deepdash.ts b/packages/server/src/utils/deepdash.ts index 0ce65216a..c0535bc2b 100644 --- a/packages/server/src/utils/deepdash.ts +++ b/packages/server/src/utils/deepdash.ts @@ -1,5 +1,5 @@ -import _ from 'lodash'; -import deepdash from 'deepdash'; +import * as _ from 'lodash'; +import * as deepdash from 'deepdash'; const { condense, @@ -24,7 +24,7 @@ const { reduceDeep, someDeep, iteratee, -} = deepdash(_); +} = deepdash.default(_); const mapValuesDeepReverse = (nodes, callback, config?) => { const clonedNodes = _.clone(nodes); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef8a17409..c4f992085 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -571,6 +571,9 @@ importers: class-validator: specifier: ^0.14.1 version: 0.14.1 + deepdash: + specifier: ^5.3.9 + version: 5.3.9 express-validator: specifier: ^7.2.0 version: 7.2.0 @@ -595,6 +598,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + mathjs: + specifier: ^9.4.0 + version: 9.5.2 moment: specifier: ^2.30.1 version: 2.30.1 @@ -649,6 +655,9 @@ importers: reflect-metadata: specifier: ^0.2.0 version: 0.2.2 + remeda: + specifier: ^2.19.2 + version: 2.19.2 rxjs: specifier: ^7.8.1 version: 7.8.1 @@ -689,6 +698,9 @@ importers: '@types/jest': specifier: ^29.5.2 version: 29.5.14 + '@types/mathjs': + specifier: ^6.0.12 + version: 6.0.12 '@types/node': specifier: ^20.3.1 version: 20.5.1 @@ -8094,7 +8106,7 @@ packages: react-refresh: 0.11.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) webpack-dev-server: 4.15.2(webpack@5.91.0) dev: false @@ -12266,7 +12278,6 @@ packages: resolution: {integrity: sha512-bpKs8CDJ0aOiiJguywryE/U6Wre/uftJ89xhp4aCgF4oRb3Yug2VyZ87958gmSeq4WMsvWPMs2Q5TtFv+dJtaA==} dependencies: decimal.js: 10.4.3 - dev: false /@types/mdx@2.0.13: resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} @@ -13642,13 +13653,6 @@ packages: dependencies: acorn: 8.11.3 - /acorn-import-assertions@1.9.0(acorn@8.13.0): - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} - peerDependencies: - acorn: ^8 - dependencies: - acorn: 8.13.0 - /acorn-import-attributes@1.9.5(acorn@8.14.0): resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -14595,7 +14599,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /babel-loader@9.1.3(@babel/core@7.26.0)(webpack@5.91.0): @@ -16950,7 +16954,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.47) postcss-value-parser: 4.2.0 semver: 7.6.2 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) /css-loader@6.11.0(webpack@5.96.1): resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} @@ -17001,7 +17005,7 @@ packages: schema-utils: 4.2.0 serialize-javascript: 6.0.2 source-map: 0.6.1 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /css-prefers-color-scheme@6.0.3(postcss@8.4.38): @@ -17351,7 +17355,6 @@ packages: /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: false /declaration-bundler-webpack-plugin@1.0.3: resolution: {integrity: sha512-bgeoSOZYTOOdiNUZd/U8K6Z+6IrM/X+DgUcm3/VI1l130lzOBeL+ObetjIkKksxcj0zUJbLaFRFumFGYDOQ9fg==} @@ -19029,7 +19032,7 @@ packages: micromatch: 4.0.7 normalize-path: 3.0.0 schema-utils: 4.2.0 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /eslint@8.57.0: @@ -19725,7 +19728,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /file-selector@0.4.0: @@ -20089,7 +20092,7 @@ packages: semver: 7.6.2 tapable: 1.1.3 typescript: 4.9.5 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.91.0): @@ -21357,7 +21360,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /htmlparser2@6.1.0: @@ -25183,7 +25186,7 @@ packages: dependencies: schema-utils: 4.2.0 tapable: 2.2.1 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /minimalistic-assert@1.0.1: @@ -27930,7 +27933,7 @@ packages: klona: 2.0.6 postcss: 8.4.38 semver: 7.6.2 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /postcss-loader@7.3.4(postcss@8.4.47)(typescript@5.6.3)(webpack@5.91.0): @@ -29369,7 +29372,7 @@ packages: strip-ansi: 6.0.1 text-table: 0.2.0 typescript: 4.9.5 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) transitivePeerDependencies: - eslint - supports-color @@ -29840,7 +29843,7 @@ packages: tailwindcss: 3.4.14(ts-node@10.9.2) terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0) typescript: 4.9.5 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) webpack-dev-server: 4.15.2(webpack@5.91.0) webpack-manifest-plugin: 4.1.1(webpack@5.91.0) workbox-webpack-plugin: 6.6.0(webpack@5.91.0) @@ -30532,6 +30535,12 @@ packages: unist-util-visit: 2.0.3 dev: true + /remeda@2.19.2: + resolution: {integrity: sha512-192lSeU0P91TIsYOX+MZ2x8I+enSkVVF0YhUhABix0CZWl+1+3/zn4b3L2d/BiWBTa6RsIeJgvLJj5nYTiTXGA==} + dependencies: + type-fest: 4.32.0 + dev: false + /remove-accents@0.4.2: resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} dev: false @@ -31010,7 +31019,7 @@ packages: klona: 2.0.6 neo-async: 2.6.2 sass: 1.77.2 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /sass-loader@13.3.3(webpack@5.91.0): @@ -31696,7 +31705,7 @@ packages: abab: 2.0.6 iconv-lite: 0.6.3 source-map-js: 1.2.0 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /source-map-loader@4.0.2(webpack@5.91.0): @@ -32334,7 +32343,7 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) /style-loader@3.3.4(webpack@5.96.1): resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} @@ -33593,6 +33602,11 @@ packages: engines: {node: '>=12.20'} dev: true + /type-fest@4.32.0: + resolution: {integrity: sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==} + engines: {node: '>=16'} + dev: false + /type-is@1.6.15: resolution: {integrity: sha512-0uqZYZDiBICTVXEsNcDLueZLPgZ8FgGe8lmVDQ0FcVFUeaxsPbFWiz60ZChVw8VELIt7iGuCehOrZSYjYteWKQ==} engines: {node: '>= 0.6'} @@ -34588,7 +34602,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) dev: false /webpack-dev-server@4.15.2(webpack@5.91.0): @@ -34632,7 +34646,7 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) webpack-dev-middleware: 5.3.4(webpack@5.91.0) ws: 8.17.0 transitivePeerDependencies: @@ -34649,7 +34663,7 @@ packages: webpack: ^4.44.2 || ^5.47.0 dependencies: tapable: 2.2.1 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) webpack-sources: 2.3.1 dev: false @@ -34712,13 +34726,13 @@ packages: optional: true dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.6 + '@types/estree': 1.0.5 '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.13.0 - acorn-import-assertions: 1.9.0(acorn@8.13.0) - browserslist: 4.24.2 + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) + browserslist: 4.23.0 chrome-trace-event: 1.0.3 enhanced-resolve: 5.16.1 es-module-lexer: 1.5.3 @@ -34741,45 +34755,6 @@ packages: - esbuild - uglify-js - /webpack@5.91.0(esbuild@0.23.1): - resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.6 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.13.0 - acorn-import-assertions: 1.9.0(acorn@8.13.0) - browserslist: 4.24.2 - chrome-trace-event: 1.0.3 - enhanced-resolve: 5.16.1 - es-module-lexer: 1.5.3 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0) - watchpack: 2.4.1 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - /webpack@5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0): resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} @@ -35267,7 +35242,7 @@ packages: fast-json-stable-stringify: 2.1.0 pretty-bytes: 5.6.0 upath: 1.2.0 - webpack: 5.91.0(esbuild@0.23.1) + webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) webpack-sources: 1.4.3 workbox-build: 6.6.0 transitivePeerDependencies: diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.controller.ts b/temp/BalanceSheet/BalanceSheet.controller.ts similarity index 74% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.controller.ts rename to temp/BalanceSheet/BalanceSheet.controller.ts index 92c00db46..202c1086a 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.controller.ts +++ b/temp/BalanceSheet/BalanceSheet.controller.ts @@ -1,20 +1,19 @@ import { Response } from 'express'; import { castArray } from 'lodash'; -import { Headers, Injectable, Query, Res } from '@nestjs/common'; -import { BalanceSheetInjectable } from './BalanceSheetInjectable'; +import { Controller, Headers, Query, Res } from '@nestjs/common'; import { IBalanceSheetQuery } from './BalanceSheet.types'; import { AcceptType } from '@/constants/accept-type'; import { BalanceSheetApplication } from './BalanceSheetApplication'; -@Injectable() +@Controller('/reports/balance-sheet') export class BalanceSheetStatementController { constructor(private readonly balanceSheetApp: BalanceSheetApplication) {} /** * Retrieve the balance sheet. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @param {Response} res - Response. + * @param {string} acceptHeader - Accept header. */ public async balanceSheet( @Query() query: IBalanceSheetQuery, @@ -27,12 +26,12 @@ export class BalanceSheetStatementController { }; // Retrieves the json table format. if (acceptHeader.includes(AcceptType.ApplicationJsonTable)) { - const table = await this.balanceSheetApp.table(tenantId, filter); + const table = await this.balanceSheetApp.table(filter); return res.status(200).send(table); // Retrieves the csv format. } else if (acceptHeader.includes(AcceptType.ApplicationCsv)) { - const buffer = await this.balanceSheetApp.csv(tenantId, filter); + const buffer = await this.balanceSheetApp.csv(filter); res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); res.setHeader('Content-Type', 'text/csv'); @@ -40,7 +39,7 @@ export class BalanceSheetStatementController { return res.send(buffer); // Retrieves the xlsx format. } else if (acceptHeader.includes(AcceptType.ApplicationXlsx)) { - const buffer = await this.balanceSheetApp.xlsx(tenantId, filter); + const buffer = await this.balanceSheetApp.xlsx(filter); res.setHeader('Content-Disposition', 'attachment; filename=output.xlsx'); res.setHeader( @@ -50,7 +49,7 @@ export class BalanceSheetStatementController { return res.send(buffer); // Retrieves the pdf format. } else if (acceptHeader.includes(AcceptType.ApplicationPdf)) { - const pdfContent = await this.balanceSheetApp.pdf(tenantId, filter); + const pdfContent = await this.balanceSheetApp.pdf(filter); res.set({ 'Content-Type': 'application/pdf', @@ -58,7 +57,7 @@ export class BalanceSheetStatementController { }); res.send(pdfContent); } else { - const sheet = await this.balanceSheetApp.sheet(tenantId, filter); + const sheet = await this.balanceSheetApp.sheet(filter); return res.status(200).send(sheet); } diff --git a/temp/BalanceSheet/BalanceSheet.module.ts b/temp/BalanceSheet/BalanceSheet.module.ts new file mode 100644 index 000000000..7dbb051e9 --- /dev/null +++ b/temp/BalanceSheet/BalanceSheet.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { BalanceSheetInjectable } from './BalanceSheetInjectable'; +import { BalanceSheetApplication } from './BalanceSheetApplication'; +import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable'; +import { BalanceSheetExportInjectable } from './BalanceSheetExportInjectable'; +import { BalanceSheetMetaInjectable } from './BalanceSheetMeta'; +import { BalanceSheetPdfInjectable } from './BalanceSheetPdfInjectable'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; +import { BalanceSheetStatementController } from './BalanceSheet.controller'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; +import { AccountsModule } from '@/modules/Accounts/Accounts.module'; + +@Module({ + imports: [FinancialSheetCommonModule, AccountsModule], + controllers: [BalanceSheetStatementController], + providers: [ + BalanceSheetRepository, + BalanceSheetInjectable, + BalanceSheetTableInjectable, + BalanceSheetExportInjectable, + BalanceSheetMetaInjectable, + BalanceSheetApplication, + BalanceSheetPdfInjectable, + TenancyContext, + ], + exports: [BalanceSheetInjectable], +}) +export class BalanceSheetModule {} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.ts b/temp/BalanceSheet/BalanceSheet.ts similarity index 89% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.ts rename to temp/BalanceSheet/BalanceSheet.ts index 6738ca5c3..c7311c66a 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.ts +++ b/temp/BalanceSheet/BalanceSheet.ts @@ -1,4 +1,5 @@ import * as R from 'ramda'; +import { I18nService } from 'nestjs-i18n'; import { IBalanceSheetQuery, IBalanceSheetSchemaNode, @@ -11,16 +12,16 @@ import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPrev import { BalanceSheetDatePeriods } from './BalanceSheetDatePeriods'; import { BalanceSheetBase } from './BalanceSheetBase'; import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; -import BalanceSheetRepository from './BalanceSheetRepository'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetFiltering } from './BalanceSheetFiltering'; import { BalanceSheetNetIncome } from './BalanceSheetNetIncome'; import { BalanceSheetAggregators } from './BalanceSheetAggregators'; import { BalanceSheetAccounts } from './BalanceSheetAccounts'; -import FinancialSheet from '../../common/FinancialSheet'; import { INumberFormatQuery } from '../../types/Report.types'; +import { FinancialSheet } from '../../common/FinancialSheet'; -export class BalanceSheet extends R.compose( +export class BalanceSheet extends R.pipe( BalanceSheetAggregators, BalanceSheetAccounts, BalanceSheetNetIncome, @@ -54,7 +55,12 @@ export class BalanceSheet extends R.compose( /** * Localization. */ - readonly i18n: any; + readonly i18n: I18nService; + + /** + * Balance sheet repository. + */ + readonly repository: BalanceSheetRepository; /** * Constructor method. @@ -66,7 +72,7 @@ export class BalanceSheet extends R.compose( query: IBalanceSheetQuery, repository: BalanceSheetRepository, baseCurrency: string, - i18n, + i18n: I18nService, ) { super(); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.types.ts b/temp/BalanceSheet/BalanceSheet.types.ts similarity index 99% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.types.ts rename to temp/BalanceSheet/BalanceSheet.types.ts index 96cff4985..c5f5be024 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.types.ts +++ b/temp/BalanceSheet/BalanceSheet.types.ts @@ -43,8 +43,8 @@ export enum BALANCE_SHEET_SCHEMA_NODE_ID { export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery { displayColumnsType: 'total' | 'date_periods'; displayColumnsBy: string; - fromDate: Date; - toDate: Date; + fromDate: string; + toDate: string; numberFormat: INumberFormatQuery; noneTransactions: boolean; noneZero: boolean; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAccounts.ts b/temp/BalanceSheet/BalanceSheetAccounts.ts similarity index 98% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAccounts.ts rename to temp/BalanceSheet/BalanceSheetAccounts.ts index 687931212..d28f5be29 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAccounts.ts +++ b/temp/BalanceSheet/BalanceSheetAccounts.ts @@ -18,11 +18,11 @@ import { BalanceSheetPercentage } from './BalanceSheetPercentage'; import { BalanceSheetSchema } from './BalanceSheetSchema'; import { BalanceSheetBase } from './BalanceSheetBase'; import { BalanceSheetQuery } from './BalanceSheetQuery'; -import { flatToNestedArray } from '@/utils'; -import BalanceSheetRepository from './BalanceSheetRepository'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; import { Constructor } from '@/common/types/Constructor'; import { INumberFormatQuery } from '../../types/Report.types'; import { Account } from '@/modules/Accounts/models/Account.model'; +import { flatToNestedArray } from '@/utils/flat-to-nested-array'; export const BalanceSheetAccounts = (Base: T) => class extends R.compose( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAggregators.ts b/temp/BalanceSheet/BalanceSheetAggregators.ts similarity index 96% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAggregators.ts rename to temp/BalanceSheet/BalanceSheetAggregators.ts index 53218b739..9e01443fc 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetAggregators.ts +++ b/temp/BalanceSheet/BalanceSheetAggregators.ts @@ -5,8 +5,7 @@ import { IBalanceSheetDataNode, IBalanceSheetSchemaAggregateNode, IBalanceSheetSchemaNode, - INumberFormatQuery, -} from '@/interfaces'; +} from './BalanceSheet.types'; import { BalanceSheetDatePeriods } from './BalanceSheetDatePeriods'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; @@ -16,6 +15,7 @@ import { BalanceSheetBase } from './BalanceSheetBase'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; import { Constructor } from '@/common/types/Constructor'; +import { INumberFormatQuery } from '../../types/Report.types'; export const BalanceSheetAggregators = (Base: T) => class extends R.compose( @@ -31,7 +31,7 @@ export const BalanceSheetAggregators = (Base: T) => * Balance sheet query. * @param {BalanceSheetQuery} */ - readonly query: BalanceSheetQuery; + public readonly query: BalanceSheetQuery; /** * Balance sheet number format query. diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetApplication.ts b/temp/BalanceSheet/BalanceSheetApplication.ts similarity index 76% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetApplication.ts rename to temp/BalanceSheet/BalanceSheetApplication.ts index 8813d05d5..dacd93c3f 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetApplication.ts +++ b/temp/BalanceSheet/BalanceSheetApplication.ts @@ -1,16 +1,20 @@ import { Injectable } from '@nestjs/common'; -import { IBalanceSheetQuery } from '@/interfaces'; +import { IBalanceSheetQuery } from './BalanceSheet.types'; import { BalanceSheetExportInjectable } from './BalanceSheetExportInjectable'; import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable'; -import { BalanceSheetStatementService } from './BalanceSheetInjectable'; +import { BalanceSheetInjectable } from './BalanceSheetInjectable'; @Injectable() export class BalanceSheetApplication { - + /** + * @param {BalanceSheetExportInjectable} balanceSheetExportService - The balance sheet export service. + * @param {BalanceSheetTableInjectable} balanceSheetTableService - The balance sheet table service. + * @param {BalanceSheetInjectable} balanceSheetService - The balance sheet service. + */ constructor( private readonly balanceSheetExportService: BalanceSheetExportInjectable, private readonly balanceSheetTableService: BalanceSheetTableInjectable, - private readonly balanceSheetService: BalanceSheetStatementService, + private readonly balanceSheetService: BalanceSheetInjectable, ) {} /** diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetBase.ts b/temp/BalanceSheet/BalanceSheetBase.ts similarity index 60% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetBase.ts rename to temp/BalanceSheet/BalanceSheetBase.ts index c89f5ee7c..355b459df 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetBase.ts +++ b/temp/BalanceSheet/BalanceSheetBase.ts @@ -1,30 +1,39 @@ import * as R from 'ramda'; -import { IBalanceSheetDataNode, IBalanceSheetSchemaNode } from '@/interfaces'; +import { + IBalanceSheetDataNode, + IBalanceSheetSchemaNode, +} from './BalanceSheet.types'; import { Constructor } from '@/common/types/Constructor'; export const BalanceSheetBase = (Base: T) => - class extends Base { + class BalanceSheetBase extends Base { /** * Detarmines the node type of the given schema node. - * @param {IBalanceSheetStructureSection} node - - * @param {string} type - + * @param {IBalanceSheetStructureSection} node - + * @param {string} type - * @return {boolean} */ public isSchemaNodeType = R.curry( (type: string, node: IBalanceSheetSchemaNode): boolean => { return node.type === type; - } + }, ); - isNodeType = R.curry( + /** + * Detarmines the node type of the given schema node. + * @param {IBalanceSheetStructureSection} node - + * @param {string} type - + * @return {boolean} + */ + public isNodeType = R.curry( (type: string, node: IBalanceSheetDataNode): boolean => { return node.nodeType === type; - } + }, ); /** * Detarmines the given display columns by type. - * @param {string} displayColumnsBy + * @param {string} displayColumnsBy * @returns {boolean} */ public isDisplayColumnsBy = (displayColumnsBy: string): boolean => { diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetComparsionPreviousPeriod.ts b/temp/BalanceSheet/BalanceSheetComparsionPreviousPeriod.ts similarity index 97% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetComparsionPreviousPeriod.ts rename to temp/BalanceSheet/BalanceSheetComparsionPreviousPeriod.ts index 918b11359..196e75a43 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetComparsionPreviousPeriod.ts +++ b/temp/BalanceSheet/BalanceSheetComparsionPreviousPeriod.ts @@ -6,8 +6,7 @@ import { IBalanceSheetAggregateNode, IBalanceSheetTotal, IBalanceSheetCommonNode, - IBalanceSheetComparsions, -} from '@/interfaces'; +} from './BalanceSheet.types'; import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; import { Constructor } from '@/common/types/Constructor'; @@ -15,10 +14,10 @@ import { Constructor } from '@/common/types/Constructor'; export const BalanceSheetComparsionPreviousPeriod = ( Base: T, ) => - class - extends R.compose(FinancialPreviousPeriod, FinancialHorizTotals)(Base) - implements IBalanceSheetComparsions - { + class BalanceSheetComparsionPreviousPeriod extends R.compose( + FinancialPreviousPeriod, + FinancialHorizTotals, + )(Base) { // ------------------------------ // # Account // ------------------------------ diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetComparsionPreviousYear.ts b/temp/BalanceSheet/BalanceSheetComparsionPreviousYear.ts similarity index 79% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetComparsionPreviousYear.ts rename to temp/BalanceSheet/BalanceSheetComparsionPreviousYear.ts index d5d048039..ce486bc26 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetComparsionPreviousYear.ts +++ b/temp/BalanceSheet/BalanceSheetComparsionPreviousYear.ts @@ -5,15 +5,17 @@ import { IBalanceSheetCommonNode, IBalanceSheetDataNode, IBalanceSheetTotal, - ITableColumn, -} from '@/interfaces'; -import { FinancialPreviousYear } from '../FinancialPreviousYear'; +} from './BalanceSheet.types'; +import { FinancialPreviousYear } from '../../common/FinancialPreviousYear'; +import { IBalanceSheetComparsions } from './BalanceSheet.types'; +import { Constructor } from '@/common/types/Constructor'; -export const BalanceSheetComparsionPreviousYear = (Base: any) => - class - extends R.compose(FinancialPreviousYear)(Base) - implements IBalanceSheetComparsions - { +export const BalanceSheetComparsionPreviousYear = ( + Base: T, +) => + class BalanceSheetComparsionPreviousYear extends R.compose( + FinancialPreviousYear, + )(Base) { // ------------------------------ // # Account // ------------------------------ @@ -23,11 +25,11 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => * @returns {IBalanceSheetDataNode} */ protected assocPreviousYearAccountNode = ( - node: IBalanceSheetDataNode + node: IBalanceSheetDataNode, ): IBalanceSheetDataNode => { const closingBalance = this.repository.PYTotalAccountsLedger.whereAccountId( - node.id + node.id, ).getClosingBalance(); return R.assoc('previousYear', this.getAmountMeta(closingBalance), node); @@ -39,22 +41,22 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => * @returns {IBalanceSheetAccountNode} */ protected previousYearAccountNodeComposer = ( - node: IBalanceSheetAccountNode + node: IBalanceSheetAccountNode, ): IBalanceSheetAccountNode => { return R.compose( R.when( this.isNodeHasHorizontalTotals, - this.assocPreviousYearAccountHorizNodeComposer + this.assocPreviousYearAccountHorizNodeComposer, ), R.when( this.query.isPreviousYearPercentageActive, - this.assocPreviousYearPercentageNode + this.assocPreviousYearPercentageNode, ), R.when( this.query.isPreviousYearChangeActive, - this.assocPreviousYearChangetNode + this.assocPreviousYearChangetNode, ), - this.assocPreviousYearAccountNode + this.assocPreviousYearAccountNode, )(node); }; @@ -67,7 +69,7 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => * @returns {IBalanceSheetAccountNode} */ protected assocPreviousYearAggregateNode = ( - node: IBalanceSheetAccountNode + node: IBalanceSheetAccountNode, ): IBalanceSheetAccountNode => { const total = sumBy(node.children, 'previousYear.amount'); @@ -80,22 +82,22 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => * @returns {IBalanceSheetAccountNode} */ protected previousYearAggregateNodeComposer = ( - node: IBalanceSheetAccountNode + node: IBalanceSheetAccountNode, ): IBalanceSheetAccountNode => { return R.compose( R.when( this.query.isPreviousYearPercentageActive, - this.assocPreviousYearTotalPercentageNode + this.assocPreviousYearTotalPercentageNode, ), R.when( this.query.isPreviousYearChangeActive, - this.assocPreviousYearTotalChangeNode + this.assocPreviousYearTotalChangeNode, ), R.when( this.isNodeHasHorizontalTotals, - this.assocPreviousYearAggregateHorizNode + this.assocPreviousYearAggregateHorizNode, ), - this.assocPreviousYearAggregateNode + this.assocPreviousYearAggregateNode, )(node); }; @@ -114,9 +116,9 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => return R.assoc( 'previousYear', this.getTotalAmountMeta(total), - totalNode + totalNode, ); - } + }, ); /** @@ -128,27 +130,27 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => ( node: IBalanceSheetCommonNode, horiontalTotalNode: IBalanceSheetTotal, - index: number + index: number, ): IBalanceSheetTotal => { return R.compose( R.when( this.query.isPreviousYearPercentageActive, - this.assocPreviousYearTotalPercentageNode + this.assocPreviousYearTotalPercentageNode, ), R.when( this.query.isPreviousYearChangeActive, - this.assocPreviousYearTotalChangeNode + this.assocPreviousYearTotalChangeNode, ), R.when( this.query.isPreviousYearActive, - this.assocPreviousYearAggregateHorizTotalNode(node, index) + this.assocPreviousYearAggregateHorizTotalNode(node, index), ), R.when( this.query.isPreviousYearActive, - this.assocPreviousYearHorizNodeFromToDates - ) + this.assocPreviousYearHorizNodeFromToDates, + ), )(horiontalTotalNode); - } + }, ); /** @@ -157,11 +159,11 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => * @returns {IBalanceSheetCommonNode} */ public assocPreviousYearAggregateHorizNode = ( - node: IBalanceSheetCommonNode + node: IBalanceSheetCommonNode, ): IBalanceSheetCommonNode => { const horizontalTotals = R.addIndex(R.map)( this.previousYearAggregateHorizNodeComposer(node), - node.horizontalTotals + node.horizontalTotals, ) as IBalanceSheetTotal[]; return R.assoc('horizontalTotals', horizontalTotals, node); @@ -186,11 +188,11 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => const PYPeriodsOpeningTotal = this.repository.PYPeriodsOpeningAccountLedger.whereAccountId( - accountId + accountId, ).getClosingBalance(); return PYPeriodsOpeningTotal + PYPeriodsTotal; - } + }, ); /** @@ -203,10 +205,10 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => const total = this.getAccountPYDatePeriodTotal( node.id, totalNode.previousYearFromDate.date, - totalNode.previousYearToDate.date + totalNode.previousYearToDate.date, ); return R.assoc('previousYear', this.getAmountMeta(total), totalNode); - } + }, ); /** @@ -218,27 +220,27 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => private previousYearAccountHorizNodeCompose = R.curry( ( node: IBalanceSheetAccountNode, - horizontalTotalNode: IBalanceSheetTotal + horizontalTotalNode: IBalanceSheetTotal, ): IBalanceSheetTotal => { return R.compose( R.when( this.query.isPreviousYearPercentageActive, - this.assocPreviousYearPercentageNode + this.assocPreviousYearPercentageNode, ), R.when( this.query.isPreviousYearChangeActive, - this.assocPreviousYearChangetNode + this.assocPreviousYearChangetNode, ), R.when( this.query.isPreviousYearActive, - this.assocPreviousYearAccountHorizTotal(node) + this.assocPreviousYearAccountHorizTotal(node), ), R.when( this.query.isPreviousYearActive, - this.assocPreviousYearHorizNodeFromToDates - ) + this.assocPreviousYearHorizNodeFromToDates, + ), )(horizontalTotalNode); - } + }, ); /** @@ -247,11 +249,11 @@ export const BalanceSheetComparsionPreviousYear = (Base: any) => * @returns {IBalanceSheetAccountNode} */ private assocPreviousYearAccountHorizNodeComposer = ( - node: IBalanceSheetAccountNode + node: IBalanceSheetAccountNode, ) => { const horizontalTotals = R.map( this.previousYearAccountHorizNodeCompose(node), - node.horizontalTotals + node.horizontalTotals, ); return R.assoc('horizontalTotals', horizontalTotals, node); }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetDatePeriods.ts b/temp/BalanceSheet/BalanceSheetDatePeriods.ts similarity index 82% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetDatePeriods.ts rename to temp/BalanceSheet/BalanceSheetDatePeriods.ts index 9ac6af1ce..19f013aaa 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetDatePeriods.ts +++ b/temp/BalanceSheet/BalanceSheetDatePeriods.ts @@ -2,24 +2,19 @@ import * as R from 'ramda'; import { sumBy } from 'lodash'; import { IBalanceSheetQuery, - IFormatNumberSettings, - IBalanceSheetDatePeriods, IBalanceSheetAccountNode, IBalanceSheetTotalPeriod, - IDateRange, IBalanceSheetCommonNode, -} from '@/interfaces'; -import FinancialSheet from '../FinancialSheet'; -import { FinancialDatePeriods } from '../FinancialDatePeriods'; +} from './BalanceSheet.types'; +import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; +import { IDateRange, IFormatNumberSettings } from '../../types/Report.types'; +import { Constructor } from '@/common/types/Constructor'; /** * Balance sheet date periods. */ -export const BalanceSheetDatePeriods = (Base: FinancialSheet) => - class - extends R.compose(FinancialDatePeriods)(Base) - implements IBalanceSheetDatePeriods - { +export const BalanceSheetDatePeriods = (Base: T) => + class BalanceSheetDatePeriods extends R.compose(FinancialDatePeriods)(Base) { /** * @param {IBalanceSheetQuery} */ @@ -33,7 +28,7 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => return this.getDateRanges( this.query.fromDate, this.query.toDate, - this.query.displayColumnsBy + this.query.displayColumnsBy, ); } @@ -43,21 +38,21 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => * @param {Function} callback * @returns {} */ - protected getReportNodeDatePeriods = ( + public getReportNodeDatePeriods = ( node: IBalanceSheetCommonNode, callback: ( node: IBalanceSheetCommonNode, fromDate: Date, toDate: Date, - index: number - ) => any + index: number, + ) => any, ) => { return this.getNodeDatePeriods( this.query.fromDate, this.query.toDate, this.query.displayColumnsBy, node, - callback + callback, ); }; @@ -68,11 +63,11 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => * @param {Date} toDate - To date. * @return {ICashFlowDatePeriod} */ - private getDatePeriodTotalMeta = ( + public getDatePeriodTotalMeta = ( total: number, fromDate: Date, toDate: Date, - overrideSettings: IFormatNumberSettings = {} + overrideSettings: IFormatNumberSettings = {}, ): IBalanceSheetTotalPeriod => { return this.getDatePeriodMeta(total, fromDate, toDate, { money: true, @@ -89,9 +84,9 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => * @param {Date} toDate * @returns {number} */ - private getAccountDatePeriodTotal = ( + public getAccountDatePeriodTotal = ( accountId: number, - toDate: Date + toDate: Date, ): number => { const periodTotalBetween = this.repository.periodsAccountsLedger .whereAccountId(accountId) @@ -112,10 +107,10 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => * @param {Date} toDate * @returns {IBalanceSheetAccountNode} */ - private getAccountNodeDatePeriod = ( + public getAccountNodeDatePeriod = ( node: IBalanceSheetAccountNode, fromDate: Date, - toDate: Date + toDate: Date, ): IBalanceSheetTotalPeriod => { const periodTotal = this.getAccountDatePeriodTotal(node.id, toDate); @@ -127,8 +122,8 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => * @param {IBalanceSheetAccountNode} node * @returns {IBalanceSheetAccountNode} */ - private getAccountsNodeDatePeriods = ( - node: IBalanceSheetAccountNode + public getAccountsNodeDatePeriods = ( + node: IBalanceSheetAccountNode, ): IBalanceSheetTotalPeriod[] => { return this.getReportNodeDatePeriods(node, this.getAccountNodeDatePeriod); }; @@ -139,7 +134,7 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => * @returns {IBalanceSheetAccountNode} */ public assocAccountNodeDatePeriods = ( - node: IBalanceSheetAccountNode + node: IBalanceSheetAccountNode, ): IBalanceSheetAccountNode => { const datePeriods = this.getAccountsNodeDatePeriods(node); @@ -155,7 +150,7 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => * @param {number} index * @returns {number} */ - private getAggregateDatePeriodIndexTotal = (node, index) => { + public getAggregateDatePeriodIndexTotal = (node, index) => { return sumBy(node.children, `horizontalTotals[${index}].total.amount`); }; @@ -170,7 +165,7 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => node: IBalanceSheetAccountNode, fromDate: Date, toDate: Date, - index: number + index: number, ) => { const periodTotal = this.getAggregateDatePeriodIndexTotal(node, index); @@ -185,7 +180,7 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => public getAggregateNodeDatePeriods = (node) => { return this.getReportNodeDatePeriods( node, - this.getAggregateNodeDatePeriod + this.getAggregateNodeDatePeriod, ); }; @@ -201,7 +196,7 @@ export const BalanceSheetDatePeriods = (Base: FinancialSheet) => }; /** - * + * * @param node * @returns */ diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetExportInjectable.ts b/temp/BalanceSheet/BalanceSheetExportInjectable.ts similarity index 94% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetExportInjectable.ts rename to temp/BalanceSheet/BalanceSheetExportInjectable.ts index 71269b744..bd89840ab 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetExportInjectable.ts +++ b/temp/BalanceSheet/BalanceSheetExportInjectable.ts @@ -13,7 +13,6 @@ export class BalanceSheetExportInjectable { /** * Retrieves the trial balance sheet in XLSX format. - * @param {number} tenantId * @param {ITrialBalanceSheetQuery} query * @returns {Promise} */ @@ -28,7 +27,6 @@ export class BalanceSheetExportInjectable { /** * Retrieves the trial balance sheet in CSV format. - * @param {number} tenantId * @param {ITrialBalanceSheetQuery} query * @returns {Promise} */ @@ -45,7 +43,6 @@ export class BalanceSheetExportInjectable { /** * Retrieves the balance sheet in pdf format. - * @param {number} tenantId * @param {IBalanceSheetQuery} query * @returns {Promise} */ diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetFiltering.ts b/temp/BalanceSheet/BalanceSheetFiltering.ts similarity index 92% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetFiltering.ts rename to temp/BalanceSheet/BalanceSheetFiltering.ts index 79808d857..cbe37076a 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetFiltering.ts +++ b/temp/BalanceSheet/BalanceSheetFiltering.ts @@ -3,12 +3,17 @@ import { get } from 'lodash'; import { IBalanceSheetDataNode, BALANCE_SHEET_NODE_TYPE, -} from '../../../interfaces'; -import { FinancialFilter } from '../FinancialFilter'; +} from './BalanceSheet.types'; import { Constructor } from '@/common/types/Constructor'; +import { FinancialFilter } from '../../common/FinancialFilter'; +import { BalanceSheetBase } from './BalanceSheetBase'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; +import { BalanceSheetQuery } from './BalanceSheetQuery'; export const BalanceSheetFiltering = (Base: T) => - class extends R.compose(FinancialFilter)(Base) { + class extends R.pipe(FinancialFilter, BalanceSheetBase)(Base) { + public repository: BalanceSheetRepository; + // ----------------------- // # Account // ----------------------- diff --git a/temp/BalanceSheet/BalanceSheetInjectable.ts b/temp/BalanceSheet/BalanceSheetInjectable.ts new file mode 100644 index 000000000..cc7e01157 --- /dev/null +++ b/temp/BalanceSheet/BalanceSheetInjectable.ts @@ -0,0 +1,67 @@ +import { + IBalanceSheetDOO, + IBalanceSheetQuery, +} from './BalanceSheet.types'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; +import { BalanceSheetMetaInjectable } from './BalanceSheetMeta'; +import { Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { BalanceSheet } from './BalanceSheet'; +import { getBalanceSheetDefaultQuery } from './constants'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { I18nService } from 'nestjs-i18n'; + +@Injectable() +export class BalanceSheetInjectable { + constructor( + private readonly balanceSheetMeta: BalanceSheetMetaInjectable, + private readonly eventPublisher: EventEmitter2, + private readonly tenancyContext: TenancyContext, + private readonly i18n: I18nService, + + @Inject(BalanceSheetRepository.name) + private readonly balanceSheetRepository: BalanceSheetRepository, + ) {} + + /** + * Retrieve balance sheet statement. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @return {Promise} + */ + public async balanceSheet( + query: IBalanceSheetQuery, + ): Promise { + const filter = { + ...getBalanceSheetDefaultQuery(), + ...query, + }; + const tenantMetadata = await this.tenancyContext.getTenantMetadata(true); + + // Loads all resources. + await this.balanceSheetRepository.asyncInitialize(filter); + + // Balance sheet report instance. + const balanceSheetInstanace = new BalanceSheet( + filter, + this.balanceSheetRepository, + tenantMetadata.baseCurrency, + this.i18n, + ); + // Balance sheet data. + const data = balanceSheetInstanace.reportData(); + + // Balance sheet meta. + const meta = await this.balanceSheetMeta.meta(filter); + + // Triggers `onBalanceSheetViewed` event. + await this.eventPublisher.emitAsync(events.reports.onBalanceSheetViewed, { + query, + }); + return { + query: filter, + data, + meta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetMeta.ts b/temp/BalanceSheet/BalanceSheetMeta.ts similarity index 58% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetMeta.ts rename to temp/BalanceSheet/BalanceSheetMeta.ts index c7a3e87c0..76a057361 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetMeta.ts +++ b/temp/BalanceSheet/BalanceSheetMeta.ts @@ -1,12 +1,13 @@ -import { Inject, Service } from 'typedi'; -import { FinancialSheetMeta } from '../FinancialSheetMeta'; -import { IBalanceSheetMeta, IBalanceSheetQuery } from '@/interfaces'; -import moment from 'moment'; +import { Injectable } from '@nestjs/common'; +import * as moment from 'moment'; +import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; +import { IBalanceSheetMeta, IBalanceSheetQuery } from './BalanceSheet.types'; -@Service() +@Injectable() export class BalanceSheetMetaInjectable { - @Inject() - private financialSheetMeta: FinancialSheetMeta; + constructor( + private readonly financialSheetMeta: FinancialSheetMeta, + ) {} /** * Retrieve the balance sheet meta. @@ -14,10 +15,9 @@ export class BalanceSheetMetaInjectable { * @returns {IBalanceSheetMeta} */ public async meta( - tenantId: number, query: IBalanceSheetQuery ): Promise { - const commonMeta = await this.financialSheetMeta.meta(tenantId); + const commonMeta = await this.financialSheetMeta.meta(); const formattedAsDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedDateRange = `As ${formattedAsDate}`; const sheetName = 'Balance Sheet Statement'; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncome.ts b/temp/BalanceSheet/BalanceSheetNetIncome.ts similarity index 87% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncome.ts rename to temp/BalanceSheet/BalanceSheetNetIncome.ts index a6695b370..e0c0a8151 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncome.ts +++ b/temp/BalanceSheet/BalanceSheetNetIncome.ts @@ -6,12 +6,12 @@ import { IBalanceSheetSchemaNetIncomeNode, IBalanceSheetSchemaNode, IBalanceSheetTotalPeriod, -} from '@/interfaces'; +} from './BalanceSheet.types'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; -import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod'; -import { FinancialHorizTotals } from '../FinancialHorizTotals'; -import BalanceSheetRepository from './BalanceSheetRepository'; +import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; +import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP'; import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY'; @@ -25,14 +25,14 @@ export const BalanceSheetNetIncome = (Base: any) => FinancialPreviousPeriod, FinancialHorizTotals )(Base) { - private repository: BalanceSheetRepository; - private query: BalanceSheetQuery; + public repository: BalanceSheetRepository; + public query: BalanceSheetQuery; /** * Retrieves the closing balance of income accounts. * @returns {number} */ - private getIncomeTotal = () => { + public getIncomeTotal = () => { const closeingBalance = this.repository.incomeLedger.getClosingBalance(); return closeingBalance; }; @@ -41,7 +41,7 @@ export const BalanceSheetNetIncome = (Base: any) => * Retrieves the closing balance of expenses accounts. * @returns {number} */ - private getExpensesTotal = () => { + public getExpensesTotal = () => { const closingBalance = this.repository.expensesLedger.getClosingBalance(); return closingBalance; }; @@ -50,7 +50,7 @@ export const BalanceSheetNetIncome = (Base: any) => * Retrieves the total net income. * @returns {number} */ - protected getNetIncomeTotal = () => { + public getNetIncomeTotal = () => { const income = this.getIncomeTotal(); const expenses = this.getExpensesTotal(); @@ -62,7 +62,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {IBalanceSheetSchemaNetIncomeNode} node - Schema node. * @return {IBalanceSheetAggregateNode} */ - protected schemaNetIncomeNodeMapper = ( + public schemaNetIncomeNodeMapper = ( node: IBalanceSheetSchemaNetIncomeNode ): IBalanceSheetNetIncomeNode => { const total = this.getNetIncomeTotal(); @@ -80,7 +80,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {IBalanceSheetSchemaNetIncomeNode} node * @returns {IBalanceSheetNetIncomeNode} */ - protected schemaNetIncomeNodeCompose = ( + public schemaNetIncomeNodeCompose = ( node: IBalanceSheetSchemaNetIncomeNode ): IBalanceSheetNetIncomeNode => { return R.compose( @@ -109,7 +109,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {Date} toDate - * @returns {number} */ - private getIncomeDatePeriodTotal = (toDate: Date): number => { + public getIncomeDatePeriodTotal = (toDate: Date): number => { const periodTotalBetween = this.repository.incomePeriodsAccountsLedger .whereToDate(toDate) .getClosingBalance(); @@ -126,7 +126,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {Date} toDate - * @returns {number} */ - private getExpensesDatePeriodTotal = (toDate: Date): number => { + public getExpensesDatePeriodTotal = (toDate: Date): number => { const periodTotalBetween = this.repository.expensesPeriodsAccountsLedger .whereToDate(toDate) .getClosingBalance(); @@ -143,7 +143,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {Date} toDate * @returns {number} */ - private getNetIncomeDatePeriodTotal = (toDate: Date): number => { + public getNetIncomeDatePeriodTotal = (toDate: Date): number => { const income = this.getIncomeDatePeriodTotal(toDate); const expense = this.getExpensesDatePeriodTotal(toDate); @@ -157,7 +157,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {Date} toDate * @returns {IBalanceSheetNetIncomeNode} */ - private getNetIncomeDatePeriodNode = ( + public getNetIncomeDatePeriodNode = ( node: IBalanceSheetNetIncomeNode, fromDate: Date, toDate: Date @@ -172,7 +172,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {IBalanceSheetNetIncomeNode} node * @returns {IBalanceSheetNetIncomeNode} */ - private getNetIncomeDatePeriodsNode = ( + public getNetIncomeDatePeriodsNode = ( node: IBalanceSheetNetIncomeNode ): IBalanceSheetTotalPeriod[] => { return this.getReportNodeDatePeriods( @@ -202,7 +202,7 @@ export const BalanceSheetNetIncome = (Base: any) => * @param {IBalanceSheetSchemaNode} node - Schema node. * @return {IBalanceSheetDataNode} */ - private reportNetIncomeNodeSchemaParser = ( + public reportNetIncomeNodeSchemaParser = ( schemaNode: IBalanceSheetSchemaNode ): IBalanceSheetDataNode => { return R.compose( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriods.ts b/temp/BalanceSheet/BalanceSheetNetIncomeDatePeriods.ts similarity index 94% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriods.ts rename to temp/BalanceSheet/BalanceSheetNetIncomeDatePeriods.ts index 63bfa362a..e81d17c95 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriods.ts +++ b/temp/BalanceSheet/BalanceSheetNetIncomeDatePeriods.ts @@ -5,9 +5,9 @@ import { } from '@/interfaces'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; -import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod'; -import { FinancialHorizTotals } from '../FinancialHorizTotals'; -import BalanceSheetRepository from './BalanceSheetRepository'; +import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; +import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetNetIncomePP } from './BalanceSheetNetIncomePP'; import { BalanceSheetNetIncomePY } from './BalanceSheetNetIncomePY'; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPP.ts b/temp/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPP.ts similarity index 76% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPP.ts rename to temp/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPP.ts index 94038de38..f83849021 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPP.ts +++ b/temp/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPP.ts @@ -1,8 +1,11 @@ import * as R from 'ramda'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; -import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod'; -import { FinancialHorizTotals } from '../FinancialHorizTotals'; -import { IBalanceSheetNetIncomeNode, IBalanceSheetTotal } from '@/interfaces'; +import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; +import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; +import { + IBalanceSheetNetIncomeNode, + IBalanceSheetTotal, +} from './BalanceSheet.types'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import BalanceSheetRepository from './BalanceSheetRepository'; @@ -10,7 +13,7 @@ export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => class extends R.compose( BalanceSheetComparsionPreviousPeriod, FinancialPreviousPeriod, - FinancialHorizTotals + FinancialHorizTotals, )(Base) { query: BalanceSheetQuery; repository: BalanceSheetRepository; @@ -21,7 +24,7 @@ export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => * @param {Date} toDate - * @return {number} */ - private getPPIncomeDatePeriodTotal = R.curry((toDate: Date) => { + public getPPIncomeDatePeriodTotal = R.curry((toDate: Date) => { const PYPeriodsTotal = this.repository.incomePPPeriodsAccountsLedger .whereToDate(toDate) .getClosingBalance(); @@ -38,7 +41,7 @@ export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => * @param {Date} toDate - * @returns {number} */ - private getPPExpenseDatePeriodTotal = R.curry((toDate: Date) => { + public getPPExpenseDatePeriodTotal = R.curry((toDate: Date) => { const PYPeriodsTotal = this.repository.expensePPPeriodsAccountsLedger .whereToDate(toDate) .getClosingBalance(); @@ -55,7 +58,7 @@ export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => * @param {Date} toDate - To date. * @returns {number} */ - private getPPNetIncomeDatePeriodTotal = R.curry((toDate: Date) => { + public getPPNetIncomeDatePeriodTotal = R.curry((toDate: Date) => { const income = this.getPPIncomeDatePeriodTotal(toDate); const expense = this.getPPExpenseDatePeriodTotal(toDate); @@ -67,13 +70,13 @@ export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => * @param {IBalanceSheetAccountNode} node * @returns {} */ - private assocPreviousPeriodNetIncomeHorizTotal = R.curry( + public assocPreviousPeriodNetIncomeHorizTotal = R.curry( (node: IBalanceSheetNetIncomeNode, totalNode) => { const total = this.getPPNetIncomeDatePeriodTotal( - totalNode.previousPeriodToDate.date + totalNode.previousPeriodToDate.date, ); return R.assoc('previousPeriod', this.getAmountMeta(total), totalNode); - } + }, ); /** @@ -81,32 +84,32 @@ export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => * @param {IBalanceSheetTotal} node * @returns {IBalanceSheetTotal} */ - private previousPeriodNetIncomeHorizNodeComposer = R.curry( + public previousPeriodNetIncomeHorizNodeComposer = R.curry( ( node: IBalanceSheetNetIncomeNode, - horiontalTotalNode: IBalanceSheetTotal + horiontalTotalNode: IBalanceSheetTotal, ): IBalanceSheetTotal => { return R.compose( R.when( this.query.isPreviousPeriodPercentageActive, - this.assocPreviousPeriodTotalPercentageNode + this.assocPreviousPeriodTotalPercentageNode, ), R.when( this.query.isPreviousPeriodChangeActive, - this.assocPreviousPeriodTotalChangeNode + this.assocPreviousPeriodTotalChangeNode, ), R.when( this.query.isPreviousPeriodActive, - this.assocPreviousPeriodNetIncomeHorizTotal(node) + this.assocPreviousPeriodNetIncomeHorizTotal(node), ), R.when( this.query.isPreviousPeriodActive, this.assocPreviousPeriodHorizNodeFromToDates( - this.query.displayColumnsBy - ) - ) + this.query.displayColumnsBy, + ), + ), )(horiontalTotalNode); - } + }, ); /** @@ -115,11 +118,11 @@ export const BalanceSheetNetIncomeDatePeriodsPP = (Base: any) => * @returns {IBalanceSheetCommonNode} */ public assocPreviousPeriodNetIncomeHorizNode = ( - node: IBalanceSheetNetIncomeNode + node: IBalanceSheetNetIncomeNode, ): IBalanceSheetNetIncomeNode => { const horizontalTotals = R.addIndex(R.map)( this.previousPeriodNetIncomeHorizNodeComposer(node), - node.horizontalTotals + node.horizontalTotals, ) as IBalanceSheetTotal[]; return R.assoc('horizontalTotals', horizontalTotals, node); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPY.ts b/temp/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPY.ts similarity index 87% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPY.ts rename to temp/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPY.ts index 3e4a289e9..ed6d86c4d 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPY.ts +++ b/temp/BalanceSheet/BalanceSheetNetIncomeDatePeriodsPY.ts @@ -1,8 +1,8 @@ import * as R from 'ramda'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; -import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod'; -import { FinancialHorizTotals } from '../FinancialHorizTotals'; -import { IBalanceSheetNetIncomeNode, IBalanceSheetTotal } from '@/interfaces'; +import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; +import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; +import { IBalanceSheetNetIncomeNode, IBalanceSheetTotal } from './BalanceSheet.types'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import BalanceSheetRepository from './BalanceSheetRepository'; @@ -20,7 +20,7 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) => * @param {Date} toDate - * @return {number} */ - private getPYIncomeDatePeriodTotal = R.curry((toDate: Date) => { + public getPYIncomeDatePeriodTotal = R.curry((toDate: Date) => { const PYPeriodsTotal = this.repository.incomePYPeriodsAccountsLedger .whereToDate(toDate) .getClosingBalance(); @@ -36,7 +36,7 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) => * @param {Date} toDate - * @returns {number} */ - private getPYExpenseDatePeriodTotal = R.curry((toDate: Date) => { + public getPYExpenseDatePeriodTotal = R.curry((toDate: Date) => { const PYPeriodsTotal = this.repository.expensePYPeriodsAccountsLedger .whereToDate(toDate) .getClosingBalance(); @@ -52,7 +52,7 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) => * @param {Date} toDate - To date. * @returns {number} */ - private getPYNetIncomeDatePeriodTotal = R.curry((toDate: Date) => { + public getPYNetIncomeDatePeriodTotal = R.curry((toDate: Date) => { const income = this.getPYIncomeDatePeriodTotal(toDate); const expense = this.getPYExpenseDatePeriodTotal(toDate); @@ -64,7 +64,7 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) => * @param {IBalanceSheetAccountNode} node * @returns {} */ - private assocPreviousYearNetIncomeHorizTotal = R.curry( + public assocPreviousYearNetIncomeHorizTotal = R.curry( (node: IBalanceSheetNetIncomeNode, totalNode) => { const total = this.getPYNetIncomeDatePeriodTotal( totalNode.previousYearToDate.date @@ -78,7 +78,7 @@ export const BalanceSheetNetIncomeDatePeriodsPY = (Base: any) => * @param {IBalanceSheetTotal} node * @returns {IBalanceSheetTotal} */ - private previousYearNetIncomeHorizNodeComposer = R.curry( + public previousYearNetIncomeHorizNodeComposer = R.curry( ( node: IBalanceSheetNetIncomeNode, horiontalTotalNode: IBalanceSheetTotal diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomePP.ts b/temp/BalanceSheet/BalanceSheetNetIncomePP.ts similarity index 68% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomePP.ts rename to temp/BalanceSheet/BalanceSheetNetIncomePP.ts index c377511a3..c3684cff1 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomePP.ts +++ b/temp/BalanceSheet/BalanceSheetNetIncomePP.ts @@ -2,11 +2,11 @@ import * as R from 'ramda'; import { IBalanceSheetDataNode, IBalanceSheetNetIncomeNode, -} from '@/interfaces'; +} from './BalanceSheet.types'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; -import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod'; -import { FinancialHorizTotals } from '../FinancialHorizTotals'; -import BalanceSheetRepository from './BalanceSheetRepository'; +import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; +import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetNetIncomeDatePeriodsPP } from './BalanceSheetNetIncomeDatePeriodsPP'; @@ -15,10 +15,10 @@ export const BalanceSheetNetIncomePP = (Base: any) => BalanceSheetNetIncomeDatePeriodsPP, BalanceSheetComparsionPreviousPeriod, FinancialPreviousPeriod, - FinancialHorizTotals + FinancialHorizTotals, )(Base) { - private repository: BalanceSheetRepository; - private query: BalanceSheetQuery; + public repository: BalanceSheetRepository; + public query: BalanceSheetQuery; // ------------------------------- // # Previous Period (PP) @@ -27,7 +27,7 @@ export const BalanceSheetNetIncomePP = (Base: any) => * Retrieves the PP net income. * @returns {} */ - protected getPreviousPeriodNetIncome = () => { + public getPreviousPeriodNetIncome = () => { const income = this.repository.incomePPAccountsLedger.getClosingBalance(); const expense = this.repository.expensePPAccountsLedger.getClosingBalance(); @@ -40,8 +40,8 @@ export const BalanceSheetNetIncomePP = (Base: any) => * @param {IBalanceSheetDataNode} node * @returns {IBalanceSheetDataNode} */ - protected assocPreviousPeriodNetIncomeNode = ( - node: IBalanceSheetDataNode + public assocPreviousPeriodNetIncomeNode = ( + node: IBalanceSheetDataNode, ): IBalanceSheetDataNode => { const total = this.getPreviousPeriodNetIncome(); @@ -53,23 +53,23 @@ export const BalanceSheetNetIncomePP = (Base: any) => * @param {IBalanceSheetNetIncomeNode} node * @returns {IBalanceSheetNetIncomeNode} */ - protected previousPeriodNetIncomeNodeCompose = ( - node: IBalanceSheetNetIncomeNode + public previousPeriodNetIncomeNodeCompose = ( + node: IBalanceSheetNetIncomeNode, ): IBalanceSheetNetIncomeNode => { return R.compose( R.when( this.isNodeHasHorizTotals, - this.assocPreviousPeriodNetIncomeHorizNode + this.assocPreviousPeriodNetIncomeHorizNode, ), R.when( this.query.isPreviousPeriodPercentageActive, - this.assocPreviousPeriodPercentageNode + this.assocPreviousPeriodPercentageNode, ), R.when( this.query.isPreviousPeriodChangeActive, - this.assocPreviousPeriodChangeNode + this.assocPreviousPeriodChangeNode, ), - this.assocPreviousPeriodNetIncomeNode + this.assocPreviousPeriodNetIncomeNode, )(node); }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomePY.ts b/temp/BalanceSheet/BalanceSheetNetIncomePY.ts similarity index 82% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomePY.ts rename to temp/BalanceSheet/BalanceSheetNetIncomePY.ts index 29d57813c..b99010845 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetNetIncomePY.ts +++ b/temp/BalanceSheet/BalanceSheetNetIncomePY.ts @@ -2,12 +2,12 @@ import * as R from 'ramda'; import { IBalanceSheetDataNode, IBalanceSheetNetIncomeNode, -} from '@/interfaces'; +} from './BalanceSheet.types'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; -import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod'; -import { FinancialHorizTotals } from '../FinancialHorizTotals'; -import BalanceSheetRepository from './BalanceSheetRepository'; +import { FinancialPreviousPeriod } from '../../common/FinancialPreviousPeriod'; +import { FinancialHorizTotals } from '../../common/FinancialHorizTotals'; +import { BalanceSheetRepository } from './BalanceSheetRepository'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetNetIncomeDatePeriodsPY } from './BalanceSheetNetIncomeDatePeriodsPY'; @@ -19,8 +19,8 @@ export const BalanceSheetNetIncomePY = (Base: any) => FinancialPreviousPeriod, FinancialHorizTotals )(Base) { - private repository: BalanceSheetRepository; - private query: BalanceSheetQuery; + public repository: BalanceSheetRepository; + public query: BalanceSheetQuery; // ------------------------------ // # Previous Year (PY) @@ -29,7 +29,7 @@ export const BalanceSheetNetIncomePY = (Base: any) => * Retrieves the previous year (PY) net income. * @returns {number} */ - protected getPreviousYearNetIncome = () => { + public getPreviousYearNetIncome = () => { const income = this.repository.incomePYTotalAccountsLedger.getClosingBalance(); const expense = @@ -43,7 +43,7 @@ export const BalanceSheetNetIncomePY = (Base: any) => * @param {IBalanceSheetAccountNode} node * @returns {IBalanceSheetAccountNode} */ - protected assocPreviousYearNetIncomeNode = ( + public assocPreviousYearNetIncomeNode = ( node: IBalanceSheetNetIncomeNode ): IBalanceSheetNetIncomeNode => { const total = this.getPreviousYearNetIncome(); @@ -56,7 +56,7 @@ export const BalanceSheetNetIncomePY = (Base: any) => * @param {IBalanceSheetAccountNode} node * @returns {IBalanceSheetAccountNode} */ - protected previousYearNetIncomeNodeCompose = ( + public previousYearNetIncomeNodeCompose = ( node: IBalanceSheetNetIncomeNode ): IBalanceSheetNetIncomeNode => { return R.compose( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetPdfInjectable.ts b/temp/BalanceSheet/BalanceSheetPdfInjectable.ts similarity index 93% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetPdfInjectable.ts rename to temp/BalanceSheet/BalanceSheetPdfInjectable.ts index 6727e57d4..3589d599a 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetPdfInjectable.ts +++ b/temp/BalanceSheet/BalanceSheetPdfInjectable.ts @@ -1,4 +1,4 @@ -import { TableSheetPdf } from '../../TableSheetPdf'; +import { TableSheetPdf } from '../../common/TableSheetPdf'; import { IBalanceSheetQuery } from './BalanceSheet.types'; import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable'; import { HtmlTableCustomCss } from './constants'; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetPercentage.ts b/temp/BalanceSheet/BalanceSheetPercentage.ts similarity index 98% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetPercentage.ts rename to temp/BalanceSheet/BalanceSheetPercentage.ts index 4c97ca1d4..f946d5693 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetPercentage.ts +++ b/temp/BalanceSheet/BalanceSheetPercentage.ts @@ -4,7 +4,7 @@ import { BalanceSheetQuery } from './BalanceSheetQuery'; import { IBalanceSheetDataNode } from './BalanceSheet.types'; import { Constructor } from '@/common/types/Constructor'; -export const BalanceSheetPercentage = (Base: any) => +export const BalanceSheetPercentage = (Base: T) => class extends Base { readonly query: BalanceSheetQuery; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetQuery.ts b/temp/BalanceSheet/BalanceSheetQuery.ts similarity index 95% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetQuery.ts rename to temp/BalanceSheet/BalanceSheetQuery.ts index 1a636952a..fdfd94bab 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetQuery.ts +++ b/temp/BalanceSheet/BalanceSheetQuery.ts @@ -1,11 +1,14 @@ import { merge } from 'lodash'; import * as R from 'ramda'; -import { IBalanceSheetQuery, IFinancialDatePeriodsUnit } from '@/interfaces'; -import { FinancialDateRanges } from '../FinancialDateRanges'; +import { + IBalanceSheetQuery, + IFinancialDatePeriodsUnit, +} from './BalanceSheet.types'; +import { FinancialDateRanges } from '../../common/FinancialDateRanges'; import { DISPLAY_COLUMNS_BY } from './constants'; export class BalanceSheetQuery extends R.compose(FinancialDateRanges)( - class {} + class {}, ) { /** * Balance sheet query. @@ -53,7 +56,7 @@ export class BalanceSheetQuery extends R.compose(FinancialDateRanges)( if (this.isTotalColumnType()) { const { fromDate, toDate } = this.getPPTotalDateRange( this.query.fromDate, - this.query.toDate + this.query.toDate, ); this.PPToDate = toDate; this.PPFromDate = fromDate; @@ -62,7 +65,7 @@ export class BalanceSheetQuery extends R.compose(FinancialDateRanges)( const { fromDate, toDate } = this.getPPDatePeriodDateRange( this.query.fromDate, this.query.toDate, - this.query.displayColumnsBy as IFinancialDatePeriodsUnit + this.query.displayColumnsBy as IFinancialDatePeriodsUnit, ); this.PPToDate = toDate; this.PPFromDate = fromDate; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetRepository.ts b/temp/BalanceSheet/BalanceSheetRepository.ts similarity index 84% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetRepository.ts rename to temp/BalanceSheet/BalanceSheetRepository.ts index 75a257191..0e2a42729 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetRepository.ts +++ b/temp/BalanceSheet/BalanceSheetRepository.ts @@ -1,27 +1,34 @@ -import { Service } from 'typedi'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import * as R from 'ramda'; import { Knex } from 'knex'; import { isEmpty } from 'lodash'; import { IAccountTransactionsGroupBy, IBalanceSheetQuery, - ILedger, -} from '@/interfaces'; -import { transformToMapBy } from 'utils'; -import Ledger from '@/services/Accounting/Ledger'; +} from './BalanceSheet.types'; import { BalanceSheetQuery } from './BalanceSheetQuery'; -import { FinancialDatePeriods } from '../FinancialDatePeriods'; +import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome'; +import { ILedger } from '@/modules/Ledger/types/Ledger.types'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { transformToMapBy } from '@/utils/transform-to-map-by'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; -@Service() -export default class BalanceSheetRepository extends R.compose( + +@Injectable({ scope: Scope.TRANSIENT }) +export class BalanceSheetRepository extends R.compose( BalanceSheetRepositoryNetIncome, FinancialDatePeriods )(class {}) { /** - * + * */ - private readonly models; + @Inject(Account.name) + public readonly accountModel: typeof Account; + + @Inject(AccountTransaction.name) + public readonly accountTransactionModel: typeof AccountTransaction; /** * @param {number} @@ -149,11 +156,8 @@ export default class BalanceSheetRepository extends R.compose( * @param {number} tenantId * @param {IBalanceSheetQuery} query */ - constructor(models: any, query: IBalanceSheetQuery) { - super(); - + public setQuery(query: IBalanceSheetQuery) { this.query = new BalanceSheetQuery(query); - this.models = models; this.transactionsGroupType = this.getGroupByFromDisplayColumnsBy( this.query.displayColumnsBy @@ -164,7 +168,9 @@ export default class BalanceSheetRepository extends R.compose( * Async initialize. * @returns {Promise} */ - public asyncInitialize = async () => { + public asyncInitialize = async (query: IBalanceSheetQuery) => { + this.setQuery(query); + await this.initAccounts(); await this.initAccountsGraph(); @@ -213,9 +219,7 @@ export default class BalanceSheetRepository extends R.compose( * Initialize accounts graph. */ public initAccountsGraph = async () => { - const { Account } = this.models; - - this.accountsGraph = Account.toDependencyGraph(this.accounts); + this.accountsGraph = this.accountModel.toDependencyGraph(this.accounts); }; // ---------------------------- @@ -225,7 +229,7 @@ export default class BalanceSheetRepository extends R.compose( * Initialize accounts closing total based on the given query. * @returns {Promise} */ - private initAccountsTotalLedger = async (): Promise => { + public initAccountsTotalLedger = async (): Promise => { const totalByAccount = await this.closingAccountsTotal(this.query.toDate); // Inject to the repository. @@ -264,7 +268,7 @@ export default class BalanceSheetRepository extends R.compose( * Initialize total of previous year. * @returns {Promise} */ - private initTotalPreviousYear = async (): Promise => { + public initTotalPreviousYear = async (): Promise => { const PYTotalsByAccounts = await this.closingAccountsTotal( this.query.PYToDate ); @@ -276,7 +280,7 @@ export default class BalanceSheetRepository extends R.compose( * Initialize date periods of previous year. * @returns {Promise} */ - private initPeriodsPreviousYear = async (): Promise => { + public initPeriodsPreviousYear = async (): Promise => { const PYPeriodsBYAccounts = await this.accountsDatePeriods( this.query.PYFromDate, this.query.PYToDate, @@ -300,7 +304,7 @@ export default class BalanceSheetRepository extends R.compose( * Initialize total of previous year. * @returns {Promise} */ - private initTotalPreviousPeriod = async (): Promise => { + public initTotalPreviousPeriod = async (): Promise => { const PPTotalsByAccounts = await this.closingAccountsTotal( this.query.PPToDate ); @@ -312,7 +316,7 @@ export default class BalanceSheetRepository extends R.compose( * Initialize date periods of previous year. * @returns {Promise} */ - private initPeriodsPreviousPeriod = async (): Promise => { + public initPeriodsPreviousPeriod = async (): Promise => { const PPPeriodsBYAccounts = await this.accountsDatePeriods( this.query.PPFromDate, this.query.PPToDate, @@ -336,10 +340,8 @@ export default class BalanceSheetRepository extends R.compose( * Retrieve accounts of the report. * @return {Promise} */ - private getAccounts = () => { - const { Account } = this.models; - - return Account.query(); + public getAccounts = () => { + return this.accountModel.query(); }; /** @@ -354,9 +356,7 @@ export default class BalanceSheetRepository extends R.compose( toDate: Date, datePeriodsType: string ) => { - const { AccountTransaction } = this.models; - - return AccountTransaction.query().onBuild((query) => { + return this.accountTransactionModel.query().onBuild((query) => { query.sum('credit as credit'); query.sum('debit as debit'); query.groupBy('accountId'); @@ -375,9 +375,7 @@ export default class BalanceSheetRepository extends R.compose( * @param {Date|string} openingDate - */ public closingAccountsTotal = async (openingDate: Date | string) => { - const { AccountTransaction } = this.models; - - return AccountTransaction.query().onBuild((query) => { + return this.accountTransactionModel.query().onBuild((query) => { query.sum('credit as credit'); query.sum('debit as debit'); query.groupBy('accountId'); @@ -394,7 +392,7 @@ export default class BalanceSheetRepository extends R.compose( * Common branches filter query. * @param {Knex.QueryBuilder} query */ - private commonFilterBranchesQuery = (query: Knex.QueryBuilder) => { + public commonFilterBranchesQuery = (query: Knex.QueryBuilder) => { if (!isEmpty(this.query.branchesIds)) { query.modify('filterByBranches', this.query.branchesIds); } diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetRepositoryNetIncome.ts b/temp/BalanceSheet/BalanceSheetRepositoryNetIncome.ts similarity index 95% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetRepositoryNetIncome.ts rename to temp/BalanceSheet/BalanceSheetRepositoryNetIncome.ts index e551dbb49..5ef58a60e 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetRepositoryNetIncome.ts +++ b/temp/BalanceSheet/BalanceSheetRepositoryNetIncome.ts @@ -4,9 +4,9 @@ import { ModelObject } from 'objection'; import { Account } from '@/modules/Accounts/models/Account.model'; import { ILedger } from '@/modules/Ledger/types/Ledger.types'; import { ACCOUNT_PARENT_TYPE } from '@/constants/accounts'; -import { Constructor } from '@/common/types/Constructor'; +import { GConstructor } from '@/common/types/Constructor'; -export const BalanceSheetRepositoryNetIncome = ( +export const BalanceSheetRepositoryNetIncome = ( Base: T, ) => class extends R.compose(FinancialDatePeriods)(Base) { @@ -89,7 +89,7 @@ export const BalanceSheetRepositoryNetIncome = ( /** * Initialize income accounts. */ - private initIncomeAccounts = () => { + public initIncomeAccounts = () => { const incomeAccounts = this.accountsByParentType.get( ACCOUNT_PARENT_TYPE.INCOME, ); @@ -102,7 +102,7 @@ export const BalanceSheetRepositoryNetIncome = ( /** * Initialize expense accounts. */ - private initExpenseAccounts = () => { + public initExpenseAccounts = () => { const expensesAccounts = this.accountsByParentType.get( ACCOUNT_PARENT_TYPE.EXPENSE, ); @@ -115,7 +115,7 @@ export const BalanceSheetRepositoryNetIncome = ( /** * Initialize the income total ledger. */ - private initIncomeTotalLedger = (): void => { + public initIncomeTotalLedger = (): void => { // Inject to the repository. this.incomeLedger = this.totalAccountsLedger.whereAccountsIds( this.incomeAccountsIds, @@ -125,7 +125,7 @@ export const BalanceSheetRepositoryNetIncome = ( /** * Initialize the expenses total ledger. */ - private initExpensesTotalLedger = (): void => { + public initExpensesTotalLedger = (): void => { this.expensesLedger = this.totalAccountsLedger.whereAccountsIds( this.expenseAccountsIds, ); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetSchema.ts b/temp/BalanceSheet/BalanceSheetSchema.ts similarity index 99% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetSchema.ts rename to temp/BalanceSheet/BalanceSheetSchema.ts index 78cba2d35..7a5ff22d2 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetSchema.ts +++ b/temp/BalanceSheet/BalanceSheetSchema.ts @@ -17,7 +17,7 @@ export const BalanceSheetSchema = (Base: T) => getSchema = () => { return getBalanceSheetSchema(); }; - }; +}; /** * Retrieve the balance sheet report schema. diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTable.ts b/temp/BalanceSheet/BalanceSheetTable.ts similarity index 80% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTable.ts rename to temp/BalanceSheet/BalanceSheetTable.ts index 1190f3c05..d63496e04 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTable.ts +++ b/temp/BalanceSheet/BalanceSheetTable.ts @@ -1,10 +1,7 @@ import * as R from 'ramda'; import { IBalanceSheetStatementData, - ITableColumnAccessor, IBalanceSheetQuery, - ITableColumn, - ITableRow, BALANCE_SHEET_SCHEMA_NODE_TYPE, IBalanceSheetDataNode, IBalanceSheetSchemaNode, @@ -12,45 +9,50 @@ import { IBalanceSheetAccountNode, IBalanceSheetAccountsNode, IBalanceSheetAggregateNode, -} from '@/interfaces'; -import { tableRowMapper } from 'utils'; -import FinancialSheet from '../FinancialSheet'; +} from './BalanceSheet.types'; +import { + ITableColumnAccessor, + ITableColumn, + ITableRow, +} from '../../types/Table.types'; +import { tableRowMapper } from '../../utils/Table.utils'; +import { FinancialSheet } from '../../common/FinancialSheet'; import { BalanceSheetComparsionPreviousYear } from './BalanceSheetComparsionPreviousYear'; import { IROW_TYPE, DISPLAY_COLUMNS_BY } from './constants'; import { BalanceSheetComparsionPreviousPeriod } from './BalanceSheetComparsionPreviousPeriod'; import { BalanceSheetPercentage } from './BalanceSheetPercentage'; -import { FinancialSheetStructure } from '../FinancialSheetStructure'; +import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; import { BalanceSheetBase } from './BalanceSheetBase'; import { BalanceSheetTablePercentage } from './BalanceSheetTablePercentage'; import { BalanceSheetTablePreviousYear } from './BalanceSheetTablePreviousYear'; import { BalanceSheetTablePreviousPeriod } from './BalanceSheetTablePreviousPeriod'; -import { FinancialTable } from '../FinancialTable'; +import { FinancialTable } from '../../common/FinancialTable'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { BalanceSheetTableDatePeriods } from './BalanceSheetTableDatePeriods'; -export class BalanceSheetTable extends R.compose( - BalanceSheetTablePreviousPeriod, - BalanceSheetTablePreviousYear, - BalanceSheetTableDatePeriods, - BalanceSheetTablePercentage, - BalanceSheetComparsionPreviousYear, - BalanceSheetComparsionPreviousPeriod, - BalanceSheetPercentage, - FinancialSheetStructure, +export class BalanceSheetTable extends R.pipe( + BalanceSheetBase, FinancialTable, - BalanceSheetBase + FinancialSheetStructure, + BalanceSheetPercentage, + BalanceSheetComparsionPreviousPeriod, + BalanceSheetComparsionPreviousYear, + BalanceSheetTablePercentage, + BalanceSheetTableDatePeriods, + BalanceSheetTablePreviousYear, + BalanceSheetTablePreviousPeriod, )(FinancialSheet) { /** * Balance sheet data. * @param {IBalanceSheetStatementData} */ - private reportData: IBalanceSheetStatementData; + public reportData: IBalanceSheetStatementData; /** * Balance sheet query. * @parma {BalanceSheetQuery} */ - private query: BalanceSheetQuery; + public query: BalanceSheetQuery; /** * Constructor method. @@ -60,7 +62,7 @@ export class BalanceSheetTable extends R.compose( constructor( reportData: IBalanceSheetStatementData, query: IBalanceSheetQuery, - i18n: any + i18n: any, ) { super(); @@ -75,10 +77,10 @@ export class BalanceSheetTable extends R.compose( * @param {string} type - * @return {boolean} */ - protected isNodeType = R.curry( + public isNodeType = R.curry( (type: string, node: IBalanceSheetSchemaNode): boolean => { return node.nodeType === type; - } + }, ); // ------------------------- @@ -88,14 +90,14 @@ export class BalanceSheetTable extends R.compose( * Retrieve the common columns for all report nodes. * @param {ITableColumnAccessor[]} */ - private commonColumnsAccessors = (): ITableColumnAccessor[] => { + public commonColumnsAccessors = (): ITableColumnAccessor[] => { return R.compose( R.concat([{ key: 'name', accessor: 'name' }]), R.ifElse( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), R.concat(this.datePeriodsColumnsAccessors()), - R.concat(this.totalColumnAccessor()) - ) + R.concat(this.totalColumnAccessor()), + ), )([]); }; @@ -103,12 +105,12 @@ export class BalanceSheetTable extends R.compose( * Retrieve the total column accessor. * @return {ITableColumnAccessor[]} */ - private totalColumnAccessor = (): ITableColumnAccessor[] => { + public totalColumnAccessor = (): ITableColumnAccessor[] => { return R.pipe( R.concat(this.previousPeriodColumnAccessor()), R.concat(this.previousYearColumnAccessor()), R.concat(this.percentageColumnsAccessor()), - R.concat([{ key: 'total', accessor: 'total.formattedAmount' }]) + R.concat([{ key: 'total', accessor: 'total.formattedAmount' }]), )([]); }; @@ -117,8 +119,8 @@ export class BalanceSheetTable extends R.compose( * @param {IBalanceSheetAggregateNode} node * @returns {ITableRow} */ - private aggregateNodeTableRowsMapper = ( - node: IBalanceSheetAggregateNode + public aggregateNodeTableRowsMapper = ( + node: IBalanceSheetAggregateNode, ): ITableRow => { const columns = this.commonColumnsAccessors(); const meta = { @@ -133,8 +135,8 @@ export class BalanceSheetTable extends R.compose( * @param {IBalanceSheetAccountsNode} node * @returns {ITableRow} */ - private accountsNodeTableRowsMapper = ( - node: IBalanceSheetAccountsNode + public accountsNodeTableRowsMapper = ( + node: IBalanceSheetAccountsNode, ): ITableRow => { const columns = this.commonColumnsAccessors(); const meta = { @@ -149,8 +151,8 @@ export class BalanceSheetTable extends R.compose( * @param {IBalanceSheetAccountNode} node * @returns {ITableRow} */ - private accountNodeTableRowsMapper = ( - node: IBalanceSheetAccountNode + public accountNodeTableRowsMapper = ( + node: IBalanceSheetAccountNode, ): ITableRow => { const columns = this.commonColumnsAccessors(); @@ -166,8 +168,8 @@ export class BalanceSheetTable extends R.compose( * @param {IBalanceSheetNetIncomeNode} node * @returns {ITableRow} */ - private netIncomeNodeTableRowsMapper = ( - node: IBalanceSheetNetIncomeNode + public netIncomeNodeTableRowsMapper = ( + node: IBalanceSheetNetIncomeNode, ): ITableRow => { const columns = this.commonColumnsAccessors(); const meta = { @@ -182,7 +184,7 @@ export class BalanceSheetTable extends R.compose( * @param {IBalanceSheetDataNode} node - * @returns {ITableRow} */ - private nodeToTableRowsMapper = (node: IBalanceSheetDataNode): ITableRow => { + public nodeToTableRowsMapper = (node: IBalanceSheetDataNode): ITableRow => { return R.cond([ [ this.isNodeType(BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE), @@ -208,25 +210,27 @@ export class BalanceSheetTable extends R.compose( * @param {IBalanceSheetDataNode[]} nodes - * @return {ITableRow} */ - private nodesToTableRowsMapper = ( - nodes: IBalanceSheetDataNode[] + public nodesToTableRowsMapper = ( + nodes: IBalanceSheetDataNode[], ): ITableRow[] => { return this.mapNodesDeep(nodes, this.nodeToTableRowsMapper); }; /** * Retrieves the total children columns. - * @returns {ITableColumn[]} + * @returns {ITableColumn[]} */ - private totalColumnChildren = (): ITableColumn[] => { + public totalColumnChildren = (): ITableColumn[] => { return R.compose( R.unless( R.isEmpty, - R.concat([{ key: 'total', label: this.i18n.__('balance_sheet.total') }]) + R.concat([ + { key: 'total', label: this.i18n.__('balance_sheet.total') }, + ]), ), R.concat(this.percentageColumns()), R.concat(this.getPreviousYearColumns()), - R.concat(this.previousPeriodColumns()) + R.concat(this.previousPeriodColumns()), )([]); }; @@ -234,7 +238,7 @@ export class BalanceSheetTable extends R.compose( * Retrieve the total column. * @returns {ITableColumn[]} */ - private totalColumn = (): ITableColumn[] => { + public totalColumn = (): ITableColumn[] => { return [ { key: 'total', @@ -251,7 +255,7 @@ export class BalanceSheetTable extends R.compose( public tableRows = (): ITableRow[] => { return R.compose( this.addTotalRows, - this.nodesToTableRowsMapper + this.nodesToTableRowsMapper, )(this.reportData); }; @@ -271,8 +275,8 @@ export class BalanceSheetTable extends R.compose( R.ifElse( this.query.isDatePeriodsColumnsType, R.concat(this.datePeriodsColumns()), - R.concat(this.totalColumn()) - ) + R.concat(this.totalColumn()), + ), )([]); }; } diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTableDatePeriods.ts b/temp/BalanceSheet/BalanceSheetTableDatePeriods.ts similarity index 83% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTableDatePeriods.ts rename to temp/BalanceSheet/BalanceSheetTableDatePeriods.ts index 3e195aa24..1f6b1be34 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTableDatePeriods.ts +++ b/temp/BalanceSheet/BalanceSheetTableDatePeriods.ts @@ -1,14 +1,14 @@ import * as R from 'ramda'; -import moment from 'moment'; +import * as moment from 'moment'; import { ITableColumn, - IDateRange, - ICashFlowDateRange, ITableColumnAccessor, -} from '@/interfaces'; -import { FinancialDatePeriods } from '../FinancialDatePeriods'; +} from '../../types/Table.types'; +import { FinancialDatePeriods } from '../../common/FinancialDatePeriods'; +import { IDateRange } from '../CashFlow/Cashflow.types'; +import { Constructor } from '@/common/types/Constructor'; -export const BalanceSheetTableDatePeriods = (Base) => +export const BalanceSheetTableDatePeriods = (Base: T) => class extends R.compose(FinancialDatePeriods)(Base) { /** * Retrieves the date periods based on the report query. @@ -27,7 +27,7 @@ export const BalanceSheetTableDatePeriods = (Base) => * @param {ICashFlowDateRange} dateRange - * @return {string} */ - private formatColumnLabel = (dateRange: ICashFlowDateRange) => { + public formatColumnLabel = (dateRange: ICashFlowDateRange) => { const monthFormat = (range) => moment(range.toDate).format('YYYY-MM'); const yearFormat = (range) => moment(range.toDate).format('YYYY'); const dayFormat = (range) => moment(range.toDate).format('YYYY-MM-DD'); @@ -57,7 +57,7 @@ export const BalanceSheetTableDatePeriods = (Base) => * @param {IDateRange} dateRange - * @param {number} index - */ - private datePeriodColumnsAccessor = R.curry( + public datePeriodColumnsAccessor = R.curry( (dateRange: IDateRange, index: number) => { return R.pipe( R.concat(this.previousPeriodHorizColumnAccessors(index)), @@ -77,7 +77,7 @@ export const BalanceSheetTableDatePeriods = (Base) => * Retrieve the date periods columns accessors. * @returns {ITableColumnAccessor[]} */ - protected datePeriodsColumnsAccessors = (): ITableColumnAccessor[] => { + public datePeriodsColumnsAccessors = (): ITableColumnAccessor[] => { return R.compose( R.flatten, R.addIndex(R.map)(this.datePeriodColumnsAccessor) @@ -93,7 +93,7 @@ export const BalanceSheetTableDatePeriods = (Base) => * @param {} dateRange * @returns {} */ - private datePeriodChildrenColumns = ( + public datePeriodChildrenColumns = ( index: number, dateRange: IDateRange ) => { @@ -116,7 +116,7 @@ export const BalanceSheetTableDatePeriods = (Base) => * @param index * @returns */ - private datePeriodColumn = ( + public datePeriodColumn = ( dateRange: IDateRange, index: number ): ITableColumn => { @@ -131,7 +131,7 @@ export const BalanceSheetTableDatePeriods = (Base) => * Date periods columns. * @returns {ITableColumn[]} */ - protected datePeriodsColumns = (): ITableColumn[] => { + public datePeriodsColumns = (): ITableColumn[] => { return this.datePeriods.map(this.datePeriodColumn); }; }; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTableInjectable.ts b/temp/BalanceSheet/BalanceSheetTableInjectable.ts similarity index 100% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTableInjectable.ts rename to temp/BalanceSheet/BalanceSheetTableInjectable.ts index 48b33bbed..627776a22 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTableInjectable.ts +++ b/temp/BalanceSheet/BalanceSheetTableInjectable.ts @@ -1,8 +1,8 @@ +import { Injectable } from '@nestjs/common'; +import { I18nService } from 'nestjs-i18n'; import { BalanceSheetInjectable } from './BalanceSheetInjectable'; import { BalanceSheetTable } from './BalanceSheetTable'; import { IBalanceSheetQuery, IBalanceSheetTable } from './BalanceSheet.types'; -import { Injectable } from '@nestjs/common'; -import { I18nService } from 'nestjs-i18n'; @Injectable() export class BalanceSheetTableInjectable { diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePercentage.ts b/temp/BalanceSheet/BalanceSheetTablePercentage.ts similarity index 71% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePercentage.ts rename to temp/BalanceSheet/BalanceSheetTablePercentage.ts index a7d3f82c4..66b41a206 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePercentage.ts +++ b/temp/BalanceSheet/BalanceSheetTablePercentage.ts @@ -1,8 +1,14 @@ import * as R from 'ramda'; -import { ITableColumn } from '@/interfaces'; +import { ITableColumn } from '../../types/Table.types'; +import { Constructor } from '@/common/types/Constructor'; +import { BalanceSheetQuery } from './BalanceSheetQuery'; +import { I18nService } from 'nestjs-i18n'; + +export const BalanceSheetTablePercentage = (Base: T) => + class BalanceSheetComparsionPreviousYear extends Base { + public readonly query: BalanceSheetQuery; + public readonly i18n: I18nService; -export const BalanceSheetTablePercentage = (Base) => - class extends Base { // -------------------- // # Columns // -------------------- @@ -10,20 +16,20 @@ export const BalanceSheetTablePercentage = (Base) => * Retrieve percentage of column/row columns. * @returns {ITableColumn[]} */ - protected percentageColumns = (): ITableColumn[] => { + public percentageColumns = (): ITableColumn[] => { return R.pipe( R.when( this.query.isColumnsPercentageActive, R.append({ key: 'percentage_of_column', - label: this.i18n.__('balance_sheet.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.__('balance_sheet.percentage_of_row'), + label: this.i18n.t('balance_sheet.percentage_of_row'), }) ) )([]); @@ -36,7 +42,7 @@ export const BalanceSheetTablePercentage = (Base) => * Retrieves percentage of column/row accessors. * @returns {ITableColumn[]} */ - protected percentageColumnsAccessor = (): ITableColumn[] => { + public percentageColumnsAccessor = (): ITableColumn[] => { return R.pipe( R.when( this.query.isColumnsPercentageActive, @@ -60,7 +66,7 @@ export const BalanceSheetTablePercentage = (Base) => * @param {number} index * @returns {ITableColumn[]} */ - protected percetangeDatePeriodColumnsAccessor = ( + public percetangeDatePeriodColumnsAccessor = ( index: number ): ITableColumn[] => { return R.pipe( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousPeriod.ts b/temp/BalanceSheet/BalanceSheetTablePreviousPeriod.ts similarity index 79% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousPeriod.ts rename to temp/BalanceSheet/BalanceSheetTablePreviousPeriod.ts index 1541df9c2..baa726d02 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousPeriod.ts +++ b/temp/BalanceSheet/BalanceSheetTablePreviousPeriod.ts @@ -1,11 +1,13 @@ import * as R from 'ramda'; -import { IDateRange, ITableColumn } from '@/interfaces'; import { BalanceSheetQuery } from './BalanceSheetQuery'; -import { FinancialTablePreviousPeriod } from '../FinancialTablePreviousPeriod'; -import { FinancialDateRanges } from '../FinancialDateRanges'; +import { FinancialTablePreviousPeriod } from '../../common/FinancialTablePreviousPeriod'; +import { FinancialDateRanges } from '../../common/FinancialDateRanges'; +import { IDateRange } from '../../types/Report.types'; +import { ITableColumn } from '../../types/Table.types'; +import { Constructor } from '@/common/types/Constructor'; -export const BalanceSheetTablePreviousPeriod = (Base) => - class extends R.compose( +export const BalanceSheetTablePreviousPeriod = (Base: T) => + class BalanceSheetTablePreviousPeriod extends R.compose( FinancialTablePreviousPeriod, FinancialDateRanges )(Base) { @@ -18,7 +20,7 @@ export const BalanceSheetTablePreviousPeriod = (Base) => * Retrieves the previous period columns. * @returns {ITableColumn[]} */ - protected previousPeriodColumns = ( + public previousPeriodColumns = ( dateRange?: IDateRange ): ITableColumn[] => { return R.pipe( @@ -43,7 +45,7 @@ export const BalanceSheetTablePreviousPeriod = (Base) => * @param {IDateRange} dateRange * @returns {ITableColumn} */ - protected previousPeriodHorizontalColumns = ( + public previousPeriodHorizontalColumns = ( dateRange: IDateRange ): ITableColumn[] => { const PPDateRange = this.getPPDatePeriodDateRange( @@ -64,7 +66,7 @@ export const BalanceSheetTablePreviousPeriod = (Base) => * Retrieves previous period columns accessors. * @returns {ITableColumn[]} */ - protected previousPeriodColumnAccessor = (): ITableColumn[] => { + public previousPeriodColumnAccessor = (): ITableColumn[] => { return R.pipe( // Previous period columns. R.when( @@ -87,7 +89,7 @@ export const BalanceSheetTablePreviousPeriod = (Base) => * @param {number} index * @returns */ - protected previousPeriodHorizColumnAccessors = ( + public previousPeriodHorizColumnAccessors = ( index: number ): ITableColumn[] => { return R.pipe( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousYear.ts b/temp/BalanceSheet/BalanceSheetTablePreviousYear.ts similarity index 100% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTablePreviousYear.ts rename to temp/BalanceSheet/BalanceSheetTablePreviousYear.ts diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTotal.ts b/temp/BalanceSheet/BalanceSheetTotal.ts similarity index 100% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheetTotal.ts rename to temp/BalanceSheet/BalanceSheetTotal.ts diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/constants.ts b/temp/BalanceSheet/constants.ts similarity index 63% rename from packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/constants.ts rename to temp/BalanceSheet/constants.ts index 2081d2859..6c09b3e43 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/BalanceSheet/constants.ts +++ b/temp/BalanceSheet/constants.ts @@ -1,3 +1,5 @@ +import { IBalanceSheetQuery } from "./BalanceSheet.types"; + export const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; export const DISPLAY_COLUMNS_BY = { @@ -62,3 +64,37 @@ table [class*="column--date-range"] { text-align: right; } `; + +export const getBalanceSheetDefaultQuery = (): IBalanceSheetQuery => { + return { + displayColumnsType: 'total', + displayColumnsBy: 'month', + + fromDate: moment().startOf('year').format('YYYY-MM-DD'), + toDate: moment().format('YYYY-MM-DD'), + + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines', + }, + noneZero: false, + noneTransactions: false, + + basis: 'cash', + accountIds: [], + + percentageOfColumn: false, + percentageOfRow: false, + + previousPeriod: false, + previousPeriodAmountChange: false, + previousPeriodPercentageChange: false, + + previousYear: false, + previousYearAmountChange: false, + previousYearPercentageChange: false, + }; +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlow.ts b/temp/CashFlow/CashFlow.ts similarity index 95% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlow.ts rename to temp/CashFlow/CashFlow.ts index 019b252ba..47df3e94f 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlow.ts +++ b/temp/CashFlow/CashFlow.ts @@ -1,12 +1,10 @@ import * as R from 'ramda'; import { defaultTo, map, set, sumBy, isEmpty, mapValues, get } from 'lodash'; import * as mathjs from 'mathjs'; -import moment from 'moment'; +import * as moment from 'moment'; import { compose } from 'lodash/fp'; +import { I18nService } from 'nestjs-i18n'; import { - IAccount, - ILedger, - INumberFormatQuery, ICashFlowSchemaSection, ICashFlowStatementQuery, ICashFlowStatementNetIncomeSection, @@ -21,15 +19,19 @@ import { ICashFlowStatementSection, ICashFlowCashBeginningNode, ICashFlowStatementAggregateSection, -} from '@/interfaces'; -import CASH_FLOW_SCHEMA from './schema'; -import FinancialSheet from '../FinancialSheet'; -import { transformToMapBy, accumSum } from 'utils'; -import { ACCOUNT_ROOT_TYPE } from '@/data/AccountTypes'; +} 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 I18nService from '@/services/I18n/I18nService'; import { DISPLAY_COLUMNS_BY } from './constants'; -import { FinancialSheetStructure } from '../FinancialSheetStructure'; +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, @@ -40,8 +42,8 @@ export default class CashFlowStatement extends compose( readonly sectionsByIds = {}; readonly cashFlowSchemaMap: Map; readonly cashFlowSchemaSeq: Array; - readonly accountByTypeMap: Map; - readonly accountsByRootType: Map; + readonly accountByTypeMap: Map; + readonly accountsByRootType: Map; readonly ledger: ILedger; readonly cashLedger: ILedger; readonly netIncomeLedger: ILedger; @@ -55,7 +57,7 @@ export default class CashFlowStatement extends compose( * @constructor */ constructor( - accounts: IAccount[], + accounts: Account[], ledger: ILedger, cashLedger: ILedger, netIncomeLedger: ILedger, @@ -181,7 +183,7 @@ export default class CashFlowStatement extends compose( */ private accountMetaMapper = ( relation: ICashFlowSchemaAccountRelation, - account: IAccount + account: ModelObject ): ICashFlowStatementAccountMeta => { // Retrieve the closing balance of the given account. const getClosingBalance = (id) => @@ -407,7 +409,7 @@ export default class CashFlowStatement extends compose( */ private cashAccountMetaMapper = ( relation: ICashFlowSchemaAccountRelation, - account: IAccount + account: ModelObject ): ICashFlowStatementAccountMeta => { const cashToDate = this.beginningCashFrom(this.query.fromDate); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowDatePeriods.ts b/temp/CashFlow/CashFlowDatePeriods.ts similarity index 89% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowDatePeriods.ts rename to temp/CashFlow/CashFlowDatePeriods.ts index 7d493cc5d..a6986ab4c 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowDatePeriods.ts +++ b/temp/CashFlow/CashFlowDatePeriods.ts @@ -1,18 +1,19 @@ import * as R from 'ramda'; import { sumBy, mapValues, get } from 'lodash'; -import { ACCOUNT_ROOT_TYPE } from '@/data/AccountTypes'; -import { accumSum, dateRangeFromToCollection } from 'utils'; +import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts'; import { ICashFlowDatePeriod, ICashFlowStatementNetIncomeSection, ICashFlowStatementAccountSection, ICashFlowStatementSection, ICashFlowSchemaTotalSection, - IFormatNumberSettings, ICashFlowStatementTotalSection, - IDateRange, ICashFlowStatementQuery, -} from '@/interfaces'; + 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 { @@ -22,7 +23,7 @@ export const CashFlowStatementDatePeriods = (Base) => /** * Initialize date range set. */ - private initDateRangeCollection() { + public initDateRangeCollection() { this.dateRangeSet = dateRangeFromToCollection( this.query.fromDate, this.query.toDate, @@ -37,7 +38,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate - To date. * @return {ICashFlowDatePeriod} */ - private getDatePeriodTotalMeta = ( + public getDatePeriodTotalMeta = ( total: number, fromDate: Date, toDate: Date, @@ -56,7 +57,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate - To date. * @return {ICashFlowDatePeriod} */ - private getDatePeriodMeta = ( + public getDatePeriodMeta = ( total: number, fromDate: Date, toDate: Date, @@ -76,7 +77,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate * @returns {number} */ - private getNetIncomeDateRange = (fromDate: Date, toDate: Date) => { + public getNetIncomeDateRange = (fromDate: Date, toDate: Date) => { // Mapping income/expense accounts ids. const incomeAccountsIds = this.getAccountsIdsByType( ACCOUNT_ROOT_TYPE.INCOME @@ -111,7 +112,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {IDateRange} dateRange - * @retrun {ICashFlowDatePeriod} */ - private getNetIncomeDatePeriod = (dateRange): ICashFlowDatePeriod => { + public getNetIncomeDatePeriod = (dateRange): ICashFlowDatePeriod => { const total = this.getNetIncomeDateRange( dateRange.fromDate, dateRange.toDate @@ -129,7 +130,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate * @returns {ICashFlowDatePeriod[]} */ - private getNetIncomeDatePeriods = ( + public getNetIncomeDatePeriods = ( section: ICashFlowStatementNetIncomeSection ): ICashFlowDatePeriod[] => { return this.dateRangeSet.map(this.getNetIncomeDatePeriod.bind(this)); @@ -140,7 +141,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowStatementNetIncomeSection} section * @returns {ICashFlowStatementNetIncomeSection} */ - protected assocPeriodsToNetIncomeNode = ( + public assocPeriodsToNetIncomeNode = ( section: ICashFlowStatementNetIncomeSection ): ICashFlowStatementNetIncomeSection => { const incomeDatePeriods = this.getNetIncomeDatePeriods(section); @@ -154,7 +155,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate - To date. * @return {number} */ - private getAccountTotalDateRange = ( + public getAccountTotalDateRange = ( node: ICashFlowStatementAccountSection, fromDate: Date, toDate: Date @@ -175,7 +176,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate - To date. * @return {ICashFlowDatePeriod} */ - private getAccountTotalDatePeriod = ( + public getAccountTotalDatePeriod = ( node: ICashFlowStatementAccountSection, fromDate: Date, toDate: Date @@ -189,7 +190,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowStatementAccountSection} node - * @return {ICashFlowDatePeriod[]} */ - private getAccountDatePeriods = ( + public getAccountDatePeriods = ( node: ICashFlowStatementAccountSection ): ICashFlowDatePeriod[] => { return this.getNodeDatePeriods( @@ -203,7 +204,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowStatementAccountSection} node - * @return {ICashFlowStatementAccountSection} */ - protected assocPeriodsToAccountNode = ( + public assocPeriodsToAccountNode = ( node: ICashFlowStatementAccountSection ): ICashFlowStatementAccountSection => { const datePeriods = this.getAccountDatePeriods(node); @@ -215,7 +216,7 @@ export const CashFlowStatementDatePeriods = (Base) => * Retrieve total of the given period index for node that has children nodes. * @return {number} */ - private getChildrenTotalPeriodByIndex = ( + public getChildrenTotalPeriodByIndex = ( node: ICashFlowStatementSection, index: number ): number => { @@ -229,7 +230,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} fromDate - From date. * @param {Date} toDate - To date. */ - private getChildrenTotalPeriodMetaByIndex( + public getChildrenTotalPeriodMetaByIndex( node: ICashFlowStatementSection, index: number, fromDate: Date, @@ -243,7 +244,7 @@ export const CashFlowStatementDatePeriods = (Base) => * Retrieve the date periods of aggregate node. * @param {ICashFlowStatementSection} node */ - private getAggregateNodeDatePeriods(node: ICashFlowStatementSection) { + public getAggregateNodeDatePeriods(node: ICashFlowStatementSection) { const getChildrenTotalPeriodMetaByIndex = R.curry( this.getChildrenTotalPeriodMetaByIndex.bind(this) )(node); @@ -262,7 +263,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowStatementSection} node - * @return {ICashFlowStatementSection} */ - protected assocPeriodsToAggregateNode = ( + public assocPeriodsToAggregateNode = ( node: ICashFlowStatementSection ): ICashFlowStatementSection => { const datePeriods = this.getAggregateNodeDatePeriods(node); @@ -271,7 +272,7 @@ export const CashFlowStatementDatePeriods = (Base) => // Total equation node -------------------- - private sectionsMapToTotalPeriod = ( + public sectionsMapToTotalPeriod = ( mappedSections: { [key: number]: any }, index ) => { @@ -287,7 +288,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {string} equation - * @return {ICashFlowDatePeriod[]} */ - private getTotalEquationDatePeriods = ( + public getTotalEquationDatePeriods = ( node: ICashFlowSchemaTotalSection, equation: string, nodesTable @@ -305,7 +306,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowSchemaTotalSection} totalSection - * @return {ICashFlowStatementTotalSection} */ - protected assocTotalEquationDatePeriods = ( + public assocTotalEquationDatePeriods = ( nodesTable: any, equation: string, node: ICashFlowSchemaTotalSection @@ -327,7 +328,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {} * @return {} */ - private getNodeDatePeriods = (node, callback) => { + public getNodeDatePeriods = (node, callback) => { const curriedCallback = R.curry(callback)(node); return this.dateRangeSet.map((dateRange, index) => { @@ -341,7 +342,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate - To date. * @return {number} */ - private getBeginningCashAccountDateRange = ( + public getBeginningCashAccountDateRange = ( node: ICashFlowStatementSection, fromDate: Date, toDate: Date @@ -361,7 +362,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {Date} toDate - To date. * @return {ICashFlowDatePeriod} */ - private getBeginningCashDatePeriod = ( + public getBeginningCashDatePeriod = ( node: ICashFlowStatementSection, fromDate: Date, toDate: Date @@ -379,7 +380,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowStatementSection} node * @return {ICashFlowDatePeriod} */ - private getBeginningCashAccountPeriods = ( + public getBeginningCashAccountPeriods = ( node: ICashFlowStatementSection ): ICashFlowDatePeriod => { return this.getNodeDatePeriods(node, this.getBeginningCashDatePeriod); @@ -390,7 +391,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowStatementSection} section - * @return {ICashFlowStatementSection} */ - protected assocCashAtBeginningDatePeriods = ( + public assocCashAtBeginningDatePeriods = ( node: ICashFlowStatementSection ): ICashFlowStatementSection => { const datePeriods = this.getAggregateNodeDatePeriods(node); @@ -402,7 +403,7 @@ export const CashFlowStatementDatePeriods = (Base) => * @param {ICashFlowStatementSection} node - * @return {ICashFlowStatementSection} */ - protected assocCashAtBeginningAccountDatePeriods = ( + public assocCashAtBeginningAccountDatePeriods = ( node: ICashFlowStatementSection ): ICashFlowStatementSection => { const datePeriods = this.getBeginningCashAccountPeriods(node); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowRepository.ts b/temp/CashFlow/CashFlowRepository.ts similarity index 58% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowRepository.ts rename to temp/CashFlow/CashFlowRepository.ts index d7e7532ac..3bdf3a42a 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowRepository.ts +++ b/temp/CashFlow/CashFlowRepository.ts @@ -1,18 +1,21 @@ -import { Inject, Service } from 'typedi'; +import { Inject, Injectable } from '@nestjs/common'; import moment from 'moment'; import { Knex } from 'knex'; import { isEmpty } from 'lodash'; -import { - ICashFlowStatementQuery, - IAccountTransaction, - IAccount, -} from '@/interfaces'; -import HasTenancyService from '@/services/Tenancy/TenancyService'; +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'; -@Service() -export default class CashFlowRepository { - @Inject() - tenancy: HasTenancyService; +@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. @@ -34,11 +37,8 @@ export default class CashFlowRepository { * Retrieve the cashflow accounts. * @returns {Promise} */ - public async cashFlowAccounts(tenantId: number): Promise { - const { Account } = this.tenancy.models(tenantId); - - const accounts = await Account.query(); - + public async cashFlowAccounts(): Promise { + const accounts = await this.accountModel.query(); return accounts; } @@ -48,26 +48,27 @@ export default class CashFlowRepository { * @param {ICashFlowStatementQuery} filter - * @return {Promise} */ - public cashAtBeginningTotalTransactions( - tenantId: number, - filter: ICashFlowStatementQuery - ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); + public async cashAtBeginningTotalTransactions( + filter: ICashFlowStatementQuery, + ): Promise[]> { const cashBeginningPeriod = moment(filter.fromDate) .subtract(1, 'day') .toDate(); - return AccountTransaction.query().onBuild((query) => { - query.modify('creditDebitSummation'); + const transactions = await this.accountTransactionModel + .query() + .onBuild((query) => { + query.modify('creditDebitSummation'); - query.select('accountId'); - query.groupBy('accountId'); + query.select('accountId'); + query.groupBy('accountId'); - query.withGraphFetched('account'); - query.modify('filterDateRange', null, cashBeginningPeriod); + query.withGraphFetched('account'); + query.modify('filterDateRange', null, cashBeginningPeriod); - this.commonFilterBranchesQuery(filter, query); - }); + this.commonFilterBranchesQuery(filter, query); + }); + return transactions; } /** @@ -76,16 +77,13 @@ export default class CashFlowRepository { * @param {ICashFlowStatementQuery} filter * @return {Promise} */ - public getAccountsTransactions( - tenantId: number, - filter: ICashFlowStatementQuery - ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); + public async getAccountsTransactions( + filter: ICashFlowStatementQuery, + ): Promise[]> { const groupByDateType = this.getGroupTypeFromPeriodsType( - filter.displayColumnsBy + filter.displayColumnsBy, ); - - return AccountTransaction.query().onBuild((query) => { + return await this.accountTransactionModel.query().onBuild((query) => { query.modify('creditDebitSummation'); query.modify('groupByDateFormat', groupByDateType); @@ -106,16 +104,13 @@ export default class CashFlowRepository { * @param {ICashFlowStatementQuery} query - * @return {Promise} */ - public getNetIncomeTransactions( - tenantId: number, - filter: ICashFlowStatementQuery - ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); + public async getNetIncomeTransactions( + filter: ICashFlowStatementQuery, + ): Promise { const groupByDateType = this.getGroupTypeFromPeriodsType( - filter.displayColumnsBy + filter.displayColumnsBy, ); - - return AccountTransaction.query().onBuild((query) => { + return await this.accountTransactionModel.query().onBuild((query) => { query.modify('creditDebitSummation'); query.modify('groupByDateFormat', groupByDateType); @@ -131,20 +126,17 @@ export default class CashFlowRepository { /** * Retrieve peridos of cash at beginning transactions. - * @param {number} tenantId - * @param {ICashFlowStatementQuery} filter - - * @return {Promise} + * @return {Promise[]>} */ - public cashAtBeginningPeriodTransactions( - tenantId: number, - filter: ICashFlowStatementQuery - ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); + public async cashAtBeginningPeriodTransactions( + filter: ICashFlowStatementQuery, + ): Promise[]> { const groupByDateType = this.getGroupTypeFromPeriodsType( - filter.displayColumnsBy + filter.displayColumnsBy, ); - return AccountTransaction.query().onBuild((query) => { + return await this.accountTransactionModel.query().onBuild((query) => { query.modify('creditDebitSummation'); query.modify('groupByDateFormat', groupByDateType); @@ -164,7 +156,7 @@ export default class CashFlowRepository { */ private commonFilterBranchesQuery = ( query: ICashFlowStatementQuery, - knexQuery: Knex.QueryBuilder + knexQuery: Knex.QueryBuilder, ) => { if (!isEmpty(query.branchesIds)) { knexQuery.modify('filterByBranches', query.branchesIds); diff --git a/temp/CashFlow/CashFlowService.ts b/temp/CashFlow/CashFlowService.ts new file mode 100644 index 000000000..4dc334330 --- /dev/null +++ b/temp/CashFlow/CashFlowService.ts @@ -0,0 +1,109 @@ +import { ModelObject } from 'objection'; +import moment from 'moment'; +import * as R from 'ramda'; +import { + ICashFlowStatementQuery, + ICashFlowStatementDOO, +} from './Cashflow.types'; +import CashFlowStatement from './CashFlow'; +import { CashflowSheetMeta } from './CashflowSheetMeta'; +import { Injectable } from '@nestjs/common'; +import { CashFlowRepository } from './CashFlowRepository'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; +import { I18nService } from 'nestjs-i18n'; +import { getDefaultCashflowQuery } from './constants'; + +@Injectable() +export class CashFlowStatementService { + /** + * @param {CashFlowRepository} cashFlowRepo - Cash flow repository. + * @param {CashflowSheetMeta} cashflowSheetMeta - Cashflow sheet meta. + * @param {TenancyContext} tenancyContext - Tenancy context. + */ + constructor( + private readonly cashFlowRepo: CashFlowRepository, + private readonly cashflowSheetMeta: CashflowSheetMeta, + private readonly tenancyContext: TenancyContext, + private readonly i18n: I18nService, + ) {} + + /** + * Retrieves cash at beginning transactions. + * @param {ICashFlowStatementQuery} filter - Cash flow statement query. + * @returns {Promise[]>} + */ + 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/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowTable.ts b/temp/CashFlow/CashFlowTable.ts similarity index 86% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowTable.ts rename to temp/CashFlow/CashFlowTable.ts index 7b49a685c..d54504254 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashFlowTable.ts +++ b/temp/CashFlow/CashFlowTable.ts @@ -1,18 +1,16 @@ import * as R from 'ramda'; -import { isEmpty, times } from 'lodash'; +import { isEmpty, } from 'lodash'; import moment from 'moment'; import { ICashFlowStatementSection, ICashFlowStatementSectionType, - ICashFlowStatement, - ITableRow, - ITableColumn, - ICashFlowStatementQuery, IDateRange, ICashFlowStatementDOO, -} from '@/interfaces'; -import { dateRangeFromToCollection, tableRowMapper } from 'utils'; -import { mapValuesDeep } from 'utils/deepdash'; +} 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', @@ -27,7 +25,7 @@ const DISPLAY_COLUMNS_BY = { TOTAL: 'total', }; -export default class CashFlowTable implements ICashFlowTable { +export class CashFlowTable { private report: ICashFlowStatementDOO; private i18n; private dateRangeSet: IDateRange[]; @@ -50,7 +48,7 @@ export default class CashFlowTable implements ICashFlowTable { this.dateRangeSet = dateRangeFromToCollection( this.report.query.fromDate, this.report.query.toDate, - this.report.query.displayColumnsBy + this.report.query.displayColumnsBy, ); } @@ -79,9 +77,9 @@ export default class CashFlowTable implements ICashFlowTable { R.concat([{ key: 'name', accessor: 'label' }]), R.when( R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), - R.concat(this.datePeriodsColumnsAccessors()) + R.concat(this.datePeriodsColumnsAccessors()), ), - R.concat(this.totalColumnAccessor()) + R.concat(this.totalColumnAccessor()), )([]); }; @@ -91,7 +89,7 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ITableRow[]} */ private regularSectionMapper = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); @@ -107,7 +105,7 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ITableRow} */ private netIncomeSectionMapper = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); @@ -123,7 +121,7 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ITableRow} */ private accountsSectionMapper = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); @@ -139,7 +137,7 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ITableRow} */ private accountSectionMapper = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); @@ -155,7 +153,7 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ITableRow} */ private totalSectionMapper = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): ITableRow => { const columns = this.commonColumns(); @@ -173,7 +171,7 @@ export default class CashFlowTable implements ICashFlowTable { */ private isSectionHasType = ( type: string, - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): boolean => { return type === section.sectionType; }; @@ -186,35 +184,35 @@ export default class CashFlowTable implements ICashFlowTable { private sectionMapper = ( section: ICashFlowStatementSection, key: string, - parentSection: ICashFlowStatementSection + parentSection: ICashFlowStatementSection, ): ITableRow => { const isSectionHasType = R.curry(this.isSectionHasType); return R.pipe( R.when( isSectionHasType(ICashFlowStatementSectionType.AGGREGATE), - this.regularSectionMapper + this.regularSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.CASH_AT_BEGINNING), - this.regularSectionMapper + this.regularSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.NET_INCOME), - this.netIncomeSectionMapper + this.netIncomeSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.ACCOUNTS), - this.accountsSectionMapper + this.accountsSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.ACCOUNT), - this.accountSectionMapper + this.accountSectionMapper, ), R.when( isSectionHasType(ICashFlowStatementSectionType.TOTAL), - this.totalSectionMapper - ) + this.totalSectionMapper, + ), )(section); }; @@ -224,7 +222,7 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ITableRow[]} */ private mapSectionsToTableRows = ( - sections: ICashFlowStatementSection[] + sections: ICashFlowStatementSection[], ): ITableRow[] => { return mapValuesDeep(sections, this.sectionMapper.bind(this), DEEP_CONFIG); }; @@ -235,7 +233,7 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ICashFlowStatementSection} */ private appendTotalToSectionChildren = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): ICashFlowStatementSection => { const label = section.footerLabel ? section.footerLabel @@ -256,12 +254,15 @@ export default class CashFlowTable implements ICashFlowTable { * @returns {ICashFlowStatementSection} */ private mapSectionsToAppendTotalChildren = ( - section: ICashFlowStatementSection + section: ICashFlowStatementSection, ): ICashFlowStatementSection => { const isSectionHasChildren = (section) => !isEmpty(section.children); return R.compose( - R.when(isSectionHasChildren, this.appendTotalToSectionChildren.bind(this)) + R.when( + isSectionHasChildren, + this.appendTotalToSectionChildren.bind(this), + ), )(section); }; @@ -274,7 +275,7 @@ export default class CashFlowTable implements ICashFlowTable { return mapValuesDeep( sections, this.mapSectionsToAppendTotalChildren.bind(this), - DEEP_CONFIG + DEEP_CONFIG, ); }; @@ -288,7 +289,7 @@ export default class CashFlowTable implements ICashFlowTable { return R.pipe( this.appendTotalToChildren, - this.mapSectionsToTableRows + this.mapSectionsToTableRows, )(sections); }; @@ -322,7 +323,7 @@ export default class CashFlowTable implements ICashFlowTable { R.always(this.isDisplayColumnsType(type)), formatFn, ], - conditions + conditions, ); return R.compose(R.cond(conditionsPairs))(dateRange); @@ -365,9 +366,9 @@ export default class CashFlowTable implements ICashFlowTable { 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.datePeriodsColumns()), ), - R.concat(this.totalColumns()) + R.concat(this.totalColumns()), )([]); }; } diff --git a/temp/CashFlow/Cashflow.controller.ts b/temp/CashFlow/Cashflow.controller.ts new file mode 100644 index 000000000..0227145ed --- /dev/null +++ b/temp/CashFlow/Cashflow.controller.ts @@ -0,0 +1,59 @@ +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 new file mode 100644 index 000000000..1646def94 --- /dev/null +++ b/temp/CashFlow/Cashflow.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { CashflowSheetMeta } from './CashflowSheetMeta'; +import { CashFlowRepository } from './CashFlowRepository'; +import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable'; +import { CashflowExportInjectable } from './CashflowExportInjectable'; +import { CashflowController } from './Cashflow.controller'; +import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; +import { CashflowTableInjectable } from './CashflowTableInjectable'; +import { CashFlowStatementService } from './CashFlowService'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { CashflowSheetApplication } from './CashflowSheetApplication'; + +@Module({ + imports: [FinancialSheetCommonModule], + providers: [ + CashFlowRepository, + CashflowSheetMeta, + CashFlowStatementService, + CashflowTablePdfInjectable, + CashflowExportInjectable, + CashflowTableInjectable, + CashflowSheetApplication, + TenancyContext + ], + controllers: [CashflowController], +}) +export class CashflowReportModule {} diff --git a/temp/CashFlow/Cashflow.types.ts b/temp/CashFlow/Cashflow.types.ts new file mode 100644 index 000000000..6d21ca118 --- /dev/null +++ b/temp/CashFlow/Cashflow.types.ts @@ -0,0 +1,299 @@ +import { Knex } from 'knex'; +import { IFinancialSheetCommonMeta, INumberFormatQuery } from '../../types/Report.types'; +import { Account } from '@/modules/Accounts/models/Account.model'; +import { Ledger } from '@/modules/Ledger/Ledger'; +import { IFinancialTable, ITableRow } from '../../types/Table.types'; + + +export interface ICashFlowStatementQuery { + fromDate: Date | string; + toDate: Date | string; + displayColumnsBy: string; + displayColumnsType: string; + noneZero: boolean; + noneTransactions: boolean; + numberFormat: INumberFormatQuery; + basis: string; + + branchesIds?: number[]; +} + +export interface ICashFlowStatementTotal { + amount: number; + formattedAmount: string; + currencyCode: string; +} + +export interface ICashFlowStatementTotalPeriod { + fromDate: Date; + toDate: Date; + total: ICashFlowStatementTotal; +} + +export interface ICashFlowStatementCommonSection { + id: string; + label: string; + total: ICashFlowStatementTotal; + footerLabel?: string; +} + +export interface ICashFlowStatementAccountMeta { + id: number; + label: string; + code: string; + total: ICashFlowStatementTotal; + accountType: string; + adjustmentType: string; + sectionType: ICashFlowStatementSectionType.ACCOUNT; +} + +export enum ICashFlowStatementSectionType { + REGULAR = 'REGULAR', + AGGREGATE = 'AGGREGATE', + NET_INCOME = 'NET_INCOME', + ACCOUNT = 'ACCOUNT', + ACCOUNTS = 'ACCOUNTS', + TOTAL = 'TOTAL', + CASH_AT_BEGINNING = 'CASH_AT_BEGINNING', +} + +export interface ICashFlowStatementAccountSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.ACCOUNTS; + children: ICashFlowStatementAccountMeta[]; + total: ICashFlowStatementTotal; +} + +export interface ICashFlowStatementNetIncomeSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.NET_INCOME; +} + +export interface ICashFlowStatementTotalSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.TOTAL; +} + +export interface ICashFlowStatementAggregateSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.AGGREGATE; +} + +export interface ICashFlowCashBeginningNode + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING; +} + +export type ICashFlowStatementSection = + | ICashFlowStatementAccountSection + | ICashFlowStatementNetIncomeSection + | ICashFlowStatementTotalSection + | ICashFlowStatementCommonSection; + +export interface ICashFlowStatementColumn {} +export interface ICashFlowStatementMeta extends IFinancialSheetCommonMeta { + formattedToDate: string; + formattedFromDate: string; + formattedDateRange: string; +} + +export interface ICashFlowStatementDOO { + data: ICashFlowStatementData; + meta: ICashFlowStatementMeta; + query: ICashFlowStatementQuery; +} + +export interface ICashFlowStatementTable extends IFinancialTable { + meta: ICashFlowStatementMeta; + query: ICashFlowStatementQuery; +} + +export interface ICashFlowStatementService { + cashFlow( + tenantId: number, + query: ICashFlowStatementQuery + ): Promise; +} + +// 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/CashFlow/CashflowExportInjectable.ts b/temp/CashFlow/CashflowExportInjectable.ts new file mode 100644 index 000000000..75f4bcae2 --- /dev/null +++ b/temp/CashFlow/CashflowExportInjectable.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common'; +import { CashflowTableInjectable } from './CashflowTableInjectable'; +import { ICashFlowStatementQuery } from './Cashflow.types'; +import { TableSheet } from '../../common/TableSheet'; + +@Injectable() +export class CashflowExportInjectable { + constructor(private readonly cashflowSheetTable: CashflowTableInjectable) {} + + /** + * Retrieves the cashflow sheet in XLSX format. + * @param {ICashFlowStatementQuery} query - Cashflow statement query. + * @returns {Promise} + */ + 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/CashFlow/CashflowSheetApplication.ts b/temp/CashFlow/CashflowSheetApplication.ts new file mode 100644 index 000000000..7d04b298c --- /dev/null +++ b/temp/CashFlow/CashflowSheetApplication.ts @@ -0,0 +1,60 @@ + +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/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowSheetMeta.ts b/temp/CashFlow/CashflowSheetMeta.ts similarity index 71% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowSheetMeta.ts rename to temp/CashFlow/CashflowSheetMeta.ts index 3a1dd40dc..094745e41 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowSheetMeta.ts +++ b/temp/CashFlow/CashflowSheetMeta.ts @@ -1,24 +1,24 @@ -import { Inject, Service } from 'typedi'; import moment from 'moment'; -import { ICashFlowStatementMeta, ICashFlowStatementQuery } from '@/interfaces'; -import { FinancialSheetMeta } from '../FinancialSheetMeta'; +import { Injectable } from '@nestjs/common'; +import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; +import { ICashFlowStatementMeta, ICashFlowStatementQuery } from './Cashflow.types'; -@Service() +@Injectable() export class CashflowSheetMeta { - @Inject() - private financialSheetMeta: FinancialSheetMeta; + constructor( + private readonly financialSheetMeta: FinancialSheetMeta, + ) {} /** - * CAshflow sheet meta. + * Cashflow sheet meta. * @param {number} tenantId * @param {ICashFlowStatementQuery} query * @returns {Promise} */ public async meta( - tenantId: number, query: ICashFlowStatementQuery ): Promise { - const meta = await this.financialSheetMeta.meta(tenantId); + 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}`; diff --git a/temp/CashFlow/CashflowTableInjectable.ts b/temp/CashFlow/CashflowTableInjectable.ts new file mode 100644 index 000000000..3440b5c3c --- /dev/null +++ b/temp/CashFlow/CashflowTableInjectable.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { CashFlowTable } from './CashFlowTable'; +import { CashFlowStatementService } from './CashFlowService'; +import { I18nService } from 'nestjs-i18n'; +import { + ICashFlowStatementQuery, + ICashFlowStatementTable, +} from './Cashflow.types'; + +@Injectable() +export class CashflowTableInjectable { + constructor( + private readonly cashflowSheet: CashFlowStatementService, + private readonly i18n: I18nService, + ) {} + + /** + * Retrieves the cash flow table. + * @returns {Promise} + */ + 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/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowTablePdfInjectable.ts b/temp/CashFlow/CashflowTablePdfInjectable.ts similarity index 52% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowTablePdfInjectable.ts rename to temp/CashFlow/CashflowTablePdfInjectable.ts index be7cb5382..6033a9df2 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/CashflowTablePdfInjectable.ts +++ b/temp/CashFlow/CashflowTablePdfInjectable.ts @@ -1,15 +1,13 @@ -import { Inject } from 'typedi'; +import { TableSheetPdf } from '../../common/TableSheetPdf'; +import { ICashFlowStatementQuery } from './Cashflow.types'; import { CashflowTableInjectable } from './CashflowTableInjectable'; -import { TableSheetPdf } from '../TableSheetPdf'; -import { ICashFlowStatementQuery } from '@/interfaces'; import { HtmlTableCustomCss } from './constants'; export class CashflowTablePdfInjectable { - @Inject() - private cashflowTable: CashflowTableInjectable; - - @Inject() - private tableSheetPdf: TableSheetPdf; + constructor( + private readonly cashflowTable: CashflowTableInjectable, + private readonly tableSheetPdf: TableSheetPdf, + ) {} /** * Converts the given cashflow sheet table to pdf. @@ -17,18 +15,14 @@ export class CashflowTablePdfInjectable { * @param {IBalanceSheetQuery} query - Balance sheet query. * @returns {Promise} */ - public async pdf( - tenantId: number, - query: ICashFlowStatementQuery - ): Promise { - const table = await this.cashflowTable.table(tenantId, query); + public async pdf(query: ICashFlowStatementQuery): Promise { + const table = await this.cashflowTable.table(query); return this.tableSheetPdf.convertToPdf( - tenantId, table.table, table.meta.sheetName, table.meta.formattedDateRange, - HtmlTableCustomCss + HtmlTableCustomCss, ); } } diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/constants.ts b/temp/CashFlow/constants.ts similarity index 58% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/constants.ts rename to temp/CashFlow/constants.ts index f3f1858fb..6aaa74486 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/constants.ts +++ b/temp/CashFlow/constants.ts @@ -1,3 +1,5 @@ +import { ICashFlowStatementQuery } from "./Cashflow.types"; + export const DISPLAY_COLUMNS_BY = { DATE_PERIODS: 'date_periods', TOTAL: 'total', @@ -31,3 +33,20 @@ 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/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/schema.ts b/temp/CashFlow/schema.ts similarity index 90% rename from packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/schema.ts rename to temp/CashFlow/schema.ts index f12520e63..67be33743 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/CashFlow/schema.ts +++ b/temp/CashFlow/schema.ts @@ -1,7 +1,11 @@ -import { ICashFlowSchemaSection, CASH_FLOW_SECTION_ID, ICashFlowStatementSectionType } from '@/interfaces'; -import { ACCOUNT_TYPE } from '@/data/AccountTypes'; +import { ACCOUNT_TYPE } from '@/constants/accounts'; +import { + ICashFlowSchemaSection, + CASH_FLOW_SECTION_ID, + ICashFlowStatementSectionType, +} from './Cashflow.types'; -export default [ +export const CASH_FLOW_SCHEMA = [ { id: CASH_FLOW_SECTION_ID.OPERATING, label: 'OPERATING ACTIVITIES', @@ -36,9 +40,7 @@ export default [ id: CASH_FLOW_SECTION_ID.INVESTMENT, sectionType: ICashFlowStatementSectionType.ACCOUNTS, label: 'INVESTMENT ACTIVITIES', - accountsRelations: [ - { type: ACCOUNT_TYPE.FIXED_ASSET, direction: 'mines' } - ], + accountsRelations: [{ type: ACCOUNT_TYPE.FIXED_ASSET, direction: 'mines' }], footerLabel: 'Net cash provided by investing activities', }, {