]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
LP#1692116 - Action trigger validator fix for paid Long Overdue items
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / cat / bucket / copy / app.js
1 /**
2  * Copy 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('egCatCopyBuckets', 
16     ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
17
18 .config(function($routeProvider, $locationProvider, $compileProvider) {
19     $locationProvider.html5Mode(true);
20     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
21         
22     var resolver = {delay : function(egStartup) {return egStartup.go()}};
23
24     $routeProvider.when('/cat/bucket/copy/pending/:id', {
25         templateUrl: './cat/bucket/copy/t_pending',
26         controller: 'PendingCtrl',
27         resolve : resolver
28     });
29
30     $routeProvider.when('/cat/bucket/copy/pending', {
31         templateUrl: './cat/bucket/copy/t_pending',
32         controller: 'PendingCtrl',
33         resolve : resolver
34     });
35
36     $routeProvider.when('/cat/bucket/copy/view/:id', {
37         templateUrl: './cat/bucket/copy/t_view',
38         controller: 'ViewCtrl',
39         resolve : resolver
40     });
41
42     $routeProvider.when('/cat/bucket/copy/view', {
43         templateUrl: './cat/bucket/copy/t_view',
44         controller: 'ViewCtrl',
45         resolve : resolver
46     });
47
48     // default page / bucket view
49     $routeProvider.otherwise({redirectTo : '/cat/bucket/copy/view'});
50 })
51
52 /**
53  * bucketSvc allows us to communicate between the pending
54  * and view controllers.  It also allows us to cache
55  * data for each so that data reloads are not needed on every 
56  * tab click (i.e. route persistence).
57  */
58 .factory('bucketSvc', ['$q','egCore', function($q,  egCore) { 
59
60     var service = {
61         allBuckets : [], // un-fleshed user buckets
62         barcodeString : '', // last scanned barcode
63         barcodeRecords : [], // last scanned barcode results
64         currentBucket : null, // currently viewed bucket
65
66         // per-page list collections
67         pendingList : [],
68         viewList  : [],
69
70         // fetches all staff/copy buckets for the authenticated user
71         // this function may only be called after startup.
72         fetchUserBuckets : function(force) {
73             if (this.allBuckets.length && !force) return;
74             var self = this;
75             return egCore.net.request(
76                 'open-ils.actor',
77                 'open-ils.actor.container.retrieve_by_class.authoritative',
78                 egCore.auth.token(), egCore.auth.user().id(), 
79                 'copy', 'staff_client'
80             ).then(function(buckets) { self.allBuckets = buckets });
81         },
82
83         createBucket : function(name, desc) {
84             var deferred = $q.defer();
85             var bucket = new egCore.idl.ccb();
86             bucket.owner(egCore.auth.user().id());
87             bucket.name(name);
88             bucket.description(desc || '');
89             bucket.btype('staff_client');
90
91             egCore.net.request(
92                 'open-ils.actor',
93                 'open-ils.actor.container.create',
94                 egCore.auth.token(), 'copy', bucket
95             ).then(function(resp) {
96                 if (resp) {
97                     if (typeof resp == 'object') {
98                         console.error('bucket create error: ' + js2JSON(resp));
99                         deferred.reject();
100                     } else {
101                         deferred.resolve(resp);
102                     }
103                 }
104             });
105
106             return deferred.promise;
107         },
108
109         // edit the current bucket.  since we edit the 
110         // local object, there's no need to re-fetch.
111         editBucket : function(args) {
112             var bucket = service.currentBucket;
113             bucket.name(args.name);
114             bucket.description(args.desc);
115             bucket.pub(args.pub);
116             return egCore.net.request(
117                 'open-ils.actor',
118                 'open-ils.actor.container.update',
119                 egCore.auth.token(), 'copy', bucket
120             );
121         }
122     }
123
124     // returns 1 if full refresh is needed
125     // returns 2 if list refresh only is needed
126     service.bucketRefreshLevel = function(id) {
127         if (!service.currentBucket) return 1;
128         if (service.bucketNeedsRefresh) {
129             service.bucketNeedsRefresh = false;
130             service.currentBucket = null;
131             return 1;
132         }
133         if (service.currentBucket.id() != id) return 1;
134         return 2;
135     }
136
137     // returns a promise, resolved with bucket, rejected if bucket is
138     // not fetch-able
139     service.fetchBucket = function(id) {
140         var refresh = service.bucketRefreshLevel(id);
141         if (refresh == 2) return $q.when(service.currentBucket);
142
143         var deferred = $q.defer();
144
145         egCore.net.request(
146             'open-ils.actor',
147             'open-ils.actor.container.flesh.authoritative',
148             egCore.auth.token(), 'copy', id
149         ).then(function(bucket) {
150             var evt = egCore.evt.parse(bucket);
151             if (evt) {
152                 console.debug(evt);
153                 deferred.reject(evt);
154                 return;
155             }
156             egCore.pcrud.retrieve(
157                 'au', bucket.owner(),
158                 {flesh : 1, flesh_fields : {au : ["card"]}}
159             ).then(function(patron) {
160                 // On the off chance no barcode is present (it's not 
161                 // required) use the patron username as the identifier.
162                 bucket._owner_ident = patron.card() ? 
163                     patron.card().barcode() : patron.usrname();
164                 bucket._owner_name = patron.family_name();
165                 bucket._owner_ou = egCore.org.get(patron.home_ou()).shortname();
166             });
167
168             service.currentBucket = bucket;
169             deferred.resolve(bucket);
170         });
171
172         return deferred.promise;
173     }
174
175     // deletes a single container item from a bucket by container item ID.
176     // promise is rejected on failure
177     service.detachCopy = function(itemId) {
178         var deferred = $q.defer();
179         egCore.net.request(
180             'open-ils.actor',
181             'open-ils.actor.container.item.delete',
182             egCore.auth.token(), 'copy', itemId
183         ).then(function(resp) { 
184             var evt = egCore.evt.parse(resp);
185             if (evt) {
186                 console.error(evt);
187                 deferred.reject(evt);
188                 return;
189             }
190             console.log('detached bucket item ' + itemId);
191             deferred.resolve(resp);
192         });
193
194         return deferred.promise;
195     }
196
197     // delete bucket by ID.
198     // resolved w/ response on successful delete,
199     // rejected otherwise.
200     service.deleteBucket = function(id) {
201         var deferred = $q.defer();
202         egCore.net.request(
203             'open-ils.actor',
204             'open-ils.actor.container.full_delete',
205             egCore.auth.token(), 'copy', id
206         ).then(function(resp) {
207             var evt = egCore.evt.parse(resp);
208             if (evt) {
209                 console.error(evt);
210                 deferred.reject(evt);
211                 return;
212             }
213             deferred.resolve(resp);
214         });
215         return deferred.promise;
216     }
217
218     return service;
219 }])
220
221 /**
222  * Top-level controller.  
223  * Hosts functions needed by all controllers.
224  */
225 .controller('CopyBucketCtrl',
226        ['$scope','$location','$q','$timeout','$uibModal',
227         '$window','egCore','bucketSvc',
228 function($scope,  $location,  $q,  $timeout,  $uibModal,  
229          $window,  egCore,  bucketSvc) {
230
231     $scope.bucketSvc = bucketSvc;
232     $scope.bucket = function() { return bucketSvc.currentBucket }
233
234     // tabs: search, pending, view
235     $scope.setTab = function(tab) { 
236         $scope.tab = tab;
237
238         // for bucket selector; must be called after route resolve
239         bucketSvc.fetchUserBuckets(); 
240     };
241
242     $scope.loadBucketFromMenu = function(item, bucket) {
243         if (bucket) return $scope.loadBucket(bucket.id());
244     }
245
246     $scope.loadBucket = function(id) {
247         $location.path(
248             '/cat/bucket/copy/' + 
249                 $scope.tab + '/' + encodeURIComponent(id));
250     }
251
252     $scope.addToBucket = function(recs) {
253         if (recs.length == 0) return;
254         bucketSvc.bucketNeedsRefresh = true;
255
256         angular.forEach(recs,
257             function(rec) {
258                 var item = new egCore.idl.ccbi();
259                 item.bucket(bucketSvc.currentBucket.id());
260                 item.target_copy(rec.id);
261                 egCore.net.request(
262                     'open-ils.actor',
263                     'open-ils.actor.container.item.create', 
264                     egCore.auth.token(), 'copy', item
265                 ).then(function(resp) {
266
267                     // HACK: add the IDs of the added items so that the size
268                     // of the view list will grow (and update any UI looking at
269                     // the list size).  The data stored is inconsistent, but since
270                     // we are forcing a bucket refresh on the next rendering of 
271                     // the view pane, the list will be repaired.
272                     bucketSvc.currentBucket.items().push(resp);
273                 });
274             }
275         );
276     }
277
278     $scope.openCreateBucketDialog = function() {
279         $uibModal.open({
280             templateUrl: './cat/bucket/share/t_bucket_create',
281             backdrop: 'static',
282             controller: 
283                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
284                 $scope.focusMe = true;
285                 $scope.ok = function(args) { $uibModalInstance.close(args) }
286                 $scope.cancel = function () { $uibModalInstance.dismiss() }
287             }]
288         }).result.then(function (args) {
289             if (!args || !args.name) return;
290             bucketSvc.createBucket(args.name, args.desc).then(
291                 function(id) {
292                     if (!id) return;
293                     bucketSvc.viewList = [];
294                     bucketSvc.allBuckets = []; // reset
295                     bucketSvc.currentBucket = null;
296                     $location.path(
297                         '/cat/bucket/copy/' + $scope.tab + '/' + id);
298                 }
299             );
300         });
301     }
302
303     $scope.openEditBucketDialog = function() {
304         $uibModal.open({
305             templateUrl: './cat/bucket/share/t_bucket_edit',
306             backdrop: 'static',
307             controller: 
308                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
309                 $scope.focusMe = true;
310                 $scope.args = {
311                     name : bucketSvc.currentBucket.name(),
312                     desc : bucketSvc.currentBucket.description(),
313                     pub : bucketSvc.currentBucket.pub() == 't'
314                 };
315                 $scope.ok = function(args) { 
316                     if (!args) return;
317                     $scope.actionPending = true;
318                     args.pub = args.pub ? 't' : 'f';
319                     // close the dialog after edit has completed
320                     bucketSvc.editBucket(args).then(
321                         function() { $uibModalInstance.close() });
322                 }
323                 $scope.cancel = function () { $uibModalInstance.dismiss() }
324             }]
325         })
326     }
327
328     // opens the delete confirmation and deletes the current
329     // bucket if the user confirms.
330     $scope.openDeleteBucketDialog = function() {
331         $uibModal.open({
332             templateUrl: './cat/bucket/share/t_bucket_delete',
333             backdrop: 'static',
334             controller : 
335                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
336                 $scope.bucket = function() { return bucketSvc.currentBucket }
337                 $scope.ok = function() { $uibModalInstance.close() }
338                 $scope.cancel = function() { $uibModalInstance.dismiss() }
339             }]
340         }).result.then(function () {
341             bucketSvc.deleteBucket(bucketSvc.currentBucket.id())
342             .then(function() {
343                 bucketSvc.allBuckets = [];
344                 $location.path('/cat/bucket/copy/view');
345             });
346         });
347     }
348
349     // retrieves the requested bucket by ID
350     $scope.openSharedBucketDialog = function() {
351         $uibModal.open({
352             templateUrl: './cat/bucket/share/t_load_shared',
353             backdrop: 'static',
354             controller :
355                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
356                 $scope.focusMe = true;
357                 $scope.ok = function(args) {
358                     if (args && args.id) {
359                         $uibModalInstance.close(args.id)
360                     }
361                 }
362                 $scope.cancel = function() { $uibModalInstance.dismiss() }
363             }]
364         }).result.then(function(id) {
365             // RecordBucketCtrl $scope is not inherited by the
366             // modal, so we need to call loadBucket from the
367             // promise resolver.
368             $scope.loadBucket(id);
369         });
370     }
371
372 }])
373
374 .controller('PendingCtrl',
375        ['$scope','$routeParams','bucketSvc','egGridDataProvider', 'egCore',
376 function($scope,  $routeParams,  bucketSvc , egGridDataProvider,   egCore) {
377     $scope.setTab('pending');
378
379     $scope.context = {
380         copyNotFound : false,
381         selectPendingBC : true
382     };
383
384     var query;
385     $scope.gridControls = {
386         setQuery : function(q) {
387             if (bucketSvc.pendingList.length)
388                 return {id : bucketSvc.pendingList};
389             else
390             return null;
391         },
392         allItemsRetrieved : function() {
393             $scope.context.selectPendingBC = true;
394         }
395     }
396
397     $scope.search = function() {
398         bucketSvc.barcodeRecords = [];
399         $scope.context.itemNotFound = false;
400
401         // clear selection so re-selecting can have an effect
402         $scope.context.selectPendingBC = false;
403
404         egCore.pcrud.search(
405             'acp',
406             {barcode : bucketSvc.barcodeString, deleted : 'f'},
407             {}
408         ).then(function(copy) {
409             if (copy) {
410                 bucketSvc.pendingList.push(copy.id());
411                 $scope.gridControls.setQuery({id : bucketSvc.pendingList});
412                 bucketSvc.barcodeString = ''; // clear form on valid copy
413             } else {
414                 $scope.context.itemNotFound = true;
415                 $scope.context.selectPendingBC = true;
416             }
417         });
418     }
419
420     $scope.resetPendingList = function() {
421         bucketSvc.pendingList = [];
422         $scope.gridControls.setQuery({});
423     }
424     
425     if ($routeParams.id && 
426         (!bucketSvc.currentBucket || 
427             bucketSvc.currentBucket.id() != $routeParams.id)) {
428         // user has accessed this page cold with a bucket ID.
429         // fetch the bucket for display, then set the totalCount
430         // (also for display), but avoid fully fetching the bucket,
431         // since it's premature, in this UI.
432         bucketSvc.fetchBucket($routeParams.id);
433     }
434     $scope.gridControls.setQuery();
435 }])
436
437 .controller('ViewCtrl',
438        ['$scope','$q','$routeParams','$timeout','$window','$uibModal','bucketSvc','egCore','egUser',
439         'egConfirmDialog',
440 function($scope,  $q , $routeParams , $timeout , $window , $uibModal , bucketSvc , egCore , egUser ,
441          egConfirmDialog) {
442
443     $scope.setTab('view');
444     $scope.bucketId = $routeParams.id;
445
446     var query;
447     $scope.gridControls = {
448         setQuery : function(q) {
449             if (q) query = q;
450             return query;
451         }
452     };
453
454     function drawBucket() {
455         return bucketSvc.fetchBucket($scope.bucketId).then(
456             function(bucket) {
457                 var ids = bucket.items().map(
458                     function(i){return i.target_copy()}
459                 );
460                 if (ids.length) {
461                     $scope.gridControls.setQuery({id : ids});
462                 } else {
463                     $scope.gridControls.setQuery({});
464                 }
465             }
466         );
467     }
468
469     $scope.detachCopies = function(copies) {
470         var promises = [];
471         angular.forEach(copies, function(rec) {
472             var item = bucketSvc.currentBucket.items().filter(
473                 function(i) {
474                     return (i.target_copy() == rec.id)
475                 }
476             );
477             if (item.length)
478                 promises.push(bucketSvc.detachCopy(item[0].id()));
479         });
480
481         bucketSvc.bucketNeedsRefresh = true;
482         return $q.all(promises).then(drawBucket);
483     }
484
485     $scope.spawnHoldingsEdit = function (copies) {
486         var cp_list = []
487         angular.forEach($scope.gridControls.selectedItems(), function (i) {
488             cp_list.push(i.id);
489         })
490
491         egCore.net.request(
492             'open-ils.actor',
493             'open-ils.actor.anon_cache.set_value',
494             null, 'edit-these-copies', {
495                 record_id: 0, // false-y value for record_id disables record summary
496                 copies: cp_list,
497                 hide_vols : true,
498                 hide_copies : false
499             }
500         ).then(function(key) {
501             if (key) {
502                 var url = egCore.env.basePath + 'cat/volcopy/' + key;
503                 $timeout(function() { $window.open(url, '_blank') });
504             } else {
505                 alert('Could not create anonymous cache key!');
506             }
507         });
508     }
509
510     $scope.print_labels = function() {
511         var cp_list = []
512         angular.forEach($scope.gridControls.selectedItems(), function (i) {
513             cp_list.push(i.id);
514         })
515
516         egCore.net.request(
517             'open-ils.actor',
518             'open-ils.actor.anon_cache.set_value',
519             null, 'print-labels-these-copies', {
520                 copies : cp_list
521             }
522         ).then(function(key) {
523             if (key) {
524                 var url = egCore.env.basePath + 'cat/printlabels/' + key;
525                 $timeout(function() { $window.open(url, '_blank') });
526             } else {
527                 alert('Could not create anonymous cache key!');
528             }
529         });
530     }
531
532     $scope.requestItems = function() {
533         var copy_list = $scope.gridControls.selectedItems().map(
534             function (i) {
535                 i.id;
536             }
537         );
538
539         if (copy_list.length == 0) return;
540
541         return $uibModal.open({
542             templateUrl: './cat/catalog/t_request_items',
543             backdrop: 'static',
544             animation: true,
545             controller:
546                    ['$scope','$uibModalInstance',
547             function($scope , $uibModalInstance) {
548                 $scope.user = null;
549                 $scope.first_user_fetch = true;
550
551                 $scope.hold_data = {
552                     hold_type : 'C',
553                     copy_list : copy_list,
554                     pickup_lib: egCore.org.get(egCore.auth.user().ws_ou()),
555                     user      : egCore.auth.user().id()
556                 };
557
558                 egUser.get( $scope.hold_data.user ).then(function(u) {
559                     $scope.user = u;
560                     $scope.barcode = u.card().barcode();
561                     $scope.user_name = egUser.format_name(u);
562                     $scope.hold_data.user = u.id();
563                 });
564
565                 $scope.user_name = '';
566                 $scope.barcode = '';
567                 $scope.$watch('barcode', function (n) {
568                     if (!$scope.first_user_fetch) {
569                         egUser.getByBarcode(n).then(function(u) {
570                             $scope.user = u;
571                             $scope.user_name = egUser.format_name(u);
572                             $scope.hold_data.user = u.id();
573                         }, function() {
574                             $scope.user = null;
575                             $scope.user_name = '';
576                             delete $scope.hold_data.user;
577                         });
578                     }
579                     $scope.first_user_fetch = false;
580                 });
581
582                 $scope.ok = function(h) {
583                     var args = {
584                         patronid  : h.user,
585                         hold_type : h.hold_type,
586                         pickup_lib: h.pickup_lib.id(),
587                         depth     : 0
588                     };
589
590                     egCore.net.request(
591                         'open-ils.circ',
592                         'open-ils.circ.holds.test_and_create.batch.override',
593                         egCore.auth.token(), args, h.copy_list
594                     );
595
596                     $uibModalInstance.close();
597                 }
598
599                 $scope.cancel = function($event) {
600                     $uibModalInstance.dismiss();
601                     $event.preventDefault();
602                 }
603             }]
604         });
605     }
606
607     $scope.deleteCopiesFromCatalog = function(copies) {
608         egConfirmDialog.open(
609             egCore.strings.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG,
610             '', {}
611         ).result.then(function() {
612             var fleshed_copies = [];
613             var promises = [];
614             angular.forEach(copies, function(i) {
615                 promises.push(
616                     egCore.net.request(
617                         'open-ils.search',
618                         'open-ils.search.asset.copy.fleshed2.retrieve',
619                         i.id
620                     ).then(function(copy) {
621                         copy.ischanged(1);
622                         copy.isdeleted(1);
623                         fleshed_copies.push(copy);
624                     })
625                 );
626             });
627             $q.all(promises).then(function() {
628                 egCore.net.request(
629                     'open-ils.cat',
630                     'open-ils.cat.asset.copy.fleshed.batch.update',
631                     egCore.auth.token(), fleshed_copies, true
632                 ).then(function(resp) {
633                     var evt = egCore.evt.parse(resp);
634                     if (evt) {
635                         egConfirmDialog.open(
636                             egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_TITLE,
637                             egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_BODY,
638                             {'evt_desc': evt.desc}
639                         ).result.then(function() {
640                             egCore.net.request(
641                                 'open-ils.cat',
642                                 'open-ils.cat.asset.copy.fleshed.batch.update.override',
643                                 egCore.auth.token(), fleshed_copies, true,
644                                 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
645                             ).then(function(resp) {
646                                 bucketSvc.bucketNeedsRefresh = true;
647                                 drawBucket();
648                             });
649                         });
650                     }
651                     bucketSvc.bucketNeedsRefresh = true;
652                     drawBucket();
653                 });
654             });
655         });
656     }
657
658     $scope.transferCopies = function(copies) {
659         var xfer_target = egCore.hatch.getLocalItem('eg.cat.item_transfer_target');
660         var copy_ids = copies.map(
661             function(curr,idx,arr) {
662                 return curr.id;
663             }
664         );
665         if (xfer_target) {
666             egCore.net.request(
667                 'open-ils.cat',
668                 'open-ils.cat.transfer_copies_to_volume',
669                 egCore.auth.token(),
670                 xfer_target,
671                 copy_ids
672             ).then(
673                 function(resp) { // oncomplete
674                     var evt = egCore.evt.parse(resp);
675                     if (evt) {
676                         egConfirmDialog.open(
677                             egCore.strings.OVERRIDE_TRANSFER_COPY_BUCKET_ITEMS_TO_MARKED_VOLUME_TITLE,
678                             egCore.strings.OVERRIDE_TRANSFER_COPY_BUCKET_ITEMS_TO_MARKED_VOLUME_BODY,
679                             {'evt_desc': evt.desc}
680                         ).result.then(function() {
681                             egCore.net.request(
682                                 'open-ils.cat',
683                                 'open-ils.cat.transfer_copies_to_volume.override',
684                                 egCore.auth.token(),
685                                 xfer_target,
686                                 copy_ids,
687                                 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
688                             ).then(function(resp) {
689                                 bucketSvc.bucketNeedsRefresh = true;
690                                 drawBucket();
691                             });
692                         });
693                     } else {
694                         bucketSvc.bucketNeedsRefresh = true;
695                         drawBucket();
696                     }
697                 },
698                 null, // onerror
699                 null // onprogress
700             )
701         }
702     }
703
704     $scope.applyTags = function(copies) {
705         return $uibModal.open({
706             templateUrl: './cat/bucket/copy/t_apply_tags',
707             backdrop: 'static',
708             animation: true,
709             controller:
710                    ['$scope','$uibModalInstance',
711             function($scope , $uibModalInstance) {
712
713                 $scope.tag_map = [];
714
715                 egCore.pcrud.retrieveAll('cctt', {order_by : { cctt : 'label' }}, {atomic : true}).then(function(list) {
716                     $scope.tag_types = list;
717                     $scope.tag_type = $scope.tag_types[0].code(); // just pick a default
718                 });
719
720                 $scope.getTags = function(val) {
721                     return egCore.pcrud.search('acpt',
722                         {
723                             owner :  egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
724                             label : { 'startwith' : {
725                                         transform: 'evergreen.lowercase',
726                                         value : [ 'evergreen.lowercase', val ]
727                                     }},
728                             tag_type : $scope.tag_type
729                         },
730                         { order_by : { 'acpt' : ['label'] } }, { atomic: true }
731                     ).then(function(list) {
732                         return list.map(function(item) {
733                             return item.label();
734                         });
735                     });
736                 }
737
738                 $scope.addTag = function() {
739                     var tagLabel = $scope.selectedLabel;
740                     // clear the typeahead
741                     $scope.selectedLabel = "";
742
743                     egCore.pcrud.search('acpt',
744                         {
745                             owner : egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
746                             label : tagLabel,
747                             tag_type : $scope.tag_type
748                         },
749                         { order_by : { 'acpt' : ['label'] } }, { atomic: true }
750                     ).then(function(list) {
751                         if (list.length > 0) {
752                             var newMap = new egCore.idl.acptcm();
753                             newMap.isnew(1);
754                             newMap.tag(egCore.idl.Clone(list[0]));
755                             $scope.tag_map.push(newMap);
756                         }
757                     });
758                 }
759
760                 $scope.ok = function() {
761                     var promises = [];
762                     angular.forEach($scope.tag_map, function(map) {
763                         if (map.isdeleted()) return;
764                         angular.forEach(copies, function (cp) {
765                             var m = new egCore.idl.acptcm();
766                             m.isnew(1);
767                             m.copy(cp.id);
768                             m.tag(map.tag().id());
769                             promises.push(egCore.pcrud.create(m));
770                         });
771                     });
772                     return $q.all(promises).then(function(){$uibModalInstance.close()});
773                 }
774
775                 $scope.cancel = function($event) {
776                     $uibModalInstance.dismiss();
777                     $event.preventDefault();
778                 }
779             }]
780         });
781     }
782
783     // fetch the bucket;  on error show the not-allowed message
784     if ($scope.bucketId) 
785         drawBucket()['catch'](function() { $scope.forbidden = true });
786 }])