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",
),
)