diff --git a/app/admin/address/route.js b/app/admin/address/route.js index f1fe6c8dcd..17a5b15a77 100644 --- a/app/admin/address/route.js +++ b/app/admin/address/route.js @@ -1,31 +1,39 @@ -import AbstractEditRoute from 'hospitalrun/routes/abstract-edit-route'; import Ember from 'ember'; +import AbstractEditRoute from 'hospitalrun/routes/abstract-edit-route'; +import { task } from 'ember-concurrency'; import { translationMacro as t } from 'ember-i18n'; import UnauthorizedError from 'hospitalrun/utils/unauthorized-error'; +const { computed } = Ember; + export default AbstractEditRoute.extend({ hideNewButton: true, newTitle: t('admin.address.newTitle'), editTitle: t('admin.address.editTitle'), + model() { - return new Ember.RSVP.Promise((resolve, reject) => { - this.get('store').find('option', 'address_options').then((addressOptions) => { - resolve(addressOptions); - }, (err) => { - if (err instanceof UnauthorizedError) { - reject(err); - } else { - let store = this.get('store'); - let newConfig = store.push(store.normalize('option', { - id: 'address_options', - value: { - address1Label: this.get('i18n').t('admin.address.addressLabel'), - address1Include: true - } - })); - resolve(newConfig); - } - }); + return this.get('fetchAddressOptions').perform(); + }, + + fetchAddressOptions: task(function* () { + let store = this.get('store'); + try { + return yield store.find('option', 'address_options'); + } catch(err) { + if (err instanceof UnauthorizedError) { + throw err; + } + return store.push(this.get('defaultAddressOption')); + } + }).keepLatest().cancelOn('deactivate'), + + defaultAddressOption: computed(function() { + return this.get('store').normalize('option', { + id: 'address_options', + value: { + address1Label: this.get('i18n').t('admin.address.addressLabel'), + address1Include: true + } }); - } + }) }); diff --git a/app/patients/delete/controller.js b/app/patients/delete/controller.js index 71ec0d466a..e92ca64a5c 100644 --- a/app/patients/delete/controller.js +++ b/app/patients/delete/controller.js @@ -6,116 +6,121 @@ import PouchDbMixin from 'hospitalrun/mixins/pouchdb'; import ProgressDialog from 'hospitalrun/mixins/progress-dialog'; import Ember from 'ember'; import { translationMacro as t } from 'ember-i18n'; +import { task, taskGroup, all } from 'ember-concurrency'; -function deleteMany(manyArray) { - if (!manyArray) { - return Ember.RSVP.resolve(); - } - if (manyArray.then) { - // recursive call after resolving async model - return manyArray.then(deleteMany); - } - let recordsCount = manyArray.get('length'); - if (!recordsCount) { - // empty array: no records to delete - return Ember.RSVP.resolve(); - } - let archivePromises = manyArray.map((recordToDelete) => { - recordToDelete.set('archived', true); - return recordToDelete.save().then(() => { - return recordToDelete.unloadRecord(); - }); - }); - return Ember.RSVP.all(archivePromises, 'async array deletion'); -} +const MAX_CONCURRENCY = 5; export default AbstractDeleteController.extend(PatientVisitsMixin, PatientInvoicesMixin, PouchDbMixin, ProgressDialog, PatientAppointmentsMixin, { title: t('patients.titles.delete'), progressTitle: t('patients.titles.deletePatientRecord'), progressMessage: t('patients.messages.deletingPatient'), + deleting: taskGroup(), + + deleteMany(manyArray) { + return this.get('deleteManyTask').perform(manyArray); + }, + + deleteManyTask: task(function* (manyArray) { + if (!manyArray) { + return; + } + let resolvedArray = yield manyArray; + if (Ember.isEmpty(resolvedArray)) { + // empty array: no records to delete + return; + } + let deleteRecordTask = this.get('deleteRecordTask'); + let archivePromises = []; + for (let recordToDelete of resolvedArray) { + archivePromises.push(deleteRecordTask.perform(recordToDelete)); + } + return yield all(archivePromises, 'async array deletion'); + }).group('deleting'), + + deleteRecordTask: task(function* (recordToDelete) { + recordToDelete.set('archived', true); + yield recordToDelete.save(); + return yield recordToDelete.unloadRecord(); + }).maxConcurrency(MAX_CONCURRENCY).enqueue().group('deleting'), // Override delete action on controller; we must delete // all related records before deleting patient record // otherwise errors will occur deletePatient() { - let controller = this; - let patient = this.get('model'); - let visits = this.getPatientVisits(patient); - let invoices = this.getPatientInvoices(patient); - let appointments = this.getPatientAppointments(patient); - let 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) { - let 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(); - }); - }); + return this.get('deletePatientTask').perform(); }, + deletePatientTask: task(function* () { + let patient = this.get('model'); + let visits = yield this.getPatientVisits(patient); + let invoices = yield this.getPatientInvoices(patient); + let appointments = yield this.getPatientAppointments(patient); + let payments = yield patient.get('payments'); + yield all([ + this.deleteVisits(visits), + this.deleteInvoices(invoices), + this.deleteMany(appointments), + this.deleteMany(payments) + ]); + return yield patient.destroyRecord(); + }).group('deleting'), + deleteVisits(visits) { - let promises = []; - visits.forEach(function(visit) { - let labs = visit.get('labs'); - let procedures = visit.get('procedures'); - let imaging = visit.get('imaging'); - let procCharges = procedures.then(function(p) { - return p.get('charges'); - }); - let labCharges = labs.then(function(l) { - return l.get('charges'); - }); - let imagingCharges = imaging.then(function(i) { - return i.get('charges'); - }); - let 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); - }); + return this.get('deleteVisitsTask').perform(visits); }, + deleteVisitsTask: task(function* (visits) { + let pendingTasks = []; + for (let visit of visits) { + let labs = yield visit.get('labs'); + let procedures = yield visit.get('procedures'); + let imaging = yield visit.get('imaging'); + let procCharges = procedures.get('charges'); + let labCharges = labs.get('charges'); + let imagingCharges = imaging.get('charges'); + let visitCharges = visit.get('charges'); + pendingTasks.push(this.deleteMany(labs)); + pendingTasks.push(this.deleteMany(labCharges)); + pendingTasks.push(this.deleteMany(visit.get('patientNotes'))); + pendingTasks.push(this.deleteMany(visit.get('vitals'))); + pendingTasks.push(this.deleteMany(procedures)); + pendingTasks.push(this.deleteMany(procCharges)); + pendingTasks.push(this.deleteMany(visit.get('medication'))); + pendingTasks.push(this.deleteMany(imaging)); + pendingTasks.push(this.deleteMany(imagingCharges)); + pendingTasks.push(this.deleteMany(visitCharges)); + } + yield all(pendingTasks); + return yield this.deleteMany(visits); + }).group('deleting'), + deleteInvoices(patientInvoices) { - return Ember.RSVP.resolve(patientInvoices).then(function(invoices) { - let lineItems = Ember.A(); - invoices.forEach(function(i) { - lineItems.addObjects(i.get('lineItems')); - }); - let lineItemDetails = Ember.A(); - lineItems.forEach(function(li) { - lineItemDetails.addObjects(li.get('details')); - }); - return Ember.RSVP.all([lineItems, lineItemDetails]).then(function() { - return Ember.RSVP.all([deleteMany(invoices), deleteMany(lineItems), deleteMany(lineItemDetails)]); - }); - }); + return this.get('deleteInvoicesTask').perform(patientInvoices); }, - actions: { + deleteInvoicesTask: task(function* (patientInvoices) { + let invoices = yield patientInvoices; + let lineItems = yield all(invoices.mapBy('lineItems')); + let lineItemDetails = yield all(lineItems.mapBy('details')); + return yield all([ + this.deleteMany(invoices), + this.deleteMany(lineItems), + this.deleteMany(lineItemDetails) + ]); + }).group('deleting'), + + deleteActionTask: task(function* (patient) { // delete related records without modal dialogs + this.send('closeModal'); + this.showProgressModal(); + yield this.deletePatient(patient); + this.closeProgressModal(); + this.send(this.get('afterDeleteAction'), patient); + }).drop(), + + actions: { delete(patient) { - let controller = this; - this.send('closeModal'); - this.showProgressModal(); - this.deletePatient(patient).then(function() { - controller.closeProgressModal(); - controller.send(controller.get('afterDeleteAction'), patient); - }); + this.get('deleteActionTask').perform(patient); } } }); diff --git a/package.json b/package.json index 5501868aed..7b94fbe736 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "ember-cli-template-lint": "0.4.12", "ember-cli-test-loader": "^1.1.0", "ember-cli-uglify": "^1.2.0", + "ember-concurrency": "0.7.19", "ember-data": "^2.10.0", "ember-export-application-global": "^1.0.5", "ember-fullcalendar": "1.3.0",