1 /* -----------------------------------------------------------------
2 * Copyright (C) 2008 Equinox Software, Inc.
3 * Bill Erickson <erickson@esilibrary.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 ----------------------------------------------------------------- */
21 var STAFF_SES_PARAM = 'ses';
22 var PATRON_BARCODE_COOKIE = 'pbcc';
24 var itemBarcode = null;
25 var itemsOutTemplate = null;
26 var isRenewal = false;
27 var pendingXact = false;
28 var patronTimeout = 600000; /* 10 minutes */
32 var successfulItems = {};
33 var scanTimeout = 800;
35 var patronBarcodeRegex;
39 var alertOnCheckoutEvent = false;
40 var overrideCircEvents = [];
41 var SET_PATRON_TIMEOUT = 'circ.selfcheck.patron_login_timeout';
42 var SET_BARCODE_REGEX = 'opac.barcode_regex';
43 var SET_ALERT_ON_CHECKOUT_EVENT = 'circ.selfcheck.alert_on_checkout_event';
44 var SET_AUTO_OVERRIDE_EVENTS = 'circ.selfcheck.auto_override_checkout_events';
47 function selfckInit() {
48 dojo.require('dojo.cookie');
50 var staff = grabUser(dojo.cookie(STAFF_SES_PARAM) || cgi.param(STAFF_SES_PARAM));
54 orgUnit = findOrgUnitSN(cgi.param('l')) || globalOrgTree;
55 selfckFetchOrgDetails();
57 // fetch the relevent org-unit setting
58 var settings = fetchBatchOrgSetting(orgUnit.id(),
59 [SET_PATRON_TIMEOUT, SET_BARCODE_REGEX, SET_ALERT_ON_CHECKOUT_EVENT, SET_AUTO_OVERRIDE_EVENTS]);
60 if(settings[SET_PATRON_TIMEOUT])
61 patronTimeout = parseInt(settings[SET_PATRON_TIMEOUT].value) * 1000;
62 if(settings[SET_BARCODE_REGEX])
63 patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);
64 if(settings[SET_ALERT_ON_CHECKOUT_EVENT])
65 alertOnCheckoutEvent = (settings[SET_ALERT_ON_CHECKOUT_EVENT].value) ? true : false;
66 if(settings[SET_AUTO_OVERRIDE_EVENTS])
67 overrideCircEvents = settings[SET_AUTO_OVERRIDE_EVENTS].value;
70 // should not happen when behind the proxy
71 return alert('Staff must login');
74 unHideMe($('selfck-patron-login-container'));
75 $('selfck-patron-login-input').focus();
77 $('selfck-patron-login-input').onkeypress = function(evt) {
78 if(userPressedEnter(evt))
82 $('selfck-item-barcode-input').onkeypress = selfckItemBarcodeKeypress;
84 // for debugging, allow passing the user barcode via param
85 var urlbc = new CGI().param('patron');
87 selfckPatronLogin(urlbc);
91 printWrapper = $('selfck-print-items-list');
92 printTemplate = printWrapper.removeChild($n(printWrapper, 'selfck-print-items-template'));
93 itemsOutTemplate = $('selfck-items-out-tbody').removeChild($('selfck-items-out-row'));
95 selfckTryPatronCookie();
97 // selfckMkDummyCirc(); // testing only
101 function selfckFetchOrgDetails() {
102 var hreq = new Request('open-ils.actor:open-ils.actor.org_unit.hours_of_operation.retrieve', G.user.session, orgUnit.id());
103 hreq.callback(function(r) { orgUnitHours = r.getResultObject(); });
106 var areq = new Request('open-ils.actor:open-ils.actor.org_unit.address.retrieve', orgUnit.mailing_address());
107 areq.callback(function(r) { orgUnitAddress = r.getResultObject(); });
111 function selfckSetupPrinter() {
112 try { // Mozilla only
113 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
114 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
115 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
116 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
117 var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
119 pref.setBoolPref('print.always_print_silent', true);
125 function selfckTryPatronCookie() {
126 var pb = dojo.cookie(PATRON_BARCODE_COOKIE);
128 dojo.cookie(PATRON_BARCODE_COOKIE, null, {'expires':-1});
129 $('selfck-patron-login-input').value = pb;
135 function selfckItemBarcodeKeypress(evt) {
136 if(userPressedEnter(evt)) {
137 clearTimeout(scanTimeoutId);
140 /* If scanTimeout milliseconds have passed and there is
141 still data in the input box, it's a likely indication
142 of a partial scan. Select the text so the next scan
143 will replace the value */
144 clearTimeout(scanTimeoutId);
145 scanTimeoutId = setTimeout(
147 if($('selfck-item-barcode-input').value) {
148 $('selfck-item-barcode-input').select();
157 * Start the logout timer
159 function selfckStartTimer() {
160 timerId = setTimeout(
162 selfckLogoutPatron();
170 * Reset the logout timer
172 function selfckResetTimer() {
173 clearTimeout(timerId);
178 * Clears fields and "logs out" the patron by reloading the page
180 function selfckLogoutPatron() {
181 $('selfck-item-barcode-input').value = ''; // prevent browser caching
182 $('selfck-patron-login-input').value = '';
184 var numItems = selfckPrint();
185 var sleepTime = 1000;
186 if(numItems > 0) sleepTime += (numItems / 2) * 1000;
188 function() { location.href = location.href; },
189 sleepTime // give the browser time to send the page to the printer
195 * Fetches the user by barcode and displays the item barcode input
198 function selfckPatronLogin(barcode) {
199 barcode = barcode || $('selfck-patron-login-input').value;
202 var bcReq = new Request(
203 'open-ils.actor:open-ils.actor.user.fleshed.retrieve_by_barcode',
204 G.user.session, barcode);
206 bcReq.request.alertEvent = false;
208 bcReq.callback(function(r) {
209 patron = r.getResultObject();
210 if(checkILSEvent(patron)) {
211 if(patron.textcode == 'ACTOR_USER_NOT_FOUND') {
212 unHideMe($('selfck-patron-not-found'));
213 $('selfck-patron-login-input').select();
217 if(patron.textcode == 'NO_SESSION')
218 return selfckLogoutStaff();
220 return alert(patron.textcode);
223 if(!isTrue(patron.active())) {
224 unHideMe($('selfck-patron-not-found'));
225 $('selfck-patron-login-input').select();
228 for (var i =0; i < patron.cards().length; i++) {
229 if (patron.cards()[i].barcode() == barcode) {
230 if (!isTrue(patron.cards()[i].active())) {
231 unHideMe($('selfck-patron-not-found'));
232 $('selfck-patron-login-input').select();
238 $('selfck-patron-login-input').value = ''; // reset the input
239 hideMe($('selfck-patron-login-container'));
240 unHideMe($('selfck-patron-checkout-container'));
241 $('selfck-patron-name-span').appendChild(text(patron.usrname()));
242 $('selfck-item-barcode-input').focus();
248 function selfckLogoutStaff() {
249 dojo.cookie(STAFF_SES_PARAM,null,{'expires':-1});
250 location.reload(true);
254 * If a user barcode was scanned into the item barcode
255 * input, log out the current user and log in the new user
257 function selfckCheckPatronBarcode(itemBc) {
258 if(patronBarcodeRegex) {
259 if(itemBc.match(patronBarcodeRegex)) {
260 dojo.cookie(PATRON_BARCODE_COOKIE, itemBc);
261 selfckLogoutPatron();
269 * Sends the checkout request
271 function selfckCheckout(override) {
272 if(pendingXact) return;
277 removeChildren($('selfck-event-time'));
278 removeChildren($('selfck-event-span'));
280 itemBarcode = $('selfck-item-barcode-input').value;
281 if(!itemBarcode) return;
283 if(selfckCheckPatronBarcode(itemBarcode))
286 if (itemBarcode in successfulItems) {
287 selfckShowMsgNode({textcode:'dupe-barcode'});
288 $('selfck-item-barcode-input').select();
293 var coReq = new Request(
294 'open-ils.circ:open-ils.circ.checkout.full' + ((override) ? '.override' : ''),
295 G.user.session, {patron_id:patron.id(), copy_barcode:itemBarcode});
297 coReq.request.alertEvent = false;
298 coReq.callback(selfckHandleCoResult);
303 * Handles the resulting event. If the item is already checked out,
304 * attempts renewal. Any other events will display a message
306 function selfckHandleCoResult(r) {
310 evt = r.getResultObject();
313 selfckShowMsgNode({textcode:'UNKNOWN'});
314 appendClear($('selfck-errors'), text(E.toString()));
318 if(evt.textcode == 'SUCCESS') {
319 selfckDislplayCheckout(evt);
320 selfckShowMsgNode(evt);
321 successfulItems[itemBarcode] = 1;
323 } else if(evt.textcode == 'OPEN_CIRCULATION_EXISTS') {
326 } else if(evt.textcode == 'NO_SESSION') {
328 return selfckLogoutStaff();
333 if(!evt.length) evt = [evt];
334 if(overrideCircEvents.length) {
336 // see if the events we received are all in the list of
337 // events to override
339 for(var i = 0; i < evt.length; i++) {
340 var match = overrideCircEvents.filter(
341 function(e) { return (e == evt[i].textcode); })[0];
349 return selfckCheckout(true);
352 selfckShowMsgNode(evt);
353 $('selfck-item-barcode-input').select();
358 * Display event text in the messages block
360 function selfckShowMsgNode(evt) {
361 var code = evt.textcode;
362 var msgspan = $('selfck-event-span');
364 // if we're not explicitly handling the event, just say "copy cannot circ"
365 if(!$('selfck-event-' + code))
366 code = 'COPY_CIRC_NOT_ALLOWED';
368 appendClear($('selfck-event-time'), text(new Date().toLocaleString()));
369 appendClear($('selfck-event-span'), text($('selfck-event-' + code).innerHTML));
371 if(code != 'SUCCESS' && alertOnCheckoutEvent)
372 alert($('selfck-event-' + code).innerHTML);
376 * Renders a row in the checkouts table for the current circulation
378 function selfckDislplayCheckout(evt) {
379 unHideMe($('selfck-items-out-table-wrapper'));
381 var template = itemsOutTemplate.cloneNode(true);
382 var copy = evt.payload.copy;
383 var record = evt.payload.record;
384 var circ = evt.payload.circ;
387 var pic = $n(template, 'selfck.jacket');
388 pic.setAttribute('src', '../ac/jacket/small/'+cleanISBN(record.isbn()));
390 $n(template, 'selfck.barcode').appendChild(text(copy.barcode()));
391 $n(template, 'selfck.title').appendChild(text(record.title()));
392 $n(template, 'selfck.author').appendChild(text(record.author()));
393 $n(template, 'selfck.due_date').appendChild(text(circ.due_date().replace(/T.*/,'')));
394 $n(template, 'selfck.remaining').appendChild(text(circ.renewal_remaining()));
396 hideMe($n(template, 'selfck.cotype_co'));
397 unHideMe($n(template, 'selfck.cotype_rn'));
400 var tbody = $('selfck-items-out-tbody');
401 tbody.insertBefore(template, tbody.getElementsByTagName('tr')[0]);
402 $('selfck-item-barcode-input').value = '';
405 // flesh out the printable version of the page as well
406 var ptemplate = printTemplate.cloneNode(true);
407 $n(ptemplate, 'title').appendChild(text(record.title()));
408 $n(ptemplate, 'barcode').appendChild(text(copy.barcode()));
409 $n(ptemplate, 'due_date').appendChild(text(circ.due_date().replace(/T.*/,'')));
410 printWrapper.insertBefore(ptemplate, printWrapper.getElementsByTagName('li')[0]);
416 * Checks to see if this item is checked out to the current patron. If so,
417 * this sends the renewal request.
419 function selfckRenew() {
421 // first, make sure the item is checked out to this patron
422 var detailReq = new Request(
423 'open-ils.circ:open-ils.circ.copy_details.retrieve.barcode',
424 G.user.session, itemBarcode);
428 var details = r.getResultObject();
429 if(details.circ.usr() == patron.id()) {
430 // OK, this is our item, renew it
432 var rnReq = new Request(
433 'open-ils.circ:open-ils.circ.renew',
434 G.user.session, {copy_barcode:itemBarcode});
435 rnReq.request.alertEvent = false;
436 rnReq.callback(selfckHandleCoResult);
440 selfckShowMsgNode({textcode:'already-out'});
449 * Sets the print date and prints the page
451 function selfckPrint() {
454 for(var x in successfulItems)
458 hideMe($('selfck-patron-checkout-container'));
459 unHideMe($('selfck-print-queuing'));
460 appendClear($('selfck-print-date'), text(new Date().toLocaleString()));
461 appendClear($('selfck-print-lib-name'), text(orgUnit.name()));
463 appendClear($('selfck-print-lib-addr-street'), text(orgUnitAddress.street1()+' '+orgUnitAddress.street2()));
464 appendClear($('selfck-print-lib-addr-city'), text(orgUnitAddress.city()));
465 appendClear($('selfck-print-lib-addr-state'), text(orgUnitAddress.state()));
466 appendClear($('selfck-print-lib-addr-post-code'), text(orgUnitAddress.post_code()));
468 appendClear($('selfck-print-lname'), text(patron.family_name()));
469 appendClear($('selfck-print-fname'), text(patron.first_given_name()));
470 appendClear($('selfck-print-lib-phone'), text(orgUnit.phone()));
472 for(var i in [0, 1, 2, 3, 4, 5, 6]) {
473 appendClear($('selfck-print-dow_'+i+'_open'), text(orgUnitHours['dow_'+i+'_open']()));
474 appendClear($('selfck-print-dow_'+i+'_close'), text(orgUnitHours['dow_'+i+'_close']()));
485 * Test method for generating dummy data in the checkout tables
487 function selfckMkDummyCirc() {
488 unHideMe($('selfck-items-out-table'));
490 var template = itemsOutTemplate.cloneNode(true);
491 $n(template, 'selfck.barcode').appendChild(text('123456789'));
492 $n(template, 'selfck.title').appendChild(text('Test Title'));
493 $n(template, 'selfck.author').appendChild(text('Test Author'));
494 $n(template, 'selfck.due_date').appendChild(text('2008-08-01'));
495 $n(template, 'selfck.remaining').appendChild(text('1'));
496 $('selfck-items-out-tbody').appendChild(template);
498 // flesh out the printable version of the page as well
499 var ptemplate = printTemplate.cloneNode(true);
500 $n(ptemplate, 'title').appendChild(text('Test Title'));
501 $n(ptemplate, 'barcode').appendChild(text('123456789'));
502 $n(ptemplate, 'due_date').appendChild(text('2008-08-01'));
503 printWrapper.appendChild(ptemplate);