]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/holds/app.js
LP#1712854: Provide a "only last captured copy" option to filter holds shelf
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / holds / app.js
1 angular.module('egHoldsApp', 
2     ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
3
4 .config(function($routeProvider, $locationProvider, $compileProvider) {
5     $locationProvider.html5Mode(true);
6     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
7
8     var resolver = {delay : 
9         ['egStartup', function(egStartup) {return egStartup.go()}]}
10
11     $routeProvider.when('/circ/holds/shelf', {
12         templateUrl: './circ/holds/t_shelf',
13         controller: 'HoldsShelfCtrl',
14         resolve : resolver
15     });
16
17     $routeProvider.when('/circ/holds/shelf/:hold_id', {
18         templateUrl: './circ/holds/t_shelf',
19         controller: 'HoldsShelfCtrl',
20         resolve : resolver
21     });
22
23     $routeProvider.when('/circ/holds/pull', {
24         templateUrl: './circ/holds/t_pull',
25         controller: 'HoldsPullListCtrl',
26         resolve : resolver
27     });
28
29     $routeProvider.when('/circ/holds/pull/:hold_id', {
30         templateUrl: './circ/holds/t_pull',
31         controller: 'HoldsPullListCtrl',
32         resolve : resolver
33     });
34
35     $routeProvider.otherwise({redirectTo : '/circ/holds/shelf'});
36 })
37
38
39 .controller('HoldsShelfCtrl',
40        ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egHoldGridActions','egCirc','egGridDataProvider','egProgressDialog',
41 function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egHoldGridActions , egCirc , egGridDataProvider , egProgressDialog)  {
42     $scope.detail_hold_id = $routeParams.hold_id;
43
44     var hold_ids = [];
45     var holds = [];
46     var clear_mode = false;
47     $scope.gridControls = {};
48     $scope.grid_actions = egHoldGridActions;
49
50     var provider = egGridDataProvider.instance({});
51     $scope.gridDataProvider = provider;
52
53     function refresh_page() {
54         hold_count = 0;
55         holds = [];
56         hold_ids = [];
57         provider.refresh();
58     }
59     // called after any egHoldGridActions action occurs
60     $scope.grid_actions.refresh = refresh_page;
61
62     provider.get = function(offset, count) {
63
64         // see if we have the requested range cached
65         if (holds[offset]) {
66             return provider.arrayNotifier(holds, offset, count);
67         }
68
69         // if in clear mode...
70         if (clear_mode && holds.length) {
71             holds = holds.filter(function(h) { return h.hold.clear_me == 't' });
72         } 
73
74         hold_count = 0;
75         holds = [];
76         var restrictions = {
77                 is_staff_request  : 'true',
78                 last_captured_hold: 'true',
79                 capture_time      : { not : null },
80                 cs_id             : 8, // on holds shelf
81                 fulfillment_time  : null,
82                 current_shelf_lib : $scope.pickup_ou.id()
83         };
84
85         var order_by = [{ capture_time : null }];
86         if (provider.sort && provider.sort.length) {
87             order_by = [];
88             angular.forEach(provider.sort, function (c) {
89                 if (!angular.isObject(c)) {
90                     if (c.match(/^hold\./)) {
91                         var i = c.replace('hold.','');
92                         var ob = {};
93                         ob[i] = null;
94                         order_by.push(ob);
95                     }
96                 } else {
97                     var i = Object.keys(c)[0];
98                     var direction = c[i];
99                     if (i.match(/^hold\./)) {
100                         i = i.replace('hold.','');
101                         var ob = {}
102                         ob[i] = {dir:direction};
103                         order_by.push(ob);
104                     }
105                 }
106             });
107         }
108
109         egProgressDialog.open({max : 1, value : 0});
110         var first = true;
111         return egHolds.fetch_wide_holds(
112             restrictions,
113             order_by
114         ).then(function () {
115                 return provider.arrayNotifier(holds, offset, count);
116             },
117             null,
118             function(hold_data) { 
119                 if (first) {
120                     hold_count = hold_data;
121                     first = false;
122                     egProgressDialog.update({max:hold_count});
123                 } else {
124                     egProgressDialog.increment();
125                     var new_item = { id : hold_data.id, hold : hold_data };
126                     new_item.status_string =
127                         egCore.strings['HOLD_STATUS_' + hold_data.hold_status]
128                         || hold_data.hold_status;
129
130                     if (clear_mode) {
131                         if (hold_data.clear_me == 't') holds.push(new_item);
132                     } else {
133                         holds.push(new_item);
134                     }
135                 }
136             }
137         ).finally(egProgressDialog.close);
138     }
139
140     // re-draw the grid when user changes the org selector
141     $scope.pickup_ou = egCore.org.get(egCore.auth.user().ws_ou());
142     $scope.$watch('pickup_ou', function(newVal, oldVal) {
143         if (newVal && newVal != oldVal) 
144             refresh_page();
145     });
146
147     $scope.detail_view = function(action, user_data, items) {
148         if (h = items[0]) {
149             $location.path('/circ/holds/shelf/' + h.hold.id);
150         }
151     }
152
153     $scope.list_view = function(items) {
154         $location.path('/circ/holds/shelf');
155     }
156
157     // when the detail hold is fetched (and updated), update the bib
158     // record summary display record id.
159     $scope.set_hold = function(hold_data) {
160         $scope.detail_hold_record_id = hold_data.hold.record_id;
161     }
162
163     // manage active vs. clearable holds display
164     var clearing = false; // true if actively clearing holds (below)
165     $scope.is_clearing = function() { return clearing };
166     $scope.active_mode = function() {return !clear_mode}
167     $scope.clear_mode = function() {return clear_mode}
168     $scope.show_clearable = function() { clear_mode = true; refresh_page() }
169     $scope.show_active = function() { clear_mode = false; refresh_page() }
170     $scope.disable_clear = function() { return clearing || !clear_mode }
171
172     // udpate the in-grid hold with the clear-shelf cached response info.
173     function handle_clear_cache_resp(resp) {
174         if (!angular.isArray(resp)) resp = [resp];
175         angular.forEach(resp, function(info) {
176             if (info.action) {
177                 var grid_item = holds.filter(function(item) {
178                     return item.hold.id == info.hold_details.id
179                 })[0];
180
181                 // there will be no grid item if the hold is off-page
182                 if (grid_item) {
183                     grid_item.post_clear = 
184                         egCore.strings['CLEAR_SHELF_ACTION_' + info.action];
185                 }
186             }
187         });
188     }
189
190     $scope.clear_holds = function() {
191         clearing = true;
192         $scope.clear_progress = {max : 0, value : 0};
193
194         // we want to see all processed holds, so (effectively) remove
195         // the grid limit.
196         $scope.gridControls.setLimit(1000, true); 
197
198         // initiate clear shelf and grab cache key
199         egCore.net.request(
200             'open-ils.circ',
201             'open-ils.circ.hold.clear_shelf.process',
202             egCore.auth.token(), $scope.pickup_ou.id(),
203             null, 1
204
205         // request responses from the clear shelf cache
206         ).then(
207             
208             // clear shelf done; fetch the cached results.
209             function(resp) {
210                 clearing = false;
211                 egCore.net.request(
212                     'open-ils.circ',
213                     'open-ils.circ.hold.clear_shelf.get_cache',
214                     egCore.auth.token(), resp.cache_key, 1
215                 ).then(null, null, handle_clear_cache_resp);
216             }, 
217
218             null,
219
220             // handle streamed clear_shelf progress updates
221             function(resp) {
222                 if (resp.maximum) 
223                     $scope.clear_progress.max = resp.maximum;
224                 if (resp.progress)
225                     $scope.clear_progress.value = resp.progress;
226             }
227
228         );
229     }
230
231     function map_prefix_to_subhash (h,pf) {
232         var newhash = {};
233         angular.forEach(Object.keys(h), function (k) {
234             if (k.startsWith(pf)) {
235                 var nk = k.substr(pf.length);
236                 newhash[nk] = h[k];
237             }
238         });
239         return newhash;
240     }
241
242     $scope.print_shelf_list = function() {
243         var print_holds = [];
244         angular.forEach(holds, function(hold_data) {
245             var phold = {};
246             print_holds.push(phold);
247
248             phold.status_string = hold_data.status_string;
249
250             phold.patron_first = hold_data.hold.usr_first_given_name;
251             phold.patron_last = hold_data.hold.usr_family_name;
252             phold.patron_alias = hold_data.hold.usr_alias;
253             phold.patron_barcode = hold_data.hold.ucard_barcode;
254
255             phold.title = hold_data.hold.title;
256             phold.author = hold_data.hold.author;
257
258             phold.hold = hold_data.hold;
259             phold.copy = map_prefix_to_subhash(hold_data.hold, 'cp_');
260             phold.volume = map_prefix_to_subhash(hold_data.hold, 'cn_');
261             phold.part = map_prefix_to_subhash(hold_data.hold, 'p_');
262         });
263
264         console.log(print_holds);
265
266         return egCore.print.print({
267             context : 'default', 
268             template : 'hold_shelf_list', 
269             scope : {holds : print_holds}
270         });
271     }
272
273     refresh_page();
274
275 }])
276
277 .controller('HoldsPullListCtrl',
278        ['$scope','$q','$routeParams','$window','$location','egCore',
279         'egHolds','egCirc','egHoldGridActions','egProgressDialog',
280 function($scope , $q , $routeParams , $window , $location , egCore , 
281          egHolds , egCirc , egHoldGridActions , egProgressDialog) {
282
283     $scope.detail_hold_id = $routeParams.hold_id;
284
285     var cached_details = {};
286     var details_needed = {};
287
288     $scope.gridControls = {
289         setQuery : function() {
290             return {'copy_circ_lib_id' : egCore.auth.user().ws_ou()}
291         },
292         setSort : function() {
293             return ['copy_location_order_position','call_number_sort_key']
294         },
295         collectStarted : function(offset) {
296             // Launch an indeterminate -> semi-determinate progress
297             // modal.  Using a determinate modal that starts counting
298             // on the post-grid holds data retrieval results in a modal
299             // that's stuck at 0% for most of its life, which is aggravating.
300             egProgressDialog.open();
301         },
302         itemRetrieved : function(item) {
303             egProgressDialog.increment();
304             if (!cached_details[item.id]) {
305                 details_needed[item.id] = item;
306             }
307         },
308         allItemsRetrieved : function() {
309             flesh_holds().finally(egProgressDialog.close);
310         }
311     }
312
313
314     // Fetches hold detail data for each hold in the grid and links
315     // the detail data to the related grid item so egHoldGridActions 
316     // and friends have access to holds data they understand.
317     // Only fetch not-yet-cached data.
318     function flesh_holds() {
319         egProgressDialog.increment();
320
321         // Start by fleshing hold details from our cached data.
322         var items = $scope.gridControls.allItems();
323         angular.forEach(items, function(item) {
324             if (!cached_details[item.id]) return $q.when();
325             angular.forEach(cached_details[item.id], 
326                 function(val, key) { item[key] = val })
327         });
328
329         // Exit if all needed details were already cached
330         if (Object.keys(details_needed).length == 0) return $q.when();
331
332         return egCore.net.request(
333             'open-ils.circ',
334             'open-ils.circ.hold.details.batch.retrieve.authoritative',
335             egCore.auth.token(), Object.keys(details_needed), {
336                 include_usr : true
337             }
338
339         ).then(null, null, function(hold_info) {
340             egProgressDialog.increment();
341
342             // check if this is a staff-created hold
343             // i.e., requestor is not the same as the user
344             hold_info['_is_staff_hold'] = hold_info.hold.requestor() != hold_info.hold.usr().id();
345
346             var hold_id = hold_info.hold.id();
347             cached_details[hold_id] = hold_info;
348             var item = details_needed[hold_id];
349             delete details_needed[hold_id];
350
351             // flesh the grid item from the blob of hold data.
352             angular.forEach(hold_info, 
353                 function(val, key) { item[key] = val });
354
355         });
356     }
357
358     $scope.grid_actions = egHoldGridActions;
359     $scope.grid_actions.refresh = function() {
360         cached_details = {}; // un-cache details after edit actions.
361         $scope.gridControls.refresh();
362     }
363
364     $scope.detail_view = function(action, user_data, items) {
365         if (h = items[0]) {
366             $location.path('/circ/holds/pull/' + h.hold.id());
367         }
368     }
369
370     $scope.list_view = function(items) {
371         $location.path('/circ/holds/pull');
372     }
373
374     // when the detail hold is fetched (and updated), update the bib
375     // record summary display record id.
376     $scope.set_hold = function(hold_data) {
377         $scope.detail_hold_record_id = hold_data.mvr.doc_id();
378     }
379
380     // By default, this action is hidded from the UI, but leaving it
381     // here in case it's needed in the future
382     $scope.print_list_alt = function() {
383         var url = '/opac/extras/circ/alt_holds_print.html';
384         var win = $window.open(url, '_blank');
385         win.ses = function() {return egCore.auth.token()};
386         win.open();
387         win.focus();
388     }
389
390     $scope.print_full_list = function() {
391         var print_holds = [];
392         egProgressDialog.open({value : 0});
393
394         // collect the full list of holds
395         egCore.net.request(
396             'open-ils.circ',
397             'open-ils.circ.hold_pull_list.fleshed.stream',
398             egCore.auth.token(), 10000, 0
399         ).then(
400             function() {
401                 console.debug('printing ' + print_holds.length + ' holds');
402
403                 // holds fetched, send to print
404                 egCore.print.print({
405                     context : 'default', 
406                     template : 'hold_pull_list', 
407                     scope : {holds : print_holds}
408                 });
409             },
410             null, 
411             function(hold_data) {
412                 egProgressDialog.increment();
413                 egHolds.local_flesh(hold_data);
414                 print_holds.push(hold_data);
415                 hold_data.title = hold_data.mvr.title();
416                 hold_data.author = hold_data.mvr.author();
417                 hold_data.hold = egCore.idl.toHash(hold_data.hold);
418                 hold_data.copy = egCore.idl.toHash(hold_data.copy);
419                 hold_data.volume = egCore.idl.toHash(hold_data.volume);
420                 hold_data.part = egCore.idl.toHash(hold_data.part);
421             }
422         ).finally(egProgressDialog.close);
423     }
424
425 }])
426