2 * Checkout items to patrons
5 angular.module('egPatronApp').controller('PatronCheckoutCtrl',
7 ['$scope','$q','$routeParams','egCore','egUser','patronSvc',
8 'egGridDataProvider','$location','$timeout','egCirc','ngToast',
10 function($scope , $q , $routeParams , egCore , egUser , patronSvc ,
11 egGridDataProvider , $location , $timeout , egCirc , ngToast) {
13 $scope.initTab('checkout', $routeParams.id).then(function(){
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'];
21 limitedDate = new Date();
22 limitedDate.setSeconds(limitedDate.getSeconds()+egCore.date.intervalToSeconds(dateLimit))
24 $scope.maxDate=limitedDate;
26 }).finally(function(){
27 $scope.focusMe = true;
29 $scope.checkouts = patronSvc.checkouts;
30 $scope.checkoutArgs = {
31 noncat_type : 'barcode',
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);
43 $scope.disable_checkout = function() {
46 patronSvc.current.active() == 'f' ||
47 patronSvc.current.deleted() == 't' ||
48 patronSvc.current.card().active() == 'f' ||
49 patronSvc.fetchedWithInactiveCard() ||
50 $scope.outOfRange == true
54 function setting_value (user, setting) {
56 var list = user.settings().filter(function(s){
57 return s.name() == setting;
60 if (list.length) return list[0].value();
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')
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;
75 $scope.toggle_opt = function(opt) {
76 if ($scope.date_options[opt]) {
77 $scope.date_options[opt] = false;
79 $scope.date_options[opt] = true;
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;
88 $scope.date_options.is_until_logout = false;
90 $scope.checkoutArgs.sticky_date = newval;
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);
100 egCore.hatch.removeSessionItem('eg.circ.checkout.is_until_logout');
101 egCore.hatch.removeSessionItem('eg.circ.checkout.due_date');
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);
110 egCore.hatch.setSessionItem('eg.circ.checkout.due_date', $scope.checkoutArgs.due_date);
115 $scope.has_email_address = function() {
118 patronSvc.current.email() &&
119 patronSvc.current.email().match(/.*@.*/)
123 $scope.may_email_receipt = function() {
125 $scope.has_email_address() &&
128 'circ.send_email_checkout_receipts'
133 egCore.hatch.usePrinting().then(function(useHatch) {
134 $scope.using_hatch_printer = useHatch;
137 egCore.hatch.getItem('circ.checkout.strict_barcode')
138 .then(function(sb){ $scope.strict_barcode = sb });
140 // avoid multiple, in-flight attempts on the same barcode
141 var pending_barcodes = {};
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)
153 egCirc.get_noncat_types().then(function(list) {
154 $scope.nonCatTypes = list;
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;
163 $scope.checkout = function(args) {
164 var params = angular.copy(args);
165 params.patron_id = patronSvc.current.id();
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.
170 delete params.due_date;
172 delete params.sticky_date;
173 if (params.noncat_type == 'barcode') {
174 if (!args.copy_barcode) return;
176 args.copy_barcode = ''; // reset UI input
177 params.noncat_type = ''; // "barcode"
179 if (pending_barcodes[params.copy_barcode]) {
181 "Skipping checkout of redundant barcode "
182 + params.copy_barcode
187 pending_barcodes[params.copy_barcode] = true;
188 send_checkout(params);
191 egCirc.noncat_dialog(params).then(function() {
192 send_checkout(params)
196 $scope.focusMe = true; // return focus to barcode input
199 function send_checkout(params) {
201 params.noncat_type = params.noncat ? params.noncat_type : '';
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.
207 index : $scope.checkouts.length,
208 input_barcode : params.copy_barcode,
209 noncat_type : params.noncat_type
212 $scope.checkouts.unshift(row_item);
213 $scope.gridDataProvider.prepend();
215 egCore.hatch.setItem('circ.checkout.strict_barcode', $scope.strict_barcode);
216 var options = {check_barcode : $scope.strict_barcode};
218 egCirc.checkout(params, options).then(
220 // update stats locally so we don't have to fetch them w/
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++;
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) {
237 if (row_item.acp) { // unset for non-cat items.
238 row_item['copy_barcode'] = row_item.acp.barcode();
241 munge_checkout_resp(co_resp, row_item);
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.
249 angular.forEach($scope.checkouts, function(co, idx) {
250 if (co.index == row_item.index) pos = idx;
252 $scope.checkouts.splice(pos, 1);
253 $scope.gridDataProvider.refresh();
256 ).finally(function() {
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];
263 $scope.focusMe = true; // return focus to barcode input
267 // add some checkout-specific additions for display
268 function munge_checkout_resp(co_resp, row_item) {
269 var params = co_resp.params;
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;
280 row_item.copy_alert_count = 0;
283 { copy : co_resp.data.acp.id(), ack_time : null },
286 ).then(function(list) {
287 row_item.copy_alert_count = list.length;
292 $scope.addCopyAlerts = function(items) {
294 angular.forEach(items, function(item) {
295 if (item.acp) copy_ids.push(item.acp.id());
297 egCirc.add_copy_alerts(copy_ids).then(function() {
298 // update grid items?
302 $scope.manageCopyAlerts = function(items) {
304 angular.forEach(items, function(item) {
305 if (item.acp) copy_ids.push(item.acp.id());
307 egCirc.manage_copy_alerts(copy_ids).then(function() {
308 // update grid items?
312 $scope.gridCellHandlers = {};
313 $scope.gridCellHandlers.copyAlertsEdit = function(id) {
314 egCirc.manage_copy_alerts([id]).then(function() {
315 // update grid items?
319 $scope.print_receipt = function() {
320 var print_data = {circulations : []};
321 var cusr = patronSvc.current;
323 if ($scope.checkouts.length == 0) return $q.when();
325 angular.forEach($scope.checkouts, function(co) {
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?
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())
359 return egCore.print.print({
361 template : 'checkout',
363 show_dialog : $scope.show_print_dialog
367 $scope.email_receipt = function() {
368 if ($scope.has_email_address() && $scope.checkouts.length) {
369 return egCore.net.request(
371 'open-ils.circ.checkout.batch_notify.session.atomic',
373 patronSvc.current.id(),
374 $scope.checkouts.map(function (c) { return c.circ.id() })
376 ngToast.create(egCore.strings.EMAILED_CHECKOUT_RECEIPT);
383 $scope.print_or_email_receipt = function() {
384 if ($scope.may_email_receipt()) return $scope.email_receipt();
385 $scope.print_receipt();
388 // set of functions to issue a receipt (if desired), then
390 $scope.done_auto_receipt = function() {
391 if ($scope.may_email_receipt()) {
392 $scope.email_receipt().then(function() {
393 $scope.done_redirect();
396 if (printOnComplete) {
398 $scope.print_receipt().then(function() {
399 $scope.done_redirect();
403 $scope.done_redirect();
407 $scope.done_print_receipt = function() {
408 $scope.print_receipt().then( function () {
409 $scope.done_redirect();
412 $scope.done_email_receipt = function() {
413 $scope.email_receipt().then( function () {
414 $scope.done_redirect();
417 $scope.done_no_receipt = function() {
418 $scope.done_redirect();
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');