Skip to content

Commit

Permalink
fix: Trial balance sheet adjusted balance (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
abouolia authored Oct 25, 2023
1 parent 0179086 commit 2c5537e
Show file tree
Hide file tree
Showing 16 changed files with 509 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
/**
* Router constructor.
*/
router() {
public router() {
const router = Router();

router.get(
Expand All @@ -36,7 +36,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
* Validation schema.
* @return {ValidationChain[]}
*/
get trialBalanceSheetValidationSchema(): ValidationChain[] {
private get trialBalanceSheetValidationSchema(): ValidationChain[] {
return [
...this.sheetNumberFormatValidationSchema,
query('basis').optional(),
Expand All @@ -59,28 +59,37 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont
/**
* Retrieve the trial balance sheet.
*/
public async trialBalanceSheet(
private async trialBalanceSheet(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId, settings } = req;
const { tenantId } = req;
let filter = this.matchedQueryData(req);

filter = {
...filter,
accountsIds: castArray(filter.accountsIds),
};

try {
const { data, query, meta } =
await this.trialBalanceSheetService.trialBalanceSheet(tenantId, filter);
const accept = this.accepts(req);
const acceptType = accept.types(['json', 'application/json+table']);

return res.status(200).send({
data: this.transfromToResponse(data),
query: this.transfromToResponse(query),
meta: this.transfromToResponse(meta),
});
if (acceptType === 'application/json+table') {
const { table, meta, query } =
await this.trialBalanceSheetService.trialBalanceSheetTable(
tenantId,
filter
);
return res.status(200).send({ table, meta, query });
} else {
const { data, query, meta } =
await this.trialBalanceSheetService.trialBalanceSheet(
tenantId,
filter
);
return res.status(200).send({ data, query, meta });
}
} catch (error) {
next(error);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/server/src/interfaces/Ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface ILedger {

getClosingBalance(): number;
getForeignClosingBalance(): number;
getClosingDebit(): number;
getClosingCredit(): number;

getContactsIds(): number[];
getAccountsIds(): number[];
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/interfaces/TrialBalanceSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface ITrialBalanceAccount extends ITrialBalanceTotal {
id: number;
parentAccountId: number;
name: string;
formattedName: string;
code: string;
accountNormal: string;
}
Expand Down
27 changes: 26 additions & 1 deletion packages/server/src/services/Accounting/Ledger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import moment from 'moment';
import { defaultTo, uniqBy } from 'lodash';
import { defaultTo, sumBy, uniqBy } from 'lodash';
import { IAccountTransaction, ILedger, ILedgerEntry } from '@/interfaces';

export default class Ledger implements ILedger {
Expand Down Expand Up @@ -49,6 +49,15 @@ export default class Ledger implements ILedger {
return this.filter((entry) => entry.accountId === accountId);
}

/**
* Filters entries by the given accounts ids then returns a new ledger.
* @param {number[]} accountsIds - Accounts ids.
* @returns {ILedger}
*/
public whereAccountsIds(accountsIds: number[]): ILedger {
return this.filter((entry) => accountsIds.indexOf(entry.accountId) !== -1);
}

/**
* Filters entries that before or same the given date and returns a new ledger.
* @param {Date|string} fromDate
Expand Down Expand Up @@ -130,6 +139,22 @@ export default class Ledger implements ILedger {
return closingBalance;
}

/**
* Retrieves the closing credit of the entries.
* @returns {number}
*/
public getClosingCredit(): number {
return sumBy(this.entries, 'credit');
}

/**
* Retrieves the closing debit of the entries.
* @returns {number}
*/
public getClosingDebit(): number {
return sumBy(this.entries, 'debit');
}

/**
* Retrieve the closing balance of the entries.
* @returns {number}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,26 @@ import {
} from '@/interfaces';
import FinancialSheet from '../FinancialSheet';
import { allPassedConditionsPass, flatToNestedArray } from 'utils';
import { TrialBalanceSheetRepository } from './TrialBalanceSheetRepository';

export default class TrialBalanceSheet extends FinancialSheet {
tenantId: number;
query: ITrialBalanceSheetQuery;
accounts: IAccount & { type: IAccountType }[];
journalFinancial: any;
baseCurrency: string;
/**
* Trial balance sheet query.
* @param {ITrialBalanceSheetQuery} query
*/
private query: ITrialBalanceSheetQuery;

/**
* Trial balance sheet repository.
* @param {TrialBalanceSheetRepository}
*/
private repository: TrialBalanceSheetRepository;

/**
* Organization base currency.
* @param {string}
*/
private baseCurrency: string;

/**
* Constructor method.
Expand All @@ -28,20 +41,58 @@ export default class TrialBalanceSheet extends FinancialSheet {
constructor(
tenantId: number,
query: ITrialBalanceSheetQuery,
accounts: IAccount & { type: IAccountType }[],
journalFinancial: any,
repository: TrialBalanceSheetRepository,
baseCurrency: string
) {
super();

this.tenantId = tenantId;
this.query = query;
this.accounts = accounts;
this.journalFinancial = journalFinancial;
this.repository = repository;
this.numberFormat = this.query.numberFormat;
this.baseCurrency = baseCurrency;
}

/**
* Retrieves the closing credit of the given account.
* @param {number} accountId
* @returns {number}
*/
public getClosingAccountCredit(accountId: number) {
const depsAccountsIds =
this.repository.accountsDepGraph.dependenciesOf(accountId);

return this.repository.totalAccountsLedger
.whereAccountsIds([accountId, ...depsAccountsIds])
.getClosingCredit();
}

/**
* Retrieves the closing debit of the given account.
* @param {number} accountId
* @returns {number}
*/
public getClosingAccountDebit(accountId: number) {
const depsAccountsIds =
this.repository.accountsDepGraph.dependenciesOf(accountId);

return this.repository.totalAccountsLedger
.whereAccountsIds([accountId, ...depsAccountsIds])
.getClosingDebit();
}

/**
* Retrieves the closing total of the given account.
* @param {number} accountId
* @returns {number}
*/
public getClosingAccountTotal(accountId: number) {
const credit = this.getClosingAccountCredit(accountId);
const debit = this.getClosingAccountDebit(accountId);

return debit - credit;
}

/**
* Account mapper.
* @param {IAccount} account
Expand All @@ -50,23 +101,28 @@ export default class TrialBalanceSheet extends FinancialSheet {
private accountTransformer = (
account: IAccount & { type: IAccountType }
): ITrialBalanceAccount => {
const trial = this.journalFinancial.getTrialBalanceWithDepands(account.id);
const debit = this.getClosingAccountDebit(account.id);
const credit = this.getClosingAccountCredit(account.id);
const balance = this.getClosingAccountTotal(account.id);

return {
id: account.id,
parentAccountId: account.parentAccountId,
name: account.name,
formattedName: account.code
? `${account.name} - ${account.code}`
: `${account.name}`,
code: account.code,
accountNormal: account.accountNormal,

credit: trial.credit,
debit: trial.debit,
balance: trial.balance,
credit,
debit,
balance,
currencyCode: this.baseCurrency,

formattedCredit: this.formatNumber(trial.credit),
formattedDebit: this.formatNumber(trial.debit),
formattedBalance: this.formatNumber(trial.balance),
formattedCredit: this.formatNumber(credit),
formattedDebit: this.formatNumber(debit),
formattedBalance: this.formatNumber(balance),
};
};

Expand Down Expand Up @@ -117,10 +173,7 @@ export default class TrialBalanceSheet extends FinancialSheet {
private filterNoneTransactions = (
accountNode: ITrialBalanceAccount
): boolean => {
const entries = this.journalFinancial.getAccountEntriesWithDepents(
accountNode.id
);
return entries.length > 0;
return false === this.repository.totalAccountsLedger.isEmpty();
};

/**
Expand Down Expand Up @@ -200,11 +253,11 @@ export default class TrialBalanceSheet extends FinancialSheet {
*/
public reportData(): ITrialBalanceSheetData {
// Don't return noting if the journal has no transactions.
if (this.journalFinancial.isEmpty()) {
if (this.repository.totalAccountsLedger.isEmpty()) {
return null;
}
// Retrieve accounts nodes.
const accounts = this.accountsSection(this.accounts);
const accounts = this.accountsSection(this.repository.accounts);

// Retrieve account node.
const total = this.tatalSection(accounts);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ITrialBalanceSheetQuery } from '@/interfaces';
import Ledger from '@/services/Accounting/Ledger';
import { Knex } from 'knex';
import { isEmpty } from 'lodash';
import { Service } from 'typedi';

@Service()
export class TrialBalanceSheetRepository {
private query: ITrialBalanceSheetQuery;
private models: any;
public accounts: any;
public accountsDepGraph;

/**
* Total closing accounts ledger.
* @param {Ledger}
*/
public totalAccountsLedger: Ledger;

/**
* Constructor method.
* @param {number} tenantId
* @param {IBalanceSheetQuery} query
*/
constructor(models: any, repos: any, query: ITrialBalanceSheetQuery) {
this.query = query;
this.repos = repos;
this.models = models;
}

/**
* Async initialize.
* @returns {Promise<void>}
*/
public asyncInitialize = async () => {
await this.initAccounts();
await this.initAccountsClosingTotalLedger();
};

// ----------------------------
// # Accounts
// ----------------------------
/**
* Initialize accounts.
* @returns {Promise<void>}
*/
public initAccounts = async () => {
const accounts = await this.getAccounts();
const accountsDepGraph =
await this.repos.accountRepository.getDependencyGraph();

this.accountsDepGraph = accountsDepGraph;
this.accounts = accounts;
};

/**
* Initialize all accounts closing total ledger.
* @return {Promise<void>}
*/
public initAccountsClosingTotalLedger = async (): Promise<void> => {
const totalByAccounts = await this.closingAccountsTotal(this.query.toDate);

this.totalAccountsLedger = Ledger.fromTransactions(totalByAccounts);
};

/**
* Retrieve accounts of the report.
* @return {Promise<IAccount[]>}
*/
private getAccounts = () => {
const { Account } = this.models;

return Account.query();
};

/**
* Retrieve the opening balance transactions of the report.
* @param {Date|string} openingDate -
*/
public closingAccountsTotal = async (openingDate: Date | string) => {
const { AccountTransaction } = this.models;

return AccountTransaction.query().onBuild((query) => {
query.sum('credit as credit');
query.sum('debit as debit');
query.groupBy('accountId');
query.select(['accountId']);

query.modify('filterDateRange', null, openingDate);
query.withGraphFetched('account');

this.commonFilterBranchesQuery(query);
});
};

/**
* Common branches filter query.
* @param {Knex.QueryBuilder} query
*/
private commonFilterBranchesQuery = (query: Knex.QueryBuilder) => {
if (!isEmpty(this.query.branchesIds)) {
query.modify('filterByBranches', this.query.branchesIds);
}
};
}
Loading

0 comments on commit 2c5537e

Please sign in to comment.