From 80e51cd2f7a224074bae9a086d69fff48f3de2dc Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Mon, 11 Jan 2021 11:21:32 +0200 Subject: [PATCH] feat: always show some sections in balance sheet. --- .../FinancialStatements/BalanceSheet.ts | 38 ++++++------- .../FinancialStatements/GeneralLedger.ts | 37 +++++++------ server/src/data/BalanceSheetStructure.ts | 18 +++---- server/src/interfaces/BalanceSheet.ts | 6 +-- .../src/services/Accounting/JournalPoster.ts | 11 ++-- .../BalanceSheet/BalanceSheet.ts | 45 +++++++++++----- .../JournalSheet/JournalSheet.ts | 49 +++++++++++------ .../TrialBalanceSheet/TrialBalanceSheet.ts | 53 +++++++++++-------- .../TrialBalanceSheetService.ts | 36 +++++++------ 9 files changed, 172 insertions(+), 121 deletions(-) diff --git a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts index d8878ab4a..fe1562e27 100644 --- a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts +++ b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts @@ -1,5 +1,5 @@ import { Inject, Service } from 'typedi'; -import { Router, Request, Response, NextFunction } from 'express'; +import { Router, Request, Response, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; import { castArray } from 'lodash'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; @@ -7,7 +7,7 @@ import BaseController from '../BaseController'; import BalanceSheetStatementService from 'services/FinancialStatements/BalanceSheet/BalanceSheetService'; @Service() -export default class BalanceSheetStatementController extends BaseController{ +export default class BalanceSheetStatementController extends BaseController { @Inject() balanceSheetService: BalanceSheetStatementService; @@ -32,25 +32,15 @@ export default class BalanceSheetStatementController extends BaseController{ */ get balanceSheetValidationSchema(): ValidationChain[] { return [ - query('accounting_method') - .optional() - .isIn(['cash', 'accural']), + query('accounting_method').optional().isIn(['cash', 'accural']), query('from_date').optional(), query('to_date').optional(), - query('display_columns_type') - .optional() - .isIn(['date_periods', 'total']), + query('display_columns_type').optional().isIn(['date_periods', 'total']), query('display_columns_by') .optional({ nullable: true, checkFalsy: true }) .isIn(['year', 'month', 'week', 'day', 'quarter']), - query('number_format.no_cents') - .optional() - .isBoolean() - .toBoolean(), - query('number_format.divide_1000') - .optional() - .isBoolean() - .toBoolean(), + query('number_format.no_cents').optional().isBoolean().toBoolean(), + query('number_format.divide_1000').optional().isBoolean().toBoolean(), query('account_ids').isArray().optional(), query('account_ids.*').isNumeric().toInt(), query('none_zero').optional().isBoolean().toBoolean(), @@ -69,14 +59,20 @@ export default class BalanceSheetStatementController extends BaseController{ ...filter, accountsIds: castArray(filter.accountsIds), }; - const organizationName = settings.get({ group: 'organization', key: 'name' }); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); try { const { data, columns, - query + query, } = await this.balanceSheetService.balanceSheet(tenantId, filter); return res.status(200).send({ @@ -85,9 +81,9 @@ export default class BalanceSheetStatementController extends BaseController{ data: this.transfromToResponse(data), columns: this.transfromToResponse(columns), query: this.transfromToResponse(query), - }) + }); } catch (error) { next(error); } } -}; +} diff --git a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts index aed3e2da0..b0357373d 100644 --- a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts +++ b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts @@ -6,8 +6,7 @@ import { Inject, Service } from 'typedi'; import GeneralLedgerService from 'services/FinancialStatements/GeneralLedger/GeneralLedgerService'; @Service() -export default class GeneralLedgerReportController extends BaseController{ - +export default class GeneralLedgerReportController extends BaseController { @Inject() generalLedgetService: GeneralLedgerService; @@ -17,7 +16,8 @@ export default class GeneralLedgerReportController extends BaseController{ router() { const router = Router(); - router.get('/', + router.get( + '/', this.validationSchema, this.validationResult, asyncMiddleware(this.generalLedger.bind(this)) @@ -35,9 +35,9 @@ export default class GeneralLedgerReportController extends BaseController{ query('basis').optional(), query('number_format.no_cents').optional().isBoolean().toBoolean(), query('number_format.divide_1000').optional().isBoolean().toBoolean(), - query('none_transactions').optional().isBoolean().toBoolean(), - query('accounts_ids').optional(), - query('accounts_ids.*').isNumeric().toInt(), + query('none_transactions').default(true).isBoolean().toBoolean(), + query('accounts_ids').optional().isArray({ min: 1 }), + query('accounts_ids.*').isInt().toInt(), query('orderBy').optional().isIn(['created_at', 'name', 'code']), query('order').optional().isIn(['desc', 'asc']), ]; @@ -45,22 +45,27 @@ export default class GeneralLedgerReportController extends BaseController{ /** * Retrieve the general ledger financial statement. - * @param {Request} req - - * @param {Response} res - + * @param {Request} req - + * @param {Response} res - */ async generalLedger(req: Request, res: Response, next: NextFunction) { const { tenantId, settings } = req; const filter = this.matchedQueryData(req); - const organizationName = settings.get({ group: 'organization', key: 'name' }); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); try { - const { - data, - query, - } = await this.generalLedgetService.generalLedger(tenantId, filter); - + const { data, query } = await this.generalLedgetService.generalLedger( + tenantId, + filter + ); return res.status(200).send({ organization_name: organizationName, base_currency: baseCurrency, @@ -71,4 +76,4 @@ export default class GeneralLedgerReportController extends BaseController{ next(error); } } -} \ No newline at end of file +} diff --git a/server/src/data/BalanceSheetStructure.ts b/server/src/data/BalanceSheetStructure.ts index 688e02596..d57a58d9c 100644 --- a/server/src/data/BalanceSheetStructure.ts +++ b/server/src/data/BalanceSheetStructure.ts @@ -9,20 +9,20 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [ { name: 'Current Asset', type: 'accounts_section', - _accountsTypesRelated: ['current_asset'], + accountsTypesRelated: ['current_asset'], }, { name: 'Fixed Asset', type: 'accounts_section', - _accountsTypesRelated: ['fixed_asset'], + accountsTypesRelated: ['fixed_asset'], }, { name: 'Other Asset', type: 'accounts_section', - _accountsTypesRelated: ['other_asset'], + accountsTypesRelated: ['other_asset'], }, ], - _forceShow: true, + alwaysShow: true, }, { name: 'Liabilities and Equity', @@ -37,17 +37,17 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [ { name: 'Current Liability', type: 'accounts_section', - _accountsTypesRelated: ['current_liability'], + accountsTypesRelated: ['current_liability'], }, { name: 'Long Term Liability', type: 'accounts_section', - _accountsTypesRelated: ['long_term_liability'], + accountsTypesRelated: ['long_term_liability'], }, { name: 'Other Liability', type: 'accounts_section', - _accountsTypesRelated: ['other_liability'], + accountsTypesRelated: ['other_liability'], }, ], }, @@ -55,10 +55,10 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [ name: 'Equity', sectionType: 'equity', type: 'accounts_section', - _accountsTypesRelated: ['equity'], + accountsTypesRelated: ['equity'], }, ], - _forceShow: true, + alwaysShow: true, }, ]; diff --git a/server/src/interfaces/BalanceSheet.ts b/server/src/interfaces/BalanceSheet.ts index 9e52c1131..a680fe13c 100644 --- a/server/src/interfaces/BalanceSheet.ts +++ b/server/src/interfaces/BalanceSheet.ts @@ -37,8 +37,8 @@ export interface IBalanceSheetStructureSection { sectionType?: string, type: 'section' | 'accounts_section', children?: IBalanceSheetStructureSection[], - _accountsTypesRelated?: string[], - _forceShow?: boolean, + accountsTypesRelated?: string[], + alwaysShow?: boolean, } export interface IBalanceSheetAccountTotal { @@ -69,6 +69,6 @@ export interface IBalanceSheetSection { total: IBalanceSheetAccountTotal, totalPeriods?: IBalanceSheetAccountTotal[]; - _accountsTypesRelated?: string[], + accountsTypesRelated?: string[], _forceShow?: boolean, } \ No newline at end of file diff --git a/server/src/services/Accounting/JournalPoster.ts b/server/src/services/Accounting/JournalPoster.ts index f7569607b..9d33e8461 100644 --- a/server/src/services/Accounting/JournalPoster.ts +++ b/server/src/services/Accounting/JournalPoster.ts @@ -324,6 +324,7 @@ export default class JournalPoster implements IJournalPoster { transactions.forEach((transaction) => { this.entries.push({ ...transaction, + referenceTypeFormatted: transaction.referenceTypeFormatted, account: transaction.accountId, accountNormal: get(transaction, 'account.type.normal'), }); @@ -417,7 +418,7 @@ export default class JournalPoster implements IJournalPoster { * @param {Number} account - * @param {Date|String} closingDate - */ - getTrialBalance(accountId, closingDate, dateType) { + getTrialBalance(accountId, closingDate) { const momentClosingDate = moment(closingDate); const result = { credit: 0, @@ -426,8 +427,8 @@ export default class JournalPoster implements IJournalPoster { }; this.entries.forEach((entry) => { if ( - (!momentClosingDate.isAfter(entry.date, dateType) && - !momentClosingDate.isSame(entry.date, dateType)) || + (!momentClosingDate.isAfter(entry.date, 'day') && + !momentClosingDate.isSame(entry.date, 'day')) || (entry.account !== accountId && accountId) ) { return; @@ -478,8 +479,8 @@ export default class JournalPoster implements IJournalPoster { accountId: number, contactId: number, contactType: string, - closingDate: Date|string, - openingDate: Date|string, + closingDate?: Date|string, + openingDate?: Date|string, ) { const momentClosingDate = moment(closingDate); const momentOpeningDate = moment(openingDate); diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts index c1ebcc21f..87dcbdaf5 100644 --- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts +++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts @@ -85,7 +85,7 @@ export default class BalanceSheetStatement extends FinancialSheet { * @return {IBalanceSheetAccountTotal[]} */ private getSectionTotalPeriods( - sections: Array + sections: Array ): IBalanceSheetAccountTotal[] { return this.dateRangeSet.map((date, index) => { const amount = sumBy(sections, `totalPeriods[${index}].amount`); @@ -203,8 +203,8 @@ export default class BalanceSheetStatement extends FinancialSheet { /** * Mappes the structure sections. - * @param {IBalanceSheetStructureSection} structure - * @param {IAccount} accounts + * @param {IBalanceSheetStructureSection} structure + * @param {IAccount} accounts */ private structureSectionMapper( structure: IBalanceSheetStructureSection, @@ -238,12 +238,12 @@ export default class BalanceSheetStatement extends FinancialSheet { name: structure.name, sectionType: structure.sectionType, type: structure.type, - ...(structure.type === 'accounts_section') + ...(structure.type === 'accounts_section' ? this.structureRelatedAccountsMapper( - structure._accountsTypesRelated, + structure.accountsTypesRelated, accounts ) - : this.structureSectionMapper(structure, accounts), + : this.structureSectionMapper(structure, accounts)), }; return result; } @@ -259,13 +259,30 @@ export default class BalanceSheetStatement extends FinancialSheet { ): IBalanceSheetSection[] { return ( reportStructure - .map((structure: IBalanceSheetStructureSection) => - this.balanceSheetStructureMapper(structure, balanceSheetAccounts) - ) - // Filter the structure sections that have no children. + .map((structure: IBalanceSheetStructureSection) => { + const sheetSection = this.balanceSheetStructureMapper( + structure, + balanceSheetAccounts + ); + return [sheetSection, structure]; + }) + // Filter the structure sections that have no children and not always show. .filter( - (structure: IBalanceSheetSection) => - structure.children.length > 0 || structure._forceShow + ([sheetSection, structure]: [ + IBalanceSheetSection, + IBalanceSheetStructureSection + ]) => { + return sheetSection.children.length > 0 || structure.alwaysShow; + } + ) + // Mappes the balance sheet scetions only + .map( + ([sheetSection, structure]: [ + IBalanceSheetSection, + IBalanceSheetStructureSection + ]) => { + return sheetSection; + } ) ); } @@ -295,6 +312,10 @@ export default class BalanceSheetStatement extends FinancialSheet { * @return {IBalanceSheetSection[]} */ public reportData(): IBalanceSheetSection[] { + // Returns nothing if there is no entries in the journal between the given period. + if (this.journalFinancial.entries.length === 0) { + return []; + } return this.balanceSheetStructureWalker( BalanceSheetStructure, this.accounts diff --git a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts index ce49f5483..718b75076 100644 --- a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts +++ b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts @@ -1,12 +1,13 @@ -import { sumBy, chain } from 'lodash'; +import { sumBy, chain, omit } from 'lodash'; import { IJournalEntry, IJournalPoster, IJournalReportEntriesGroup, IJournalReportQuery, - IJournalReport -} from "interfaces"; -import FinancialSheet from "../FinancialSheet"; + IJournalReport, +} from 'interfaces'; +import FinancialSheet from '../FinancialSheet'; +import { AccountTransaction } from 'models'; export default class JournalSheet extends FinancialSheet { tenantId: number; @@ -16,14 +17,14 @@ export default class JournalSheet extends FinancialSheet { /** * Constructor method. - * @param {number} tenantId - * @param {IJournalPoster} journal + * @param {number} tenantId + * @param {IJournalPoster} journal */ constructor( tenantId: number, query: IJournalReportQuery, journal: IJournalPoster, - baseCurrency: string, + baseCurrency: string ) { super(); @@ -34,23 +35,38 @@ export default class JournalSheet extends FinancialSheet { this.baseCurrency = baseCurrency; } + /** + * Mappes the journal entries. + * @param {IJournalEntry[]} entries - + */ + entriesMapper( + entries: IJournalEntry[], + ) { + return entries.map((entry: IJournalEntry) => { + return { + ...omit(entry, 'account'), + currencyCode: this.baseCurrency, + }; + }) + } + /** * Mapping journal entries groups. * @param {IJournalEntry[]} entriesGroup - * @param {string} key - * @return {IJournalReportEntriesGroup} */ - entriesGroupMapper( + entriesGroupsMapper( entriesGroup: IJournalEntry[], - key: string, + key: string ): IJournalReportEntriesGroup { const totalCredit = sumBy(entriesGroup, 'credit'); const totalDebit = sumBy(entriesGroup, 'debit'); return { id: key, - entries: entriesGroup, - + entries: this.entriesMapper(entriesGroup), + currencyCode: this.baseCurrency, credit: totalCredit, @@ -63,16 +79,15 @@ export default class JournalSheet extends FinancialSheet { /** * Mapping the journal entries to entries groups. - * @param {IJournalEntry[]} entries + * @param {IJournalEntry[]} entries * @return {IJournalReportEntriesGroup[]} */ entriesWalker(entries: IJournalEntry[]): IJournalReportEntriesGroup[] { return chain(entries) .groupBy((entry) => `${entry.referenceId}-${entry.referenceType}`) - .map(( - entriesGroup: IJournalEntry[], - key: string - ) => this.entriesGroupMapper(entriesGroup, key)) + .map((entriesGroup: IJournalEntry[], key: string) => + this.entriesGroupsMapper(entriesGroup, key) + ) .value(); } @@ -83,4 +98,4 @@ export default class JournalSheet extends FinancialSheet { reportData(): IJournalReport { return this.entriesWalker(this.journal.entries); } -} \ No newline at end of file +} diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 17841111b..0fe38c8a1 100644 --- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -1,4 +1,3 @@ - import { ITrialBalanceSheetQuery, ITrialBalanceAccount, @@ -8,7 +7,7 @@ import { import FinancialSheet from '../FinancialSheet'; import { flatToNestedArray } from 'utils'; -export default class TrialBalanceSheet extends FinancialSheet{ +export default class TrialBalanceSheet extends FinancialSheet { tenantId: number; query: ITrialBalanceSheetQuery; accounts: IAccount & { type: IAccountType }[]; @@ -17,17 +16,17 @@ export default class TrialBalanceSheet extends FinancialSheet{ /** * Constructor method. - * @param {number} tenantId - * @param {ITrialBalanceSheetQuery} query - * @param {IAccount[]} accounts - * @param journalFinancial + * @param {number} tenantId + * @param {ITrialBalanceSheetQuery} query + * @param {IAccount[]} accounts + * @param journalFinancial */ constructor( tenantId: number, query: ITrialBalanceSheetQuery, accounts: IAccount & { type: IAccountType }[], journalFinancial: any, - baseCurrency: string, + baseCurrency: string ) { super(); @@ -42,13 +41,15 @@ export default class TrialBalanceSheet extends FinancialSheet{ /** * Account mapper. - * @param {IAccount} account + * @param {IAccount} account */ - private accountMapper(account: IAccount & { type: IAccountType }): ITrialBalanceAccount { + private accountMapper( + account: IAccount & { type: IAccountType } + ): ITrialBalanceAccount { const trial = this.journalFinancial.getTrialBalanceWithDepands(account.id); // Retrieve all entries that associated to the given account. - const entries = this.journalFinancial.getAccountEntries(account.id) + const entries = this.journalFinancial.getAccountEntries(account.id); return { id: account.id, @@ -71,29 +72,35 @@ export default class TrialBalanceSheet extends FinancialSheet{ /** * Accounts walker. - * @param {IAccount[]} accounts + * @param {IAccount[]} accounts */ private accountsWalker( accounts: IAccount & { type: IAccountType }[] ): ITrialBalanceAccount[] { const flattenAccounts = accounts // Mapping the trial balance accounts sections. - .map((account: IAccount & { type: IAccountType }) => this.accountMapper(account)) - + .map((account: IAccount & { type: IAccountType }) => + this.accountMapper(account) + ) // Filter accounts that have no transaction when `noneTransactions` is on. - .filter((trialAccount: ITrialBalanceAccount): boolean => - !(!trialAccount.hasTransactions && this.query.noneTransactions), + .filter( + (trialAccount: ITrialBalanceAccount): boolean => + !(!trialAccount.hasTransactions && this.query.noneTransactions) ) // Filter accounts that have zero total amount when `noneZero` is on. .filter( (trialAccount: ITrialBalanceAccount): boolean => - !(trialAccount.credit === 0 && trialAccount.debit === 0 && this.query.noneZero) - ); - - return flatToNestedArray( - flattenAccounts, - { id: 'id', parentId: 'parentAccountId' }, - ); + !( + trialAccount.credit === 0 && + trialAccount.debit === 0 && + this.query.noneZero + ) + ); + + return flatToNestedArray(flattenAccounts, { + id: 'id', + parentId: 'parentAccountId', + }); } /** @@ -102,4 +109,4 @@ export default class TrialBalanceSheet extends FinancialSheet{ public reportData() { return this.accountsWalker(this.accounts); } -} \ No newline at end of file +} diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts index b2c25937e..d82b848f1 100644 --- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts +++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts @@ -1,13 +1,12 @@ -import { Service, Inject } from "typedi"; +import { Service, Inject } from 'typedi'; import moment from 'moment'; import TenancyService from 'services/Tenancy/TenancyService'; import { ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces'; -import TrialBalanceSheet from "./TrialBalanceSheet"; +import TrialBalanceSheet from './TrialBalanceSheet'; import Journal from 'services/Accounting/JournalPoster'; @Service() export default class TrialBalanceSheetService { - @Inject() tenancy: TenancyService; @@ -36,16 +35,15 @@ export default class TrialBalanceSheetService { /** * Retrieve trial balance sheet statement. * ------------- - * @param {number} tenantId - * @param {IBalanceSheetQuery} query - * + * @param {number} tenantId + * @param {IBalanceSheetQuery} query + * * @return {IBalanceSheetStatement} */ public async trialBalanceSheet( tenantId: number, - query: ITrialBalanceSheetQuery, + query: ITrialBalanceSheetQuery ): Promise { - const filter = { ...this.defaultQuery, ...query, @@ -57,10 +55,15 @@ export default class TrialBalanceSheetService { // Settings tenant service. const settings = this.tenancy.settings(tenantId); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); - - this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { tenantId, filter }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { + tenantId, + filter, + }); // Retrieve all accounts on the storage. const accounts = await accountRepository.all('type'); const accountsGraph = await accountRepository.getDependencyGraph(); @@ -72,8 +75,11 @@ export default class TrialBalanceSheetService { sumationCreditDebit: true, }); // Transform transactions array to journal collection. - const transactionsJournal = Journal.fromTransactions(transactions, tenantId, accountsGraph); - + const transactionsJournal = Journal.fromTransactions( + transactions, + tenantId, + accountsGraph + ); // Trial balance report instance. const trialBalanceInstance = new TrialBalanceSheet( tenantId, @@ -88,6 +94,6 @@ export default class TrialBalanceSheetService { return { data: trialBalanceSheetData, query: filter, - } + }; } -} \ No newline at end of file +}