Skip to content

Commit

Permalink
feat(registration report): implement clientside
Browse files Browse the repository at this point in the history
This commit implements the client PDF report using the Receipt service
created by @sfount.  Because the client-side filters do not have a good
data model, printing with filtering has been left for later.

There are several prominent TODOs in this commit:
 1. Filtering on the client should be additive, and use a key:value
   model, so that each filter takes in one parameter.  For example,
   `name:jniles` would be stored in the JSON `{ name : 'jniles' }`, and
   be sent to the server as `?name=jniles`.
 2. The Receipt Service is now overloaded with reports.  We need to
 cleanly separate Receipts as a subset of reports, and ensure that the
 modal and controllers are appropriately named.
  • Loading branch information
jniles committed Jun 13, 2016
1 parent cc595a4 commit 1d4f528
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 35 deletions.
2 changes: 1 addition & 1 deletion client/src/js/services/PatientService.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ function PatientService($http, util, Session, $uibModal, Documents, Visits) {

function openSearchModal() {
return $uibModal.open({
templateUrl : 'partials/patients/registry/modal.html',
templateUrl : 'partials/patients/registry/search.modal.html',
size : 'md',
animation : true,
controller : 'PatientRegistryModalController as ModalCtrl'
Expand Down
27 changes: 25 additions & 2 deletions client/src/js/services/receipts/ReceiptModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function ReceiptModal(Modal, Receipts) {
// expose available receipts
service.invoice = invoice;
service.patient = patient;
service.patientRegistrations = patientRegistrations;

/**
* Invokes a patient invoice receipt
Expand All @@ -44,7 +45,7 @@ function ReceiptModal(Modal, Receipts) {
notifyCreated : notifyCreated
};

var invoiceRequest = Receipts.invoice(uuid, { render : options.renderer });
var invoiceRequest = Receipts.invoice(uuid, { renderer : options.renderer });
var invoiceProvider = {
resolve : {
receipt : function receiptProvider() { return { promise : invoiceRequest }; },
Expand All @@ -67,7 +68,7 @@ function ReceiptModal(Modal, Receipts) {
notifyCreated : notifyCreated
};

var patientRequest = Receipts.patient(uuid, { render : options.renderer });
var patientRequest = Receipts.patient(uuid, { renderer : options.renderer });
var patientProvider = {
resolve : {
receipt : function receiptProvider() { return { promise : patientRequest }; },
Expand All @@ -79,4 +80,26 @@ function ReceiptModal(Modal, Receipts) {
var instance = Modal.open(configuration);
return instance.result;
}

// in this case, the options are actually all filters from the ui-grid
function patientRegistrations(options) {
var reportOptions = {
title: 'PATIENT_REG.PATIENT_REGISTRATIONS',
renderer: Receipts.renderers.PDF,
};

options.renderer = Receipts.renderers.PDF;

var reportRequest = Receipts.patientRegistrations(options);
var reportProvider = {
resolve : {
receipt : function reportProvider () { return { promise : reportRequest }; },
options : function optionsProvider() { return reportOptions; },
}
};

var configuration = angular.extend(modalConfiguration, reportProvider);
var instance = Modal.open(configuration);
return instance.result;
}
}
23 changes: 19 additions & 4 deletions client/src/js/services/receipts/ReceiptService.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,21 @@ function ReceiptService($http, util) {
service.invoice = invoice;
service.patient = patient;
service.renderers = renderers;
service.patientRegistrations = patientRegistrations;

/**
* Fetch invoice report data from /reports/invoices/:uuid
*
* @param {String} uuid Target invoice UUID to report on
* @param {Object} options Configuration options for the server generated
* report, this includes things like render target.
* @return {Promise} Eventually returns report object from server
* report, this includes things like render target.
* @return {Promise} Eventually returns report object from server
*/
function invoice(uuid, options) {
var route = '/reports/invoices/'.concat(uuid);
var responseType = null;

if (options.render === renderers.PDF) {
if (options.renderer === renderers.PDF) {
responseType = 'arraybuffer';
}
return $http.get(route, {params: options, responseType: responseType})
Expand All @@ -46,11 +47,25 @@ function ReceiptService($http, util) {
var route ='/reports/patient/'.concat(uuid);
var responseType = null;

if (options.render === renderers.PDF) {
if (options.renderer === renderers.PDF) {
responseType = 'arraybuffer';
}

return $http.get(route, {params : options, responseType : responseType})
.then(util.unwrapHttpResponse);
}

function patientRegistrations(options) {
var route = '/reports/patient/registrations';
var responseType = null;

if (options.renderer === renderers.PDF) {
responseType = 'arraybuffer';
}

delete options.render;

return $http.get(route, {params : options, responseType : responseType})
.then(util.unwrapHttpResponse);
}
}
46 changes: 46 additions & 0 deletions client/src/partials/patients/registry/print.modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<div class="modal-header">
<div style="display : inline-block">
<ol class="headercrumb">
<li class="title">{{ "PATIENT_REG.PAGE_TITLE | translate }}</li>
</ol>
</div>
</div>

<!-- Temporary Loading State -->
<div style="padding : 10px;" ng-hide="PatientRegistryPrintCtrl.report">
<p class="text-muted"><i class="fa fa-spin fa-cog"></i> {{"RECEIPTS.LOADING"}}</p>
</div>

<!-- ng-if used in favour of ng-switch to allow dynamic states -->
<div>

<!-- PDF -->
<div ng-if="PatientRegistryPrintCtrl.renderer === PatientRegistryPrintCtrl.renderers.PDF">

<iframe
id="pdf"
ng-src="{{PatientRegistryPrintCtrl.report}}"
type="application/pdf"
style="width : 100%; height : 50vh; margin-bottom : -5px; border : none;">
</iframe>
</div>

<!-- HTML -->
<div ng-if="PatientRegistryPrintCtrl.renderer === PatientRegistryPrintCtrl.renderers.HTML">

<div class="bh-modal-content">
<div ng-bind-html="PatientRegistryPrintCtrl.receipt"></div>
</div>
</div>

<!-- CSV -->

<!-- Excel -->
</div>

<div class="modal-footer">
<button class="btn btn-default btn-sm" ng-click="PatientRegistryPrintCtrl.close()" data-action="close">Close</button>
<button class="btn btn-primary btn-sm" ng-click="PatientRegistryPrintCtrl.print()" data-action="print">
<span class="glyphicon glyphicon-print"></span> Print
</button>
</div>
18 changes: 18 additions & 0 deletions client/src/partials/patients/registry/print.modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
angular.module('bhima.controllers')
.controller('PatientRegistryPrintController', PatientRegistryPrintCtrl);

PatientRegistryPrintCtrl.$inject = [
'$http', '$uibModalInstance', 'filters'
];

function PatientRegistryPrintCtrl($http, ModalInstance, data) {
var vm = this;
vm.data = data;

// request the report from the server
$http.get(data.url, { params : data.params })
.then(function (report) {
vm.report = report;
});
}

15 changes: 12 additions & 3 deletions client/src/partials/patients/registry/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ angular.module('bhima.controllers')
.controller('PatientRegistryController', PatientRegistryController);

PatientRegistryController.$inject = [
'PatientService', 'NotifyService', 'moment'
'PatientService', 'NotifyService', 'moment', 'ReceiptModal'
];

/**
* Patient Registry Controller
*
* This module is responsible for the management of Patient Registry.
*/
function PatientRegistryController(Patients, Notify, moment, Modals) {
function PatientRegistryController(Patients, Notify, moment, Receipt) {
var vm = this;

var patientActionsTemplate =
Expand Down Expand Up @@ -73,6 +73,7 @@ function PatientRegistryController(Patients, Notify, moment, Modals) {
.then(function (data) {
var response = data.response;
vm.filters = data.filters;
console.log(data.filters);
response.forEach(function (patient) {
patient.patientAge = momentAge(patient.dob);
});
Expand All @@ -84,8 +85,16 @@ function PatientRegistryController(Patients, Notify, moment, Modals) {
});
}

// open a print modal to print all patient registrations to date
function print() {
//Modals.

// @todo(jniles): eventually, we would like to populate this with the
// filters before sending the report request. However, the client-side
// modeling of filters is not up to par yet.
var options = {};

// @todo(jniles): Make reports and receipts use the same rendering modal
Receipt.patientRegistrations(options);
}

// moment() provides the current date, similar to the new Date() API. This requests the difference between two dates
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/finance/reports/invoice.receipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function build(req, res, next) {

var invoiceResponse = {};

var renderTarget = queryString.render || defaultRender;
var renderTarget = queryString.renderer || defaultRender;
var renderer = supportedRender[renderTarget];

/** @todo delegate to additional method */
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/medical/reports/patient.receipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function build(req, res, next) {
var queryString = req.query;
var patientID = req.params.uuid;

var renderTarget = queryString.render || defaultRender;
var renderTarget = queryString.renderer || defaultRender;
var renderer = supportedRender[renderTarget];

if (_.isUndefined(renderer)) {
Expand Down
46 changes: 23 additions & 23 deletions server/test/api/invoiceReceipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,67 @@ var expect = chai.expect;
var helpers = require('./helpers');
helpers.configure(chai);

describe('/reports/invoices Receipts Interface', function () {
describe('/reports/invoices Receipts Interface', function () {
var agent = chai.request.agent(helpers.baseUrl);
before(helpers.login(agent));

// known data for requests and assertions
const validInvoice = '957e4e79-a6bb-4b4d-a8f7-c42152b2c2f6';
const invoiceItemLength = 1;
const invalidInvoice = 'unknown';
const jsonRenderTarget = '?render=json';
const htmlRenderTarget = '?render=html';
const pdfRenderTarget = '?render=pdf';
const invalidRenderTarget = '?render=unkownRender';
const jsonRenderTarget = '?renderer=json';
const htmlRenderTarget = '?renderer=html';
const pdfRenderTarget = '?renderer=pdf';
const invalidRenderTarget = '?renderer=unkownRender';

it('GET /reports/invoices/:uuid should return report data for a valid invoice uuid', function () {
it('GET /reports/invoices/:uuid should return report data for a valid invoice uuid', function () {
return agent.get('/reports/invoices/'.concat(validInvoice))
.then(expectReportJSONSuccess)
.catch(helpers.handler);
});

it('GET /reports/invoices/:uuid should return not found for invalid uuid', function () {
it('GET /reports/invoices/:uuid should return not found for invalid uuid', function () {
return agent.get('/reports/invoices/'.concat(invalidInvoice))
.then(function (result) {
helpers.api.errored(result, 404);
.then(function (result) {
helpers.api.errored(result, 404);
})
.catch(helpers.handler);
});
});

it('GET /reports/invoices/:uuid should return bad request for invalid renderer', function () {
it('GET /reports/invoices/:uuid should return bad request for invalid renderer', function () {
return agent.get('/reports/invoices/'.concat(validInvoice, invalidRenderTarget))
.then(function (result) {
.then(function (result) {
helpers.api.errored(result, 400);
})
.catch(helpers.handler);
});

it('GET /reports/invoices/:uuid should return JSON data for `json` rendering target', function () {
it('GET /reports/invoices/:uuid should return JSON data for `json` rendering target', function () {
return agent.get('/reports/invoices/'.concat(validInvoice, jsonRenderTarget))
.then(expectReportJSONSuccess)
.catch(helpers.handler);
});
it('GET /reports/invoices/:uuid should return HTML data for `html` rendering target', function () {

it('GET /reports/invoices/:uuid should return HTML data for `html` rendering target', function () {
return agent.get('/reports/invoices/'.concat(validInvoice, htmlRenderTarget))
.then(function (result) {
.then(function (result) {
expect(result.headers['content-type']).to.equal('text/html; charset=utf-8');
expect(result.text).to.not.be.empty;
})
.catch(helpers.handler);
});
it('GET /reports/invoices/:uuid should return PDF data for `pdf` rendering target', function () {

it('GET /reports/invoices/:uuid should return PDF data for `pdf` rendering target', function () {
return agent.get('/reports/invoices/'.concat(validInvoice, pdfRenderTarget))
.then(function (result) {
.then(function (result) {
expect(result.headers['content-type']).to.equal('application/pdf');
expect(result.type).to.equal('application/pdf');
})
.catch(helpers.handler);
});
// utility methods
function expectReportJSONSuccess(result) {

// utility methods
function expectReportJSONSuccess(result) {
var recipientKeys = ['items', 'recipient', 'enterprise'];
expect(result).to.have.status(200);
expect(result.headers['content-type']).to.equal('application/json; charset=utf-8');
Expand Down

0 comments on commit 1d4f528

Please sign in to comment.