Merge branch 'master' of git.evergreen-ils.org:Evergreen
[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?|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','egCore','checkinSvc','egGridDataProvider','egCirc',
38 function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataProvider , egCirc)  {
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     // checkin & hold capture modifiers
62     var modifiers = [
63         'void_overdues', 
64         'clear_expired',
65         'hold_as_transit',
66         'manual_float',
67         'no_precat_alert',
68         'retarget_holds',
69         'retarget_holds_all'
70     ];
71
72     if ($scope.is_capture) {
73         // in hold capture mode, some values are forced, regardless
74         // of stored preferences.
75         $scope.modifiers.noop = false;
76         $scope.modifiers.auto_print_holds_transits = true;
77     } else {
78         modifiers.push('noop'); // AKA suppress holds and transits
79         modifiers.push('auto_print_holds_transits');
80     }
81
82     // set modifiers from stored preferences
83     angular.forEach(modifiers, function(mod) {
84         egCore.hatch.getItem('eg.circ.checkin.' + mod)
85         .then(function(val) { if (val) $scope.modifiers[mod] = true });
86     });
87
88     // set / unset a checkin modifier
89     // when set, store the preference
90     $scope.toggle_mod = function(mod) {
91         if ($scope.modifiers[mod]) {
92             $scope.modifiers[mod] = false;
93             egCore.hatch.removeItem('eg.circ.checkin.' + mod);
94         } else {
95             $scope.modifiers[mod] = true;
96             egCore.hatch.setItem('eg.circ.checkin.' + mod, true);
97         }
98     }
99
100
101     // ensure the backdate is not in the future
102     // note: input type=date max=foo not yet supported anywhere
103     $scope.$watch('checkinArgs.backdate', function(newval) {
104         if (newval && newval > today) 
105             $scope.checkinArgs.backdate = today;
106     });
107
108     $scope.is_backdate = function() {
109         return $scope.checkinArgs.backdate < today;
110     }
111
112     var checkinGrid = $scope.gridControls = {};
113
114     $scope.gridDataProvider = egGridDataProvider.instance({
115         get : function(offset, count) {
116             return this.arrayNotifier($scope.checkins, offset, count);
117         }
118     });
119
120     // turns the various inputs (form args, modifiers, etc.) into
121     // checkin params and options.
122     function compile_checkin_args(args) {
123         var params = angular.copy(args);
124
125         if (params.backdate) {
126             params.backdate = 
127                 params.backdate.toISOString().replace(/T.*/,'');
128
129             // a backdate of 'today' is not really a backdate
130             if (params.backdate == $scope.max_backdate)
131                 delete params.backdate;
132         }
133
134         angular.forEach(['noop','void_overdues',
135                 'clear_expired','hold_as_transit','manual_float'],
136             function(opt) {
137                 if ($scope.modifiers[opt]) params[opt] = true;
138             }
139         );
140
141         if ($scope.modifiers.retarget_holds) {
142             if ($scope.modifiers.retarget_holds_all) {
143                 params.retarget_mode = 'retarget.all';
144             } else {
145                 params.retarget_mode = 'retarget';
146             }
147         }
148
149         egCore.hatch.setItem('circ.checkin.strict_barcode', $scope.strict_barcode);
150         var options = {
151             check_barcode : $scope.strict_barcode,
152             no_precat_alert : $scope.modifiers.no_precat_alert,
153             auto_print_holds_transits : 
154                 $scope.modifiers.auto_print_holds_transits,
155             suppress_popups : suppress_popups
156         };
157
158         return {params : params, options: options};
159     }
160
161     $scope.checkin = function(args) {
162
163         var compiled = compile_checkin_args(args);
164         args.copy_barcode = ''; // reset UI for next scan
165         $scope.focusMe = true;
166         delete $scope.alert;
167         delete $scope.billable_amount;
168         delete $scope.billable_barcode;
169         delete $scope.billable_user_id;
170
171         var params = compiled.params;
172         var options = compiled.options;
173
174         if (!params.copy_barcode) return;
175         delete $scope.alert;
176
177         var row_item = {
178             index : checkinSvc.checkins.length,
179             copy_barcode : params.copy_barcode
180         };
181
182         // track the item in the grid before sending the request
183         checkinSvc.checkins.unshift(row_item);
184         checkinGrid.refresh();
185
186         egCirc.checkin(params, options).then(
187         function(final_resp) {
188
189             row_item.evt = final_resp.evt;
190             angular.forEach(final_resp.data, function(val, key) {
191                 row_item[key] = val;
192             });
193
194             if (row_item.mbts) {
195                 var amt = Number(row_item.mbts.balance_owed());
196                 if (amt != 0) {
197                     $scope.billable_barcode = row_item.copy_barcode;
198                     $scope.billable_amount = amt;
199                     $scope.billable_user_id = row_item.circ.usr();
200                     $scope.fine_total = 
201                         ($scope.fine_total * 100 + amt * 100) / 100;
202                 }
203             }
204
205             if (final_resp.evt.textcode == 'NO_CHANGE') {
206                 $scope.alert = 
207                     {already_checked_in : final_resp.evt.copy_barcode};
208             }
209
210             if ($scope.trim_list && checkinSvc.checkins.length > 20)
211                 checkinSvc.checkins = checkinSvc.checkins.splice(0, 20);
212         },
213         function() {
214             // Checkin was rejected somewhere along the way.
215             // Remove the copy from the grid since there was no action.
216             // note: since checkins are unshifted onto the array, the
217             // index value does not (generally) match the array position.
218             var pos = -1;
219             angular.forEach(checkinSvc.checkins, function(ci, idx) {
220                 if (ci.index == row_item.index) pos = idx;
221             });
222             checkinSvc.checkins.splice(pos, 1);
223
224         })['finally'](function() {
225
226             // when all is said and done, refresh the grid and refocus
227             checkinGrid.refresh();
228             $scope.focusMe = true;
229         });
230     }
231
232     $scope.print_receipt = function() {
233         var print_data = {checkins : []}
234
235         if (checkinSvc.checkins.length == 0) return $q.when();
236
237         angular.forEach(checkinSvc.checkins, function(checkin) {
238
239             var checkin = {
240                 copy : egCore.idl.toHash(checkin.acp) || {},
241                 call_number : egCore.idl.toHash(checkin.acn) || {},
242                 copy_barcode : checkin.copy_barcode,
243                 title : checkin.title,
244                 author : checkin.author
245             }
246
247             print_data.checkins.push(checkin);
248         });
249
250         return egCore.print.print({
251             template : 'checkin', 
252             scope : print_data,
253             show_dialog : $scope.show_print_dialog
254         });
255     }
256
257
258     // --- context menu actions
259     //
260     $scope.fetchLastCircPatron = function(items) {
261         var checkin = items[0];
262         if (!checkin || !checkin.acp) return;
263
264         egCirc.last_copy_circ(checkin.acp.id())
265         .then(function(circ) {
266
267             if (circ) {
268                 // jump to the patron UI (separate app)
269                 $window.location.href = $location
270                     .path('/circ/patron/' + circ.usr() + '/checkout')
271                     .absUrl();
272                 return;
273             }
274
275             $scope.alert = {item_never_circed : checkin.acp.barcode()};
276         });
277     }
278
279     $scope.showBackdateDialog = function(items) {
280         var circ_ids = [];
281
282         angular.forEach(items, function(item) {
283             if (item.circ) circ_ids.push(item.circ.id());
284         });
285
286         if (circ_ids.length) {
287             egCirc.backdate_dialog(circ_ids).then(function(result) {
288                 angular.forEach(items, function(item) {
289                     item.circ.checkin_time(result.backdate);
290                 })
291             });
292             // TODO: support grid row styling
293             checkinGrid.refresh();
294         }
295     }
296
297     $scope.showMarkDamaged = function(items) {
298         var copy_ids = [];
299         angular.forEach(items, function(item) {
300             if (item.acp) copy_ids.push(item.acp.id());
301         });
302
303         if (copy_ids.length) {
304             egCirc.mark_damaged(copy_ids).then(function() {
305                 // update grid items?
306             });
307         }
308     }
309
310     $scope.abortTransit = function(items) {
311         var transit_ids = [];
312         angular.forEach(items, function(item) {
313             if (item.transit) transit_ids.push(item.transit.id());
314         });
315
316         egCirc.abort_transits(transit_ids).then(function() {
317             // update grid items?
318         });
319     }
320
321 }])
322