LP#1570091: webstaff: fix transfer Volumes to Previously Marked Library
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / cat / catalog / app.js
1 /**
2  * TPAC Frame App
3  *
4  * currently, this app doesn't use routes for each sub-ui, because 
5  * reloading the catalog each time is sloooow.  better so far to 
6  * swap out divs w/ ng-if / ng-show / ng-hide as needed.
7  *
8  */
9
10 angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','ngLocationUpdate','egCoreMod','egGridMod', 'egMarcMod', 'egUserMod', 'egHoldingsMod', 'ngToast'])
11
12 .config(['ngToastProvider', function(ngToastProvider) {
13   ngToastProvider.configure({
14     verticalPosition: 'bottom',
15     animation: 'fade'
16   });
17 }])
18
19 .config(function($routeProvider, $locationProvider, $compileProvider) {
20     $locationProvider.html5Mode(true);
21     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
22
23     var resolver = {delay : ['egCore','egStartup','egUser', function(egCore, egStartup, egUser) {
24         egCore.env.classLoaders.aous = function() {
25             return egCore.org.settings([
26                 'cat.marc_control_number_identifier'
27             ]).then(function(settings) {
28                 // local settings are cached within egOrg.  Caching them
29                 // again in egEnv just simplifies the syntax for access.
30                 egCore.env.aous = settings;
31             });
32         }
33         egCore.env.loadClasses.push('aous');
34         return egStartup.go()
35     }]};
36
37     $routeProvider.when('/cat/catalog/index', {
38         templateUrl: './cat/catalog/t_catalog',
39         controller: 'CatalogCtrl',
40         resolve : resolver
41     });
42
43     $routeProvider.when('/cat/catalog/retrieve_by_id', {
44         templateUrl: './cat/catalog/t_retrieve_by_id',
45         controller: 'CatalogRecordRetrieve',
46         resolve : resolver
47     });
48
49     $routeProvider.when('/cat/catalog/retrieve_by_tcn', {
50         templateUrl: './cat/catalog/t_retrieve_by_tcn',
51         controller: 'CatalogRecordRetrieve',
52         resolve : resolver
53     });
54
55     $routeProvider.when('/cat/catalog/new_bib', {
56         templateUrl: './cat/catalog/t_new_bib',
57         controller: 'NewBibCtrl',
58         resolve : resolver
59     });
60
61     // create some catalog page-specific mappings
62     $routeProvider.when('/cat/catalog/record/:record_id', {
63         templateUrl: './cat/catalog/t_catalog',
64         controller: 'CatalogCtrl',
65         resolve : resolver
66     });
67
68     // create some catalog page-specific mappings
69     $routeProvider.when('/cat/catalog/record/:record_id/:record_tab', {
70         templateUrl: './cat/catalog/t_catalog',
71         controller: 'CatalogCtrl',
72         resolve : resolver
73     });
74
75     $routeProvider.when('/cat/catalog/batchEdit', {
76         templateUrl: './cat/catalog/t_batchedit',
77         controller: 'BatchEditCtrl',
78         resolve : resolver
79     });
80
81     $routeProvider.when('/cat/catalog/batchEdit/:container_type/:container_id', {
82         templateUrl: './cat/catalog/t_batchedit',
83         controller: 'BatchEditCtrl',
84         resolve : resolver
85     });
86
87     $routeProvider.when('/cat/catalog/vandelay', {
88         templateUrl: './cat/catalog/t_vandelay',
89         controller: 'VandelayCtrl',
90         resolve : resolver
91     });
92
93     $routeProvider.when('/cat/catalog/verifyURLs', {
94         templateUrl: './cat/catalog/t_verifyurls',
95         controller: 'URLVerifyCtrl',
96         resolve : resolver
97     });
98
99     $routeProvider.when('/cat/catalog/manageAuthorities', {
100         templateUrl: './cat/catalog/t_manageauthorities',
101         controller: 'ManageAuthoritiesCtrl',
102         resolve : resolver
103     });
104
105     $routeProvider.when('/cat/catalog/authority/:authority_id/marc_edit', {
106         templateUrl: './cat/catalog/t_authority',
107         controller: 'AuthorityCtrl',
108         resolve : resolver
109     });
110
111     $routeProvider.otherwise({redirectTo : '/cat/catalog/index'});
112 })
113
114
115 /**
116  * */
117 .controller('CatalogRecordRetrieve',
118        ['$scope','$routeParams','$location','$q','egCore',
119 function($scope , $routeParams , $location , $q , egCore ) {
120
121     $scope.focusMe = true;
122
123     // jump to the patron checkout UI
124     function loadRecord(record_id) {
125         $location
126         .path('/cat/catalog/record/' + record_id);
127     }
128
129     $scope.submitId = function(args) {
130         $scope.recordNotFound = null;
131         if (!args.record_id) return;
132
133         // blur so next time it's set to true it will re-apply select()
134         $scope.selectMe = false;
135
136         return loadRecord(args.record_id);
137     }
138
139     $scope.submitTCN = function(args) {
140         $scope.recordNotFound = null;
141         $scope.moreRecordsFound = null;
142         if (!args.record_tcn) return;
143
144         // blur so next time it's set to true it will re-apply select()
145         $scope.selectMe = false;
146
147         // lookup TCN
148         egCore.net.request(
149             'open-ils.search',
150             'open-ils.search.biblio.tcn',
151             args.record_tcn)
152
153         .then(function(resp) { // get_barcodes
154
155             if (evt = egCore.evt.parse(resp)) {
156                 alert(evt); // FIXME
157                 return;
158             }
159
160             if (!resp.count) {
161                 $scope.recordNotFound = args.record_tcn;
162                 $scope.selectMe = true;
163                 return;
164             }
165
166             if (resp.count > 1) {
167                 $scope.moreRecordsFound = args.record_tcn;
168                 $scope.selectMe = true;
169                 return;
170             }
171
172             var record_id = resp.ids[0];
173             return loadRecord(record_id);
174         });
175     }
176
177 }])
178
179 .controller('NewBibCtrl',
180        ['$scope','$routeParams','$location','$window','$q','egCore',
181         'egGridDataProvider','egHoldGridActions','$timeout','holdingsSvc',
182 function($scope , $routeParams , $location , $window , $q , egCore) {
183
184     $scope.have_template = false;
185     $scope.marc_template = '';
186     $scope.stop_unload = false;
187     $scope.template_list = [];
188     $scope.template_name = '';
189     $scope.new_bib_id = 0;
190
191     egCore.net.request(
192         'open-ils.cat',
193         'open-ils.cat.marc_template.types.retrieve'
194     ).then(function(resp) {
195         angular.forEach(resp, function(name) {
196             $scope.template_list.push(name);
197         });
198         $scope.template_list.sort();
199     });
200     $scope.template_name = egCore.hatch.getSessionItem('eg.cat.last_bib_marc_template');
201     if (!$scope.template_name) {
202         egCore.hatch.getItem('cat.default_bib_marc_template').then(function(template) {
203             $scope.template_name = template;
204         });
205     }
206
207     $scope.loadTemplate = function() {
208         if ($scope.template_name) {
209             egCore.net.request(
210                 'open-ils.cat',
211                 'open-ils.cat.biblio.marc_template.retrieve',
212                 $scope.template_name
213             ).then(function(template) {
214                 $scope.marc_template = template;
215                 $scope.have_template = true;
216                 egCore.hatch.setSessionItem('eg.cat.last_bib_marc_template', $scope.template_name);
217             });
218         }
219     }
220
221     $scope.setDefaultTemplate = function() {
222         var hatch_key = "cat.default_bib_marc_template";
223         if ($scope.template_name) {
224             egCore.hatch.setItem(hatch_key, $scope.template_name);
225         } else {
226             egCore.hatch.removeItem(hatch_key);
227         }
228     }
229
230     $scope.$watch('new_bib_id', function(newVal, oldVal) {
231         if (newVal) {
232             $location.path('/cat/catalog/record/' + $scope.new_bib_id);
233         }
234     });
235     
236
237 }])
238
239 .controller('CatalogCtrl',
240        ['$scope','$routeParams','$location','$window','$q','egCore','egHolds','egCirc','egConfirmDialog','ngToast',
241         'egGridDataProvider','egHoldGridActions','$timeout','$modal','holdingsSvc','egUser','conjoinedSvc',
242 function($scope , $routeParams , $location , $window , $q , egCore , egHolds , egCirc , egConfirmDialog , ngToast ,
243          egGridDataProvider , egHoldGridActions , $timeout , $modal , holdingsSvc , egUser , conjoinedSvc) {
244
245     var holdingsSvcInst = new holdingsSvc();
246
247     // set record ID on page load if available...
248     $scope.record_id = $routeParams.record_id;
249     $scope.summary_pane_record;
250
251     if ($routeParams.record_id) $scope.from_route = true;
252     else $scope.from_route = false;
253
254     // will hold a ref to the opac iframe
255     $scope.opac_iframe = null;
256     $scope.parts_iframe = null;
257
258     $scope.search_result_index = 1;
259     $scope.search_result_hit_count = 1;
260
261     $scope.$watch(
262         'opac_iframe.dom.contentWindow.search_result_index',
263         function (n,o) {
264             if (!isNaN(parseInt(n)))
265                 $scope.search_result_index = n + 1;
266         }
267     );
268
269     $scope.$watch(
270         'opac_iframe.dom.contentWindow.search_result_hit_count',
271         function (n,o) {
272             if (!isNaN(parseInt(n)))
273                 $scope.search_result_hit_count = n;
274         }
275     );
276
277     $scope.in_opac_call = false;
278     $scope.opac_call = function (opac_frame_function, force_opac_tab) {
279         if ($scope.opac_iframe) {
280             if (force_opac_tab) $scope.record_tab = 'catalog';
281             $scope.in_opac_call = true;
282             $scope.opac_iframe.dom.contentWindow[opac_frame_function]();
283             if (opac_frame_function == 'rdetailBackToResults') {
284                 $location.update_path('/cat/catalog/index');
285             }
286         }
287     }
288
289     $scope.add_to_record_bucket = function() {
290         var recId = $scope.record_id;
291         return $modal.open({
292             templateUrl: './cat/catalog/t_add_to_bucket',
293             animation: true,
294             size: 'md',
295             controller:
296                    ['$scope','$modalInstance',
297             function($scope , $modalInstance) {
298
299                 $scope.bucket_id = 0;
300                 $scope.newBucketName = '';
301                 $scope.allBuckets = [];
302                 egCore.net.request(
303                     'open-ils.actor',
304                     'open-ils.actor.container.retrieve_by_class.authoritative',
305                     egCore.auth.token(), egCore.auth.user().id(),
306                     'biblio', 'staff_client'
307                 ).then(function(buckets) { $scope.allBuckets = buckets; });
308
309                 $scope.add_to_bucket = function() {
310                     var item = new egCore.idl.cbrebi();
311                     item.bucket($scope.bucket_id);
312                     item.target_biblio_record_entry(recId);
313                     egCore.net.request(
314                         'open-ils.actor',
315                         'open-ils.actor.container.item.create',
316                         egCore.auth.token(), 'biblio', item
317                     ).then(function(resp) {
318                         $modalInstance.close();
319                     });
320                 }
321
322                 $scope.add_to_new_bucket = function() {
323                     var bucket = new egCore.idl.cbreb();
324                     bucket.owner(egCore.auth.user().id());
325                     bucket.name($scope.newBucketName);
326                     bucket.description('');
327                     bucket.btype('staff_client');
328
329                     egCore.net.request(
330                         'open-ils.actor',
331                         'open-ils.actor.container.create',
332                         egCore.auth.token(), 'biblio', bucket
333                     ).then(function(bucket) {
334                         $scope.bucket_id = bucket;
335                         $scope.add_to_bucket();
336                     });
337                 }
338
339                 $scope.cancel = function() {
340                     $modalInstance.dismiss();
341                 }
342             }]
343         });
344     }
345
346     $scope.current_overlay_target     = egCore.hatch.getLocalItem('eg.cat.marked_overlay_record');
347     $scope.current_voltransfer_target = egCore.hatch.getLocalItem('eg.cat.marked_volume_transfer_record');
348     $scope.current_conjoined_target   = egCore.hatch.getLocalItem('eg.cat.marked_conjoined_record');
349
350     $scope.markConjoined = function () {
351         $scope.current_conjoined_target = $scope.record_id;
352         egCore.hatch.setLocalItem('eg.cat.marked_conjoined_record',$scope.record_id);
353         ngToast.create(egCore.strings.MARK_CONJ_TARGET);
354     };
355
356     $scope.markVolTransfer = function () {
357         ngToast.create(egCore.strings.MARK_VOL_TARGET);
358         $scope.current_voltransfer_target = $scope.record_id;
359         egCore.hatch.setLocalItem('eg.cat.marked_volume_transfer_record',$scope.record_id);
360     };
361
362     $scope.markOverlay = function () {
363         $scope.current_overlay_target = $scope.record_id;
364         egCore.hatch.setLocalItem('eg.cat.marked_overlay_record',$scope.record_id);
365         ngToast.create(egCore.strings.MARK_OVERLAY_TARGET);
366     };
367
368     $scope.clearRecordMarks = function () {
369         $scope.current_overlay_target     = null;
370         $scope.current_voltransfer_target = null;
371         $scope.current_conjoined_target   = null;
372         egCore.hatch.removeLocalItem('eg.cat.marked_volume_transfer_record');
373         egCore.hatch.removeLocalItem('eg.cat.marked_conjoined_record');
374         egCore.hatch.removeLocalItem('eg.cat.marked_overlay_record');
375     }
376
377     $scope.stop_unload = false;
378     $scope.$watch('stop_unload',
379         function(newVal, oldVal) {
380             if (newVal && newVal != oldVal && $scope.opac_iframe) {
381                 $($scope.opac_iframe.dom.contentWindow).on('beforeunload', function(){
382                     return 'There is unsaved data in this record.'
383                 });
384             } else {
385                 if ($scope.opac_iframe)
386                     $($scope.opac_iframe.dom.contentWindow).off('beforeunload');
387             }
388         }
389     );
390
391     // Set the "last bib" cookie, if we have that
392     if ($scope.record_id)
393         egCore.hatch.setLocalItem("eg.cat.last_record_retrieved", $scope.record_id);
394
395     $scope.refresh_record_callback = function (record_id) {
396         egCore.pcrud.retrieve('bre', record_id, {
397             flesh : 1,
398             flesh_fields : {
399                 bre : ['simple_record','creator','editor']
400             }
401         }).then(function(rec) {
402             rec.owner(egCore.org.get(rec.owner()));
403             $scope.summary_pane_record = rec;
404         });
405
406         return record_id;
407     }
408
409     // also set it when the iframe changes to a new record
410     $scope.handle_page = function(url) {
411
412         if (!url || url == 'about:blank') {
413             // nothing loaded.  If we already have a record ID, leave it.
414             return;
415         }
416
417         var match = url.match(/\/+opac\/+record\/+(\d+)/);
418         if (match) {
419             $scope.record_id = match[1];
420             egCore.hatch.setLocalItem("eg.cat.last_record_retrieved", $scope.record_id);
421             $scope.holdings_record_id_changed($scope.record_id);
422             conjoinedSvc.fetch($scope.record_id).then(function(){
423                 $scope.conjoinedGridDataProvider.refresh();
424             });
425             init_parts_url();
426             $location.update_path('/cat/catalog/record/' + $scope.record_id);
427         } else {
428             delete $scope.record_id;
429             $scope.from_route = false;
430         }
431
432         // child scope is executing this function, so our digest doesn't fire ... thus,
433         $scope.$apply();
434
435         if (!$scope.in_opac_call) {
436             if ($scope.record_id) {
437                 $scope.default_tab = egCore.hatch.getLocalItem( 'eg.cat.default_record_tab' );
438                 tab = $routeParams.record_tab || $scope.default_tab || 'catalog';
439             } else {
440                 tab = $routeParams.record_tab || 'catalog';
441             }
442             $scope.set_record_tab(tab);
443         } else {
444             $scope.in_opac_call = false;
445         }
446     }
447
448     // xulG catalog handlers
449     $scope.handlers = { }
450
451     // ------------------------------------------------------------------
452     // Conjoined items
453
454     $scope.conjoinedGridControls = {};
455     $scope.conjoinedGridDataProvider = egGridDataProvider.instance({
456         get : function(offset, count) {
457             return this.arrayNotifier(conjoinedSvc.items, offset, count);
458         }
459     });
460
461     $scope.changeConjoinedType = function () {
462         var peers = egCore.idl.Clone($scope.conjoinedGridControls.selectedItems());
463         angular.forEach(peers, function (p) {
464             p.target_copy(p.target_copy().id());
465             p.peer_type(p.peer_type().id());
466         });
467
468         var conjoinedGridDataProviderRef = $scope.conjoinedGridDataProvider;
469
470         return $modal.open({
471             templateUrl: './cat/catalog/t_conjoined_selector',
472             animation: true,
473             controller:
474                    ['$scope','$modalInstance',
475             function($scope , $modalInstance) {
476                 $scope.update = true;
477
478                 $scope.peer_type = null;
479                 $scope.peer_type_list = [];
480                 conjoinedSvc.get_peer_types().then(function(list){
481                     $scope.peer_type_list = list;
482                 });
483     
484                 $scope.ok = function(type) {
485                     var promises = [];
486     
487                     angular.forEach(peers, function (p) {
488                         p.ischanged(1);
489                         p.peer_type(type);
490                         promises.push(egCore.pcrud.update(p));
491                     });
492     
493                     return $q.all(promises)
494                         .then(function(){$modalInstance.close()})
495                         .then(function(){return conjoinedSvc.fetch()})
496                         .then(function(){conjoinedGridDataProviderRef.refresh()});
497                 }
498     
499                 $scope.cancel = function($event) {
500                     $modalInstance.dismiss();
501                     $event.preventDefault();
502                 }
503             }]
504         });
505         
506     }
507
508     $scope.refreshConjoined = function () {
509         conjoinedSvc.fetch($scope.record_id)
510         .then(function(){$scope.conjoinedGridDataProvider.refresh();});
511     }
512
513     $scope.deleteSelectedConjoined = function () {
514         var peers = $scope.conjoinedGridControls.selectedItems();
515
516         if (peers.length > 0) {
517             egConfirmDialog.open(
518                 egCore.strings.CONFIRM_DELETE_PEERS,
519                 egCore.strings.CONFIRM_DELETE_PEERS_MESSAGE,
520                 {peers : peers.length}
521             ).result.then(function() {
522                 angular.forEach(peers, function (p) {
523                     p.isdeleted(1);
524                 });
525
526                 egCore.pcrud.remove(peers).then(function() {
527                     return conjoinedSvc.fetch();
528                 }).then(function() {
529                     $scope.conjoinedGridDataProvider.refresh();
530                 });
531             });
532         }
533     }
534     if ($scope.record_id)
535         conjoinedSvc.fetch($scope.record_id);
536
537     // ------------------------------------------------------------------
538     // Holdings
539
540     $scope.holdingsGridControls = {
541         activateItem : function (item) {
542             $scope.selectedHoldingsVolCopyEdit();
543         }
544     };
545     $scope.holdingsGridDataProvider = egGridDataProvider.instance({
546         get : function(offset, count) {
547             return this.arrayNotifier(holdingsSvcInst.copies, offset, count);
548         }
549     });
550
551     $scope.add_copies_to_bucket = function() {
552         var copy_list = gatherSelectedHoldingsIds();
553         if (copy_list.length == 0) return;
554
555         return $modal.open({
556             templateUrl: './cat/catalog/t_add_to_bucket',
557             animation: true,
558             size: 'md',
559             controller:
560                    ['$scope','$modalInstance',
561             function($scope , $modalInstance) {
562
563                 $scope.bucket_id = 0;
564                 $scope.newBucketName = '';
565                 $scope.allBuckets = [];
566
567                 egCore.net.request(
568                     'open-ils.actor',
569                     'open-ils.actor.container.retrieve_by_class.authoritative',
570                     egCore.auth.token(), egCore.auth.user().id(),
571                     'copy', 'staff_client'
572                 ).then(function(buckets) { $scope.allBuckets = buckets; });
573
574                 $scope.add_to_bucket = function() {
575                     var promises = [];
576                     angular.forEach(copy_list, function (cp) {
577                         var item = new egCore.idl.ccbi()
578                         item.bucket($scope.bucket_id);
579                         item.target_copy(cp);
580                         promises.push(
581                             egCore.net.request(
582                                 'open-ils.actor',
583                                 'open-ils.actor.container.item.create',
584                                 egCore.auth.token(), 'copy', item
585                             )
586                         );
587
588                         return $q.all(promises).then(function() {
589                             $modalInstance.close();
590                         });
591                     });
592                 }
593
594                 $scope.add_to_new_bucket = function() {
595                     var bucket = new egCore.idl.ccb();
596                     bucket.owner(egCore.auth.user().id());
597                     bucket.name($scope.newBucketName);
598                     bucket.description('');
599                     bucket.btype('staff_client');
600
601                     return egCore.net.request(
602                         'open-ils.actor',
603                         'open-ils.actor.container.create',
604                         egCore.auth.token(), 'copy', bucket
605                     ).then(function(bucket) {
606                         $scope.bucket_id = bucket;
607                         $scope.add_to_bucket();
608                     });
609                 }
610
611                 $scope.cancel = function() {
612                     $modalInstance.dismiss();
613                 }
614             }]
615         });
616     }
617
618     $scope.requestItems = function() {
619         var copy_list = gatherSelectedHoldingsIds();
620         if (copy_list.length == 0) return;
621
622         return $modal.open({
623             templateUrl: './cat/catalog/t_request_items',
624             animation: true,
625             controller:
626                    ['$scope','$modalInstance',
627             function($scope , $modalInstance) {
628                 $scope.user = null;
629                 $scope.first_user_fetch = true;
630
631                 $scope.hold_data = {
632                     hold_type : 'C',
633                     copy_list : copy_list,
634                     pickup_lib: egCore.org.get(egCore.auth.user().ws_ou()),
635                     user      : egCore.auth.user().id()
636                 };
637
638                 egUser.get( $scope.hold_data.user ).then(function(u) {
639                     $scope.user = u;
640                     $scope.barcode = u.card().barcode();
641                     $scope.user_name = egUser.format_name(u);
642                     $scope.hold_data.user = u.id();
643                 });
644
645                 $scope.user_name = '';
646                 $scope.barcode = '';
647                 $scope.$watch('barcode', function (n) {
648                     if (!$scope.first_user_fetch) {
649                         egUser.getByBarcode(n).then(function(u) {
650                             $scope.user = u;
651                             $scope.user_name = egUser.format_name(u);
652                             $scope.hold_data.user = u.id();
653                         }, function() {
654                             $scope.user = null;
655                             $scope.user_name = '';
656                             delete $scope.hold_data.user;
657                         });
658                     }
659                     $scope.first_user_fetch = false;
660                 });
661
662                 $scope.ok = function(h) {
663                     var args = {
664                         patronid  : h.user,
665                         hold_type : h.hold_type,
666                         pickup_lib: h.pickup_lib.id(),
667                         depth     : 0
668                     };
669
670                     egCore.net.request(
671                         'open-ils.circ',
672                         'open-ils.circ.holds.test_and_create.batch.override',
673                         egCore.auth.token(), args, h.copy_list
674                     );
675
676                     $modalInstance.close();
677                 }
678
679                 $scope.cancel = function($event) {
680                     $modalInstance.dismiss();
681                     $event.preventDefault();
682                 }
683             }]
684         });
685     }
686
687     $scope.replaceBarcodes = function() {
688         var copy_list = gatherSelectedRawCopies();
689         if (copy_list.length == 0) return;
690
691         var holdingsGridDataProviderRef = $scope.holdingsGridDataProvider;
692
693         angular.forEach(copy_list, function (cp) {
694             $modal.open({
695                 templateUrl: './cat/share/t_replace_barcode',
696                 animation: true,
697                 controller:
698                            ['$scope','$modalInstance',
699                     function($scope , $modalInstance) {
700                         $scope.isModal = true;
701                         $scope.focusBarcode = false;
702                         $scope.focusBarcode2 = true;
703                         $scope.barcode1 = cp.barcode();
704
705                         $scope.updateBarcode = function() {
706                             $scope.copyNotFound = false;
707                             $scope.updateOK = false;
708                 
709                             egCore.pcrud.search('acp',
710                                 {deleted : 'f', barcode : $scope.barcode1})
711                             .then(function(copy) {
712                 
713                                 if (!copy) {
714                                     $scope.focusBarcode = true;
715                                     $scope.copyNotFound = true;
716                                     return;
717                                 }
718                 
719                                 $scope.copyId = copy.id();
720                                 copy.barcode($scope.barcode2);
721                 
722                                 egCore.pcrud.update(copy).then(function(stat) {
723                                     $scope.updateOK = stat;
724                                     $scope.focusBarcode = true;
725                                     holdingsSvc.fetchAgain().then(function (){
726                                         holdingsGridDataProviderRef.refresh();
727                                     });
728                                 });
729
730                             });
731                             $modalInstance.close();
732                         }
733
734                         $scope.cancel = function($event) {
735                             $modalInstance.dismiss();
736                             $event.preventDefault();
737                         }
738                     }
739                 ]
740             });
741         });
742     }
743
744     // refresh the list of holdings when the record_id is changed.
745     $scope.holdings_record_id_changed = function(id) {
746         if ($scope.record_id != id) $scope.record_id = id;
747         console.log('record id changed to ' + id + ', loading new holdings');
748         holdingsSvcInst.fetch({
749             rid : $scope.record_id,
750             org : $scope.holdings_ou,
751             copy: $scope.holdings_show_copies,
752             vol : $scope.holdings_show_vols,
753             empty: $scope.holdings_show_empty
754         }).then(function() {
755             $scope.holdingsGridDataProvider.refresh();
756         });
757     }
758
759     // refresh the list of holdings when the filter lib is changed.
760     $scope.holdings_ou = egCore.org.get(egCore.auth.user().ws_ou());
761     $scope.holdings_ou_changed = function(org) {
762         $scope.holdings_ou = org;
763         holdingsSvcInst.fetch({
764             rid : $scope.record_id,
765             org : $scope.holdings_ou,
766             copy: $scope.holdings_show_copies,
767             vol : $scope.holdings_show_vols,
768             empty: $scope.holdings_show_empty
769         }).then(function() {
770             $scope.holdingsGridDataProvider.refresh();
771         });
772     }
773
774     $scope.holdings_cb_changed = function(cb,newVal,norefresh) {
775         $scope[cb] = newVal;
776         egCore.hatch.setItem('cat.' + cb, newVal);
777         if (!norefresh) holdingsSvcInst.fetch({
778             rid : $scope.record_id,
779             org : $scope.holdings_ou,
780             copy: $scope.holdings_show_copies,
781             vol : $scope.holdings_show_vols,
782             empty: $scope.holdings_show_empty
783         }).then(function() {
784             $scope.holdingsGridDataProvider.refresh();
785         });
786     }
787
788     egCore.hatch.getItem('cat.holdings_show_vols').then(function(x){
789         if (typeof x ==  'undefined') x = true;
790         $scope.holdings_cb_changed('holdings_show_vols',x,true);
791         $('#holdings_show_vols').prop('checked', x);
792     }).then(function(){
793         egCore.hatch.getItem('cat.holdings_show_copies').then(function(x){
794             if (typeof x ==  'undefined') x = true;
795             $scope.holdings_cb_changed('holdings_show_copies',x,true);
796             $('#holdings_show_copies').prop('checked', x);
797         }).then(function(){
798             egCore.hatch.getItem('cat.holdings_show_empty').then(function(x){
799                 if (typeof x ==  'undefined') x = true;
800                 $scope.holdings_cb_changed('holdings_show_empty',x);
801                 $('#holdings_show_empty').prop('checked', x);
802             })
803         })
804     });
805
806     $scope.vols_not_shown = function () {
807         return !$scope.holdings_show_vols;
808     }
809
810     $scope.copies_not_shown = function () {
811         return !$scope.holdings_show_copies;
812     }
813
814     $scope.holdings_checkbox_handler = function (item) {
815         $scope.holdings_cb_changed(item.checkbox,item.checked);
816     }
817
818     function gatherSelectedHoldingsIds () {
819         var cp_id_list = [];
820         angular.forEach(
821             $scope.holdingsGridControls.selectedItems(),
822             function (item) { cp_id_list = cp_id_list.concat(item.id_list) }
823         );
824         return cp_id_list;
825     }
826
827     function gatherSelectedRawCopies () {
828         var cp_list = [];
829         angular.forEach(
830             $scope.holdingsGridControls.selectedItems(),
831             function (item) { if (item.raw) cp_list = cp_list.concat(item.raw) }
832         );
833         return cp_list;
834     }
835
836     function gatherSelectedEmptyVolumeIds () {
837         var cn_id_list = [];
838         angular.forEach(
839             $scope.holdingsGridControls.selectedItems(),
840             function (item) {
841                 if (item.copy_count == 0)
842                     cn_id_list.push(item.call_number.id)
843             }
844         );
845         return cn_id_list;
846     }
847
848     function gatherSelectedVolumeIds () {
849         var cn_id_list = [];
850         angular.forEach(
851             $scope.holdingsGridControls.selectedItems(),
852             function (item) {
853                 if (cn_id_list.indexOf(item.call_number.id) == -1)
854                     cn_id_list.push(item.call_number.id)
855             }
856         );
857         return cn_id_list;
858     }
859
860     $scope.selectedHoldingsDelete = function (vols, copies) {
861
862         var cnHash = {};
863         var perCnCopies = {};
864
865         var cn_count = 0;
866         var cp_count = 0;
867
868         angular.forEach(
869             $scope.holdingsGridControls.selectedItems(),
870             function (item) {
871                 if (vols && item.raw_call_number) {
872                     cnHash[item.call_number.id] = egCore.idl.Clone(item.raw_call_number);
873                     cnHash[item.call_number.id].isdeleted(1);
874                     cn_count++;
875                 } else if (copies) {
876                     angular.forEach(egCore.idl.Clone(item.raw), function (cp) {
877                         cp.isdeleted(1);
878                         cp_count++;
879                         var cn_id = cp.call_number().id();
880                         if (!cnHash[cn_id]) {
881                             cnHash[cn_id] = cp.call_number();
882                             perCnCopies[cn_id] = [cp];
883                         } else {
884                             perCnCopies[cn_id].push(cp);
885                         }
886                         cp.call_number(cn_id); // prevent loops in JSON-ification
887                     });
888
889                 }
890             }
891         );
892
893         angular.forEach(perCnCopies, function (v, k) {
894             if (vols) {
895                 cnHash[k].isdeleted(1);
896                 cn_count++;
897             }
898             cnHash[k].copies(v);
899         });
900
901         cnList = [];
902         angular.forEach(cnHash, function (v, k) {
903             cnList.push(v);
904         });
905
906         if (cnList.length == 0) return;
907
908         var flags = {};
909         if (vols && copies) flags.force_delete_copies = 1;
910
911         egConfirmDialog.open(
912             egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES,
913             egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES_MESSAGE,
914             {copies : cp_count, volumes : cn_count}
915         ).result.then(function() {
916             egCore.net.request(
917                 'open-ils.cat',
918                 'open-ils.cat.asset.volume.fleshed.batch.update.override',
919                 egCore.auth.token(), cnList, 1, flags
920             ).then(function(update_count) {
921                 holdingsSvcInst.fetchAgain().then(function() {
922                     $scope.holdingsGridDataProvider.refresh();
923                 });
924             });
925         });
926     }
927     $scope.selectedHoldingsCopyDelete = function () { $scope.selectedHoldingsDelete(false,true) }
928     $scope.selectedHoldingsVolCopyDelete = function () { $scope.selectedHoldingsDelete(true,true) }
929     $scope.selectedHoldingsEmptyVolCopyDelete = function () { $scope.selectedHoldingsDelete(true,false) }
930
931     spawnHoldingsAdd = function (vols,copies){
932         var raw = [];
933         if (copies) { // just a copy on existing volumes
934             angular.forEach(gatherSelectedVolumeIds(), function (v) {
935                 raw.push( {callnumber : v} );
936             });
937         } else if (vols) {
938             angular.forEach(
939                 $scope.holdingsGridControls.selectedItems(),
940                 function (item) {
941                     raw.push({owner : item.owner_id});
942                 }
943             );
944         }
945
946         if (raw.length == 0) raw.push({});
947
948         egCore.net.request(
949             'open-ils.actor',
950             'open-ils.actor.anon_cache.set_value',
951             null, 'edit-these-copies', {
952                 record_id: $scope.record_id,
953                 raw: raw,
954                 hide_vols : false,
955                 hide_copies : false
956             }
957         ).then(function(key) {
958             if (key) {
959                 var url = egCore.env.basePath + 'cat/volcopy/' + key;
960                 $timeout(function() { $window.open(url, '_blank') });
961             } else {
962                 alert('Could not create anonymous cache key!');
963             }
964         });
965     }
966     $scope.selectedHoldingsVolCopyAdd = function () { spawnHoldingsAdd(true,false) }
967     $scope.selectedHoldingsCopyAdd = function () { spawnHoldingsAdd(false,true) }
968
969     spawnHoldingsEdit = function (hide_vols,hide_copies){
970         egCore.net.request(
971             'open-ils.actor',
972             'open-ils.actor.anon_cache.set_value',
973             null, 'edit-these-copies', {
974                 record_id: $scope.record_id,
975                 copies: gatherSelectedHoldingsIds(),
976                 raw: gatherSelectedEmptyVolumeIds().map(
977                     function(v){ return { callnumber : v } }
978                 ),
979                 hide_vols : hide_vols,
980                 hide_copies : hide_copies
981             }
982         ).then(function(key) {
983             if (key) {
984                 var url = egCore.env.basePath + 'cat/volcopy/' + key;
985                 $timeout(function() { $window.open(url, '_blank') });
986             } else {
987                 alert('Could not create anonymous cache key!');
988             }
989         });
990     }
991     $scope.selectedHoldingsVolCopyEdit = function () { spawnHoldingsEdit(false,false) }
992     $scope.selectedHoldingsVolEdit = function () { spawnHoldingsEdit(false,true) }
993     $scope.selectedHoldingsCopyEdit = function () { spawnHoldingsEdit(true,false) }
994
995     $scope.selectedHoldingsItemStatus = function (){
996         var url = egCore.env.basePath + 'cat/item/search/' + gatherSelectedHoldingsIds().join(',')
997         $timeout(function() { $window.open(url, '_blank') });
998     }
999
1000     $scope.markVolAsItemTarget = function() {
1001         if ($scope.holdingsGridControls.selectedItems()[0].call_number.id) { // cn.id missing when vols are collapsed
1002             egCore.hatch.setLocalItem(
1003                 'eg.cat.item_transfer_target',
1004                 $scope.holdingsGridControls.selectedItems()[0].call_number.id
1005             );
1006             ngToast.create(egCore.strings.MARK_ITEM_TARGET);
1007         }
1008     }
1009
1010     $scope.markLibAsVolTarget = function() {
1011         return $modal.open({
1012             templateUrl: './cat/catalog/t_choose_vol_target_lib',
1013             animation: true,
1014             controller:
1015                    ['$scope','$modalInstance',
1016             function($scope , $modalInstance) {
1017
1018                 var orgId = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target') || 1;
1019                 $scope.org = egCore.org.get(orgId);
1020                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
1021                 $scope.ok = function(org) {
1022                     egCore.hatch.setLocalItem(
1023                         'eg.cat.volume_transfer_target',
1024                         org.id()
1025                     );
1026                     $modalInstance.close();
1027                 }
1028                 $scope.cancel = function($event) {
1029                     $modalInstance.dismiss();
1030                     $event.preventDefault();
1031                 }
1032             }]
1033         });
1034     }
1035     $scope.markLibFromSelectedAsVolTarget = function() {
1036         egCore.hatch.setLocalItem(
1037             'eg.cat.volume_transfer_target',
1038             $scope.holdingsGridControls.selectedItems()[0].owner_id
1039         );
1040         ngToast.create(egCore.strings.MARK_VOL_TARGET);
1041     }
1042
1043     $scope.selectedHoldingsItemStatusDetail = function (){
1044         angular.forEach(
1045             gatherSelectedHoldingsIds(),
1046             function (cid) {
1047                 var url = egCore.env.basePath +
1048                           'cat/item/' + cid;
1049                 $timeout(function() { $window.open(url, '_blank') });
1050             }
1051         );
1052     }
1053
1054     $scope.transferVolumesToRecord = function (){
1055         var target_record = egCore.hatch.getLocalItem('eg.cat.marked_volume_transfer_record');
1056         if (!target_record) return;
1057         if ($scope.record_id == target_record) return;
1058         var items = $scope.holdingsGridControls.selectedItems();
1059         if (!items.length) return;
1060
1061         var vols_to_move   = {};
1062         angular.forEach(items, function(item) {
1063             if (!(item.call_number.owning_lib in vols_to_move)) {
1064                 vols_to_move[item.call_number.owning_lib] = new Array;
1065             }
1066             vols_to_move[item.call_number.owning_lib].push(item.call_number.id);
1067         });
1068
1069         var promises = [];        
1070         angular.forEach(vols_to_move, function(vols, owning_lib) {
1071             promises.push(egCore.net.request(
1072                 'open-ils.cat',
1073                 'open-ils.cat.asset.volume.batch.transfer.override',
1074                 egCore.auth.token(), {
1075                     docid   : target_record,
1076                     lib     : owning_lib,
1077                     volumes : vols
1078                 }
1079             ));
1080         });
1081         $q.all(promises).then(function(success) {
1082             if (success) {
1083                 ngToast.create(egCore.strings.VOLS_TRANSFERED);
1084                 holdingsSvcInst.fetchAgain().then(function() {
1085                     $scope.holdingsGridDataProvider.refresh();
1086                 });
1087             } else {
1088                 alert('Could not transfer volumes!');
1089             }
1090         });
1091     }
1092
1093     function transferVolumes(new_record){
1094         var xfer_target = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target');
1095
1096         if (xfer_target) {
1097             egCore.net.request(
1098                 'open-ils.cat',
1099                 'open-ils.cat.asset.volume.batch.transfer.override',
1100                 egCore.auth.token(), {
1101                     docid   : (new_record ? new_record : $scope.record_id),
1102                     lib     : xfer_target,
1103                     volumes : gatherSelectedVolumeIds()
1104                 }
1105             ).then(function(success) {
1106                 if (success) {
1107                     ngToast.create(egCore.strings.VOLS_TRANSFERED);
1108                     holdingsSvcInst.fetchAgain().then(function() {
1109                         $scope.holdingsGridDataProvider.refresh();
1110                     });
1111                 } else {
1112                     alert('Could not transfer volumes!');
1113                 }
1114             });
1115         }
1116         
1117     }
1118
1119     $scope.transferVolumesToLibrary = function() {
1120         transferVolumes();
1121     }
1122
1123     $scope.transferVolumesToRecordAndLibrary = function() {
1124         var target_record = egCore.hatch.getLocalItem('eg.cat.marked_volume_transfer_record');
1125         if (!target_record) return;
1126         transferVolumes(target_record);
1127     }
1128
1129     // this "transfers" selected copies to a new owning library,
1130     // auto-creating volumes and deleting unused volumes as required.
1131     $scope.changeItemOwningLib = function() {
1132         var xfer_target = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target');
1133         var items = $scope.holdingsGridControls.selectedItems();
1134         if (!xfer_target || !items.length) {
1135             return;
1136         }
1137         var vols_to_move   = {};
1138         var copies_to_move = {};
1139         angular.forEach(items, function(item) {
1140             if (item.call_number.owning_lib != xfer_target) {
1141                 if (item.call_number.id in vols_to_move) {
1142                     copies_to_move[item.call_number.id].push(item.id);
1143                 } else {
1144                     vols_to_move[item.call_number.id] = item.call_number;
1145                     copies_to_move[item.call_number.id] = new Array;
1146                     copies_to_move[item.call_number.id].push(item.id);
1147                 }
1148             }
1149         });
1150     
1151         var promises = [];
1152         angular.forEach(vols_to_move, function(vol) {
1153             promises.push(egCore.net.request(
1154                 'open-ils.cat',
1155                 'open-ils.cat.call_number.find_or_create',
1156                 egCore.auth.token(),
1157                 vol.label,
1158                 vol.record,
1159                 xfer_target,
1160                 vol.prefix.id,
1161                 vol.suffix.id,
1162                 vol.label_class
1163             ).then(function(resp) {
1164                 var evt = egCore.evt.parse(resp);
1165                 if (evt) return;
1166                 return egCore.net.request(
1167                     'open-ils.cat',
1168                     'open-ils.cat.transfer_copies_to_volume',
1169                     egCore.auth.token(),
1170                     resp.acn_id,
1171                     copies_to_move[vol.id]
1172                 );
1173             }));
1174         });
1175         $q.all(promises).then(function() {
1176             ngToast.create(egCore.strings.ITEMS_TRANSFERED);
1177             holdingsSvcInst.fetchAgain().then(function() {
1178                 $scope.holdingsGridDataProvider.refresh();
1179             });
1180         });
1181     }
1182
1183     $scope.transferItems = function (){
1184         var xfer_target = egCore.hatch.getLocalItem('eg.cat.item_transfer_target');
1185         var copy_ids = gatherSelectedHoldingsIds();
1186         if (xfer_target && copy_ids.length > 0) {
1187             egCore.net.request(
1188                 'open-ils.cat',
1189                 'open-ils.cat.transfer_copies_to_volume',
1190                 egCore.auth.token(),
1191                 xfer_target,
1192                 copy_ids
1193             ).then(
1194                 function(resp) { // oncomplete
1195                     var evt = egCore.evt.parse(resp);
1196                     if (evt) {
1197                         egConfirmDialog.open(
1198                             egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_TITLE,
1199                             egCore.strings.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_BODY,
1200                             {'evt_desc': evt.desc}
1201                         ).result.then(function() {
1202                             egCore.net.request(
1203                                 'open-ils.cat',
1204                                 'open-ils.cat.transfer_copies_to_volume.override',
1205                                 egCore.auth.token(),
1206                                 xfer_target,
1207                                 copy_ids,
1208                                 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
1209                             ).then(function(resp) {
1210                                 holdingsSvcInst.fetchAgain().then(function() {
1211                                     $scope.holdingsGridDataProvider.refresh();
1212                                 });
1213                             });
1214                         });
1215                     } else {
1216                         ngToast.create(egCore.strings.ITEMS_TRANSFERED);
1217                         holdingsSvcInst.fetchAgain().then(function() {
1218                             $scope.holdingsGridDataProvider.refresh();
1219                         });
1220                     }
1221                 },
1222                 null, // onerror
1223                 null // onprogress
1224             )
1225         }
1226     }
1227
1228     $scope.selectedHoldingsItemStatusTgrEvt = function (){
1229         angular.forEach(
1230             gatherSelectedHoldingsIds(),
1231             function (cid) {
1232                 var url = egCore.env.basePath +
1233                           'cat/item/' + cid + '/triggered_events';
1234                 $timeout(function() { $window.open(url, '_blank') });
1235             }
1236         );
1237     }
1238
1239     $scope.selectedHoldingsItemStatusHolds = function (){
1240         angular.forEach(
1241             gatherSelectedHoldingsIds(),
1242             function (cid) {
1243                 var url = egCore.env.basePath +
1244                           'cat/item/' + cid + '/holds';
1245                 $timeout(function() { $window.open(url, '_blank') });
1246             }
1247         );
1248     }
1249
1250     $scope.selectedHoldingsDamaged = function () {
1251         egCirc.mark_damaged(gatherSelectedHoldingsIds()).then(function() {
1252             holdingsSvcInst.fetchAgain().then(function() {
1253                 $scope.holdingsGridDataProvider.refresh();
1254             });
1255         });
1256     }
1257
1258     $scope.selectedHoldingsMissing = function () {
1259         egCirc.mark_missing(gatherSelectedHoldingsIds()).then(function() {
1260             holdingsSvcInst.fetchAgain().then(function() {
1261                 $scope.holdingsGridDataProvider.refresh();
1262             });
1263         });
1264     }
1265
1266     $scope.attach_to_peer_bib = function() {
1267         var copy_list = gatherSelectedHoldingsIds();
1268         if (copy_list.length == 0) return;
1269
1270         egCore.hatch.getItem('eg.cat.marked_conjoined_record').then(function(target_record) {
1271             if (!target_record) return;
1272
1273             return $modal.open({
1274                 templateUrl: './cat/catalog/t_conjoined_selector',
1275                 animation: true,
1276                 controller:
1277                        ['$scope','$modalInstance',
1278                 function($scope , $modalInstance) {
1279                     $scope.update = false;
1280
1281                     $scope.peer_type = null;
1282                     $scope.peer_type_list = [];
1283                     conjoinedSvc.get_peer_types().then(function(list){
1284                         $scope.peer_type_list = list;
1285                     });
1286     
1287                     $scope.ok = function(type) {
1288                         var promises = [];
1289     
1290                         angular.forEach(copy_list, function (cp) {
1291                             var n = new egCore.idl.bpbcm();
1292                             n.isnew(true);
1293                             n.peer_record(target_record);
1294                             n.target_copy(cp);
1295                             n.peer_type(type);
1296                             promises.push(egCore.pcrud.create(n));
1297                         });
1298     
1299                         return $q.all(promises).then(function(){$modalInstance.close()});
1300                     }
1301     
1302                     $scope.cancel = function($event) {
1303                         $modalInstance.dismiss();
1304                         $event.preventDefault();
1305                     }
1306                 }]
1307             });
1308         });
1309     }
1310
1311
1312     // ------------------------------------------------------------------
1313     // Holds 
1314     var provider = egGridDataProvider.instance({});
1315     $scope.hold_grid_data_provider = provider;
1316     $scope.grid_actions = egHoldGridActions;
1317     $scope.grid_actions.refresh = function () { provider.refresh() };
1318     $scope.hold_grid_controls = {};
1319
1320     var hold_ids = []; // current list of holds
1321     function fetchHolds(offset, count) {
1322         var ids = hold_ids.slice(offset, offset + count);
1323         return egHolds.fetch_holds(ids).then(null, null,
1324             function(hold_data) { 
1325                 return hold_data;
1326             }
1327         );
1328     }
1329
1330     provider.get = function(offset, count) {
1331         if ($scope.record_tab != 'holds') return $q.when();
1332         var deferred = $q.defer();
1333         hold_ids = []; // no caching ATM
1334
1335         // fetch the IDs
1336         egCore.net.request(
1337             'open-ils.circ',
1338             'open-ils.circ.holds.retrieve_all_from_title',
1339             egCore.auth.token(), $scope.record_id, 
1340             {pickup_lib : egCore.org.descendants($scope.pickup_ou.id(), true)}
1341         ).then(
1342             function(hold_data) {
1343                 angular.forEach(hold_data, function(list, type) {
1344                     hold_ids = hold_ids.concat(list);
1345                 });
1346                 fetchHolds(offset, count).then(
1347                     deferred.resolve, null, deferred.notify);
1348             }
1349         );
1350
1351         return deferred.promise;
1352     }
1353
1354     $scope.detail_view = function(action, user_data, items) {
1355         if (h = items[0]) {
1356             $scope.detail_hold_id = h.hold.id();
1357         }
1358     }
1359
1360     $scope.list_view = function(items) {
1361          $scope.detail_hold_id = null;
1362     }
1363
1364     // refresh the list of record holds when the pickup lib is changed.
1365     $scope.pickup_ou = egCore.org.get(egCore.auth.user().ws_ou());
1366     $scope.pickup_ou_changed = function(org) {
1367         $scope.pickup_ou = org;
1368         provider.refresh();
1369     }
1370
1371     $scope.print_holds = function() {
1372         var holds = [];
1373         angular.forEach($scope.hold_grid_controls.allItems(), function(item) {
1374             holds.push({
1375                 hold : egCore.idl.toHash(item.hold),
1376                 patron_last : item.patron_last,
1377                 patron_alias : item.patron_alias,
1378                 patron_barcode : item.patron_barcode,
1379                 copy : egCore.idl.toHash(item.copy),
1380                 volume : egCore.idl.toHash(item.volume),
1381                 title : item.mvr.title(),
1382                 author : item.mvr.author()
1383             });
1384         });
1385
1386         egCore.print.print({
1387             context : 'receipt', 
1388             template : 'holds_for_bib', 
1389             scope : {holds : holds}
1390         });
1391     }
1392
1393     $scope.mark_hold_transfer_dest = function() {
1394         egCore.hatch.setLocalItem(
1395             'eg.circ.hold.title_transfer_target', $scope.record_id);
1396         ngToast.create(egCore.strings.HOLD_TRANSFER_DEST_MARKED);
1397     }
1398
1399     // UI presents this option as "all holds"
1400     $scope.transfer_holds_to_marked = function() {
1401         var hold_ids = $scope.hold_grid_controls.allItems().map(
1402             function(hold_data) {return hold_data.hold.id()});
1403         egHolds.transfer_to_marked_title(hold_ids);
1404     }
1405
1406     // ------------------------------------------------------------------
1407     // Initialize the selected tab
1408
1409     function init_cat_url() {
1410         // Set the initial catalog URL.  This only happens once.
1411         // The URL is otherwise generated through user navigation.
1412         if ($scope.catalog_url) return; 
1413
1414         var url = $location.absUrl().replace(/\/staff.*/, '/opac/advanced');
1415
1416         // A record ID in the path indicates a request for the record-
1417         // specific page.
1418         if ($routeParams.record_id) {
1419             url = url.replace(/advanced/, '/record/' + $scope.record_id);
1420         }
1421
1422         $scope.catalog_url = url;
1423     }
1424
1425     function init_parts_url() {
1426         $scope.parts_url = $location
1427             .absUrl()
1428             .replace(
1429                 /\/staff.*/,
1430                 '/conify/global/biblio/monograph_part?r='+$scope.record_id
1431             );
1432     }
1433
1434     $scope.set_record_tab = function(tab) {
1435         $scope.record_tab = tab;
1436
1437         switch(tab) {
1438
1439             case 'monoparts':
1440                 init_parts_url();
1441                 break;
1442
1443             case 'catalog':
1444                 init_cat_url();
1445                 break;
1446
1447             case 'holds':
1448                 $scope.detail_hold_record_id = $scope.record_id; 
1449                 // refresh the holds grid
1450                 provider.refresh();
1451                 break;
1452         }
1453     }
1454
1455     $scope.set_default_record_tab = function() {
1456         egCore.hatch.setLocalItem(
1457             'eg.cat.default_record_tab', $scope.record_tab);
1458         $timeout(function(){$scope.default_tab = $scope.record_tab});
1459     }
1460
1461     var tab;
1462     if ($scope.record_id) {
1463         $scope.default_tab = egCore.hatch.getLocalItem( 'eg.cat.default_record_tab' );
1464         tab = $routeParams.record_tab || $scope.default_tab || 'catalog';
1465
1466     } else {
1467         tab = $routeParams.record_tab || 'catalog';
1468     }
1469     $scope.set_record_tab(tab);
1470
1471 }])
1472
1473 .controller('AuthorityCtrl',
1474        ['$scope','$routeParams','$location','$window','$q','egCore',
1475 function($scope , $routeParams , $location , $window , $q , egCore) {
1476
1477     // set record ID on page load if available...
1478     $scope.authority_id = $routeParams.authority_id;
1479
1480     if ($routeParams.authority_id) $scope.from_route = true;
1481     else $scope.from_route = false;
1482
1483     $scope.stop_unload = false;
1484 }])
1485
1486 .controller('URLVerifyCtrl',
1487        ['$scope','$location',
1488 function($scope , $location) {
1489     $scope.verifyurls_url = $location.absUrl().replace(/\/staff.*/, '/url_verify/sessions');
1490 }])
1491
1492 .controller('VandelayCtrl',
1493        ['$scope','$location',
1494 function($scope , $location) {
1495     $scope.vandelay_url = $location.absUrl().replace(/\/staff.*/, '/vandelay/vandelay');
1496 }])
1497
1498 .controller('ManageAuthoritiesCtrl',
1499        ['$scope','$location',
1500 function($scope , $location) {
1501     $scope.manageauthorities_url = $location.absUrl().replace(/\/staff.*/, '/cat/authority/list');
1502 }])
1503
1504 .controller('BatchEditCtrl',
1505        ['$scope','$location','$routeParams',
1506 function($scope , $location , $routeParams) {
1507     $scope.batchedit_url = $location.absUrl().replace(/\/eg.*/, '/opac/extras/merge_template');
1508     if ($routeParams.container_type) {
1509         switch ($routeParams.container_type) {
1510             case 'bucket':
1511                 $scope.batchedit_url += '?recordSource=b&containerid=' + $routeParams.container_id;
1512                 break;
1513             case 'record':
1514                 $scope.batchedit_url += '?recordSource=r&recid=' + $routeParams.container_id;
1515                 break;
1516         };
1517     }
1518 }])
1519
1520  
1521 .filter('boolText', function(){
1522     return function (v) {
1523         return v == 't';
1524     }
1525 })
1526
1527 .factory('conjoinedSvc', 
1528        ['egCore','$q',
1529 function(egCore , $q) {
1530
1531     var service = {
1532         items : [], // record search results
1533         index : 0, // search grid index
1534         rid : null
1535     };
1536
1537     service.flesh = {   
1538         flesh : 4, 
1539         flesh_fields : {
1540             bpbcm : ['target_copy','peer_type'],
1541             acp : ['call_number'],
1542             acn : ['record'],
1543             bre : ['simple_record']
1544         },
1545         // avoid fetching the MARC blob by specifying which
1546         // fields on the bre to select.  More may be needed.
1547         // note that fleshed fields are explicitly selected.
1548         select : { bre : ['id'] },
1549         order_by : { bpbcm : ['id'] },
1550     }
1551
1552     // resolved with the last received copy
1553     service.fetch = function(rid) {
1554         if (!rid && !service.rid) return $q.when();
1555
1556         if (rid) service.rid = rid;
1557         service.items = [];
1558         service.index = 0;
1559
1560         return egCore.pcrud.search(
1561             'bpbcm',
1562             {peer_record : service.rid},
1563             service.flesh,
1564             {atomic : true}
1565         ).then( function(list) { // finished
1566             service.items = list;
1567             return service.items;
1568         });
1569     }
1570
1571     // returns a promise resolved with the list of peer bib types
1572     service.get_peer_types = function() {
1573         if (egCore.env.bpt)
1574             return $q.when(egCore.env.bpt.list);
1575
1576         return egCore.pcrud.retrieveAll('bpt', null, {atomic : true})
1577         .then(function(list) {
1578             egCore.env.absorbList(list, 'bpt');
1579             return list;
1580         });
1581     };
1582
1583     return service;
1584 }])
1585
1586