From 88073bc92206f482adfcd0b0b34b4472e680429f Mon Sep 17 00:00:00 2001 From: Adeola Badmus Date: Wed, 15 Mar 2017 20:02:30 +0100 Subject: [PATCH] Visit Reports (#991) * added basic translations for report * feat: added report model * feat: added report tabs to visit page * feat: added report route to router * feat: added report page * feat: reports to visits children * feat: added add/delete report capabilities * fix: fixed diagnosisContainer issue on browser back history button * fix: display different report title based on type of visit * Modified Package.json for new NPM Deployment * fix: added next appointment and operative plans to opd form * Restore package.json to working state * Implement custom forms for OPD Report * Add custom forms attribute to report model * fix: saved report object * fix: added preview report feat * fix: added report by visit view * fix: added logic to show different report sections * fix: added some report translations * fix: edited report model * fix: implemented logic to toggle between show report and new report * Remove preview functionality from OPD Reports * Implement print button * Remove print section header * Write styles and markup for print page * Refactor report header into partial * Rename report types * fix style lint errors * fix: completed internationalization and lint fix * 0.9.18 * Incremented version number, added scope * fix: remove patientId field, fixed next appointment bug, removed unused translation * fix: change date fields to simple text * fix: fixed bug that has to do with page header title * Added .travis.yml file for building and deploying to npm * Removed ember test. Pretty much does the same as npm test * Allowing all branches to be tested and built * fix: added translations for discharge report * fix: modified report model to accommodate discharge report * fix: added discharge report * fix: added next appointment date to discharge report on save * fix: implemented next appointment as a mixin * fix: added translations for followup appointment message * fix: made sure a followup appointment exists before you generate discharge report * Add Hospital Info report header as option config * Add report model to patient-diagnosis and visit unit tests * fix: fixed report header conflict * Refactor report template * Add custom forms to discharge report * fix: added hospital info doc to environment * fix: create sample docs on couchdb * Change how "new" routes work Makes sure that if user redirects to url the patient is properly selected. * fix: made sure sample docs are created when deleted * Add query parameters to improve app navigation experience * set patient on visit models * Redirect reports/new to patients when no visit model * Clean up visit controller * Set visit on models * Update visit acceptance tests * Fix appointments new surgery test error * Remove double reference to visits controller * Display visit diagnosis appropriately * Fix Operative Plans display * fix: fixed translation lables for next appointments * fix: modified get futureAppointment to work for list of appointments * fix: implemented next appointments on both template and controller * fix: fix lint * FIx next appointments display * Added auto trigger hospitalrun-server refresh script on successful build * Fix reports controller bug * Add diagnosis container * Set patient on model if model is not new * Set visit on controller and add display current operative plan * Add `Completed By` field to report * Refactor reports template * Ensure reports.edit returns to visits.edit * Display patient procedures on report * Remove discharge report compulsory next appointment modal * Fix `completed by` on report page * Style select element properly; Add titles to report * fix: fix new user title bug after a new user has been added * Added deploy branches to .travis.yml * fix: fix test for new user title bug * fix: updated report template with testing attributes on fields * fix: update visit template with testing attributes on fields * fix: implemented acceptance tests for opd and discharge reports * Implement acceptance tests for OPD Report * Refactor code to conform with style guide * Fix nextAppointment bug --- app/admin/custom-forms/edit/controller.js | 4 +- app/appointments/edit/route.js | 4 + app/helpers/date-format.js | 3 + app/locales/en/translations.js | 61 +++- app/medication/edit/controller.js | 2 +- app/medication/edit/route.js | 2 + app/mixins/add-to-patient-route.js | 29 +- app/mixins/patient-submodule.js | 12 + app/mixins/patient-visits.js | 63 +++- app/mixins/user-session.js | 17 ++ app/models/report.js | 32 ++ app/models/visit.js | 1 + app/patients/edit/controller.js | 36 +-- app/procedures/edit/route.js | 3 +- app/reports/edit/controller.js | 79 +++++ app/reports/edit/route.js | 68 +++++ app/reports/edit/template.hbs | 204 +++++++++++++ app/router.js | 7 + app/services/database.js | 40 +++ app/styles/_print.scss | 48 ++- app/templates/section.hbs | 1 - app/users/edit/controller.js | 10 +- app/utils/pouch-views.js | 6 + app/visits/edit/controller.js | 34 ++- app/visits/edit/route.js | 30 +- app/visits/edit/template.hbs | 51 +++- app/visits/route.js | 3 + config/environment.js | 13 + tests/acceptance/users-test.js | 1 + tests/acceptance/visit-test.js | 306 ++++++++++++++++++-- tests/unit/mixins/patient-diagnosis-test.js | 1 + tests/unit/models/visit-test.js | 1 + 32 files changed, 1094 insertions(+), 78 deletions(-) create mode 100644 app/models/report.js create mode 100644 app/reports/edit/controller.js create mode 100644 app/reports/edit/route.js create mode 100644 app/reports/edit/template.hbs diff --git a/app/admin/custom-forms/edit/controller.js b/app/admin/custom-forms/edit/controller.js index 9e8e13010c..cc9c446c8a 100644 --- a/app/admin/custom-forms/edit/controller.js +++ b/app/admin/custom-forms/edit/controller.js @@ -79,7 +79,9 @@ export default AbstractEditController.extend({ 'operativePlan', 'patient', 'socialwork', - 'visit' + 'visit', + 'opdReport', + 'dischargeReport' ], formTypes: computed(function() { diff --git a/app/appointments/edit/route.js b/app/appointments/edit/route.js index bd5c5c7055..59103d68e0 100644 --- a/app/appointments/edit/route.js +++ b/app/appointments/edit/route.js @@ -14,6 +14,7 @@ const { } = Ember; export default AbstractEditRoute.extend(AddToPatientRoute, PatientListRoute, { + editTitle: t('appointments.editTitle'), modelName: 'appointment', newButtonText: t('appointments.buttons.newButton'), @@ -59,6 +60,9 @@ export default AbstractEditRoute.extend(AddToPatientRoute, PatientListRoute, { if (!isEmpty(params.forPatientId)) { let modelPromise = this._super(params); return this._setPatientOnModel(modelPromise, params.forPatientId); + } else if (!isEmpty(params.forVisitId)) { + let modelPromise = this._super(params); + return this._setVisitOnModel(modelPromise, params.forVisitId); } else { return this._createNewRecord(params); } diff --git a/app/helpers/date-format.js b/app/helpers/date-format.js index 7f4ec8d0c3..084f1be96b 100644 --- a/app/helpers/date-format.js +++ b/app/helpers/date-format.js @@ -7,6 +7,9 @@ export default Ember.Helper.helper(function(params, hash) { if (hash && hash.format) { dateFormat = hash.format; } + if (date && typeof date.get == 'function') { + date = date.get('content'); + } return moment(date).format(dateFormat); } }); diff --git a/app/locales/en/translations.js b/app/locales/en/translations.js index 8a56e98bd5..bd0c908b59 100644 --- a/app/locales/en/translations.js +++ b/app/locales/en/translations.js @@ -173,7 +173,9 @@ export default { socialworkFormType: 'Social Work', text: 'Text', textarea: 'Large Text', - visitFormType: 'Visit' + visitFormType: 'Visit', + opdReportFormType: 'Outpatient Report', + dischargeReportFormType: 'Discharge Report' }, messages: { deleteForm: 'Are you sure you want to delete this custom form?', @@ -994,14 +996,16 @@ export default { statusDischarged: 'Discharged', statusCheckedIn: 'Checked In', statusCheckedOut: 'Checked Out', - createNewPatient: 'Create New Patient' + createNewPatient: 'Create New Patient', + reportType: 'Report type' }, navigation: { charges: 'Charges', notes: 'Notes', orders: 'Orders', procedures: 'Procedures', - vitals: 'Vitals' + vitals: 'Vitals', + reports: 'Reports' } }, labs: { @@ -1105,6 +1109,8 @@ export default { backToPatients: 'Back to Patient List', newPatient: '+ new patient', patientCheckIn: 'Patient Check In', + newOPDReport: 'New OPD Report', + newDischargeReport: 'New Discharge Report', scheduleSurgery: 'Schedule Surgery' }, labels: { @@ -1234,6 +1240,55 @@ export default { addMedication: 'Add Medication' } }, + reports: { + titles: { + saved: 'Report saved', + opdTitle: 'OPD Report', + dischargeReport: 'Discharge Report' + }, + form: { + visitDate: 'Date of Visit', + admissionDate: 'Admission Date', + dischargeDate: 'Discharge Date', + notes: { + title: 'Notes', + date: 'Date', + author: 'Author' + }, + primaryDiagnosis: 'Primary Diagnosis', + secondaryDiagnosis: 'Secondary Diagnosis', + procedures: 'Procedures', + allProcedures: 'All Procedures Performed', + labs: 'Labs', + images: 'Images', + medications: 'Medications', + nextAppointment: 'Next Appointment', + nextAppointments: 'Next Appointments', + operativePlan: { + title: 'Operative Plan', + description: 'Operation Description', + procedures: 'Planned Procedures', + instructions: 'Instructions upon Admission' + }, + completedBy: 'Completed By' + }, + opd: { + titles: { + new: 'New OPD Report', + edit: 'Edit OPD Report' + } + }, + discharge: { + titles: { + new: 'New Discharge Report', + edit: 'Edit Discharge Report' + } + }, + messages: { + delete: 'Are you sure you wish to delete this report?', + saved: 'The report has been saved.' + } + }, components: { chargesByTypeTab: { charges: 'charges' diff --git a/app/medication/edit/controller.js b/app/medication/edit/controller.js index 9aaa6a6666..21c142449a 100644 --- a/app/medication/edit/controller.js +++ b/app/medication/edit/controller.js @@ -35,7 +35,7 @@ export default AbstractEditController.extend(AddNewPatient, FulfillRequest, Inve prescriptionClass: function() { let quantity = this.get('model.quantity'); if (Ember.isEmpty(quantity)) { - return 'required'; + return 'required test-medication-prescription'; } }.property('model.quantity'), diff --git a/app/medication/edit/route.js b/app/medication/edit/route.js index 7cdbed5d87..e8ae0b4b0d 100644 --- a/app/medication/edit/route.js +++ b/app/medication/edit/route.js @@ -35,6 +35,8 @@ export default AbstractEditRoute.extend(AddToPatientRoute, FulfillRequest, Inven if (!Ember.isEmpty(idParam) && params[idParam] === 'new' || params[idParam] === 'dispense') { if (!isEmpty(params.forPatientId)) { return this._setPatientOnModel(modelPromise, params.forPatientId); + } else if (!isEmpty(params.forVisitId)) { + return this._setVisitOnModel(modelPromise, params.forVisitId); } else { return this._createNewRecord(params); } diff --git a/app/mixins/add-to-patient-route.js b/app/mixins/add-to-patient-route.js index c1f79dfe39..865816d2c7 100644 --- a/app/mixins/add-to-patient-route.js +++ b/app/mixins/add-to-patient-route.js @@ -6,14 +6,23 @@ export default Mixin.create({ queryParams: { forPatientId: { refreshModel: false + }, + forVisitId: { + refreshModel: false } }, model(params) { let idParam = get(this, 'idParam'); let modelPromise = this._super(params); - if (!isEmpty(params.forPatientId) && params[idParam] === 'new') { - return this._setPatientOnModel(modelPromise, params.forPatientId); + if (params[idParam] === 'new') { + if (!isEmpty(params.forPatientId)) { + return this._setPatientOnModel(modelPromise, params.forPatientId); + } else if (!isEmpty(params.forVisitId)) { + return this._setVisitOnModel(modelPromise, params.forVisitId); + } else { + return this._createNewRecord(params); + } } else { return modelPromise; } @@ -32,5 +41,21 @@ export default Mixin.create({ return model; }); }); + }, + + /** + * Resolves the model promise and then sets the visit information on the model. + */ + _setVisitOnModel(modelPromise, visitId) { + let store = get(this, 'store'); + return modelPromise.then((model) => { + return store.find('visit', visitId).then((visit) => { + model.set('visit', visit); + model.set('returnToVisit', visitId); + model.set('selectPatient', false); + model.set('patient', visit.get('patient')); + return model; + }); + }); } }); diff --git a/app/mixins/patient-submodule.js b/app/mixins/patient-submodule.js index 999c342fb1..704bf69cbc 100644 --- a/app/mixins/patient-submodule.js +++ b/app/mixins/patient-submodule.js @@ -183,6 +183,17 @@ export default Ember.Mixin.create(PatientVisits, { }); }.property('model.patient.id', 'newVisitAdded'), + patientProcedures: Ember.computed('patientVisits.[]', function() { + let patient = get(this, 'model.patient'); + return DS.PromiseArray.create({ + promise: get(this, 'patientVisits').then((patientVisits) => { + return get(patient, 'operationReports').then((operationReports) => { + return this._getPatientProcedures(operationReports, patientVisits); + }); + }) + }); + }), + patientVisitsForSelect: function() { return DS.PromiseArray.create({ promise: this.get('patientVisits').then(function(patientVisits) { @@ -227,6 +238,7 @@ export default Ember.Mixin.create(PatientVisits, { promises.push(visit.get('medication')); promises.push(visit.get('procedures')); promises.push(visit.get('vitals')); + promises.push(visit.get('reports')); } return promises; }, diff --git a/app/mixins/patient-visits.js b/app/mixins/patient-visits.js index aee87fa98b..716ac48c34 100644 --- a/app/mixins/patient-visits.js +++ b/app/mixins/patient-visits.js @@ -1,9 +1,11 @@ import Ember from 'ember'; import PouchDbMixin from 'hospitalrun/mixins/pouchdb'; import VisitStatus from 'hospitalrun/utils/visit-statuses'; - +import DS from 'ember-data'; +import moment from 'moment'; const { - isEmpty + isEmpty, + get } = Ember; export default Ember.Mixin.create(PouchDbMixin, { @@ -20,6 +22,63 @@ export default Ember.Mixin.create(PouchDbMixin, { }); }, + getPatientFutureAppointment(visit, outPatient) { + let patientId = get(visit, 'patient.id'); + let visitDate = get(visit, 'startDate'); + let maxValue = get(this, 'maxValue'); + let promise = this.store.query('appointment', { + options: { + startkey: [patientId, null, null, 'appointment_'], + endkey: [patientId, maxValue, maxValue, maxValue] + }, + mapReduce: 'appointments_by_patient' + }).then(function(result) { + let futureAppointments = result.filter(function(data) { + let startDate = get(data, 'startDate'); + return startDate && moment(startDate).isAfter(moment(visitDate), 'day'); + }).sortBy('startDate'); + if (!futureAppointments.length) { + return null; + } + if (!outPatient) { + let [appointment] = futureAppointments; + return appointment; + } else { + return futureAppointments.slice(0, 3); + } + + }); + return (outPatient) ? DS.PromiseArray.create({ promise }) : DS.PromiseObject.create({ promise }); + }, + + _getVisitCollection(visits, name) { + let returnList = []; + if (!Ember.isEmpty(visits)) { + visits.forEach(function(visit) { + get(visit, name).then(function(items) { + returnList.addObjects(items); + }); + }); + } + return returnList; + }, + + _getPatientProcedures(operationReports, visits) { + let patientProcedures = this._getVisitCollection(visits, 'procedures'); + operationReports.forEach((report) => { + let reportedProcedures = get(report, 'procedures'); + let surgeryDate = get(report, 'surgeryDate'); + reportedProcedures.forEach((procedure) => { + patientProcedures.addObject({ + description: get(procedure, 'description'), + procedureDate: surgeryDate, + report + }); + }); + }); + return patientProcedures; + }, + checkoutVisit(visit, status) { visit.set('status', status); visit.set('endDate', new Date()); diff --git a/app/mixins/user-session.js b/app/mixins/user-session.js index 4316714ba6..8a77720332 100644 --- a/app/mixins/user-session.js +++ b/app/mixins/user-session.js @@ -198,6 +198,15 @@ export default Ember.Mixin.create({ 'Nurse Manager', 'System Administrator' ], + add_report: [ + 'Data Entry', + 'Doctor', + 'Hospital Administrator', + 'Medical Records Officer', + 'Nurse', + 'Nurse Manager', + 'System Administrator' + ], admit_patient: [ 'Data Entry', 'Doctor', @@ -324,6 +333,14 @@ export default Ember.Mixin.create({ 'Nurse Manager', 'System Administrator' ], + delete_report: [ + 'Doctor', + 'Hospital Administrator', + 'Medical Records Officer', + 'Nurse', + 'Nurse Manager', + 'System Administrator' + ], delete_visit: [ 'Doctor', 'Hospital Administrator', diff --git a/app/models/report.js b/app/models/report.js new file mode 100644 index 0000000000..2de87f00e8 --- /dev/null +++ b/app/models/report.js @@ -0,0 +1,32 @@ +import AbstractModel from 'hospitalrun/models/abstract'; +import DS from 'ember-data'; + +export default AbstractModel.extend({ + // Attributes + reportDate: DS.attr('date'), + customForms: DS.attr('custom-forms'), + reportType: DS.attr('string'), + surgeon: DS.attr('string'), + + // Associations + visit: DS.belongsTo('visit', { async: false }), + + validations: { + visit: { + presence: true + }, + + reportDate: { + presence: true + }, + + surgeon: { + presence: { + 'if'(object) { + return !object.get('visit.outPatient'); + }, + message: 'Please select a surgeon' + } + } + } +}); diff --git a/app/models/visit.js b/app/models/visit.js index e400cb4136..317d5ae968 100644 --- a/app/models/visit.js +++ b/app/models/visit.js @@ -70,6 +70,7 @@ export default AbstractModel.extend({ status: DS.attr('string'), visitType: DS.attr(), vitals: DS.hasMany('vital', { async: true }), + reports: DS.hasMany('report', { async: true }), diagnosisList: computed('diagnoses.[]', function() { let diagnoses = this.get('diagnoses'); diff --git a/app/patients/edit/controller.js b/app/patients/edit/controller.js index 6de354eb74..0b0ba25527 100644 --- a/app/patients/edit/controller.js +++ b/app/patients/edit/controller.js @@ -8,13 +8,14 @@ import ReturnTo from 'hospitalrun/mixins/return-to'; import SelectValues from 'hospitalrun/utils/select-values'; import UserSession from 'hospitalrun/mixins/user-session'; import VisitStatus from 'hospitalrun/utils/visit-statuses'; +import PatientVisits from 'hospitalrun/mixins/patient-visits'; const { get, isEmpty } = Ember; -export default AbstractEditController.extend(BloodTypes, DiagnosisActions, PatientId, ReturnTo, UserSession, PatientNotes, { +export default AbstractEditController.extend(BloodTypes, DiagnosisActions, ReturnTo, UserSession, PatientId, PatientNotes, PatientVisits, { canAddAppointment: function() { return this.currentUserCan('add_appointment'); @@ -144,32 +145,21 @@ export default AbstractEditController.extend(BloodTypes, DiagnosisActions, Patie }], patientImaging: function() { - return this._getVisitCollection('imaging'); + return this.getVisitCollection('imaging'); }.property('model.visits.[].imaging'), patientLabs: function() { - return this._getVisitCollection('labs'); + return this.getVisitCollection('labs'); }.property('model.visits.[].labs'), patientMedications: function() { - return this._getVisitCollection('medication'); + return this.getVisitCollection('medication'); }.property('model.visits.[].medication'), patientProcedures: function() { - let patientProcedures = this._getVisitCollection('procedures'); + let visits = this.get('model.visits'); let operationReports = get(this, 'model.operationReports'); - operationReports.forEach((report) => { - let reportedProcedures = get(report, 'procedures'); - let surgeryDate = get(report, 'surgeryDate'); - reportedProcedures.forEach((procedure) => { - patientProcedures.addObject({ - description: get(procedure, 'description'), - procedureDate: surgeryDate, - report - }); - }); - }); - return patientProcedures; + return this._getPatientProcedures(operationReports, visits); }.property('model.visits.[].procedures', 'model.operationReports.[].procedures'), showExpenseTotal: function() { @@ -533,17 +523,9 @@ export default AbstractEditController.extend(BloodTypes, DiagnosisActions, Patie this.send('openModal', `patients.socialwork.${route}`, model); }, - _getVisitCollection(name) { - let returnList = []; + getVisitCollection(name) { let visits = this.get('model.visits'); - if (!Ember.isEmpty(visits)) { - visits.forEach(function(visit) { - visit.get(name).then(function(items) { - returnList.addObjects(items); - }); - }); - } - return returnList; + return this._getVisitCollection(visits, name); }, _updateSocialRecord(recordToUpdate, name) { diff --git a/app/procedures/edit/route.js b/app/procedures/edit/route.js index cfac071baf..714a4b84eb 100644 --- a/app/procedures/edit/route.js +++ b/app/procedures/edit/route.js @@ -1,9 +1,10 @@ import AbstractEditRoute from 'hospitalrun/routes/abstract-edit-route'; +import AddToPatientRoute from 'hospitalrun/mixins/add-to-patient-route'; import ChargeRoute from 'hospitalrun/mixins/charge-route'; import Ember from 'ember'; import { translationMacro as t } from 'ember-i18n'; -export default AbstractEditRoute.extend(ChargeRoute, { +export default AbstractEditRoute.extend(AddToPatientRoute, ChargeRoute, { editTitle: t('procedures.titles.edit'), modelName: 'procedure', newTitle: t('procedures.titles.new'), diff --git a/app/reports/edit/controller.js b/app/reports/edit/controller.js new file mode 100644 index 0000000000..fda9cad173 --- /dev/null +++ b/app/reports/edit/controller.js @@ -0,0 +1,79 @@ +import AbstractEditController from 'hospitalrun/controllers/abstract-edit-controller'; +import Ember from 'ember'; +import PatientSubmodule from 'hospitalrun/mixins/patient-submodule'; +import PatientDiagnosis from 'hospitalrun/mixins/patient-diagnosis'; +import PouchDbMixin from 'hospitalrun/mixins/pouchdb'; + +const { + get, + set +} = Ember; + +export default AbstractEditController.extend(PatientSubmodule, PatientDiagnosis, PouchDbMixin, { + lookupListsToUpdate: [{ + name: 'physicianList', + property: 'model.surgeon', + id: 'physician_list' + }], + + newReport: false, + + visitsController: Ember.inject.controller('visits'), + + physicianList: Ember.computed.alias('visitsController.physicianList'), + + logoURL: Ember.computed.alias('visitsController.printHeader.value.logoURL'), + facilityName: Ember.computed.alias('visitsController.printHeader.value.facilityName'), + headerLine1: Ember.computed.alias('visitsController.printHeader.value.headerLine1'), + headerLine2: Ember.computed.alias('visitsController.printHeader.value.headerLine2'), + headerLine3: Ember.computed.alias('visitsController.printHeader.value.headerLine3'), + + diagnosisList: Ember.computed.alias('visitsController.diagnosisList'), + + additionalButtons: Ember.computed('model.{isNew}', function() { + let isNew = get(this, 'model.isNew'); + if (!isNew) { + return [{ + class: 'btn btn-primary on-white', + buttonAction: 'printReport', + buttonIcon: 'octicon octicon-check', + buttonText: 'Print' + }]; + } + }), + + updateCapability: 'add_report', + + beforeUpdate() { + return new Ember.RSVP.Promise((resolve) => { + let model = get(this, 'model'); + if (get(model, 'isNew')) { + if (get(this, 'model.visit.outPatient')) { + set(model, 'reportType', 'OPD Report'); + } else { + set(model, 'reportType', 'Discharge Report'); + } + } + resolve(); + }); + }, + + afterUpdate() { + let alertTitle = get(this, 'i18n').t('reports.titles.saved'); + let alertMessage = get(this, 'i18n').t('reports.messages.saved'); + this.saveVisitIfNeeded(alertTitle, alertMessage); + let opdTitle = get(this, 'i18n').t('reports.opd.titles.edit'); + let dischargeTitle = get(this, 'i18n').t('reports.discharge.titles.edit'); + let editTitle = get(this, 'model.visit.outPatient') ? opdTitle : dischargeTitle; + let sectionDetails = {}; + sectionDetails.currentScreenTitle = editTitle; + this.send('setSectionHeader', sectionDetails); + }, + + actions: { + printReport() { + window.print(); + } + } + +}); diff --git a/app/reports/edit/route.js b/app/reports/edit/route.js new file mode 100644 index 0000000000..f5277f0748 --- /dev/null +++ b/app/reports/edit/route.js @@ -0,0 +1,68 @@ +import AbstractEditRoute from 'hospitalrun/routes/abstract-edit-route'; +import AddToPatientRoute from 'hospitalrun/mixins/add-to-patient-route'; +import Ember from 'ember'; +import { translationMacro as t } from 'ember-i18n'; +import PatientVisits from 'hospitalrun/mixins/patient-visits'; + +const { + get, + set +} = Ember; + +export default AbstractEditRoute.extend(AddToPatientRoute, PatientVisits, { + modelName: 'report', + customForms: Ember.inject.service(), + + getNewData() { + let newReportData = { + reportDate: new Date(), + customForms: Ember.Object.create() + }; + let customForms = get(this, 'customForms'); + return customForms.setDefaultCustomForms(['opdReport', 'dischargeReport'], newReportData); + }, + + getScreenTitle(model) { + let state = get(model, 'isNew') ? 'new' : 'edit'; + let type = get(model, 'visit.outPatient') ? 'opd' : 'discharge'; + return t(`reports.${type}.titles.${state}`); + }, + + getDiagnosisContainer(visit) { + if (get(visit, 'outPatient')) { + return visit; + } + return null; + }, + + getCurrentOperativePlan(patient) { + let operativePlans = get(patient, 'operativePlans'); + return operativePlans.findBy('isPlanned', true); + }, + + afterModel(model) { + if (!get(model, 'isNew')) { + let patient = get(model, 'visit.patient'); + set(model, 'patient', patient); + } + if (!get(model, 'visit')) { + return this.transitionTo('patients'); + } + }, + + setupController(controller, model) { + this._super(controller, model); + let visit = get(model, 'visit'); + let patient = get(model, 'patient'); + let isOutPatient = get(visit, 'outPatient'); + set(controller, 'visit', visit); + set(controller, 'isOutPatient', isOutPatient); + set(controller, 'diagnosisContainer', this.getDiagnosisContainer(visit)); + set(controller, 'currentOperativePlan', this.getCurrentOperativePlan(patient)); + if (isOutPatient) { + set(controller, 'nextAppointments', this.getPatientFutureAppointment(visit, true)); + } else { + set(controller, 'nextAppointment', this.getPatientFutureAppointment(visit)); + } + } +}); diff --git a/app/reports/edit/template.hbs b/app/reports/edit/template.hbs new file mode 100644 index 0000000000..00000bc153 --- /dev/null +++ b/app/reports/edit/template.hbs @@ -0,0 +1,204 @@ +
+ {{#if logoURL}} + + {{/if}} + {{#if facilityName }} +

{{facilityName}}

+ {{/if}} + {{#if headerLine1}} + {{headerLine1}}
+ {{/if}} + {{#if headerLine2}} + {{headerLine2}}
+ {{/if}} + {{#if headerLine3}} + {{headerLine3}}
+ {{/if}} + {{#if isOutPatient}} +

{{t 'reports.titles.opdTitle' }}

+ {{else}} +

{{t 'reports.titles.dischargeReport' }}

+ {{/if}} +
+
+ {{#edit-panel editPanelProps=editPanelProps}} + {{#em-form model=model submitButton=false }} + {{#if model.patient}} + {{patient-summary + patient=model.patient + disablePatientLink=true + diagnosisContainer=diagnosisContainer + diagnosisList=diagnosisList + }} + {{/if}} + +
+ + {{date-format visit.startDate format="DD/MM/YYYY"}} +
+ + {{#if isOutPatient}} + + {{#if visit.procedures}} +
+ +
    + {{#each visit.procedures as |item|}} +
  • {{item.description}}
  • + {{/each}} +
+
+ {{/if}} + + {{#if visit.labs}} +
+ +
    + {{#each visit.labs as |item|}} +
  • {{item.labType.name}}-{{item.result}} ({{item.status}})
  • + {{/each}} +
+
+ {{/if}} + + {{#if visit.imaging}} +
+ +
    + {{#each visit.imaging as |item|}} +
  • {{item.imagingType.name}}-{{item.result}} ({{item.status}})
  • + {{/each}} +
+
+ {{/if}} + + {{#if visit.medication}} +
+ +
    + {{#each visit.medication as |medication|}} +
  • {{medication.medicationName}} - {{medication.prescription}} ({{medication.status}})
  • + {{/each}} +
+
+ {{/if}} + + {{#if nextAppointments}} +
+ +
    + {{#each nextAppointments as |appointment|}} +
  • + {{date-format appointment.startDate format="DD/MM/YYYY hh:mm a"}} + {{#if appointment.appointmentType}} + ({{appointment.appointmentType}}) + {{/if}} +
  • + {{/each}} +
+
+ {{/if}} + + {{#if currentOperativePlan}} +
+ +
    + {{#if currentOperativePlan.operationDescription}} +
  • +

    {{t 'reports.form.operativePlan.description' }}:
    + {{currentOperativePlan.operationDescription}}

    +
  • + {{/if}} + +
  • +

    {{t 'reports.form.operativePlan.procedures' }}:

    +
      + {{#each currentOperativePlan.procedures as |procedure|}} +
    • {{procedure.description}}
    • + {{/each}} +
    +
  • + + {{#if currentOperativePlan.admissionInstructions}} +
  • +

    {{t 'reports.form.operativePlan.instructions' }}:
    + {{currentOperativePlan.admissionInstructions}}

    +
  • + {{/if}} +
+
+ {{/if}} + + {{custom-form-manager model=model formType="opdReport"}} + + {{else}} + +
+ + {{date-format model.reportDate format="DD/MM/YYYY"}} +
+ +
+ {{select-or-typeahead className="col-sm-4" property="surgeon" + label=(t "operativePlan.labels.surgeon") list=physicianList + selection=model.surgeon + class="plan-surgeon" + }} +
+ + {{#if patientProcedures}} + + + {{/if}} + + {{#if visit.patientNotes}} +
+ +
    + {{#each visit.patientNotes as |note|}} +
  • + {{t 'reports.form.notes.date' }}: {{date-format note.date}}
    + {{t 'reports.form.notes.author' }}: {{note.authoredBy}}
    + {{note.noteType}}: {{note.content}} +
  • + {{/each}} +
+
+ {{/if}} + + {{#if nextAppointment}} +
+ + {{date-format nextAppointment.startDate format="DD/MM/YYYY hh:mm a"}} + {{#if nextAppointment.location}} + ({{nextAppointment.location}}) + {{/if}} +
+ {{/if}} + + {{custom-form-manager model=model formType="dischargeReport"}} + + {{#if model.modifiedBy}} +
+ + {{model.modifiedBy}} +
+ {{/if}} + + {{/if}} + {{/em-form}} + {{/edit-panel}} +
diff --git a/app/router.js b/app/router.js index 37f4489015..d59b63baa5 100755 --- a/app/router.js +++ b/app/router.js @@ -115,6 +115,13 @@ Router.map(function() { }, function() { this.route('edit', { path: '/edit/:procedure_id' }); }); + + this.route('reports', { + resetNamespace: true + }, function() { + this.route('edit', { path: '/edit/:report_id' }); + }); + }); }); diff --git a/app/services/database.js b/app/services/database.js index e04e25c424..249bb14388 100644 --- a/app/services/database.js +++ b/app/services/database.js @@ -3,8 +3,10 @@ import createPouchViews from 'hospitalrun/utils/pouch-views'; import List from 'npm:pouchdb-list'; import PouchAdapterMemory from 'npm:pouchdb-adapter-memory'; import UnauthorizedError from 'hospitalrun/utils/unauthorized-error'; +import enviroment from './../config/environment'; const { + get, isEmpty } = Ember; @@ -20,9 +22,47 @@ export default Ember.Service.extend({ .then((db) => { this.set('mainDB', db); this.set('setMainDB', true); + }) + .then(() => { + this.createOptionHeader([enviroment.hospitalInfoDoc]); }); }, + createOptionHeader(docs) { + return new Ember.RSVP.Promise((resolve, reject) => { + let mainDB = get(this, 'mainDB'); + let ids = docs.map((doc) => { + return doc._id; + }); + return mainDB.allDocs({ keys: ids }) + .then((res) => { + if (res.rows) { + let docsToCreate = res.rows.filter((row) => { + return (row.error && row.error === 'not_found') || (row.value && row.value.deleted); + }).map((row) => { + return docs.find((doc) => { + if (doc._id === row.key) { + if (row.value && row.value.deleted && row.value.rev !== doc._rev) { + doc._rev = row.value.rev; + } + return true; + } + }); + }); + if (docsToCreate.length) { + return mainDB.bulkDocs(docsToCreate); + } + } + }) + .then((res) => { + resolve(res); + }) + .catch((err) => { + reject(err); + }); + }); + }, + createDB(configs) { return new Ember.RSVP.Promise((resolve, reject) => { let pouchOptions = {}; diff --git a/app/styles/_print.scss b/app/styles/_print.scss index 2816743972..750c1e367c 100644 --- a/app/styles/_print.scss +++ b/app/styles/_print.scss @@ -1,4 +1,4 @@ -.print-section { +.report-header { display: none; } @@ -17,8 +17,10 @@ width: 90%; } - .print-section { + .report-header { display: block; + border-bottom: 1px solid #d2dae3; + padding: 10px 0; text-align: center; } @@ -32,4 +34,46 @@ .patient-history-heading { border-bottom: 1px solid $black; } + + .report-logo { + height: 60px; + } + + .report .patient-summary { + margin-bottom: 20px; + border-bottom: 1px solid #d2dae3; + } + + .report input { + border: 0; + padding: 5px; + font-size: .8em; + } + + .report select { + outline: 0; + border: 0; + text-overflow: ''; + -moz-appearance: none; + -webkit-appearance: none; + } + + .report .panel-primary { + position: absolute; + left: 0; + margin: 0; + width: 100%; + } + + .print-hide { + display: none; + } + + .view-top-bar { + display: none; + } + + .view-sub-bar { + display: none; + } } diff --git a/app/templates/section.hbs b/app/templates/section.hbs index 7deaed121b..7c89d7f305 100644 --- a/app/templates/section.hbs +++ b/app/templates/section.hbs @@ -2,7 +2,6 @@

{{currentScreenTitle}}

-
{{#if newButtonAction}} diff --git a/app/users/edit/controller.js b/app/users/edit/controller.js index b2d0733f2e..bac2f26798 100644 --- a/app/users/edit/controller.js +++ b/app/users/edit/controller.js @@ -3,6 +3,10 @@ import Ember from 'ember'; import UserRoles from 'hospitalrun/mixins/user-roles'; import uuid from 'npm:uuid'; +const { + get +} = Ember; + export default AbstractEditController.extend(UserRoles, { usersController: Ember.inject.controller('users/index'), updateCapability: 'add_user', @@ -39,7 +43,11 @@ export default AbstractEditController.extend(UserRoles, { updateModel.set('userPrefix', prefix); } updateModel.save().then(() => { - this.displayAlert(this.get('i18n').t('messages.userSaved'), this.get('i18n').t('messages.userHasBeenSaved')); + this.displayAlert(get(this, 'i18n').t('messages.userSaved'), get(this, 'i18n').t('messages.userHasBeenSaved')); + let editTitle = get(this, 'i18n').t('labels.editUser'); + let sectionDetails = {}; + sectionDetails.currentScreenTitle = editTitle; + this.send('setSectionHeader', sectionDetails); }).catch((error) => this.send('error', error)); } } diff --git a/app/utils/pouch-views.js b/app/utils/pouch-views.js index 4f229dccf3..845bf55312 100644 --- a/app/utils/pouch-views.js +++ b/app/utils/pouch-views.js @@ -477,6 +477,12 @@ let designDocs = [{ emit([doc.data.patient, startDate, endDate, doc.data.visitType, doc._id]);` ), version: 4 +}, { + name: 'report_by_visit', + function: generateView('report', + 'emit(doc.data.visit);' + ), + version: 1 }]; export default function(db, runningTest, testDumpFile) { diff --git a/app/visits/edit/controller.js b/app/visits/edit/controller.js index fc17787684..f6a1db76f7 100644 --- a/app/visits/edit/controller.js +++ b/app/visits/edit/controller.js @@ -37,6 +37,8 @@ export default AbstractEditController.extend(AddNewPatient, ChargeActions, Diagn } }), + noReport: false, + canAddAppointment: computed('model.isNew', function() { return (!this.get('model.isNew') && this.currentUserCan('add_appointment')); }), @@ -65,6 +67,10 @@ export default AbstractEditController.extend(AddNewPatient, ChargeActions, Diagn return this.currentUserCan('add_vitals'); }.property(), + canAddReport: computed('hasReport', function() { + return this.currentUserCan('add_report') && !this.get('hasReport'); + }), + canDeleteImaging: function() { return this.currentUserCan('delete_imaging'); }.property(), @@ -85,6 +91,10 @@ export default AbstractEditController.extend(AddNewPatient, ChargeActions, Diagn return this.currentUserCan('delete_vitals'); }.property(), + canDeleteReport: function() { + return this.currentUserCan('delete_report'); + }.property(), + isAdmissionVisit: function() { let visitType = this.get('model.visitType'); let isAdmission = (visitType === 'Admission'); @@ -162,17 +172,16 @@ export default AbstractEditController.extend(AddNewPatient, ChargeActions, Diagn }.property('visitTypes', 'model.outPatient'), _addChildObject(route, afterTransition) { - this.transitionToRoute(route, 'new').then(function(newRoute) { - newRoute.currentModel.setProperties({ - patient: this.get('model.patient'), - visit: this.get('model'), - selectPatient: false, - returnToVisit: this.get('model.id') - }); + let options = { + queryParams: { + forVisitId: this.get('model.id') + } + }; + this.transitionToRoute(route, 'new', options).then((newRoute) => { if (afterTransition) { afterTransition(newRoute); } - }.bind(this)); + }); }, _finishAfterUpdate() { @@ -398,6 +407,11 @@ export default AbstractEditController.extend(AddNewPatient, ChargeActions, Diagn } }, + editReport(report) { + set(report, 'returnToVisit', get(this, 'model.id')); + this.transitionToRoute('reports.edit', report); + }, + newPatientChanged(createNewPatient) { set(this, 'model.createNewPatient', createNewPatient); let model = this.get('model'); @@ -443,6 +457,10 @@ export default AbstractEditController.extend(AddNewPatient, ChargeActions, Diagn this._addChildObject('medication.edit'); }, + newReport() { + this._addChildObject('reports.edit'); + }, + showAddProcedure() { this._addChildObject('procedures.edit'); }, diff --git a/app/visits/edit/route.js b/app/visits/edit/route.js index d49288ce9b..8a275b614c 100644 --- a/app/visits/edit/route.js +++ b/app/visits/edit/route.js @@ -3,8 +3,16 @@ import AbstractEditRoute from 'hospitalrun/routes/abstract-edit-route'; import ChargeRoute from 'hospitalrun/mixins/charge-route'; import Ember from 'ember'; import PatientListRoute from 'hospitalrun/mixins/patient-list-route'; +import PatientVisit from 'hospitalrun/mixins/patient-visits'; +import DS from 'ember-data'; -export default AbstractEditRoute.extend(ChargeRoute, PatientListRoute, { +const { + get, + set, + isEmpty +} = Ember; + +export default AbstractEditRoute.extend(ChargeRoute, PatientListRoute, PatientVisit, { customForms: Ember.inject.service(), editTitle: t('visits.titles.editVisit'), modelName: 'visit', @@ -12,11 +20,11 @@ export default AbstractEditRoute.extend(ChargeRoute, PatientListRoute, { pricingCategory: 'Ward', model(params) { - let idParam = this.get('idParam'); - if (!Ember.isEmpty(idParam) && params[idParam] === 'checkin') { + let idParam = get(this, 'idParam'); + if (!isEmpty(idParam) && params[idParam] === 'checkin') { return this.getNewData().then((newData) => { newData.checkIn = true; - let newVisit = this.get('store').createRecord('visit', newData); + let newVisit = get(this, 'store').createRecord('visit', newData); return newVisit; }); } else { @@ -42,6 +50,20 @@ export default AbstractEditRoute.extend(ChargeRoute, PatientListRoute, { } }, + setupController(controller, model) { + let promise = this.store.query('report', { + options: { + key: get(model, 'id') + }, + mapReduce: 'report_by_visit' + }).then((reports) => { + set(controller, 'noReport', isEmpty(reports)); + return isEmpty(reports) ? '' : get(reports, 'firstObject'); + }); + set(controller, 'report', DS.PromiseObject.create({ promise })); + this._super(controller, model); + }, + actions: { updateNote() { this.controller.send('update', true); diff --git a/app/visits/edit/template.hbs b/app/visits/edit/template.hbs index 49d6331c14..1837d98fa1 100644 --- a/app/visits/edit/template.hbs +++ b/app/visits/edit/template.hbs @@ -129,6 +129,11 @@ {{t 'visits.navigation.charges'}} +
  • + + {{t 'visits.navigation.reports'}} + +
  • @@ -162,7 +167,7 @@
    @@ -196,7 +201,7 @@
    @@ -257,6 +262,48 @@ {{partial 'item-charges'}} {{/em-form}}
    +
    +
    + {{#if noReport}} + +
    + {{#if canAddReport}} + + {{/if}} +
    + + {{else}} + +
    + + + + + + + + + + + + + + +
    {{t 'labels.date'}}{{t 'visits.labels.authoredBy'}}{{t 'visits.labels.reportType'}}{{t 'labels.actions'}}
    {{date-format report.reportDate}}{{report.modifiedBy}}{{report.reportType}} + +
    +
    + + {{/if}} + +
    +
    {{/unless}} diff --git a/app/visits/route.js b/app/visits/route.js index d397fb2d74..3c66313539 100644 --- a/app/visits/route.js +++ b/app/visits/route.js @@ -37,6 +37,9 @@ export default AbstractModuleRoute.extend({ }, { name: 'wardPricingTypes', findArgs: ['lookup', 'ward_pricing_types'] + }, { + name: 'printHeader', + findArgs: ['option', 'print_header'] }], moduleName: 'visits', newButtonAction: null, // No new button diff --git a/config/environment.js b/config/environment.js index 54cd1a20c6..b84301428c 100644 --- a/config/environment.js +++ b/config/environment.js @@ -74,5 +74,18 @@ module.exports = function(environment) { schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source' }; + ENV.hospitalInfoDoc = { + "_id": "option_2_print_header", + "_rev": "1-4457555eacb405267c6d3b7a53d8521d", + "data": { + "value": { + "facilityName": "Beit CURE International Hospital", + "headerLine1": "PO Box 31236", + "headerLine2": "Blantyre 3", + "headerLine3": "+265 (0) 1 871 900 / +265 (0) 1 875 015 /+265 (0) 1 873 694 / +265 (0) 999 505 212", + "logoURL": "https://curehospital.mw/wp-content/uploads/4/2012/11/CURE-Malawi-Logo_rgb_280_89.jpg" + } + } + }; return ENV; }; diff --git a/tests/acceptance/users-test.js b/tests/acceptance/users-test.js index 8c1060087a..2fe3d4e189 100644 --- a/tests/acceptance/users-test.js +++ b/tests/acceptance/users-test.js @@ -126,6 +126,7 @@ test('create new user', function(assert) { waitToAppear('.modal-dialog'); andThen(() => { assert.equal(find('.modal-title').text(), 'User Saved', 'User was saved successfully'); + assert.equal(find('.view-current-title').text(), 'Edit User', 'Page title changed to Edit User'); }); click('button:contains(Ok)'); }); diff --git a/tests/acceptance/visit-test.js b/tests/acceptance/visit-test.js index 29dfdc895c..a572b75ea0 100644 --- a/tests/acceptance/visit-test.js +++ b/tests/acceptance/visit-test.js @@ -1,7 +1,29 @@ import Ember from 'ember'; +import moment from 'moment'; import { module, test } from 'qunit'; import startApp from 'hospitalrun/tests/helpers/start-app'; +const { isEmpty } = Ember; + +const visitData = { + outPatient: { + PRIMARY_DIAGNOSIS: 'ACL deficient knee, right', + SECONDARY_DIAGNOSIS: 'ACL deficient knee, left', + OPERATION_DESCRIPTION: 'Describe Operation here', + PROCEDURE_SPLINT: 'application of long arm post splint', + ADMISSION_INSTRUCTIONS: 'Admission Instructions here', + OPD_PROCEDURE_DESCRIPTION: 'Bilateral knee Release', + OPD_PROCEDURE_PHYSICIAN: 'Sarah Kearney', + LAB_TYPE: 'Cholesterol', + IMAGING_TYPE: 'Cervical Spine AP-L', + APPOINTMENT_START_DATE: moment().add(7, 'days').format('l h:mm A'), + APPOINTMENT_END_DATE: moment().add(8, 'days').format('l h:mm A') + }, + admission: { + NOTE_CONTENT: 'Patient notes are entered here' + } +}; + module('Acceptance | visits', { beforeEach() { this.application = startApp(); @@ -12,27 +34,27 @@ module('Acceptance | visits', { } }); -test('Add visit', function(assert) { +test('Add admission visit', function(assert) { runWithPouchDump('patient', function() { authenticateUser(); - visit('/patients'); - andThen(function() { - assert.equal(currentURL(), '/patients', 'Patient url is correct'); - click('button:contains(Edit)'); - }); - andThen(function() { - assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Joe Bagadonuts patient record displays'); - click('[data-test-selector=visits-tab]'); - waitToAppear('#visits button:contains(Edit)'); // Make sure visits have been retrieved. - }); - andThen(function() { - click('#visits button:contains(New Visit)'); - waitToAppear('#visit-info'); - }); - andThen(function() { - assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Joe Bagadonuts displays as patient for visit'); - updateVisit(assert, 'Add'); - }); + addVisit(assert); + addAdmissionData(assert); + newReport(assert, 'Discharge'); + checkDischargeReport(assert); + saveReport(assert, 'Discharge'); + editReport(assert, 'Discharge'); + }); +}); + +test('Add OPD visit', function(assert) { + runWithPouchDump('patient', function() { + authenticateUser(); + addVisit(assert, 'Clinic'); + addOutpatientData(assert); + newReport(assert, 'OPD'); + checkOPDReport(assert); + saveReport(assert, 'OPD'); + editReport(assert, 'OPD'); }); }); @@ -68,7 +90,7 @@ test('Edit visit', function(assert) { click('button:contains(New Medication)'); }); andThen(function() { - assert.equal(currentURL(), '/medication/edit/new', 'New medication url is correct'); + assert.equal(currentURL(), '/medication/edit/new?forVisitId=03C7BF8B-04E0-DD9E-9469-96A5604F5340', 'New medication url is correct'); assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'New medication prepopulates with patient'); click('button:contains(Cancel)'); }); @@ -76,7 +98,7 @@ test('Edit visit', function(assert) { click('button:contains(New Lab)'); }); andThen(function() { - assert.equal(currentURL(), '/labs/edit/new', 'New lab url is correct'); + assert.equal(currentURL(), '/labs/edit/new?forVisitId=03C7BF8B-04E0-DD9E-9469-96A5604F5340', 'New lab url is correct'); assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'New lab prepopulates with patient'); click('button:contains(Cancel)'); }); @@ -84,7 +106,7 @@ test('Edit visit', function(assert) { click('button:contains(New Imaging)'); }); andThen(function() { - assert.equal(currentURL(), '/imaging/edit/new', 'New imaging url is correct'); + assert.equal(currentURL(), '/imaging/edit/new?forVisitId=03C7BF8B-04E0-DD9E-9469-96A5604F5340', 'New imaging url is correct'); assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'New imaging prepopulates with patient'); click('button:contains(Cancel)'); }); @@ -196,8 +218,246 @@ test('Delete visit', function(assert) { }); }); -function updateVisit(assert, buttonText) { +function addVisit(assert, type) { + visit('/patients'); + andThen(function() { + assert.equal(currentURL(), '/patients', 'Patient url is correct'); + click('button:contains(Edit)'); + }); + andThen(function() { + assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Joe Bagadonuts patient record displays'); + click('[data-test-selector=visits-tab]'); + waitToAppear('#visits button:contains(Edit)'); + }); + andThen(function() { + click('#visits button:contains(New Visit)'); + waitToAppear('#visit-info'); + }); + andThen(function() { + assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Joe Bagadonuts displays as patient for visit'); + updateVisit(assert, 'Add', type); + }); + +} + +function addOutpatientData(assert) { + andThen(() =>{ + click('a:contains(Add Diagnosis)'); + waitToAppear('.modal-dialog'); + }); + andThen(() => { + assert.equal(find('.modal-title').text(), 'Add Diagnosis', 'Add Diagnosis dialog displays'); + fillIn('.diagnosis-text input', visitData.outPatient.PRIMARY_DIAGNOSIS); + click('.modal-footer button:contains(Add)'); + }); + andThen(function() { + waitToDisappear('.modal-dialog'); + }); + andThen(() => { + click('a:contains(Add Diagnosis)'); + waitToAppear('.modal-dialog'); + }); + andThen(() => { + assert.equal(find('.modal-title').text(), 'Add Diagnosis', 'Add Diagnosis dialog displays'); + fillIn('.diagnosis-text input', visitData.outPatient.SECONDARY_DIAGNOSIS); + click('.secondary-diagnosis input'); + click('.modal-footer button:contains(Add)'); + }); + andThen(function() { + waitToDisappear('.modal-dialog'); + }); + andThen(() => { + click('a:contains(Add Operative Plan)'); + }); + andThen(() => { + assert.ok(currentURL().indexOf('/patients/operative-plan/new?forVisitId') > -1, 'New operative plan URL is visited'); + assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Joe Bagadonuts patient header displays'); + assert.equal(find('.view-current-title').text(), 'New Operative Plan', 'New operative plan title is correct'); + fillIn('.operation-description textarea', visitData.outPatient.OPERATION_DESCRIPTION); + typeAheadFillIn('.procedure-description', visitData.outPatient.PROCEDURE_SPLINT); + click('button:contains(Add Procedure)'); + waitToAppear('.procedure-listing td.procedure-description'); + fillIn('.admission-instructions textarea', visitData.outPatient.ADMISSION_INSTRUCTIONS); + }); + updateVisitData(assert, 'Plan Saved'); + andThen(() => { + click('[data-test-selector=procedures-tab]'); + waitToAppear('[data-test-selector=new-procedure-btn]'); + assert.equal(find('[data-test-selector=new-procedure-btn]').text().trim(), 'New Procedure', 'New Procedure button displayed'); + click('[data-test-selector=new-procedure-btn]'); + }); + andThen(() => { + assert.ok(currentURL().indexOf('visits/procedures/edit/new?forVisitId') > -1, 'New Procedures URL is visited'); + typeAheadFillIn('.procedure-description', visitData.outPatient.OPD_PROCEDURE_DESCRIPTION); + typeAheadFillIn('.procedure-physician', visitData.outPatient.OPD_PROCEDURE_PHYSICIAN); + }); + updateVisitData(assert, 'Procedure Saved'); + andThen(() => { + click('button:contains(New Lab)'); + }); + andThen(() => { + assert.ok(currentURL().indexOf('/labs/edit/new?forVisitId') > -1, 'New Lab URL is visited'); + typeAheadFillIn('.test-lab-type', visitData.outPatient.LAB_TYPE); + }); + updateVisitData(assert, 'Lab Request Saved'); + andThen(() => { + click('button:contains(New Imaging)'); + }); + andThen(() => { + assert.ok(currentURL().indexOf('/imaging/edit/new?forVisitId') > -1, 'New Imaging URL is visited'); + typeAheadFillIn('.imaging-type-input', visitData.outPatient.IMAGING_TYPE); + }); + updateVisitData(assert, 'Imaging Request Saved'); + andThen(() => { + click('button:contains(New Appointment)'); + }); + andThen(() => { + assert.ok(currentURL().indexOf('/appointments/edit/new?forVisitId') > -1, 'New Appointment URL is visited'); + click('.appointment-all-day input'); + fillIn('.test-appointment-start input', visitData.outPatient.APPOINTMENT_START_DATE); + fillIn('.test-appointment-end input', visitData.outPatient.APPOINTMENT_END_DATE); + }); + updateVisitData(assert, 'Appointment Saved'); +} + +function addAdmissionData(assert) { + andThen(function() { + click('[data-test-selector=notes-tab]'); + waitToAppear('[data-test-selector=new-note-btn]'); + click('[data-test-selector=new-note-btn]'); + }); + andThen(() => { + assert.equal(find('.modal-title').text(), 'New Note for Joe Bagadonuts', 'New Note dialog displays'); + fillIn('.test-note-content textarea', visitData.admission.NOTE_CONTENT); + click('.modal-footer button:contains(Add)'); + }); + andThen(function() { + waitToDisappear('.modal-dialog'); + }); + andThen(() => { + assert.ok(currentURL().indexOf('visits/edit/') > -1, 'Returns back to visit URL'); + }); +} + +function newReport(assert, type) { + andThen(function() { + click('[data-test-selector=reports-tab]'); + waitToAppear('[data-test-selector=report-btn]'); + assert.equal(find('[data-test-selector=report-btn]').text().trim(), `New ${type} Report`, 'Discharge report can be created for this type of visit'); + click('[data-test-selector=report-btn]'); + }); + andThen(function() { + assert.ok(currentURL().indexOf('visits/reports/edit/new') > -1, 'Report url is correct'); + assert.equal(find('.view-current-title').text(), `New ${type} Report`, `${type} report title displayed correctly`); + assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Patient record displays'); + }); +} + +function checkOPDReport(assert) { + andThen(() => { + assert.equal(find('.patient-id .ps-info-data').text(), 'P00001', 'Patient ID is displayed'); + assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Patient First Name & Last Name is displayed'); + assert.equal(find('.test-visit-date .test-visit-date-label').text().trim(), 'Date of Visit:', 'Visit date label displayed'); + assert.ok(!isEmpty(find('.test-visit-date .test-visit-date-data').text()), 'Visit date is displayed'); + findWithAssert(`.primary-diagnosis:contains(${visitData.outPatient.PRIMARY_DIAGNOSIS})`); + findWithAssert(`.secondary-diagnosis:contains(${visitData.outPatient.SECONDARY_DIAGNOSIS})`); + findWithAssert('.test-opd-procedure .test-opd-procedure-label:contains(Procedures)'); + assert.ok(find('.test-opd-procedure .test-opd-procedure-data').text().indexOf(visitData.outPatient.OPD_PROCEDURE_DESCRIPTION) > -1, 'OPD Procedure is displayed'); + findWithAssert('.test-labs .test-labs-label:contains(Labs)'); + assert.ok(find('.test-labs .test-labs-data').text().indexOf(visitData.outPatient.LAB_TYPE) > -1, 'Lab request is displayed'); + findWithAssert('.test-images .test-images-label:contains(Images)'); + assert.ok(find('.test-images .test-images-data').text().indexOf(visitData.outPatient.IMAGING_TYPE) > -1, 'Image request is displayed'); + findWithAssert('.test-operative-plan .test-operative-plan-label:contains(Operative Plan)'); + findWithAssert('.test-operative-plan .test-operative-plan-description-label:contains(Operation Description:)'); + assert.equal(find('.test-operative-plan .test-operative-plan-description-data').text(), visitData.outPatient.OPERATION_DESCRIPTION); + findWithAssert('.test-operative-plan .test-operative-plan-procedures-label:contains(Planned Procedures:)'); + assert.equal(find('.test-operative-plan .test-operative-plan-procedures-description').text(), visitData.outPatient.PROCEDURE_SPLINT); + findWithAssert('.test-operative-plan .test-operative-plan-instructions-label:contains(Instructions upon Admission:)'); + assert.equal(find('.test-operative-plan .test-operative-plan-instructions-data').text(), visitData.outPatient.ADMISSION_INSTRUCTIONS, 'Admission Instruction is displayed'); + }); +} + +function checkDischargeReport(assert) { + andThen(function() { + assert.equal(find('.test-visit-date .test-visit-date-label').text().trim(), 'Admission Date:', 'Visit date label displays as admission'); + assert.equal(find('.test-visit-date .test-visit-discharge-date-label').text().trim(), 'Discharge Date:', 'Discharge date label displays'); + findWithAssert('.test-notes .test-notes-label:contains(Notes)'); + assert.ok(find('.test-notes .test-notes-data').text().indexOf(visitData.admission.NOTE_CONTENT) > -1, 'Notes are displayed'); + }); + andThen(function() { + click('.panel-footer button:contains(Add)'); + waitToAppear('.modal-dialog'); + }); + andThen(function() { + assert.equal(find('.modal-title').text(), 'Warning!!!!', 'Cant save discharge report without entering doctors name'); + click('button:contains(Ok)'); + waitToDisappear('.modal-dialog'); + }); + andThen(function() { + typeAheadFillIn('.plan-surgeon', 'Dr Test'); + }); +} + +function saveReport(assert, type) { + andThen(function() { + click('.panel-footer button:contains(Add)'); + waitToAppear('.modal-dialog'); + }); + andThen(function() { + assert.equal(find('.modal-title').text(), 'Report saved', `${type} report saved successfully`); + click('button:contains(Ok)'); + waitToDisappear('.modal-dialog'); + }); + andThen(function() { + assert.equal(find('.view-current-title').text(), `Edit ${type} Report`, 'Report title updated correctly'); + assert.ok(find('.panel-footer button:contains(Print)').is(':visible'), 'Print button is now visible'); + click('button:contains(Return)'); + }); + andThen(function() { + assert.ok(currentURL().indexOf('visits/edit/') > -1, 'Visit url is correct'); + }); +} + +function editReport(assert, type) { + andThen(function() { + click('[data-test-selector=reports-tab]'); + waitToAppear('[data-test-selector=edit-report-btn]'); + click('[data-test-selector=edit-report-btn]'); + }); + andThen(function() { + assert.ok(currentURL().indexOf('visits/reports/edit') > -1, 'Edit report url is correct'); + assert.equal(find('.patient-name .ps-info-data').text(), 'Joe Bagadonuts', 'Patient record displays'); + assert.equal(find('.view-current-title').text(), `Edit ${type} Report`, 'Edit report title displayed correctly'); + assert.ok(find('.panel-footer button:contains(Print)').is(':visible'), 'Print button is on edit visible'); + }); +} + +function updateVisitData(assert, modalTitle) { + andThen(() => { + click('.panel-footer button:contains(Add)'); + waitToAppear('.modal-dialog'); + }); + andThen(() => { + assert.equal(find('.modal-title').text(), modalTitle, `${modalTitle} modal displays`); + click('.modal-footer button:contains(Ok)'); + }); + andThen(function() { + waitToDisappear('.modal-dialog'); + }); + andThen(() => { + click('button:contains(Return)'); + }); + andThen(() => { + assert.ok(currentURL().indexOf('visits/edit/') > -1, 'Returns back to visit URL'); + }); +} + +function updateVisit(assert, buttonText, visitType) { andThen(function() { + if (visitType) { + select('select[id*="visitType"]', visitType); + waitToDisappear('label[for*="display_endDate"]'); + } click(`.panel-footer button:contains(${buttonText})`); waitToAppear('.modal-dialog'); }); diff --git a/tests/unit/mixins/patient-diagnosis-test.js b/tests/unit/mixins/patient-diagnosis-test.js index 8001771292..11fdf1714e 100644 --- a/tests/unit/mixins/patient-diagnosis-test.js +++ b/tests/unit/mixins/patient-diagnosis-test.js @@ -22,6 +22,7 @@ moduleFor('mixin:patient-diagnosis', 'Unit | Mixin | patient-diagnosis', { 'model:patient-note', 'model:procedure', 'model:diagnosis', + 'model:report', 'ember-validations@validator:local/acceptance', 'ember-validations@validator:local/presence' ], diff --git a/tests/unit/models/visit-test.js b/tests/unit/models/visit-test.js index 601379d135..50db6d0be0 100644 --- a/tests/unit/models/visit-test.js +++ b/tests/unit/models/visit-test.js @@ -16,6 +16,7 @@ moduleForModel('visit', 'Unit | Model | visit', { 'model:proc-charge', 'model:vital', 'model:visit', + 'model:report', 'service:validations' ] });