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