diff --git a/app/client/views/patients/patient.coffee b/app/client/views/patients/patient.coffee index 8ed47ba..f4f3474 100644 --- a/app/client/views/patients/patient.coffee +++ b/app/client/views/patients/patient.coffee @@ -1,38 +1,143 @@ +AutoForm.hooks + editPatientForm: + onSubmit: (insertDoc, updateDoc, currentDoc) -> + self = @ + Meteor.call "updatePatients", [Session.get("selectedPatientId")], updateDoc, (error) -> + if error? + throwError error + self.done() + false + Template.patient.created = -> self = @ @autorun -> Session.set 'patientSubscriptionsReady', false patient = Patients.findOne Session.get("selectedPatientId") if patient? self.subscribe("studyCompositesForPatient", patient._id) self.subscribe("visitsCompositeForPatient", patient._id, onReady: -> Session.set 'patientSubscriptionsReady', true) Template.patient.rendered = -> @$('[data-toggle=tooltip]').tooltip() Template.patient.helpers designs: -> ds = "" designs = @studyDesigns().forEach (d) -> ds += d.title+', ' ds.slice(0, -2) numVisits: -> visits = 0 @studyDesigns().forEach (d) -> visits += d.visits.length if d? visits template: -> selectedDesignVisitId = Session.get 'selectedDesignVisitId' if selectedDesignVisitId? "patientVisit" else "patientVisits" templateData: -> patient: @ + formDoc: -> + Patients.findOne Session.get("selectedPatientId") + + editPatientSchema: -> + caseManagers = Meteor.users.find( + roles: "caseManager" + ).map (t) -> + label: getUserDescription(t) + value: t._id + designs = StudyDesigns.find( + studyId: @_id + ).map (d) -> + label: d.title + value: d._id + langs = isoLangs.map (l) -> + label: "#{l.name} (#{l.nativeName})" + value: l.code + schema = + primaryLanguage: + label: 'Primary Language' + type: String + optional: true + autoform: + options: langs + secondaryLanguage: + label: 'Secondary Language' + type: String + optional: true + autoform: + options: langs + hrid: + label: "HRID" + type: String + optional: true + max: 8 + if Roles.userIsInRole(Meteor.userId(), 'admin') + schema = _.extend schema, + caseManagerId: + label: "Case Manager" + type: String + optional: true + autoform: + type: "select" + options: caseManagers + new SimpleSchema(schema) + Template.patient.events "click .id, click .hrid": (evt) -> selectPatientId(@_id, true) + + "click button.delete": (evt) -> + patientId = @_id + swal { + title: 'Are you sure?' + text: 'Do you really want to delete this patient? A log entry will be created.' + type: 'warning' + showCancelButton: true + confirmButtonText: 'Yes' + closeOnConfirm: false + }, -> + Meteor.call "removePatient", patientId, (error) -> + if error? + if error.reason is "patientHasData" + swal { + title: 'Attention!' + text: """The patient you are about to remove has data entries. Please consider using the exclude checkbox instead. If you really want to proceed removing the patient please state a reason. A log entry will be created.""" + type: 'input' + showCancelButton: true + confirmButtonText: 'Yes' + inputPlaceholder: "Please state a reason." + closeOnConfirm: false + }, (confirmedWithReason)-> + if confirmedWithReason is false #cancel + swal.close() + else + if !confirmedWithReason? or confirmedWithReason.length is 0 + swal.showInputError("You need to state a reason!") + else + Meteor.call "removePatient", patientId, confirmedWithReason, (error2) -> + if error2? + throwError error2 + else + swal.close() + $('#studiesSelect').selectpicker('deselectAll') + Session.set 'selectedDesignVisitId', null + Session.set 'selectedPatientId', null + Session.set 'selectedStudyIds', null + return + else + throwError error + else + swal.close() + $('#studiesSelect').selectpicker('deselectAll') + Session.set 'selectedDesignVisitId', null + Session.set 'selectedPatientId', null + Session.set 'selectedStudyIds', null + return + return false diff --git a/app/client/views/patients/patient.html b/app/client/views/patients/patient.html index edc4699..ef34b55 100644 --- a/app/client/views/patients/patient.html +++ b/app/client/views/patients/patient.html @@ -1,41 +1,58 @@ diff --git a/app/lib/collections/patients.coffee b/app/lib/collections/patients.coffee index 1196522..93e8ae9 100644 --- a/app/lib/collections/patients.coffee +++ b/app/lib/collections/patients.coffee @@ -1,262 +1,262 @@ class @Patient constructor: (doc) -> _.extend this, doc caseManager: -> return null unless @caseManagerId? Meteor.users.findOne _id: @caseManagerId caseManagerName: -> getUserDescription @caseManager() study: -> return null unless @studyId? Studies.findOne _id: @studyId studyDesigns: -> return [] unless @studyDesignIds? StudyDesigns.find _id: $in: @studyDesignIds , sort: createdAt: 1 languages: -> langs = "" langs += @primaryLanguage if @primaryLanguage? if @secondaryLanguage langs += ", #{@secondaryLanguage}" langs @Patients = new Meteor.Collection("patients", transform: (doc) -> new Patient(doc) ) if Meteor.isServer Patients._ensureIndex( { id: 1 }, { unique: true } ) Patients.before.insert BeforeInsertTimestampHook Patients.before.update BeforeUpdateTimestampHook schema = 'id': type: String 'hrid': type: String optional: true unique: true index: true 'creatorId': type: String 'studyId': type: String 'studyDesignId': type: String optional: true 'studyDesignIds': type: [String] optional: true 'caseManagerId': type: String optional: true 'primaryLanguage': type: String optional: true 'secondaryLanguage': type: String optional: true 'hasData': type: Boolean defaultValue: false 'hasDataForDesignIds': type: [String] optional: true 'isExcluded': type: Boolean defaultValue: false 'excludesIncludes': type: [Object] optional: true 'excludesIncludes.$.reason': type: String 'excludesIncludes.$.createdAt': type: Number 'updatedAt': type: Number optional: true 'createdAt': type: Number optional: true Patients.attachSchema new SimpleSchema(schema) if Meteor.isServer Meteor.methods "createPatient": (studyId) -> canAddPatient(studyId) check studyId, String study = Studies.findOne studyId throw new Meteor.Error(403, "study not found.") unless study? throw new Meteor.Error(400, "Study is locked. Please unlock it first.") if study.isLocked _id = null tries = 0 loop try _id = Patients.insert id: readableRandom(6) creatorId: Meteor.userId() studyId: studyId hasData: false caseManagerId: Meteor.userId() catch e console.log "Error: createPatient" console.log e finally tries += 1 break if _id or tries >= 10 throw new Meteor.Error(500, "Can't create patient, id space seems to be full.") unless _id? return _id Meteor.methods 'updatePatients': (ids, update) -> check ids, [String] check update, Object ids.forEach((patientId) -> canUpdatePatient(patientId)) allowedKeys = [ "$set.caseManagerId", "$set.studyDesignIds", "$set.primaryLanguage", "$set.secondaryLanguage", "$unset.primaryLanguage", "$unset.secondaryLanguage", ] if Roles.userIsInRole(Meteor.user(), 'admin') allowedKeys = [ allowedKeys..., "$unset.caseManagerId", "$unset.studyDesignIds" ] if ids.length > 1 if update['$set']? #don't overwrite with empty values delete update['$unset'] else if ids.length is 1 allowedKeys = _.union allowedKeys, [ "$set.hrid", "$unset.hrid" ] #pick whitelisted keys update = _.pickDeep update, allowedKeys Patients.find(_id: $in: ids).forEach (p) -> study = Studies.findOne p.studyId throw new Meteor.Error(403, "study not found.") unless study? throw new Meteor.Error(400, "Study is locked. Please unlock it first.") if study.isLocked #check if changing studyDesign for a patient which has data if update['$set']?['studyDesignIds']? or update['$unset']?['studyDesignIds']? patientIds = [] Patients.find(_id: $in: ids).forEach (p) -> # unset is always a string if update['$unset']?['studyDesignIds']? and p.hasData patientIds.push p.id if p.hasDataForDesignIds? if update['$set']?['studyDesignIds']? allFound = true p.hasDataForDesignIds.forEach (sDId) -> if update['$set']['studyDesignIds'].indexOf(sDId) is -1 allFound = false if !allFound patientIds.push p.id if patientIds.length > 0 throw new Meteor.Error(400, "You have removed one or more study designs from patient(s) (#{patientIds.join(', ')}) which have already entered data for one of these designs. This is not allowed and your changes are therefore discarded.") #remove already created visits with no data Patients.find(_id: $in: ids).forEach (p) -> Visits.find( patientId: p._id ).forEach (v) -> if Answers.find( visitId: v._id).count() is 0 Visits.remove _id: v._id ids.forEach (id) -> try Patients.update _id: id, update catch e if e.code is 11000 #MongoError: E11000 duplicate key error throw new Meteor.Error(400, "The HRID you entered exists already, please choose a unique value.") else throw e return "excludePatient": (patientId, reason) -> checkIfAdmin() check patientId, String check reason, String patient = Patients.findOne patientId throw new Meteor.Error(500, "Patient can't be found.") unless patient? throw new Meteor.Error(500, "Patient is already excluded.") if patient.isExcluded study = Studies.findOne patient.studyId throw new Meteor.Error(403, "study not found.") unless study? throw new Meteor.Error(400, "Study is locked. Please unlock it first.") if study.isLocked Patients.update patientId, $push: excludesIncludes: reason: reason createdAt: Date.now() Patients.update patientId, $set: isExcluded: true return "includePatient": (patientId, reason) -> checkIfAdmin() check patientId, String check reason, String patient = Patients.findOne patientId throw new Meteor.Error(500, "Patient can't be found.") unless patient? throw new Meteor.Error(500, "Patient isn't excluded.") if !patient.isExcluded study = Studies.findOne patient.studyId throw new Meteor.Error(403, "study not found.") unless study? throw new Meteor.Error(400, "Study is locked. Please unlock it first.") if study.isLocked Patients.update patientId, $push: excludesIncludes: reason: reason createdAt: Date.now() Patients.update patientId, $set: isExcluded: false return "removePatient": (patientId, forceReason) -> - checkIfAdmin() check patientId, String + canUpdatePatient(patientId) patient = Patients.findOne patientId throw new Meteor.Error(500, "Patient can't be found.") unless patient? study = Studies.findOne patient.studyId throw new Meteor.Error(403, "study not found.") unless study? throw new Meteor.Error(400, "Study is locked. Please unlock it first.") if study.isLocked if patient.hasData and !forceReason throw new Meteor.Error(500, "patientHasData") if patient.hasData Meteor.call "logActivity", "remove patient (#{patient.id}) which has data", "critical", forceReason, patient else Meteor.call "logActivity", "remove patient (#{patient.id}) which has no data", "notice", null, patient Visits.find(patientId: patientId).forEach (v) -> Answers.remove visitId: v._id Visits.remove v._id Patients.remove _id: patientId return