]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/circ/selfcheck/selfcheck.js
plugged in audio alerts for selfcheck. local config is managed by TT path overriding...
[Evergreen.git] / Open-ILS / web / js / ui / default / circ / selfcheck / selfcheck.js
1 dojo.require('dojo.date.locale');
2 dojo.require('dojo.date.stamp');
3 dojo.require('openils.CGI');
4 dojo.require('openils.Util');
5 dojo.require('openils.User');
6 dojo.require('openils.Event');
7 dojo.require('openils.widget.ProgressDialog');
8
9 dojo.requireLocalization('openils.circ', 'selfcheck');
10 var localeStrings = dojo.i18n.getLocalization('openils.circ', 'selfcheck');
11
12
13 const SET_BARCODE_REGEX = 'opac.barcode_regex';
14 const SET_PATRON_TIMEOUT = 'circ.selfcheck.patron_login_timeout';
15 const SET_ALERT_ON_CHECKOUT_EVENT = 'circ.selfcheck.alert_on_checkout_event';
16 const SET_AUTO_OVERRIDE_EVENTS = 'circ.selfcheck.auto_override_checkout_events';
17 const SET_PATRON_PASSWORD_REQUIRED = 'circ.selfcheck.patron_password_required';
18 const SET_AUTO_RENEW_INTERVAL = 'circ.checkout_auto_renew_age';
19 const SET_WORKSTATION_REQUIRED = 'circ.selfcheck.workstation_required';
20 const SET_SOUND_ON_CHECKOUT_EVENT = 'circ.selfcheck.sound_on_checkout_event';
21
22 //openils.Util.playAudioUrl('/xul/server/skin/media/audio/bonus.wav');
23
24 function SelfCheckManager() {
25
26     this.cgi = new openils.CGI();
27     this.staff = null; 
28     this.workstation = null;
29     this.authtoken = null;
30
31     this.patron = null; 
32     this.patronBarcodeRegex = null;
33
34     // current item barcode
35     this.itemBarcode = null; 
36
37     // are we currently performing a renewal?
38     this.isRenewal = false; 
39
40     // dict of org unit settings for "here"
41     this.orgSettings = {};
42
43     // Construct a mock checkout for debugging purposes
44     if(this.mockCheckouts = this.cgi.param('mock-circ')) {
45
46         this.mockCheckout = {
47             payload : {
48                 record : new fieldmapper.mvr(),
49                 copy : new fieldmapper.acp(),
50                 circ : new fieldmapper.circ()
51             }
52         };
53
54         this.mockCheckout.payload.record.title('Jazz improvisation for guitar');
55         this.mockCheckout.payload.record.author('Wise, Les');
56         this.mockCheckout.payload.record.isbn('0634033565');
57         this.mockCheckout.payload.copy.barcode('123456789');
58         this.mockCheckout.payload.circ.renewal_remaining(1);
59         this.mockCheckout.payload.circ.parent_circ(1);
60         this.mockCheckout.payload.circ.due_date('2012-12-21');
61     }
62 }
63
64
65
66 /**
67  * Fetch the org-unit settings, initialize the display, etc.
68  */
69 SelfCheckManager.prototype.init = function() {
70
71     this.staff = openils.User.user;
72     this.workstation = openils.User.workstation;
73     this.authtoken = openils.User.authtoken;
74     this.loadOrgSettings();
75
76     // workstation is required but none provided
77     if(this.orgSettings[SET_WORKSTATION_REQUIRED] && !this.workstation) {
78         alert(dojo.string.substitute(localeStrings.WORKSTATION_REQUIRED));
79         return;
80     }
81     
82     var self = this;
83     // connect onclick handlers to the various navigation links
84     var linkHandlers = {
85         'oils-selfck-hold-details-link' : function() { self.drawHoldsPage(); },
86         'oils-selfck-nav-holds' : function() { self.drawHoldsPage(); },
87         'oils-selfck-pay-fines-link' : function() { self.drawFinesPage(); },
88         'oils-selfck-nav-fines' : function() { self.drawFinesPage(); },
89         'oils-selfck-nav-home' : function() { self.drawCircPage(); },
90         'oils-selfck-nav-logout' : function() { self.logoutPatron(); }
91     }
92
93     for(var id in linkHandlers) 
94         dojo.connect(dojo.byId(id), 'onclick', linkHandlers[id]);
95
96
97     if(this.cgi.param('patron')) {
98         
99         // Patron barcode via cgi param.  Mainly used for debugging and
100         // only works if password is not required by policy
101         this.loginPatron(this.cgi.param('patron'));
102
103     } else {
104         this.drawLoginPage();
105     }
106 }
107
108 /**
109  * Loads the org unit settings
110  */
111 SelfCheckManager.prototype.loadOrgSettings = function() {
112
113     var settings = fieldmapper.aou.fetchOrgSettingBatch(
114         this.staff.ws_ou(), [
115             SET_BARCODE_REGEX,
116             SET_PATRON_TIMEOUT,
117             SET_ALERT_ON_CHECKOUT_EVENT,
118             SET_AUTO_OVERRIDE_EVENTS,
119             SET_PATRON_PASSWORD_REQUIRED,
120             SET_AUTO_RENEW_INTERVAL,
121             SET_WORKSTATION_REQUIRED
122         ]
123     );
124
125     for(k in settings) {
126         if(settings[k])
127             this.orgSettings[k] = settings[k].value;
128     }
129
130     if(settings[SET_BARCODE_REGEX]) 
131         this.patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);
132 }
133
134 SelfCheckManager.prototype.drawLoginPage = function() {
135     var self = this;
136
137     var bcHandler = function(barcode) {
138         // handle patron barcode entry
139
140         if(self.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
141             
142             // password is required.  wire up the scan box to read it
143             self.updateScanBox({
144                 msg : 'Please enter your password', // TODO i18n 
145                 handler : function(pw) { self.loginPatron(barcode, pw); },
146                 password : true
147             });
148
149         } else {
150             // password is not required, go ahead and login
151             self.loginPatron(barcode);
152         }
153     };
154
155     this.updateScanBox({
156         msg : 'Please log in with your library barcode.', // TODO
157         handler : bcHandler
158     });
159 }
160
161 /**
162  * Login the patron.  
163  */
164 SelfCheckManager.prototype.loginPatron = function(barcode, passwd) {
165
166     if(this.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
167         
168         if(!passwd) {
169             // would only happen in dev/debug mode when using the patron= param
170             alert('password required by org setting.  remove patron= from URL'); 
171             return;
172         }
173
174         // patron password is required.  Verify it.
175
176         var res = fieldmapper.standardRequest(
177             ['open-ils.actor', 'open-ils.actor.verify_user_password'],
178             {params : [this.authtoken, barcode, null, hex_md5(passwd)]}
179         );
180
181         if(res == 0) {
182             // user-not-found results in login failure
183             this.handleAlert(
184                 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
185                 false, 'login-failure'
186             );
187             this.drawLoginPage();
188             return;
189         }
190     } 
191
192     // retrieve the fleshed user by barcode
193     this.patron = fieldmapper.standardRequest(
194         ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve_by_barcode'],
195         {params : [this.authtoken, barcode]}
196     );
197
198     var evt = openils.Event.parse(this.patron);
199     if(evt) {
200         this.handleAlert(
201             dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
202             false, 'login-failure'
203         );
204         this.drawLoginPage();
205
206     } else {
207
208         this.handleAlert('', false, 'login-success');
209         dojo.byId('oils-selfck-user-banner').innerHTML = 'Welcome, ' + this.patron.usrname(); // TODO i18n
210         this.drawCircPage();
211     }
212 }
213
214
215 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
216
217     console.log("Handling alert " + message);
218
219     dojo.byId('oils-selfck-status-div').innerHTML = message;
220
221     if(shouldPopup && this.orgSettings[SET_ALERT_ON_CHECKOUT_EVENT]) 
222         alert(message);
223
224     if(sound && this.orgSettings[SET_SOUND_ON_CHECKOUT_EVENT])
225         openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);
226 }
227
228
229 /**
230  * Manages the main input box
231  * @param msg The context message to display with the box
232  * @param clearOnly Don't update the context message, just clear the value and re-focus
233  * @param handler Optional "on-enter" handler.  
234  */
235 SelfCheckManager.prototype.updateScanBox = function(args) {
236     args = args || {};
237
238     if(args.select) {
239         selfckScanBox.domNode.select();
240     } else {
241         selfckScanBox.attr('value', '');
242     }
243
244     if(args.password) {
245         selfckScanBox.domNode.setAttribute('type', 'password');
246     } else {
247         selfckScanBox.domNode.setAttribute('type', '');
248     }
249
250     if(args.value)
251         selfckScanBox.attr('value', args.value);
252
253     if(args.msg) 
254         dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;
255
256     if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {
257         dojo.disconnect(selfckScanBox._lastHandler);
258     }
259
260     if(args.handler) {
261         selfckScanBox._lastHandler = dojo.connect(
262             selfckScanBox, 
263             'onKeyDown', 
264             function(e) {
265                 if(e.keyCode != dojo.keys.ENTER) 
266                     return;
267                 args.handler(selfckScanBox.attr('value'));
268             }
269         );
270     }
271
272     selfckScanBox.focus();
273 }
274
275 /**
276  *  Sets up the checkout/renewal interface
277  */
278 SelfCheckManager.prototype.drawCircPage = function() {
279
280     var self = this;
281     this.updateScanBox({
282         msg : 'Please enter an item barcode', // TODO i18n
283         handler : function(barcode) { self.checkout(barcode); }
284     });
285
286     openils.Util.hide('oils-selfck-payment-page');
287     openils.Util.hide('oils-selfck-holds-page');
288     openils.Util.show('oils-selfck-circ-page');
289
290     this.circTbody = dojo.byId('oils-selfck-circ-tbody');
291     if(!this.circTemplate)
292         this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));
293
294     // items out, holds, and fines summaries
295
296     // fines summary
297     fieldmapper.standardRequest(
298         ['open-ils.actor', 'open-ils.actor.user.fines.summary'],
299         {   async : true,
300             params : [this.authtoken, this.patron.id()],
301             oncomplete : function(r) {
302                 var summary = openils.Util.readResponse(r);
303                 dojo.byId('oils-selfck-fines-total').innerHTML = 
304                     dojo.string.substitute(
305                         localeStrings.TOTAL_FINES_ACCOUNT, 
306                         [summary.balance_owed()]
307                     );
308             }
309         }
310     );
311
312     // holds summary
313     this.updateHoldsSummary();
314
315     // items out summary
316     this.updateCircSummary();
317
318     // render mock checkouts for debugging?
319     if(this.mockCheckouts) {
320         for(var i in [1,2,3]) 
321             this.displayCheckout(this.mockCheckout, 'checkout');
322     }
323 }
324
325 SelfCheckManager.prototype.updateHoldsSummary = function() {
326
327     if(!this.holdsSummary) {
328         var summary = fieldmapper.standardRequest(
329             ['open-ils.circ', 'open-ils.circ.holds.user_summary'],
330             {params : [this.authtoken, this.patron.id()]}
331         );
332
333         this.holdsSummary = {};
334         this.holdsSummary.ready = Number(summary['4']);
335         this.holdsSummary.total = 0;
336
337         for(var i in summary) 
338             this.holdsSummary.total += Number(summary[i]);
339     }
340
341     dojo.byId('oils-selfck-holds-total').innerHTML = 
342         dojo.string.substitute(
343             localeStrings.TOTAL_HOLDS, 
344             [this.holdsSummary.total]
345         );
346
347     dojo.byId('oils-selfck-holds-ready').innerHTML = 
348         dojo.string.substitute(
349             localeStrings.HOLDS_READY_FOR_PICKUP, 
350             [this.holdsSummary.ready]
351         );
352 }
353
354
355 SelfCheckManager.prototype.updateCircSummary = function(increment) {
356
357     if(!this.circSummary) {
358
359         var summary = fieldmapper.standardRequest(
360             ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],
361             {params : [this.authtoken, this.patron.id()]}
362         );
363
364         this.circSummary = {
365             total : Number(summary.out) + Number(summary.overdue),
366             overdue : Number(summary.overdue),
367             session : 0
368         };
369     }
370
371     if(increment) {
372         // local checkout occurred.  Add to the total and the session.
373         this.circSummary.total += 1;
374         this.circSummary.session += 1;
375     }
376
377     dojo.byId('oils-selfck-circ-account-total').innerHTML = 
378         dojo.string.substitute(
379             localeStrings.TOTAL_ITEMS_ACCOUNT, 
380             [this.circSummary.total]
381         );
382
383     dojo.byId('oils-selfck-circ-session-total').innerHTML = 
384         dojo.string.substitute(
385             localeStrings.TOTAL_ITEMS_SESSION, 
386             [this.circSummary.session]
387         );
388 }
389
390
391 SelfCheckManager.prototype.drawHoldsPage = function() {
392
393     // TODO add option to hid scanBox
394     // this.updateScanBox(...)
395
396     openils.Util.hide('oils-selfck-circ-page');
397     openils.Util.hide('oils-selfck-payment-page');
398     openils.Util.show('oils-selfck-holds-page');
399
400     this.holdTbody = dojo.byId('oils-selfck-hold-tbody');
401     if(!this.holdTemplate)
402         this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));
403     while(this.holdTbody.childNodes[0])
404         this.holdTbody.removeChild(this.holdTbody.childNodes[0]);
405
406     progressDialog.show(true);
407
408     var self = this;
409     fieldmapper.standardRequest( // fetch the hold IDs
410
411         ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],
412         {   async : true,
413             params : [this.authtoken, this.patron.id()],
414
415             oncomplete : function(r) { 
416                 var ids = openils.Util.readResponse(r);
417                 if(!ids || ids.length == 0) {
418                     progressDialog.hide();
419                     return;
420                 }
421
422                 fieldmapper.standardRequest( // fetch the hold objects with fleshed details
423                     ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve.atomic'],
424                     {   async : true,
425                         params : [self.authtoken, ids],
426
427                         oncomplete : function(rr) {
428                             self.drawHolds(openils.Util.readResponse(rr));
429                         }
430                     }
431                 );
432             }
433         }
434     );
435 }
436
437 /**
438  * Fetch and add a single hold to the list of holds
439  */
440 SelfCheckManager.prototype.drawHolds = function(holds) {
441
442     holds = holds.sort(
443         // sort available holds to the top of the list
444         // followed by queue position order
445         function(a, b) {
446             if(a.status == 4) return -1;
447             if(a.queue_position < b.queue_position) return -1;
448             return 1;
449         }
450     );
451
452     progressDialog.hide();
453
454     for(var i in holds) {
455
456         var data = holds[i];
457         var row = this.holdTemplate.cloneNode(true);
458
459         if(data.mvr.isbn()) {
460             this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());
461         }
462
463         this.byName(row, 'title').innerHTML = data.mvr.title();
464         this.byName(row, 'author').innerHTML = data.mvr.author();
465
466         if(data.status == 4) {
467
468             // hold is ready for pickup
469             this.byName(row, 'status').innerHTML = localeStrings.HOLD_STATUS_READY;
470
471         } else {
472
473             // hold is still pending
474             this.byName(row, 'status').innerHTML = 
475                 dojo.string.substitute(
476                     localeStrings.HOLD_STATUS_WAITING,
477                     [data.queue_position, data.potential_copies]
478                 );
479         }
480
481         this.holdTbody.appendChild(row);
482     }
483 }
484
485
486
487 /**
488  * Check out a single item.  If the item is already checked 
489  * out to the patron, redirect to renew()
490  */
491 SelfCheckManager.prototype.checkout = function(barcode, override) {
492
493     if(!barcode) {
494         this.updateScanbox(null, true);
495         return;
496     }
497
498     if(this.mockCheckouts) {
499         // if we're in mock-checkout mode, just insert another
500         // fake circ into the table and get out of here.
501         this.displayCheckout(this.mockCheckout, 'checkout');
502         return;
503     }
504
505     // TODO see if it's a patron barcode
506     // TODO see if this item has already been checked out in this session
507
508     var method = 'open-ils.circ.checkout.full';
509     if(override) method += '.override';
510
511     console.log("Checkout out item " + barcode + " with method " + method);
512
513     var result = fieldmapper.standardRequest(
514         ['open-ils.circ', 'open-ils.circ.checkout.full'],
515         {params: [
516             this.authtoken, {
517                 patron_id : this.patron.id(),
518                 copy_barcode : barcode
519             }
520         ]}
521     );
522
523     console.log(js2JSON(result));
524
525     var stat = this.handleXactResult('checkout', barcode, result);
526
527     if(stat.override) {
528         this.checkout(barcode, true);
529     } else if(stat.renew) {
530         this.renew(barcode);
531     }
532 }
533
534
535 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
536
537     var displayText = '';
538
539     // If true, the display message is important enough to pop up.  Whether or not
540     // an alert() actually occurs, depends on org unit settings
541     var popup = false;  
542
543     var sound = '';
544
545     // TODO handle lost/missing/etc checkin+checkout override steps
546     
547     var payload = result.payload || {};
548         
549     if(result.textcode == 'NO_SESSION') {
550
551         return this.logoutStaff();
552
553     } else if(result.textcode == 'SUCCESS') {
554
555         if(action == 'checkout') {
556
557             displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);
558             this.displayCheckout(result, 'checkout');
559
560             if(payload.holds_fulfilled && payload.holds_fulfilled.length) {
561                 // A hold was fulfilled, update the hold numbers in the circ summary
562                 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");
563                 this.holdsSummary = null;
564                 this.updateHoldsSummary();
565             }
566
567             this.updateCircSummary(true);
568
569         } else if(action == 'renew') {
570
571             displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);
572             this.displayCheckout(result, 'renew');
573         }
574
575         sound = 'checkout-success';
576         this.updateScanBox();
577
578     } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {
579
580         // Server says the item is already checked out.  If it's checked out to the
581         // current user, we may need to renew it.  
582
583         if(payload.old_circ) { 
584
585             /*
586             old_circ refers to the previous checkout IFF it's for the same user. 
587             If no auto-renew interval is not defined, assume we should renew it
588             If an auto-renew interval is defined and the payload comes back with
589             auto_renew set to true, do the renewal.  Otherwise, let the patron know
590             the item is already checked out to them.  */
591
592             if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||
593                 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {
594                 return { renew : true };
595             }
596
597             popup = true;
598             sound = 'checkout-failure';
599             displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);
600
601         } else {
602             
603             // item is checked out to some other user
604             popup = true;
605             sound = 'checkout-failure';
606             displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
607         }
608
609         this.updateScanBox({select:true});
610
611     } else {
612
613         var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];
614     
615         if(overrideEvents && overrideEvents.length) {
616             
617             // see if the events we received are all in the list of
618             // events to override
619     
620             if(!result.length) result = [result];
621     
622             var override = true;
623             for(var i = 0; i < result.length; i++) {
624                 var match = overrideEvents.filter(
625                     function(e) { return (e == result[i].textcode); })[0];
626                 if(!match) {
627                     override = false;
628                     break;
629                 }
630             }
631
632             if(override) 
633                 return { override : true };
634         }
635     
636         this.updateScanBox({select : true});
637         popup = true;
638         sound = 'checkout-failure';
639
640         if(result.length) 
641             result = result[0];
642
643         switch(result.textcode) {
644
645             case 'MAX_RENEWALS_REACHED' :
646                 displayText = dojo.string.substitute(
647                     localeStrings.MAX_RENEWALS, [item]);
648                 break;
649
650             case 'ITEM_NOT_CATALOGED' :
651                 displayText = dojo.string.substitute(
652                     localeStrings.ITEM_NOT_CATALOGED, [item]);
653                 break;
654
655             case 'OPEN_CIRCULATION_EXISTS' :
656                 displayText = dojo.string.substitute(
657                     localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
658                 break;
659
660             default:
661                 console.error('Unhandled event ' + result.textcode);
662
663                 if(action == 'checkout' || action == 'renew') {
664                     displayText = dojo.string.substitute(
665                         localeStrings.GENERIC_CIRC_FAILURE, [item]);
666                 } else {
667                     displayText = dojo.string.substitute(
668                         localeStrings.UNKNOWN_ERROR, [result.textcode]);
669                 }
670         }
671     }
672
673     this.handleAlert(displayText, popup, sound);
674     return {};
675 }
676
677
678 /**
679  * Renew an item
680  */
681 SelfCheckManager.prototype.renew = function(barcode, override) {
682
683     var method = 'open-ils.circ.renew';
684     if(override) method += '.override';
685
686     console.log("Renewing item " + barcode + " with method " + method);
687
688     var result = fieldmapper.standardRequest(
689         ['open-ils.circ', method],
690         {params: [
691             this.authtoken, {
692                 patron_id : this.patron.id(),
693                 copy_barcode : barcode
694             }
695         ]}
696     );
697
698     console.log(js2JSON(result));
699
700     var stat = this.handleXactResult('renew', barcode, result);
701
702     if(stat.override)
703         this.renew(barcode, true);
704 }
705
706 /**
707  * Display the result of a checkout or renewal in the items out table
708  */
709 SelfCheckManager.prototype.displayCheckout = function(evt, type) {
710
711     var copy = evt.payload.copy;
712     var record = evt.payload.record;
713     var circ = evt.payload.circ;
714     var row = this.circTemplate.cloneNode(true);
715
716     if(record.isbn()) {
717         this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());
718     }
719
720     this.byName(row, 'barcode').innerHTML = copy.barcode();
721     this.byName(row, 'title').innerHTML = record.title();
722     this.byName(row, 'author').innerHTML = record.author();
723     this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();
724     openils.Util.show(this.byName(row, type));
725
726     var date = dojo.date.stamp.fromISOString(circ.due_date());
727     this.byName(row, 'due_date').innerHTML = 
728         dojo.date.locale.format(date, {selector : 'date'});
729
730     // put new circs at the top of the list
731     this.circTbody.insertBefore(row, this.circTbody.getElementsByTagName('tr')[0]);
732 }
733
734
735 SelfCheckManager.prototype.byName = function(node, name) {
736     return dojo.query('[name=' + name+']', node)[0];
737 }
738
739
740 SelfCheckManager.prototype.drawFinesPage = function() {
741
742     openils.Util.hide('oils-selfck-circ-page');
743     openils.Util.hide('oils-selfck-holds-page');
744     openils.Util.show('oils-selfck-payment-page');
745
746 }
747
748 /**
749  * Print a receipt
750  */
751 SelfCheckManager.prototype.printReceipt = function() {
752 }
753
754
755 /**
756  * Logout the patron and return to the login page
757  */
758 SelfCheckManager.prototype.logoutPatron = function() {
759
760     this.patron = null;
761     this.holdsSummary = null;
762     this.circSummary = null;
763
764     this.drawLoginPage();
765 }
766
767
768 /**
769  * Fire up the manager on page load
770  */
771 openils.Util.addOnLoad(
772     function() {
773         new SelfCheckManager().init();
774     }
775 );