Skip to content

Commit

Permalink
feat(account): report opening balances
Browse files Browse the repository at this point in the history
This commit builds a new feature for accounts that can discern their opening balance (from the
General Ledger) given a date and account id.  The major changes have been added in an "AccountExtra"
file that should be merged into accounts/index.js as soon as the major refactoring is finished.

This PR consists of two parts: the updated account report that now
contains an opening balance and the utility to find the opening balance
give a particular date and account_id.

Closes Third-Culture-Software#1611.  Closes Third-Culture-Software#1636.
  • Loading branch information
jniles committed May 19, 2017
1 parent 6bdcb00 commit 9116551
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 90 deletions.
83 changes: 41 additions & 42 deletions client/src/i18n/en/report.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,41 @@
{"REPORT":{"ACCOUNT":"Report of Account",
"BALANCE":"Balance Report",
"DOWNLOAD":"Download",
"DELETE":"Delete Report",
"AGED_DEBTORS":{"TITLE":"Aged Debtors"},
"ZERO_TO_THIRTY_DAYS":"Less Than 30 Days",
"THIRTY_TO_SIXTY_DAYS":"30 to 60 Days",
"SIXTY_TO_NINETY_DAYS":"60 to 90 Days",
"OVER_NINETY_DAYS":"Over 90 Days",
"AGED_CREDITORS":"Aged Debts",
"OPEN_DEBTORS":{"TITLE":"Debtors with Unpaid Debts",
"TREE":"Open Debtors"},
"CASH_EXPENSE":"Expenses",
"CASH_INCOME":"Incomes",
"CASHFLOW":"Cashflow",
"CASHFLOW_BY_SERVICE":"Cashflow By Service",
"CHART_OF_ACCOUNTS":"Chart of Accounts",
"CLIENTS_REPORT":"Clients Report",
"CLOSING_BALANCE":"Closing Balance",
"CONFIGURATION":"Report Configuration",
"GENERATED":"Generated Report",
"MONTHLY_BALANCE":"Monthly Balance",
"OPENNING_BALANCE":"Opening Balance",
"PERIOD_START":"Start",
"PERIOD_STOP":"Stop",
"PRODUCED_BY":"Produced by",
"PRODUCED_DATE":"Production Date",
"PRODUCED_ON":"Produced on",
"REPORT_ACCOUNTS":"Report accounts",
"INCOME_REPORT":"Incomes Report",
"EXPENSE_REPORT":"Expenses Report",
"INCOME_EXPENSE": "Income Expense Report",
"VIEW_CREDIT_NOTE": "View Credit Note",
"VIEW_INVOICE":"View Invoices",
"VIEW_PATIENT":"View Patient",
"SINCE":"Since",
"VIEW_RECEIPT":"View Receipt",
"VIEW_CREDIT_NOTE":"View Credit Note",
"CLIENTS_REPORT":"Clients Report",
"VIEW_PAYMENTS":"View Cash Payments",
"VIEW_RECEIPT":"View Receipt",
"INCOME_EXPENSE":"Income Expense Report"}}
{
"REPORT":{
"ACCOUNT":"Report of Account",
"AGED_CREDITORS":"Aged Debts",
"AGED_DEBTORS":{"TITLE":"Aged Debtors"},
"BALANCE":"Balance Report",
"CASH_EXPENSE":"Expenses",
"CASHFLOW_BY_SERVICE":"Cashflow By Service",
"CASHFLOW":"Cashflow",
"CASH_INCOME":"Incomes",
"CHART_OF_ACCOUNTS":"Chart of Accounts",
"CLIENTS_REPORT":{"TITLE":"Clients Report", "CURRENT_MVT":"Current Movement", "PREVIOUS_MVT":"Previous Movement"},
"CLOSING_BALANCE":"Closing Balance",
"CONFIGURATION":"Report Configuration",
"DELETE":"Delete Report",
"DOWNLOAD":"Download",
"EXPENSE_REPORT":"Expenses Report",
"GENERATED":"Generated Report",
"INCOME_EXPENSE": "Income Expense Report",
"INCOME_REPORT":"Incomes Report",
"MONTHLY_BALANCE":"Monthly Balance",
"OPEN_DEBTORS":{ "TITLE":"Debtors with Unpaid Debts", "TREE":"Open Debtors" },
"OPENING_BALANCE":"Opening Balance",
"OVER_NINETY_DAYS":"Over 90 Days",
"PERIOD_START":"Start",
"PERIOD_STOP":"Stop",
"PRODUCED_BY":"Produced by",
"PRODUCED_DATE":"Production Date",
"PRODUCED_ON":"Produced on",
"REPORT_ACCOUNTS":"Report accounts",
"SINCE":"Since",
"SIXTY_TO_NINETY_DAYS":"60 to 90 Days",
"THIRTY_TO_SIXTY_DAYS":"30 to 60 Days",
"VIEW_CREDIT_NOTE":"View Credit Note",
"VIEW_INVOICE":"View Invoices",
"VIEW_PATIENT":"View Patient",
"VIEW_PAYMENTS":"View Cash Payments",
"VIEW_RECEIPT":"View Receipt",
"ZERO_TO_THIRTY_DAYS":"Less Than 30 Days"
}
}
84 changes: 42 additions & 42 deletions client/src/i18n/fr/report.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
{"REPORT":{"ACCOUNT":"Rapport du Compte",
"BALANCE":"Rapport de la Balance",
"AGED_DEBTORS":{"TITLE":"Balance agée des clients"},
"ZERO_TO_THIRTY_DAYS":"Moins de 30 jours",
"THIRTY_TO_SIXTY_DAYS":"30 a 60 jours",
"SIXTY_TO_NINETY_DAYS":"60 a 90 jours",
"OVER_NINETY_DAYS":"Plus de 90 jours",
"AGED_CREDITORS":"Balance agée des Créances",
"OPEN_DEBTORS":{"TITLE":"Debiteurs Avec Dettes",
"TREE":"Debiteurs Avec Dettes"},
"CASH_EXPENSE":"Depenses",
"CASH_INCOME":"Recettes",
"CASHFLOW":"Cashflow",
"CASHFLOW_BY_SERVICE":"Cashflow par Service",
"CHART_OF_ACCOUNTS":"Plan Comptable",
"CLIENTS_REPORT":{"TITLE":"Rapport Clients",
"CURRENT_MVT":"Mouvement Courant",
"PREVIOUS_MVT":"Mouvement Precedent"},
"CLOSING_BALANCE":"Balance à la cloture",
"CONFIGURATION":"Configuration rapport",
"DELETE":"Supprimer un rapport",
"DOWNLOAD":"Télécharger",
"GENERATED":"Rapport généré",
"INCOME_EXPENSE":"Rapport des recettes et des dépenses",
"MONTHLY_BALANCE":"Balance mensuelle",
"OPENNING_BALANCE":"Balance d'ouverture",
"PERIOD_START":"Debut",
"PERIOD_STOP":"Fin",
"PRODUCED_BY":"Produit par",
"PRODUCED_DATE":"Date de generation",
"PRODUCED_ON":"Produit le",
"REPORT_ACCOUNTS":"Rapport de comptes",
"INCOME_REPORT":"Rapport des recettes",
"EXPENSE_REPORT":"Rapport des dépenses",
"SINCE":"Depuis",
"VIEW_CREDIT_NOTE":"Voir la note de credit",
"VIEW_INVOICE":"Voir les factures",
"VIEW_PATIENT":"Voir le Patient",
"VIEW_RECEIPT":"Voir le document",
"VIEW_PAYMENTS":"Voir les payments",
"VIEW_RECEIPT":"Voir le document",
"VIEW_CREDIT_NOTE":"Voir la note de credit"}}
{
"REPORT":{
"ACCOUNT":"Rapport du Compte",
"AGED_CREDITORS":"Balance agée des Créances",
"AGED_DEBTORS":{"TITLE":"Balance agée des clients"},
"BALANCE":"Rapport de la Balance",
"CASH_EXPENSE":"Depenses",
"CASHFLOW_BY_SERVICE":"Cashflow par Service",
"CASHFLOW":"Cashflow",
"CASH_INCOME":"Recettes",
"CHART_OF_ACCOUNTS":"Plan Comptable",
"CLIENTS_REPORT":{"TITLE":"Rapport Clients", "CURRENT_MVT":"Mouvement Courant", "PREVIOUS_MVT":"Mouvement Precedent"},
"CLOSING_BALANCE":"Balance à la cloture",
"CONFIGURATION":"Configuration rapport",
"DELETE":"Supprimer un rapport",
"DOWNLOAD":"Télécharger",
"EXPENSE_REPORT":"Rapport des dépenses",
"GENERATED":"Rapport généré",
"INCOME_EXPENSE":"Rapport des recettes et des dépenses",
"INCOME_REPORT":"Rapport des recettes",
"MONTHLY_BALANCE":"Balance mensuelle",
"OPEN_DEBTORS":{"TITLE":"Debiteurs Avec Dettes", "TREE":"Debiteurs Avec Dettes"},
"OPENING_BALANCE":"Balance d'ouverture",
"OVER_NINETY_DAYS":"Plus de 90 jours",
"PERIOD_START":"Debut",
"PERIOD_STOP":"Fin",
"PRODUCED_BY":"Produit par",
"PRODUCED_DATE":"Date de generation",
"PRODUCED_ON":"Produit le",
"REPORT_ACCOUNTS":"Rapport de comptes",
"SINCE":"Depuis",
"SIXTY_TO_NINETY_DAYS":"60 a 90 jours",
"THIRTY_TO_SIXTY_DAYS":"30 a 60 jours",
"VIEW_CREDIT_NOTE":"Voir la note de credit",
"VIEW_INVOICE":"Voir les factures",
"VIEW_PATIENT":"Voir le Patient",
"VIEW_PAYMENTS":"Voir les payments",
"VIEW_RECEIPT":"Voir le document",
"VIEW_RECEIPT":"Voir le document",
"ZERO_TO_THIRTY_DAYS":"Moins de 30 jours"
}
}
127 changes: 127 additions & 0 deletions server/controllers/finance/accounts/extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* @overview AccountExtras
*
* @description
* Temporary file to avoid conflicts with changes in accounts/index.js. This will be merged into accounts/index.js.
* It should only be used in the account report.
*/

const db = require('../../../lib/db');

/**
* @function getFiscalYearForDate
* @private
*
* @description
* Helper method to return the fiscal year associated with a provided date.
*
* @param {Date} date - the sought after JS date
* @returns Promise - promise wrapping an integer fiscalYearId
*/
function getFiscalYearForDate(date) {
const sql = `
SELECT id FROM fiscal_year WHERE start_date <= DATE(?) AND end_date >= DATE(?);
`;

return db.one(sql, [date, date])
.then(data => data.id);
}


/**
* @function getPeriodForDate
* @private
*
* @description
* Helper method to return the period id associated with a given date.
*
* @param {Date} date - the sought after date
* @returns Promise - promise wrapping an integer periodId
*/
function getPeriodForDate(date) {
const sql = `
SELECT id FROM period WHERE start_date <= DATE(?) AND end_date >= DATE(?);
`;

return db.one(sql, [date, date])
.then(data => data.id);
}

/**
* @function getPeriodAccountBalanceUntilDate
* @private
*
* @description
* Sums the balance of an account up to just before the given date's period. For example, if the date was provided as
* May 19, 2016, this function would return the account balance at the end of April 30, 2016. This is useful for
* computing opening balances of the period given a date.
*
* @param {Number} accountId - the account_id for the period_total table
* @param {Date} date - the upper limit of the period
* @param {Number} fiscalYearId - the fiscal_year_id for the period_total table
*
* @returns Promise - promise wrapping the balance object
*/
function getPeriodAccountBalanceUntilDate(accountId, date, fiscalYearId) {
const sql = `
SELECT SUM(debit - credit) AS balance
FROM period_total JOIN period ON period.id = period_total.period_id
WHERE period_total.account_id = ?
AND period.end_date <= DATE(?)
AND period.fiscal_year_id = ?;
`;

return db.one(sql, [accountId, date, fiscalYearId])
.then(data => data.balance);
}

/**
* @function getComputedAccountBalanceUntilDate
* @private
*
* @description
* Sums general ledger lines hitting an account during a period, up to (and including) the provided date.
*/
function getComputedAccountBalanceUntilDate(accountId, date, periodId) {
const sql = `
SELECT SUM(debit_equiv - credit_equiv) AS balance FROM general_ledger
WHERE account_id = ?
AND trans_date <= DATE(?)
AND period_id = ?;
`;

return db.one(sql, [accountId, date, periodId])
.then(data => data.balance);
}


/**
* @method getOpeningBalanceForDate
* @public
*
* @description
* Query the database for an account's balance as of a start date. Note that the date should be escaped (via new
* Date()) prior to calling this function - this method is expected to does not do any escaping.
*
* @param {Number} accountId - the identifier for the account
* @param {Date} date - the date that the opening balance should be computed through
* @returns {Promise} - promise wrapping the balance of the account
*/
function getOpeningBalanceForDate(accountId, date) {
let balance = 0;

return getFiscalYearForDate(date)
.then(fiscalYearId =>
getPeriodAccountBalanceUntilDate(accountId, date, fiscalYearId)
)
.then((previousPeriodClosingBalance) => {
balance += previousPeriodClosingBalance;
return getPeriodForDate(date);
})
.then(periodId =>
getComputedAccountBalanceUntilDate(accountId, date, periodId)
)
.then(runningPeriodBalance => balance + runningPeriodBalance);
}

exports.getOpeningBalanceForDate = getOpeningBalanceForDate;
4 changes: 2 additions & 2 deletions server/controllers/finance/reports/cashflow/report.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

<!-- openning balance -->
<tr>
<td class="text-left"><strong>{{translate 'REPORT.OPENNING_BALANCE'}}<strong></td>
<td class="text-left"><strong>{{translate 'REPORT.OPENING_BALANCE'}}<strong></td>
{{#each periodStartArray as |period $index|}}
<td class="text-right">
{{#if (look ../periodicOpenningBalance period)}}
Expand Down Expand Up @@ -112,7 +112,7 @@
<tbody>
<!-- cash on hand (openning) -->
<tr>
<td style="background-color: #ccc;" class="text-left text-uppercase"><strong>{{translate 'REPORT.OPENNING_BALANCE'}}<strong></td>
<td style="background-color: #ccc;" class="text-left text-uppercase"><strong>{{translate 'REPORT.OPENING_BALANCE'}}<strong></td>
{{#each periodStartArray as |period $index|}}
<td class="text-right" style="background-color: #ccc;">
<strong>
Expand Down
24 changes: 21 additions & 3 deletions server/controllers/finance/reports/reportAccounts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ const _ = require('lodash');
const db = require('../../../../lib/db');
const ReportManager = require('../../../../lib/ReportManager');

// TODO(@jniles) - merge this into the regular accounts controller
const AccountsExtra = require('../../accounts/extra');

const TEMPLATE = './server/controllers/finance/reports/reportAccounts/report.handlebars';
const BadRequest = require('../../../../lib/errors/BadRequest');

Expand Down Expand Up @@ -47,7 +50,19 @@ function document(req, res, next) {
return next(e);
}

return getAccountTransactions(params.account_id, params.sourceId, params.dateFrom, params.dateTo)
const dateFrom = (params.dateFrom) ? new Date(params.dateFrom) : new Date();

return AccountsExtra.getOpeningBalanceForDate(params.account_id, dateFrom)
.then((balance) => {
const openingBalance = {
date : dateFrom,
amount : balance,
isCreditBalance : balance < 0,
};

_.extend(bundle, { openingBalance });
return getAccountTransactions(params.account_id, params.sourceId, params.dateFrom, params.dateTo, balance);
})
.then((result) => {
_.extend(bundle, { transactions : result.transactions, sum : result.sum, title });

Expand All @@ -65,7 +80,7 @@ function document(req, res, next) {
* @function getAccountTransactions
* This feature select all transactions for a specific account
*/
function getAccountTransactions(accountId, source, dateFrom, dateTo) {
function getAccountTransactions(accountId, source, dateFrom, dateTo, openingBalance) {
const sourceId = parseInt(source, 10);
let tableName;

Expand Down Expand Up @@ -104,7 +119,7 @@ function getAccountTransactions(accountId, source, dateFrom, dateTo) {
WHERE account_id = ? ${dateCondition}
GROUP BY record_uuid
ORDER BY trans_date ASC
)c, (SELECT @cumsum := 0)z
)c, (SELECT @cumsum := ${openingBalance})z
) AS groups
`;

Expand All @@ -127,6 +142,9 @@ function getAccountTransactions(accountId, source, dateFrom, dateTo) {
return db.one(sqlAggrega, params);
})
.then((sum) => {
// if the sum come back as zero (because there were no lines), set the default sum to the
// opening balance
sum.balance = sum.balance || openingBalance;
_.extend(bundle, { sum });
return bundle;
});
Expand Down
Loading

0 comments on commit 9116551

Please sign in to comment.