]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
LP#1537223 Show Username on copy buckets
[working/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?|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             ).then(function(patron) {
159                 bucket._owner_name = patron.usrname();
160                 bucket._owner_ou = egCore.org.get(patron.home_ou()).shortname();
161             });
162
163             service.currentBucket = bucket;
164             deferred.resolve(bucket);
165         });
166
167         return deferred.promise;
168     }
169
170     // deletes a single container item from a bucket by container item ID.
171     // promise is rejected on failure
172     service.detachCopy = function(itemId) {
173         var deferred = $q.defer();
174         egCore.net.request(
175             'open-ils.actor',
176             'open-ils.actor.container.item.delete',
177             egCore.auth.token(), 'copy', itemId
178         ).then(function(resp) { 
179             var evt = egCore.evt.parse(resp);
180             if (evt) {
181                 console.error(evt);
182                 deferred.reject(evt);
183                 return;
184             }
185             console.log('detached bucket item ' + itemId);
186             deferred.resolve(resp);
187         });
188
189         return deferred.promise;
190     }
191
192     // delete bucket by ID.
193     // resolved w/ response on successful delete,
194     // rejected otherwise.
195     service.deleteBucket = function(id) {
196         var deferred = $q.defer();
197         egCore.net.request(
198             'open-ils.actor',
199             'open-ils.actor.container.full_delete',
200             egCore.auth.token(), 'copy', id
201         ).then(function(resp) {
202             var evt = egCore.evt.parse(resp);
203             if (evt) {
204                 console.error(evt);
205                 deferred.reject(evt);
206                 return;
207             }
208             deferred.resolve(resp);
209         });
210         return deferred.promise;
211     }
212
213     return service;
214 }])
215
216 /**
217  * Top-level controller.  
218  * Hosts functions needed by all controllers.
219  */
220 .controller('CopyBucketCtrl',
221        ['$scope','$location','$q','$timeout','$uibModal',
222         '$window','egCore','bucketSvc',
223 function($scope,  $location,  $q,  $timeout,  $uibModal,  
224          $window,  egCore,  bucketSvc) {
225
226     $scope.bucketSvc = bucketSvc;
227     $scope.bucket = function() { return bucketSvc.currentBucket }
228
229     // tabs: search, pending, view
230     $scope.setTab = function(tab) { 
231         $scope.tab = tab;
232
233         // for bucket selector; must be called after route resolve
234         bucketSvc.fetchUserBuckets(); 
235     };
236
237     $scope.loadBucketFromMenu = function(item, bucket) {
238         if (bucket) return $scope.loadBucket(bucket.id());
239     }
240
241     $scope.loadBucket = function(id) {
242         $location.path(
243             '/cat/bucket/copy/' + 
244                 $scope.tab + '/' + encodeURIComponent(id));
245     }
246
247     $scope.addToBucket = function(recs) {
248         if (recs.length == 0) return;
249         bucketSvc.bucketNeedsRefresh = true;
250
251         angular.forEach(recs,
252             function(rec) {
253                 var item = new egCore.idl.ccbi();
254                 item.bucket(bucketSvc.currentBucket.id());
255                 item.target_copy(rec.id);
256                 egCore.net.request(
257                     'open-ils.actor',
258                     'open-ils.actor.container.item.create', 
259                     egCore.auth.token(), 'copy', item
260                 ).then(function(resp) {
261
262                     // HACK: add the IDs of the added items so that the size
263                     // of the view list will grow (and update any UI looking at
264                     // the list size).  The data stored is inconsistent, but since
265                     // we are forcing a bucket refresh on the next rendering of 
266                     // the view pane, the list will be repaired.
267                     bucketSvc.currentBucket.items().push(resp);
268                 });
269             }
270         );
271     }
272
273     $scope.openCreateBucketDialog = function() {
274         $uibModal.open({
275             templateUrl: './cat/bucket/share/t_bucket_create',
276             controller: 
277                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
278                 $scope.focusMe = true;
279                 $scope.ok = function(args) { $uibModalInstance.close(args) }
280                 $scope.cancel = function () { $uibModalInstance.dismiss() }
281             }]
282         }).result.then(function (args) {
283             if (!args || !args.name) return;
284             bucketSvc.createBucket(args.name, args.desc).then(
285                 function(id) {
286                     if (!id) return;
287                     bucketSvc.viewList = [];
288                     bucketSvc.allBuckets = []; // reset
289                     bucketSvc.currentBucket = null;
290                     $location.path(
291                         '/cat/bucket/copy/' + $scope.tab + '/' + id);
292                 }
293             );
294         });
295     }
296
297     $scope.openEditBucketDialog = function() {
298         $uibModal.open({
299             templateUrl: './cat/bucket/share/t_bucket_edit',
300             controller: 
301                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
302                 $scope.focusMe = true;
303                 $scope.args = {
304                     name : bucketSvc.currentBucket.name(),
305                     desc : bucketSvc.currentBucket.description(),
306                     pub : bucketSvc.currentBucket.pub() == 't'
307                 };
308                 $scope.ok = function(args) { 
309                     if (!args) return;
310                     $scope.actionPending = true;
311                     args.pub = args.pub ? 't' : 'f';
312                     // close the dialog after edit has completed
313                     bucketSvc.editBucket(args).then(
314                         function() { $uibModalInstance.close() });
315                 }
316                 $scope.cancel = function () { $uibModalInstance.dismiss() }
317             }]
318         })
319     }
320
321     // opens the delete confirmation and deletes the current
322     // bucket if the user confirms.
323     $scope.openDeleteBucketDialog = function() {
324         $uibModal.open({
325             templateUrl: './cat/bucket/share/t_bucket_delete',
326             controller : 
327                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
328                 $scope.bucket = function() { return bucketSvc.currentBucket }
329                 $scope.ok = function() { $uibModalInstance.close() }
330                 $scope.cancel = function() { $uibModalInstance.dismiss() }
331             }]
332         }).result.then(function () {
333             bucketSvc.deleteBucket(bucketSvc.currentBucket.id())
334             .then(function() {
335                 bucketSvc.allBuckets = [];
336                 $location.path('/cat/bucket/copy/view');
337             });
338         });
339     }
340
341     // retrieves the requested bucket by ID
342     $scope.openSharedBucketDialog = function() {
343         $uibModal.open({
344             templateUrl: './cat/bucket/share/t_load_shared',
345             controller :
346                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
347                 $scope.focusMe = true;
348                 $scope.ok = function(args) {
349                     if (args && args.id) {
350                         $uibModalInstance.close(args.id)
351                     }
352                 }
353                 $scope.cancel = function() { $uibModalInstance.dismiss() }
354             }]
355         }).result.then(function(id) {
356             // RecordBucketCtrl $scope is not inherited by the
357             // modal, so we need to call loadBucket from the
358             // promise resolver.
359             $scope.loadBucket(id);
360         });
361     }
362
363 }])
364
365 .controller('PendingCtrl',
366        ['$scope','$routeParams','bucketSvc','egGridDataProvider', 'egCore',
367 function($scope,  $routeParams,  bucketSvc , egGridDataProvider,   egCore) {
368     $scope.setTab('pending');
369
370     var query;
371     $scope.gridControls = {
372         setQuery : function(q) {
373             if (bucketSvc.pendingList.length)
374                 return {id : bucketSvc.pendingList};
375             else
376             return null;
377         }
378     }
379
380     $scope.search = function() {
381         bucketSvc.barcodeRecords = [];
382
383         egCore.pcrud.search(
384             'acp',
385             {barcode : bucketSvc.barcodeString, deleted : 'f'},
386             {}
387         ).then(null, null, function(copy) {
388             bucketSvc.pendingList.push(copy.id());
389             $scope.gridControls.setQuery({id : bucketSvc.pendingList});
390         });
391     }
392
393     $scope.resetPendingList = function() {
394         bucketSvc.pendingList = [];
395         $scope.gridControls.setQuery({});
396     }
397     
398     if ($routeParams.id && 
399         (!bucketSvc.currentBucket || 
400             bucketSvc.currentBucket.id() != $routeParams.id)) {
401         // user has accessed this page cold with a bucket ID.
402         // fetch the bucket for display, then set the totalCount
403         // (also for display), but avoid fully fetching the bucket,
404         // since it's premature, in this UI.
405         bucketSvc.fetchBucket($routeParams.id);
406     }
407     $scope.gridControls.setQuery();
408 }])
409
410 .controller('ViewCtrl',
411        ['$scope','$q','$routeParams','$timeout','$window','$uibModal','bucketSvc','egCore','egUser',
412         'egConfirmDialog',
413 function($scope,  $q , $routeParams , $timeout , $window , $uibModal , bucketSvc , egCore , egUser ,
414          egConfirmDialog) {
415
416     $scope.setTab('view');
417     $scope.bucketId = $routeParams.id;
418
419     var query;
420     $scope.gridControls = {
421         setQuery : function(q) {
422             if (q) query = q;
423             return query;
424         }
425     };
426
427     function drawBucket() {
428         return bucketSvc.fetchBucket($scope.bucketId).then(
429             function(bucket) {
430                 var ids = bucket.items().map(
431                     function(i){return i.target_copy()}
432                 );
433                 if (ids.length) {
434                     $scope.gridControls.setQuery({id : ids});
435                 } else {
436                     $scope.gridControls.setQuery({});
437                 }
438             }
439         );
440     }
441
442     $scope.detachCopies = function(copies) {
443         var promises = [];
444         angular.forEach(copies, function(rec) {
445             var item = bucketSvc.currentBucket.items().filter(
446                 function(i) {
447                     return (i.target_copy() == rec.id)
448                 }
449             );
450             if (item.length)
451                 promises.push(bucketSvc.detachCopy(item[0].id()));
452         });
453
454         bucketSvc.bucketNeedsRefresh = true;
455         return $q.all(promises).then(drawBucket);
456     }
457
458     $scope.spawnHoldingsEdit = function (copies) {
459         var cp_list = []
460         angular.forEach($scope.gridControls.selectedItems(), function (i) {
461             cp_list.push(i.id);
462         })
463
464         egCore.net.request(
465             'open-ils.actor',
466             'open-ils.actor.anon_cache.set_value',
467             null, 'edit-these-copies', {
468                 record_id: 0, // false-y value for record_id disables record summary
469                 copies: cp_list,
470                 hide_vols : true,
471                 hide_copies : false
472             }
473         ).then(function(key) {
474             if (key) {
475                 var url = egCore.env.basePath + 'cat/volcopy/' + key;
476                 $timeout(function() { $window.open(url, '_blank') });
477             } else {
478                 alert('Could not create anonymous cache key!');
479             }
480         });
481     }
482
483     $scope.requestItems = function() {
484         var copy_list = $scope.gridControls.selectedItems().map(
485             function (i) {
486                 i.id;
487             }
488         );
489
490         if (copy_list.length == 0) return;
491
492         return $uibModal.open({
493             templateUrl: './cat/catalog/t_request_items',
494             animation: true,
495             controller:
496                    ['$scope','$uibModalInstance',
497             function($scope , $uibModalInstance) {
498                 $scope.user = null;
499                 $scope.first_user_fetch = true;
500
501                 $scope.hold_data = {
502                     hold_type : 'C',
503                     copy_list : copy_list,
504                     pickup_lib: egCore.org.get(egCore.auth.user().ws_ou()),
505                     user      : egCore.auth.user().id()
506                 };
507
508                 egUser.get( $scope.hold_data.user ).then(function(u) {
509                     $scope.user = u;
510                     $scope.barcode = u.card().barcode();
511                     $scope.user_name = egUser.format_name(u);
512                     $scope.hold_data.user = u.id();
513                 });
514
515                 $scope.user_name = '';
516                 $scope.barcode = '';
517                 $scope.$watch('barcode', function (n) {
518                     if (!$scope.first_user_fetch) {
519                         egUser.getByBarcode(n).then(function(u) {
520                             $scope.user = u;
521                             $scope.user_name = egUser.format_name(u);
522                             $scope.hold_data.user = u.id();
523                         }, function() {
524                             $scope.user = null;
525                             $scope.user_name = '';
526                             delete $scope.hold_data.user;
527                         });
528                     }
529                     $scope.first_user_fetch = false;
530                 });
531
532                 $scope.ok = function(h) {
533                     var args = {
534                         patronid  : h.user,
535                         hold_type : h.hold_type,
536                         pickup_lib: h.pickup_lib.id(),
537                         depth     : 0
538                     };
539
540                     egCore.net.request(
541                         'open-ils.circ',
542                         'open-ils.circ.holds.test_and_create.batch.override',
543                         egCore.auth.token(), args, h.copy_list
544                     );
545
546                     $uibModalInstance.close();
547                 }
548
549                 $scope.cancel = function($event) {
550                     $uibModalInstance.dismiss();
551                     $event.preventDefault();
552                 }
553             }]
554         });
555     }
556
557     $scope.deleteCopiesFromCatalog = function(copies) {
558         egConfirmDialog.open(
559             egCore.strings.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG,
560             '', {}
561         ).result.then(function() {
562             var fleshed_copies = [];
563             var promises = [];
564             angular.forEach(copies, function(i) {
565                 promises.push(
566                     egCore.net.request(
567                         'open-ils.search',
568                         'open-ils.search.asset.copy.fleshed2.retrieve',
569                         i.id
570                     ).then(function(copy) {
571                         copy.ischanged(1);
572                         copy.isdeleted(1);
573                         fleshed_copies.push(copy);
574                     })
575                 );
576             });
577             $q.all(promises).then(function() {
578                 egCore.net.request(
579                     'open-ils.cat',
580                     'open-ils.cat.asset.copy.fleshed.batch.update',
581                     egCore.auth.token(), fleshed_copies, true
582                 ).then(function(resp) {
583                     var evt = egCore.evt.parse(resp);
584                     if (evt) {
585                         egConfirmDialog.open(
586                             egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_TITLE,
587                             egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_BODY,
588                             {'evt_desc': evt.desc}
589                         ).result.then(function() {
590                             egCore.net.request(
591                                 'open-ils.cat',
592                                 'open-ils.cat.asset.copy.fleshed.batch.update.override',
593                                 egCore.auth.token(), fleshed_copies, true,
594                                 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
595                             ).then(function(resp) {
596                                 bucketSvc.bucketNeedsRefresh = true;
597                                 drawBucket();
598                             });
599                         });
600                     }
601                     bucketSvc.bucketNeedsRefresh = true;
602                     drawBucket();
603                 });
604             });
605         });
606     }
607
608     $scope.transferCopies = function(copies) {
609         var xfer_target = egCore.hatch.getLocalItem('eg.cat.item_transfer_target');
610         var copy_ids = copies.map(
611             function(curr,idx,arr) {
612                 return curr.id;
613             }
614         );
615         if (xfer_target) {
616             egCore.net.request(
617                 'open-ils.cat',
618                 'open-ils.cat.transfer_copies_to_volume',
619                 egCore.auth.token(),
620                 xfer_target,
621                 copy_ids
622             ).then(
623                 function(resp) { // oncomplete
624                     var evt = egCore.evt.parse(resp);
625                     if (evt) {
626                         egConfirmDialog.open(
627                             egCore.strings.OVERRIDE_TRANSFER_COPY_BUCKET_ITEMS_TO_MARKED_VOLUME_TITLE,
628                             egCore.strings.OVERRIDE_TRANSFER_COPY_BUCKET_ITEMS_TO_MARKED_VOLUME_BODY,
629                             {'evt_desc': evt.desc}
630                         ).result.then(function() {
631                             egCore.net.request(
632                                 'open-ils.cat',
633                                 'open-ils.cat.transfer_copies_to_volume.override',
634                                 egCore.auth.token(),
635                                 xfer_target,
636                                 copy_ids,
637                                 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
638                             ).then(function(resp) {
639                                 bucketSvc.bucketNeedsRefresh = true;
640                                 drawBucket();
641                             });
642                         });
643                     } else {
644                         bucketSvc.bucketNeedsRefresh = true;
645                         drawBucket();
646                     }
647                 },
648                 null, // onerror
649                 null // onprogress
650             )
651         }
652     }
653
654     // fetch the bucket;  on error show the not-allowed message
655     if ($scope.bucketId) 
656         drawBucket()['catch'](function() { $scope.forbidden = true });
657 }])