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.
15 angular.module('egCatCopyBuckets',
16 ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
18 .config(function($routeProvider, $locationProvider, $compileProvider) {
19 $locationProvider.html5Mode(true);
20 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
22 var resolver = {delay : function(egStartup) {return egStartup.go()}};
24 $routeProvider.when('/cat/bucket/copy/pending/:id', {
25 templateUrl: './cat/bucket/copy/t_pending',
26 controller: 'PendingCtrl',
30 $routeProvider.when('/cat/bucket/copy/pending', {
31 templateUrl: './cat/bucket/copy/t_pending',
32 controller: 'PendingCtrl',
36 $routeProvider.when('/cat/bucket/copy/view/:id', {
37 templateUrl: './cat/bucket/copy/t_view',
38 controller: 'ViewCtrl',
42 $routeProvider.when('/cat/bucket/copy/view', {
43 templateUrl: './cat/bucket/copy/t_view',
44 controller: 'ViewCtrl',
48 // default page / bucket view
49 $routeProvider.otherwise({redirectTo : '/cat/bucket/copy/view'});
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).
58 .factory('bucketSvc', ['$q','egCore', function($q, egCore) {
61 allBuckets : [], // un-fleshed user buckets
62 barcodeString : '', // last scanned barcode
63 barcodeRecords : [], // last scanned barcode results
64 currentBucket : null, // currently viewed bucket
66 // per-page list collections
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;
75 return egCore.net.request(
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 });
83 createBucket : function(name, desc) {
84 var deferred = $q.defer();
85 var bucket = new egCore.idl.ccb();
86 bucket.owner(egCore.auth.user().id());
88 bucket.description(desc || '');
89 bucket.btype('staff_client');
93 'open-ils.actor.container.create',
94 egCore.auth.token(), 'copy', bucket
95 ).then(function(resp) {
97 if (typeof resp == 'object') {
98 console.error('bucket create error: ' + js2JSON(resp));
101 deferred.resolve(resp);
106 return deferred.promise;
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(
118 'open-ils.actor.container.update',
119 egCore.auth.token(), 'copy', bucket
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;
133 if (service.currentBucket.id() != id) return 1;
137 // returns a promise, resolved with bucket, rejected if bucket is
139 service.fetchBucket = function(id) {
140 var refresh = service.bucketRefreshLevel(id);
141 if (refresh == 2) return $q.when(service.currentBucket);
143 var deferred = $q.defer();
147 'open-ils.actor.container.flesh.authoritative',
148 egCore.auth.token(), 'copy', id
149 ).then(function(bucket) {
150 var evt = egCore.evt.parse(bucket);
153 deferred.reject(evt);
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();
168 service.currentBucket = bucket;
169 deferred.resolve(bucket);
172 return deferred.promise;
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();
181 'open-ils.actor.container.item.delete',
182 egCore.auth.token(), 'copy', itemId
183 ).then(function(resp) {
184 var evt = egCore.evt.parse(resp);
187 deferred.reject(evt);
190 console.log('detached bucket item ' + itemId);
191 deferred.resolve(resp);
194 return deferred.promise;
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();
204 'open-ils.actor.container.full_delete',
205 egCore.auth.token(), 'copy', id
206 ).then(function(resp) {
207 var evt = egCore.evt.parse(resp);
210 deferred.reject(evt);
213 deferred.resolve(resp);
215 return deferred.promise;
218 // apply last inventory data to fetched bucket items
219 service.fetchRecentInventoryData = function(copy) {
220 return egCore.pcrud.search('alci',
222 {flesh: 2, flesh_fields: {alci: ['inventory_workstation']}}
223 ).then(function(alci) {
232 * Top-level controller.
233 * Hosts functions needed by all controllers.
235 .controller('CopyBucketCtrl',
236 ['$scope','$location','$q','$timeout','$uibModal',
237 '$window','egCore','bucketSvc','egProgressDialog',
238 function($scope, $location, $q, $timeout, $uibModal,
239 $window, egCore, bucketSvc , egProgressDialog) {
241 $scope.bucketSvc = bucketSvc;
242 $scope.bucket = function() { return bucketSvc.currentBucket }
244 // tabs: search, pending, view
245 $scope.setTab = function(tab) {
248 // for bucket selector; must be called after route resolve
249 bucketSvc.fetchUserBuckets();
252 $scope.loadBucketFromMenu = function(item, bucket) {
253 if (bucket) return $scope.loadBucket(bucket.id());
256 $scope.loadBucket = function(id) {
258 '/cat/bucket/copy/' +
259 $scope.tab + '/' + encodeURIComponent(id));
262 $scope.addToBucket = function(recs) {
263 if (recs.length == 0) return;
264 bucketSvc.bucketNeedsRefresh = true;
266 var ids = recs.map(function(rec) { return rec.id; });
268 egProgressDialog.open();
272 'open-ils.actor.container.item.create.batch',
273 egCore.auth.token(), 'copy',
274 bucketSvc.currentBucket.id(), ids
277 egProgressDialog.close();
281 // HACK: add the IDs of the added items so that the size
282 // of the view list will grow (and update any UI looking at
283 // the list size). The data stored is inconsistent, but since
284 // we are forcing a bucket refresh on the next rendering of
285 // the view pane, the list will be repaired.
286 bucketSvc.currentBucket.items().push(resp);
291 $scope.openCreateBucketDialog = function() {
293 templateUrl: './cat/bucket/share/t_bucket_create',
296 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
297 $scope.focusMe = true;
298 $scope.ok = function(args) { $uibModalInstance.close(args) }
299 $scope.cancel = function () { $uibModalInstance.dismiss() }
301 }).result.then(function (args) {
302 if (!args || !args.name) return;
303 bucketSvc.createBucket(args.name, args.desc).then(
306 bucketSvc.viewList = [];
307 bucketSvc.allBuckets = []; // reset
308 bucketSvc.currentBucket = null;
310 '/cat/bucket/copy/' + $scope.tab + '/' + id);
316 $scope.openEditBucketDialog = function() {
318 templateUrl: './cat/bucket/share/t_bucket_edit',
321 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
322 $scope.focusMe = true;
324 name : bucketSvc.currentBucket.name(),
325 desc : bucketSvc.currentBucket.description(),
326 pub : bucketSvc.currentBucket.pub() == 't'
328 $scope.ok = function(args) {
330 $scope.actionPending = true;
331 args.pub = args.pub ? 't' : 'f';
332 // close the dialog after edit has completed
333 bucketSvc.editBucket(args).then(
334 function() { $uibModalInstance.close() });
336 $scope.cancel = function () { $uibModalInstance.dismiss() }
341 // opens the delete confirmation and deletes the current
342 // bucket if the user confirms.
343 $scope.openDeleteBucketDialog = function() {
345 templateUrl: './cat/bucket/share/t_bucket_delete',
348 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
349 $scope.bucket = function() { return bucketSvc.currentBucket }
350 $scope.ok = function() { $uibModalInstance.close() }
351 $scope.cancel = function() { $uibModalInstance.dismiss() }
353 }).result.then(function () {
354 bucketSvc.deleteBucket(bucketSvc.currentBucket.id())
356 bucketSvc.allBuckets = [];
357 $location.path('/cat/bucket/copy/view');
362 // retrieves the requested bucket by ID
363 $scope.openSharedBucketDialog = function() {
365 templateUrl: './cat/bucket/share/t_load_shared',
368 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
369 $scope.focusMe = true;
370 $scope.ok = function(args) {
371 if (args && args.id) {
372 $uibModalInstance.close(args.id)
375 $scope.cancel = function() { $uibModalInstance.dismiss() }
377 }).result.then(function(id) {
378 // RecordBucketCtrl $scope is not inherited by the
379 // modal, so we need to call loadBucket from the
381 $scope.loadBucket(id);
387 .controller('PendingCtrl',
388 ['$scope','$routeParams','bucketSvc','egGridDataProvider', 'egCore',
389 function($scope, $routeParams, bucketSvc , egGridDataProvider, egCore) {
390 $scope.setTab('pending');
393 copyNotFound : false,
394 selectPendingBC : true
398 $scope.gridControls = {
399 setQuery : function(q) {
400 if (bucketSvc.pendingList.length)
401 return {id : bucketSvc.pendingList};
405 allItemsRetrieved : function() {
406 $scope.context.selectPendingBC = true;
410 $scope.handle_barcode_completion = function(barcode) {
411 return egCore.net.request(
413 'open-ils.actor.get_barcodes',
414 egCore.auth.token(), egCore.auth.user().ws_ou(),
417 .then(function(resp) {
418 // TODO: handle event during barcode lookup
419 if (evt = egCore.evt.parse(resp)) {
420 console.error(evt.toString());
424 // no matching barcodes: return the barcode as entered
425 // by the user (so that, e.g., checkout can fall back to
426 // precat/noncat handling)
427 if (!resp || !resp[0]) {
431 // exactly one matching barcode: return it
432 if (resp.length == 1) {
433 return resp[0].barcode;
436 // multiple matching barcodes: let the user pick one
437 console.debug('multiple matching barcodes');
441 angular.forEach(resp, function(cp) {
445 'open-ils.circ.copy_details.retrieve',
446 egCore.auth.token(), cp.id
449 barcode: r.copy.barcode(),
450 title: r.mvr.title(),
451 org_name: egCore.org.get(r.copy.circ_lib()).name(),
452 org_shortname: egCore.org.get(r.copy.circ_lib()).shortname()
457 return $q.all(promises)
459 return $uibModal.open({
460 templateUrl: './circ/share/t_barcode_choice_dialog',
462 ['$scope', '$uibModalInstance',
463 function($scope, $uibModalInstance) {
464 $scope.matches = matches;
465 $scope.ok = function(barcode) {
466 $uibModalInstance.close();
467 final_barcode = barcode;
469 $scope.cancel = function() {$uibModalInstance.dismiss()}
471 }).result.then(function() { return final_barcode });
476 $scope.search = function() {
477 bucketSvc.barcodeRecords = [];
478 $scope.context.itemNotFound = false;
480 // clear selection so re-selecting can have an effect
481 $scope.context.selectPendingBC = false;
483 return $scope.handle_barcode_completion(bucketSvc.barcodeString)
484 .then(function(actual_barcode) {
487 {barcode : actual_barcode, deleted : 'f'},
489 ).then(function(copy) {
491 bucketSvc.pendingList.push(copy.id());
492 $scope.gridControls.setQuery({id : bucketSvc.pendingList});
493 bucketSvc.barcodeString = ''; // clear form on valid copy
495 $scope.context.itemNotFound = true;
496 $scope.context.selectPendingBC = true;
502 $scope.resetPendingList = function() {
503 bucketSvc.pendingList = [];
504 $scope.gridControls.setQuery({});
507 if ($routeParams.id &&
508 (!bucketSvc.currentBucket ||
509 bucketSvc.currentBucket.id() != $routeParams.id)) {
510 // user has accessed this page cold with a bucket ID.
511 // fetch the bucket for display, then set the totalCount
512 // (also for display), but avoid fully fetching the bucket,
513 // since it's premature, in this UI.
514 bucketSvc.fetchBucket($routeParams.id);
516 $scope.gridControls.setQuery();
519 .controller('ViewCtrl',
520 ['$scope','$q','$routeParams','$timeout','$window','$uibModal','bucketSvc','egCore','egOrg','egUser',
521 'ngToast','egConfirmDialog','egProgressDialog', 'egItem',
522 function($scope, $q , $routeParams , $timeout , $window , $uibModal , bucketSvc , egCore , egOrg , egUser ,
523 ngToast , egConfirmDialog , egProgressDialog, itemSvc) {
525 $scope.setTab('view');
526 $scope.bucketId = $routeParams.id;
529 $scope.gridControls = {
530 setQuery : function(q) {
536 function drawBucket() {
537 return bucketSvc.fetchBucket($scope.bucketId).then(
539 var ids = bucket.items().map(
540 function(i){return i.target_copy()}
543 $scope.gridControls.setQuery({id : ids});
545 $scope.gridControls.setQuery({});
551 $scope.detachCopies = function(copies) {
552 bucketSvc.bucketNeedsRefresh = true;
553 var ids = copies.map(function(rec) { return rec.id; });
556 egProgressDialog.open();
557 return egCore.net.request(
559 'open-ils.actor.container.item.delete.batch',
560 egCore.auth.token(), 'copy',
561 bucketSvc.currentBucket.id(), ids
564 egProgressDialog.close();
568 // Remove the items as the API responds so the UI can show
569 // the count of items decreasing.
570 bucketSvc.currentBucket.items(
571 bucketSvc.currentBucket.items().filter(function(item) {
572 return item.target_copy() != resp;
579 $scope.moveToPending = function(copies) {
580 angular.forEach(copies, function(copy) {
581 bucketSvc.pendingList.push(copy.id);
583 $scope.detachCopies(copies);
586 $scope.spawnHoldingsEdit = function() {
587 $scope.spawnEdit(true, false);
590 $scope.spawnCallNumberEdit = function() {
591 $scope.spawnEdit(false, true);
594 $scope.spawnEdit = function(hide_vols,hide_copies) {
596 angular.forEach($scope.gridControls.selectedItems(), function (i) {
601 'open-ils.actor.anon_cache.set_value',
602 null, 'edit-these-copies', {
603 record_id: 0, // false-y value for record_id disables record summary
605 hide_vols : hide_vols,
606 hide_copies : hide_copies
608 ).then(function(key) {
610 var tab = (hide_vols === true) ? 'attrs' : 'holdings';
611 var url = '/eg2/staff/cat/volcopy/' + tab + '/session/ ' + key;
612 $timeout(function() { $window.open(url, '_blank') });
614 alert('Could not create anonymous cache key!');
619 $scope.print_labels = function() {
621 angular.forEach($scope.gridControls.selectedItems(), function (i) {
627 'open-ils.actor.anon_cache.set_value',
628 null, 'print-labels-these-copies', {
631 ).then(function(key) {
633 var url = egCore.env.basePath + 'cat/printlabels/' + key;
634 $timeout(function() { $window.open(url, '_blank') });
636 alert('Could not create anonymous cache key!');
641 $scope.showItems = function() {
643 angular.forEach($scope.gridControls.selectedItems(), function (i) {
646 var url = egCore.env.basePath + '/cat/item/search/' + cp_list.join();
647 $timeout(function() { $window.open(url, '_blank') });
650 $scope.requestItems = function() {
651 var copy_list = $scope.gridControls.selectedItems().map(
656 var record_list = $scope.gridControls.selectedItems().map(
658 return i['call_number.record.id'];
660 ).filter(function(v,i,s){ // dedup
661 return s.indexOf(v) == i;
664 if (copy_list.length == 0) return;
666 return $uibModal.open({
667 templateUrl: './cat/catalog/t_request_items',
671 ['$scope','$uibModalInstance',
672 function($scope , $uibModalInstance) {
674 $scope.first_user_fetch = true;
678 copy_list : copy_list,
679 record_list : record_list,
680 pickup_lib: egCore.org.get(egCore.auth.user().ws_ou()),
681 user : egCore.auth.user().id(),
682 honor_user_settings :
683 egCore.hatch.getLocalItem('eg.cat.request_items.honor_user_settings')
686 egUser.get( $scope.hold_data.user ).then(function(u) {
688 $scope.barcode = u.card().barcode();
689 $scope.user_name = egUser.format_name(u);
690 $scope.hold_data.user = u.id();
693 $scope.user_name = '';
695 function user_preferred_pickup_lib(u) {
696 var pickup_lib = u.home_ou();
697 angular.forEach(u.settings(), function (s) {
698 if (s.name() == "opac.default_pickup_location") {
699 pickup_lib = s.value();
702 return egOrg.get(pickup_lib);
704 $scope.$watch('barcode', function (n) {
705 if (!$scope.first_user_fetch) {
706 egUser.getByBarcode(n).then(function(u) {
708 $scope.user_name = egUser.format_name(u);
709 $scope.hold_data.user = u.id();
710 if ($scope.hold_data.honor_user_settings) {
711 $scope.hold_data.pickup_lib = user_preferred_pickup_lib(u);
715 $scope.user_name = '';
716 delete $scope.hold_data.user;
719 $scope.first_user_fetch = false;
721 $scope.$watch('hold_data.honor_user_settings', function (n) {
722 if (n && $scope.user) {
723 $scope.hold_data.pickup_lib = user_preferred_pickup_lib($scope.user);
725 $scope.hold_data.pickup_lib = egCore.org.get(egCore.auth.user().ws_ou());
727 egCore.hatch.setLocalItem('eg.cat.request_items.honor_user_settings',n);
730 $scope.ok = function(h) {
733 hold_type : h.hold_type,
734 pickup_lib: h.pickup_lib.id(),
740 'open-ils.circ.holds.test_and_create.batch.override',
741 egCore.auth.token(), args,
742 h.hold_type == 'T' ? h.record_list : h.copy_list,
743 { 'all' : 1, 'honor_user_settings' : h.honor_user_settings }
745 console.log('request result',r);
746 if (isNaN(r.result)) {
747 if (typeof r.result.desc != 'undefined') {
748 ngToast.danger(r.result.desc);
750 if (typeof r.result.last_event != 'undefined') {
751 ngToast.danger(r.result.last_event.desc);
753 ngToast.danger(egCore.strings.FAILURE_HOLD_REQUEST);
757 ngToast.success(egCore.strings.SUCCESS_HOLD_REQUEST);
761 $uibModalInstance.close();
764 $scope.cancel = function($event) {
765 $uibModalInstance.dismiss();
766 $event.preventDefault();
772 $scope.deleteCopiesFromCatalog = function(copies) {
773 egConfirmDialog.open(
774 egCore.strings.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG,
776 ).result.then(function() {
777 egProgressDialog.open();
778 var fleshed_copies = [];
780 var chain = $q.when();
781 angular.forEach(copies, function(i) {
782 chain = chain.then(function() {
783 return egCore.net.request(
785 'open-ils.search.asset.copy.fleshed2.retrieve',
787 ).then(function(copy) {
790 fleshed_copies.push(copy);
795 chain.finally(function() {
798 'open-ils.cat.asset.copy.fleshed.batch.update',
799 egCore.auth.token(), fleshed_copies, true
800 ).then(function(resp) {
801 var evt = egCore.evt.parse(resp);
803 egConfirmDialog.open(
804 egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_TITLE,
805 egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_BODY,
806 {'evt_desc': evt.desc}
807 ).result.then(function() {
810 'open-ils.cat.asset.copy.fleshed.batch.update.override',
811 egCore.auth.token(), fleshed_copies, true,
812 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
813 ).then(function(resp) {
814 bucketSvc.bucketNeedsRefresh = true;
819 bucketSvc.bucketNeedsRefresh = true;
821 egProgressDialog.close();
827 $scope.createCarouselFromBucket = function() {
828 if (!bucketSvc?.currentBucket?.items()?.length) {
831 itemSvc.create_carousel_from_items(
832 bucketSvc.currentBucket.items().map(function (item) {return item.target_copy()})
836 $scope.transferCopies = function(copies) {
837 var xfer_target = egCore.hatch.getLocalItem('eg.cat.transfer_target_vol');
838 var copy_ids = copies.map(
839 function(curr,idx,arr) {
846 'open-ils.cat.transfer_copies_to_volume',
851 function(resp) { // oncomplete
852 var evt = egCore.evt.parse(resp);
854 egConfirmDialog.open(
855 egCore.strings.OVERRIDE_TRANSFER_COPY_BUCKET_ITEMS_TO_MARKED_VOLUME_TITLE,
856 egCore.strings.OVERRIDE_TRANSFER_COPY_BUCKET_ITEMS_TO_MARKED_VOLUME_BODY,
857 {'evt_desc': evt.desc}
858 ).result.then(function() {
861 'open-ils.cat.transfer_copies_to_volume.override',
865 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
866 ).then(function(resp) {
867 bucketSvc.bucketNeedsRefresh = true;
872 bucketSvc.bucketNeedsRefresh = true;
882 $scope.applyTags = function(copies) {
883 return $uibModal.open({
884 templateUrl: './cat/bucket/copy/t_apply_tags',
888 ['$scope','$uibModalInstance',
889 function($scope , $uibModalInstance) {
893 egCore.pcrud.retrieveAll('cctt', {order_by : { cctt : 'label' }}, {atomic : true}).then(function(list) {
894 $scope.tag_types = list;
895 $scope.tag_type = $scope.tag_types[0].code(); // just pick a default
898 $scope.getTags = function(val) {
899 return egCore.pcrud.search('acpt',
901 owner : egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
902 label : { 'startwith' : {
903 transform: 'evergreen.lowercase',
904 value : [ 'evergreen.lowercase', val ]
906 tag_type : $scope.tag_type
908 { order_by : { 'acpt' : ['label'] } }, { atomic: true }
909 ).then(function(list) {
910 return list.map(function(item) {
911 return { value: item.label(), display: item.label() + " (" + egCore.org.get(item.owner()).shortname() + ")" };
916 $scope.addTag = function() {
917 var tagLabel = $scope.selectedLabel;
918 // clear the typeahead
919 $scope.selectedLabel = "";
921 egCore.pcrud.search('acpt',
923 owner : egCore.org.fullPath(egCore.auth.user().ws_ou(), true),
925 tag_type : $scope.tag_type
927 { order_by : { 'acpt' : ['label'] } }, { atomic: true }
928 ).then(function(list) {
929 if (list.length > 0) {
930 var newMap = new egCore.idl.acptcm();
932 newMap.tag(egCore.idl.Clone(list[0]));
933 $scope.tag_map.push(newMap);
938 $scope.ok = function() {
940 angular.forEach($scope.tag_map, function(map) {
941 if (map.isdeleted()) return;
942 angular.forEach(copies, function (cp) {
943 var m = new egCore.idl.acptcm();
946 m.tag(map.tag().id());
947 promises.push(egCore.pcrud.create(m));
950 return $q.all(promises).then(function(){$uibModalInstance.close()});
953 $scope.cancel = function($event) {
954 $uibModalInstance.dismiss();
955 $event.preventDefault();
961 // fetch the bucket; on error show the not-allowed message
963 drawBucket()['catch'](function() { $scope.forbidden = true });