]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
LP#1537223 Show Username on copy buckets
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / cat / bucket / record / app.js
1 /**
2  * Catalog Record Buckets
3  *
4  * Known Issues
5  *
6  * add-all actions only add visible/fetched items.
7  * remove all from bucket UI leaves busted pagination 
8  *   -- apply a refresh after item removal?
9  * problems with bucket view fetching by record ID instead of bucket item:
10  *   -- dupe bibs always sort to the bottom
11  *   -- dupe bibs result in more records displayed per page than requested
12  *   -- item 'pos' ordering is not honored on initial load.
13  */
14
15 angular.module('egCatRecordBuckets', 
16     ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egMarcMod', 'egHoldingsMod'])
17
18 .config(function($routeProvider, $locationProvider, $compileProvider) {
19     $locationProvider.html5Mode(true);
20     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
21
22     var resolver = {delay : function(egStartup) {return egStartup.go()}};
23
24     $routeProvider.when('/cat/bucket/record/search/:id', {
25         templateUrl: './cat/bucket/record/t_search',
26         controller: 'SearchCtrl',
27         resolve : resolver
28     });
29     
30     $routeProvider.when('/cat/bucket/record/search', {
31         templateUrl: './cat/bucket/record/t_search',
32         controller: 'SearchCtrl',
33         resolve : resolver
34     });
35
36     $routeProvider.when('/cat/bucket/record/pending/:id', {
37         templateUrl: './cat/bucket/record/t_pending',
38         controller: 'PendingCtrl',
39         resolve : resolver
40     });
41
42     $routeProvider.when('/cat/bucket/record/pending', {
43         templateUrl: './cat/bucket/record/t_pending',
44         controller: 'PendingCtrl',
45         resolve : resolver
46     });
47
48     $routeProvider.when('/cat/bucket/record/view/:id', {
49         templateUrl: './cat/bucket/record/t_view',
50         controller: 'ViewCtrl',
51         resolve : resolver
52     });
53
54     $routeProvider.when('/cat/bucket/record/view', {
55         templateUrl: './cat/bucket/record/t_view',
56         controller: 'ViewCtrl',
57         resolve : resolver
58     });
59
60     // default page / bucket view
61     $routeProvider.otherwise({redirectTo : '/cat/bucket/record/view'});
62 })
63
64 /**
65  * bucketSvc allows us to communicate between the search,
66  * pending, and view controllers.  It also allows us to cache
67  * data for each so that data reloads are not needed on every 
68  * tab click (i.e. route persistence).
69  */
70 .factory('bucketSvc', ['$q','egCore', function($q,  egCore) { 
71
72     var service = {
73         allBuckets : [], // un-fleshed user buckets
74         queryString : '', // last run query
75         queryRecords : [], // last run query results
76         currentBucket : null, // currently viewed bucket
77
78         // per-page list collections
79         searchList  : [],
80         pendingList : [],
81         viewList  : [],
82
83         // fetches all staff/biblio buckets for the authenticated user
84         // this function may only be called after startup.
85         fetchUserBuckets : function(force) {
86             if (this.allBuckets.length && !force) return;
87             var self = this;
88             return egCore.net.request(
89                 'open-ils.actor',
90                 'open-ils.actor.container.retrieve_by_class.authoritative',
91                 egCore.auth.token(), egCore.auth.user().id(), 
92                 'biblio', ['staff_client', 'vandelay_queue']
93             ).then(function(buckets) { self.allBuckets = buckets });
94         },
95
96         createBucket : function(name, desc) {
97             var deferred = $q.defer();
98             var bucket = new egCore.idl.cbreb();
99             bucket.owner(egCore.auth.user().id());
100             bucket.name(name);
101             bucket.description(desc || '');
102             bucket.btype('staff_client');
103
104             egCore.net.request(
105                 'open-ils.actor',
106                 'open-ils.actor.container.create',
107                 egCore.auth.token(), 'biblio', bucket
108             ).then(function(resp) {
109                 if (resp) {
110                     if (typeof resp == 'object') {
111                         console.error('bucket create error: ' + js2JSON(resp));
112                         deferred.reject();
113                     } else {
114                         deferred.resolve(resp);
115                     }
116                 }
117             });
118
119             return deferred.promise;
120         },
121
122         // edit the current bucket.  since we edit the 
123         // local object, there's no need to re-fetch.
124         editBucket : function(args) {
125             var bucket = service.currentBucket;
126             bucket.name(args.name);
127             bucket.description(args.desc);
128             bucket.pub(args.pub);
129             return egCore.net.request(
130                 'open-ils.actor',
131                 'open-ils.actor.container.update',
132                 egCore.auth.token(), 'biblio', bucket
133             );
134         }
135     }
136
137     // returns 1 if full refresh is needed
138     // returns 2 if list refresh only is needed
139     service.bucketRefreshLevel = function(id) {
140         if (!service.currentBucket) return 1;
141         if (service.bucketNeedsRefresh) {
142             service.bucketNeedsRefresh = false;
143             service.currentBucket = null;
144             return 1;
145         }
146         if (service.currentBucket.id() != id) return 1;
147         return 2;
148     }
149
150     // returns a promise, resolved with bucket, rejected if bucket is
151     // not fetch-able
152     service.fetchBucket = function(id) {
153         var refresh = service.bucketRefreshLevel(id);
154         if (refresh == 2) return $q.when(service.currentBucket);
155
156         var deferred = $q.defer();
157
158         egCore.net.request(
159             'open-ils.actor',
160             'open-ils.actor.container.flesh.authoritative',
161             egCore.auth.token(), 'biblio', id
162         ).then(function(bucket) {
163             var evt = egCore.evt.parse(bucket);
164             if (evt) {
165                 console.debug(evt);
166                 deferred.reject(evt);
167                 return;
168             }
169             egCore.pcrud.retrieve(
170                 'au', bucket.owner()
171             ).then(function(patron) {
172                 bucket._owner_name = patron.usrname();
173                 bucket._owner_ou = egCore.org.get(patron.home_ou()).shortname();
174             });
175             service.currentBucket = bucket;
176             deferred.resolve(bucket);
177         });
178
179         return deferred.promise;
180     }
181
182     // deletes a single container item from a bucket by container item ID.
183     // promise is rejected on failure
184     service.detachRecord = function(itemId) {
185         var deferred = $q.defer();
186         egCore.net.request(
187             'open-ils.actor',
188             'open-ils.actor.container.item.delete',
189             egCore.auth.token(), 'biblio', itemId
190         ).then(function(resp) { 
191             var evt = egCore.evt.parse(resp);
192             if (evt) {
193                 console.error(evt);
194                 deferred.reject(evt);
195                 return;
196             }
197             console.log('detached bucket item ' + itemId);
198             deferred.resolve(resp);
199         });
200
201         return deferred.promise;
202     }
203
204     service.deleteRecordFromCatalog = function(recordId) {
205         var deferred = $q.defer();
206
207         egCore.net.request(
208             'open-ils.cat',
209             'open-ils.cat.biblio.record_entry.delete',
210             egCore.auth.token(), recordId
211         ).then(function(resp) { 
212             // rather than rejecting the promise in the
213             // case of a failure, we'll let the caller
214             // look for errors -- doing this because AngularJS
215             // does not have a native $q.allSettled() yet.
216             deferred.resolve(resp);
217         });
218         
219         return deferred.promise;
220     }
221
222     // delete bucket by ID.
223     // resolved w/ response on successful delete,
224     // rejected otherwise.
225     service.deleteBucket = function(id) {
226         var deferred = $q.defer();
227         egCore.net.request(
228             'open-ils.actor',
229             'open-ils.actor.container.full_delete',
230             egCore.auth.token(), 'biblio', id
231         ).then(function(resp) {
232             var evt = egCore.evt.parse(resp);
233             if (evt) {
234                 console.error(evt);
235                 deferred.reject(evt);
236                 return;
237             }
238             deferred.resolve(resp);
239         });
240         return deferred.promise;
241     }
242
243     return service;
244 }])
245
246 /**
247  * Top-level controller.  
248  * Hosts functions needed by all controllers.
249  */
250 .controller('RecordBucketCtrl',
251        ['$scope','$location','$q','$timeout','$uibModal',
252         '$window','egCore','bucketSvc',
253 function($scope,  $location,  $q,  $timeout,  $uibModal,  
254          $window,  egCore,  bucketSvc) {
255
256     $scope.bucketSvc = bucketSvc;
257     $scope.bucket = function() { return bucketSvc.currentBucket }
258
259     // tabs: search, pending, view
260     $scope.setTab = function(tab) { 
261         $scope.tab = tab;
262
263         // for bucket selector; must be called after route resolve
264         bucketSvc.fetchUserBuckets(); 
265     };
266
267     $scope.loadBucketFromMenu = function(item, bucket) {
268         if (bucket) return $scope.loadBucket(bucket.id());
269     }
270
271     $scope.loadBucket = function(id) {
272         $location.path(
273             '/cat/bucket/record/' + 
274                 $scope.tab + '/' + encodeURIComponent(id));
275     }
276
277     $scope.addToBucket = function(recs) {
278         if (recs.length == 0) return;
279         bucketSvc.bucketNeedsRefresh = true;
280
281         angular.forEach(recs,
282             function(rec) {
283                 var item = new egCore.idl.cbrebi();
284                 item.bucket(bucketSvc.currentBucket.id());
285                 item.target_biblio_record_entry(rec.id);
286                 egCore.net.request(
287                     'open-ils.actor',
288                     'open-ils.actor.container.item.create', 
289                     egCore.auth.token(), 'biblio', item
290                 ).then(function(resp) {
291
292                     // HACK: add the IDs of the added items so that the size
293                     // of the view list will grow (and update any UI looking at
294                     // the list size).  The data stored is inconsistent, but since
295                     // we are forcing a bucket refresh on the next rendering of 
296                     // the view pane, the list will be repaired.
297                     bucketSvc.currentBucket.items().push(resp);
298                 });
299             }
300         );
301     }
302
303     $scope.openCreateBucketDialog = function() {
304         $uibModal.open({
305             templateUrl: './cat/bucket/share/t_bucket_create',
306             controller: 
307                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
308                 $scope.focusMe = true;
309                 $scope.ok = function(args) { $uibModalInstance.close(args) }
310                 $scope.cancel = function () { $uibModalInstance.dismiss() }
311             }]
312         }).result.then(function (args) {
313             if (!args || !args.name) return;
314             bucketSvc.createBucket(args.name, args.desc).then(
315                 function(id) {
316                     if (!id) return;
317                     bucketSvc.viewList = [];
318                     bucketSvc.allBuckets = []; // reset
319                     bucketSvc.currentBucket = null;
320                     $location.path(
321                         '/cat/bucket/record/' + $scope.tab + '/' + id);
322                 }
323             );
324         });
325     }
326
327     $scope.openEditBucketDialog = function() {
328         $uibModal.open({
329             templateUrl: './cat/bucket/share/t_bucket_edit',
330             controller: 
331                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
332                 $scope.focusMe = true;
333                 $scope.args = {
334                     name : bucketSvc.currentBucket.name(),
335                     desc : bucketSvc.currentBucket.description(),
336                     pub : bucketSvc.currentBucket.pub() == 't'
337                 };
338                 $scope.ok = function(args) { 
339                     if (!args) return;
340                     $scope.actionPending = true;
341                     args.pub = args.pub ? 't' : 'f';
342                     // close the dialog after edit has completed
343                     bucketSvc.editBucket(args).then(
344                         function() { $uibModalInstance.close() });
345                 }
346                 $scope.cancel = function () { $uibModalInstance.dismiss() }
347             }]
348         })
349     }
350
351
352     // opens the delete confirmation and deletes the current
353     // bucket if the user confirms.
354     $scope.openDeleteBucketDialog = function() {
355         $uibModal.open({
356             templateUrl: './cat/bucket/share/t_bucket_delete',
357             controller : 
358                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
359                 $scope.bucket = function() { return bucketSvc.currentBucket }
360                 $scope.ok = function() { $uibModalInstance.close() }
361                 $scope.cancel = function() { $uibModalInstance.dismiss() }
362             }]
363         }).result.then(function () {
364             bucketSvc.deleteBucket(bucketSvc.currentBucket.id())
365             .then(function() {
366                 bucketSvc.allBuckets = [];
367                 $location.path('/cat/bucket/record/view');
368             });
369         });
370     }
371
372     // retrieves the requested bucket by ID
373     $scope.openSharedBucketDialog = function() {
374         $uibModal.open({
375             templateUrl: './cat/bucket/share/t_load_shared',
376             controller : 
377                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
378                 $scope.focusMe = true;
379                 $scope.ok = function(args) { 
380                     if (args && args.id) {
381                         $uibModalInstance.close(args.id) 
382                     }
383                 }
384                 $scope.cancel = function() { $uibModalInstance.dismiss() }
385             }]
386         }).result.then(function(id) {
387             // RecordBucketCtrl $scope is not inherited by the
388             // modal, so we need to call loadBucket from the 
389             // promise resolver.
390             $scope.loadBucket(id);
391         });
392     }
393
394     // opens the record export dialog
395     $scope.openExportBucketDialog = function() {
396         $uibModal.open({
397             templateUrl: './cat/bucket/record/t_bucket_export',
398             controller : 
399                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
400                 $scope.args = {format : 'XML', encoding : 'UTF-8'}; // defaults
401                 $scope.ok = function(args) { $uibModalInstance.close(args) }
402                 $scope.cancel = function() { $uibModalInstance.dismiss() }
403             }]
404         }).result.then(function (args) {
405             if (!args) return;
406             args.containerid = bucketSvc.currentBucket.id();
407
408             var url = '/exporter?containerid=' + args.containerid + 
409                 '&format=' + args.format + '&encoding=' + args.encoding;
410
411             if (args.holdings) url += '&holdings=1';
412
413             // TODO: improve auth cookie handling so this isn't necessary.
414             // today the cookie path is too specific (/eg/staff) for non-staff
415             // UIs to access it.  See services/auth.js
416             url += '&ses=' + egCore.auth.token(); 
417
418             $timeout(function() { $window.open(url) });
419         });
420     }
421 }])
422
423 .controller('SearchCtrl',
424        ['$scope','$routeParams','egCore','bucketSvc',
425 function($scope,  $routeParams,  egCore , bucketSvc) {
426
427     $scope.setTab('search');
428     $scope.focusMe = true;
429     var idQueryHash = {};
430
431     function generateQuery() {
432         if (bucketSvc.queryRecords.length)
433             return {id : bucketSvc.queryRecords};
434         else 
435             return null;
436     }
437
438     $scope.gridControls = {
439         setQuery : function() {return generateQuery()},
440         setSort : function() {return ['id']}
441     }
442
443     // add selected items directly to the pending list
444     $scope.addToPending = function(recs) {
445         angular.forEach(recs, function(rec) {
446             if (bucketSvc.pendingList.filter( // remove dupes
447                 function(r) {return r.id == rec.id}).length) return;
448             bucketSvc.pendingList.push(rec);
449         });
450     }
451
452     $scope.search = function() {
453         $scope.searchList = [];
454         $scope.searchInProgress = true;
455         bucketSvc.queryRecords = [];
456
457         egCore.net.request(
458             'open-ils.search',
459             'open-ils.search.biblio.multiclass.query.staff', {   
460                 limit : 500 // meh
461             }, bucketSvc.queryString, true
462         ).then(function(resp) {
463             bucketSvc.queryRecords = 
464                 resp.ids.map(function(id){return id[0]});
465             $scope.gridControls.setQuery(generateQuery());
466         })['finally'](function() {
467             $scope.searchInProgress = false;
468         });
469     }
470
471     if ($routeParams.id && 
472         (!bucketSvc.currentBucket || 
473             bucketSvc.currentBucket.id() != $routeParams.id)) {
474         // user has accessed this page cold with a bucket ID.
475         // fetch the bucket for display, then set the totalCount
476         // (also for display), but avoid fully fetching the bucket,
477         // since it's premature, in this UI.
478         bucketSvc.fetchBucket($routeParams.id);
479     }
480 }])
481
482 .controller('PendingCtrl',
483        ['$scope','$routeParams','bucketSvc','egGridDataProvider',
484 function($scope,  $routeParams,  bucketSvc , egGridDataProvider) {
485     $scope.setTab('pending');
486
487     var provider = egGridDataProvider.instance({});
488     provider.get = function(offset, count) {
489         return provider.arrayNotifier(
490             bucketSvc.pendingList, offset, count);
491     }
492     $scope.gridDataProvider = provider;
493
494     $scope.resetPendingList = function() {
495         bucketSvc.pendingList = [];
496         $scope.gridDataProvider.refresh();
497     }
498     
499
500     if ($routeParams.id && 
501         (!bucketSvc.currentBucket || 
502             bucketSvc.currentBucket.id() != $routeParams.id)) {
503         // user has accessed this page cold with a bucket ID.
504         // fetch the bucket for display, then set the totalCount
505         // (also for display), but avoid fully fetching the bucket,
506         // since it's premature, in this UI.
507         bucketSvc.fetchBucket($routeParams.id);
508     }
509 }])
510
511 .controller('ViewCtrl',
512        ['$scope','$q','$routeParams','bucketSvc','egCore','$window',
513         '$timeout','egConfirmDialog','$uibModal','egHolds',
514 function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
515          $timeout,  egConfirmDialog,  $uibModal,  egHolds) {
516
517     $scope.setTab('view');
518     $scope.bucketId = $routeParams.id;
519
520     var query;
521     $scope.gridControls = {
522         setQuery : function(q) {
523             if (q) query = q;
524             return query;
525         }
526     };
527
528     function drawBucket() {
529         return bucketSvc.fetchBucket($scope.bucketId).then(
530             function(bucket) {
531                 var ids = bucket.items().map(
532                     function(i){return i.target_biblio_record_entry()}
533                 );
534                 if (ids.length) {
535                     $scope.gridControls.setQuery({id : ids});
536                 } else {
537                     $scope.gridControls.setQuery({});
538                 }
539             }
540         );
541     }
542
543     // runs the transfer title holds action
544     $scope.transfer_holds_to_marked = function(records) {
545         var bib_ids = records.map(function(val) { return val.id; })
546         egHolds.transfer_all_bib_holds_to_marked_title(bib_ids);
547     }
548
549     // opens the record merge dialog
550     $scope.openRecordMergeDialog = function(records) {
551         $uibModal.open({
552             templateUrl: './cat/bucket/record/t_merge_records',
553             size: 'lg',
554             windowClass: 'eg-wide-modal',
555             controller:
556                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
557                 $scope.records = [];
558                 $scope.lead_id = 0;
559                 $scope.merge_profile = null;
560                 $scope.lead = { marc_xml : null };
561                 $scope.editing_inplace = false;
562                 angular.forEach(records, function(rec) {
563                     $scope.records.push({ id : rec.id });
564                 });
565                 $scope.ok = function() {
566                     $uibModalInstance.close({
567                         lead_id : $scope.lead_id,
568                         records : $scope.records,
569                         merge_profile : $scope.merge_profile,
570                         lead : $scope.lead
571                     });
572                 }
573                 $scope.cancel = function () { $uibModalInstance.dismiss() }
574
575                 $scope.merge_marc = function() {
576                     // need lead, at least one sub, and a merge profile
577                     if (!$scope.lead_id) return;
578                     if (!$scope.merge_profile) return;
579
580                     if (!$scope.records.length) {
581                         // if we got here, the last subordinate record
582                         // was likely removed, so let's refresh the
583                         // lead for the sake of a consistent display
584                         egCore.pcrud.retrieve('bre', $scope.lead_id)
585                         .then(function(rec) {
586                             $scope.lead.marc_xml = rec.marc();
587                         });
588                         return;
589                     }
590
591                     var recs = $scope.records.map(function(val) { return val.id; });
592                     recs.unshift($scope.lead_id);
593                     egCore.net.request(
594                         'open-ils.cat',
595                         'open-ils.cat.merge.biblio.per_profile',
596                         egCore.auth.token(),
597                         $scope.merge_profile,
598                         recs
599                     ).then(function(merged) {
600                         if (merged) $scope.lead.marc_xml = merged;
601                     });
602                 }
603                 $scope.$watch('merge_profile', function(newVal, oldVal) {
604                     if (newVal && newVal !== oldVal) {
605                         $scope.merge_marc();
606                     }
607                 });
608
609                 $scope.use_as_lead = function(rec) {
610                     if ($scope.lead_id) {
611                         $scope.records.push({ id : $scope.lead_id });
612                     }
613                     $scope.lead_id = rec.id;
614                     $scope.drop(rec);
615
616                     egCore.pcrud.retrieve('bre', $scope.lead_id)
617                     .then(function(rec) {
618                         $scope.lead.marc_xml = rec.marc();
619                         $scope.merge_marc();
620                     });
621                 }
622                 $scope.drop = function(rec) {
623                     angular.forEach($scope.records, function(val, i) {
624                         if (rec == $scope.records[i]) {
625                             $scope.records.splice(i, 1);
626                         }
627                     });
628                     $scope.merge_marc();
629                 }
630                 $scope.post_edit_inplace = function() {
631                     $scope.editing_inplace = false;
632                 }
633                 $scope.edit_lead_inplace = function() {
634                     $scope.editing_inplace = true;
635                 }
636                 $scope.edit_lead = function() {
637                     var lead = { marc_xml : $scope.lead.marc_xml };
638
639                     // passing the on-save callback this way is a
640                     // hack - this invocation of the MARC editor doesn't
641                     // need it, but for some reason using this stomps
642                     // over the callback set by the other MARC editor
643                     // instance
644                     var callback = $scope.post_edit_inplace;
645
646                     $uibModal.open({
647                         templateUrl: './cat/bucket/record/t_edit_lead_record',
648                         size: 'lg',
649                         controller:
650                             ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
651                             $scope.focusMe = true;
652                             $scope.lead = lead;
653                             $scope.dirty_flag = false;
654                             $scope.ok = function() { $uibModalInstance.close() }
655                             $scope.cancel = function () { $uibModalInstance.dismiss() }
656                             $scope.on_save = callback;
657                         }]
658                     }).result.then(function() {
659                         $scope.lead.marc_xml = lead.marc_xml;
660                     });
661                 };
662             }]
663         }).result.then(function (args) {
664             if (!args.lead_id) return;
665             if (!args.records.length) return;
666
667             function update_bib() {
668                 if (args.merge_profile) {
669                     return egCore.pcrud.retrieve('bre', args.lead_id)
670                     .then(function(rec) {
671                         rec.marc(args.lead.marc_xml);
672                         rec.edit_date('now');
673                         rec.editor(egCore.auth.user().id());
674                         return egCore.pcrud.update(rec);
675                     });
676                 } else {
677                     return $q.when();
678                 }
679             }
680
681             update_bib().then(function() {
682                 egCore.net.request(
683                     'open-ils.cat',
684                     'open-ils.cat.biblio.records.merge',
685                     egCore.auth.token(),
686                     args.lead_id,
687                     args.records.map(function(val) { return val.id; })
688                 ).then(function() {
689                     $window.location.href =
690                         egCore.env.basePath + 'cat/catalog/record/' + args.lead_id;
691                 });
692             });
693         });
694     }
695
696     $scope.showRecords = function(records) {
697         // TODO: probably want to set a limit on the number of
698         //       new tabs one could choose to open at once
699         angular.forEach(records, function(rec) {
700             var url = egCore.env.basePath +
701                       'cat/catalog/record/' +
702                       rec.id;
703             $timeout(function() { $window.open(url, '_blank') });
704         });
705     }
706
707     $scope.batchEdit = function() {
708         var url = egCore.env.basePath +
709                   'cat/catalog/batchEdit/bucket/' + $scope.bucketId;
710         $timeout(function() { $window.open(url, '_blank') });
711     }
712
713     $scope.detachRecords = function(records) {
714         var promises = [];
715         angular.forEach(records, function(rec) {
716             var item = bucketSvc.currentBucket.items().filter(
717                 function(i) {
718                     return (i.target_biblio_record_entry() == rec.id)
719                 }
720             );
721             if (item.length)
722                 promises.push(bucketSvc.detachRecord(item[0].id()));
723         });
724
725         bucketSvc.bucketNeedsRefresh = true;
726         return $q.all(promises).then(drawBucket);
727     }
728
729     $scope.deleteRecordsFromCatalog = function(records) {
730         egConfirmDialog.open(
731             egCore.strings.CONFIRM_DELETE_RECORD_BUCKET_ITEMS_FROM_CATALOG,
732             '',
733             {}
734         ).result.then(function() {
735             var promises = [];
736             angular.forEach(records, function(rec) {
737                 promises.push(bucketSvc.deleteRecordFromCatalog(rec.id));
738             });
739             bucketSvc.bucketNeedsRefresh = true;
740             return $q.all(promises).then(function(results) {
741                 var failures = results.filter(function(result) {
742                     return egCore.evt.parse(result);
743                 }).map(function(result) {
744                     var evt = egCore.evt.parse(result);
745                     if (evt) {
746                         return { recordId: evt.payload, desc: evt.desc };
747                     }
748                 });
749                 if (failures.length) {
750                     $uibModal.open({
751                         templateUrl: './cat/bucket/record/t_records_not_deleted',
752                         controller :
753                             ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
754                             $scope.failures = failures;
755                             $scope.ok = function() { $uibModalInstance.close() }
756                             $scope.cancel = function() { $uibModalInstance.dismiss() }
757                             }]
758                     });
759                 }
760                 drawBucket();
761             });
762         });
763     }
764
765     // fetch the bucket;  on error show the not-allowed message
766     if ($scope.bucketId) 
767         drawBucket()['catch'](function() { $scope.forbidden = true });
768 }])