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