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