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