1 angular.module('egSerialsAppDep')
3 .directive('egSubscriptionManager', function() {
10 templateUrl: './serials/t_subscription_manager',
12 ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
13 '$uibModal','ngToast','egConfirmDialog',
14 function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
15 $uibModal , ngToast , egConfirmDialog ) {
17 $scope.selected_owning_ou = null;
18 $scope.owning_ou_changed = function(org) {
19 $scope.selected_owning_ou = org.id();
24 egSerialsCoreSvc.fetch($scope.bibId, $scope.selected_owning_ou).then(function() {
25 $scope.subscriptions = egCore.idl.toTypedHash(egSerialsCoreSvc.subTree);
26 // un-flesh receive unit template so that we can use
27 // it as a model of a select
28 angular.forEach($scope.subscriptions, function(ssub) {
29 angular.forEach(ssub.distributions, function(sdist) {
30 if (angular.isObject(sdist.receive_unit_template)) {
31 sdist.receive_unit_template = sdist.receive_unit_template.id;
35 $scope.distStreamGridDataProvider.refresh();
40 $scope.localStreamNames = [];
41 egCore.hatch.getItem('eg.serials.stream_names')
42 .then(function(list) {
43 if (list) $scope.localStreamNames = list;
46 $scope.distStreamGridControls = {
47 activateItem : function (item) { } // TODO
49 $scope.distStreamGridDataProvider = egGridDataProvider.instance({
50 get : function(offset, count) {
51 return this.arrayNotifier(egSerialsCoreSvc.subList, offset, count);
55 $scope.need_one_selected = function() {
56 var items = $scope.distStreamGridControls.selectedItems();
57 if (items.length == 1) return false;
61 $scope.receiving_templates = {};
62 angular.forEach(egCore.org.list(), function(org) {
63 egSerialsCoreSvc.fetch_templates(org.id()).then(function(list){
64 $scope.receiving_templates[org.id()] = egCore.idl.toTypedHash(list);
68 $scope.add_subscription = function() {
69 var new_ssub = egCore.idl.toTypedHash(new egCore.idl.ssub());
70 new_ssub._isnew = true;
71 new_ssub.record_entry = $scope.bibId;
72 new_ssub._focus_me = true;
73 $scope.subscriptions.push(new_ssub);
74 $scope.add_distribution(new_ssub); // since we know we want at least one distribution
76 $scope.add_distribution = function(ssub, grab_focus) {
78 'serial.default_display_grouping'
79 ]).then(function(set) {
80 var new_sdist = egCore.idl.toTypedHash(new egCore.idl.sdist());
81 new_sdist._isnew = true;
82 new_sdist.subscription = ssub.id;
83 new_sdist.display_grouping = set['serial.default_display_grouping'] || 'chron';
84 if (!angular.isArray(ssub.distributions)){
85 ssub.distributions = [];
88 new_sdist._focus_me = true;
89 ssub._focus_me = false;
91 ssub.distributions.push(new_sdist);
92 $scope.add_stream(new_sdist); // since we know we want at least one stream
95 $scope.remove_pending_distribution = function(ssub, sdist) {
97 for (var i = 0; i < ssub.distributions.length; i++) {
98 if (ssub.distributions[i] === sdist) {
103 if (to_remove > -1) {
104 ssub.distributions.splice(to_remove, 1);
107 $scope.add_stream = function(sdist, grab_focus) {
108 var new_sstr = egCore.idl.toTypedHash(new egCore.idl.sstr());
109 new_sstr.distribution = sdist.id;
110 new_sstr._isnew = true;
112 new_sstr._focus_me = true;
113 sdist._has_focus = false; // and take focus away from a newly created sdist
115 if (!angular.isArray(sdist.streams)){
118 sdist.streams.push(new_sstr);
121 $scope.remove_pending_stream = function(sdist, sstr) {
123 for (var i = 0; i < sdist.streams.length; i++) {
124 if (sdist.streams[i] === sstr) {
129 if (to_remove > -1) {
130 sdist.streams.splice(to_remove, 1);
134 $scope.abort_changes = function(form) {
138 function updateLocalStreamNames (new_name) {
139 if (new_name && $scope.localStreamNames.filter(function(x){ return x == new_name}).length == 0) {
140 $scope.localStreamNames.push(new_name);
141 egCore.hatch.setItem('eg.serials.stream_names', $scope.localStreamNames)
145 $scope.dirtyForm = function () {
146 $scope.ssubform.$dirty = true;
149 $scope.save_subscriptions = function(form) {
150 // traverse through structure and set _ischanged
151 // TODO add more granular dirty input detection
152 angular.forEach($scope.subscriptions, function(ssub) {
153 if (!ssub._isnew) ssub._ischanged = true;
154 angular.forEach(ssub.distributions, function(sdist) {
155 if (!sdist._isnew) sdist._ischanged = true;
156 angular.forEach(sdist.streams, function(sstr) {
157 if (!sstr._isnew) sstr._ischanged = true;
158 updateLocalStreamNames(sstr.routing_label);
163 var obj = egCore.idl.fromTypedHash($scope.subscriptions);
165 // create a bunch of promises that each get resolved upon each
166 // CUD update; that way, we can know when the entire save
167 // operation is completed
169 angular.forEach(obj, function(ssub) {
170 ssub._cud_done = $q.defer();
171 promises.push(ssub._cud_done.promise);
172 angular.forEach(ssub.distributions(), function(sdist) {
173 sdist._cud_done = $q.defer();
174 promises.push(sdist._cud_done.promise);
175 angular.forEach(sdist.streams(), function(sstr) {
176 sstr._cud_done = $q.defer();
177 promises.push(sstr._cud_done.promise);
182 angular.forEach(obj, function(ssub) {
183 ssub.owning_lib(ssub.owning_lib().id()); // deflesh
184 egCore.pcrud.apply(ssub).then(function(res) {
185 var ssub_id = (ssub.isnew() && angular.isObject(res)) ? res.id() : ssub.id();
186 angular.forEach(ssub.distributions(), function(sdist) {
187 // set subscription ID just in case it's new
188 sdist.holding_lib(sdist.holding_lib().id()); // deflesh
189 sdist.subscription(ssub_id);
190 egCore.pcrud.apply(sdist).then(function(res) {
191 var sdist_id = (sdist.isnew() && angular.isObject(res)) ? res.id() : sdist.id();
192 angular.forEach(sdist.streams(), function(sstr) {
193 // set distribution ID just in case it's new
194 sstr.distribution(sdist_id);
195 egCore.pcrud.apply(sstr).then(function(res) {
196 sstr._cud_done.resolve();
200 sdist._cud_done.resolve();
202 ssub._cud_done.resolve();
205 $q.all(promises).then(function(resolutions) {
210 $scope.delete_subscription = function(rows) {
211 if (rows.length == 0) { return; }
212 var s_rows = rows.filter(function(el) {
213 return typeof el['id'] != 'undefined';
215 if (s_rows.length == 0) { return; }
216 egConfirmDialog.open(
217 egCore.strings.CONFIRM_DELETE_SUBSCRIPTION,
218 egCore.strings.CONFIRM_DELETE_SUBSCRIPTION_MESSAGE,
219 {count : s_rows.length}
220 ).result.then(function () {
222 angular.forEach(s_rows, function(el) {
226 'open-ils.serial.subscription.safe_delete',
229 ).then(function(resp){
230 var evt = egCore.evt.parse(resp);
232 ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_DELETE + ' : ' + evt.desc);
234 ngToast.success(egCore.strings.SERIALS_SUBSCRIPTION_SUCCESS_DELETE);
239 $q.all(promises).then(function() {
244 $scope.delete_distribution = function(rows) {
245 if (rows.length == 0) { return; }
246 var d_rows = rows.filter(function(el) {
247 return typeof el['sdist.id'] != 'undefined';
249 if (d_rows.length == 0) { return; }
250 egConfirmDialog.open(
251 egCore.strings.CONFIRM_DELETE_DISTRIBUTION,
252 egCore.strings.CONFIRM_DELETE_DISTRIBUTION_MESSAGE,
253 {count : d_rows.length}
254 ).result.then(function () {
256 angular.forEach(d_rows, function(el) {
260 'open-ils.serial.distribution.safe_delete',
263 ).then(function(resp){
264 var evt = egCore.evt.parse(resp);
266 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_DELETE + ' : ' + evt.desc);
268 ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_DELETE);
273 $q.all(promises).then(function() {
278 $scope.delete_stream = function(rows) {
279 if (rows.length == 0) { return; }
280 var s_rows = rows.filter(function(el) {
281 return typeof el['sstr.id'] != 'undefined';
283 if (s_rows.length == 0) { return; }
284 egConfirmDialog.open(
285 egCore.strings.CONFIRM_DELETE_STREAM,
286 egCore.strings.CONFIRM_DELETE_STREAM_MESSAGE,
287 {count : s_rows.length}
288 ).result.then(function () {
290 angular.forEach(s_rows, function(el) {
294 'open-ils.serial.stream.safe_delete',
297 ).then(function(resp){
298 var evt = egCore.evt.parse(resp);
300 ngToast.danger(egCore.strings.SERIALS_STREAM_FAIL_DELETE + ' : ' + evt.desc);
302 ngToast.success(egCore.strings.SERIALS_STREAM_SUCCESS_DELETE);
307 $q.all(promises).then(function() {
312 $scope.additional_routing = function(rows) {
313 if (!rows) { return; }
315 if (!row) { row = $scope.distStreamGridControls.selectedItems()[0]; }
316 if (row && row['sstr.id']) {
317 egCore.pcrud.search('srlu', {
318 stream : row['sstr.id']
323 'au' : ['mailing_address','billing_address','home_ou']
325 order_by : { srlu : 'pos' }
328 ).then(function(list) {
330 templateUrl: './serials/t_routing_list',
331 controller: 'RoutingCtrl',
333 rowInfo : function() {
336 routes : function() {
337 return egCore.idl.toHash(list);
340 }).result.then(function(routes) {
341 // delete all of the routes first;
342 // it's easiest given the constraints
345 angular.forEach(routes, function(r) {
346 var srlu = new egCore.idl.srlu();
347 srlu.stream(r.stream);
350 srlu.reader(r.reader.id);
352 srlu.department(r.department);
356 var srlu_copy = angular.copy(srlu);
357 srlu_copy.isdeleted(true);
358 deletions.push(srlu_copy);
362 creations.push(srlu);
365 egCore.pcrud.apply(deletions.concat(creations)).then(function(){
372 $scope.clone_subscription = function(rows) {
373 if (!rows) { return; }
376 templateUrl: './serials/t_clone_subscription',
377 controller: 'CloneCtrl',
383 windowClass: 'app-modal-window',
386 }).result.then(function(args) {
388 var some_failure = false;
389 var some_success = false;
391 angular.forEach(rows, function(row) {
398 'open-ils.serial.subscription.clone',
404 var evt = egCore.evt.parse(resp);
405 if (evt) { // any way to just throw or return this to the error handler?
406 console.log('failure',resp);
408 ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_CLONE);
410 console.log('success',resp);
412 ngToast.success(egCore.strings.SERIALS_SUBSCRIPTION_SUCCESS_CLONE);
416 console.log('failure',resp);
418 ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_CLONE);
424 $q.all(promises).then(function() {
429 $scope.link_mfhd = function(rows) {
430 if (!rows) { return; }
432 if (!row['sdist.id']) { return; }
434 templateUrl: './serials/t_link_mfhd',
435 controller: 'LinkMFHDCtrl',
444 windowClass: 'app-modal-window',
447 }).result.then(function(args) {
448 console.log('modal done', args);
449 egCore.pcrud.search('sdist', {
450 id: rows[0]['sdist.id']
451 }, {}, { atomic : true }
452 ).then(function(resp){
453 var evt = egCore.evt.parse(resp);
454 if (evt) { // any way to just throw or return this to the error handler?
455 console.log('failure',resp);
456 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
459 sdist.ischanged(true);
460 sdist.summary_method( args.summary_method );
461 sdist.record_entry( args.which_mfhd );
462 egCore.pcrud.apply(sdist).then(
463 function(resp) { // maybe success
464 console.log('apply',resp);
465 var evt = egCore.evt.parse(resp);
466 if (evt) { // any way to just throw or return this to the error handler?
467 console.log('failure',resp);
468 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
470 console.log('success',resp);
471 ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_LINK_MFHD);
476 console.log('failure',resp);
477 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
483 $scope.apply_binding_template = function(rows) {
484 if (rows.length == 0) { return; }
485 var d_rows = rows.filter(function(el) {
486 return typeof el['sdist.id'] != 'undefined';
488 if (d_rows.length == 0) { return; }
489 var libs = []; var seen_lib = {};
490 angular.forEach(d_rows, function(el) {
491 if (el['sdist.holding_lib.id'] && !seen_lib[el['sdist.holding_lib.id']]) {
492 seen_lib[el['sdist.holding_lib.id']] = 1;
494 id: el['sdist.holding_lib.id'],
495 name: el['sdist.holding_lib.name'],
500 templateUrl: './serials/t_apply_binding_template',
501 controller: 'ApplyBindingTemplateCtrl',
510 windowClass: 'app-modal-window',
513 }).result.then(function(args) {
515 egCore.pcrud.search('sdist', {
516 id: d_rows.map(function(el) { return el['sdist.id']; })
517 }, {}, { atomic : true }
518 ).then(function(resp){
519 var evt = egCore.evt.parse(resp);
520 if (evt) { // any way to just throw or return this to the error handler?
521 console.log('failure',resp);
522 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
525 angular.forEach(resp,function(sdist) {
526 var promise = $q.defer();
527 promises.push(promise.promise);
528 sdist.ischanged(true);
529 sdist.bind_unit_template(
530 typeof args.bind_unit_template[sdist.holding_lib()] == 'undefined'
532 : args.bind_unit_template[sdist.holding_lib()]
534 egCore.pcrud.apply(sdist).then(
535 function(resp2) { // maybe success
536 console.log('apply',resp2);
537 var evt = egCore.evt.parse(resp2);
538 if (evt) { // any way to just throw or return this to the error handler?
539 console.log('failure',resp2);
540 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
542 console.log('success',resp2);
543 ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_BINDING_TEMPLATE);
548 console.log('failure',resp2);
549 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
554 $q.all(promises).then(function() {
560 $scope.subscription_notes = function(rows) {
561 return $scope.notes('subscription',rows);
563 $scope.distribution_notes = function(rows) {
564 return $scope.notes('distribution',rows);
566 $scope.notes = function(note_type,rows) {
567 if (!rows) { return; }
569 function modal(existing_notes) {
571 templateUrl: './serials/t_notes',
573 controller: 'NotesCtrl',
575 note_type : function() { return note_type; },
580 return existing_notes;
583 windowClass: 'app-modal-window',
586 }).result.then(function(notes) {
587 console.log('results',notes);
588 egCore.pcrud.apply(notes).then(
589 function(a) { console.log('toast here 1',a); },
590 function(a) { console.log('toast here 2',a); }
595 if (rows.length == 1) {
597 var search_hash = {};
602 search_hash.subscription = rows[0]['id'];
603 search_opt.order_by = { ssubn : 'create_date' };
607 search_hash.distribution = rows[0]['sdist.id'];
608 search_opt.order_by = { sdistn : 'create_date' };
610 case 'item': default:
612 search_hash.item = rows[0]['si.id'];
613 search_opt.order_by = { sin : 'create_date' };
616 egCore.pcrud.search(fm_hint, search_hash, search_opt,
618 ).then(function(list) {
622 // support batch creation of notes across selections,
632 .controller('ApplyBindingTemplateCtrl',
633 ['$scope','$q','$uibModalInstance','egCore','egSerialsCoreSvc',
635 function($scope , $q , $uibModalInstance , egCore , egSerialsCoreSvc ,
637 $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
638 $scope.cancel = function () { $uibModalInstance.dismiss() }
641 $scope.args = { bind_unit_template : {} };
642 $scope.templates = {};
643 angular.forEach(libs, function(org) {
644 egSerialsCoreSvc.fetch_templates(org.id).then(function(list){
645 $scope.templates[org.id] = egCore.idl.toTypedHash(list);
650 .controller('LinkMFHDCtrl',
651 ['$scope','$q','$uibModalInstance','egCore','row','bibId',
652 function($scope , $q , $uibModalInstance , egCore , row , bibId ) {
653 console.log('row',row);
654 console.log('bibId',bibId);
656 summary_method: row['sdist.summary_method'] || 'add_to_sre',
658 if (row['sdist.record_entry']) {
659 $scope.args.which_mfhd = row['sdist.record_entry'].id;
661 $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
662 $scope.cancel = function () { $uibModalInstance.dismiss() }
663 $scope.legacies = {};
664 egCore.pcrud.search('sre', {
665 record: bibId, owning_lib : row['sdist.holding_lib.id'], active: 't', deleted: 'f'
666 }, {}, { atomic : true }
668 function(resp) { // maybe success
669 var evt; if (evt = egCore.evt.parse(resp)) { console.error(evt.toString()); return; }
670 if (!resp) { return; }
675 angular.forEach(resp, function(sre) {
676 console.log('sre',sre);
677 if (!seen[sre.record()]) {
678 seen[sre.record()] = 1;
679 $scope.legacies[sre.record()] = { mvr: null, svrs: [] };
683 'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
685 ).then(function(resp2) {
686 var evt; if (evt = egCore.evt.parse(resp2)) { console.error(evt.toString()); return; }
687 if (!resp2) { return; }
688 $scope.legacies[sre.record()].mvr = egCore.idl.toHash(resp2);
694 'open-ils.search.serial.record.bib.retrieve',
697 ).then(function(resp2) {
698 angular.forEach(resp2,function(r) {
699 if (r.sre_id() > 0) {
700 console.log('svr',egCore.idl.toHash(r));
701 $scope.legacies[sre.record()].svrs.push( egCore.idl.toHash(r) );
707 if (typeof $scope.legacies[sre.record()].sres == 'undefined') {
708 $scope.legacies[sre.record()].sres = {};
710 $scope.legacies[sre.record()].sres[sre.id()] = egCore.idl.toHash(sre);
713 $q.all(promises).then(function(){
714 console.log('done',$scope.legacies);
717 function(resp) { // outright failure
718 console.error('failure',resp);
723 .controller('CloneCtrl',
724 ['$scope','$uibModalInstance','egCore','subs',
725 function($scope , $uibModalInstance , egCore , subs ) {
727 $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
728 $scope.cancel = function () { $uibModalInstance.dismiss() }
730 $scope.find_bib = function () {
732 $scope.bibNotFound = null;
734 if (!$scope.args.bib_id) return;
736 return egCore.net.request(
738 'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
741 function(resp) { // maybe success
743 if (evt = egCore.evt.parse(resp)) {
744 $scope.bibNotFound = $scope.args.bib_id;
745 console.error(evt.toString());
750 $scope.bibNotFound = $scope.args.bib_id;
754 $scope.mvr = egCore.idl.toHash(resp);
755 //console.log($scope.mvr);
757 function(resp) { // outright failure
759 $scope.bibNotFound = $scope.args.bib_id;
764 $scope.$watch("args.bib_id", function(newVal, oldVal) {
765 if (newVal && newVal != oldVal) {
771 .controller('RoutingCtrl',
772 ['$scope','$uibModalInstance','egCore','rowInfo','routes',
773 function($scope , $uibModalInstance , egCore , rowInfo , routes ) {
775 which_radio_button: 'reader'
780 $scope.stream_id = rowInfo['sstr.id'];
781 $scope.stream_label = rowInfo['sstr.routing_label'];
782 $scope.routes = routes;
783 $scope.readerInFocus = true;
784 $scope.ok = function(count) { $uibModalInstance.close($scope.routes) }
785 $scope.cancel = function () { $uibModalInstance.dismiss() }
786 $scope.model_has_changed = false;
787 $scope.find_user = function () {
789 $scope.readerNotFound = null;
790 $scope.reader_obj = null;
791 if (!$scope.args.reader) return;
795 'open-ils.actor.get_barcodes',
796 egCore.auth.token(), egCore.auth.user().ws_ou(),
797 'actor', $scope.args.reader)
799 .then(function(resp) { // get_barcodes
801 if (evt = egCore.evt.parse(resp)) {
802 console.error(evt.toString());
806 if (!resp || !resp[0]) {
807 $scope.readerNotFound = $scope.args.reader;
811 egCore.pcrud.search('au', {
816 'au' : ['mailing_address','billing_address','home_ou']
820 ).then(function(usr) {
821 $scope.reader_obj = egCore.idl.toHash(usr[0]);
825 $scope.add_route = function () {
827 stream: $scope.stream_id
828 ,pos: $scope.routes.length
829 ,note: $scope.args.note
831 if ($scope.args.which_radio_button == 'reader') {
832 new_route.reader = $scope.reader_obj;
834 new_route.department = $scope.args.department;
836 $scope.routes.push(new_route);
837 $scope.model_has_changed = true;
839 function adjust_pos_field() {
841 for (var i = 0; i < $scope.routes.length; i++) {
842 $scope.routes[i].pos = $scope.routes[i].delete_me ? idx : idx++;
844 $scope.model_has_changed = true;
846 $scope.move_route_up = function(r) {
849 var temp = $scope.routes[ pos - 1 ];
850 $scope.routes[ pos - 1 ] = $scope.routes[ pos ];
851 $scope.routes[ pos ] = temp;
855 $scope.move_route_down = function(r) {
857 if (pos < $scope.routes.length - 1) {
858 var temp = $scope.routes[ pos + 1 ];
859 $scope.routes[ pos + 1 ] = $scope.routes[ pos ];
860 $scope.routes[ pos ] = temp;
864 $scope.toggle_delete = function(r) {
865 r.delete_me = ! r.delete_me;
868 $scope.$watch("args.reader", function(newVal, oldVal) {
869 if (newVal && newVal != oldVal) {
875 .controller('NotesCtrl',
876 ['$scope','$uibModalInstance','egCore','note_type','rows','notes',
877 function($scope , $uibModalInstance , egCore , note_type , rows , notes ) {
878 $scope.note_type = note_type;
879 $scope.focusNote = true;
881 creator : egCore.auth.user().id(),
888 $scope.note_list = notes;
890 $scope.ok = function(note) {
892 var return_notes = [];
893 if (note.initials) note.value += ' [' + note.initials + ']';
894 if ( (typeof note.title != 'undefined' && note.title != '')
895 || (typeof note.value != 'undefined' && note.value != '')) {
896 angular.forEach(rows, function (r) {
902 n = new egCore.idl.ssubn();
903 n.subscription(r['id']);
906 n = new egCore.idl.sdistn();
907 n.distribution(r['sdist.id']);
911 n = new egCore.idl.sin();
915 n.creator(note.creator);
917 n['alert'](note['alert']);
920 return_notes.push( n );
923 angular.forEach(notes, function(n) {
924 if (n.ischanged() || n.isdeleted()) {
925 return_notes.push( n );
928 window.return_notes = return_notes;
929 $uibModalInstance.close(return_notes);
932 $scope.cancel = function($event) {
933 $uibModalInstance.dismiss();
934 $event.preventDefault();