mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
fix: Closing balance in general ledger report does not sum the negative figures.
This commit is contained in:
@@ -3,7 +3,6 @@ import * as R from 'ramda';
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
IAccount,
|
|
||||||
IAccountTransactionsGroupBy,
|
IAccountTransactionsGroupBy,
|
||||||
IBalanceSheetQuery,
|
IBalanceSheetQuery,
|
||||||
ILedger,
|
ILedger,
|
||||||
@@ -12,7 +11,6 @@ import { transformToMapBy } from 'utils';
|
|||||||
import Ledger from '@/services/Accounting/Ledger';
|
import Ledger from '@/services/Accounting/Ledger';
|
||||||
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
import { BalanceSheetQuery } from './BalanceSheetQuery';
|
||||||
import { FinancialDatePeriods } from '../FinancialDatePeriods';
|
import { FinancialDatePeriods } from '../FinancialDatePeriods';
|
||||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/data/AccountTypes';
|
|
||||||
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
|
import { BalanceSheetRepositoryNetIncome } from './BalanceSheetRepositoryNetIncome';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
import { isEmpty, get, last, sumBy } from 'lodash';
|
import { isEmpty, get, last, sumBy } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
IGeneralLedgerSheetQuery,
|
IGeneralLedgerSheetQuery,
|
||||||
IGeneralLedgerSheetAccount,
|
IGeneralLedgerSheetAccount,
|
||||||
IGeneralLedgerSheetAccountBalance,
|
IGeneralLedgerSheetAccountBalance,
|
||||||
IGeneralLedgerSheetAccountTransaction,
|
IGeneralLedgerSheetAccountTransaction,
|
||||||
IAccount,
|
IAccount,
|
||||||
IJournalPoster,
|
ILedgerEntry,
|
||||||
IJournalEntry,
|
|
||||||
IContact,
|
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import FinancialSheet from '../FinancialSheet';
|
import FinancialSheet from '../FinancialSheet';
|
||||||
import moment from 'moment';
|
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General ledger sheet.
|
* General ledger sheet.
|
||||||
*/
|
*/
|
||||||
export default class GeneralLedgerSheet extends FinancialSheet {
|
export default class GeneralLedgerSheet extends FinancialSheet {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
accounts: IAccount[];
|
|
||||||
query: IGeneralLedgerSheetQuery;
|
query: IGeneralLedgerSheetQuery;
|
||||||
openingBalancesJournal: IJournalPoster;
|
|
||||||
transactions: IJournalPoster;
|
|
||||||
contactsMap: Map<number, IContact>;
|
|
||||||
baseCurrency: string;
|
baseCurrency: string;
|
||||||
i18n: any;
|
i18n: any;
|
||||||
|
repository: GeneralLedgerRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -36,11 +32,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
constructor(
|
constructor(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: IGeneralLedgerSheetQuery,
|
query: IGeneralLedgerSheetQuery,
|
||||||
accounts: IAccount[],
|
repository: GeneralLedgerRepository,
|
||||||
contactsByIdMap: Map<number, IContact>,
|
|
||||||
transactions: IJournalPoster,
|
|
||||||
openingBalancesJournal: IJournalPoster,
|
|
||||||
baseCurrency: string,
|
|
||||||
i18n
|
i18n
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -48,11 +40,8 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
this.tenantId = tenantId;
|
this.tenantId = tenantId;
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
this.accounts = accounts;
|
this.repository = repository;
|
||||||
this.contactsMap = contactsByIdMap;
|
this.baseCurrency = this.repository.tenant.metadata.currencyCode;
|
||||||
this.transactions = transactions;
|
|
||||||
this.openingBalancesJournal = openingBalancesJournal;
|
|
||||||
this.baseCurrency = baseCurrency;
|
|
||||||
this.i18n = i18n;
|
this.i18n = i18n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,17 +57,17 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry mapper.
|
* Entry mapper.
|
||||||
* @param {IJournalEntry} entry -
|
* @param {ILedgerEntry} entry -
|
||||||
* @return {IGeneralLedgerSheetAccountTransaction}
|
* @return {IGeneralLedgerSheetAccountTransaction}
|
||||||
*/
|
*/
|
||||||
entryReducer(
|
entryReducer(
|
||||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||||
entry: IJournalEntry,
|
entry: ILedgerEntry,
|
||||||
openingBalance: number
|
openingBalance: number
|
||||||
): IGeneralLedgerSheetAccountTransaction[] {
|
): IGeneralLedgerSheetAccountTransaction[] {
|
||||||
const lastEntry = last(entries);
|
const lastEntry = last(entries);
|
||||||
|
|
||||||
const contact = this.contactsMap.get(entry.contactId);
|
const contact = this.repository.contactsById.get(entry.contactId);
|
||||||
const amount = this.getAmount(
|
const amount = this.getAmount(
|
||||||
entry.credit,
|
entry.credit,
|
||||||
entry.debit,
|
entry.debit,
|
||||||
@@ -130,12 +119,14 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
account: IAccount,
|
account: IAccount,
|
||||||
openingBalance: number
|
openingBalance: number
|
||||||
): IGeneralLedgerSheetAccountTransaction[] {
|
): IGeneralLedgerSheetAccountTransaction[] {
|
||||||
const entries = this.transactions.getAccountEntries(account.id);
|
const entries = this.repository.transactionsLedger
|
||||||
|
.whereAccountId(account.id)
|
||||||
|
.getEntries();
|
||||||
|
|
||||||
return entries.reduce(
|
return entries.reduce(
|
||||||
(
|
(
|
||||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||||
entry: IJournalEntry
|
entry: ILedgerEntry
|
||||||
) => {
|
) => {
|
||||||
return this.entryReducer(entries, entry, openingBalance);
|
return this.entryReducer(entries, entry, openingBalance);
|
||||||
},
|
},
|
||||||
@@ -151,7 +142,9 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
private accountOpeningBalance(
|
private accountOpeningBalance(
|
||||||
account: IAccount
|
account: IAccount
|
||||||
): IGeneralLedgerSheetAccountBalance {
|
): IGeneralLedgerSheetAccountBalance {
|
||||||
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
|
const amount = this.repository.openingBalanceTransactionsLedger
|
||||||
|
.whereAccountId(account.id)
|
||||||
|
.getClosingBalance();
|
||||||
const formattedAmount = this.formatTotalNumber(amount);
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
const date = this.query.fromDate;
|
const date = this.query.fromDate;
|
||||||
@@ -238,6 +231,6 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
* @return {IGeneralLedgerSheetAccount[]}
|
* @return {IGeneralLedgerSheetAccount[]}
|
||||||
*/
|
*/
|
||||||
public reportData(): IGeneralLedgerSheetAccount[] {
|
public reportData(): IGeneralLedgerSheetAccount[] {
|
||||||
return this.accountsWalker(this.accounts);
|
return this.accountsWalker(this.repository.accounts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { difference } from 'lodash';
|
|
||||||
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
|
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import Journal from '@/services/Accounting/JournalPoster';
|
|
||||||
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
|
||||||
import { transformToMap } from 'utils';
|
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
import { GeneralLedgerMeta } from './GeneralLedgerMeta';
|
||||||
|
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||||
const ERRORS = {
|
|
||||||
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
|
|
||||||
};
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class GeneralLedgerService {
|
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.
|
* Retrieve general ledger report statement.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IGeneralLedgerSheetQuery} query
|
* @param {IGeneralLedgerSheetQuery} query
|
||||||
* @return {IGeneralLedgerStatement}
|
* @return {Promise<IGeneralLedgerStatement>}
|
||||||
*/
|
*/
|
||||||
async generalLedger(
|
public async generalLedger(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: IGeneralLedgerSheetQuery
|
query: IGeneralLedgerSheetQuery
|
||||||
): Promise<{
|
): Promise<{
|
||||||
@@ -70,60 +46,25 @@ export class GeneralLedgerService {
|
|||||||
query: IGeneralLedgerSheetQuery;
|
query: IGeneralLedgerSheetQuery;
|
||||||
meta: IGeneralLedgerMeta;
|
meta: IGeneralLedgerMeta;
|
||||||
}> {
|
}> {
|
||||||
const { accountRepository, transactionsRepository, contactRepository } =
|
const repositories = this.tenancy.repositories(tenantId);
|
||||||
this.tenancy.repositories(tenantId);
|
|
||||||
|
|
||||||
const i18n = this.tenancy.i18n(tenantId);
|
const i18n = this.tenancy.i18n(tenantId);
|
||||||
|
|
||||||
const tenant = await Tenant.query()
|
|
||||||
.findById(tenantId)
|
|
||||||
.withGraphFetched('metadata');
|
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
...this.defaultQuery,
|
...this.defaultQuery,
|
||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
// Retrieve all accounts with associated type from the storage.
|
const genealLedgerRepository = new GeneralLedgerRepository(
|
||||||
const accounts = await accountRepository.all();
|
repositories,
|
||||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
query,
|
||||||
|
tenantId
|
||||||
// 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
|
|
||||||
);
|
);
|
||||||
|
await genealLedgerRepository.asyncInitialize();
|
||||||
|
|
||||||
// General ledger report instance.
|
// General ledger report instance.
|
||||||
const generalLedgerInstance = new GeneralLedgerSheet(
|
const generalLedgerInstance = new GeneralLedgerSheet(
|
||||||
tenantId,
|
tenantId,
|
||||||
filter,
|
filter,
|
||||||
accounts,
|
genealLedgerRepository,
|
||||||
contactsByIdMap,
|
|
||||||
transactionsJournal,
|
|
||||||
openingTransJournal,
|
|
||||||
tenant.metadata.baseCurrency,
|
|
||||||
i18n
|
i18n
|
||||||
);
|
);
|
||||||
// Retrieve general ledger report data.
|
// Retrieve general ledger report data.
|
||||||
|
|||||||
Reference in New Issue
Block a user