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