LP#1676608: copy alert and suppression matrix
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / checkin / app.js
1 angular.module('egCheckinApp', ['ngRoute', 'ui.bootstrap', 
2     'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
3
4 .config(function($routeProvider, $locationProvider, $compileProvider) {
5     $locationProvider.html5Mode(true);
6     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
7         
8     var resolver = {delay : 
9         ['egStartup', function(egStartup) {return egStartup.go()}]}
10
11     $routeProvider.when('/circ/checkin/checkin', {
12         templateUrl: './circ/checkin/t_checkin',
13         controller: 'CheckinCtrl',
14         resolve : resolver
15     });
16
17     $routeProvider.when('/circ/checkin/capture', {
18         templateUrl: './circ/checkin/t_checkin',
19         controller: 'CheckinCtrl',
20         resolve : resolver
21     });
22
23     $routeProvider.otherwise({redirectTo : '/circ/checkin/checkin'});
24 })
25
26 .factory('checkinSvc', [function() {
27     var service = {};
28     service.checkins = [];
29     return service;
30 }])
31
32
33 /**
34  * Manages checkin
35  */
36 .controller('CheckinCtrl',
37        ['$scope','$q','$window','$location', '$timeout','egCore','checkinSvc','egGridDataProvider','egCirc', 'egItem',
38 function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , egGridDataProvider , egCirc, itemSvc)  {
39
40     $scope.focusMe = true;
41     $scope.checkins = checkinSvc.checkins;
42     var today = new Date();
43     $scope.checkinArgs = {backdate : today}
44     $scope.using_hatch_printer = egCore.hatch.usePrinting();
45     $scope.modifiers = {};
46     $scope.fine_total = 0;
47     $scope.is_capture = $location.path().match(/capture$/);
48     var suppress_popups = false;
49     $scope.grid_persist_key = $scope.is_capture ? 
50         'circ.checkin.capture' : 'circ.checkin.checkin';
51
52     egCore.hatch.getItem('circ.checkin.strict_barcode')
53         .then(function(sb){ $scope.strict_barcode = sb });
54
55     egCore.org.settings([
56         'ui.circ.suppress_checkin_popups' // add other settings as needed
57     ]).then(function(set) {
58         suppress_popups = set['ui.circ.suppress_checkin_popups'];
59     });
60
61     $scope.sort_money = function (a,b) {
62         var ma = parseFloat(a);
63         var mb = parseFloat(b);
64         if (ma < mb) return -1;
65         if (ma > mb) return 1;
66         return 0
67     }
68
69     // checkin & hold capture modifiers
70     var modifiers = [
71         'void_overdues', 
72         'clear_expired',
73         'hold_as_transit',
74         'manual_float',
75         'no_precat_alert',
76         'retarget_holds',
77         'retarget_holds_all'
78     ];
79
80     if ($scope.is_capture) {
81         // in hold capture mode, some values are forced, regardless
82         // of stored preferences.
83         $scope.modifiers.noop = false;
84         $scope.modifiers.auto_print_holds_transits = true;
85     } else {
86         modifiers.push('noop'); // AKA suppress holds and transits
87         modifiers.push('auto_print_holds_transits');
88     }
89
90     // set modifiers from stored preferences
91     angular.forEach(modifiers, function(mod) {
92         egCore.hatch.getItem('eg.circ.checkin.' + mod)
93         .then(function(val) { if (val) $scope.modifiers[mod] = true });
94     });
95
96     // set / unset a checkin modifier
97     // when set, store the preference
98     $scope.toggle_mod = function(mod) {
99         if ($scope.modifiers[mod]) {
100             $scope.modifiers[mod] = false;
101             egCore.hatch.removeItem('eg.circ.checkin.' + mod);
102         } else {
103             $scope.modifiers[mod] = true;
104             egCore.hatch.setItem('eg.circ.checkin.' + mod, true);
105         }
106     }
107
108
109     // ensure the backdate is not in the future
110     // note: input type=date max=foo not yet supported anywhere
111     $scope.$watch('checkinArgs.backdate', function(newval) {
112         if (newval && newval > today) 
113             $scope.checkinArgs.backdate = today;
114     });
115
116     $scope.is_backdate = function() {
117         return $scope.checkinArgs.backdate < today;
118     }
119
120     var checkinGrid = $scope.gridControls = {};
121
122     $scope.gridDataProvider = egGridDataProvider.instance({
123         get : function(offset, count) {
124             return this.arrayNotifier($scope.checkins, offset, count);
125         }
126     });
127
128     // turns the various inputs (form args, modifiers, etc.) into
129     // checkin params and options.
130     function compile_checkin_args(args) {
131         var params = angular.copy(args);
132
133         if (params.backdate) {
134             params.backdate = 
135                 params.backdate.toISOString().replace(/T.*/,'');
136
137             // a backdate of 'today' is not really a backdate
138             if (params.backdate == $scope.max_backdate)
139                 delete params.backdate;
140         }
141
142         angular.forEach(['noop','void_overdues',
143                 'clear_expired','hold_as_transit','manual_float'],
144             function(opt) {
145                 if ($scope.modifiers[opt]) params[opt] = true;
146             }
147         );
148
149         if ($scope.modifiers.retarget_holds) {
150             if ($scope.modifiers.retarget_holds_all) {
151                 params.retarget_mode = 'retarget.all';
152             } else {
153                 params.retarget_mode = 'retarget';
154             }
155         }
156
157         egCore.hatch.setItem('circ.checkin.strict_barcode', $scope.strict_barcode);
158         var options = {
159             check_barcode : $scope.strict_barcode,
160             no_precat_alert : $scope.modifiers.no_precat_alert,
161             auto_print_holds_transits : 
162                 $scope.modifiers.auto_print_holds_transits,
163             suppress_popups : suppress_popups
164         };
165
166         return {params : params, options: options};
167     }
168
169     $scope.checkin = function(args) {
170
171         var compiled = compile_checkin_args(args);
172         args.copy_barcode = ''; // reset UI for next scan
173         $scope.focusMe = true;
174         delete $scope.alert;
175         delete $scope.billable_amount;
176         delete $scope.billable_barcode;
177         delete $scope.billable_user_id;
178
179         var params = compiled.params;
180         var options = compiled.options;
181
182         if (!params.copy_barcode) return;
183         delete $scope.alert;
184
185         var row_item = {
186             index : checkinSvc.checkins.length,
187             input_barcode : params.copy_barcode
188         };
189
190         // track the item in the grid before sending the request
191         checkinSvc.checkins.unshift(row_item);
192         checkinGrid.refresh();
193
194         egCirc.checkin(params, options).then(
195         function(final_resp) {
196             
197             row_item.evt = final_resp.evt;
198             angular.forEach(final_resp.data, function(val, key) {
199                 row_item[key] = val;
200             });
201             
202             row_item['copy_barcode'] = row_item.acp.barcode();
203
204             if (row_item.mbts) {
205                 var amt = Number(row_item.mbts.balance_owed());
206                 if (amt != 0) {
207                     $scope.billable_barcode = row_item.copy_barcode;
208                     $scope.billable_amount = amt;
209                     $scope.billable_user_id = row_item.circ.usr();
210                     $scope.fine_total = 
211                         ($scope.fine_total * 100 + amt * 100) / 100;
212                 }
213             }
214
215             if (final_resp.evt.textcode == 'NO_CHANGE') {
216                 $scope.alert = 
217                     {already_checked_in : final_resp.evt.copy_barcode};
218             }
219
220             if ($scope.trim_list && checkinSvc.checkins.length > 20)
221                 checkinSvc.checkins = checkinSvc.checkins.splice(0, 20);
222         },
223         function() {
224             // Checkin was rejected somewhere along the way.
225             // Remove the copy from the grid since there was no action.
226             // note: since checkins are unshifted onto the array, the
227             // index value does not (generally) match the array position.
228             var pos = -1;
229             angular.forEach(checkinSvc.checkins, function(ci, idx) {
230                 if (ci.index == row_item.index) pos = idx;
231             });
232             checkinSvc.checkins.splice(pos, 1);
233
234         })['finally'](function() {
235
236             // when all is said and done, refresh the grid and refocus
237             checkinGrid.refresh();
238             $scope.focusMe = true;
239         });
240     }
241
242     $scope.print_receipt = function() {
243         var print_data = {checkins : []}
244
245         if (checkinSvc.checkins.length == 0) return $q.when();
246
247         angular.forEach(checkinSvc.checkins, function(checkin) {
248
249             var checkin = {
250                 copy : egCore.idl.toHash(checkin.acp) || {},
251                 call_number : egCore.idl.toHash(checkin.acn) || {},
252                 copy_barcode : checkin.copy_barcode,
253                 title : checkin.title,
254                 author : checkin.author
255             }
256
257             print_data.checkins.push(checkin);
258         });
259
260         return egCore.print.print({
261             template : 'checkin', 
262             scope : print_data,
263             show_dialog : $scope.show_print_dialog
264         });
265     }
266
267
268     // --- context menu actions
269     //
270     $scope.fetchLastCircPatron = function(items) {
271         var checkin = items[0];
272         if (!checkin || !checkin.acp) return;
273
274         egCirc.last_copy_circ(checkin.acp.id())
275         .then(function(circ) {
276
277             if (circ) {
278                 // jump to the patron UI (separate app)
279                 $window.location.href = $location
280                     .path('/circ/patron/' + circ.usr() + '/checkout')
281                     .absUrl();
282                 return;
283             }
284
285             $scope.alert = {item_never_circed : checkin.acp.barcode()};
286         });
287     }
288
289     $scope.showBackdateDialog = function(items) {
290         var circ_ids = [];
291
292         angular.forEach(items, function(item) {
293             if (item.circ) circ_ids.push(item.circ.id());
294         });
295
296         if (circ_ids.length) {
297             egCirc.backdate_dialog(circ_ids).then(function(result) {
298                 angular.forEach(items, function(item) {
299                     item.circ.checkin_time(result.backdate);
300                 })
301             });
302             // TODO: support grid row styling
303             checkinGrid.refresh();
304         }
305     }
306
307     $scope.showMarkDamaged = function(items) {
308         var copy_ids = [];
309         angular.forEach(items, function(item) {
310             if (item.acp) {
311                 egCirc.mark_damaged({
312                     id: item.acp.id(),
313                     barcode: item.acp.barcode()
314                 })
315
316             }
317         });
318
319     }
320
321     $scope.abortTransit = function(items) {
322         var transit_ids = [];
323         angular.forEach(items, function(item) {
324             if (item.transit) transit_ids.push(item.transit.id());
325         });
326
327         egCirc.abort_transits(transit_ids).then(function() {
328             // update grid items?
329         });
330     }
331
332     $scope.add_copies_to_bucket = function(items){
333         var itemsIds = [];
334         angular.forEach(items, function(cp){
335             itemsIds.push(cp.acp.id());
336         });
337
338         itemSvc.add_copies_to_bucket(itemsIds);
339     }
340
341     $scope.showBibHolds = function(items){
342         var recordIds = [];
343         angular.forEach(items, function(i){
344             recordIds.push(i.acn.record());
345         });
346         angular.forEach(recordIds, function (r) {
347             var url = egCore.env.basePath + 'cat/catalog/record/' + r + '/holds';
348             $timeout(function() { $window.open(url, '_blank') });
349         });
350     }
351
352     $scope.showLastCircs = function(items){
353         var itemIds = [];
354         angular.forEach(items, function(cp){
355             itemIds.push(cp.acp.id());
356         });
357         angular.forEach(itemIds, function (id) {
358             var url = egCore.env.basePath + 'cat/item/' + id + '/circs';
359             $timeout(function() { $window.open(url, '_blank') });
360         });
361     }
362
363     $scope.selectedHoldingsVolCopyEdit = function (items) {
364         var itemObjs = [];
365         angular.forEach(items, function(i){
366             var h = egCore.idl.toHash(i);
367             itemObjs.push({
368                 'call_number.record.id': h.record.doc_id,
369                 'id' : h.acp.id
370             });
371         });
372         itemSvc.spawnHoldingsEdit(itemObjs,false,false);
373     }
374
375     $scope.show_mark_missing_pieces = function(items){
376         angular.forEach(items, function(i){
377             i.acp.call_number(i.acn);
378             i.acp.call_number().record(i.record);
379             itemSvc.mark_missing_pieces(i.acp);
380         });
381     }
382
383     $scope.printSpineLabels = function(items){
384         var copy_ids = [];
385         angular.forEach(items, function(item) {
386             if (item.acp) copy_ids.push(item.acp.id());
387         });
388         itemSvc.print_spine_labels(copy_ids);
389     }
390
391     $scope.addCopyAlerts = function(items) {
392         var copy_ids = [];
393         angular.forEach(items, function(item) {
394             if (item.acp) copy_ids.push(item.acp.id());
395         });
396         egCirc.add_copy_alerts(copy_ids).then(function() {
397             // update grid items?
398         });
399     }
400
401     $scope.manageCopyAlerts = function(items) {
402         var copy_ids = [];
403         angular.forEach(items, function(item) {
404             if (item.acp) copy_ids.push(item.acp.id());
405         });
406         egCirc.manage_copy_alerts(copy_ids).then(function() {
407             // update grid items?
408         });
409     }
410
411 }])
412