Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

Fix issue #380: cascading delete for patient records; delete related records first and then patient record #457

Merged
merged 7 commits into from
Aug 29, 2016
16 changes: 16 additions & 0 deletions app/mixins/patient-appointments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Ember from 'ember';
import PouchDbMixin from 'hospitalrun/mixins/pouchdb';

export default Ember.Mixin.create(PouchDbMixin, {
getPatientAppointments: function(patient) {
var patientId = patient.get('id');
var maxValue = this.get('maxValue');
return this.store.query('appointment', {
options: {
startkey: [patientId, null, null, 'appointment_'],
endkey: [patientId, maxValue, maxValue, maxValue]
},
mapReduce: 'appointments_by_patient'
});
}
});
14 changes: 14 additions & 0 deletions app/mixins/patient-invoices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Ember from 'ember';
import PouchDbMixin from 'hospitalrun/mixins/pouchdb';

export default Ember.Mixin.create(PouchDbMixin, {
getPatientInvoices: function(patient) {
var patientId = patient.get('id');
return this.store.query('invoice', {
options: {
key: patientId
},
mapReduce: 'invoice_by_patient'
});
}
});
112 changes: 110 additions & 2 deletions app/patients/delete/controller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,112 @@
import AbstractDeleteController from 'hospitalrun/controllers/abstract-delete-controller';
export default AbstractDeleteController.extend({
title: 'Delete Patient'
import PatientVisitsMixin from 'hospitalrun/mixins/patient-visits';
import PatientAppointmentsMixin from 'hospitalrun/mixins/patient-appointments';
import PatientInvoicesMixin from 'hospitalrun/mixins/patient-invoices';
import PouchDbMixin from 'hospitalrun/mixins/pouchdb';
import ProgressDialog from 'hospitalrun/mixins/progress-dialog';
import Ember from 'ember';

function deleteMany(manyArray) {
if (!manyArray) {
return Ember.RSVP.resolve();
}
if (manyArray.then) {
// recursive call after resolving async model
return manyArray.then(deleteMany);
}
var recordsCount = manyArray.get('length');
if (!recordsCount) {
// empty array: no records to delete
return Ember.RSVP.resolve();
}
return Ember.RSVP.all(manyArray.invoke('destroyRecord', 'async array deletion'));
}

export default AbstractDeleteController.extend(PatientVisitsMixin, PatientInvoicesMixin, PouchDbMixin, ProgressDialog, PatientAppointmentsMixin, {
title: 'Delete Patient',
progressTitle: 'Delete Patient Record',
progressMessage: 'Deleting patient and all associated records',

// Override delete action on controller; we must delete
// all related records before deleting patient record
// otherwise errors will occur
deletePatient: function() {
var controller = this;
var patient = this.get('model');
var visits = this.getPatientVisits(patient);
var invoices = this.getPatientInvoices(patient);
var appointments = this.getPatientAppointments(patient);
var payments = patient.get('payments');
// resolve all async models first since they reference each other, then delete
return Ember.RSVP.all([visits, invoices, appointments, payments]).then(function(records) {
var promises = [];
promises.push(controller.deleteVisits(records[0]));
promises.push(controller.deleteInvoices(records[1]));
promises.push(deleteMany(records[2])); // appointments
promises.push(deleteMany(records[3])); // payments
return Ember.RSVP.all(promises)
.then(function() {
return patient.destroyRecord();
});
});
},

deleteVisits: function(visits) {
var promises = [];
visits.forEach(function(visit) {
var labs = visit.get('labs');
var procedures = visit.get('procedures');
var imaging = visit.get('imaging');
var procCharges = procedures.then(function(p) {
return p.get('charges');
});
var labCharges = labs.then(function(l) {
return l.get('charges');
});
var imagingCharges = imaging.then(function(i) {
return i.get('charges');
});
var visitCharges = visit.get('charges');
promises.push(deleteMany(labs));
promises.push(deleteMany(labCharges));
promises.push(deleteMany(visit.get('patientNotes')));
promises.push(deleteMany(visit.get('vitals')));
promises.push(deleteMany(procedures));
promises.push(deleteMany(procCharges));
promises.push(deleteMany(visit.get('medication')));
promises.push(deleteMany(imaging));
promises.push(deleteMany(imagingCharges));
promises.push(deleteMany(visitCharges));
});
return Ember.RSVP.all(promises).then(function() {
return deleteMany(visits);
});
},

deleteInvoices: function(patientInvoices) {
return Ember.RSVP.resolve(patientInvoices).then(function(invoices) {
var lineItems = invoices.map(function(i) {
return i.get('lineItems');
});
var lineItemDetails = lineItems.map(function(li) {
return li.get('details');
});
return Ember.RSVP.all([lineItems, lineItemDetails]).then(function() {
return Ember.RSVP.all([deleteMany(invoices), deleteMany(lineItems), deleteMany(lineItemDetails)]);
});
});
},

actions: {
// delete related records without modal dialogs
delete: function(patient) {
var controller = this;
this.send('closeModal');
this.showProgressModal();
this.deletePatient(patient).then(function() {
controller.closeProgressModal();
controller.send(controller.get('afterDeleteAction'), patient);
});
}
}
});
6 changes: 6 additions & 0 deletions app/utils/pouch-views.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ var designDocs = [{
'emit([doc.data.status, dateCompleted, doc._id]);'
),
version: 4
}, {
name: 'invoice_by_patient',
function: generateView('invoice',
'emit(doc.data.patient);'
),
version: 1
}, {
name: 'invoice_by_status',
function: generateView('invoice',
Expand Down