]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/patron/bucket/app.js
35396f462556941949708a9febbd93d700321513
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / patron / bucket / app.js
1 /**
2  * User 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('egCatUserBuckets', 
16     ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod', 'egUserBucketMod', 'ngToast'])
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('/circ/patron/bucket/add/:id', {
25         templateUrl: './circ/patron/bucket/t_pending',
26         controller: 'PendingCtrl',
27         resolve : resolver
28     });
29
30     $routeProvider.when('/circ/patron/bucket/add', {
31         templateUrl: './circ/patron/bucket/t_pending',
32         controller: 'PendingCtrl',
33         resolve : resolver
34     });
35
36     $routeProvider.when('/circ/patron/bucket/view/:id', {
37         templateUrl: './circ/patron/bucket/t_view',
38         controller: 'ViewCtrl',
39         resolve : resolver
40     });
41
42     $routeProvider.when('/circ/patron/bucket/view', {
43         templateUrl: './circ/patron/bucket/t_view',
44         controller: 'ViewCtrl',
45         resolve : resolver
46     });
47
48     // default page / bucket view
49     $routeProvider.otherwise({redirectTo : '/circ/patron/bucket/view'});
50 })
51
52 /**
53  * Top-level controller.  
54  * Hosts functions needed by all controllers.
55  */
56 .controller('UserBucketCtrl',
57        ['$scope','$location','$q','$timeout','$uibModal',
58         '$window','egCore','bucketSvc','ngToast',
59 function($scope,  $location,  $q,  $timeout,  $uibModal,  
60          $window,  egCore,  bucketSvc , ngToast) {
61
62     $scope.bucketSvc = bucketSvc;
63     $scope.bucket = function() { return bucketSvc.currentBucket }
64
65     // tabs: add, view
66     $scope.setTab = function(tab) { 
67         $scope.tab = tab;
68
69         // for bucket selector; must be called after route resolve
70         bucketSvc.fetchUserBuckets(); 
71     };
72
73     $scope.loadBucketFromMenu = function(item, bucket) {
74         if (bucket) return $scope.loadBucket(bucket.id());
75     }
76
77     $scope.loadBucket = function(id) {
78         $location.path(
79             '/circ/patron/bucket/' + 
80                 $scope.tab + '/' + encodeURIComponent(id));
81     }
82
83     $scope.addToBucket = function(recs) {
84         if (recs.length == 0) return;
85         bucketSvc.bucketNeedsRefresh = true;
86
87         angular.forEach(recs,
88             function(rec) {
89                 var item = new egCore.idl.cubi();
90                 item.bucket(bucketSvc.currentBucket.id());
91                 item.target_user(rec.id);
92                 egCore.net.request(
93                     'open-ils.actor',
94                     'open-ils.actor.container.item.create', 
95                     egCore.auth.token(), 'user', item
96                 ).then(function(resp) {
97
98                     // HACK: add the IDs of the added items so that the size
99                     // of the view list will grow (and update any UI looking at
100                     // the list size).  The data stored is inconsistent, but since
101                     // we are forcing a bucket refresh on the next rendering of 
102                     // the view pane, the list will be repaired.
103                     bucketSvc.currentBucket.items().push(resp);
104                 });
105             }
106         );
107         $scope.resetPendingList();
108     }
109
110     $scope.openCreateBucketDialog = function() {
111         $uibModal.open({
112             templateUrl: './circ/patron/bucket/t_bucket_create',
113             backdrop: 'static',
114             controller: 
115                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
116                 $scope.focusMe = true;
117                 $scope.ok = function(args) { $uibModalInstance.close(args) }
118                 $scope.cancel = function () { $uibModalInstance.dismiss() }
119             }]
120         }).result.then(function (args) {
121             if (!args || !args.name) return;
122             bucketSvc.createBucket(args.name, args.desc).then(
123                 function(id) {
124                     if (!id) return;
125                     bucketSvc.viewList = [];
126                     bucketSvc.allBuckets = []; // reset
127                     bucketSvc.currentBucket = null;
128                     $location.path(
129                         '/circ/patron/bucket/' + $scope.tab + '/' + id);
130                 }
131             );
132         });
133     }
134
135     $scope.openEditBucketDialog = function() {
136         $uibModal.open({
137             templateUrl: './circ/patron/bucket/t_bucket_edit',
138             backdrop: 'static',
139             controller: 
140                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
141                 $scope.focusMe = true;
142                 $scope.args = {
143                     name : bucketSvc.currentBucket.name(),
144                     desc : bucketSvc.currentBucket.description(),
145                     pub : bucketSvc.currentBucket.pub() == 't'
146                 };
147                 $scope.ok = function(args) { 
148                     if (!args) return;
149                     $scope.actionPending = true;
150                     args.pub = args.pub ? 't' : 'f';
151                     // close the dialog after edit has completed
152                     bucketSvc.editBucket(args).then(
153                         function() { $uibModalInstance.close() });
154                 }
155                 $scope.cancel = function () { $uibModalInstance.dismiss() }
156             }]
157         })
158     }
159
160     // opens the delete confirmation and deletes the current
161     // bucket if the user confirms.
162     $scope.openDeleteBucketDialog = function() {
163         $uibModal.open({
164             templateUrl: './circ/patron/bucket/t_bucket_delete',
165             backdrop: 'static',
166             controller : 
167                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
168                 $scope.bucket = function() { return bucketSvc.currentBucket }
169                 $scope.ok = function() { $uibModalInstance.close() }
170                 $scope.cancel = function() { $uibModalInstance.dismiss() }
171             }]
172         }).result.then(function () {
173             bucketSvc.deleteBucket(bucketSvc.currentBucket.id())
174             .then(function() {
175                 bucketSvc.allBuckets = [];
176                 $location.path('/circ/patron/bucket/view');
177             });
178         });
179     }
180
181     // retrieves the requested bucket by ID
182     $scope.openSharedBucketDialog = function() {
183         $uibModal.open({
184             templateUrl: './circ/patron/bucket/t_load_shared',
185             backdrop: 'static',
186             controller :
187                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
188                 $scope.focusMe = true;
189                 $scope.ok = function(args) {
190                     if (args && args.id) {
191                         $uibModalInstance.close(args.id)
192                     }
193                 }
194                 $scope.cancel = function() { $uibModalInstance.dismiss() }
195             }]
196         }).result.then(function(id) {
197             // RecordBucketCtrl $scope is not inherited by the
198             // modal, so we need to call loadBucket from the
199             // promise resolver.
200             $scope.loadBucket(id);
201         });
202     }
203
204 }])
205
206 .controller('PendingCtrl',
207        ['$scope','$routeParams','bucketSvc','egGridDataProvider', 'egCore','ngToast','$q',
208 function($scope,  $routeParams,  bucketSvc , egGridDataProvider,   egCore , ngToast , $q) {
209     $scope.setTab('add');
210
211     var query;
212     $scope.gridControls = {
213         setQuery : function(q) {
214             if (bucketSvc.pendingList.length)
215                 return {id : bucketSvc.pendingList};
216             else
217             return null;
218         }
219     }
220
221     $scope.$watch('barcodesFromFile', function(newVal, oldVal) {
222         if (newVal && newVal != oldVal) {
223             var promises = [];
224             // $scope.resetPendingList(); // ??? Add instead of replace
225             angular.forEach(newVal.split(/\n/), function(line) {
226                 if (!line) return;
227                 // scrub any trailing spaces or commas from the barcode
228                 line = line.replace(/(.*?)($|\s.*|,.*)/,'$1');
229                 promises.push(egCore.pcrud.search(
230                     'ac',
231                     {barcode : line},
232                     {}
233                 ).then(null, null, function(card) {
234                     bucketSvc.pendingList.push(card.usr());
235                 }));
236             });
237
238             $q.all(promises).then(function () {
239                 $scope.gridControls.setQuery({id : bucketSvc.pendingList});
240             });
241         }
242     });
243
244     $scope.search = function() {
245         bucketSvc.barcodeRecords = [];
246
247         egCore.pcrud.search(
248             'ac',
249             {barcode : bucketSvc.barcodeString},
250             {}
251         ).then(null, null, function(card) {
252             bucketSvc.pendingList.push(card.usr());
253             $scope.gridControls.setQuery({id : bucketSvc.pendingList});
254         });
255         bucketSvc.barcodeString = '';
256     }
257
258     $scope.resetPendingList = function() {
259         bucketSvc.pendingList = [];
260         $scope.gridControls.setQuery({});
261     }
262
263     $scope.$parent.resetPendingList = $scope.resetPendingList;
264     
265     if ($routeParams.id && 
266         (!bucketSvc.currentBucket || 
267             bucketSvc.currentBucket.id() != $routeParams.id)) {
268         // user has accessed this page cold with a bucket ID.
269         // fetch the bucket for display, then set the totalCount
270         // (also for display), but avoid fully fetching the bucket,
271         // since it's premature, in this UI.
272         bucketSvc.fetchBucket($routeParams.id);
273     }
274     $scope.gridControls.setQuery();
275 }])
276
277 .controller('ViewCtrl',
278        ['$scope','$q','$routeParams','$timeout','$window','$uibModal','bucketSvc','egCore','egUser',
279         'egConfirmDialog','egPerm','ngToast','$filter',
280 function($scope,  $q , $routeParams , $timeout , $window , $uibModal , bucketSvc , egCore , egUser ,
281          egConfirmDialog , egPerm , ngToast , $filter) {
282
283     $scope.setTab('view');
284     $scope.bucketId = $routeParams.id;
285
286     var query;
287     $scope.gridControls = {
288         setQuery : function(q) {
289             if (q) query = q;
290             return query;
291         }
292     };
293
294     $scope.modifyStatcats = function() {
295         bucketSvc.bucketNeedsRefresh = true;
296
297         $uibModal.open({
298             templateUrl: './circ/patron/bucket/t_update_statcats',
299             backdrop: 'static',
300             controller: 
301                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
302                 $scope.running = false;
303                 $scope.complete = false;
304                 $scope.states = [];
305
306                 $scope.modal = $uibModalInstance;
307                 $scope.ok = function(args) { $uibModalInstance.close() }
308                 $scope.cancel = function () { $uibModalInstance.dismiss() }
309
310                 $scope.current_bucket = bucketSvc.currentBucket;
311
312                 egCore.net.request(
313                     'open-ils.circ',
314                     'open-ils.circ.stat_cat.actor.retrieve.all',
315                     egCore.auth.token(), egCore.auth.user().ws_ou()
316                 ).then(function(cats) {
317                     cats = cats.sort(function(a, b) {
318                         return a.name() < b.name() ? -1 : 1});
319                     angular.forEach(cats, function(cat) {
320                         cat.new_value = '';
321                         cat.allow_freetext(parseInt(cat.allow_freetext())); // just to be sure
322                         cat.entries(
323                             cat.entries().sort(function(a,b) {
324                                 return a.value() < b.value() ? -1 : 1
325                             })
326                         );
327                     });
328                     $scope.stat_cats = cats;
329                 });
330
331                 // This handels the progress magic instead of a normal close handler
332                 $scope.$on('modal.closing', function(event, reason, closed) {
333                     if (!closed) return; // dismissed
334                     if ($scope.complete) return; // already done
335
336                     $scope.running = true;
337
338                     var changes = {remove:[], apply:{}};
339                     angular.forEach($scope.stat_cats, function (sc) {
340                         if (sc.delete_me) {
341                             changes.remove.push(sc.id());
342                         } else if (sc.new_value) {
343                             changes.apply[sc.id()] = sc.new_value;
344                         }
345                     });
346
347                     egCore.net.request(
348                         'open-ils.actor',
349                         'open-ils.actor.container.user.batch_statcat_apply',
350                         egCore.auth.token(), bucketSvc.currentBucket.id(), changes
351                     ).then(
352                         function () {
353                             $scope.complete = true;
354                             $scope.modal.close();
355                             drawBucket();
356                         },
357                         function (err) { console.log('User edit error: ' + err); },
358                         function (p) {
359                             if (p.error) {
360                                 ngToast.warning(p.error);
361                             }
362                             if (p.stage == 'COMPLETE') return;
363
364                             p.label = egCore.strings[p.stage];
365                             if (!p.max) {
366                                 p.max = 1;
367                                 p.count = 1;
368                             }
369                             $scope.states[p.ord] = p;
370                         }
371                     );
372
373                     return event.preventDefault();
374                 });
375             }]
376         });
377     }
378
379
380     function drawBucket() {
381         return bucketSvc.fetchBucket($scope.bucketId).then(
382             function(bucket) {
383                 var ids = bucket.items().map(
384                     function(i){return i.target_user()}
385                 );
386                 if (ids.length) {
387                     $scope.gridControls.setQuery({id : ids});
388                 } else {
389                     $scope.gridControls.setQuery({});
390                 }
391             }
392         );
393     }
394
395     $scope.no_update_perms = true;
396     $scope.noUpdatePerms = function () { return $scope.no_update_perms; }
397
398     egPerm.hasPermHere(['UPDATE_USER']).then(
399         function (hash) {
400             if (Object.keys(hash).length == 0) return;
401
402             var one_false = false;
403             angular.forEach(hash, function(has) {
404                 if (!has) one_false = true;
405             });
406
407             if (!one_false) $scope.no_update_perms = false;
408         }
409     );
410
411     function annotate_groups(grps) {
412         angular.forEach(grps, function (g) {
413             if (!g.hasOwnProperty('cannot_use')) {
414                 if (g.usergroup() == 'f') {
415                     g.cannot_use = true;
416                 } else if (g.application_perm) {
417                     egPerm.hasPermHere(['EVERYTHING',g.application_perm]).then(
418                         function (hash) {
419                             if (Object.keys(hash).length == 0) {
420                                 g.cannot_use = true;
421                                 return;
422                             }
423
424                             var one_false = false;
425                             angular.forEach(hash, function(has) {
426                                 if (has) g.cannot_use = false;
427                             });
428                         }
429                     );
430                 } else {
431                     g.cannot_use = false;
432                 }
433             }
434         });
435     }
436
437     $scope.viewChangesets = function() {
438         bucketSvc.bucketNeedsRefresh = true;
439
440         $uibModal.open({
441             templateUrl: './circ/patron/bucket/t_changesets',
442             backdrop: 'static',
443             controller: 
444                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
445                 $scope.running = false;
446                 $scope.complete = false;
447                 $scope.states = [];
448
449                 $scope.focusMe = true;
450                 $scope.modal = $uibModalInstance;
451                 $scope.ok = function() { $uibModalInstance.close() }
452                 $scope.cancel = function () { $uibModalInstance.dismiss() }
453
454                 $scope.current_bucket = bucketSvc.currentBucket;
455                 $scope.fieldset_groups = [];
456
457                 $scope.deleteChangeset = function (grp) {
458                     egCore.pcrud.remove(grp).then(
459                         function () {
460                             if (grp.rollback_group()) {
461                                 egCore.pcrud
462                                     .retrieve('afsg',grp.rollback_group())
463                                     .then(function(g) {
464                                         egCore.pcrud.remove(g)
465                                             .then( function () { refresh_groups() } );
466                                     });
467                             }
468                         }
469                     );
470                     return event.preventDefault();
471                 }
472
473                 function refresh_groups () {
474                     $scope.fieldset_groups = [];
475                     egCore.pcrud.search('afsg',{
476                         rollback_group : { '>' : 0 },
477                         container      : bucketSvc.currentBucket.id(),
478                         container_type : 'user'
479                     } ).then( null,null,function(g) {
480                         $scope.fieldset_groups.push(g);
481                     });
482                 }
483                 refresh_groups();
484
485             }]
486         });
487     }
488
489     $scope.applyRollback = function() {
490         bucketSvc.bucketNeedsRefresh = true;
491
492         $uibModal.open({
493             templateUrl: './circ/patron/bucket/t_rollback',
494             controller: 
495                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
496                 $scope.running = false;
497                 $scope.complete = false;
498                 $scope.states = [];
499                 $scope.revert_me = null;
500
501                 $scope.focusMe = true;
502                 $scope.modal = $uibModalInstance;
503                 $scope.ok = function(args) { $uibModalInstance.close() }
504                 $scope.cancel = function () { $uibModalInstance.dismiss() }
505
506                 $scope.current_bucket = bucketSvc.currentBucket;
507                 $scope.revertable_fieldset_groups = [];
508
509                 egCore.pcrud.search('afsg',{
510                     rollback_group : { '>' : 0},
511                     rollback_time  : null,
512                     container      : bucketSvc.currentBucket.id(),
513                     container_type : 'user'
514                 } ).then( null,null,function(g) {
515                     $scope.revertable_fieldset_groups.push(g);
516                 });
517
518                 // This handels the progress magic instead of a normal close handler
519                 $scope.$on('modal.closing', function(event, reason, closed) {
520                     if (!$scope.revert_me) return;
521                     if (!closed) return; // dismissed
522                     if ($scope.complete) return; // already done
523
524                     $scope.running = true;
525
526                     var last_stage = '';
527                     egCore.net.request(
528                         'open-ils.actor',
529                         'open-ils.actor.container.user.apply_rollback',
530                         egCore.auth.token(), bucketSvc.currentBucket.id(), $scope.revert_me.id()
531                     ).then(
532                         function () {
533                             $scope.complete = true;
534                             $scope.modal.close();
535                             drawBucket();
536                         },
537                         function (err) { console.log('User edit error: ' + err); },
538                         function (p) {
539                             last_stage = p.stage;
540                             if (p.error) {
541                                 ngToast.warning(p.error);
542                             }
543                             if (p.stage == 'COMPLETE') return;
544
545                             p.label = egCore.strings[p.stage];
546                             if (!p.max) {
547                                 p.max = 1;
548                                 p.count = 1;
549                             }
550                             $scope.states[p.ord] = p;
551                         }
552                     ).then(function() {
553                         if (last_stage != 'COMPLETE')
554                             ngToast.warning(egCore.strings.BATCH_FAILED);
555                     });
556
557                     return event.preventDefault();
558                 });
559             }]
560         });
561     }
562
563     $scope.updateAllUsers = function() {
564         bucketSvc.bucketNeedsRefresh = true;
565
566         $uibModal.open({
567             templateUrl: './circ/patron/bucket/t_update_all',
568             backdrop: 'static',
569             controller: 
570                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
571                 $scope.running = false;
572                 $scope.complete = false;
573                 $scope.states = [];
574                 $scope.home_ou_name = '';
575                 $scope.args = {home_ou:null};
576                 $scope.focusMe = true;
577                 $scope.modal = $uibModalInstance;
578                 $scope.ok = function(args) { $uibModalInstance.close() }
579                 $scope.cancel = function () { $uibModalInstance.dismiss() }
580
581                 $scope.disable_home_org = function(org_id) {
582                     if (!org_id) return;
583                     var org = egCore.org.get(org_id);
584                     return (
585                         org &&
586                         org.ou_type() &&
587                         org.ou_type().can_have_users() == 'f'
588                     );
589                 }
590
591                 $scope.pgt_depth = function(grp) {
592                     var d = 0;
593                     while (grp = egCore.env.pgt.map[grp.parent()]) d++;
594                     return d;
595                 }
596
597                 if (egCore.env.cnal) {
598                     $scope.net_access_levels = egCore.env.cnal.list;
599                 } else {
600                     egCore.pcrud.retrieveAll('cnal', {}, {atomic : true})
601                     .then(function(types) {
602                         egCore.env.absorbList(types, 'cnal')
603                         $scope.net_access_levels = egCore.env.cnal.list;
604                     });
605                 }
606
607                 if (egCore.env.pgt) {
608                     $scope.profiles = egCore.env.pgt.list;
609                     annotate_groups($scope.profiles);
610                 } else {
611                     egCore.pcrud.search('pgt', {parent : null}, 
612                         {flesh : -1, flesh_fields : {pgt : ['children']}}
613                     ).then(
614                         function(tree) {
615                             egCore.env.absorbTree(tree, 'pgt')
616                             $scope.profiles = egCore.env.pgt.list;
617                             annotate_groups($scope.profiles);
618                         }
619                     );
620                 }
621
622                 $scope.unset_field = function (event,field) {
623                     $scope.args[field] = null;
624                     return event.preventDefault();
625                 }
626
627                 // This handels the progress magic instead of a normal close handler
628                 $scope.$on('modal.closing', function(event, reason, closed) {
629                     if (!$scope.args || !$scope.args.name) return;
630                     if (!closed) return; // dismissed
631                     if ($scope.complete) return; // already done
632
633                     $scope.running = true;
634
635                     // XXX fix up $scope.args values here
636                     if ($scope.args.home_ou) {
637                         $scope.args.home_ou = $scope.args.home_ou.id();
638                     }
639                     if ($scope.args.net_access_level) {
640                         $scope.args.net_access_level = $scope.args.net_access_level.id();
641                     }
642                     if ($scope.args.profile) {
643                         $scope.args.profile = $scope.args.profile.id();
644                     }
645                     if ($scope.args.expire_date) {
646                         $scope.args.expire_date = $scope.args.expire_date.toJSON().substr(0,10);
647                     }
648
649                     for (var key in $scope.args) {
650                         if (!$scope.args[key] && $scope.args[key] !== 0) {
651                             delete $scope.args[key];
652                         }
653                     }
654
655                     var last_stage = '';
656                     egCore.net.request(
657                         'open-ils.actor',
658                         'open-ils.actor.container.user.batch_edit',
659                         egCore.auth.token(), bucketSvc.currentBucket.id(), $scope.args.name, $scope.args
660                     ).then(
661                         function () {
662                             $scope.complete = true;
663                             $scope.modal.close();
664                             drawBucket();
665                         },
666                         function (err) { console.log('User edit error: ' + err); },
667                         function (p) {
668                             last_stage = p.stage;
669                             if (p.error) {
670                                 ngToast.warning(p.error);
671                             }
672                             if (p.stage == 'COMPLETE') return;
673
674                             p.label = egCore.strings[p.stage];
675                             if (!p.max) {
676                                 p.max = 1;
677                                 p.count = 1;
678                             }
679                             $scope.states[p.ord] = p;
680                         }
681                     ).then(function() {
682                         if (last_stage != 'COMPLETE')
683                             ngToast.warning(egCore.strings.BATCH_FAILED);
684                     });
685
686                     return event.preventDefault();
687                 });
688             }]
689         });
690     }
691
692     $scope.no_delete_perms = true;
693     $scope.noDeletePerms = function () { return $scope.no_delete_perms; }
694
695     egPerm.hasPermHere(['UPDATE_USER','DELETE_USER']).then(
696         function (hash) {
697             if (Object.keys(hash).length == 0) return;
698
699             var one_false = false;
700             angular.forEach(hash, function(has) {
701                 if (!has) one_false = true;
702             });
703
704             if (!one_false) $scope.no_delete_perms = false;
705         }
706     );
707
708     $scope.deleteAllUsers = function() {
709         bucketSvc.bucketNeedsRefresh = true;
710
711         $uibModal.open({
712             templateUrl: './circ/patron/bucket/t_delete_all',
713             backdrop: 'static',
714             controller: 
715                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
716                 $scope.running = false;
717                 $scope.complete = false;
718                 $scope.states = [];
719                 $scope.args = {};
720                 $scope.focusMe = true;
721                 $scope.modal = $uibModalInstance;
722                 $scope.ok = function(args) { $uibModalInstance.close() }
723                 $scope.cancel = function () { $uibModalInstance.dismiss() }
724
725                 // This handels the progress magic instead of a normal close handler
726                 $scope.$on('modal.closing', function(event, reason, closed) {
727                     if (!$scope.args || !$scope.args.name) return;
728                     if (!closed) return; // dismissed
729                     if ($scope.complete) return; // already done
730
731                     $scope.running = true;
732
733                     var last_stage = '';
734                     egCore.net.request(
735                         'open-ils.actor',
736                         'open-ils.actor.container.user.batch_delete',
737                         egCore.auth.token(), bucketSvc.currentBucket.id(), $scope.args.name, { deleted : 't' }
738                     ).then(
739                         function () {
740                             $scope.complete = true;
741                             $scope.modal.close();
742                             drawBucket();
743                         },
744                         function (err) { console.log('User deletion error: ' + err); },
745                         function (p) {
746                             last_stage = p.stage;
747                             if (p.error) {
748                                 ngToast.warning(p.error);
749                             }
750                             if (p.stage == 'COMPLETE') return;
751
752                             p.label = egCore.strings[p.stage];
753                             if (!p.max) {
754                                 p.max = 1;
755                                 p.count = 1;
756                             }
757                             $scope.states[p.ord] = p;
758                         }
759                     ).then(function() {
760                         if (last_stage != 'COMPLETE')
761                             ngToast.warning(egCore.strings.BATCH_FAILED);
762                     });
763
764                     return event.preventDefault();
765                 });
766             }]
767         });
768
769     }
770
771     $scope.detachUsers = function(users) {
772         var promises = [];
773         angular.forEach(users, function(rec) {
774             var item = bucketSvc.currentBucket.items().filter(
775                 function(i) {
776                     return (i.target_user() == rec.id)
777                 }
778             );
779             if (item.length)
780                 promises.push(bucketSvc.detachUser(item[0].id()));
781         });
782
783         bucketSvc.bucketNeedsRefresh = true;
784         return $q.all(promises).then(drawBucket);
785     }
786
787     $scope.spawnUserEdit = function (users) {
788         angular.forEach($scope.gridControls.selectedItems(), function (i) {
789             var url = egCore.env.basePath + 'circ/patron/' + i.id + '/edit';
790             $timeout(function() { $window.open(url, '_blank') });
791         })
792     }
793
794     // fetch the bucket;  on error show the not-allowed message
795     if ($scope.bucketId) 
796         drawBucket()['catch'](function() { $scope.forbidden = true });
797 }])