Skip to content

Commit

Permalink
perf(general_leger): optimize GL Matrix
Browse files Browse the repository at this point in the history
This commit optimizes the General Ledger matrix query by only doing a
single SELECT with a GROUP BY.  It also removes the need for
pre-querying period ids and uses the raw fiscal year id instead.  This
is the first step to further optimizations of calculating the balances
of title accounts by summing down the account tree.
  • Loading branch information
jniles committed Jan 26, 2018
1 parent 800db66 commit 542db22
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 52 deletions.
1 change: 1 addition & 0 deletions client/src/i18n/en/form.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"GROUPS_DEBTOR_UPDATE": "Update Group Debtor",
"GROUPS_PATIENT_UPDATE": "Update Group Assignments",
"IMPORT": "Import",
"IMPORT_PREVIOUS_CLOSING_BALANCE": "Import Previous Closing Balance",
"INSTALL": "Install",
"LINK_SUBMISSION": "Link to Submission",
"LOGOUT": "Logout",
Expand Down
1 change: 1 addition & 0 deletions client/src/i18n/fr/form.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"GROUPS_PATIENT_UPDATE": "Mettre à jour les affectations de groupe de ce patient",
"GROUPS_DEBTOR_UPDATE": "Mettre à jour le groupe de débiteurs de ce patient",
"IMPORT": "Importer",
"IMPORT_PREVIOUS_CLOSING_BALANCE": "Importer le Solde de Cloture Precedant",
"INSTALL": "Installer",
"LOGOUT": "Déconnexion",
"LINK_SUBMISSION": "Lien vers l'envoie d'un formulaire",
Expand Down
3 changes: 1 addition & 2 deletions client/src/modules/accounts/accounts.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ function AccountService(Api, bhConstants) {
* @returns {Array} - the properly ordered list of account objects
*/
function order(accounts) {
// NOTE
// we assume the root node is 0
// NOTE: we assume the root node is 0
var ROOT_NODE = 0;

// build the account tree
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ function GeneralLedgerAccountsController(
// make sure the title accounts are identified
accounts.forEach(labelTitleAccounts);

// TODO - finish this
GeneralLedger.computeAccountBalances(accounts);

vm.gridOptions.data = accounts;
}

Expand Down
32 changes: 31 additions & 1 deletion client/src/modules/general-ledger/general-ledger.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ angular.module('bhima.services')

GeneralLedgerService.$inject = [
'PrototypeApiService', '$httpParamSerializer', 'LanguageService',
'AccountService',
];

/**
* General Ledger Service
* This service is responsible of all process with the General ledger
*/
function GeneralLedgerService(Api, $httpParamSerializer, Languages) {
function GeneralLedgerService(Api, $httpParamSerializer, Languages, Accounts) {
var service = new Api('/general_ledger/');

service.accounts = new Api('/general_ledger/accounts');

service.download = download;
service.slip = slip;
service.computeAccountBalances = computeAccountBalances;

function download(type, filters) {
var filterOpts = filters;
Expand Down Expand Up @@ -43,5 +45,33 @@ function GeneralLedgerService(Api, $httpParamSerializer, Languages) {
return $httpParamSerializer(options);
}

// for simplicity, I'm using "column" here instead of doing the entire row at
// once. It's less efficient.
function computeChildrenBalances(account, column) {
// base case: no more children.
if (!account.children || !account.children.length) {
return account[column];
}

// recursively compute the child balances
return account.children.reduce(function (debut, child) {
return debut + computeChildrenBalances(child, column);
}, 0);
}

function computeAccountBalances(accounts) {
// first, we need to make sure that accounts are in a tree structure
var tree = Accounts.order(accounts);

// now we need to recursively compute the balances of the
tree.forEach(function (account) {
var balance = computeChildrenBalances(account, 'balance');
console.log('Account: ', account.number, ' has balance ', balance);
console.log('With children:', account.children);
});

return tree;
}

return service;
}
69 changes: 31 additions & 38 deletions server/controllers/finance/generalLedger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,16 @@ function list(req, res, next) {
}

/**
* @function listAccounts
*
* @description
* List accounts and their balances.
* GET /general_ledger/accounts
* list accounts and their solds
*/
function listAccounts(req, res, next) {
const fiscalYearId = req.query.fiscal_year_id;

Fiscal.getPeriodByFiscal(fiscalYearId)
.then((rows) => {
return getAccountTotalsMatrix(rows);
})
getAccountTotalsMatrix(fiscalYearId)
.then((rows) => {
res.status(200).json(rows);
})
Expand All @@ -119,38 +119,31 @@ function listAccounts(req, res, next) {
* @function getAccountTotalsMatrix
*
* @description
* This function gets the period totals for all general ledger accounts from
* the period totals table.
* This function gets the period totals for all general ledger accounts from the
* period totals table.
*/
function getAccountTotalsMatrix(periodsId) {
let sqlCase = '';
let getBalance = '';
let headSql = '';
let signPlus = '';

if (periodsId) {
periodsId.forEach((period) => {
headSql += `, balance${period.number}`;

signPlus = period.number === 0 ? '' : '+';
getBalance += `${signPlus} balance${period.number} `;

sqlCase += `, SUM(
IF(period_total.period_id = ${period.id}, period_total.debit - period_total.credit, 0)
) AS balance${period.number}
`;
});
}

const sql =
`SELECT account.number, account.type_id, account.label, p.account_id AS id,
(${getBalance}) AS balance ${headSql}
FROM (
SELECT period_total.account_id ${sqlCase}
FROM period_total GROUP BY period_total.account_id
) AS p
JOIN account ON account.id = p.account_id
ORDER BY account.number ASC`;

return db.exec(sql);
function getAccountTotalsMatrix(fiscalYearId) {
const periodNumbers = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
];

// this creates a series of columns that only sum values if they are in the
// correct periodNumber.
const periodColumns = periodNumbers.reduce(
(q, number) => `${q}, SUM(IF(p.number = ${number}, pt.debit - pt.credit, 0)) AS balance${number}`,
''
);

const sql = `
SELECT a.id, a.number, a.label, a.type_id, a.label, a.parent,
SUM(pt.debit - pt.credit) AS balance ${periodColumns}
FROM account AS a
JOIN period_total AS pt ON a.id = pt.account_id
JOIN period AS p ON p.id = pt.period_id
WHERE pt.fiscal_year_id = ?
GROUP BY a.id
ORDER BY a.number;
`;

return db.exec(sql, [fiscalYearId]);
}
6 changes: 1 addition & 5 deletions server/controllers/finance/reports/generalLedger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const Accounts = require('../../accounts');

const REPORT_TEMPLATE = './server/controllers/finance/reports/generalLedger/report.handlebars';
const ACCOUNT_SLIP_TEMPLATE = './server/controllers/finance/reports/generalLedger/accountSlip.handlebars';
const Fiscal = require('../../fiscal');

const GENERAL_LEDGER_SOURCE = 1;

Expand Down Expand Up @@ -47,10 +46,7 @@ function renderReport(req, res, next) {

const fiscalYearId = options.fiscal_year_id;

return Fiscal.getPeriodByFiscal(fiscalYearId)
.then(rows => {
return GeneralLedger.getAccountTotalsMatrix(rows);
})
return GeneralLedger.getAccountTotalsMatrix(fiscalYearId)
.then((rows) => {
data = { rows };
data.fiscal_year_label = options.fiscal_year_label;
Expand Down
9 changes: 3 additions & 6 deletions server/controllers/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,11 @@ function getChildren(units, parentId) {
// Return null
if (units.length === 0) { return null; }

// Returns all units where the parent is the
// parentId
const children = units.filter(unit => {
return unit.parent === parentId;
});
// Returns all units where the parent is the parentId
const children = units.filter(unit => unit.parent === parentId);

// Recursively call getChildren on all child units
// and attach them as childen of their parent unit
// and attach them as children of their parent unit
children.forEach(unit => {
unit.children = getChildren(units, unit.id);
});
Expand Down

0 comments on commit 542db22

Please sign in to comment.