]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/serials/directives/subscription_manager.js
LP2045292 Color contrast for AngularJS patron bills
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / serials / directives / subscription_manager.js
1 angular.module('egSerialsAppDep')
2
3 .directive('egSubscriptionManager', function() {
4     return {
5         transclude: true,
6         restrict:   'E',
7         scope: {
8             bibId : '='
9         },
10         templateUrl: './serials/t_subscription_manager',
11         controller:
12        ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
13         '$uibModal','ngToast','egConfirmDialog',
14 function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
15          $uibModal , ngToast , egConfirmDialog ) {
16
17     $scope.selected_owning_ou = null;
18     $scope.owning_ou_changed = function(org) {
19         $scope.selected_owning_ou = org.id();
20         reload();
21     }
22
23     function reload() {
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;
32                     }
33                 });
34             });
35             $scope.distStreamGridDataProvider.refresh();
36         });
37     }
38     reload();
39
40     $scope.localStreamNames = [];
41     egCore.hatch.getItem('eg.serials.stream_names')
42     .then(function(list) {
43         if (list) $scope.localStreamNames = list;
44     });
45
46     $scope.distStreamGridControls = {
47         activateItem : function (item) { } // TODO
48     };
49     $scope.distStreamGridDataProvider = egGridDataProvider.instance({
50         get : function(offset, count) {
51             return this.arrayNotifier(egSerialsCoreSvc.subList, offset, count);
52         }
53     });
54
55     $scope.need_one_selected = function() {
56         var items = $scope.distStreamGridControls.selectedItems();
57         if (items.length == 1) return false;
58         return true;
59     };
60
61     $scope.receiving_templates = {};
62     egSerialsCoreSvc.fetch_templates(egCore.org.list()).then(function(templates){
63         $scope.receiving_templates = templates;
64     });
65
66     $scope.add_subscription = function() {
67         var new_ssub = egCore.idl.toTypedHash(new egCore.idl.ssub());
68         new_ssub._isnew = true;
69         new_ssub.record_entry = $scope.bibId;
70         new_ssub._focus_me = true;
71         $scope.subscriptions.push(new_ssub);
72         $scope.add_distribution(new_ssub); // since we know we want at least one distribution
73     }
74     $scope.add_distribution = function(ssub, grab_focus) {
75         egCore.org.settings([
76             'serial.default_display_grouping'
77         ]).then(function(set) {
78             var new_sdist = egCore.idl.toTypedHash(new egCore.idl.sdist());
79             new_sdist._isnew = true;
80             new_sdist.subscription = ssub.id;
81             new_sdist.display_grouping = set['serial.default_display_grouping'] || 'chron';
82             if (!angular.isArray(ssub.distributions)){
83                 ssub.distributions = [];
84             }
85             if (grab_focus) {
86                 new_sdist._focus_me = true;
87                 ssub._focus_me = false;
88             }
89             ssub.distributions.push(new_sdist);
90             $scope.add_stream(new_sdist); // since we know we want at least one stream
91         });
92     }
93     $scope.remove_pending_distribution = function(ssub, sdist) {
94         var to_remove = -1;
95         for (var i = 0; i < ssub.distributions.length; i++) {
96             if (ssub.distributions[i] === sdist) {
97                 to_remove = i;
98                 break;
99             }
100         }
101         if (to_remove > -1) {
102             ssub.distributions.splice(to_remove, 1);
103         }
104     }
105     $scope.add_stream = function(sdist, grab_focus) {
106         var new_sstr = egCore.idl.toTypedHash(new egCore.idl.sstr());
107         new_sstr.distribution = sdist.id;
108         new_sstr._isnew = true;
109         if (grab_focus) {
110             new_sstr._focus_me = true;
111             sdist._has_focus = false; // and take focus away from a newly created sdist
112         }
113         if (!angular.isArray(sdist.streams)){
114             sdist.streams = [];
115         }
116         sdist.streams.push(new_sstr);
117         $scope.dirtyForm();
118     }
119     $scope.remove_pending_stream = function(sdist, sstr) {
120         var to_remove = -1;
121         for (var i = 0; i < sdist.streams.length; i++) {
122             if (sdist.streams[i] === sstr) {
123                 to_remove = i;
124                 break;
125             }
126         }
127         if (to_remove > -1) {
128             sdist.streams.splice(to_remove, 1);
129         }
130     }
131
132     $scope.abort_changes = function(form) {
133         reload();
134         form.$setPristine();
135     }
136     function updateLocalStreamNames (new_name) {
137         if (new_name && $scope.localStreamNames.filter(function(x){ return x == new_name}).length == 0) {
138             $scope.localStreamNames.push(new_name);
139             egCore.hatch.setItem('eg.serials.stream_names', $scope.localStreamNames)
140         }
141     }
142
143     $scope.dirtyForm = function () {
144         $scope.ssubform.$dirty = true;
145     }
146
147     $scope.save_subscriptions = function(form) {
148         // traverse through structure and set _ischanged
149         // TODO add more granular dirty input detection
150         angular.forEach($scope.subscriptions, function(ssub) {
151             if (!ssub._isnew) ssub._ischanged = true;
152             angular.forEach(ssub.distributions, function(sdist) {
153                 if (!sdist._isnew) sdist._ischanged = true;
154                 angular.forEach(sdist.streams, function(sstr) {
155                     if (!sstr._isnew) sstr._ischanged = true;
156                     updateLocalStreamNames(sstr.routing_label);
157                 });
158             });
159         });
160
161         var obj = egCore.idl.fromTypedHash($scope.subscriptions);
162
163         // create a bunch of promises that each get resolved upon each
164         // CUD update; that way, we can know when the entire save
165         // operation is completed
166         var promises = [];
167         angular.forEach(obj, function(ssub) {
168             ssub._cud_done = $q.defer();
169             promises.push(ssub._cud_done.promise);
170             angular.forEach(ssub.distributions(), function(sdist) {
171                 sdist._cud_done = $q.defer();
172                 promises.push(sdist._cud_done.promise);
173                 angular.forEach(sdist.streams(), function(sstr) {
174                     sstr._cud_done = $q.defer();
175                     promises.push(sstr._cud_done.promise);
176                 });
177             });
178         });
179
180         angular.forEach(obj, function(ssub) {
181             ssub.owning_lib(ssub.owning_lib().id()); // deflesh
182             egCore.pcrud.apply(ssub).then(function(res) {
183                 var ssub_id = (ssub.isnew() && angular.isObject(res)) ? res.id() : ssub.id();
184                 angular.forEach(ssub.distributions(), function(sdist) {
185                     // set subscription ID just in case it's new
186                     sdist.holding_lib(sdist.holding_lib().id()); // deflesh
187                     sdist.subscription(ssub_id);
188                     egCore.pcrud.apply(sdist).then(function(res) {
189                         var sdist_id = (sdist.isnew() && angular.isObject(res)) ? res.id() : sdist.id();
190                         angular.forEach(sdist.streams(), function(sstr) {
191                             // set distribution ID just in case it's new
192                             sstr.distribution(sdist_id);
193                             egCore.pcrud.apply(sstr).then(function(res) {
194                                 sstr._cud_done.resolve();
195                             });
196                         });
197                     });
198                     sdist._cud_done.resolve();
199                 });
200                 ssub._cud_done.resolve();
201             });
202         });
203         $q.all(promises).then(function(resolutions) {
204             reload();
205             form.$setPristine();
206         });
207     }
208     $scope.delete_subscription = function(rows) {
209         if (rows.length == 0) { return; }
210         var s_rows = rows.filter(function(el) {
211             return typeof el['id'] != 'undefined';
212         });
213         if (s_rows.length == 0) { return; }
214         egConfirmDialog.open(
215             egCore.strings.CONFIRM_DELETE_SUBSCRIPTION,
216             egCore.strings.CONFIRM_DELETE_SUBSCRIPTION_MESSAGE,
217             {count : s_rows.length}
218         ).result.then(function () {
219             var promises = [];
220             angular.forEach(s_rows, function(el) {
221                 promises.push(
222                     egCore.net.request(
223                         'open-ils.serial',
224                         'open-ils.serial.subscription.safe_delete',
225                         egCore.auth.token(),
226                         el['id']
227                     ).then(function(resp){
228                         var evt = egCore.evt.parse(resp);
229                         if (evt) {
230                             ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_DELETE + ' : ' + evt.desc);
231                         } else {
232                             ngToast.success(egCore.strings.SERIALS_SUBSCRIPTION_SUCCESS_DELETE);
233                         }
234                     })
235                 );
236             });
237             $q.all(promises).then(function() {
238                 reload();
239             });
240         });
241     }
242     $scope.delete_distribution = function(rows) {
243         if (rows.length == 0) { return; }
244         var d_rows = rows.filter(function(el) {
245             return typeof el['sdist.id'] != 'undefined';
246         });
247         if (d_rows.length == 0) { return; }
248         egConfirmDialog.open(
249             egCore.strings.CONFIRM_DELETE_DISTRIBUTION,
250             egCore.strings.CONFIRM_DELETE_DISTRIBUTION_MESSAGE,
251             {count : d_rows.length}
252         ).result.then(function () {
253             var promises = [];
254             angular.forEach(d_rows, function(el) {
255                 promises.push(
256                     egCore.net.request(
257                         'open-ils.serial',
258                         'open-ils.serial.distribution.safe_delete',
259                         egCore.auth.token(),
260                         el['sdist.id']
261                     ).then(function(resp){
262                         var evt = egCore.evt.parse(resp);
263                         if (evt) {
264                             ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_DELETE + ' : ' + evt.desc);
265                         } else {
266                             ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_DELETE);
267                         }
268                     })
269                 );
270             });
271             $q.all(promises).then(function() {
272                 reload();
273             });
274         });
275     }
276     $scope.delete_stream = function(rows) {
277         if (rows.length == 0) { return; }
278         var s_rows = rows.filter(function(el) {
279             return typeof el['sstr.id'] != 'undefined';
280         });
281         if (s_rows.length == 0) { return; }
282         egConfirmDialog.open(
283             egCore.strings.CONFIRM_DELETE_STREAM,
284             egCore.strings.CONFIRM_DELETE_STREAM_MESSAGE,
285             {count : s_rows.length}
286         ).result.then(function () {
287             var promises = [];
288             angular.forEach(s_rows, function(el) {
289                 promises.push(
290                     egCore.net.request(
291                         'open-ils.serial',
292                         'open-ils.serial.stream.safe_delete',
293                         egCore.auth.token(),
294                         el['sstr.id']
295                     ).then(function(resp){
296                         var evt = egCore.evt.parse(resp);
297                         if (evt) {
298                             ngToast.danger(egCore.strings.SERIALS_STREAM_FAIL_DELETE + ' : ' + evt.desc);
299                         } else {
300                             ngToast.success(egCore.strings.SERIALS_STREAM_SUCCESS_DELETE);
301                         }
302                     })
303                 );
304             });
305             $q.all(promises).then(function() {
306                 reload();
307             });
308         });
309     }
310     $scope.additional_routing = function(rows) {
311         if (!rows) { return; }
312         var row = rows[0];
313         if (!row) { row = $scope.distStreamGridControls.selectedItems()[0]; }
314         if (row && row['sstr.id']) {
315             egCore.pcrud.search('srlu', {
316                     stream : row['sstr.id']
317                 }, {
318                     flesh : 2,
319                     flesh_fields : {
320                         'srlu' : ['reader'],
321                         'au'  : ['mailing_address','billing_address','home_ou']
322                     },
323                     order_by : { srlu : 'pos' }
324                 },
325                 { atomic : true }
326             ).then(function(list) {
327                 $uibModal.open({
328                     templateUrl: './serials/t_routing_list',
329                     backdrop: 'static',
330                     controller: 'RoutingCtrl',
331                     resolve : {
332                         rowInfo : function() {
333                             return row;
334                         },
335                         routes : function() {
336                             return egCore.idl.toHash(list);
337                         }
338                     }
339                 }).result.then(function(routes) {
340                     // delete all of the routes first;
341                     // it's easiest given the constraints
342                     var deletions = [];
343                     var creations = [];
344                     angular.forEach(routes, function(r) {
345                         var srlu = new egCore.idl.srlu();
346                         srlu.stream(r.stream);
347                         srlu.pos(r.pos);
348                         if (r.reader) {
349                             srlu.reader(r.reader.id);
350                         }
351                         srlu.department(r.department);
352                         srlu.note(r.note);
353                         if (r.id) {
354                             srlu.id(r.id);
355                             var srlu_copy = angular.copy(srlu);
356                             srlu_copy.isdeleted(true);
357                             deletions.push(srlu_copy);
358                         }
359                         if (!r.delete_me) {
360                             srlu.isnew(true);
361                             creations.push(srlu);
362                         }
363                     });
364                     egCore.pcrud.apply(deletions.concat(creations)).then(function(){
365                         reload();
366                     });
367                 });
368             });
369         }
370     }
371     $scope.clone_subscription = function(rows) {
372         if (!rows) { return; }
373         var row = rows[0];
374         $uibModal.open({
375             templateUrl: './serials/t_clone_subscription',
376             controller: 'CloneCtrl',
377             resolve : {
378                 subs : function() {
379                     return rows;
380                 }
381             },
382             windowClass: 'app-modal-window',
383             backdrop: 'static',
384             keyboard: false
385         }).result.then(function(args) {
386             var promises = [];
387             var some_failure = false;
388             var some_success = false;
389             var seen = {};
390             angular.forEach(rows, function(row) { 
391                 //console.log(row);
392                 if (!seen[row.id]) {
393                     seen[row.id] = 1;
394                     promises.push(
395                         egCore.net.request(
396                             'open-ils.serial',
397                             'open-ils.serial.subscription.clone',
398                             egCore.auth.token(),
399                             row.id,
400                             args.bib_id
401                         ).then(
402                             function(resp) {
403                                 var evt = egCore.evt.parse(resp);
404                                 if (evt) { // any way to just throw or return this to the error handler?
405                                     console.log('failure',resp);
406                                     some_failure = true;
407                                     ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_CLONE);
408                                 } else {
409                                     console.log('success',resp);
410                                     some_success = true;
411                                     ngToast.success(egCore.strings.SERIALS_SUBSCRIPTION_SUCCESS_CLONE);
412                                 }
413                             },
414                             function(resp) {
415                                 console.log('failure',resp);
416                                 some_failure = true;
417                                 ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_CLONE);
418                             }
419                         )
420                     );
421                 }
422             });
423             $q.all(promises).then(function() {
424                 reload();
425             });
426         });
427     }
428     $scope.link_mfhd = function(rows) {
429         if (!rows) { return; }
430         var row = rows[0];
431         if (!row['sdist.id']) { return; }
432         $uibModal.open({
433             templateUrl: './serials/t_link_mfhd',
434             controller: 'LinkMFHDCtrl',
435             resolve : {
436                 row : function() {
437                     return rows[0];
438                 },
439                 bibId : function() {
440                     return $scope.bibId;
441                 }
442             },
443             windowClass: 'app-modal-window',
444             backdrop: 'static',
445             keyboard: false
446         }).result.then(function(args) {
447             console.log('modal done', args);
448             egCore.pcrud.search('sdist', {
449                     id: rows[0]['sdist.id']
450                 }, {}, { atomic : true }
451             ).then(function(resp){
452                 var evt = egCore.evt.parse(resp);
453                 if (evt) { // any way to just throw or return this to the error handler?
454                     console.log('failure',resp);
455                     ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
456                 }
457                 var sdist = resp[0];
458                 sdist.ischanged(true);
459                 sdist.summary_method( args.summary_method );
460                 sdist.record_entry( args.which_mfhd );
461                 egCore.pcrud.apply(sdist).then(
462                     function(resp) { // maybe success
463                         console.log('apply',resp);
464                         var evt = egCore.evt.parse(resp);
465                         if (evt) { // any way to just throw or return this to the error handler?
466                             console.log('failure',resp);
467                             ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
468                         } else {
469                             console.log('success',resp);
470                             ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_LINK_MFHD);
471                             reload();
472                         }
473                     },
474                     function(resp) {
475                         console.log('failure',resp);
476                         ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
477                     }
478                 );
479             });
480         });
481     }
482     $scope.apply_binding_template = function(rows) {
483         if (rows.length == 0) { return; }
484         var d_rows = rows.filter(function(el) {
485             return typeof el['sdist.id'] != 'undefined';
486         });
487         if (d_rows.length == 0) { return; }
488         var libs = []; var seen_lib = {};
489         angular.forEach(d_rows, function(el) {
490             if (el['sdist.holding_lib.id'] && !seen_lib[el['sdist.holding_lib.id']]) {
491                 seen_lib[el['sdist.holding_lib.id']] = 1;
492                 libs.push({
493                       id: el['sdist.holding_lib.id'],
494                     name: el['sdist.holding_lib.name'],
495                 });
496             }
497         });
498         $uibModal.open({
499             templateUrl: './serials/t_apply_binding_template',
500             controller: 'ApplyBindingTemplateCtrl',
501             resolve : {
502                 rows : function() {
503                     return d_rows;
504                 },
505                 libs : function() {
506                     return libs;
507                 }
508             },
509             windowClass: 'app-modal-window',
510             backdrop: 'static',
511             keyboard: false
512         }).result.then(function(args) {
513             console.log(args);
514             egCore.pcrud.search('sdist', {
515                     id: d_rows.map(function(el) { return el['sdist.id']; })
516                 }, {}, { atomic : true }
517             ).then(function(resp){
518                 var evt = egCore.evt.parse(resp);
519                 if (evt) { // any way to just throw or return this to the error handler?
520                     console.log('failure',resp);
521                     ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
522                 }
523                 var promises = [];
524                 angular.forEach(resp,function(sdist) {
525                     var promise = $q.defer();
526                     promises.push(promise.promise);
527                     sdist.ischanged(true);
528                     sdist.bind_unit_template(
529                         typeof args.bind_unit_template[sdist.holding_lib()] == 'undefined'
530                         ? null
531                         : args.bind_unit_template[sdist.holding_lib()]
532                     );
533                     egCore.pcrud.apply(sdist).then(
534                         function(resp2) { // maybe success
535                             console.log('apply',resp2);
536                             var evt = egCore.evt.parse(resp2);
537                             if (evt) { // any way to just throw or return this to the error handler?
538                                 console.log('failure',resp2);
539                                 ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
540                             } else {
541                                 console.log('success',resp2);
542                                 ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_BINDING_TEMPLATE);
543                             }
544                             promise.resolve();
545                         },
546                         function(resp2) {
547                             console.log('failure',resp2);
548                             ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
549                             promise.resolve();
550                         }
551                     );
552                 });
553                 $q.all(promises).then(function() {
554                     reload();
555                 });
556             });
557         });
558     }
559     $scope.subscription_notes = function(rows) {
560         return $scope.notes('subscription',rows);
561     }
562     $scope.distribution_notes = function(rows) {
563         return $scope.notes('distribution',rows);
564     }
565     $scope.notes = function(note_type,rows) {
566         if (!rows) { return; }
567
568         function modal(existing_notes) {
569             $uibModal.open({
570                 templateUrl: './serials/t_notes',
571                 animation: true,
572                 controller: 'NotesCtrl',
573                 resolve : {
574                     note_type : function() { return note_type; },
575                     rows : function() {
576                         return rows;
577                     },
578                     notes : function() {
579                         return existing_notes;
580                     }
581                 },
582                 windowClass: 'app-modal-window',
583                 backdrop: 'static',
584                 keyboard: false
585             }).result.then(function(notes) {
586                 console.log('results',notes);
587                 egCore.pcrud.apply(notes).then(
588                     function(a) { console.log('toast here 1',a); },
589                     function(a) { console.log('toast here 2',a); }
590                 );
591             });
592         }
593
594         if (rows.length == 1) {
595             var fm_hint;
596             var search_hash = {};
597             var search_opt = {};
598             switch(note_type) {
599                 case 'subscription':
600                     fm_hint = 'ssubn';
601                     search_hash.subscription = rows[0]['id'];
602                     search_opt.order_by = { ssubn : 'create_date' };
603                 break;
604                 case 'distribution':
605                     fm_hint = 'sdistn';
606                     search_hash.distribution = rows[0]['sdist.id'];
607                     search_opt.order_by = { sdistn : 'create_date' };
608                 break;
609                 case 'item': default:
610                     fm_hint = 'sin';
611                     search_hash.item = rows[0]['si.id'];
612                     search_opt.order_by = { sin : 'create_date' };
613                 break;
614             }
615             egCore.pcrud.search(fm_hint, search_hash, search_opt,
616                 { atomic : true }
617             ).then(function(list) {
618                 modal(list);
619             });
620         } else {
621                 // support batch creation of notes across selections,
622                 // but not editing
623                 modal([]);
624         }
625     }
626
627 }]
628     }
629 })
630
631 .controller('ApplyBindingTemplateCtrl',
632        ['$scope','$q','$uibModalInstance','egCore','egSerialsCoreSvc',
633         'rows','libs',
634 function($scope , $q , $uibModalInstance , egCore , egSerialsCoreSvc ,
635          rows , libs ) {
636     $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
637     $scope.cancel = function () { $uibModalInstance.dismiss() }
638     $scope.libs = libs;
639     $scope.rows = rows;
640     $scope.args = { bind_unit_template : {} };
641     $scope.templates = {};
642     var _libs = libs.map(function(x) {
643         return x.id;
644     });
645     egSerialsCoreSvc.fetch_templates(_libs).then(function(templates){
646         $scope.templates = templates;
647     });
648 }])
649
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);
655     $scope.args = {
656         summary_method: row['sdist.summary_method'] || 'add_to_sre',
657     };
658     if (row['sdist.record_entry']) {
659         $scope.args.which_mfhd = row['sdist.record_entry'].id;
660     }
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 }
667     ).then(
668         function(resp) { // maybe success
669             var evt; if (evt = egCore.evt.parse(resp)) { console.error(evt.toString()); return; }
670             if (!resp) { return; }
671
672             var promises = [];
673             var seen = {};
674
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: [] };
680                     promises.push(
681                         egCore.net.request(
682                             'open-ils.search',
683                             'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
684                             sre.record()
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);
689                         })
690                     );
691                     promises.push(
692                         egCore.net.request(
693                             'open-ils.search',
694                             'open-ils.search.serial.record.bib.retrieve',
695                             sre.record(),
696                             row['owning_lib.id']
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) );
702                                 }
703                             });
704                         })
705                     );
706                 }
707                 if (typeof $scope.legacies[sre.record()].sres == 'undefined') {
708                     $scope.legacies[sre.record()].sres = {};
709                 }
710                 $scope.legacies[sre.record()].sres[sre.id()] = egCore.idl.toHash(sre);
711             });
712
713             $q.all(promises).then(function(){
714                 console.log('done',$scope.legacies);
715             });
716         },
717         function(resp) { // outright failure
718             console.error('failure',resp);
719         }
720     )
721 }])
722
723 .controller('CloneCtrl',
724        ['$scope','$uibModalInstance','egCore','subs',
725 function($scope , $uibModalInstance , egCore , subs ) {
726     $scope.args = {};
727     $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
728     $scope.cancel = function () { $uibModalInstance.dismiss() }
729     $scope.subs = subs;
730     $scope.find_bib = function () {
731
732         $scope.bibNotFound = null;
733         $scope.mvr = null;
734         if (!$scope.args.bib_id) return;
735
736         return egCore.net.request(
737             'open-ils.search',
738             'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
739             $scope.args.bib_id
740         ).then(
741             function(resp) { // maybe success 
742
743                 if (evt = egCore.evt.parse(resp)) {
744                     $scope.bibNotFound = $scope.args.bib_id;
745                     console.error(evt.toString());
746                     return;
747                 }
748
749                 if (!resp) {
750                     $scope.bibNotFound = $scope.args.bib_id;
751                     return;
752                 }
753
754                 $scope.mvr = egCore.idl.toHash(resp);
755                 //console.log($scope.mvr);
756             },
757             function(resp) { // outright failure
758                 console.error(resp);
759                 $scope.bibNotFound = $scope.args.bib_id;
760                 return;
761             }
762         );
763     }
764     $scope.$watch("args.bib_id", function(newVal, oldVal) {
765         if (newVal && newVal != oldVal) {
766             $scope.find_bib();
767         }
768     });
769 }])
770
771 .controller('RoutingCtrl',
772        ['$scope','$uibModalInstance','egCore','rowInfo','routes',
773 function($scope , $uibModalInstance , egCore , rowInfo , routes ) {
774     $scope.args = {
775          which_radio_button: 'reader'
776         ,reader: ''
777         ,department: ''
778         ,delete_me: false
779     };
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 () {
788
789         $scope.readerNotFound = null;
790         $scope.reader_obj = null;
791         if (!$scope.args.reader) return;
792
793         egCore.net.request(
794             'open-ils.actor',
795             'open-ils.actor.get_barcodes',
796             egCore.auth.token(), egCore.auth.user().ws_ou(),
797             'actor', $scope.args.reader)
798
799         .then(function(resp) { // get_barcodes
800
801             if (evt = egCore.evt.parse(resp)) {
802                 console.error(evt.toString());
803                 return;
804             }
805
806             if (!resp || !resp[0]) {
807                 $scope.readerNotFound = $scope.args.reader;
808                 return;
809             }
810
811             egCore.pcrud.search('au', {
812                     id : resp[0].id
813                 }, {
814                     flesh : 1,
815                     flesh_fields : {
816                         'au'  : ['mailing_address','billing_address','home_ou']
817                     }
818                 },
819                 { atomic : true }
820             ).then(function(usr) {
821                 $scope.reader_obj = egCore.idl.toHash(usr[0]);
822             });
823         });
824     }
825     $scope.add_route = function () {
826         var new_route = {
827              stream: $scope.stream_id
828             ,pos: $scope.routes.length
829             ,note: $scope.args.note
830         }
831         if ($scope.args.which_radio_button == 'reader') {
832             new_route.reader = $scope.reader_obj;
833         } else {
834             new_route.department = $scope.args.department;
835         }
836         $scope.routes.push(new_route);
837         $scope.model_has_changed = true;
838     }
839     function adjust_pos_field() {
840         var idx = 0;
841         for (var i = 0; i < $scope.routes.length; i++) {
842             $scope.routes[i].pos = $scope.routes[i].delete_me ? idx : idx++;
843         }
844         $scope.model_has_changed = true;
845     }
846     $scope.move_route_up = function(r) {
847         var pos = r.pos;
848         if (pos > 0) {
849             var temp = $scope.routes[ pos - 1 ];
850             $scope.routes[ pos - 1 ] = $scope.routes[ pos ];
851             $scope.routes[ pos ] = temp;
852             adjust_pos_field();
853         }
854     }
855     $scope.move_route_down = function(r) {
856         var pos = r.pos;
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;
861             adjust_pos_field();
862         }
863     }
864     $scope.toggle_delete = function(r) {
865         r.delete_me = ! r.delete_me;
866         adjust_pos_field();
867     }
868     $scope.$watch("args.reader", function(newVal, oldVal) {
869         if (newVal && newVal != oldVal) {
870             $scope.find_user();
871         }
872     });
873 }])
874
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;
880     $scope.note = {
881         creator : egCore.auth.user().id(),
882         title   : '',
883         value   : '',
884         pub     : false,
885         'alert' : false,
886     };
887
888     $scope.note_list = notes;
889
890     $scope.ok = function(note) {
891
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) {
897                 console.log('r',r);
898                 window.my_r = r;
899                 var n;
900                 switch(note_type) {
901                     case 'subscription':
902                         n = new egCore.idl.ssubn();
903                         n.subscription(r['id']);
904                         break;
905                     case 'distribution':
906                         n = new egCore.idl.sdistn();
907                         n.distribution(r['sdist.id']);
908                         break;
909                     case 'item':
910                     default:
911                         n = new egCore.idl.sin();
912                         n.item(r['si.id']);
913                 }
914                 n.isnew(true);
915                 n.creator(note.creator);
916                 n.pub(note.pub);
917                 n['alert'](note['alert']);
918                 n.title(note.title);
919                 n.value(note.value);
920                 return_notes.push( n );
921             });
922         }
923         angular.forEach(notes, function(n) {
924             if (n.ischanged() || n.isdeleted()) {
925                 return_notes.push( n );
926             }
927         });
928         window.return_notes = return_notes;
929         $uibModalInstance.close(return_notes);
930     }
931
932     $scope.cancel = function($event) {
933         $uibModalInstance.dismiss();
934         $event.preventDefault();
935     }
936 }])