diff --git a/elcid/__init__.py b/elcid/__init__.py index f81e7a146..36585c610 100644 --- a/elcid/__init__.py +++ b/elcid/__init__.py @@ -17,6 +17,7 @@ class Application(application.OpalApplication): 'js/elcid/controllers/clinical_advice_form.js', 'js/elcid/controllers/result_view.js', 'js/elcid/controllers/bloodculture_pathway_form.js', + 'js/elcid/controllers/tagging_step.js', 'js/elcid/services/blood_culture_helper.js', 'js/elcid/services/dicharge_patient.js', 'js/elcid/services/blood_culture_record.js', diff --git a/elcid/assets/js/elcid/controllers/diagnosis_discharge.js b/elcid/assets/js/elcid/controllers/diagnosis_discharge.js deleted file mode 100644 index 24a203c33..000000000 --- a/elcid/assets/js/elcid/controllers/diagnosis_discharge.js +++ /dev/null @@ -1,500 +0,0 @@ -// -// This is the controller for elCID episodes that have a -// presenting complaint/final diagnosis pair. -// -// We do the standard discharge, then ask some more questions. -// -controllers.controller( - 'DiagnosisDischargeCtrl', - function( - $scope, $rootScope, $modalInstance, $modal, $q, - $location, - growl, - Flow, - tags, options, episode, DischargePatientService){ - - $scope.tags = tags; - $scope.episode = episode; - $scope.saving = false; - - $scope.steps = [ - "diagnosis" - ]; - - $scope.steps_details = { - discharge: { - icon: "fa fa-home", - title: "Discharge", - subtitle: undefined, - done: false - }, - diagnosis: { - icon: "fa fa-stethoscope", - title: "Diagnosis", - subtitle: undefined, - status: 'disabled', - done: false - }, - presenting_complaint: { - icon: "fa fa-heartbeat", - title: "Presenting Complaint", - subtitle: "Please enter one or more symptoms", - done: false - }, - antimicrobial: { - icon: "fa fa-flask", - title: "Antimicrobial", - subtitle: "Please enter the drug name and the start and end dates or state that the patient was not on antimicrobials.", - done: false - }, - travel: { - icon: "fa fa-plane", - title: "Travel", - subtitle: "Please enter a travel destination and dates, or state that the patient did not travel.", - done: false - }, - consultant_at_discharge: { - icon: "fa fa-user-md", - title: "Consultant At Discharge", - subtitle: "Please record the consultant at discharge.", - done: false - } - }; - - - var dischargePatientService = new DischargePatientService(); - - /* - * a multi step model that acts a bit like a form controller for travel and - * antimicrobial - */ - var MultiStep = function(requiredFields, negationField, editing, episode, columnName){ - this.none = false; - this.warning = false; - - this.remove = function(index){ - editing[columnName].splice(index, 1); - }; - - this.getRequiredFields = function(antimicrobial){ - return _.map(requiredFields, function(r){ - return antimicrobial[r]; - }); - }; - - this.newItem = function(){ - return _.reduce(this.requiredFields, function(o, f){ - o[f] = undefined; - return o; - }, {}); - }; - - this.pristine = function(antimicrobial){ - return !_.some(this.getRequiredFields(antimicrobial)); - }; - - this.clear = function(){ - if(this.none){ - editing[columnName] = [this.newItem()]; - editing[columnName][0][negationField] = true; - } - this.warning = false; - }; - - this.validate = function(antimicrobial){ - var requiredAll = this.getRequiredFields(antimicrobial); - return _.every(requiredAll) || this.none; - }; - - // validates a whole step, e.g. all of the antimicrobial - this.validateStep = function(){ - var toReview = $scope.editing[columnName]; - - // if there's just an empty form at the end, lets ignore that - if(toReview > 1){ - if(this.pristine(toReview)){ - toReview = _.first(toReview, toReview.length-1); - } - } - - var invalidModels = _.filter(toReview, function(a){ - return !this.validate(a); - }, this); - - if(invalidModels.length){ - this.warning = true; - return false; - } - - return true; - }; - - this.addAnother = function(model){ - if(!this.validate(model)){ - this.warning=true; - } - else{ - if(!this.none){ - model.submitted=true; - var newModel = this.newItem(); - editing[columnName].push(newModel); - } - } - }; - - this.reset = function(){ - this.warning = false; - }; - - this.save = function(){ - saves = []; - - _.each($scope.editing[columnName], function(editingItem){ - delete editingItem.submitted; - delete editingItem.id; - if(!this.pristine(editingItem) || editingItem[negationField]){ - episodeItem = $scope.episode.newItem(columnName); - saves.push(episodeItem.save(editingItem)); - } - }, this); - - return saves; - }; - }; - - $scope.currentCategory = episode.location[0].category; - - $scope.editing = dischargePatientService.getEditing(episode); - - if(!$scope.episode.presenting_complaint.length || - !$scope.episode.presenting_complaint[0].symptoms || - !$scope.episode.presenting_complaint[0].symptoms.length - ){ - var presenting_complaint; - - if(!$scope.episode.presenting_complaint.length){ - presenting_complaint = $scope.episode.newItem('presenting_complaint'); - } - else{ - presenting_complaint = $scope.episode.presenting_complaint[0]; - } - - $scope.episode.presenting_complaint = [presenting_complaint]; - $scope.editing.presenting_complaint = presenting_complaint.makeCopy(); - $scope.editing.presenting_complaint.symptoms =[]; - $scope.steps.unshift("presenting_complaint"); - } - - if(!$scope.episode.antimicrobial.length){ - $scope.antimicrobialStep = new MultiStep( - ["drug", "start_date", "end_date"], - "no_antimicrobials", - $scope.editing, - $scope.episode, - "antimicrobial" - ); - $scope.editing.antimicrobial = [$scope.antimicrobialStep.newItem()]; - $scope.steps.push("antimicrobial"); - } - - if(!$scope.episode.travel.length){ - $scope.travelStep = new MultiStep( - ["dates", "destination"], - "did_not_travel", - $scope.editing, - $scope.episode, - "travel" - ); - - $scope.editing.travel = [$scope.travelStep.newItem()]; - - $scope.steps.push("travel"); - } - - $scope.editing.primary_diagnosis = $scope.episode.primary_diagnosis[0].makeCopy(); - - if($scope.is_list_view || !episode.isDischarged()){ - $scope.steps.push("discharge"); - } - - if($scope.episode.primary_diagnosis.length === 0){ - var primary = $scope.episode.newItem('primary_diagnosis'); - $scope.episode.primary_diagnosis[0] = primary; - } - - if(!$scope.episode.consultant_at_discharge[0].consultant){ - $scope.editing.consultant_at_discharge = $scope.episode.consultant_at_discharge[0].makeCopy(); - $scope.steps.push("consultant_at_discharge"); - } - - $scope.errors = _.reduce($scope.steps, function(mem, y){ - mem[y] = undefined; - return mem; - }, {}); - - $scope.processSteps = []; - - _.each($scope.steps, function(step){ - var processStep = $scope.steps_details[step]; - processStep.name = step; - $scope.processSteps.push(processStep); - }); - - $scope.nextStep = function(){ - var currentIndex = _.indexOf($scope.steps, $scope.step); - - if(currentIndex + 1 === $scope.steps.length){ - return null; - } - return $scope.steps[currentIndex + 1]; - }; - - $scope.previousStep = function(){ - var currentIndex = _.indexOf($scope.steps, $scope.step); - - if(!currentIndex){ - return null; - } - - return $scope.steps[currentIndex - 1]; - }; - - $scope.goToPreviousStep = function(){ - var processStep = _.find($scope.processSteps, function(processStep){ - return processStep.name === $scope.step; - }); - processStep.done = false; - $scope.step = $scope.previousStep(); - }; - - $scope.resetFormValidation = function(someForm){ - someForm.warning = false; - }; - - $scope.resetRequired = function(someFormField){ - someFormField.$setValidity("required", true); - }; - - $scope.goToNextStep = function(form, model){ - var require_all, nextStep; - if($scope.step === "diagnosis"){ - if(!form.primary_diagnosis_condition.$valid){ - form.primary_diagnosis_condition.$setDirty(); - return; - } - - } - if($scope.step === "travel"){ - if(!$scope.travelStep.validateStep()){ - return; - } - } - if($scope.step === "antimicrobial"){ - if(!$scope.antimicrobialStep.validateStep()){ - return; - } - } - if($scope.step === "presenting_complaint"){ - /* - this is a work around as multiple angular ui select does not play nicely - with ngRequired. It might be better to set each model as a different form - */ - if(!model.presenting_complaint.symptoms.length){ - form.presenting_complaint_symptoms.$setValidity("required", false); - form.presenting_complaint_symptoms.$setDirty(); - return; - } - } - if($scope.step === "consultant_at_discharge"){ - if(!form.consultant_at_discharge_consultant.$valid){ - form.consultant_at_discharge_consultant.$setDirty(); - return; - } - } - - nextStep = $scope.nextStep(); - var processStep = _.find($scope.processSteps, function(processStep){ - return processStep.name === $scope.step; - }); - - processStep.done = true; - - if(nextStep){ - $scope.step = nextStep; - } - else{ - $scope.save(); - } - }; - - if(!$scope.step){ - $scope.step = _.first($scope.steps); - } - - if($scope.episode.secondary_diagnosis.length === 0){ - $scope.editing.secondary_diagnosis = [{condition: null, co_primary: false, id: 1}]; - }else{ - $scope.editing.secondary_diagnosis = _.map( - $scope.episode.secondary_diagnosis, function(sd){ - var copy = sd.makeCopy(); - copy.submitted = true; - return copy; - }); - } - - $scope.confirming = false; - $scope.validDiagnosis = false; - $scope.is_list_view = $location.path().indexOf('/list/') === 0; - // - // This flag sets the visibility of the modal body - // - $scope.discharged = false; - - // - // We only really need one lookuplist. - // TODO: put these into a nicer service. - // - for (var name in options) { - if (name.indexOf('micro_test') !== 0) { - $scope[name + '_list'] = _.uniq(options[name]); - } - } - - // - // We should deal with the case where we're confirming discharge - // - if(!$scope.is_list_view){ - $scope.confirming = true; - $scope.validDiagnosis = _.contains($scope.condition_list, $scope.episode.primary_diagnosis[0].condition); - if(!$scope.validDiagnosis){ - $scope.oldDiagnosis = $scope.episode.primary_diagnosis[0].condition; - $scope.editing.primary_diagnosis.condition = undefined; - } - } - - // - // Add an extra Secondary diagnosis option to the list - // - $scope.secondaryDiagnosisWarning = false; - - $scope.addSecondary = function(){ - $scope.secondaryDiagnosisWarning = !_.every($scope.editing.secondary_diagnosis, function(x){ - return x.condition; - }); - - if(!$scope.secondaryDiagnosisWarning){ - _.each($scope.editing.secondary_diagnosis, function(e){ - e.submitted = true; - }); - - var d = { - condition: null, - co_primary: false, - id: $scope.editing.secondary_diagnosis.length + 1 - }; - - $scope.editing.secondary_diagnosis.push(d); - } - }; - - $scope.removeSecondary = function($index){ - $scope.editing.secondary_diagnosis.splice(index, 1); - } - - // Let's have a nice way to kill the modal. - $scope.cancel = function() { - $modalInstance.close('cancel'); - }; - - // - // We need to save both the primary diagnosis and any secondary diagnoses. - // The PD is simple as it's a singleton model, and we ensured it existed - // above. - // - // For SDs, we need to check whether we are creating or updating, and - // hit the appropriate .save(). - // - // Once everything has come back from the server, growl the user and kill - // the modal. - // - $scope.save = function() { - var to_save; - var primary = episode.primary_diagnosis[0]; - $scope.saving = true; - - if($scope.confirming){ - $scope.editing.primary_diagnosis.confirmed = true; - } - - var saves = []; - saves.push(primary.save($scope.editing.primary_diagnosis)); - - if(_.contains($scope.steps, "consultant_at_discharge")){ - to_save = $scope.episode.consultant_at_discharge[0]; - saves.push(to_save.save($scope.editing.consultant_at_discharge)); - } - - if(_.contains($scope.steps, "presenting_complaint")){ - to_save = $scope.episode.presenting_complaint[0]; - saves.push(to_save.save($scope.editing.presenting_complaint)); - } - - if($scope.antimicrobialStep){ - saves.concat($scope.antimicrobialStep.save()); - } - if($scope.travelStep){ - saves.concat($scope.travelStep.save()); - } - - // if they've removed an already existing diagnosis, let them delete it - _.each($scope.episode.secondary_diagnosis, function(sd){ - if(sd.consistency_token){ - if(!_.find($scope.editing.secondary_diagnosis)){ - sd.destroy(); - } - } - }); - - // $scope.episode.presenting_complaint[0].save($scope.editing.presenting_complaint); - - _.each(_.filter($scope.editing.secondary_diagnosis, - function(sd){ return sd.condition!== null; }), - function(sd, index){ - var save; - var secondary; - delete sd.submitted; - - if(sd.consistency_token){ - var consistency_token = sd.consistency_token; - secondary = _.find( - $scope.episode.secondary_diagnosis, - function(sd){ - return sd.consistency_token == consistency_token; - } - ); - save = secondary.save(sd); - }else{ - secondary = $scope.episode.newItem('secondary_diagnosis'); - delete sd.id; - save = secondary.save(sd); - } - saves.push(save); - } - ); - - - dischargePatientService.discharge(episode, $scope.editing, tags).then(function(){ - $q.all(saves).then(function(){ - $scope.saving = false; - if($scope.confirming){ - growl.success('Final Diagnosis approved.'); - }else{ - growl.success($scope.episode.demographics[0].first_name + ' ' + $scope.episode.demographics[0].surname + ' discharged.'); - } - $scope.discharged = true; - $modalInstance.close('discharged'); - }); - }); - - }; - }); diff --git a/elcid/assets/js/elcid/controllers/tagging_step.js b/elcid/assets/js/elcid/controllers/tagging_step.js new file mode 100644 index 000000000..aba8da1b8 --- /dev/null +++ b/elcid/assets/js/elcid/controllers/tagging_step.js @@ -0,0 +1,6 @@ +angular.module('opal.controllers').controller('TaggingStepCtrl', + function(scope, step, episode) { + "use strict"; + scope.editing.tagging = {}; + } +); diff --git a/elcid/assets/js/elcidtest/diagnosis_discharge.controller.test.js b/elcid/assets/js/elcidtest/diagnosis_discharge.controller.test.js deleted file mode 100644 index 73c8b1ce6..000000000 --- a/elcid/assets/js/elcidtest/diagnosis_discharge.controller.test.js +++ /dev/null @@ -1,173 +0,0 @@ -describe('DiagnosisDischarge', function() { - "use strict"; - - var $rootScope, $scope, $modal, $httpBackend, $controller; - var Episode; - var modalInstance, tags, options, episode, fieldData; - - fieldData = { - 'demographics': { - name: 'demographics', - fields: [] - }, - location: { - name: 'location', - fields: [] - }, - presenting_complaint: { - name: 'presenting_complaint', - fields: [] - }, - antimicrobial: { - name: 'antimicrobial', - fields: [] - }, - travel: { - name: 'travel', - fields: [] - }, - primary_diagnosis: { - name: 'primary_diagnosis', - fields: [] - }, - consultant_at_discharge: { - name: 'consultant_at_discharge', - fields: [] - }, - secondary_diagnosis: { - name: 'secondary_diagnosis', - fields: [] - } - }; - - beforeEach(module('opal.controllers')); - - beforeEach(function(){ - inject(function($injector){ - $httpBackend = $injector.get('$httpBackend'); - $rootScope = $injector.get('$rootScope'); - $modal = $injector.get('$modal'); - $controller = $injector.get('$controller'); - Episode = $injector.get('Episode'); - }); - - $rootScope.fields = fieldData - $scope = $rootScope.$new(); - modalInstance = $modal.open({template: 'notatemplate'}); - - episode = new Episode({ - demographics: [ { patient_id: 123} ], - location: [ { category: 'first' } ], - presenting_complaint: [], - antimicrobial: [], - travel: [], - primary_diagnosis: [{condition: 'liver disease'}], - consultant_at_discharge: [{consultant: 'Dr Foster'}], - secondary_diagnosis: [] - }); - - $controller('DiagnosisDischargeCtrl', { - $scope : $scope, - $modalInstance : modalInstance, - options : options, - tags : tags, - episode : episode - }); - }); - - describe('nextStep()', function() { - - it('should give you the next step', function() { - expect($scope.nextStep()).toEqual('diagnosis'); - }); - - it('should give you null on the last step', function() { - $scope.step = 'discharge'; - expect($scope.nextStep()).toEqual(null); - }); - - }); - - describe('previousStep()', function() { - - it('should return null on the first step', function() { - expect($scope.previousStep()).toEqual(null) - }); - - it('should return the previous step', function() { - $scope.step = 'discharge'; - expect($scope.previousStep()).toEqual('travel'); - }); - - }); - - describe('resetFormValidation()', function() { - - it('should reset the warning', function() { - var form = {}; - $scope.resetFormValidation(form); - expect(form.warning).toEqual(false); - }); - - }); - - describe('resetRequired', function() { - - it('should reset the validity', function() { - var formfield = { '$setValidity': jasmine.createSpy() }; - $scope.resetRequired(formfield); - expect(formfield.$setValidity).toHaveBeenCalledWith('required', true) - }); - - }); - - describe('goToNextStep()', function() { - var form, model; - - beforeEach(function(){ - form = {}; - model = {}; - }); - - describe('diagnosis', function() { - - it('should not allow invlaid steps', function() { - $scope.step = 'diagnosis'; - form.primary_diagnosis_condition = { - '$valid': false, - '$setDirty': jasmine.createSpy() - }; - $scope.goToNextStep(form, model); - expect(form.primary_diagnosis_condition.$setDirty).toHaveBeenCalledWith(); - }); - - }); - - describe('presenting_complaint', function() { - - it('should not allow invalid forms', function() { - model.presenting_complaint = {symptoms: []}; - form.presenting_complaint_symptoms = { - '$setValidity': jasmine.createSpy(), - '$setDirty' : jasmine.createSpy() - }; - $scope.goToNextStep(form, model); - expect(form.presenting_complaint_symptoms.$setValidity).toHaveBeenCalledWith("required", false); - expect(form.presenting_complaint_symptoms.$setDirty).toHaveBeenCalledWith(); - }); - - }); - - }); - - describe('cancel()', function(){ - - it('should close with null', function(){ - spyOn(modalInstance, 'close'); - $scope.cancel(); - expect(modalInstance.close).toHaveBeenCalledWith('cancel'); - }); - - }); - -}); diff --git a/elcid/assets/js/elcidtest/tagging_step.controller.test.js b/elcid/assets/js/elcidtest/tagging_step.controller.test.js new file mode 100644 index 000000000..a0cec6e80 --- /dev/null +++ b/elcid/assets/js/elcidtest/tagging_step.controller.test.js @@ -0,0 +1,20 @@ +describe('TaggingStepCtrl', function() { + "use strict"; + var $controller; + + beforeEach(function(){ + module('opal.controllers'); + inject(function($injector){ + $controller = $injector.get('$controller'); + }); + }); + + it('should add a tagging dictionary onto the controller', function(){ + var scope = {editing: {}}; + $controller('TaggingStepCtrl', { + scope: scope, step: {}, episode: {} + }); + + expect(scope.editing.tagging).toEqual({}); + }); +}); diff --git a/elcid/pathways.py b/elcid/pathways.py index 38f3730b7..32499c3a2 100644 --- a/elcid/pathways.py +++ b/elcid/pathways.py @@ -45,6 +45,7 @@ class AddPatientPathway(SaveTaggingMixin, RedirectsToEpisodeMixin, WizardPathway Step( model=models.Location, template_url="/templates/pathway/blood_culture_location.html", + step_controller="TaggingStepCtrl", ), )