]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
LP1864056: Don't compare multiple new Date()s
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / circ / patron / checkout.js
1 /**
2  * Checkout items to patrons
3  */
4
5 angular.module('egPatronApp').controller('PatronCheckoutCtrl',
6
7        ['$scope','$q','$routeParams','egCore','egUser','patronSvc',
8         'egGridDataProvider','$location','$timeout','egCirc','ngToast',
9
10 function($scope , $q , $routeParams , egCore , egUser , patronSvc , 
11          egGridDataProvider , $location , $timeout , egCirc , ngToast) {
12     var now = new Date();
13
14     $scope.initTab('checkout', $routeParams.id).finally(function(){
15         $scope.focusMe = true;
16     });
17     $scope.checkouts = patronSvc.checkouts;
18     $scope.checkoutArgs = {
19         noncat_type : 'barcode',
20         due_date : new Date(now)
21     };
22
23     $scope.minDate = new Date(now);
24     $scope.outOfRange = false;
25     $scope.gridDataProvider = egGridDataProvider.instance({
26         get : function(offset, count) {
27             return this.arrayNotifier($scope.checkouts, offset, count);
28         }
29     });
30
31     $scope.disable_checkout = function() {
32         return (
33             !patronSvc.current ||
34             patronSvc.current.active() == 'f' ||
35             patronSvc.current.deleted() == 't' ||
36             patronSvc.current.card().active() == 'f' ||
37             patronSvc.fetchedWithInactiveCard() ||
38             $scope.outOfRange == true
39         );
40     }
41
42     function setting_value (user, setting) {
43         if (user) {
44             var list = user.settings().filter(function(s){
45                 return s.name() == setting;
46             });
47
48             if (list.length) return list[0].value();
49         }
50     }
51
52     $scope.date_options = {
53         due_date : egCore.hatch.getSessionItem('eg.circ.checkout.due_date'),
54         has_sticky_date : egCore.hatch.getSessionItem('eg.circ.checkout.is_until_logout'),
55         is_until_logout : egCore.hatch.getSessionItem('eg.circ.checkout.is_until_logout')
56     };
57
58     if ($scope.date_options.is_until_logout) { // If until_logout is set there should also be a date set.
59         $scope.checkoutArgs.due_date = new Date($scope.date_options.due_date);
60         $scope.checkoutArgs.sticky_date = true;
61     }
62
63     $scope.toggle_opt = function(opt) {
64         if ($scope.date_options[opt]) {
65             $scope.date_options[opt] = false;
66         } else {
67             $scope.date_options[opt] = true;
68         }
69     };
70
71     // The interactions between these options are complicated enough that $watch'ing them all is the only safe way to keep things sane.
72     $scope.$watch('date_options.has_sticky_date', function(newval) {
73         if ( newval ) { // was false, is true
74             // $scope.date_options.due_date = checkoutArgs.due_date;
75         } else {
76             $scope.date_options.is_until_logout = false;
77         }
78         $scope.checkoutArgs.sticky_date = newval;
79     });
80
81     $scope.$watch('date_options.is_until_logout', function(newval) {
82         if ( newval ) { // was false, is true
83             $scope.date_options.has_sticky_date = true;
84             $scope.date_options.due_date = $scope.checkoutArgs.due_date;
85             egCore.hatch.setSessionItem('eg.circ.checkout.is_until_logout', true);
86             egCore.hatch.setSessionItem('eg.circ.checkout.due_date', $scope.checkoutArgs.due_date);
87         } else {
88             egCore.hatch.removeSessionItem('eg.circ.checkout.is_until_logout');
89             egCore.hatch.removeSessionItem('eg.circ.checkout.due_date');
90         }
91     });
92
93     $scope.$watch('checkoutArgs.due_date', function(newval) {
94         if ( $scope.date_options.is_until_logout && !isNaN(newval)) {
95             if (!$scope.outOfRange) {
96                 egCore.hatch.setSessionItem('eg.circ.checkout.due_date', newval);
97             } else {
98                 egCore.hatch.setSessionItem('eg.circ.checkout.due_date', $scope.checkoutArgs.due_date);
99             }
100         }
101     });
102
103     $scope.has_email_address = function() {
104         return (
105             patronSvc.current &&
106             patronSvc.current.email() &&
107             patronSvc.current.email().match(/.*@.*/)
108         );
109     }
110
111     $scope.may_email_receipt = function() {
112         return (
113             $scope.has_email_address() &&
114             setting_value(
115                 patronSvc.current,
116                 'circ.send_email_checkout_receipts'
117             ) == 'true'
118         );
119     }
120
121     egCore.hatch.usePrinting().then(function(useHatch) {
122         $scope.using_hatch_printer = useHatch;
123     });
124
125     egCore.hatch.getItem('circ.checkout.strict_barcode')
126         .then(function(sb){ $scope.strict_barcode = sb });
127
128     // avoid multiple, in-flight attempts on the same barcode
129     var pending_barcodes = {};
130
131     var printOnComplete = true;
132     egCore.org.settings([
133         'circ.staff_client.do_not_auto_attempt_print'
134     ]).then(function(settings) { 
135         printOnComplete = !Boolean(
136             angular.isArray(settings['circ.staff_client.do_not_auto_attempt_print']) &&
137             (settings['circ.staff_client.do_not_auto_attempt_print'].indexOf('Checkout') > -1)
138         );
139     });
140
141     egCirc.get_noncat_types().then(function(list) {
142         $scope.nonCatTypes = list;
143     });
144
145     $scope.selectedNcType = function() {
146         if (!egCore.env.cnct) return null; // too soon
147         var type = egCore.env.cnct.map[$scope.checkoutArgs.noncat_type];
148         return type ? type.name() : null;
149     }
150
151     $scope.checkout = function(args) {
152         var params = angular.copy(args);
153         params.patron_id = patronSvc.current.id();
154
155         if (args.sticky_date) {
156             params.due_date = args.due_date.toISOString();
157         } else {
158             delete params.due_date;
159         }
160         delete params.sticky_date;
161
162         if (params.noncat_type == 'barcode') {
163             if (!args.copy_barcode) return;
164
165             args.copy_barcode = ''; // reset UI input
166             params.noncat_type = ''; // "barcode"
167
168             if (pending_barcodes[params.copy_barcode]) {
169                 console.log(
170                     "Skipping checkout of redundant barcode " 
171                     + params.copy_barcode
172                 );
173                 return;
174             }
175
176             pending_barcodes[params.copy_barcode] = true;
177             send_checkout(params);
178
179         } else {
180             egCirc.noncat_dialog(params).then(function() {
181                 send_checkout(params)
182             });
183         }
184
185         $scope.focusMe = true; // return focus to barcode input
186     }
187
188     function send_checkout(params) {
189
190         params.noncat_type = params.noncat ? params.noncat_type : '';
191
192         // populate the grid row before we send the request so that the
193         // order of actions is maintained and so the user gets an 
194         // immediate reaction to their barcode input action.
195         var row_item = {
196             index : $scope.checkouts.length,
197             input_barcode : params.copy_barcode,
198             noncat_type : params.noncat_type
199         };
200
201         $scope.checkouts.unshift(row_item);
202         $scope.gridDataProvider.prepend();
203
204         egCore.hatch.setItem('circ.checkout.strict_barcode', $scope.strict_barcode);
205         var options = {check_barcode : $scope.strict_barcode};
206
207         egCirc.checkout(params, options).then(
208             function(co_resp) {
209                 // update stats locally so we don't have to fetch them w/
210                 // each checkout.
211
212                 // Avoid updating checkout counts when a checkout turns
213                 // into a renewal via auto_renew.
214                 if (!co_resp.auto_renew && !params.noncat && !options.sameCopyCheckout) {
215                     patronSvc.patron_stats.checkouts.out++;
216                     patronSvc.patron_stats.checkouts.total_out++;
217                 }
218
219                 // copy the response event into the original grid row item
220                 // note: angular.copy clobbers the destination
221                 row_item.evt = co_resp.evt;
222                 angular.forEach(co_resp.data, function(val, key) {
223                     row_item[key] = val;
224                 });
225                
226                 if (row_item.acp) { // unset for non-cat items.
227                     row_item['copy_barcode'] = row_item.acp.barcode();
228                 }
229
230                 munge_checkout_resp(co_resp, row_item);
231             },
232             function() {
233                 // Circ was rejected somewhere along the way.
234                 // Remove the copy from the grid since there was no action.
235                 // note: since checkouts are unshifted onto the array, the
236                 // index value does not (generally) match the array position.
237                 var pos = -1;
238                 angular.forEach($scope.checkouts, function(co, idx) {
239                     if (co.index == row_item.index) pos = idx;
240                 });
241                 $scope.checkouts.splice(pos, 1);
242                 $scope.gridDataProvider.refresh();
243             }
244
245         ).finally(function() {
246
247             // regardless of the outcome of the circ, remove the 
248             // barcode from the pending list.
249             if (params.copy_barcode)
250                 delete pending_barcodes[params.copy_barcode];
251
252             $scope.focusMe = true; // return focus to barcode input
253         });
254     }
255
256     // add some checkout-specific additions for display
257     function munge_checkout_resp(co_resp, row_item) {
258         var params = co_resp.params;
259         if (params.noncat) {
260             row_item.title = egCore.env.cnct.map[params.noncat_type].name();
261             row_item.noncat_count = params.noncat_count;
262             row_item.circ = new egCore.idl.circ();
263             row_item.circ.due_date(co_resp.evt[0].payload.noncat_circ.duedate());
264             // Non-cat circs don't return the full list of circs.
265             // Refresh the list of non-cat circs from the server.
266             patronSvc.getUserNonCats(patronSvc.current.id());
267             row_item.copy_alert_count = 0;
268         } else {
269             row_item.copy_alert_count = 0;
270             egCore.pcrud.search(
271                 'aca',
272                 { copy : co_resp.data.acp.id(), ack_time : null },
273                 null,
274                 { atomic : true }
275             ).then(function(list) {
276                 row_item.copy_alert_count = list.length;
277             });
278         }
279     }
280
281     $scope.addCopyAlerts = function(items) {
282         var copy_ids = [];
283         angular.forEach(items, function(item) {
284             if (item.acp) copy_ids.push(item.acp.id());
285         });
286         egCirc.add_copy_alerts(copy_ids).then(function() {
287             // update grid items?
288         });
289     }
290
291     $scope.manageCopyAlerts = function(items) {
292         var copy_ids = [];
293         angular.forEach(items, function(item) {
294             if (item.acp) copy_ids.push(item.acp.id());
295         });
296         egCirc.manage_copy_alerts(copy_ids).then(function() {
297             // update grid items?
298         });
299     }
300
301     $scope.gridCellHandlers = {};
302     $scope.gridCellHandlers.copyAlertsEdit = function(id) {
303         egCirc.manage_copy_alerts([id]).then(function() {
304             // update grid items?
305         });
306     };
307
308     $scope.print_receipt = function() {
309         var print_data = {circulations : []};
310         var cusr = patronSvc.current;
311
312         if ($scope.checkouts.length == 0) return $q.when();
313
314         angular.forEach($scope.checkouts, function(co) {
315             if (co.circ) {
316                 print_data.circulations.push({
317                     circ : egCore.idl.toHash(co.circ),
318                     copy : egCore.idl.toHash(co.acp),
319                     call_number : egCore.idl.toHash(co.acn), // Wrong?
320                     owning_lib : egCore.idl.toHash(co.aou), // Wrong?
321                     title : co.title,
322                     author : co.author
323                 });
324             };
325         });
326
327         // This is repeated in patron.* so everything is in one place but left here so existing templates don't break.
328         print_data.patron_money = patronSvc.patron_stats.fines;
329         print_data.patron = {
330             prefix : cusr.prefix(),
331             first_given_name : cusr.first_given_name(),
332             second_given_name : cusr.second_given_name(),
333             family_name : cusr.family_name(),
334             suffix : cusr.suffix(),
335             pref_prefix : cusr.pref_prefix(),
336             pref_first_given_name : cusr.pref_first_given_name(),
337             pref_secondg_given_name : cusr.second_given_name(),
338             pref_family_name : cusr.pref_family_name(),
339             suffix : cusr.suffix(),
340             card : { barcode : cusr.card().barcode() },
341             money_summary : patronSvc.patron_stats.fines,
342             expire_date : cusr.expire_date(),
343             alias : cusr.alias(),
344             has_email : Boolean($scope.has_email_address()),
345             has_phone : Boolean(cusr.day_phone() || cusr.evening_phone() || cusr.other_phone())
346         };
347
348         return egCore.print.print({
349             context : 'default', 
350             template : 'checkout', 
351             scope : print_data,
352             show_dialog : $scope.show_print_dialog
353         });
354     }
355
356     $scope.email_receipt = function() {
357         if ($scope.has_email_address() && $scope.checkouts.length) {
358             return egCore.net.request(
359                 'open-ils.circ',
360                 'open-ils.circ.checkout.batch_notify.session.atomic',
361                 egCore.auth.token(),
362                 patronSvc.current.id(),
363                 $scope.checkouts.map(function (c) { return c.circ.id() })
364             ).then(function() {
365                 ngToast.create(egCore.strings.EMAILED_CHECKOUT_RECEIPT);
366                 return $q.when();
367             });
368         }
369         return $q.when();
370     }
371
372     $scope.print_or_email_receipt = function() {
373         if ($scope.may_email_receipt()) return $scope.email_receipt();
374         $scope.print_receipt();
375     }
376
377     // set of functions to issue a receipt (if desired), then
378     // redirect
379     $scope.done_auto_receipt = function() {
380         if ($scope.may_email_receipt()) {
381             $scope.email_receipt().then(function() {
382                 $scope.done_redirect();
383             });
384         } else {
385             if (printOnComplete) {
386
387                 $scope.print_receipt().then(function() {
388                     $scope.done_redirect();
389                 });
390
391             } else {
392                 $scope.done_redirect();
393             }
394         }
395     }
396     $scope.done_print_receipt = function() {
397         $scope.print_receipt().then( function () {
398             $scope.done_redirect();
399         });
400     }
401     $scope.done_email_receipt = function() {
402         $scope.email_receipt().then( function () {
403             $scope.done_redirect();
404         });
405     }
406     $scope.done_no_receipt = function() {
407         $scope.done_redirect();
408     }
409
410     // Redirect the user to the barcode entry page to load a new patron.
411     $scope.done_redirect = function() {
412         $location.path('/circ/patron/bcsearch');
413     }
414 }])
415