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