fix: Closing balance in general ledger report does not sum the negative figures.

This commit is contained in:
Ahmed Bouhuolia
2024-06-04 21:26:46 +02:00
parent 909a70e2c5
commit 6afe1a09c6
4 changed files with 152 additions and 97 deletions

View File

@@ -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<number, IContact>;
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<number, IContact>,
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);
}
}

View File

@@ -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<number, IContact>;
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
);
}
}

View File

@@ -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<IGeneralLedgerStatement>}
*/
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.