From 6afe1a09c673bfd0c3933d9fd02e3ec5784562e7 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 4 Jun 2024 21:26:46 +0200 Subject: [PATCH] fix: Closing balance in general ledger report does not sum the negative figures. --- .../BalanceSheet/BalanceSheetRepository.ts | 2 - .../GeneralLedger/GeneralLedger.ts | 43 +++--- .../GeneralLedger/GeneralLedgerRepository.ts | 123 ++++++++++++++++++ .../GeneralLedger/GeneralLedgerService.ts | 81 ++---------- 4 files changed, 152 insertions(+), 97 deletions(-) create mode 100644 packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerRepository.ts diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetRepository.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetRepository.ts index 001f266be..889593516 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetRepository.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetRepository.ts @@ -3,7 +3,6 @@ import * as R from 'ramda'; import { Knex } from 'knex'; import { isEmpty } from 'lodash'; import { - IAccount, IAccountTransactionsGroupBy, IBalanceSheetQuery, ILedger, @@ -12,7 +11,6 @@ import { transformToMapBy } from 'utils'; import Ledger from '@/services/Accounting/Ledger'; import { BalanceSheetQuery } from './BalanceSheetQuery'; import { FinancialDatePeriods } from '../FinancialDatePeriods'; -import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/data/AccountTypes'; import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome'; @Service() diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts index 5e12e9078..98c7ca09c 100644 --- a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts @@ -1,29 +1,25 @@ import { isEmpty, get, last, sumBy } from 'lodash'; +import moment from 'moment'; import { IGeneralLedgerSheetQuery, IGeneralLedgerSheetAccount, IGeneralLedgerSheetAccountBalance, IGeneralLedgerSheetAccountTransaction, IAccount, - IJournalPoster, - IJournalEntry, - IContact, + ILedgerEntry, } from '@/interfaces'; import FinancialSheet from '../FinancialSheet'; -import moment from 'moment'; +import { GeneralLedgerRepository } from './GeneralLedgerRepository'; /** * General ledger sheet. */ export default class GeneralLedgerSheet extends FinancialSheet { tenantId: number; - accounts: IAccount[]; query: IGeneralLedgerSheetQuery; - openingBalancesJournal: IJournalPoster; - transactions: IJournalPoster; - contactsMap: Map; baseCurrency: string; i18n: any; + repository: GeneralLedgerRepository; /** * Constructor method. @@ -36,11 +32,7 @@ export default class GeneralLedgerSheet extends FinancialSheet { constructor( tenantId: number, query: IGeneralLedgerSheetQuery, - accounts: IAccount[], - contactsByIdMap: Map, - transactions: IJournalPoster, - openingBalancesJournal: IJournalPoster, - baseCurrency: string, + repository: GeneralLedgerRepository, i18n ) { super(); @@ -48,11 +40,8 @@ export default class GeneralLedgerSheet extends FinancialSheet { this.tenantId = tenantId; this.query = query; this.numberFormat = this.query.numberFormat; - this.accounts = accounts; - this.contactsMap = contactsByIdMap; - this.transactions = transactions; - this.openingBalancesJournal = openingBalancesJournal; - this.baseCurrency = baseCurrency; + this.repository = repository; + this.baseCurrency = this.repository.tenant.metadata.currencyCode; this.i18n = i18n; } @@ -68,17 +57,17 @@ export default class GeneralLedgerSheet extends FinancialSheet { /** * Entry mapper. - * @param {IJournalEntry} entry - + * @param {ILedgerEntry} entry - * @return {IGeneralLedgerSheetAccountTransaction} */ entryReducer( entries: IGeneralLedgerSheetAccountTransaction[], - entry: IJournalEntry, + entry: ILedgerEntry, openingBalance: number ): IGeneralLedgerSheetAccountTransaction[] { const lastEntry = last(entries); - const contact = this.contactsMap.get(entry.contactId); + const contact = this.repository.contactsById.get(entry.contactId); const amount = this.getAmount( entry.credit, entry.debit, @@ -130,12 +119,14 @@ export default class GeneralLedgerSheet extends FinancialSheet { account: IAccount, openingBalance: number ): IGeneralLedgerSheetAccountTransaction[] { - const entries = this.transactions.getAccountEntries(account.id); + const entries = this.repository.transactionsLedger + .whereAccountId(account.id) + .getEntries(); return entries.reduce( ( entries: IGeneralLedgerSheetAccountTransaction[], - entry: IJournalEntry + entry: ILedgerEntry ) => { return this.entryReducer(entries, entry, openingBalance); }, @@ -151,7 +142,9 @@ export default class GeneralLedgerSheet extends FinancialSheet { private accountOpeningBalance( account: IAccount ): IGeneralLedgerSheetAccountBalance { - const amount = this.openingBalancesJournal.getAccountBalance(account.id); + const amount = this.repository.openingBalanceTransactionsLedger + .whereAccountId(account.id) + .getClosingBalance(); const formattedAmount = this.formatTotalNumber(amount); const currencyCode = this.baseCurrency; const date = this.query.fromDate; @@ -238,6 +231,6 @@ export default class GeneralLedgerSheet extends FinancialSheet { * @return {IGeneralLedgerSheetAccount[]} */ public reportData(): IGeneralLedgerSheetAccount[] { - return this.accountsWalker(this.accounts); + return this.accountsWalker(this.repository.accounts); } } diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerRepository.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerRepository.ts new file mode 100644 index 000000000..60ecca5dd --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerRepository.ts @@ -0,0 +1,123 @@ +import moment from 'moment'; +import { + IAccount, + IAccountTransaction, + IContact, + IGeneralLedgerSheetQuery, + ITenant, +} from '@/interfaces'; +import Ledger from '@/services/Accounting/Ledger'; +import { transformToMap } from '@/utils'; +import { Tenant } from '@/system/models'; + +export class GeneralLedgerRepository { + public filter: IGeneralLedgerSheetQuery; + public accounts: IAccount[]; + + public transactions: IAccountTransaction[]; + public openingBalanceTransactions: IAccountTransaction[]; + + public transactionsLedger: Ledger; + public openingBalanceTransactionsLedger: Ledger; + + public repositories: any; + public models: any; + public accountsGraph: any; + + public contacts: IContact; + public contactsById: Map; + + public tenantId: number; + public tenant: ITenant; + + /** + * Constructor method. + * @param models + * @param repositories + * @param filter + */ + constructor( + repositories: any, + filter: IGeneralLedgerSheetQuery, + tenantId: number + ) { + this.filter = filter; + this.repositories = repositories; + this.tenantId = tenantId; + } + + /** + * Initialize the G/L report. + */ + public async asyncInitialize() { + await this.initTenant(); + await this.initAccounts(); + await this.initAccountsGraph(); + await this.initContacts(); + await this.initTransactions(); + await this.initAccountsOpeningBalance(); + } + + /** + * Initialize the tenant. + */ + public async initTenant() { + this.tenant = await Tenant.query() + .findById(this.tenantId) + .withGraphFetched('metadata'); + } + + /** + * Initialize the accounts. + */ + public async initAccounts() { + this.accounts = await this.repositories.accountRepository.all(); + } + + /** + * 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, + }); + // 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 + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts index 53451d2d8..c2e407f5c 100644 --- a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts @@ -1,18 +1,10 @@ import { Service, Inject } from 'typedi'; import moment from 'moment'; -import { ServiceError } from '@/exceptions'; -import { difference } from 'lodash'; import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces'; import TenancyService from '@/services/Tenancy/TenancyService'; -import Journal from '@/services/Accounting/JournalPoster'; import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger'; -import { transformToMap } from 'utils'; -import { Tenant } from '@/system/models'; import { GeneralLedgerMeta } from './GeneralLedgerMeta'; - -const ERRORS = { - ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND', -}; +import { GeneralLedgerRepository } from './GeneralLedgerRepository'; @Service() export class GeneralLedgerService { @@ -40,29 +32,13 @@ export class GeneralLedgerService { }; } - /** - * Validates accounts existance on the storage. - * @param {number} tenantId - * @param {number[]} accountsIds - */ - async validateAccountsExistance(tenantId: number, accountsIds: number[]) { - const { Account } = this.tenancy.models(tenantId); - - const storedAccounts = await Account.query().whereIn('id', accountsIds); - const storedAccountsIds = storedAccounts.map((a) => a.id); - - if (difference(accountsIds, storedAccountsIds).length > 0) { - throw new ServiceError(ERRORS.ACCOUNTS_NOT_FOUND); - } - } - /** * Retrieve general ledger report statement. * @param {number} tenantId * @param {IGeneralLedgerSheetQuery} query - * @return {IGeneralLedgerStatement} + * @return {Promise} */ - async generalLedger( + public async generalLedger( tenantId: number, query: IGeneralLedgerSheetQuery ): Promise<{ @@ -70,60 +46,25 @@ export class GeneralLedgerService { query: IGeneralLedgerSheetQuery; meta: IGeneralLedgerMeta; }> { - const { accountRepository, transactionsRepository, contactRepository } = - this.tenancy.repositories(tenantId); - + const repositories = this.tenancy.repositories(tenantId); const i18n = this.tenancy.i18n(tenantId); - const tenant = await Tenant.query() - .findById(tenantId) - .withGraphFetched('metadata'); - const filter = { ...this.defaultQuery, ...query, }; - // Retrieve all accounts with associated type from the storage. - const accounts = await accountRepository.all(); - const accountsGraph = await accountRepository.getDependencyGraph(); - - // Retrieve all contacts on the storage. - const contacts = await contactRepository.all(); - const contactsByIdMap = transformToMap(contacts, 'id'); - - // Retreive journal transactions from/to the given date. - const transactions = await transactionsRepository.journal({ - fromDate: filter.fromDate, - toDate: filter.toDate, - branchesIds: filter.branchesIds, - }); - // Retreive opening balance credit/debit sumation. - const openingBalanceTrans = await transactionsRepository.journal({ - toDate: moment(filter.fromDate).subtract(1, 'day'), - sumationCreditDebit: true, - branchesIds: filter.branchesIds, - }); - // Transform array transactions to journal collection. - const transactionsJournal = Journal.fromTransactions( - transactions, - tenantId, - accountsGraph - ); - // Accounts opening transactions. - const openingTransJournal = Journal.fromTransactions( - openingBalanceTrans, - tenantId, - accountsGraph + const genealLedgerRepository = new GeneralLedgerRepository( + repositories, + query, + tenantId ); + await genealLedgerRepository.asyncInitialize(); + // General ledger report instance. const generalLedgerInstance = new GeneralLedgerSheet( tenantId, filter, - accounts, - contactsByIdMap, - transactionsJournal, - openingTransJournal, - tenant.metadata.baseCurrency, + genealLedgerRepository, i18n ); // Retrieve general ledger report data.