mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
feat: general ledger sub-accounts
This commit is contained in:
@@ -56,6 +56,8 @@ export interface IGeneralLedgerSheetAccount {
|
||||
transactions: IGeneralLedgerSheetAccountTransaction[];
|
||||
openingBalance: IGeneralLedgerSheetAccountBalance;
|
||||
closingBalance: IGeneralLedgerSheetAccountBalance;
|
||||
closingBalanceSubaccounts: IGeneralLedgerSheetAccountBalance;
|
||||
children?: IGeneralLedgerSheetAccount[];
|
||||
}
|
||||
|
||||
export type IGeneralLedgerSheetData = IGeneralLedgerSheetAccount[];
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class Ledger implements ILedger {
|
||||
|
||||
/**
|
||||
* Filters entries by the given accounts ids then returns a new ledger.
|
||||
* @param {number[]} accountIds
|
||||
* @param {number[]} accountIds
|
||||
* @returns {ILedger}
|
||||
*/
|
||||
public whereAccountsIds(accountIds: number[]): ILedger {
|
||||
@@ -274,4 +274,14 @@ export default class Ledger implements ILedger {
|
||||
const entries = Ledger.mappingTransactions(transactions);
|
||||
return new Ledger(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the transaction amount.
|
||||
* @param {number} credit - Credit amount.
|
||||
* @param {number} debit - Debit amount.
|
||||
* @param {string} normal - Credit or debit.
|
||||
*/
|
||||
static getAmount(credit: number, debit: number, normal: string) {
|
||||
return normal === 'credit' ? credit - debit : debit - credit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import * as R from 'ramda';
|
||||
import { FinancialPreviousPeriod } from '../FinancialPreviousPeriod';
|
||||
import { FinancialHorizTotals } from '../FinancialHorizTotals';
|
||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||
import {
|
||||
BALANCE_SHEET_SCHEMA_NODE_TYPE,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isEmpty, get, last, sumBy } from 'lodash';
|
||||
import { isEmpty, get, last, sumBy, first, head } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IGeneralLedgerSheetQuery,
|
||||
IGeneralLedgerSheetAccount,
|
||||
@@ -10,11 +11,16 @@ import {
|
||||
} from '@/interfaces';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import { GeneralLedgerRepository } from './GeneralLedgerRepository';
|
||||
import { FinancialSheetStructure } from '../FinancialSheetStructure';
|
||||
import { flatToNestedArray } from '@/utils';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
|
||||
/**
|
||||
* General ledger sheet.
|
||||
*/
|
||||
export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
export default class GeneralLedgerSheet extends R.compose(
|
||||
FinancialSheetStructure
|
||||
)(FinancialSheet) {
|
||||
tenantId: number;
|
||||
query: IGeneralLedgerSheetQuery;
|
||||
baseCurrency: string;
|
||||
@@ -46,13 +52,14 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the transaction amount.
|
||||
* @param {number} credit - Credit amount.
|
||||
* @param {number} debit - Debit amount.
|
||||
* @param {string} normal - Credit or debit.
|
||||
* Calculate the running balance.
|
||||
* @param {number} amount - Transaction amount.
|
||||
* @param {number} lastRunningBalance - Last running balance.
|
||||
* @param {number} openingBalance - Opening balance.
|
||||
* @return {number} Running balance.
|
||||
*/
|
||||
getAmount(credit: number, debit: number, normal: string) {
|
||||
return normal === 'credit' ? credit - debit : debit - credit;
|
||||
calculateRunningBalance(amount: number, lastRunningBalance: number): number {
|
||||
return amount + lastRunningBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,26 +67,38 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
* @param {ILedgerEntry} entry -
|
||||
* @return {IGeneralLedgerSheetAccountTransaction}
|
||||
*/
|
||||
entryReducer(
|
||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||
private getEntryRunningBalance(
|
||||
entry: ILedgerEntry,
|
||||
openingBalance: number
|
||||
): IGeneralLedgerSheetAccountTransaction[] {
|
||||
const lastEntry = last(entries);
|
||||
openingBalance: number,
|
||||
runningBalance?: number
|
||||
): number {
|
||||
const lastRunningBalance = runningBalance || openingBalance;
|
||||
|
||||
const contact = this.repository.contactsById.get(entry.contactId);
|
||||
const amount = this.getAmount(
|
||||
const amount = Ledger.getAmount(
|
||||
entry.credit,
|
||||
entry.debit,
|
||||
entry.accountNormal
|
||||
);
|
||||
const runningBalance =
|
||||
amount + (!isEmpty(entries) ? lastEntry.runningBalance : openingBalance);
|
||||
return this.calculateRunningBalance(amount, lastRunningBalance);
|
||||
}
|
||||
|
||||
const newEntry = {
|
||||
/**
|
||||
*
|
||||
* @param entry
|
||||
* @param runningBalance
|
||||
* @returns
|
||||
*/
|
||||
private entryMapper(entry: ILedgerEntry, runningBalance: number) {
|
||||
const contact = this.repository.contactsById.get(entry.contactId);
|
||||
const amount = Ledger.getAmount(
|
||||
entry.credit,
|
||||
entry.debit,
|
||||
entry.accountNormal
|
||||
);
|
||||
return {
|
||||
id: entry.id,
|
||||
date: entry.date,
|
||||
dateFormatted: moment(entry.date).format('YYYY MMM DD'),
|
||||
entryId: entry.id,
|
||||
|
||||
transactionNumber: entry.transactionNumber,
|
||||
referenceType: entry.referenceType,
|
||||
@@ -104,10 +123,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
formattedRunningBalance: this.formatNumber(runningBalance),
|
||||
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
entries.push(newEntry);
|
||||
|
||||
return entries;
|
||||
} as IGeneralLedgerSheetAccountTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,28 +139,40 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
.whereAccountId(account.id)
|
||||
.getEntries();
|
||||
|
||||
return entries.reduce(
|
||||
(
|
||||
entries: IGeneralLedgerSheetAccountTransaction[],
|
||||
entry: ILedgerEntry
|
||||
) => {
|
||||
return this.entryReducer(entries, entry, openingBalance);
|
||||
},
|
||||
[]
|
||||
);
|
||||
return entries
|
||||
.reduce((prev: Array<[number, ILedgerEntry]>, current: ILedgerEntry) => {
|
||||
const amount = this.getEntryRunningBalance(
|
||||
current,
|
||||
openingBalance,
|
||||
head(last(prev)) as number
|
||||
);
|
||||
return new Array([amount, current]);
|
||||
}, [])
|
||||
.map(([runningBalance, entry]: [number, ILedgerEntry]) =>
|
||||
this.entryMapper(entry, runningBalance)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account opening balance.
|
||||
* Retrieves the given account opening balance.
|
||||
* @param {number} accountId
|
||||
* @returns {number}
|
||||
*/
|
||||
private accountOpeningBalance(accountId: number): number {
|
||||
return this.repository.openingBalanceTransactionsLedger
|
||||
.whereAccountId(accountId)
|
||||
.getClosingBalance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given account opening balance.
|
||||
* @param {IAccount} account
|
||||
* @return {IGeneralLedgerSheetAccountBalance}
|
||||
*/
|
||||
private accountOpeningBalance(
|
||||
account: IAccount
|
||||
private accountOpeningBalanceTotal(
|
||||
accountId: number
|
||||
): IGeneralLedgerSheetAccountBalance {
|
||||
const amount = this.repository.openingBalanceTransactionsLedger
|
||||
.whereAccountId(account.id)
|
||||
.getClosingBalance();
|
||||
const amount = this.accountOpeningBalance(accountId);
|
||||
const formattedAmount = this.formatTotalNumber(amount);
|
||||
const currencyCode = this.baseCurrency;
|
||||
const date = this.query.fromDate;
|
||||
@@ -153,15 +181,31 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account closing balance.
|
||||
* Retrieves the given account closing balance.
|
||||
* @param {number} accountId
|
||||
* @returns {number}
|
||||
*/
|
||||
private accountClosingBalance(accountId: number): number {
|
||||
const openingBalance = this.repository.openingBalanceTransactionsLedger
|
||||
.whereAccountId(accountId)
|
||||
.getClosingBalance();
|
||||
|
||||
const transactionsBalance = this.repository.transactionsLedger
|
||||
.whereAccountId(accountId)
|
||||
.getClosingBalance();
|
||||
|
||||
return openingBalance + transactionsBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given account closing balance.
|
||||
* @param {IAccount} account
|
||||
* @return {IGeneralLedgerSheetAccountBalance}
|
||||
*/
|
||||
private accountClosingBalance(
|
||||
openingBalance: number,
|
||||
transactions: IGeneralLedgerSheetAccountTransaction[]
|
||||
private accountClosingBalanceTotal(
|
||||
accountId: number
|
||||
): IGeneralLedgerSheetAccountBalance {
|
||||
const amount = this.calcClosingBalance(openingBalance, transactions);
|
||||
const amount = this.accountClosingBalance(accountId);
|
||||
const formattedAmount = this.formatTotalNumber(amount);
|
||||
const currencyCode = this.baseCurrency;
|
||||
const date = this.query.toDate;
|
||||
@@ -169,29 +213,63 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
return { amount, formattedAmount, currencyCode, date };
|
||||
}
|
||||
|
||||
private calcClosingBalance(
|
||||
openingBalance: number,
|
||||
transactions: IGeneralLedgerSheetAccountTransaction[]
|
||||
) {
|
||||
return openingBalance + sumBy(transactions, (trans) => trans.amount);
|
||||
}
|
||||
/**
|
||||
* Retrieves the given account closing balance with subaccounts.
|
||||
* @param {number} accountId
|
||||
* @returns {number}
|
||||
*/
|
||||
private accountClosingBalanceWithSubaccounts = (
|
||||
accountId: number
|
||||
): number => {
|
||||
const depsAccountsIds =
|
||||
this.repository.accountsGraph.dependenciesOf(accountId);
|
||||
|
||||
console.log([...depsAccountsIds, accountId]);
|
||||
|
||||
const openingBalance = this.repository.openingBalanceTransactionsLedger
|
||||
.whereAccountsIds([...depsAccountsIds, accountId])
|
||||
.getClosingBalance();
|
||||
|
||||
const transactionsBalanceWithSubAccounts =
|
||||
this.repository.transactionsLedger
|
||||
.whereAccountsIds([...depsAccountsIds, accountId])
|
||||
.getClosingBalance();
|
||||
|
||||
const closingBalance = openingBalance + transactionsBalanceWithSubAccounts;
|
||||
|
||||
return closingBalance;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} accountId
|
||||
* @returns {IGeneralLedgerSheetAccountBalance}
|
||||
*/
|
||||
private accountClosingBalanceWithSubaccountsTotal = (
|
||||
accountId: number
|
||||
): IGeneralLedgerSheetAccountBalance => {
|
||||
const amount = this.accountClosingBalanceWithSubaccounts(accountId);
|
||||
const formattedAmount = this.formatTotalNumber(amount);
|
||||
const currencyCode = this.baseCurrency;
|
||||
const date = this.query.toDate;
|
||||
|
||||
return { amount, formattedAmount, currencyCode, date };
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreive general ledger accounts sections.
|
||||
* @param {IAccount} account
|
||||
* @return {IGeneralLedgerSheetAccount}
|
||||
*/
|
||||
private accountMapper(account: IAccount): IGeneralLedgerSheetAccount {
|
||||
const openingBalance = this.accountOpeningBalance(account);
|
||||
|
||||
private accountMapper = (account: IAccount): IGeneralLedgerSheetAccount => {
|
||||
const openingBalance = this.accountOpeningBalanceTotal(account.id);
|
||||
const transactions = this.accountTransactionsMapper(
|
||||
account,
|
||||
openingBalance.amount
|
||||
);
|
||||
const closingBalance = this.accountClosingBalance(
|
||||
openingBalance.amount,
|
||||
transactions
|
||||
);
|
||||
const closingBalance = this.accountClosingBalanceTotal(account.id);
|
||||
const closingBalanceSubaccounts =
|
||||
this.accountClosingBalanceWithSubaccountsTotal(account.id);
|
||||
|
||||
return {
|
||||
id: account.id,
|
||||
@@ -202,32 +280,65 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
||||
openingBalance,
|
||||
transactions,
|
||||
closingBalance,
|
||||
closingBalanceSubaccounts,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve mapped accounts with general ledger transactions and opeing/closing balance.
|
||||
* Maps over deep nodes to retrieve the G/L account node.
|
||||
* @param {IAccount[]} accounts
|
||||
* @returns {IGeneralLedgerSheetAccount[]}
|
||||
*/
|
||||
private accountNodesDeepMap = (
|
||||
accounts: IAccount[]
|
||||
): IGeneralLedgerSheetAccount[] => {
|
||||
return this.mapNodesDeep(accounts, this.accountMapper);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the flatten nodes to nested nodes.
|
||||
*/
|
||||
private nestedAccountsNode = (flattenAccounts: IAccount[]): IAccount[] => {
|
||||
return flatToNestedArray(flattenAccounts, {
|
||||
id: 'id',
|
||||
parentId: 'parentAccountId',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters account nodes.
|
||||
* @param {IGeneralLedgerSheetAccount[]} nodes
|
||||
* @returns {IGeneralLedgerSheetAccount[]}
|
||||
*/
|
||||
private filterAccountNodes = (
|
||||
nodes: IGeneralLedgerSheetAccount[]
|
||||
): IGeneralLedgerSheetAccount[] => {
|
||||
return this.filterNodesDeep(
|
||||
nodes,
|
||||
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
|
||||
!(
|
||||
generalLedgerAccount.transactions.length === 0 &&
|
||||
this.query.noneTransactions
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves mapped accounts with general ledger transactions and
|
||||
* opeing/closing balance.
|
||||
* @param {IAccount[]} accounts -
|
||||
* @return {IGeneralLedgerSheetAccount[]}
|
||||
*/
|
||||
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
|
||||
return (
|
||||
accounts
|
||||
.map((account: IAccount) => this.accountMapper(account))
|
||||
// Filter general ledger accounts that have no transactions
|
||||
// when`noneTransactions` is on.
|
||||
.filter(
|
||||
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
|
||||
!(
|
||||
generalLedgerAccount.transactions.length === 0 &&
|
||||
this.query.noneTransactions
|
||||
)
|
||||
)
|
||||
);
|
||||
return R.compose(
|
||||
this.filterAccountNodes,
|
||||
this.accountNodesDeepMap,
|
||||
this.nestedAccountsNode
|
||||
)(accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve general ledger report data.
|
||||
* Retrieves general ledger report data.
|
||||
* @return {IGeneralLedgerSheetAccount[]}
|
||||
*/
|
||||
public reportData(): IGeneralLedgerSheetAccount[] {
|
||||
|
||||
@@ -113,6 +113,27 @@ export class GeneralLedgerTable extends R.compose(
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing balance row column accessors.
|
||||
* @returns {ITableColumnAccessor[]}
|
||||
*/
|
||||
private closingBalanceWithSubaccountsColumnAccessors(): IColumnMapperMeta[] {
|
||||
return [
|
||||
{ key: 'date', value: this.meta.toDate },
|
||||
{ key: 'account_name', value: 'Closing Balance with sub-accounts' },
|
||||
{ key: 'reference_type', accessor: '_empty_' },
|
||||
{ key: 'reference_number', accessor: '_empty_' },
|
||||
{ key: 'description', accessor: '_empty_' },
|
||||
{ key: 'credit', accessor: '_empty_' },
|
||||
{ key: 'debit', accessor: '_empty_' },
|
||||
{ key: 'amount', accessor: 'closingBalanceSubaccounts.formattedAmount' },
|
||||
{
|
||||
key: 'running_balance',
|
||||
accessor: 'closingBalanceSubaccounts.formattedAmount',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the common table columns.
|
||||
* @returns {ITableColumn[]}
|
||||
@@ -191,6 +212,21 @@ export class GeneralLedgerTable extends R.compose(
|
||||
return tableRowMapper(account, columns, meta);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the given account node to opening balance table row.
|
||||
* @param {IGeneralLedgerSheetAccount} account
|
||||
* @returns {ITableRow}
|
||||
*/
|
||||
private closingBalanceWithSubaccountsMapper = (
|
||||
account: IGeneralLedgerSheetAccount
|
||||
): ITableRow => {
|
||||
const columns = this.closingBalanceWithSubaccountsColumnAccessors();
|
||||
const meta = {
|
||||
rowTypes: [ROW_TYPE.CLOSING_BALANCE],
|
||||
};
|
||||
return tableRowMapper(account, columns, meta);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the given account node to transactions table rows.
|
||||
* @param {IGeneralLedgerSheetAccount} account
|
||||
@@ -221,8 +257,23 @@ export class GeneralLedgerTable extends R.compose(
|
||||
rowTypes: [ROW_TYPE.ACCOUNT],
|
||||
};
|
||||
const row = tableRowMapper(account, columns, meta);
|
||||
const closingBalanceWithSubaccounts =
|
||||
this.closingBalanceWithSubaccountsMapper(account);
|
||||
|
||||
return R.assoc('children', transactions)(row);
|
||||
const children = R.compose(
|
||||
// Appends the closing balance with sub-accounts row if the account has children accounts.
|
||||
R.when(
|
||||
() => account.children?.length > 0,
|
||||
R.append(closingBalanceWithSubaccounts)
|
||||
),
|
||||
R.concat(R.defaultTo([], transactions)),
|
||||
R.when(
|
||||
() => account?.children?.length > 0,
|
||||
R.concat(R.defaultTo([], account.children))
|
||||
)
|
||||
)([]);
|
||||
|
||||
return R.assoc('children', children)(row);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -233,7 +284,7 @@ export class GeneralLedgerTable extends R.compose(
|
||||
private accountsMapper = (
|
||||
accounts: IGeneralLedgerSheetAccount[]
|
||||
): ITableRow[] => {
|
||||
return this.mapNodesDeep(accounts, this.accountMapper);
|
||||
return this.mapNodesDeepReverse(accounts, this.accountMapper);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -250,7 +301,6 @@ export class GeneralLedgerTable extends R.compose(
|
||||
*/
|
||||
public tableColumns(): ITableColumn[] {
|
||||
const columns = this.commonColumns();
|
||||
|
||||
return R.compose(this.tableColumnsCellIndexing)(columns);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user