1 dojo.require('dojo.date.locale');
\r
2 dojo.require('dojo.date.stamp');
\r
3 dojo.require('dijit.form.CheckBox');
\r
4 dojo.require('dijit.form.NumberSpinner');
\r
5 dojo.require('openils.CGI');
\r
6 dojo.require('openils.Util');
\r
7 dojo.require('openils.User');
\r
8 dojo.require('openils.Event');
\r
9 dojo.require('openils.widget.ProgressDialog');
\r
10 dojo.require('openils.widget.OrgUnitFilteringSelect');
\r
12 dojo.requireLocalization('openils.circ', 'selfcheck');
\r
13 var localeStrings = dojo.i18n.getLocalization('openils.circ', 'selfcheck');
\r
15 var itemsOutCirc = [];
\r
16 var itemsOutMod = [];
\r
17 var itemsOutCopy = [];
\r
18 var TIMEOUT = 45; // logout timer
\r
21 const SET_BARCODE_REGEX = 'opac.barcode_regex';
\r
22 const SET_PATRON_TIMEOUT = 'circ.selfcheck.patron_login_timeout';
\r
23 const SET_AUTO_OVERRIDE_EVENTS = 'circ.selfcheck.auto_override_checkout_events';
\r
24 const SET_PATRON_PASSWORD_REQUIRED = 'circ.selfcheck.patron_password_required';
\r
25 const SET_AUTO_RENEW_INTERVAL = 'circ.checkout_auto_renew_age';
\r
26 const SET_WORKSTATION_REQUIRED = 'circ.selfcheck.workstation_required';
\r
27 const SET_ALERT_POPUP = 'circ.selfcheck.alert.popup';
\r
28 const SET_ALERT_SOUND = 'circ.selfcheck.alert.sound';
\r
29 const SET_CC_PAYMENT_ALLOWED = 'credit.payments.allow';
\r
30 // This setting only comes into play if COPY_NOT_AVAILABLE is in the SET_AUTO_OVERRIDE_EVENTS list
\r
31 const SET_BLOCK_CHECKOUT_ON_COPY_STATUS = 'circ.selfcheck.block_checkout_on_copy_status';
\r
33 function SelfCheckManager() {
\r
34 selfCheckMgr = this;
\r
38 this.cgi = new openils.CGI();
\r
40 this.workstation = null;
\r
41 this.authtoken = null;
\r
43 this.patron = null;
\r
44 this.patronBarcodeRegex = null;
\r
46 this.checkouts = [];
\r
49 // During renewals, keep track of the ID of the previous circulation.
\r
50 // Previous circ is used for tracking failed renewals (for receipts).
\r
51 this.prevCirc = null;
\r
53 // current item barcode
\r
54 this.itemBarcode = null;
\r
56 // are we currently performing a renewal?
\r
57 this.isRenewal = false;
\r
59 // dict of org unit settings for "here"
\r
60 this.orgSettings = {};
\r
62 // Construct a mock checkout for debugging purposes
\r
63 if(this.mockCheckouts = this.cgi.param('mock-circ')) {
\r
65 this.mockCheckout = {
\r
67 record : new fieldmapper.mvr(),
\r
68 copy : new fieldmapper.acp(),
\r
69 circ : new fieldmapper.circ()
\r
73 this.mockCheckout.payload.record.title('Jazz improvisation for guitar');
\r
74 this.mockCheckout.payload.record.author('Wise, Les');
\r
75 this.mockCheckout.payload.record.isbn('0634033565');
\r
76 this.mockCheckout.payload.copy.barcode('123456789');
\r
77 this.mockCheckout.payload.circ.renewal_remaining(1);
\r
78 this.mockCheckout.payload.circ.parent_circ(1);
\r
79 this.mockCheckout.payload.circ.due_date('2012-12-21');
\r
85 SelfCheckManager.prototype.keepMeLoggedIn = function() {
\r
86 //alert(this.timer);
\r
87 if(this.timer) try {clearTimeout(this.timer)} catch(e){}
\r
88 this.timer = setTimeout('selfCheckMgr.logoutPatron();', TIMEOUT*1000);
\r
92 * Fetch the org-unit settings, initialize the display, etc.
\r
94 SelfCheckManager.prototype.init = function() {
\r
95 this.staff = openils.User.user;
\r
96 this.workstation = openils.User.workstation;
\r
97 this.authtoken = openils.User.authtoken;
\r
98 this.loadOrgSettings();
\r
100 this.circTbody = dojo.byId('oils-selfck-circ-tbody');
\r
101 this.itemsOutTbody = dojo.byId('oils-selfck-circ-out-tbody');
\r
103 // workstation is required but none provided
\r
104 if(this.orgSettings[SET_WORKSTATION_REQUIRED] && !this.workstation) {
\r
105 if(confirm(dojo.string.substitute(localeStrings.WORKSTATION_REQUIRED))) {
\r
106 this.registerWorkstation();
\r
112 // connect onclick handlers to the various navigation links
\r
113 var linkHandlers = {
\r
114 'oils-selfck-hold-details-link' : function() { self.drawHoldsPage(true); },
\r
115 'oils-selfck-view-fines-link' : function() { self.drawFinesPage(); openils.Util.show('oils-selfck-fines-tbody'); openils.Util.hide('pay_fines'); },
\r
116 'oils-selfck-pay-fines-link' : function() {
\r
117 switchTo('step3','step3c');
\r
118 openils.Util.hide('oils-selfck-fines-tbody');
\r
119 openils.Util.show('pay_fines');
\r
120 self.keepMeLoggedIn();
\r
121 self.drawPayFinesPage(
\r
123 self.getSelectedFinesTotal(),
\r
124 self.getSelectedFineTransactions(),
\r
126 var evt = openils.Event.parse(resp);
\r
128 var message = evt + '';
\r
129 if(evt.textcode == 'CREDIT_PROCESSOR_DECLINED_TRANSACTION' && evt.payload)
\r
130 message += '\n' + evt.payload.error_message;
\r
131 self.handleAlert(message, true, 'payment-failure');
\r
134 self.patron.last_xact_id(resp.last_xact_id);
\r
135 self.printPaymentReceipt(
\r
138 self.updateFinesSummary();
\r
139 self.drawFinesPage();
\r
145 //'oils-selfck-nav-home' : function() { self.drawCircPage(); },
\r
146 'oils-selfck-nav-logout' : function() { self.logoutPatron(); },
\r
147 'oils-selfck-nav-logout-print' : function() { self.logoutPatron(true); },
\r
148 'oils-selfck-items-out-details-link' : function() { self.drawItemsOutPage(); },
\r
149 //'oils-selfck-print-list-link' : function() { self.printList(); }
\r
152 for(var id in linkHandlers) {
\r
153 //var obj1 = dojo.byId(id);
\r
154 //obj1.onclick = linkHandlers[id];
\r
155 dojo.connect(dojo.byId(id), 'onclick', linkHandlers[id]);
\r
159 if(this.cgi.param('patron')) {
\r
161 // Patron barcode via cgi param. Mainly used for debugging and
\r
162 // only works if password is not required by policy
\r
163 this.loginPatron(this.cgi.param('patron'));
\r
166 this.drawLoginPage();
\r
170 * To test printing, pass a URL param of 'testprint'. The value for the param
\r
171 * should be a JSON string like so: [{circ:<circ_id>}, ...]
\r
173 var testPrint = this.cgi.param('testprint');
\r
175 this.checkouts = JSON2js(testPrint);
\r
176 this.printSessionReceipt();
\r
177 this.checkouts = [];
\r
182 SelfCheckManager.prototype.getSelectedFinesTotal = function() {
\r
185 dojo.query("[name=selector]", this.finesTbody),
\r
188 total += Number(input.balance_owed);
\r
191 return total.toFixed(2);
\r
194 SelfCheckManager.prototype.getSelectedFineTransactions = function() {
\r
195 return dojo.query("[name=selector]", this.finesTbody).
\r
196 filter(function (o) { return o.checked }).
\r
200 o.getAttribute("xact"),
\r
201 Number(o.balance_owed).toFixed(2)
\r
208 * Registers a new workstion
\r
210 SelfCheckManager.prototype.registerWorkstation = function() {
\r
212 oilsSelfckWsDialog.show();
\r
214 new openils.User().buildPermOrgSelector(
\r
215 'REGISTER_WORKSTATION',
\r
216 oilsSelfckWsLocSelector,
\r
217 this.staff.home_ou()
\r
222 dojo.connect(oilsSelfckWsSubmit, 'onClick',
\r
225 oilsSelfckWsDialog.hide();
\r
226 var name = oilsSelfckWsLocSelector.attr('displayedValue') + '-' + oilsSelfckWsName.attr('value');
\r
228 var res = fieldmapper.standardRequest(
\r
229 ['open-ils.actor', 'open-ils.actor.workstation.register'],
\r
231 self.authtoken, name, oilsSelfckWsLocSelector.attr('value')
\r
236 if(evt = openils.Event.parse(res)) {
\r
237 if(evt.textcode == 'WORKSTATION_NAME_EXISTS') {
\r
238 if(confirm(localeStrings.WORKSTATION_EXISTS)) {
\r
239 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
\r
241 self.registerWorkstation();
\r
248 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
\r
255 * Loads the org unit settings
\r
257 SelfCheckManager.prototype.loadOrgSettings = function() {
\r
259 var settings = fieldmapper.aou.fetchOrgSettingBatch(
\r
260 this.staff.ws_ou(), [
\r
262 SET_PATRON_TIMEOUT,
\r
265 SET_AUTO_OVERRIDE_EVENTS,
\r
266 SET_BLOCK_CHECKOUT_ON_COPY_STATUS,
\r
267 SET_PATRON_PASSWORD_REQUIRED,
\r
268 SET_AUTO_RENEW_INTERVAL,
\r
269 SET_WORKSTATION_REQUIRED,
\r
270 SET_CC_PAYMENT_ALLOWED
\r
274 for(k in settings) {
\r
276 this.orgSettings[k] = settings[k].value;
\r
279 if(settings[SET_BARCODE_REGEX])
\r
280 this.patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);
\r
283 SelfCheckManager.prototype.drawLoginPage = function() {
\r
285 var bcHandler = function(barcode) {
\r
286 // handle patron barcode entry
\r
288 if(self.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
\r
290 // password is required. wire up the scan box to read it
\r
291 self.updateScanBox({
\r
292 msg : 'Please enter your password', // TODO i18n
\r
293 handler : function(pw) { self.loginPatron(barcode, pw); },
\r
298 // password is not required, go ahead and login
\r
299 self.loginPatron(barcode);
\r
303 this.updateScanBox({
\r
304 msg : 'Please log in with your library barcode.', // TODO
\r
305 handler : bcHandler
\r
308 var txtBox = (dojo.byId('step2').style.display=='none') ? 'patron-login-username' : 'patron-login-password';
\r
309 try{var a=dojo.byId(txtBox);a.focus();a.select();}catch(e){}
\r
313 * Login the patron.
\r
315 SelfCheckManager.prototype.loginPatron = function(barcode, passwd) {
\r
317 //if(this.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) { // password always reqired, per KCLS - fail safe
\r
319 // would only happen in dev/debug mode when using the patron= param
\r
320 alert('password required by org setting. remove patron= from URL');
\r
324 // patron password is required. Verify it.
\r
326 var res = fieldmapper.standardRequest(
\r
327 ['open-ils.actor', 'open-ils.actor.verify_user_password'],
\r
328 {params : [this.authtoken, barcode, null, hex_md5(passwd)]}
\r
332 // user-not-found results in login failure
\r
334 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
\r
335 false, 'login-failure'
\r
337 this.drawLoginPage();
\r
338 openils.Util.show('back_to_login');
\r
343 // retrieve the fleshed user by barcode
\r
344 this.patron = fieldmapper.standardRequest(
\r
345 ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve_by_barcode'],
\r
346 {params : [this.authtoken, barcode]}
\r
349 var evt = openils.Event.parse(this.patron);
\r
352 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
\r
353 false, 'login-failure'
\r
355 this.drawLoginPage();
\r
356 openils.Util.show('back_to_login');
\r
360 //this.handleAlert('', true, 'login-success');
\r
361 dojo.byId('user_name').innerHTML =
\r
362 dojo.string.substitute(localeStrings.WELCOME_BANNER, [this.patron.first_given_name()]);
\r
363 dojo.byId('oils-selfck-status-div').innerHTML = '';
\r
364 dojo.byId('oils-selfck-status-div2').innerHTML = '';
\r
365 dojo.byId('oils-selfck-status-div3').innerHTML = '';
\r
366 openils.Util.hide('back_to_login');
\r
367 this.drawCircPage();
\r
372 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
\r
373 console.log("Handling alert " + message);
\r
375 dojo.byId('oils-selfck-status-div').innerHTML = message;
\r
377 dojo.byId('oils-selfck-status-div2').innerHTML = message;
\r
378 dojo.byId('oils-selfck-status-div3').innerHTML = message;
\r
381 if(shouldPopup && this.orgSettings[SET_ALERT_POPUP])
\r
384 if(this.orgSettings[SET_ALERT_SOUND])
\r
385 openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);
\r
390 * Manages the main input box
\r
391 * @param msg The context message to display with the box
\r
392 * @param clearOnly Don't update the context message, just clear the value and re-focus
\r
393 * @param handler Optional "on-enter" handler.
\r
395 SelfCheckManager.prototype.updateScanBox = function(args) {
\r
399 selfckScanBox.domNode.select();
\r
401 selfckScanBox.attr('value', '');
\r
404 if(args.password) {
\r
405 selfckScanBox.domNode.setAttribute('type', 'password');
\r
407 selfckScanBox.domNode.setAttribute('type', '');
\r
411 selfckScanBox.attr('value', args.value);
\r
414 dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;
\r
416 if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {
\r
417 dojo.disconnect(selfckScanBox._lastHandler);
\r
422 selfckScanBox._lastHandler = dojo.connect(
\r
426 if(e.keyCode != dojo.keys.ENTER)
\r
428 args.handler(selfckScanBox.attr('value'));
\r
433 selfckScanBox.focus();
\r
437 * Sets up the checkout/renewal interface
\r
439 SelfCheckManager.prototype.drawCircPage = function() {
\r
440 this.keepMeLoggedIn();
\r
441 openils.Util.show('oils-selfck-circ-tbody', 'table-row-group');
\r
445 this.updateScanBox({
\r
446 msg : 'Please enter an item barcode', // TODO i18n
\r
447 handler : function(barcode) {
\r
448 openils.Util.show('oils-selfck-fines-tbody');
\r
449 openils.Util.hide('pay_fines'); switchTo('step3');
\r
450 self.checkout(barcode); }
\r
453 if(!this.circTemplate)
\r
454 this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));
\r
457 this.updateFinesSummary();
\r
460 this.updateHoldsSummary();
\r
462 // items out summary
\r
463 this.updateCircSummary();
\r
465 // render mock checkouts for debugging?
\r
466 if(this.mockCheckouts) {
\r
467 for(var i in [1,2,3])
\r
468 this.displayCheckout(this.mockCheckout, 'checkout');
\r
473 SelfCheckManager.prototype.updateFinesSummary = function() {
\r
477 fieldmapper.standardRequest(
\r
478 ['open-ils.actor', 'open-ils.actor.user.fines.summary'],
\r
480 params : [this.authtoken, this.patron.id()],
\r
481 oncomplete : function(r) {
\r
482 var summary = openils.Util.readResponse(r);
\r
483 var finesSum = dojo.byId('acct_fines');
\r
484 var bal = summary.balance_owed();
\r
485 var bal2 = parseFloat(bal);
\r
487 if(bal2>0) {finesSum.style.color="red"; openils.Util.show('oils-selfck-pay-fines-link');}
\r
488 finesSum.innerHTML = dojo.string.substitute(localeStrings.TOTAL_FINES_ACCOUNT, [bal2.toFixed(2)]);
\r
489 self.creditPayableBalance = bal2+'';
\r
496 SelfCheckManager.prototype.drawItemsOutPage = function() {
\r
497 this.keepMeLoggedIn();
\r
498 switchTo('step3','step3d');
\r
500 if(!this.outTemplate)
\r
501 this.outTemplate = this.itemsOutTbody.removeChild(dojo.byId('oils-selfck-circ-out-row'));
\r
502 while(this.itemsOutTbody.childNodes[0])
\r
503 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
\r
505 progressDialog.show(true);
\r
508 fieldmapper.standardRequest(
\r
509 ['open-ils.circ', 'open-ils.circ.actor.user.checked_out.atomic'],
\r
512 params : [this.authtoken, this.patron.id()],
\r
513 oncomplete : function(r) {
\r
514 var resp = openils.Util.readResponse(r);
\r
516 var circs = resp.sort(
\r
518 if(a.circ.due_date() > b.circ.due_date())
\r
524 self.itemsOut = [];
\r
525 dojo.forEach(circs,
\r
527 self.itemsOut.push(circ.circ.id());
\r
528 handleCheckedItems(circ);
\r
531 progressDialog.hide();
\r
537 function handleCheckedItems(circ) {
\r
538 var self = selfCheckMgr;
\r
539 var row = self.outTemplate.cloneNode(true);
\r
541 self.byName(row,'barcode').innerHTML = circ.copy.barcode();
\r
542 self.byName(row,'title').innerHTML = circ.record.title();
\r
543 self.byName(row,'author').innerHTML = circ.record.author();
\r
544 if(dojo.date.stamp.fromISOString(circ.circ.due_date())<(new Date())) self.byName(row,'due_date').style.color="red";
\r
545 self.byName(row,'due_date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(circ.circ.due_date()), {selector: 'date', fullYear: true});
\r
546 self.byName(row,'format').innerHTML = circ.record.types_of_resource()[0];
\r
548 self.itemsOutTbody.appendChild(row);
\r
551 SelfCheckManager.prototype.goToTab = function(name) {
\r
552 this.tabName = name;
\r
554 openils.Util.hide('oils-selfck-fines-page');
\r
555 openils.Util.hide('oils-selfck-payment-page');
\r
556 openils.Util.hide('oils-selfck-holds-page');
\r
557 openils.Util.hide('oils-selfck-circ-page');
\r
558 openils.Util.hide('oils-selfck-pay-fines-link');
\r
562 openils.Util.show('oils-selfck-circ-page');
\r
565 openils.Util.show('oils-selfck-circ-page');
\r
568 openils.Util.show('oils-selfck-holds-page');
\r
571 openils.Util.show('oils-selfck-fines-page');
\r
574 openils.Util.show('oils-selfck-payment-page');
\r
580 SelfCheckManager.prototype.printList = function(which) {
\r
581 this.keepMeLoggedIn();
\r
584 this.printSessionReceipt();
\r
587 this.printItemsOutReceipt();
\r
590 this.printHoldsReceipt();
\r
593 this.printFinesReceipt();
\r
598 SelfCheckManager.prototype.updateHoldsSummary = function() {
\r
599 if(!this.holdsSummary) {
\r
600 var summary = fieldmapper.standardRequest(
\r
601 ['open-ils.circ', 'open-ils.circ.holds.user_summary'],
\r
602 {params : [this.authtoken, this.patron.id()]}
\r
605 this.holdsSummary = {};
\r
606 this.holdsSummary.ready = Number(summary['4']);
\r
607 this.holdsSummary.total = 0;
\r
609 for(var i in summary)
\r
610 this.holdsSummary.total += Number(summary[i]);
\r
613 dojo.byId('oils-selfck-holds-total').innerHTML =dojo.string.substitute("${0}) Item"+(this.holdsSummary.total==1?"":"s"),[this.holdsSummary.total]);
\r
614 dojo.byId('oils-selfck-holds-ready').innerHTML =dojo.string.substitute("${0}) Item"+(this.holdsSummary.ready==1?"":"s"),[this.holdsSummary.ready]);
\r
618 SelfCheckManager.prototype.updateCircSummary = function(increment) {
\r
619 if(!this.circSummary) {
\r
621 var summary = fieldmapper.standardRequest(
\r
622 ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],
\r
623 {params : [this.authtoken, this.patron.id()]}
\r
626 this.circSummary = {
\r
627 total : Number(summary.out) + Number(summary.overdue),
\r
628 overdue : Number(summary.overdue),
\r
634 // local checkout occurred. Add to the total and the session.
\r
635 this.circSummary.total += 1;
\r
636 this.circSummary.session += 1;
\r
639 dojo.byId('oils-selfck-circ-account-total').innerHTML = dojo.string.substitute("${0}) Item"+(this.circSummary.total==1?"":"s"), [this.circSummary.total]);
\r
642 dojo.byId('oils-selfck-circ-session-total').innerHTML =
\r
643 dojo.string.substitute(
\r
644 localeStrings.TOTAL_ITEMS_SESSION,
\r
645 [this.circSummary.session]
\r
651 SelfCheckManager.prototype.drawHoldsPage = function(bool) {
\r
652 this.keepMeLoggedIn();
\r
653 if(bool) switchTo('step3','step3f'); else switchTo('step3','step3e');
\r
655 this.holdTbody = dojo.byId('oils-selfck-hold-tbody');
\r
656 this.readyTbody = dojo.byId('oils-selfck-rdy-tbody');
\r
657 if(!this.readyTemplate)
\r
658 this.readyTemplate = this.readyTbody.removeChild(dojo.byId('oils-selfck-rdy-row'));
\r
659 if(!this.holdTemplate)
\r
660 this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));
\r
661 while(this.holdTbody.childNodes[0])
\r
662 this.holdTbody.removeChild(this.holdTbody.childNodes[0]);
\r
663 while(this.readyTbody.childNodes[0])
\r
664 this.readyTbody.removeChild(this.readyTbody.childNodes[0]);
\r
666 progressDialog.show(true);
\r
669 fieldmapper.standardRequest( // fetch the hold IDs
\r
671 ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],
\r
673 params : [this.authtoken, this.patron.id()],
\r
675 oncomplete : function(r) {
\r
676 var ids = openils.Util.readResponse(r);
\r
677 if(!ids || ids.length == 0) {
\r
678 progressDialog.hide();
\r
682 fieldmapper.standardRequest( // fetch the hold objects with fleshed details
\r
683 ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve'],
\r
685 params : [self.authtoken, ids],
\r
687 onresponse : function(rr) {
\r
688 progressDialog.hide();
\r
689 self.drawHolds(openils.Util.readResponse(rr));
\r
699 * Fetch and add a single hold to the list of holds
\r
701 SelfCheckManager.prototype.drawHolds = function(holds) {
\r
702 //this.keepMeLoggedIn();
\r
703 this.holds = holds;
\r
704 progressDialog.hide();
\r
708 var row = this.holdTemplate.cloneNode(true);
\r
709 var row2 = this.readyTemplate.cloneNode(true);
\r
711 //if(data.mvr.isbn()) {
\r
712 // this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());
\r
715 if(data.status == 4) {
\r
716 this.byName(row2, 'title').innerHTML = data.mvr.title();
\r
717 this.byName(row2, 'format').innerHTML = data.mvr.types_of_resource()[0];
\r
718 this.byName(row2, 'lib').innerHTML = fieldmapper.aou.findOrgUnit(data.hold.pickup_lib()).name();
\r
719 if(dojo.date.stamp.fromISOString(data.hold.capture_time())<(new Date())) this.byName(row2, 'date').style.color="red";
\r
720 this.byName(row2, 'date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(data.hold.capture_time()), {selector: 'date', fullYear: true});
\r
721 this.readyTbody.appendChild(row2);
\r
724 this.byName(row, 'title').innerHTML = data.mvr.title();
\r
725 this.byName(row, 'author').innerHTML = data.mvr.author();
\r
726 this.byName(row, 'format').innerHTML = data.mvr.types_of_resource()[0];
\r
728 // hold is still pending
\r
729 this.byName(row, 'status').innerHTML = dojo.string.substitute(localeStrings.HOLD_STATUS_WAITING,[data.queue_position, data.potential_copies]);
\r
730 this.holdTbody.appendChild(row);
\r
735 SelfCheckManager.prototype.drawFinesPage = function() {
\r
736 this.keepMeLoggedIn();
\r
737 // TODO add option to hid scanBox
\r
738 // this.updateScanBox(...)
\r
740 //this.goToTab('fines');
\r
741 switchTo('step3','step3c');
\r
742 progressDialog.show(true);
\r
744 //if(this.creditPayableBalance > 0 && this.orgSettings[SET_CC_PAYMENT_ALLOWED])
\r
745 // openils.Util.show('oils-selfck-pay-fines-link', 'inline');
\r
748 this.finesTbody = dojo.byId('oils-selfck-fines-tbody');
\r
749 if(!this.finesTemplate)
\r
750 this.finesTemplate = this.finesTbody.removeChild(dojo.byId('oils-selfck-fines-row'));
\r
751 while(this.finesTbody.childNodes[0])
\r
752 this.finesTbody.removeChild(this.finesTbody.childNodes[0]);
\r
755 // when user clicks on a selector checkbox, update the total owed
\r
756 var updateSelected = function() {
\r
759 dojo.query('[name=selector]', this.finesTbody),
\r
762 total += Number(input.getAttribute('balance_owed'));
\r
766 total = total.toFixed(2);
\r
767 dojo.byId('oils-selfck-selected-total').innerHTML =
\r
768 dojo.string.substitute(localeStrings.TOTAL_FINES_SELECTED, [total]);
\r
771 // wire up the batch on/off selector
\r
772 var sel = dojo.byId('oils-selfck-fines-selector');
\r
773 sel.onchange = function() {
\r
775 dojo.query('[name=selector]', this.finesTbody),
\r
777 input.checked = sel.checked;
\r
783 var handler = function(dataList) {
\r
785 self.finesCount = dataList.length;
\r
786 self.finesData = dataList;
\r
788 for(var i in dataList) {
\r
790 var data = dataList[i];
\r
791 var row = self.finesTemplate.cloneNode(true);
\r
792 var type = data.transaction.xact_type();
\r
794 if(type == 'circulation') {
\r
795 self.byName(row, 'title').innerHTML = data.record.title();
\r
796 if(dojo.date.stamp.fromISOString(data.circ.due_date())<(new Date())) self.byName(row, 'due_date').style.color="red";
\r
797 self.byName(row, 'due_date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(data.circ.due_date()), {selector: 'date', fullYear: true});
\r
798 self.byName(row, 'date_return').innerHTML = (data.circ.checkin_time())?dojo.date.locale.format(dojo.date.stamp.fromISOString(data.circ.checkin_time()), {selector: 'date', fullYear: true}):"";
\r
800 } else if(type == 'grocery') {
\r
801 self.byName(row, 'title').innerHTML = (data.transaction.last_billing_type())?("Miscellaneous - "+data.transaction.last_billing_type()):"Miscellaneous"; // Go ahead and head off any confusion around "grocery". TODO i18n
\r
804 //self.byName(row, 'total_owed').innerHTML = data.transaction.total_owed();
\r
805 //self.byName(row, 'total_paid').innerHTML = data.transaction.total_paid();
\r
806 self.byName(row, 'balance').innerHTML = data.transaction.balance_owed();
\r
807 self.byName(row, 'selector').balance_owed = data.transaction.balance_owed();
\r
808 self.byName(row, 'selector').setAttribute('xact', data.transaction.id());
\r
811 var selector = self.byName(row, 'selector')
\r
812 selector.onchange = updateSelected;
\r
813 selector.setAttribute('xact', data.transaction.id());
\r
814 selector.setAttribute('balance_owed', data.transaction.balance_owed());
\r
815 selector.checked = true;
\r
817 self.finesTbody.appendChild(row);
\r
820 //updateSelected();
\r
824 fieldmapper.standardRequest(
\r
825 ['open-ils.actor', 'open-ils.actor.user.transactions.have_balance.fleshed'],
\r
827 params : [this.authtoken, this.patron.id()],
\r
828 oncomplete : function(r) {
\r
829 progressDialog.hide();
\r
830 handler(openils.Util.readResponse(r));
\r
836 SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
\r
837 var resp = fieldmapper.standardRequest(
\r
838 ['open-ils.circ', 'open-ils.circ.transit.abort'],
\r
839 {params : [this.authtoken, {barcode : barcode}]}
\r
842 // resp == 1 on success
\r
843 if(openils.Event.parse(resp))
\r
846 var resp = fieldmapper.standardRequest(
\r
847 ['open-ils.circ', 'open-ils.circ.checkin.override'],
\r
850 patron_id : this.patron.id(),
\r
851 copy_barcode : barcode,
\r
857 if(!resp.length) resp = [resp];
\r
858 for(var i = 0; i < resp.length; i++) {
\r
859 var tc = openils.Event.parse(resp[i]).textcode;
\r
860 if(tc == 'SUCCESS' || tc == 'NO_CHANGE') {
\r
871 * Check out a single item. If the item is already checked
\r
872 * out to the patron, redirect to renew()
\r
874 SelfCheckManager.prototype.checkout = function(barcode, override) {
\r
875 this.keepMeLoggedIn();
\r
876 this.prevCirc = null;
\r
879 this.updateScanbox(null, true);
\r
883 if(this.mockCheckouts) {
\r
884 // if we're in mock-checkout mode, just insert another
\r
885 // fake circ into the table and get out of here.
\r
886 this.displayCheckout(this.mockCheckout, 'checkout');
\r
890 // TODO see if it's a patron barcode
\r
891 // TODO see if this item has already been checked out in this session
\r
893 var method = 'open-ils.circ.checkout.full';
\r
894 if(override) method += '.override';
\r
896 console.log("Checkout out item " + barcode + " with method " + method);
\r
898 var result = fieldmapper.standardRequest(
\r
899 ['open-ils.circ', method],
\r
902 patron_id : this.patron.id(),
\r
903 copy_barcode : barcode
\r
908 var stat = this.handleXactResult('checkout', barcode, result);
\r
910 if(stat.override) {
\r
911 this.checkout(barcode, true);
\r
912 } else if(stat.doOver) {
\r
913 this.checkout(barcode);
\r
914 } else if(stat.renew) {
\r
915 this.renew(barcode);
\r
919 SelfCheckManager.prototype.failPartMessage = function(result) {
\r
920 if (result.payload && result.payload.fail_part) {
\r
921 var stringKey = "FAIL_PART_" +
\r
922 result.payload.fail_part.replace(/\./g, "_");
\r
923 return localeStrings[stringKey];
\r
929 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
\r
930 var displayText = '';
\r
932 // If true, the display message is important enough to pop up. Whether or not
\r
933 // an alert() actually occurs, depends on org unit settings
\r
934 var popup = false;
\r
935 var sound = ''; // sound file reference
\r
936 var payload = result.payload || {};
\r
937 var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];
\r
938 var blockStatuses = this.orgSettings[SET_BLOCK_CHECKOUT_ON_COPY_STATUS];
\r
939 result.payload = payload;
\r
941 if(result.textcode == 'NO_SESSION') {
\r
943 return this.logoutStaff();
\r
945 } else if(result.textcode == 'SUCCESS') {
\r
947 if(action == 'checkout') {
\r
949 displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);
\r
950 this.displayCheckout(result, 'checkout');
\r
952 if(payload.holds_fulfilled && payload.holds_fulfilled.length) {
\r
953 // A hold was fulfilled, update the hold numbers in the circ summary
\r
954 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");
\r
955 this.holdsSummary = null;
\r
956 this.updateHoldsSummary();
\r
959 this.updateCircSummary(true);
\r
961 } else if(action == 'renew') {
\r
963 displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);
\r
964 this.displayCheckout(result, 'renew');
\r
967 this.checkouts.push({circ : result.payload.circ.id()});
\r
968 sound = 'checkout-success';
\r
969 this.updateScanBox();
\r
971 } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {
\r
973 // Server says the item is already checked out. If it's checked out to the
\r
974 // current user, we may need to renew it.
\r
976 if(payload.old_circ) {
\r
979 old_circ refers to the previous checkout IFF it's for the same user.
\r
980 If no auto-renew interval is not defined, assume we should renew it
\r
981 If an auto-renew interval is defined and the payload comes back with
\r
982 auto_renew set to true, do the renewal. Otherwise, let the patron know
\r
983 the item is already checked out to them. */
\r
985 if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||
\r
986 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {
\r
987 this.prevCirc = payload.old_circ.id();
\r
988 return { renew : true };
\r
992 sound = 'checkout-failure';
\r
993 displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);
\r
997 if( // copy is marked lost. if configured to do so, check it in and try again.
\r
998 result.payload.copy &&
\r
999 result.payload.copy.status() == /* LOST */ 3 &&
\r
1000 overrideEvents && overrideEvents.length &&
\r
1001 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
\r
1003 if(this.checkin(item)) {
\r
1004 return { doOver : true };
\r
1009 // item is checked out to some other user
\r
1011 sound = 'checkout-failure';
\r
1012 displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
\r
1015 this.updateScanBox();
\r
1020 if(overrideEvents && overrideEvents.length) {
\r
1022 // see if the events we received are all in the list of
\r
1023 // events to override
\r
1025 if(!result.length) result = [result];
\r
1027 var override = true;
\r
1028 for(var i = 0; i < result.length; i++) {
\r
1030 var match = overrideEvents.filter(function(e) { return (e == result[i].textcode); })[0];
\r
1037 if(result[i].textcode == 'COPY_NOT_AVAILABLE' && blockStatuses && blockStatuses.length) {
\r
1039 var stat = result[i].payload.status(); // copy status
\r
1040 if(typeof stat == 'object') stat = stat.id();
\r
1042 var match2 = blockStatuses.filter(function(e) { return (e == stat); })[0];
\r
1044 if(match2) { // copy is in a blocked status
\r
1050 if(result[i].textcode == 'COPY_IN_TRANSIT') {
\r
1051 // to override a transit, we have to abort the transit and check it in first
\r
1052 if(this.checkin(item, true)) {
\r
1053 return { doOver : true };
\r
1061 return { override : true };
\r
1064 this.updateScanBox();
\r
1066 sound = 'checkout-failure';
\r
1068 if(action == 'renew')
\r
1069 this.checkouts.push({circ : this.prevCirc, renewal_failure : true});
\r
1071 if(result.length)
\r
1072 result = result[0];
\r
1074 switch(result.textcode) {
\r
1076 // TODO custom handler for blocking penalties
\r
1078 case 'MAX_RENEWALS_REACHED' :
\r
1079 displayText = dojo.string.substitute(
\r
1080 localeStrings.MAX_RENEWALS, [item]);
\r
1083 case 'ITEM_NOT_CATALOGED' :
\r
1084 displayText = dojo.string.substitute(
\r
1085 localeStrings.ITEM_NOT_CATALOGED, [item]);
\r
1088 case 'OPEN_CIRCULATION_EXISTS' :
\r
1089 displayText = dojo.string.substitute(
\r
1090 localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
\r
1095 console.error('Unhandled event ' + result.textcode);
\r
1097 if (!(displayText = this.failPartMessage(result))) {
\r
1098 if (action == 'checkout' || action == 'renew') {
\r
1099 displayText = dojo.string.substitute(
\r
1100 localeStrings.GENERIC_CIRC_FAILURE, [item]);
\r
1102 displayText = dojo.string.substitute(
\r
1103 localeStrings.UNKNOWN_ERROR, [result.textcode]);
\r
1109 this.handleAlert(displayText, popup, sound);
\r
1117 SelfCheckManager.prototype.renew = function(barcode, override) {
\r
1119 var method = 'open-ils.circ.renew';
\r
1120 if(override) method += '.override';
\r
1122 console.log("Renewing item " + barcode + " with method " + method);
\r
1124 var result = fieldmapper.standardRequest(
\r
1125 ['open-ils.circ', method],
\r
1128 patron_id : this.patron.id(),
\r
1129 copy_barcode : barcode
\r
1134 console.log(js2JSON(result));
\r
1136 var stat = this.handleXactResult('renew', barcode, result);
\r
1139 this.renew(barcode, true);
\r
1143 * Display the result of a checkout or renewal in the items out table
\r
1145 SelfCheckManager.prototype.displayCheckout = function(evt, type, itemsOut) {
\r
1146 var copy = evt.payload.copy;
\r
1147 var record = evt.payload.record;
\r
1148 var circ = evt.payload.circ;
\r
1149 var row = this.circTemplate.cloneNode(true);
\r
1151 //if(record.isbn()) {
\r
1152 // this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());
\r
1155 this.byName(row, 'barcode').innerHTML = copy.barcode();
\r
1156 this.byName(row, 'title').innerHTML = record.title();
\r
1157 //this.byName(row, 'author').innerHTML = record.author();
\r
1158 //this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();
\r
1159 openils.Util.show(this.byName(row, type));
\r
1161 var date = dojo.date.stamp.fromISOString(circ.due_date());
\r
1162 this.byName(row, 'due_date').innerHTML =
\r
1163 dojo.date.locale.format(date, {selector : 'date'});
\r
1165 // put new circs at the top of the list
\r
1166 var tbody = this.circTbody;
\r
1167 if(itemsOut) tbody = this.itemsOutTbody;
\r
1168 tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
\r
1172 SelfCheckManager.prototype.byName = function(node, name) {
\r
1173 return dojo.query('[name=' + name+']', node)[0];
\r
1177 SelfCheckManager.prototype.initPrinter = function() {
\r
1178 try { // Mozilla only
\r
1179 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
\r
1180 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
\r
1181 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
\r
1182 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
\r
1183 var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
\r
1185 pref.setBoolPref('print.always_print_silent', true);
\r
1187 console.log("Unable to initialize auto-printing");
\r
1192 * Print a receipt for this session's checkouts
\r
1194 SelfCheckManager.prototype.printSessionReceipt = function(callback) {
\r
1196 var circCtx = []; // circ context data. in this case, renewal_failure info
\r
1198 // collect the circs and failure info
\r
1202 circIds.push(blob.circ);
\r
1203 circCtx.push({renewal_failure:blob.renewal_failure});
\r
1209 this.staff.ws_ou(),
\r
1211 'format.selfcheck.checkout',
\r
1212 'print-on-demand',
\r
1218 fieldmapper.standardRequest(
\r
1219 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
\r
1223 oncomplete : function(r) {
\r
1224 var resp = openils.Util.readResponse(r);
\r
1225 var output = resp.template_output();
\r
1227 self.printData(output.data(), self.checkouts.length, callback);
\r
1229 var error = resp.error_output();
\r
1231 throw new Error("Error creating receipt: " + error.data());
\r
1233 throw new Error("No receipt data returned from server");
\r
1241 SelfCheckManager.prototype.printData = function(data, numItems, callback) {
\r
1242 var win = window.open('', '', 'resizable,width=350,height=250,scrollbars=1');
\r
1243 win.document.body.innerHTML = data;
\r
1247 * There is no way to know when the browser is done printing.
\r
1248 * Make a best guess at when to close the print window by basing
\r
1249 * the setTimeout wait on the number of items to be printed plus
\r
1252 var sleepTime = 1000;
\r
1254 sleepTime += (numItems / 2) * 1000;
\r
1258 win.close(); // close the print window
\r
1259 if(callback) callback(); // fire optional post-print callback
\r
1267 * Print a receipt for this user's items out
\r
1269 SelfCheckManager.prototype.printItemsOutReceipt = function(callback) {
\r
1270 if(!this.itemsOut.length) return;
\r
1272 progressDialog.show(true);
\r
1276 this.staff.ws_ou(),
\r
1278 'format.selfcheck.items_out',
\r
1279 'print-on-demand',
\r
1284 fieldmapper.standardRequest(
\r
1285 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
\r
1289 oncomplete : function(r) {
\r
1290 progressDialog.hide();
\r
1291 var resp = openils.Util.readResponse(r);
\r
1292 var output = resp.template_output();
\r
1294 self.printData(output.data(), self.itemsOut.length, callback);
\r
1296 var error = resp.error_output();
\r
1298 throw new Error("Error creating receipt: " + error.data());
\r
1300 throw new Error("No receipt data returned from server");
\r
1309 * Print a receipt for this user's items out
\r
1311 SelfCheckManager.prototype.printHoldsReceipt = function(callback) {
\r
1312 if(!this.holds.length) return;
\r
1314 progressDialog.show(true);
\r
1317 var holdData = [];
\r
1319 dojo.forEach(this.holds,
\r
1321 holdIds.push(data.hold.id());
\r
1322 if(data.status == 4) {
\r
1323 holdData.push({ready : true});
\r
1326 queue_position : data.queue_position,
\r
1327 potential_copies : data.potential_copies
\r
1335 this.staff.ws_ou(),
\r
1337 'format.selfcheck.holds',
\r
1338 'print-on-demand',
\r
1344 fieldmapper.standardRequest(
\r
1345 ['open-ils.circ', 'open-ils.circ.fire_hold_trigger_events'],
\r
1349 oncomplete : function(r) {
\r
1350 progressDialog.hide();
\r
1351 var resp = openils.Util.readResponse(r);
\r
1352 var output = resp.template_output();
\r
1354 self.printData(output.data(), self.holds.length, callback);
\r
1356 var error = resp.error_output();
\r
1358 throw new Error("Error creating receipt: " + error.data());
\r
1360 throw new Error("No receipt data returned from server");
\r
1369 SelfCheckManager.prototype.printPaymentReceipt = function(paymentIds, callback) {
\r
1371 progressDialog.show(true);
\r
1373 fieldmapper.standardRequest(
\r
1374 ['open-ils.circ', 'open-ils.circ.money.payment_receipt.print'],
\r
1377 params : [this.authtoken, paymentIds],
\r
1378 oncomplete : function(r) {
\r
1379 var resp = openils.Util.readResponse(r);
\r
1380 var output = resp.template_output();
\r
1381 progressDialog.hide();
\r
1383 self.printData(output.data(), 1, callback);
\r
1385 var error = resp.error_output();
\r
1387 throw new Error("Error creating receipt: " + error.data());
\r
1389 throw new Error("No receipt data returned from server");
\r
1398 * Print a receipt for this user's items out
\r
1400 SelfCheckManager.prototype.printFinesReceipt = function(callback) {
\r
1401 progressDialog.show(true);
\r
1405 this.staff.ws_ou(),
\r
1407 'format.selfcheck.fines',
\r
1408 'print-on-demand',
\r
1409 [this.patron.id()]
\r
1413 fieldmapper.standardRequest(
\r
1414 ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
\r
1418 oncomplete : function(r) {
\r
1419 progressDialog.hide();
\r
1420 var resp = openils.Util.readResponse(r);
\r
1421 var output = resp.template_output();
\r
1423 self.printData(output.data(), self.finesCount, callback);
\r
1425 var error = resp.error_output();
\r
1427 throw new Error("Error creating receipt: " + error.data());
\r
1429 throw new Error("No receipt data returned from server");
\r
1439 * Logout the patron and return to the login page
\r
1441 SelfCheckManager.prototype.logoutPatron = function(print) {
\r
1442 progressDialog.show(true); // prevent patron from clicking logout link twice
\r
1443 if(print && this.checkouts.length) {
\r
1444 this.printSessionReceipt(
\r
1446 location.href = location.href;
\r
1450 location.href = location.href;
\r
1455 function checkLogin() {
\r
1456 selfCheckMgr.keepMeLoggedIn();
\r
1457 if(selfCheckMgr.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
\r
1458 switchTo('step2');
\r
1459 try{dojo.byId('patron-login-password').focus();}catch(e){}
\r
1461 selfCheckMgr.loginPatron(dojo.byId('patron-login-username').value);
\r
1466 function cancelLogin() {
\r
1467 dojo.byId('oils-selfck-status-div').innerHTML = '';
\r
1468 dojo.byId('oils-selfck-status-div2').innerHTML = '';
\r
1469 dojo.byId('oils-selfck-status-div3').innerHTML = '';
\r
1470 dojo.byId('patron-login-password').value = '';
\r
1471 openils.Util.hide('back_to_login');
\r
1472 switchTo('step1');
\r
1474 dojo.byId('patron-login-username').focus();
\r
1475 dojo.byId('patron-login-username').select();
\r
1480 * Fire up the manager on page load
\r
1482 openils.Util.addOnLoad(
\r
1484 new SelfCheckManager().init();
\r
1485 openils.Util.registerEnterHandler(dojo.byId('patron-login-username'), function(){checkLogin();});
\r
1486 openils.Util.registerEnterHandler(dojo.byId('patron-login-password'), function(){selfCheckMgr.loginPatron(dojo.byId('patron-login-username').value,dojo.byId('patron-login-password').value);});
\r