Skip to content

Commit

Permalink
feat(reports): Open Debtors Report
Browse files Browse the repository at this point in the history
This commit implements the server and client sides to the open debtors
report.  It provides a list of all debtors who have unpaid debts to the
enterprise.

The current implementation lacks proper translations as well as proper
SQL filtering for unposted records.  To complete this, there needs to be
 - [ ] i18n
 - [ ] Integration Tests
 - [ ] End to End Tests
 - [ ] General Report Refactoring (Notify should use report keys)

Closes ....
  • Loading branch information
Jonathan Niles authored and sfount committed Feb 16, 2017
1 parent 160123e commit de318b3
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 12 deletions.
6 changes: 6 additions & 0 deletions client/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,9 @@
"SIXTY_TO_NINETY_DAYS" : "60 to 90 Days",
"OVER_NINETY_DAYS" : "Over 90 Days"
},
"OPEN_DEBTORS" : {
"TITLE" : "Debtors with Unpaid Debts"
},
"CASH_EXPENSE" : "Expenses",
"CASH_INCOME" : "Incomes",
"CASHFLOW" : "Cashflow",
Expand Down Expand Up @@ -1347,6 +1350,8 @@
"LAST_NAME" : "Last Name",
"LAST_TRANSACTION" : "Last Transactions",
"LAST_VISIT" : "Last Visit",
"LAST_INVOICE" : "Last Invoice",
"LAST_PAYMENT" : "Last Payment",
"LISTS" : "Registered price list",
"LOADING" : "Fetching data from the server.",
"LOCKED" : "Locked",
Expand Down Expand Up @@ -1408,6 +1413,7 @@
"TITLE" : "Title",
"TOTAL" : "Total",
"TOTAL_COST" : "Total Cost",
"TOTAL_DEBT" : "Total Debt",
"TRANSACTION" : "Transaction",
"TRANSACTION_ID" : "Transaction ID",
"TRANSACTION_PRICE" : "Transaction Price",
Expand Down
4 changes: 3 additions & 1 deletion client/src/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1367,7 +1367,9 @@
"LAST_BILL" : "Dernière Facture",
"LAST_NAME" : "Nom",
"LAST_TRANSACTION" : "Derniers Transactions",
"LAST_VISIT" : "Derniere visite",
"LAST_VISIT" : "Derniere Visite",
"LAST_INVOICE" : "Derniere Facture",
"LAST_PAYMENT" : "Derniere Paiement",
"LISTS" : "Liste des prix enregistrés",
"LOADING" : "Récupération des données à partir du serveur.",
"LOCKED" : "Bloquée",
Expand Down
58 changes: 58 additions & 0 deletions client/src/partials/reports/modals/openDebtors.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
angular.module('bhima.controllers')
.controller('openDebtorsController', OpenDebtorsConfigController);

OpenDebtorsConfigController.$inject = [
'$state', '$uibModalInstance', 'LanguageService', 'BaseReportService', 'reportDetails'
];

/**
* @module
* Open Debtors Controller
*
* @description
* This controller produces the Open Debtors report of debtors which have unpaid
* debts to the hospital. It provides the accountant a few different ways to
* investigate their institution.
*/
function OpenDebtorsConfigController($state, ModalInstance, Languages, SavedReports, reportDetails) {
var vm = this;
var report = reportDetails;

// expose to the view
vm.generate = generate;
vm.cancel = ModalInstance.dismiss;
vm.report = report;

// how the report should be ordered
vm.orders = [
{ id: 'payment-date-asc', key: 'ORDER.PAYMENT_DATE_ASC' },
{ id: 'payment-date-desc', key: 'ORDER.PAYMENT_DATE_ASC' },
{ id: 'invoice-date-asc', key: 'ORDER.PAYMENT_DATE_ASC' },
{ id: 'invoice-date-desc', key: 'ORDER.PAYMENT_DATE_ASC' },
{ id: 'debt-desc', key: 'ORDER.PAYMENT_DATE_ASC' },
{ id: 'debt-asc', key: 'ORDER.PAYMENT_DATE_ASC' }
];

// default order
vm.order = 'payment-date-desc';

// whether the report should include posting journal records or not
vm.includePostingJournal = false;

function generate(form) {
if (form.$invalid) { return; }

var url = 'reports/finance/debtors/open';

var options = {
label : vm.label,
lang : Languages.key,
};

return SavedReports.requestPDF(url, report, options)
.then(function (result) {
ModalInstance.dismiss();
$state.reload();
});
}
}
57 changes: 57 additions & 0 deletions client/src/partials/reports/modals/openDebtors.modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<form name="ConfigForm" bh-submit="ReportConfigCtrl.generate(ConfigForm)" bh-form-defaults novalidate autocomplete="none">

<div class="modal-header">
<ol class="headercrumb">
<li class="static">{{ ::ReportConfigCtrl.report.title_key | translate }}</li>
<li class="title" translate>FORM.LABELS.CREATE</li>
</ol>
</div>

<div class="modal-body">

<div class="form-group"
ng-class="{ 'has-error' : ConfigForm.$submitted && ConfigForm.label.$invalid }">
<label class="control-label" translate>FORM.LABELS.LABEL</label>
<input class="form-control" name="label" autocomplete="off" ng-model="ReportConfigCtrl.label" required />
<div class="help-block" ng-messages="ConfigForm.label.$error" ng-show="ConfigForm.$submitted">
<div ng-messages-include="partials/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group"
ng-class="{ 'has-error' : ConfigForm.$submitted && ConfigForm.order.$invalid }">
<label class="control-label" translate>FORM.LABELS.ORDER</label>
<select class="form-control" ng-model="ReportConfigCtrl.order" name="order" ng-options="order.id as order.key for order in ReportConfigCtrl.orders">
</select>
</div>

<!-- disabled until actually useful -->
<fieldset disabled>
<div class="radio">
<p class="control-label" style="margin-bottom:5px;">
<strong translate>FORM.LABELS.INCLUDE_UNPOSTED_RECORDS</strong>
</p>

<label class="radio-inline">
<input type="radio" name="includePostingJournal" ng-model="ReportConfigCtrl.includePostingJournal" ng-value="true">
<span translate>FORM.LABELS.YES</span>
</label>

<label class="radio-inline">
<input type="radio" name="includePostingJournal" ng-model="ReportConfigCtrl.includePostingJournal" ng-value="false">
<span translate>FORM.LABELS.NO</span>
</label>
</div>
</fieldset>
</div>

<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="ReportConfigCtrl.cancel()" data-method="cancel">
<span translate>FORM.BUTTONS.CANCEL</span>
</button>

<bh-loading-button loading-state="ConfigForm.$loading">
<span translate>FORM.BUTTONS.GENERATE</span>
</bh-loading-button>
</div>
</form>
4 changes: 3 additions & 1 deletion client/src/partials/reports/reports.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
angular.module('bhima.controllers')
.controller('ReportsController', ReportsController);

ReportsController.$inject = ['$state', 'BaseReportService', '$uibModal', 'NotifyService'];
ReportsController.$inject = [
'$state', 'BaseReportService', '$uibModal', 'NotifyService'
];

function ReportsController($state, SavedReports, Modal, Notify) {
var vm = this;
Expand Down
1 change: 1 addition & 0 deletions server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ exports.configure = function configure(app) {
app.get('/reports/finance/cash', financeReports.cash.report);
app.get('/reports/finance/cash/:uuid', financeReports.cash.receipt);
app.get('/reports/finance/debtors/aged', financeReports.debtors.aged);
app.get('/reports/finance/debtors/open', financeReports.debtors.open);
app.get('/reports/finance/vouchers', financeReports.vouchers.report);
app.get('/reports/finance/vouchers/:uuid', financeReports.vouchers.receipt);
app.get('/reports/finance/accounts/chart', financeReports.accounts.chart);
Expand Down
9 changes: 5 additions & 4 deletions server/controllers/finance/debtors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function invoices(req, res, next) {
const options = req.query;

getDebtorInvoices(req.params.uuid)
.then(function (uuids){
.then(function (uuids){
return invoiceBalances(req.params.uuid, uuids, options);
})
.then(function (invoices) {
Expand All @@ -149,23 +149,24 @@ function invoices(req, res, next) {
}

/**
* This function sends back a list of invoices uuids
* This function sends back a list of invoices uuids
* which belong to a particular debtor
**/

function getDebtorInvoices (debtorUid){
debtorUid = db.bid(debtorUid);
const reversalVoucherType = 10;

// get the debtor invoice uuids from the invoice table
let sql =`
SELECT BUID(invoice.uuid) as uuid
FROM invoice
WHERE debtor_uuid = ? AND invoice.uuid NOT IN (SELECT voucher.reference_uuid FROM voucher WHERE voucher.type_id = ?)
ORDER BY invoice.date ASC, invoice.reference;
`;

return db.exec(sql, [debtorUid, reversalVoucherType])
.then(function (uuids) {
.then(uuids => {
// if nothing found, return an empty array
if (!uuids.length) { return []; }
uuids = uuids.map(item => item.uuid);
Expand Down
7 changes: 3 additions & 4 deletions server/controllers/finance/reports/debtors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ function agedDebtorReport(req, res, next) {

// fire the SQL for the report
queryContext(qs)
.then(function (data) {
return report.render(data);
})
.then(data => report.render(data))
.then(result => {
res.set(result.headers).send(result.report);
})
Expand All @@ -57,7 +55,7 @@ function agedDebtorReport(req, res, next) {
/**
* @method queryContext
*
* @param {Object} params Paramters passed in to customise the report - these
* @param {Object} params Parameters passed in to customise the report - these
* are usually passed in through the query string
* @description
* The HTTP interface which actually creates the report.
Expand Down Expand Up @@ -116,3 +114,4 @@ function queryContext(queryParams) {

exports.context = queryContext;
exports.aged = agedDebtorReport;
exports.open = require('./openDebtors').report;
48 changes: 48 additions & 0 deletions server/controllers/finance/reports/debtors/openDebtors.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{{> head title="REPORT.OPEN_DEBTORS.TITLE"}}

<body>
<main class="container">
{{> header }}

<h4 class="text-center">
{{translate "REPORT.OPEN_DEBTORS.TITLE"}}
<p><small>{{date this.timestamp }}</small></p>
</h4>

<!-- margin is the cell size -->
<section>
<table class="table table-condensed table-report">
<thead>
<tr>
<th>{{translate "FORM.LABELS.REFERENCE"}}</th>
<th>{{translate "FORM.LABELS.PATIENT"}}</th>
<th>{{translate "TABLE.COLUMNS.LAST_INVOICE"}}</th>
<th>{{translate "TABLE.COLUMNS.LAST_PAYMENT"}}</th>
<th class="text-center">{{translate "TABLE.COLUMNS.TOTAL_DEBT"}}</th>
</tr>
</thead>
<tbody>
{{#each debtors as | debtor |}}
<tr>
<th>{{debtor.reference }}</th>
<td>{{debtor.display_name }}</td>
<td>{{date debtor.lastInvoiceDate}}</td>
<td>{{date debtor.lastPaymentDate}}</td>
<td class="text-right">{{currency debtor.debt ../metadata.enterprise.currency_id}}</td>
</tr>
{{else}}
{{> emptyTable columns=5}}
{{/each}}
</tbody>
{{#if aggregates}}
<tfoot>
<tr>
<th colspan="4">{{translate "TABLE.COLUMNS.TOTAL"}} {{aggregates.numDebtors}} {{translate "TABLE.AGGREGATES.RECORDS"}}</th>
<th class="text-right">{{currency aggregates.balance metadata.enterprise.currency_id}}</th>
</tr>
</tfoot>
{{/if}}
</table>
</section>
</main>
</body>
Loading

0 comments on commit de318b3

Please sign in to comment.