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).finally(function(){
14 $scope.focusMe = true;
16 $scope.checkouts = patronSvc.checkouts;
17 $scope.checkoutArgs = {
18 noncat_type : 'barcode',
22 $scope.gridDataProvider = egGridDataProvider.instance({
23 get : function(offset, count) {
24 return this.arrayNotifier($scope.checkouts, offset, count);
28 $scope.disable_checkout = function() {
31 patronSvc.current.active() == 'f' ||
32 patronSvc.current.deleted() == 't' ||
33 patronSvc.current.card().active() == 'f' ||
34 patronSvc.fetchedWithInactiveCard()
38 function setting_value (user, setting) {
40 var list = user.settings().filter(function(s){
41 return s.name() == setting;
44 if (list.length) return list[0].value();
48 $scope.date_options = {
49 due_date : egCore.hatch.getSessionItem('eg.circ.checkout.due_date'),
50 has_sticky_date : egCore.hatch.getSessionItem('eg.circ.checkout.is_until_logout'),
51 is_until_logout : egCore.hatch.getSessionItem('eg.circ.checkout.is_until_logout')
54 if ($scope.date_options.is_until_logout) { // If until_logout is set there should also be a date set.
55 $scope.checkoutArgs.due_date = new Date($scope.date_options.due_date);
56 $scope.checkoutArgs.sticky_date = true;
59 $scope.toggle_opt = function(opt) {
60 if ($scope.date_options[opt]) {
61 $scope.date_options[opt] = false;
63 $scope.date_options[opt] = true;
67 // The interactions between these options are complicated enough that $watch'ing them all is the only safe way to keep things sane.
68 $scope.$watch('date_options.has_sticky_date', function(newval) {
69 if ( newval ) { // was false, is true
70 // $scope.date_options.due_date = checkoutArgs.due_date;
72 $scope.date_options.is_until_logout = false;
74 $scope.checkoutArgs.sticky_date = newval;
77 $scope.$watch('date_options.is_until_logout', function(newval) {
78 if ( newval ) { // was false, is true
79 $scope.date_options.has_sticky_date = true;
80 $scope.date_options.due_date = $scope.checkoutArgs.due_date;
81 egCore.hatch.setSessionItem('eg.circ.checkout.is_until_logout', true);
82 egCore.hatch.setSessionItem('eg.circ.checkout.due_date', $scope.checkoutArgs.due_date);
84 egCore.hatch.removeSessionItem('eg.circ.checkout.is_until_logout');
85 egCore.hatch.removeSessionItem('eg.circ.checkout.due_date');
89 $scope.$watch('checkoutArgs.due_date', function(newval) {
90 if ( $scope.date_options.is_until_logout ) {
91 egCore.hatch.setSessionItem('eg.circ.checkout.due_date', newval);
95 $scope.has_email_address = function() {
98 patronSvc.current.email() &&
99 patronSvc.current.email().match(/.*@.*/).length
103 $scope.may_email_receipt = function() {
105 $scope.has_email_address() &&
108 'circ.send_email_checkout_receipts'
113 $scope.using_hatch_printer = egCore.hatch.usePrinting();
115 egCore.hatch.getItem('circ.checkout.strict_barcode')
116 .then(function(sb){ $scope.strict_barcode = sb });
118 // avoid multiple, in-flight attempts on the same barcode
119 var pending_barcodes = {};
121 var printOnComplete = true;
122 egCore.org.settings([
123 'circ.staff_client.do_not_auto_attempt_print'
124 ]).then(function(settings) {
125 printOnComplete = !Boolean(
126 angular.isArray(settings['circ.staff_client.do_not_auto_attempt_print']) &&
127 (settings['circ.staff_client.do_not_auto_attempt_print'].indexOf('Checkout') > -1)
131 egCirc.get_noncat_types().then(function(list) {
132 $scope.nonCatTypes = list;
135 $scope.selectedNcType = function() {
136 if (!egCore.env.cnct) return null; // too soon
137 var type = egCore.env.cnct.map[$scope.checkoutArgs.noncat_type];
138 return type ? type.name() : null;
141 $scope.checkout = function(args) {
142 var params = angular.copy(args);
143 params.patron_id = patronSvc.current.id();
145 if (args.sticky_date) {
146 params.due_date = args.due_date.toISOString();
148 delete params.due_date;
150 delete params.sticky_date;
152 if (params.noncat_type == 'barcode') {
153 if (!args.copy_barcode) return;
155 args.copy_barcode = ''; // reset UI input
156 params.noncat_type = ''; // "barcode"
158 if (pending_barcodes[params.copy_barcode]) {
160 "Skipping checkout of redundant barcode "
161 + params.copy_barcode
166 pending_barcodes[params.copy_barcode] = true;
167 send_checkout(params);
170 egCirc.noncat_dialog(params).then(function() {
171 send_checkout(params)
175 $scope.focusMe = true; // return focus to barcode input
178 function send_checkout(params) {
180 params.noncat_type = params.noncat ? params.noncat_type : '';
182 // populate the grid row before we send the request so that the
183 // order of actions is maintained and so the user gets an
184 // immediate reaction to their barcode input action.
186 index : $scope.checkouts.length,
187 input_barcode : params.copy_barcode,
188 noncat_type : params.noncat_type
191 $scope.checkouts.unshift(row_item);
192 $scope.gridDataProvider.refresh();
194 egCore.hatch.setItem('circ.checkout.strict_barcode', $scope.strict_barcode);
195 var options = {check_barcode : $scope.strict_barcode};
197 egCirc.checkout(params, options).then(
199 // update stats locally so we don't have to fetch them w/
202 // Avoid updating checkout counts when a checkout turns
203 // into a renewal via auto_renew.
204 if (!co_resp.auto_renew && !params.noncat) {
205 patronSvc.patron_stats.checkouts.out++;
206 patronSvc.patron_stats.checkouts.total_out++;
209 // copy the response event into the original grid row item
210 // note: angular.copy clobbers the destination
211 row_item.evt = co_resp.evt;
212 angular.forEach(co_resp.data, function(val, key) {
216 row_item['copy_barcode'] = row_item.acp.barcode();
218 munge_checkout_resp(co_resp, row_item);
221 // Circ was rejected somewhere along the way.
222 // Remove the copy from the grid since there was no action.
223 // note: since checkouts are unshifted onto the array, the
224 // index value does not (generally) match the array position.
226 angular.forEach($scope.checkouts, function(co, idx) {
227 if (co.index == row_item.index) pos = idx;
229 $scope.checkouts.splice(pos, 1);
230 $scope.gridDataProvider.refresh();
233 ).finally(function() {
235 // regardless of the outcome of the circ, remove the
236 // barcode from the pending list.
237 if (params.copy_barcode)
238 delete pending_barcodes[params.copy_barcode];
240 $scope.focusMe = true; // return focus to barcode input
244 // add some checkout-specific additions for display
245 function munge_checkout_resp(co_resp, row_item) {
246 var params = co_resp.params;
248 row_item.title = egCore.env.cnct.map[params.noncat_type].name();
249 row_item.noncat_count = params.noncat_count;
250 row_item.circ = new egCore.idl.circ();
251 row_item.circ.due_date(co_resp.evt[0].payload.noncat_circ.duedate());
252 // Non-cat circs don't return the full list of circs.
253 // Refresh the list of non-cat circs from the server.
254 patronSvc.getUserNonCats(patronSvc.current.id());
258 $scope.print_receipt = function() {
259 var print_data = {circulations : []}
261 if ($scope.checkouts.length == 0) return $q.when();
263 angular.forEach($scope.checkouts, function(co) {
265 print_data.circulations.push({
266 circ : egCore.idl.toHash(co.circ),
267 copy : egCore.idl.toHash(co.acp),
268 call_number : egCore.idl.toHash(co.acn),
275 print_data.patron_money = patronSvc.patron_stats.fines;
277 return egCore.print.print({
279 template : 'checkout',
281 show_dialog : $scope.show_print_dialog
285 $scope.email_receipt = function() {
286 if ($scope.has_email_address() && $scope.checkouts.length) {
287 return egCore.net.request(
289 'open-ils.circ.checkout.batch_notify.session.atomic',
291 patronSvc.current.id(),
292 $scope.checkouts.map(function (c) { return c.circ.id() })
294 ngToast.create(egCore.strings.EMAILED_CHECKOUT_RECEIPT);
301 $scope.print_or_email_receipt = function() {
302 if ($scope.may_email_receipt()) return $scope.email_receipt();
303 $scope.print_receipt();
306 // set of functions to issue a receipt (if desired), then
308 $scope.done_auto_receipt = function() {
309 if ($scope.may_email_receipt()) {
310 $scope.email_receipt().then(function() {
311 $scope.done_redirect();
314 if (printOnComplete) {
316 $scope.print_receipt().then(function() {
317 $scope.done_redirect();
321 $scope.done_redirect();
325 $scope.done_print_receipt = function() {
326 $scope.print_receipt().then( function () {
327 $scope.done_redirect();
330 $scope.done_email_receipt = function() {
331 $scope.email_receipt().then( function () {
332 $scope.done_redirect();
335 $scope.done_no_receipt = function() {
336 $scope.done_redirect();
339 // Redirect the user to the barcode entry page to load a new patron.
340 $scope.done_redirect = function() {
341 $location.path('/circ/patron/bcsearch');