diff --git a/client/src/js/services/PatientService.js b/client/src/js/services/PatientService.js index 0df7572fba..b919be35e2 100644 --- a/client/src/js/services/PatientService.js +++ b/client/src/js/services/PatientService.js @@ -6,20 +6,17 @@ PatientService.$inject = [ '$http', 'util', 'SessionService', '$uibModal', 'Docu /** * @module PatientService * - * This service is reponsible for providing an interface between angular + * This service is responsible for providing an interface between angular * module controllers and the server /patients API. * * @example - * Controller.$inject = ['PatientService']; - * - * var Patients = PatientService; - * - * // returns patient details - * Patients.read(uuid)... - * - * // creates a patient - * Patients.create(medicalDetails, financeDetails)... + * function Controller(Patients) { + * // returns patient details + * Patients.read(uuid).then(callback); * + * // creates a patient + * Patients.create(medicalDetails, financeDetails).then(callback); +* } */ function PatientService($http, util, Session, $uibModal, Documents, Visits) { var service = this; @@ -133,7 +130,7 @@ function PatientService($http, util, Session, $uibModal, Documents, Visits) { * paramSerializer */ function search(options) { - options = options || {}; + options = angular.copy(options || {}); var target = baseUrl.concat('search'); @@ -200,22 +197,21 @@ function PatientService($http, util, Session, $uibModal, Documents, Visits) { } - /* + /** * This function prepares the headers patient properties which were filtered, * Special treatment occurs when processing data related to the date * @todo - this might be better in it's own service */ function formatFilterParameters(params) { - var columns = [ { field: 'name', displayName: 'FORM.LABELS.NAME' }, { field: 'sex', displayName: 'FORM.LABELS.GENDER' }, { field: 'hospital_no', displayName: 'FORM.LABELS.HOSPITAL_NO' }, { field: 'reference', displayName: 'FORM.LABELS.REFERENCE' }, - { field: 'dateBirthFrom', displayName: 'FORM.LABELS.DOB', comparitor: '<', ngFilter:'date' }, - { field: 'dateBirthTo', displayName: 'FORM.LABELS.DOB', comparitor: '>', ngFilter:'date' }, - { field: 'dateRegistrationFrom', displayName: 'FORM.LABELS.DATE_REGISTRATION', comparitor: '<', ngFilter:'date' }, - { field: 'dateRegistrationTo', displayName: 'FORM.LABELS.DATE_REGISTRATION', comparitor: '>', ngFilter:'date' }, + { field: 'dateBirthFrom', displayName: 'FORM.LABELS.DOB', comparitor: '>', ngFilter:'date' }, + { field: 'dateBirthTo', displayName: 'FORM.LABELS.DOB', comparitor: '<', ngFilter:'date' }, + { field: 'dateRegistrationFrom', displayName: 'FORM.LABELS.DATE_REGISTRATION', comparitor: '>', ngFilter:'date' }, + { field: 'dateRegistrationTo', displayName: 'FORM.LABELS.DATE_REGISTRATION', comparitor: '<', ngFilter:'date' }, ]; // returns columns from filters @@ -231,13 +227,23 @@ function PatientService($http, util, Session, $uibModal, Documents, Visits) { }); } - function openSearchModal() { + /** + * @method openSearchModal + * + * @param {Object} params - an object of filter parameters to be passed to + * the modal. + * @returns {Promise} modalInstance + */ + function openSearchModal(params) { return $uibModal.open({ templateUrl: 'partials/patients/registry/search.modal.html', size: 'md', keyboard: false, animation: true, - controller: 'PatientRegistryModalController as ModalCtrl' + controller: 'PatientRegistryModalController as ModalCtrl', + resolve : { + params : function paramsProvider() { return params; } + } }).result; } diff --git a/client/src/js/services/receipts/ReceiptService.js b/client/src/js/services/receipts/ReceiptService.js index cd258fdd33..8117c16c4b 100644 --- a/client/src/js/services/receipts/ReceiptService.js +++ b/client/src/js/services/receipts/ReceiptService.js @@ -29,7 +29,7 @@ function ReceiptService($http, util) { * * @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. + * report, this includes things like renderer target. * @return {Promise} Eventually returns report object from server */ function invoice(uuid, options) { @@ -63,8 +63,6 @@ function ReceiptService($http, util) { responseType = 'arraybuffer'; } - delete options.render; - return $http.get(route, {params : options, responseType : responseType}) .then(util.unwrapHttpResponse); } diff --git a/client/src/partials/patients/registry/registry.html b/client/src/partials/patients/registry/registry.html index 126ede0ece..cd1014e355 100644 --- a/client/src/partials/patients/registry/registry.html +++ b/client/src/partials/patients/registry/registry.html @@ -34,7 +34,12 @@ > - + {{ "FORM.INFOS.CLEAR_FILTERS" | translate }} diff --git a/client/src/partials/patients/registry/registry.js b/client/src/partials/patients/registry/registry.js index de24e19f5d..97bf33a8bc 100644 --- a/client/src/partials/patients/registry/registry.js +++ b/client/src/partials/patients/registry/registry.js @@ -55,10 +55,8 @@ function PatientRegistryController(Patients, Notify, moment, Receipt, AppCache, // error handler function handler(error) { - if (error) { - vm.hasError = true; - Notify.handleError(error); - } + vm.hasError = true; + Notify.handleError(error); } // this uses function loads patients from the database with search parameters @@ -78,9 +76,8 @@ function PatientRegistryController(Patients, Notify, moment, Receipt, AppCache, // hook the returned patients up to the grid. request.then(function (patients) { - // this will improve with @dedrickenc's pull requst patients.forEach(function (patient) { - patient.patientAge = util.getMomentAge(patient.dob); + patient.patientAge = util.getMomentAge(patient.dob, 'years'); }); // put data in the grid @@ -94,8 +91,12 @@ function PatientRegistryController(Patients, Notify, moment, Receipt, AppCache, // search and filter data in Patient Registry function search() { - Patients.openSearchModal() + Patients.openSearchModal(vm.filters) .then(function (parameters) { + + // no parameters means the modal was dismissed. + if (!parameters) { return; } + cacheFilters(parameters); return load(vm.filters); }); @@ -122,7 +123,7 @@ function PatientRegistryController(Patients, Notify, moment, Receipt, AppCache, // open a print modal to print all patient registrations to date function print() { - var options = vm.filters || {}; + var options = angular.copy(vm.filters || {}); // @todo(jniles): Make reports and receipts use the same rendering modal Receipt.patientRegistrations(options); diff --git a/client/src/partials/patients/registry/search.modal.js b/client/src/partials/patients/registry/search.modal.js index ce147763ed..ad9d1351f0 100644 --- a/client/src/partials/patients/registry/search.modal.js +++ b/client/src/partials/patients/registry/search.modal.js @@ -2,7 +2,7 @@ angular.module('bhima.controllers') .controller('PatientRegistryModalController', PatientRegistryModalController); PatientRegistryModalController.$inject = [ - '$uibModalInstance', 'PatientService', 'DateService' + '$uibModalInstance', 'PatientService', 'DateService', 'params' ]; /** @@ -10,17 +10,19 @@ PatientRegistryModalController.$inject = [ * * @description * This controller is responsible for setting up the filters for the patient - * search functionality on the patient registry page. + * search functionality on the patient registry page. Filters that are already + * applied to the grid can be passed in via the params inject. */ -function PatientRegistryModalController(ModalInstance, Patients, Dates) { +function PatientRegistryModalController(ModalInstance, Patients, Dates, params) { var vm = this; // bind period labels from the service vm.periods = Dates.period(); vm.today = new Date(); - // this will hold the parameters - vm.params = {}; + // bind filters if they have already been applied. Otherwise, default to an + // empty object. + vm.params = params || {}; // bind methods vm.submit = submit; @@ -28,6 +30,7 @@ function PatientRegistryModalController(ModalInstance, Patients, Dates) { vm.clear = clear; vm.setDateRange = setDateRange; + // returns the parameters to the parent controller function submit(form) { if (form.$invalid) { return; } @@ -43,7 +46,8 @@ function PatientRegistryModalController(ModalInstance, Patients, Dates) { return ModalInstance.close(parameters); } - // clears search parameters + // clears search parameters. Custom logic if a date is used so that we can + // clear two properties. function clear(value) { if (value === 'registration') { delete vm.params.dateRegistrationFrom; @@ -87,7 +91,8 @@ function PatientRegistryModalController(ModalInstance, Patients, Dates) { } } + // dismiss the modal function cancel() { - ModalInstance.dismiss(); + ModalInstance.close(); } } diff --git a/client/test/e2e/patient/registry.search.js b/client/test/e2e/patient/registry.search.js index 14389ed08a..5dd3d10866 100644 --- a/client/test/e2e/patient/registry.search.js +++ b/client/test/e2e/patient/registry.search.js @@ -59,6 +59,7 @@ function PatientRegistrySearch() { expectNumberOfGridRows(1); expectNumberOfFilters(1); + FU.buttons.clear(); }); // demonstrates additive filters @@ -70,6 +71,7 @@ function PatientRegistrySearch() { expectNumberOfGridRows(2); expectNumberOfFilters(2); + FU.buttons.clear(); }); // demonstrates that additive + time-delimited filtering works @@ -81,6 +83,7 @@ function PatientRegistrySearch() { expectNumberOfGridRows(1); expectNumberOfFilters(3); + FU.buttons.clear(); }); // demonstrates that sex + time-delimited filtering works @@ -92,6 +95,7 @@ function PatientRegistrySearch() { expectNumberOfGridRows(1); expectNumberOfFilters(3); + FU.buttons.clear(); }); // changes every single date input manually. @@ -107,6 +111,7 @@ function PatientRegistrySearch() { expectNumberOfGridRows(0); expectNumberOfFilters(4); + FU.buttons.clear(); }); // combines dates with manual date manipulation @@ -119,8 +124,10 @@ function PatientRegistrySearch() { element(by.id('male')).click(); FU.buttons.submit(); + expectNumberOfGridRows(2); expectNumberOfFilters(3); + FU.buttons.clear(); }); // clears filters to assert that the "error state" bug does not occur when the @@ -135,7 +142,7 @@ function PatientRegistrySearch() { expectNumberOfFilters(3); // click the "clear filters" button - $('[data-method="clear"]').click(); + FU.buttons.clear(); // the filter bar shouldn't exist expectNumberOfGridRows(defaultVisibleRowNumber); @@ -143,23 +150,23 @@ function PatientRegistrySearch() { }); it('should remember the cached filters', () => { + FU.buttons.search(); - // Add all the filters (7 in total) - $('[data-date-registration]').$('[data-date-range="today"]').click(); + // Add all the filters (4 in total) $('[data-date-dob]').$('[data-date-range="year"]').click(); element(by.id('male')).click(); FU.input('ModalCtrl.params.name', 'Some Non-Existant Patient'); - FU.input('ModalCtrl.params.reference', 'TPA#'); FU.buttons.submit(); expectNumberOfGridRows(0); - expectNumberOfFilters(7); + expectNumberOfFilters(4); browser.refresh(); expectNumberOfGridRows(0); - expectNumberOfFilters(7); + expectNumberOfFilters(4); + FU.buttons.clear(); }); } diff --git a/client/test/e2e/shared/FormUtils.js b/client/test/e2e/shared/FormUtils.js index 63f524108f..746e40628f 100644 --- a/client/test/e2e/shared/FormUtils.js +++ b/client/test/e2e/shared/FormUtils.js @@ -9,11 +9,12 @@ helpers.configure(chai); // These buttons depend on custom data tags to indicate actions. This seems // cleaner than using a whole bunch of ids which may potentially collide. // However, this decision can be reviewed -var buttons = { +const buttons = { create: function create() { return $('[data-method="create"]').click(); }, search: function search() { return $('[data-method="search"]').click(); }, submit: function submit() { return $('[data-method="submit"]').click(); }, cancel: function cancel() { return $('[data-method="cancel"]').click(); }, + clear: function clear() { return $('[data-method="clear"]').click(); }, back: function back() { return $('[data-method="back"]').click(); }, delete: function delet() { return $('[data-method="delete"]').click(); } }; diff --git a/server/controllers/medical/reports/registrations.handlebars b/server/controllers/medical/reports/registrations.handlebars index 118b3e44fc..fe7404a058 100644 --- a/server/controllers/medical/reports/registrations.handlebars +++ b/server/controllers/medical/reports/registrations.handlebars @@ -36,7 +36,14 @@

{{#each metadata.filters}} - {{@key}}: {{this}} + {{! this madness is to make sure dates are properly formatted }} + {{translate this.displayName}}: + + {{#if this.comparitor}} {{this.comparitor}} {{/if}} + + {{#if this.isDate}} {{date this.value}} + {{else}} {{this.value}} {{/if}} + {{/each}}

diff --git a/server/controllers/medical/reports/registrations.js b/server/controllers/medical/reports/registrations.js index bd63a1b2dc..c9df457be8 100644 --- a/server/controllers/medical/reports/registrations.js +++ b/server/controllers/medical/reports/registrations.js @@ -6,17 +6,19 @@ * matching query conditions passed from the patient registry UI grid. * * @requires path - * @requires db * @requires lodash + * @requires moment * @requires BadRequest * @requires Patients * @requires renderers/json * @requires renderers/html * @requires renderers/pdf */ +'use strict'; const path = require('path'); const _ = require('lodash'); +const moment = require('moment'); const BadRequest = require('../../../lib/errors/BadRequest'); @@ -42,15 +44,29 @@ const template = path.normalize('./server/controllers/medical/reports/registrati // Basically, to show a pretty filter bar, this will translate URL query params // into human-readable text to be placed in the report, showing the properties // filtered on. -// TODO - make sure this is translated before being handed to the report. -const qsTranslations = { - 'reference' : 'TABLE.COLUMNS.REFERENCE', - 'name' : 'TABLE.COLUMNS.NAME', - 'dob' : 'TABLE.COLUMNS.DOB', - 'sex' : 'TABLE.COLUMNS.GENDER', - 'hospital_no' : 'TABLE.COLUMNS.HOSPITAL_FILE_NR', - 'date_registered' : 'TABLE.COLUMNS.DATE_REGISTRATED', -}; +function formatFilters(qs) { + const columns = [ + { field: 'name', displayName: 'FORM.LABELS.NAME' }, + { field: 'sex', displayName: 'FORM.LABELS.GENDER' }, + { field: 'hospital_no', displayName: 'FORM.LABELS.HOSPITAL_NO' }, + { field: 'reference', displayName: 'FORM.LABELS.REFERENCE' }, + { field: 'dateBirthFrom', displayName: 'FORM.LABELS.DOB', comparitor: '>', isDate: true }, + { field: 'dateBirthTo', displayName: 'FORM.LABELS.DOB', comparitor: '<', isDate: true }, + { field: 'dateRegistrationFrom', displayName: 'FORM.LABELS.DATE_REGISTRATION', comparitor: '>', isDate: true }, + { field: 'dateRegistrationTo', displayName: 'FORM.LABELS.DATE_REGISTRATION', comparitor: '<', isDate: true } + ]; + + return columns.filter(column => { + let value = qs[column.field]; + + if (!_.isUndefined(value)) { + column.value = value; + return true; + } else { + return false; + } + }); +} /** * @method build @@ -74,17 +90,17 @@ function build(req, res, next) { // delete from the query string delete qs.renderer; - // clone the query string filters for "metadata" + const params = _.clone(qs); + const metadata = { - filters: _.clone(qs), timestamp: new Date(), - filtersI18n: qsTranslations + filters: formatFilters(qs) }; // enforced detailed - qs.detailed = 1; + params.detailed = 1; - Patients.find(qs) + Patients.find(params) .then(patients => renderer.render({ patients, metadata }, template, defaults)) .then(result => { res.set(renderer.headers).send(result); diff --git a/server/lib/template.js b/server/lib/template.js index 21a660d27b..fd6e487fba 100644 --- a/server/lib/template.js +++ b/server/lib/template.js @@ -51,6 +51,7 @@ function translate(translateCode, languageKey) { if (!translateCode) { return; } + const codeList = translateCode.split('.'); /** @@ -95,7 +96,8 @@ function currency(value, currencyKey) { * @returns {String} - the formatted string for insertion into templates */ function date(value) { - return moment(value).format('DD/MM/YYYY'); + let date = moment(value); + return date.isValid() ? date.format('DD/MM/YYYY') : ''; } /**