From 91165513ded9832684723dbee77f3ab90377156a Mon Sep 17 00:00:00 2001 From: Jonathan Niles Date: Thu, 11 May 2017 12:55:26 +0100 Subject: [PATCH] feat(account): report opening balances 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 #1611. Closes #1636. --- client/src/i18n/en/report.json | 83 ++++++------ client/src/i18n/fr/report.json | 84 ++++++------ server/controllers/finance/accounts/extra.js | 127 ++++++++++++++++++ .../reports/cashflow/report.handlebars | 4 +- .../finance/reports/reportAccounts/index.js | 24 +++- .../reports/reportAccounts/report.handlebars | 16 ++- utilities/translation/tfcomp.js | 2 + 7 files changed, 250 insertions(+), 90 deletions(-) create mode 100644 server/controllers/finance/accounts/extra.js diff --git a/client/src/i18n/en/report.json b/client/src/i18n/en/report.json index 6e961bb7ff..0c69abebfd 100644 --- a/client/src/i18n/en/report.json +++ b/client/src/i18n/en/report.json @@ -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"}} \ No newline at end of file +{ + "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" + } +} diff --git a/client/src/i18n/fr/report.json b/client/src/i18n/fr/report.json index c3be842c15..f153111df1 100644 --- a/client/src/i18n/fr/report.json +++ b/client/src/i18n/fr/report.json @@ -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"}} \ No newline at end of file +{ + "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" + } +} diff --git a/server/controllers/finance/accounts/extra.js b/server/controllers/finance/accounts/extra.js new file mode 100644 index 0000000000..afd1da94e6 --- /dev/null +++ b/server/controllers/finance/accounts/extra.js @@ -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; diff --git a/server/controllers/finance/reports/cashflow/report.handlebars b/server/controllers/finance/reports/cashflow/report.handlebars index 0b92693d25..2d840ec564 100644 --- a/server/controllers/finance/reports/cashflow/report.handlebars +++ b/server/controllers/finance/reports/cashflow/report.handlebars @@ -62,7 +62,7 @@ - {{translate 'REPORT.OPENNING_BALANCE'}} + {{translate 'REPORT.OPENING_BALANCE'}} {{#each periodStartArray as |period $index|}} {{#if (look ../periodicOpenningBalance period)}} @@ -112,7 +112,7 @@ - {{translate 'REPORT.OPENNING_BALANCE'}} + {{translate 'REPORT.OPENING_BALANCE'}} {{#each periodStartArray as |period $index|}} diff --git a/server/controllers/finance/reports/reportAccounts/index.js b/server/controllers/finance/reports/reportAccounts/index.js index 8cf309154f..ce36d4ba8e 100644 --- a/server/controllers/finance/reports/reportAccounts/index.js +++ b/server/controllers/finance/reports/reportAccounts/index.js @@ -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'); @@ -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 }); @@ -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; @@ -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 `; @@ -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; }); diff --git a/server/controllers/finance/reports/reportAccounts/report.handlebars b/server/controllers/finance/reports/reportAccounts/report.handlebars index 5f9203bfec..570bddd69e 100644 --- a/server/controllers/finance/reports/reportAccounts/report.handlebars +++ b/server/controllers/finance/reports/reportAccounts/report.handlebars @@ -14,7 +14,7 @@ {{/if}}
- +
@@ -27,6 +27,20 @@ + + {{! Opening Balance Line }} + + + + + + + + {{#each transactions}} diff --git a/utilities/translation/tfcomp.js b/utilities/translation/tfcomp.js index 94bc7a65c2..5defcaecd7 100644 --- a/utilities/translation/tfcomp.js +++ b/utilities/translation/tfcomp.js @@ -31,6 +31,8 @@ let frFileMissList = []; const jsonFiles = buildJsonFileArray(); +const notSwapFile = (fname) => !fname.includes('.swp'); + jsonFiles.forEach(function (jsonFile) { // Arrays to save differences in
{{translate "TABLE.COLUMNS.DATE" }}
{{ date openingBalance.date }}{{translate "REPORT.OPENING_BALANCE"}} + {{#unless openingBalance.isCreditBalance}} {{currency openingBalance.amount metadata.enterprise.currency_id}} {{/unless}} + + {{#if openingBalance.isCreditBalance}} {{currency openingBalance.amount metadata.enterprise.currency_id}} {{/if}} + {{currency openingBalance.amount metadata.enterprise.currency_id}}
{{date this.trans_date}}