From ba00e8baeb0b234175fc23b30df8054d80fa36c1 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 19 Oct 2023 19:32:27 +0200 Subject: [PATCH 1/4] fix: trial balance sheet adjusted balance --- .../FinancialStatements/TrialBalanceSheet.ts | 33 ++-- packages/server/src/interfaces/Ledger.ts | 2 + .../server/src/services/Accounting/Ledger.ts | 18 ++- .../TrialBalanceSheet/TrialBalanceSheet.ts | 74 ++++++--- .../TrialBalanceSheetRepository.ts | 104 +++++++++++++ .../TrialBalanceSheetService.ts | 72 +++++---- .../TrialBalanceSheetTable.ts | 146 ++++++++++++++++++ .../TrialBalanceSheet/_constants.ts | 5 + packages/webapp/src/lang/en/index.json | 1 + 9 files changed, 388 insertions(+), 67 deletions(-) create mode 100644 packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts create mode 100644 packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts create mode 100644 packages/server/src/services/FinancialStatements/TrialBalanceSheet/_constants.ts diff --git a/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts b/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts index 4fa298fa0..da59287e4 100644 --- a/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts @@ -16,7 +16,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont /** * Router constructor. */ - router() { + public router() { const router = Router(); router.get( @@ -36,7 +36,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont * Validation schema. * @return {ValidationChain[]} */ - get trialBalanceSheetValidationSchema(): ValidationChain[] { + private get trialBalanceSheetValidationSchema(): ValidationChain[] { return [ ...this.sheetNumberFormatValidationSchema, query('basis').optional(), @@ -59,28 +59,37 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont /** * Retrieve the trial balance sheet. */ - public async trialBalanceSheet( + private async trialBalanceSheet( req: Request, res: Response, next: NextFunction ) { - const { tenantId, settings } = req; + const { tenantId } = req; let filter = this.matchedQueryData(req); filter = { ...filter, accountsIds: castArray(filter.accountsIds), }; - try { - const { data, query, meta } = - await this.trialBalanceSheetService.trialBalanceSheet(tenantId, filter); + const accept = this.accepts(req); + const acceptType = accept.types(['json', 'application/json+table']); - return res.status(200).send({ - data: this.transfromToResponse(data), - query: this.transfromToResponse(query), - meta: this.transfromToResponse(meta), - }); + if (acceptType === 'application/json+table') { + const { table, meta, query } = + await this.trialBalanceSheetService.trialBalanceSheetTable( + tenantId, + filter + ); + return res.status(200).send({ table, meta, query }); + } else { + const { data, query, meta } = + await this.trialBalanceSheetService.trialBalanceSheet( + tenantId, + filter + ); + return res.status(200).send({ data, query, meta }); + } } catch (error) { next(error); } diff --git a/packages/server/src/interfaces/Ledger.ts b/packages/server/src/interfaces/Ledger.ts index 0f6379676..2305d1ed3 100644 --- a/packages/server/src/interfaces/Ledger.ts +++ b/packages/server/src/interfaces/Ledger.ts @@ -16,6 +16,8 @@ export interface ILedger { getClosingBalance(): number; getForeignClosingBalance(): number; + getClosingDebit(): number; + getClosingCredit(): number; getContactsIds(): number[]; getAccountsIds(): number[]; diff --git a/packages/server/src/services/Accounting/Ledger.ts b/packages/server/src/services/Accounting/Ledger.ts index 7cb71bed8..ab241ebe2 100644 --- a/packages/server/src/services/Accounting/Ledger.ts +++ b/packages/server/src/services/Accounting/Ledger.ts @@ -1,5 +1,5 @@ import moment from 'moment'; -import { defaultTo, uniqBy } from 'lodash'; +import { defaultTo, sumBy, uniqBy } from 'lodash'; import { IAccountTransaction, ILedger, ILedgerEntry } from '@/interfaces'; export default class Ledger implements ILedger { @@ -130,6 +130,22 @@ export default class Ledger implements ILedger { return closingBalance; } + /** + * Retrieves the closing credit of the entries. + * @returns {number} + */ + public getClosingCredit(): number { + return sumBy(this.entries, 'credit'); + } + + /** + * Retrieves the closing debit of the entries. + * @returns {number} + */ + public getClosingDebit(): number { + return sumBy(this.entries, 'debit'); + } + /** * Retrieve the closing balance of the entries. * @returns {number} diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 980bd1dd4..4a517650c 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -10,13 +10,12 @@ import { } from '@/interfaces'; import FinancialSheet from '../FinancialSheet'; import { allPassedConditionsPass, flatToNestedArray } from 'utils'; +import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository'; export default class TrialBalanceSheet extends FinancialSheet { - tenantId: number; - query: ITrialBalanceSheetQuery; - accounts: IAccount & { type: IAccountType }[]; - journalFinancial: any; - baseCurrency: string; + private query: ITrialBalanceSheetQuery; + private repository: TrialBalanceSheetRepository; + private baseCurrency: string; /** * Constructor method. @@ -28,20 +27,52 @@ export default class TrialBalanceSheet extends FinancialSheet { constructor( tenantId: number, query: ITrialBalanceSheetQuery, - accounts: IAccount & { type: IAccountType }[], - journalFinancial: any, + repository: TrialBalanceSheetRepository, baseCurrency: string ) { super(); this.tenantId = tenantId; this.query = query; - this.accounts = accounts; - this.journalFinancial = journalFinancial; + this.repository = repository; this.numberFormat = this.query.numberFormat; this.baseCurrency = baseCurrency; } + /** + * Retrieves the closing credit of the given account. + * @param {number} accountId + * @returns {number} + */ + public getClosingAccountCredit(accountId: number) { + return this.repository.totalAccountsLedger + .whereAccountId(accountId) + .getClosingCredit(); + } + + /** + * Retrieves the closing debit of the given account. + * @param {number} accountId + * @returns {number} + */ + public getClosingAccountDebit(accountId: number) { + return this.repository.totalAccountsLedger + .whereAccountId(accountId) + .getClosingDebit(); + } + + /** + * Retrieves the closing total of the given account. + * @param {number} accountId + * @returns {number} + */ + public getClosingAccountTotal(accountId: number) { + const credit = this.getClosingAccountCredit(accountId); + const debit = this.getClosingAccountDebit(accountId); + + return debit - credit; + } + /** * Account mapper. * @param {IAccount} account @@ -50,7 +81,9 @@ export default class TrialBalanceSheet extends FinancialSheet { private accountTransformer = ( account: IAccount & { type: IAccountType } ): ITrialBalanceAccount => { - const trial = this.journalFinancial.getTrialBalanceWithDepands(account.id); + const debit = this.getClosingAccountDebit(account.id); + const credit = this.getClosingAccountCredit(account.id); + const balance = this.getClosingAccountTotal(account.id); return { id: account.id, @@ -59,14 +92,14 @@ export default class TrialBalanceSheet extends FinancialSheet { code: account.code, accountNormal: account.accountNormal, - credit: trial.credit, - debit: trial.debit, - balance: trial.balance, + credit, + debit, + balance, currencyCode: this.baseCurrency, - formattedCredit: this.formatNumber(trial.credit), - formattedDebit: this.formatNumber(trial.debit), - formattedBalance: this.formatNumber(trial.balance), + formattedCredit: this.formatNumber(credit), + formattedDebit: this.formatNumber(debit), + formattedBalance: this.formatNumber(balance), }; }; @@ -117,10 +150,7 @@ export default class TrialBalanceSheet extends FinancialSheet { private filterNoneTransactions = ( accountNode: ITrialBalanceAccount ): boolean => { - const entries = this.journalFinancial.getAccountEntriesWithDepents( - accountNode.id - ); - return entries.length > 0; + return false === this.repository.totalAccountsLedger.isEmpty(); }; /** @@ -200,11 +230,11 @@ export default class TrialBalanceSheet extends FinancialSheet { */ public reportData(): ITrialBalanceSheetData { // Don't return noting if the journal has no transactions. - if (this.journalFinancial.isEmpty()) { + if (this.repository.totalAccountsLedger.isEmpty()) { return null; } // Retrieve accounts nodes. - const accounts = this.accountsSection(this.accounts); + const accounts = this.accountsSection(this.repository.accounts); // Retrieve account node. const total = this.tatalSection(accounts); diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts new file mode 100644 index 000000000..cffc71c7a --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts @@ -0,0 +1,104 @@ +import { ITrialBalanceSheetQuery } from '@/interfaces'; +import Ledger from '@/services/Accounting/Ledger'; +import { Knex } from 'knex'; +import { isEmpty } from 'lodash'; +import { Service } from 'typedi'; + +@Service() +export class TrialBalanceSheetRepository { + private query: ITrialBalanceSheetQuery; + private models: any; + + /** + * + */ + public accounts: any; + + /** + * Total closing accounts ledger. + * @param {Ledger} + */ + public totalAccountsLedger: Ledger; + + /** + * Constructor method. + * @param {number} tenantId + * @param {IBalanceSheetQuery} query + */ + constructor(models: any, query: ITrialBalanceSheetQuery) { + this.query = query; + this.models = models; + } + + /** + * Async initialize. + * @returns {Promise} + */ + public asyncInitialize = async () => { + await this.initAccounts(); + await this.initAccountsClosingTotalLedger(); + }; + + // ---------------------------- + // # Accounts + // ---------------------------- + /** + * Initialize accounts. + * @returns {Promise} + */ + public initAccounts = async () => { + const accounts = await this.getAccounts(); + + this.accounts = accounts; + }; + + /** + * Initialize all accounts closing total ledger. + * @return {Promise} + */ + public initAccountsClosingTotalLedger = async (): Promise => { + const totalByAccounts = await this.closingAccountsTotal(this.query.toDate); + + this.totalAccountsLedger = Ledger.fromTransactions(totalByAccounts); + }; + + /** + * Retrieve accounts of the report. + * @return {Promise} + */ + private getAccounts = () => { + const { Account } = this.models; + + return Account.query(); + }; + + /** + * Retrieve the opening balance transactions of the report. + * @param {Date|string} openingDate - + */ + public closingAccountsTotal = async (openingDate: Date | string) => { + const { AccountTransaction } = this.models; + + return AccountTransaction.query().onBuild((query) => { + query.sum('credit as credit'); + query.sum('debit as debit'); + query.groupBy('accountId'); + query.select(['accountId']); + + query.modify('filterDateRange', null, openingDate); + query.withGraphFetched('account'); + + this.commonFilterBranchesQuery(query); + }); + }; + + /** + * Common branches filter query. + * @param {Knex.QueryBuilder} query + */ + private commonFilterBranchesQuery = (query: Knex.QueryBuilder) => { + if (!isEmpty(this.query.branchesIds)) { + query.modify('filterByBranches', this.query.branchesIds); + } + }; +} diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts index f22624a3e..1b7789d62 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts @@ -2,12 +2,18 @@ import { Service, Inject } from 'typedi'; import moment from 'moment'; import TenancyService from '@/services/Tenancy/TenancyService'; import Journal from '@/services/Accounting/JournalPoster'; -import { ITrialBalanceSheetMeta, ITrialBalanceSheetQuery, ITrialBalanceStatement } from '@/interfaces'; +import { + ITrialBalanceSheetMeta, + ITrialBalanceSheetQuery, + ITrialBalanceStatement, +} from '@/interfaces'; import TrialBalanceSheet from './TrialBalanceSheet'; import FinancialSheet from '../FinancialSheet'; import InventoryService from '@/services/Inventory/Inventory'; import { parseBoolean } from 'utils'; import { Tenant } from '@/system/models'; +import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository'; +import { TrialBalanceSheetTable } from './TrialBalanceSheetTable'; @Service() export default class TrialBalanceSheetService extends FinancialSheet { @@ -51,9 +57,8 @@ export default class TrialBalanceSheetService extends FinancialSheet { reportMetadata(tenantId: number): ITrialBalanceSheetMeta { const settings = this.tenancy.settings(tenantId); - const isCostComputeRunning = this.inventoryService.isItemsCostComputeRunning( - tenantId - ); + const isCostComputeRunning = + this.inventoryService.isItemsCostComputeRunning(tenantId); const organizationName = settings.get({ group: 'organization', key: 'name', @@ -72,10 +77,8 @@ export default class TrialBalanceSheetService extends FinancialSheet { /** * Retrieve trial balance sheet statement. - * ------------- * @param {number} tenantId * @param {IBalanceSheetQuery} query - * * @return {IBalanceSheetStatement} */ public async trialBalanceSheet( @@ -86,43 +89,25 @@ export default class TrialBalanceSheetService extends FinancialSheet { ...this.defaultQuery, ...query, }; - const { - accountRepository, - transactionsRepository, - } = this.tenancy.repositories(tenantId); const tenant = await Tenant.query() .findById(tenantId) .withGraphFetched('metadata'); - this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { - tenantId, - filter, - }); - // Retrieve all accounts on the storage. - const accounts = await accountRepository.all(); - const accountsGraph = await accountRepository.getDependencyGraph(); + const models = this.tenancy.models(tenantId); - // Retrieve all journal transactions based on the given query. - const transactions = await transactionsRepository.journal({ - fromDate: query.fromDate, - toDate: query.toDate, - sumationCreditDebit: true, - branchesIds: query.branchesIds - }); - // Transform transactions array to journal collection. - const transactionsJournal = Journal.fromTransactions( - transactions, - tenantId, - accountsGraph + const trialBalanceSheetRepos = new TrialBalanceSheetRepository( + models, + filter ); + await trialBalanceSheetRepos.asyncInitialize(); + // Trial balance report instance. const trialBalanceInstance = new TrialBalanceSheet( tenantId, filter, - accounts, - transactionsJournal, - tenant.metadata.baseCurrency, + trialBalanceSheetRepos, + tenant.metadata.baseCurrency ); // Trial balance sheet data. const trialBalanceSheetData = trialBalanceInstance.reportData(); @@ -133,4 +118,27 @@ export default class TrialBalanceSheetService extends FinancialSheet { meta: this.reportMetadata(tenantId), }; } + + /** + * Retrieves the trial balance sheet table. + * @param {number} tenantId + * @param {ITrialBalanceSheetQuery} query + * @returns {Promise} + */ + public async trialBalanceSheetTable( + tenantId: number, + query: ITrialBalanceSheetQuery + ) { + const trialBalance = await this.trialBalanceSheet(tenantId, query); + const table = new TrialBalanceSheetTable(trialBalance.data, query, {}); + + return { + table: { + columns: table.tableColumns(), + rows: table.tableRows(), + }, + meta: trialBalance.meta, + query: trialBalance.query, + }; + } } diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts new file mode 100644 index 000000000..b00d379c8 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts @@ -0,0 +1,146 @@ +import * as R from 'ramda'; +import FinancialSheet from '../FinancialSheet'; +import { FinancialTable } from '../FinancialTable'; +import { + IBalanceSheetStatementData, + ITableColumn, + ITableColumnAccessor, + ITableRow, + ITrialBalanceAccount, + ITrialBalanceSheetData, + ITrialBalanceSheetQuery, + ITrialBalanceTotal, +} from '@/interfaces'; +import { tableRowMapper } from '@/utils'; +import { IROW_TYPE } from '../BalanceSheet/constants'; +import { FinancialSheetStructure } from '../FinancialSheetStructure'; + +export class TrialBalanceSheetTable extends R.compose( + FinancialTable, + FinancialSheetStructure +)(FinancialSheet) { + /** + * @param {ITrialBalanceSheetData} + */ + public data: ITrialBalanceSheetData; + + /** + * Balance sheet query. + * @param {ITrialBalanceSheetQuery} + */ + public query: ITrialBalanceSheetQuery; + + /** + * Constructor method. + * @param {IBalanceSheetStatementData} reportData - + * @param {ITrialBalanceSheetQuery} query - + */ + constructor( + data: ITrialBalanceSheetData, + query: ITrialBalanceSheetQuery, + i18n: any + ) { + super(); + + this.data = data; + this.query = query; + this.i18n = i18n; + } + + /** + * Retrieve the common columns for all report nodes. + * @param {ITableColumnAccessor[]} + */ + private commonColumnsAccessors = (): ITableColumnAccessor[] => { + return [ + { key: 'account', accessor: 'name' }, + { key: 'debit', accessor: 'formattedDebit' }, + { key: 'credit', accessor: 'formattedCredit' }, + { key: 'total', accessor: 'formattedBalance' }, + ]; + }; + + /** + * Maps the account node to table row. + * @param {ITrialBalanceAccount} node - + * @returns {ITableRow} + */ + private accountNodeTableRowsMapper = ( + node: ITrialBalanceAccount + ): ITableRow => { + const columns = this.commonColumnsAccessors(); + const meta = { + rowTypes: [IROW_TYPE.ACCOUNT], + id: node.id, + }; + return tableRowMapper(node, columns, meta); + }; + + /** + * Maps the total node to table row. + * @param {ITrialBalanceTotal} node - + * @returns {ITableRow} + */ + private totalNodeTableRowsMapper = (node: ITrialBalanceTotal): ITableRow => { + const columns = this.commonColumnsAccessors(); + const meta = { + rowTypes: [IROW_TYPE.TOTAL], + id: node.id, + }; + return tableRowMapper(node, columns, meta); + }; + + /** + * Mappes the given report sections to table rows. + * @param {IBalanceSheetDataNode[]} nodes - + * @return {ITableRow} + */ + private accountsToTableRowsMap = ( + nodes: ITrialBalanceAccount[] + ): ITableRow[] => { + return this.mapNodesDeep(nodes, this.accountNodeTableRowsMapper); + }; + + /** + * Retrieves the accounts table rows of the given report data. + * @returns {ITableRow[]} + */ + private accountsTableRows = (): ITableRow[] => { + return this.accountsToTableRowsMap(this.data.accounts); + }; + + /** + * Maps the given total node to table row. + * @returns {ITableRow} + */ + private totalTableRow = (): ITableRow => { + return this.totalNodeTableRowsMapper(this.data.total); + }; + + /** + * Retrieves the table rows. + * @returns {ITableRow[]} + */ + public tableRows = (): ITableRow[] => { + return R.compose( + R.append(this.totalTableRow()), + R.concat(this.accountsTableRows()) + )([]); + }; + + /** + * Retrrieves the table columns. + * @returns {ITableColumn[]} + */ + public tableColumns = (): ITableColumn[] => { + return R.compose( + this.tableColumnsCellIndexing, + R.concat([ + { key: 'account_name', label: 'Account' }, + { key: 'debit', label: 'Debit' }, + { key: 'credit', label: 'Credit' }, + { key: 'total', label: 'Total' }, + ]) + )([]); + }; +} diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/_constants.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/_constants.ts new file mode 100644 index 000000000..91e8c595f --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/_constants.ts @@ -0,0 +1,5 @@ + +export enum IROW_TYPE { + ACCOUNT = 'ACCOUNT', + TOTAL = 'TOTAL', +} \ No newline at end of file diff --git a/packages/webapp/src/lang/en/index.json b/packages/webapp/src/lang/en/index.json index 1b2e25f1f..f4a2d91cf 100644 --- a/packages/webapp/src/lang/en/index.json +++ b/packages/webapp/src/lang/en/index.json @@ -1804,6 +1804,7 @@ "balance_sheet.total_change": "Total Change", "balance_sheet.change": "% Change", "balance_sheet.previous_period": "Previous Period (PP)", + "balance_sheet.net_income": "Net Income", "profit_loss_sheet.comparisons": "Comparisons", "profit_loss_sheet.dimensions": "Dimensions", "profit_loss_sheet.previous_year": "Previous Year", From 4ace761fbb27ba503765ec50a301977904e21c57 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 19 Oct 2023 21:34:13 +0200 Subject: [PATCH 2/4] refactor(webapp): trial balance sheet to dynamic columns format --- .../TrialBalanceSheet/TrialBalanceSheet.tsx | 1 - .../TrialBalanceSheetTable.tsx | 11 ++- .../TrialBalanceSheet/components.tsx | 82 +------------------ .../TrialBalanceSheet/dynamicColumns.ts | 48 +++++++++++ .../TrialBalanceSheet/hooks.ts | 18 ++++ .../src/hooks/query/financialReports.tsx | 13 +-- 6 files changed, 77 insertions(+), 96 deletions(-) create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/hooks.ts diff --git a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.tsx b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.tsx index 904edbccc..5156f4358 100644 --- a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.tsx +++ b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.tsx @@ -15,7 +15,6 @@ import { } from './components'; import withTrialBalanceActions from './withTrialBalanceActions'; - import { compose } from '@/utils'; /** diff --git a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.tsx b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.tsx index 9909f4e27..d3c630bb5 100644 --- a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.tsx +++ b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.tsx @@ -8,8 +8,7 @@ import { tableRowTypesToClassnames } from '@/utils'; import { ReportDataTable, FinancialSheet } from '@/components'; import { useTrialBalanceSheetContext } from './TrialBalanceProvider'; -import { useTrialBalanceTableColumns } from './components'; - +import { useTrialBalanceSheetTableColumns } from './hooks'; /** * Trial Balance sheet data table. @@ -17,12 +16,12 @@ import { useTrialBalanceTableColumns } from './components'; export default function TrialBalanceSheetTable({ companyName }) { // Trial balance sheet context. const { - trialBalanceSheet: { tableRows, query }, + trialBalanceSheet: { table, query }, isLoading, } = useTrialBalanceSheetContext(); // Trial balance sheet table columns. - const columns = useTrialBalanceTableColumns(); + const columns = useTrialBalanceSheetTableColumns(); return ( { - const width = getColumnWidth(data, `credit`, { minWidth: 140 }); - - return { - Header: intl.get('credit'), - Cell: CellTextSpan, - accessor: 'formatted_credit', - className: 'credit', - width, - textOverview: true, - align: Align.Right, - }; -}; - -/** - * Retrieves the debit column. - */ -const getDebitColumn = (data) => { - return { - Header: intl.get('debit'), - Cell: CellTextSpan, - accessor: 'formatted_debit', - width: getColumnWidth(data, `debit`, { minWidth: 140 }), - textOverview: true, - align: Align.Right, - }; -}; - -/** - * Retrieves the balance column. - */ -const getBalanceColumn = (data) => { - return { - Header: intl.get('balance'), - Cell: CellTextSpan, - accessor: 'formatted_balance', - className: 'balance', - width: getColumnWidth(data, `balance`, { minWidth: 140 }), - textOverview: true, - align: Align.Right, - }; -}; - -/** - * Retrieve trial balance sheet table columns. - */ -export const useTrialBalanceTableColumns = () => { - // Trial balance sheet context. - const { - trialBalanceSheet: { tableRows }, - } = useTrialBalanceSheetContext(); - - return React.useMemo( - () => [ - { - Header: intl.get('account_name'), - accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name), - className: 'name', - width: 350, - textOverview: true, - }, - getCreditColumn(tableRows), - getDebitColumn(tableRows), - getBalanceColumn(tableRows), - ], - [tableRows], - ); -}; - /** * Trial balance sheet progress loading bar. */ @@ -101,7 +23,7 @@ export function TrialBalanceSheetLoadingBar() { */ export function TrialBalanceSheetAlerts() { const { - trialBalanceSheet: { meta }, + trialBalanceSheet, isLoading, refetchSheet, } = useTrialBalanceSheetContext(); @@ -115,7 +37,7 @@ export function TrialBalanceSheetAlerts() { return null; } // Can't continue if the cost compute job is not running. - if (!meta.is_cost_compute_running) { + if (!trialBalanceSheet?.meta.is_cost_compute_running) { return null; } diff --git a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts new file mode 100644 index 000000000..50fc1eef9 --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts @@ -0,0 +1,48 @@ +import { Align } from '@/constants'; +import { getColumnWidth } from '@/utils'; +import * as R from 'ramda'; + +const getTableCellValueAccessor = (index: number) => `cells[${index}].value`; + +const accountNameAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + id: column.key, + accessor, + className: column.key, + width: getColumnWidth(data, accessor, { minWidth: 120 }), + }; +}); + +const amountAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + id: column.key, + accessor, + className: column.key, + width: getColumnWidth(data, accessor, { minWidth: 120 }), + align: Align.Right, + }; +}); + +const dynamicColumnMapper = R.curry((data, column) => { + const accountNameColumn = accountNameAccessor(data); + const creditColumn = amountAccessor(data); + const debitColumn = amountAccessor(data); + const totalColumn = amountAccessor(data); + + return R.compose( + R.when(R.pathEq(['key'], 'account_name'), accountNameColumn), + R.when(R.pathEq(['key'], 'credit'), creditColumn), + R.when(R.pathEq(['key'], 'debit'), debitColumn), + R.when(R.pathEq(['key'], 'total'), totalColumn), + )(column); +}); + +export const trialBalancesheetDynamicColumns = (columns, data) => { + return R.map(dynamicColumnMapper(data), columns); +}; diff --git a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/hooks.ts b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/hooks.ts new file mode 100644 index 000000000..47bcc293d --- /dev/null +++ b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/hooks.ts @@ -0,0 +1,18 @@ +// @ts-nocheck +import React from 'react'; +import { useTrialBalanceSheetContext } from './TrialBalanceProvider'; +import { trialBalancesheetDynamicColumns } from './dynamicColumns'; + +/** + * Retrieves the trial balance sheet columns. + */ +export const useTrialBalanceSheetTableColumns = () => { + const { + trialBalanceSheet: { table }, + } = useTrialBalanceSheetContext(); + + return React.useMemo( + () => trialBalancesheetDynamicColumns(table.columns, table.rows), + [table], + ); +}; diff --git a/packages/webapp/src/hooks/query/financialReports.tsx b/packages/webapp/src/hooks/query/financialReports.tsx index e0919584d..3645a6293 100644 --- a/packages/webapp/src/hooks/query/financialReports.tsx +++ b/packages/webapp/src/hooks/query/financialReports.tsx @@ -43,17 +43,12 @@ export function useTrialBalanceSheet(query, props) { method: 'get', url: '/financial_statements/trial_balance_sheet', params: query, + headers: { + Accept: 'application/json+table', + }, }, { - select: (res) => ({ - tableRows: trialBalanceSheetReducer(res.data.data), - ...res.data, - }), - defaultData: { - tableRows: [], - data: [], - query: {}, - }, + select: (res) => res.data, ...props, }, ); From ae6688a81ce069f5c61fbef9864ccd7413b28209 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 19 Oct 2023 21:43:53 +0200 Subject: [PATCH 3/4] feat: trial balance sheet --- .../server/src/interfaces/TrialBalanceSheet.ts | 1 + .../TrialBalanceSheet/TrialBalanceSheet.ts | 17 +++++++++++++++++ .../TrialBalanceSheet/TrialBalanceSheetTable.ts | 4 ++-- .../TrialBalanceSheet/dynamicColumns.ts | 3 ++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/server/src/interfaces/TrialBalanceSheet.ts b/packages/server/src/interfaces/TrialBalanceSheet.ts index a3cc37568..56f13e7fa 100644 --- a/packages/server/src/interfaces/TrialBalanceSheet.ts +++ b/packages/server/src/interfaces/TrialBalanceSheet.ts @@ -33,6 +33,7 @@ export interface ITrialBalanceAccount extends ITrialBalanceTotal { id: number; parentAccountId: number; name: string; + formattedName: string; code: string; accountNormal: string; } diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 4a517650c..61ec665ec 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -13,8 +13,22 @@ import { allPassedConditionsPass, flatToNestedArray } from 'utils'; import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository'; export default class TrialBalanceSheet extends FinancialSheet { + /** + * Trial balance sheet query. + * @param {ITrialBalanceSheetQuery} query + */ private query: ITrialBalanceSheetQuery; + + /** + * Trial balance sheet repository. + * @param {TrialBalanceSheetRepository} + */ private repository: TrialBalanceSheetRepository; + + /** + * Organization base currency. + * @param {string} + */ private baseCurrency: string; /** @@ -89,6 +103,9 @@ export default class TrialBalanceSheet extends FinancialSheet { id: account.id, parentAccountId: account.parentAccountId, name: account.name, + formattedName: account.code + ? `${account.name} - ${account.code}` + : `${account.name}`, code: account.code, accountNormal: account.accountNormal, diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts index b00d379c8..1cbb2e7e6 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts @@ -46,14 +46,14 @@ export class TrialBalanceSheetTable extends R.compose( this.query = query; this.i18n = i18n; } - + /** * Retrieve the common columns for all report nodes. * @param {ITableColumnAccessor[]} */ private commonColumnsAccessors = (): ITableColumnAccessor[] => { return [ - { key: 'account', accessor: 'name' }, + { key: 'account', accessor: 'formattedName' }, { key: 'debit', accessor: 'formattedDebit' }, { key: 'credit', accessor: 'formattedCredit' }, { key: 'total', accessor: 'formattedBalance' }, diff --git a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts index 50fc1eef9..0413c41a0 100644 --- a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts +++ b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { Align } from '@/constants'; import { getColumnWidth } from '@/utils'; import * as R from 'ramda'; @@ -12,7 +13,7 @@ const accountNameAccessor = R.curry((data, column) => { id: column.key, accessor, className: column.key, - width: getColumnWidth(data, accessor, { minWidth: 120 }), + width: 350, }; }); From 1f1db41bd4f444dec85e145f06d9580c72d6d709 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 24 Oct 2023 18:35:32 +0200 Subject: [PATCH 4/4] feat: trial balance sheet adjusted total --- packages/server/src/services/Accounting/Ledger.ts | 9 +++++++++ .../TrialBalanceSheet/TrialBalanceSheet.ts | 10 ++++++++-- .../TrialBalanceSheetRepository.ts | 11 ++++++----- .../TrialBalanceSheet/TrialBalanceSheetService.ts | 2 ++ .../TrialBalanceSheet/dynamicColumns.ts | 13 ++++++++++--- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/server/src/services/Accounting/Ledger.ts b/packages/server/src/services/Accounting/Ledger.ts index ab241ebe2..e8bbcdfa4 100644 --- a/packages/server/src/services/Accounting/Ledger.ts +++ b/packages/server/src/services/Accounting/Ledger.ts @@ -49,6 +49,15 @@ export default class Ledger implements ILedger { return this.filter((entry) => entry.accountId === accountId); } + /** + * Filters entries by the given accounts ids then returns a new ledger. + * @param {number[]} accountsIds - Accounts ids. + * @returns {ILedger} + */ + public whereAccountsIds(accountsIds: number[]): ILedger { + return this.filter((entry) => accountsIds.indexOf(entry.accountId) !== -1); + } + /** * Filters entries that before or same the given date and returns a new ledger. * @param {Date|string} fromDate diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 61ec665ec..38ed3a944 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -59,8 +59,11 @@ export default class TrialBalanceSheet extends FinancialSheet { * @returns {number} */ public getClosingAccountCredit(accountId: number) { + const depsAccountsIds = + this.repository.accountsDepGraph.dependenciesOf(accountId); + return this.repository.totalAccountsLedger - .whereAccountId(accountId) + .whereAccountsIds([accountId, ...depsAccountsIds]) .getClosingCredit(); } @@ -70,8 +73,11 @@ export default class TrialBalanceSheet extends FinancialSheet { * @returns {number} */ public getClosingAccountDebit(accountId: number) { + const depsAccountsIds = + this.repository.accountsDepGraph.dependenciesOf(accountId); + return this.repository.totalAccountsLedger - .whereAccountId(accountId) + .whereAccountsIds([accountId, ...depsAccountsIds]) .getClosingDebit(); } diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts index cffc71c7a..1c37edd4d 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetRepository.ts @@ -8,11 +8,8 @@ import { Service } from 'typedi'; export class TrialBalanceSheetRepository { private query: ITrialBalanceSheetQuery; private models: any; - - /** - * - */ public accounts: any; + public accountsDepGraph; /** * Total closing accounts ledger. @@ -25,8 +22,9 @@ export class TrialBalanceSheetRepository { * @param {number} tenantId * @param {IBalanceSheetQuery} query */ - constructor(models: any, query: ITrialBalanceSheetQuery) { + constructor(models: any, repos: any, query: ITrialBalanceSheetQuery) { this.query = query; + this.repos = repos; this.models = models; } @@ -48,7 +46,10 @@ export class TrialBalanceSheetRepository { */ public initAccounts = async () => { const accounts = await this.getAccounts(); + const accountsDepGraph = + await this.repos.accountRepository.getDependencyGraph(); + this.accountsDepGraph = accountsDepGraph; this.accounts = accounts; }; diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts index 1b7789d62..150f160b0 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts @@ -95,9 +95,11 @@ export default class TrialBalanceSheetService extends FinancialSheet { .withGraphFetched('metadata'); const models = this.tenancy.models(tenantId); + const repos = this.tenancy.repositories(tenantId); const trialBalanceSheetRepos = new TrialBalanceSheetRepository( models, + repos, filter ); await trialBalanceSheetRepos.asyncInitialize(); diff --git a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts index 0413c41a0..c7f23f2aa 100644 --- a/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts +++ b/packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dynamicColumns.ts @@ -1,7 +1,11 @@ // @ts-nocheck +import * as R from 'ramda'; import { Align } from '@/constants'; import { getColumnWidth } from '@/utils'; -import * as R from 'ramda'; + +const ACCOUNT_NAME_COLUMN_WIDTH = 320; +const AMOUNT_COLUMNS_MIN_WIDTH = 120; +const AMOUNT_COLUMNS_MAGIC_SPACING = 10; const getTableCellValueAccessor = (index: number) => `cells[${index}].value`; @@ -13,7 +17,7 @@ const accountNameAccessor = R.curry((data, column) => { id: column.key, accessor, className: column.key, - width: 350, + width: ACCOUNT_NAME_COLUMN_WIDTH, }; }); @@ -25,7 +29,10 @@ const amountAccessor = R.curry((data, column) => { id: column.key, accessor, className: column.key, - width: getColumnWidth(data, accessor, { minWidth: 120 }), + width: getColumnWidth(data, accessor, { + magicSpacing: AMOUNT_COLUMNS_MAGIC_SPACING, + minWidth: AMOUNT_COLUMNS_MIN_WIDTH, + }), align: Align.Right, }; });