testing commit, please ignore
[kcls-web.git] / opac / extras / selfcheck / selfcheck111010.js
1 /* -----------------------------------------------------------------
2 * Copyright (C) 2008  Equinox Software, Inc.
3 * Bill Erickson <erickson@esilibrary.com>
4
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.
9
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.
14
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  
18 * 02110-1301, USA
19 ----------------------------------------------------------------- */
20
21 var STAFF_SES_PARAM = 'ses';
22 var PATRON_BARCODE_COOKIE = 'pbcc';
23 var patron = null
24 var itemBarcode = null;
25 var itemsOutTemplate = null;
26 var isRenewal = false;
27 var pendingXact = false;
28 var patronTimeout = 600000; /* 10 minutes */
29 var timerId = null;
30 var printWrapper;
31 var printTemplate;
32 var successfulItems = {};
33 var scanTimeout = 800;
34 var scanTimeoutId;
35 var patronBarcodeRegex;
36 var orgUnit;
37 var orgUnitAddress;
38 var orgUnitHours;
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';
45
46
47 function selfckInit() {
48     var cgi = new CGI();
49     var staff = grabUser(cookieManager.read(STAFF_SES_PARAM) || cgi.param(STAFF_SES_PARAM));
50
51     selfckSetupPrinter();
52
53     orgUnit = findOrgUnitSN(cgi.param('l')) || globalOrgTree;
54     selfckFetchOrgDetails();
55
56     // fetch the relevent org-unit setting
57     var settings = fetchBatchOrgSetting(orgUnit.id(), 
58         [SET_PATRON_TIMEOUT, SET_BARCODE_REGEX, SET_ALERT_ON_CHECKOUT_EVENT, SET_AUTO_OVERRIDE_EVENTS]);
59     if(settings[SET_PATRON_TIMEOUT])
60         patronTimeout = parseInt(settings[SET_PATRON_TIMEOUT].value) * 1000;
61     if(settings[SET_BARCODE_REGEX])
62         patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);
63     if(settings[SET_ALERT_ON_CHECKOUT_EVENT])
64         alertOnCheckoutEvent = (settings[SET_ALERT_ON_CHECKOUT_EVENT].value) ? true : false;
65     if(settings[SET_AUTO_OVERRIDE_EVENTS])
66         overrideCircEvents = settings[SET_AUTO_OVERRIDE_EVENTS].value;
67
68     if(!staff) {
69         // should not happen when behind the proxy
70         return alert('Staff must login');
71     }
72
73     unHideMe($('selfck-patron-login-container'));
74     $('selfck-patron-login-input').focus();
75
76     $('selfck-patron-login-input').onkeypress = function(evt) {
77         if(userPressedEnter(evt)) 
78             selfckPatronLogin();
79     };
80
81     $('selfck-item-barcode-input').onkeypress = selfckItemBarcodeKeypress;
82
83     // for debugging, allow passing the user barcode via param
84     var urlbc = new CGI().param('patron');
85     if(urlbc)
86         selfckPatronLogin(urlbc);
87
88     selfckStartTimer();
89
90     printWrapper = $('selfck-print-items-list');
91     printTemplate = printWrapper.removeChild($n(printWrapper, 'selfck-print-items-template'));
92     itemsOutTemplate = $('selfck-items-out-tbody').removeChild($('selfck-items-out-row'));
93
94     selfckTryPatronCookie();
95
96 //    selfckMkDummyCirc(); // testing only
97     
98 }
99
100 function selfckFetchOrgDetails() {
101     var hreq = new Request('open-ils.actor:open-ils.actor.org_unit.hours_of_operation.retrieve', G.user.session, orgUnit.id());
102     hreq.callback(function(r) { orgUnitHours = r.getResultObject(); });
103     hreq.send();
104
105     var areq = new Request('open-ils.actor:open-ils.actor.org_unit.address.retrieve', orgUnit.mailing_address());
106     areq.callback(function(r) { orgUnitAddress = r.getResultObject(); });
107     areq.send();
108 }
109
110 function selfckSetupPrinter() {
111     try { // Mozilla only
112                 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
113         netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
114         netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
115         netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
116         var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
117         if (pref)
118             pref.setBoolPref('print.always_print_silent', true);
119     } catch(E) {
120         
121     }
122 }
123
124 function selfckTryPatronCookie() {
125     var pb = cookieManager.read(PATRON_BARCODE_COOKIE);
126     if(pb) {
127         cookieManager.write(PATRON_BARCODE_COOKIE, '');
128         $('selfck-patron-login-input').value = pb;
129         selfckPatronLogin();
130     }
131 }
132
133
134 function selfckItemBarcodeKeypress(evt) {
135     if(userPressedEnter(evt)) {
136         clearTimeout(scanTimeoutId);
137         selfckCheckout();
138     } else {
139         /*  If scanTimeout milliseconds have passed and there is
140             still data in the input box, it's a likely indication
141             of a partial scan. Select the text so the next scan
142             will replace the value */
143         clearTimeout(scanTimeoutId);
144         scanTimeoutId = setTimeout(
145             function() {
146                 if($('selfck-item-barcode-input').value) {
147                     $('selfck-item-barcode-input').select();
148                 }
149             },
150             scanTimeout
151         );
152     }
153 }
154
155 /*
156  * Start the logout timer
157  */
158 function selfckStartTimer() {
159     timerId = setTimeout(
160         function() {
161             selfckLogoutPatron();
162         },
163         patronTimeout
164     );
165
166 }
167
168 /*
169  * Reset the logout timer
170  */
171 function selfckResetTimer() {
172     clearTimeout(timerId);
173     selfckStartTimer();
174 }
175
176 /*
177  * Clears fields and "logs out" the patron by reloading the page
178  */
179 function selfckLogoutPatron() {
180     $('selfck-item-barcode-input').value = ''; // prevent browser caching
181     $('selfck-patron-login-input').value = '';
182     if(patron) {
183         var numItems = selfckPrint();
184         var sleepTime = 1000;
185         if(numItems > 0) sleepTime += (numItems / 2) * 1000;
186         setTimeout(
187             function() { location.href = location.href; },
188             sleepTime // give the browser time to send the page to the printer
189         );
190     }
191 }
192
193 /*
194  * Fetches the user by barcode and displays the item barcode input
195  */
196
197 function selfckPatronLogin(barcode) {
198     barcode = barcode || $('selfck-patron-login-input').value;
199     if(!barcode) return;
200
201     var bcReq = new Request(
202         'open-ils.actor:open-ils.actor.user.fleshed.retrieve_by_barcode',
203         G.user.session, barcode);
204
205         bcReq.request.alertEvent = false;
206
207     bcReq.callback(function(r) {
208         patron = r.getResultObject();
209         if(checkILSEvent(patron)) {
210             if(patron.textcode == 'ACTOR_USER_NOT_FOUND') {
211                 unHideMe($('selfck-patron-not-found'));
212                 $('selfck-patron-login-input').select();
213                 return;
214             }
215
216             if(patron.textcode == 'NO_SESSION') 
217                 return selfckLogoutStaff();
218
219             return alert(patron.textcode);
220         }
221
222         if(!isTrue(patron.active())) {
223             unHideMe($('selfck-patron-not-found'));
224             $('selfck-patron-login-input').select();
225             return;
226         }
227         for (var i =0; i < patron.cards().length; i++) {
228             if (patron.cards()[i].barcode() == barcode) {
229                 if (!isTrue(patron.cards()[i].active())) {
230                     unHideMe($('selfck-patron-not-found'));
231                     $('selfck-patron-login-input').select();
232                     return;
233                 }
234             }
235         }
236
237         $('selfck-patron-login-input').value = ''; // reset the input
238         hideMe($('selfck-patron-login-container'));
239         unHideMe($('selfck-patron-checkout-container'));
240         $('selfck-patron-name-span').appendChild(text(patron.usrname()));
241         $('selfck-item-barcode-input').focus();
242     });
243
244     bcReq.send();
245 }
246
247 function selfckLogoutStaff() {
248     cookieManager.remove(STAFF_SES_PARAM);
249     location.reload(true);
250 }
251
252 /**
253   * If a user barcode was scanned into the item barcode
254   * input, log out the current user and log in the new user
255   */
256 function selfckCheckPatronBarcode(itemBc) {
257     if(patronBarcodeRegex) {
258         if(itemBc.match(patronBarcodeRegex)) {
259             cookieManager.write(PATRON_BARCODE_COOKIE, itemBc, -1);
260             selfckLogoutPatron();
261             return true;
262         }
263     }
264     return false;
265 }
266
267 /**
268   * Sends the checkout request
269   */
270 function selfckCheckout(override) {
271     if(pendingXact) return;
272     selfckResetTimer();
273     pendingXact = true;
274     isRenewal = false;
275
276     removeChildren($('selfck-event-time'));
277     removeChildren($('selfck-event-span'));
278
279     itemBarcode = $('selfck-item-barcode-input').value;
280     if(!itemBarcode) return;
281
282     if(selfckCheckPatronBarcode(itemBarcode))
283         return;
284
285     if (itemBarcode in successfulItems) {
286         selfckShowMsgNode({textcode:'dupe-barcode'});
287         $('selfck-item-barcode-input').select();
288         pendingXact = false;
289         return;
290     }
291
292     var coReq = new Request(
293         'open-ils.circ:open-ils.circ.checkout.full' + ((override) ? '.override' : ''),
294         G.user.session, {patron_id:patron.id(), copy_barcode:itemBarcode});
295
296         coReq.request.alertEvent = false;
297     coReq.callback(selfckHandleCoResult);
298     coReq.send();
299 }
300
301 /**
302   * Handles the resulting event.  If the item is already checked out,
303   * attempts renewal.  Any other events will display a message
304   */
305 function selfckHandleCoResult(r) {
306     var evt;
307
308     try {
309         evt = r.getResultObject();
310     } catch(E) {
311         pendingXact = false;
312         selfckShowMsgNode({textcode:'UNKNOWN'});
313         appendClear($('selfck-errors'), text(E.toString()));
314         return;
315     }
316
317     if(evt.textcode == 'SUCCESS') {
318         selfckDislplayCheckout(evt);
319         selfckShowMsgNode(evt);
320         successfulItems[itemBarcode] = 1;
321
322     } else if(evt.textcode == 'OPEN_CIRCULATION_EXISTS') {
323         selfckRenew();
324
325     } else if(evt.textcode == 'NO_SESSION') {
326         
327         return selfckLogoutStaff();
328
329     } else {
330         pendingXact = false;
331
332         if(!evt.length) evt = [evt];
333         if(overrideCircEvents.length) {
334
335             // see if the events we received are all in the list of 
336             // events to override
337             var override = true;
338             for(var i = 0; i < evt.length; i++) {
339                 var match = overrideCircEvents.filter(
340                     function(e) { return (e == evt[i].textcode); })[0];
341                 if(!match) {
342                     override = false;
343                     break;
344                 }
345             }
346
347             if(override)
348                 return selfckCheckout(true);
349         }
350
351         selfckShowMsgNode(evt);
352         $('selfck-item-barcode-input').select();
353     }
354 }
355
356 /**
357   * Display event text in the messages block
358   */
359 function selfckShowMsgNode(evt) {
360     var code = evt.textcode;
361     var msgspan = $('selfck-event-span');
362
363     // if we're not explicitly handling the event, just say "copy cannot circ"
364     if(!$('selfck-event-' + code)) 
365         code = 'COPY_CIRC_NOT_ALLOWED';
366
367     appendClear($('selfck-event-time'), text(new Date().toLocaleString()));
368     appendClear($('selfck-event-span'), text($('selfck-event-' + code).innerHTML));
369
370     if(code != 'SUCCESS' && alertOnCheckoutEvent)
371         alert($('selfck-event-' + code).innerHTML);
372 }
373
374 /**
375   * Renders a row in the checkouts table for the current circulation
376   */
377 function selfckDislplayCheckout(evt) {
378     unHideMe($('selfck-items-out-table-wrapper'));
379
380     var template = itemsOutTemplate.cloneNode(true);
381     var copy = evt.payload.copy;
382     var record = evt.payload.record;
383     var circ = evt.payload.circ;
384
385     if(record.isbn()) {
386             var pic = $n(template, 'selfck.jacket');
387             pic.setAttribute('src', '../ac/jacket/small/'+cleanISBN(record.isbn()));
388     }
389     $n(template, 'selfck.barcode').appendChild(text(copy.barcode()));
390     $n(template, 'selfck.title').appendChild(text(record.title()));
391     $n(template, 'selfck.author').appendChild(text(record.author()));
392     $n(template, 'selfck.due_date').appendChild(text(circ.due_date().replace(/T.*/,'')));
393     $n(template, 'selfck.remaining').appendChild(text(circ.renewal_remaining()));
394     if(isRenewal) {
395         hideMe($n(template, 'selfck.cotype_co'));
396         unHideMe($n(template, 'selfck.cotype_rn'));
397     }
398
399     var tbody = $('selfck-items-out-tbody');
400     tbody.insertBefore(template, tbody.getElementsByTagName('tr')[0]);
401     $('selfck-item-barcode-input').value = '';
402
403
404     // flesh out the printable version of the page as well
405     var ptemplate = printTemplate.cloneNode(true);
406     $n(ptemplate, 'title').appendChild(text(record.title()));
407     $n(ptemplate, 'barcode').appendChild(text(copy.barcode()));
408     $n(ptemplate, 'due_date').appendChild(text(circ.due_date().replace(/T.*/,'')));
409     printWrapper.insertBefore(ptemplate, printWrapper.getElementsByTagName('li')[0]);
410
411     pendingXact = false;
412 }
413
414 /**
415   * Checks to see if this item is checked out to the current patron.  If so, 
416   * this sends the renewal request.
417   */
418 function selfckRenew() {
419
420     // first, make sure the item is checked out to this patron
421     var detailReq = new Request(
422         'open-ils.circ:open-ils.circ.copy_details.retrieve.barcode',
423         G.user.session, itemBarcode);
424
425     detailReq.callback(
426         function(r) {
427             var details = r.getResultObject();
428             if(details.circ.usr() == patron.id()) {
429                 // OK, this is our item, renew it
430                 isRenewal = true;
431                 var rnReq = new Request(
432                     'open-ils.circ:open-ils.circ.renew',
433                     G.user.session, {copy_barcode:itemBarcode});
434                 rnReq.request.alertEvent = false;
435                 rnReq.callback(selfckHandleCoResult);
436                 rnReq.send();
437             } else {
438                 pendingXact = false;
439                 selfckShowMsgNode({textcode:'already-out'});
440             }
441         }
442     );
443
444     detailReq.send();
445 }
446
447 /**
448   * Sets the print date and prints the page
449   */
450 function selfckPrint() {
451
452     var numItems = 0;
453     for(var x in successfulItems)  
454         numItems++;
455
456     if(numItems > 0) {
457         hideMe($('selfck-patron-checkout-container'));
458         unHideMe($('selfck-print-queuing'));
459         appendClear($('selfck-print-date'), text(new Date().toLocaleString()));
460         appendClear($('selfck-print-lib-name'), text(orgUnit.name()));
461         if(orgUnitAddress) {
462             appendClear($('selfck-print-lib-addr-street'), text(orgUnitAddress.street1()+' '+orgUnitAddress.street2()));
463             appendClear($('selfck-print-lib-addr-city'), text(orgUnitAddress.city()));
464             appendClear($('selfck-print-lib-addr-state'), text(orgUnitAddress.state()));
465             appendClear($('selfck-print-lib-addr-post-code'), text(orgUnitAddress.post_code()));
466         }
467         appendClear($('selfck-print-lname'), text(patron.family_name()));
468         appendClear($('selfck-print-fname'), text(patron.first_given_name()));
469         appendClear($('selfck-print-lib-phone'), text(orgUnit.phone()));
470         if(orgUnitHours) {
471             for(var i in [0, 1, 2, 3, 4, 5, 6]) {
472                 appendClear($('selfck-print-dow_'+i+'_open'), text(orgUnitHours['dow_'+i+'_open']()));
473                 appendClear($('selfck-print-dow_'+i+'_close'), text(orgUnitHours['dow_'+i+'_close']()));
474             }
475         }
476         window.print();
477     }
478
479     return numItems;
480 }
481
482
483 /**
484   * Test method for generating dummy data in the checkout tables
485   */
486 function selfckMkDummyCirc() {
487     unHideMe($('selfck-items-out-table'));
488
489     var template = itemsOutTemplate.cloneNode(true);
490     $n(template, 'selfck.barcode').appendChild(text('123456789'));
491     $n(template, 'selfck.title').appendChild(text('Test Title'));
492     $n(template, 'selfck.author').appendChild(text('Test Author'));
493     $n(template, 'selfck.due_date').appendChild(text('2008-08-01'));
494     $n(template, 'selfck.remaining').appendChild(text('1'));
495     $('selfck-items-out-tbody').appendChild(template);
496
497     // flesh out the printable version of the page as well
498     var ptemplate = printTemplate.cloneNode(true);
499     $n(ptemplate, 'title').appendChild(text('Test Title'));
500     $n(ptemplate, 'barcode').appendChild(text('123456789'));
501     $n(ptemplate, 'due_date').appendChild(text('2008-08-01'));
502     printWrapper.appendChild(ptemplate);
503 }