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 = 60; // 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
382 openils.Util.addCSSClass( dojo.byId('oils-selfck-status-div'), 'checkout_failure' );
\r
384 openils.Util.removeCSSClass( dojo.byId('oils-selfck-status-div'), 'checkout_failure' );
\r
386 if(message && shouldPopup && this.orgSettings[SET_ALERT_POPUP])
\r
389 if(this.orgSettings[SET_ALERT_SOUND])
\r
390 openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);
\r
395 * Manages the main input box
\r
396 * @param msg The context message to display with the box
\r
397 * @param clearOnly Don't update the context message, just clear the value and re-focus
\r
398 * @param handler Optional "on-enter" handler.
\r
400 SelfCheckManager.prototype.updateScanBox = function(args) {
\r
404 selfckScanBox.domNode.select();
\r
406 selfckScanBox.attr('value', '');
\r
409 if(args.password) {
\r
410 selfckScanBox.domNode.setAttribute('type', 'password');
\r
412 selfckScanBox.domNode.setAttribute('type', '');
\r
416 selfckScanBox.attr('value', args.value);
\r
419 dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;
\r
421 if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {
\r
422 dojo.disconnect(selfckScanBox._lastHandler);
\r
427 selfckScanBox._lastHandler = dojo.connect(
\r
431 if(e.keyCode == dojo.keys.ESCAPE)
\r
432 selfCheckMgr.logoutPatron(true);
\r
433 if(e.keyCode != dojo.keys.ENTER)
\r
435 args.handler(selfckScanBox.attr('value'));
\r
440 selfckScanBox.focus();
\r
444 * Sets up the checkout/renewal interface
\r
446 SelfCheckManager.prototype.drawCircPage = function() {
\r
447 this.keepMeLoggedIn();
\r
448 openils.Util.show('oils-selfck-circ-tbody', 'table-row-group');
\r
452 this.updateScanBox({
\r
453 msg : 'Please enter an item barcode', // TODO i18n
\r
454 handler : function(barcode) {
\r
455 openils.Util.show('oils-selfck-fines-tbody');
\r
456 openils.Util.hide('pay_fines'); switchTo('step3');
\r
457 self.checkout(barcode); }
\r
460 if(!this.circTemplate)
\r
461 this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));
\r
464 this.updateFinesSummary();
\r
467 this.updateHoldsSummary();
\r
469 // items out summary
\r
470 this.updateCircSummary();
\r
472 // render mock checkouts for debugging?
\r
473 if(this.mockCheckouts) {
\r
474 for(var i in [1,2,3])
\r
475 this.displayCheckout(this.mockCheckout, 'checkout');
\r
480 SelfCheckManager.prototype.updateFinesSummary = function() {
\r
484 fieldmapper.standardRequest(
\r
485 ['open-ils.actor', 'open-ils.actor.user.fines.summary'],
\r
487 params : [this.authtoken, this.patron.id()],
\r
488 oncomplete : function(r) {
\r
489 var summary = openils.Util.readResponse(r);
\r
490 var finesSum = dojo.byId('acct_fines');
\r
491 var bal = summary.balance_owed();
\r
492 var bal2 = parseFloat(bal);
\r
494 if(bal2>0) {finesSum.style.color="red"; openils.Util.show('oils-selfck-pay-fines-link');}
\r
495 finesSum.innerHTML = dojo.string.substitute(localeStrings.TOTAL_FINES_ACCOUNT, [bal2.toFixed(2)]);
\r
496 self.creditPayableBalance = bal2+'';
\r
503 SelfCheckManager.prototype.drawItemsOutPage = function() {
\r
504 this.keepMeLoggedIn();
\r
505 switchTo('step3','step3d');
\r
507 if(!this.outTemplate)
\r
508 this.outTemplate = this.itemsOutTbody.removeChild(dojo.byId('oils-selfck-circ-out-row'));
\r
509 while(this.itemsOutTbody.childNodes[0])
\r
510 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
\r
512 progressDialog.show(true);
\r
515 fieldmapper.standardRequest(
\r
516 ['open-ils.circ', 'open-ils.circ.actor.user.checked_out.atomic'],
\r
519 params : [this.authtoken, this.patron.id()],
\r
520 oncomplete : function(r) {
\r
521 var resp = openils.Util.readResponse(r);
\r
523 var circs = resp.sort(
\r
525 if(a.circ.due_date() > b.circ.due_date())
\r
531 self.itemsOut = [];
\r
532 dojo.forEach(circs,
\r
534 self.itemsOut.push(circ.circ.id());
\r
535 handleCheckedItems(circ);
\r
538 progressDialog.hide();
\r
544 function handleCheckedItems(circ) {
\r
545 var self = selfCheckMgr;
\r
546 var row = self.outTemplate.cloneNode(true);
\r
548 self.byName(row,'barcode').innerHTML = circ.copy.barcode();
\r
549 self.byName(row,'title').innerHTML = circ.record.title();
\r
550 self.byName(row,'author').innerHTML = circ.record.author();
\r
551 if(dojo.date.stamp.fromISOString(circ.circ.due_date())<(new Date())) self.byName(row,'due_date').style.color="red";
\r
552 self.byName(row,'due_date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(circ.circ.due_date()), {selector: 'date', fullYear: true});
\r
553 self.byName(row,'format').innerHTML = circ.record.types_of_resource()[0];
\r
555 self.itemsOutTbody.appendChild(row);
\r
558 SelfCheckManager.prototype.goToTab = function(name) {
\r
559 this.tabName = name;
\r
561 openils.Util.hide('oils-selfck-fines-page');
\r
562 openils.Util.hide('oils-selfck-payment-page');
\r
563 openils.Util.hide('oils-selfck-holds-page');
\r
564 openils.Util.hide('oils-selfck-circ-page');
\r
565 openils.Util.hide('oils-selfck-pay-fines-link');
\r
569 openils.Util.show('oils-selfck-circ-page');
\r
572 openils.Util.show('oils-selfck-circ-page');
\r
575 openils.Util.show('oils-selfck-holds-page');
\r
578 openils.Util.show('oils-selfck-fines-page');
\r
581 openils.Util.show('oils-selfck-payment-page');
\r
587 SelfCheckManager.prototype.printList = function(which) {
\r
588 this.keepMeLoggedIn();
\r
591 this.printSessionReceipt();
\r
594 this.printItemsOutReceipt();
\r
597 this.printHoldsReceipt();
\r
600 this.printFinesReceipt();
\r
605 SelfCheckManager.prototype.updateHoldsSummary = function() {
\r
606 if(!this.holdsSummary) {
\r
607 var summary = fieldmapper.standardRequest(
\r
608 ['open-ils.circ', 'open-ils.circ.holds.user_summary'],
\r
609 {params : [this.authtoken, this.patron.id()]}
\r
612 this.holdsSummary = {};
\r
613 this.holdsSummary.ready = Number(summary['4']);
\r
614 this.holdsSummary.total = 0;
\r
616 for(var i in summary)
\r
617 this.holdsSummary.total += Number(summary[i]);
\r
620 dojo.byId('oils-selfck-holds-total').innerHTML =dojo.string.substitute("${0}) Item"+(this.holdsSummary.total==1?"":"s"),[this.holdsSummary.total]);
\r
621 dojo.byId('oils-selfck-holds-ready').innerHTML =dojo.string.substitute("${0}) Item"+(this.holdsSummary.ready==1?"":"s"),[this.holdsSummary.ready]);
\r
625 SelfCheckManager.prototype.updateCircSummary = function(increment) {
\r
626 if(!this.circSummary) {
\r
628 var summary = fieldmapper.standardRequest(
\r
629 ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],
\r
630 {params : [this.authtoken, this.patron.id()]}
\r
633 this.circSummary = {
\r
634 total : Number(summary.out) + Number(summary.overdue),
\r
635 overdue : Number(summary.overdue),
\r
641 // local checkout occurred. Add to the total and the session.
\r
642 this.circSummary.total += 1;
\r
643 this.circSummary.session += 1;
\r
646 dojo.byId('oils-selfck-circ-account-total').innerHTML = dojo.string.substitute("${0}) Item"+(this.circSummary.total==1?"":"s"), [this.circSummary.total]);
\r
649 dojo.byId('oils-selfck-circ-session-total').innerHTML =
\r
650 dojo.string.substitute(
\r
651 localeStrings.TOTAL_ITEMS_SESSION,
\r
652 [this.circSummary.session]
\r
658 SelfCheckManager.prototype.drawHoldsPage = function(bool) {
\r
659 this.keepMeLoggedIn();
\r
660 if(bool) switchTo('step3','step3f'); else switchTo('step3','step3e');
\r
662 this.holdTbody = dojo.byId('oils-selfck-hold-tbody');
\r
663 this.readyTbody = dojo.byId('oils-selfck-rdy-tbody');
\r
664 if(!this.readyTemplate)
\r
665 this.readyTemplate = this.readyTbody.removeChild(dojo.byId('oils-selfck-rdy-row'));
\r
666 if(!this.holdTemplate)
\r
667 this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));
\r
668 while(this.holdTbody.childNodes[0])
\r
669 this.holdTbody.removeChild(this.holdTbody.childNodes[0]);
\r
670 while(this.readyTbody.childNodes[0])
\r
671 this.readyTbody.removeChild(this.readyTbody.childNodes[0]);
\r
673 progressDialog.show(true);
\r
676 fieldmapper.standardRequest( // fetch the hold IDs
\r
678 ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],
\r
680 params : [this.authtoken, this.patron.id()],
\r
682 oncomplete : function(r) {
\r
683 var ids = openils.Util.readResponse(r);
\r
684 if(!ids || ids.length == 0) {
\r
685 progressDialog.hide();
\r
689 fieldmapper.standardRequest( // fetch the hold objects with fleshed details
\r
690 ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve'],
\r
692 params : [self.authtoken, ids],
\r
694 onresponse : function(rr) {
\r
695 progressDialog.hide();
\r
696 self.drawHolds(openils.Util.readResponse(rr));
\r
706 * Fetch and add a single hold to the list of holds
\r
708 SelfCheckManager.prototype.drawHolds = function(holds) {
\r
709 //this.keepMeLoggedIn();
\r
710 this.holds = holds;
\r
711 progressDialog.hide();
\r
715 var row = this.holdTemplate.cloneNode(true);
\r
716 var row2 = this.readyTemplate.cloneNode(true);
\r
718 //if(data.mvr.isbn()) {
\r
719 // this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());
\r
722 if(data.status == 4) {
\r
723 this.byName(row2, 'title').innerHTML = data.mvr.title();
\r
724 this.byName(row2, 'format').innerHTML = data.mvr.types_of_resource()[0];
\r
725 this.byName(row2, 'lib').innerHTML = fieldmapper.aou.findOrgUnit(data.hold.pickup_lib()).name();
\r
726 if(dojo.date.stamp.fromISOString(data.hold.capture_time())<(new Date())) this.byName(row2, 'date').style.color="red";
\r
727 this.byName(row2, 'date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(data.hold.capture_time()), {selector: 'date', fullYear: true});
\r
728 this.readyTbody.appendChild(row2);
\r
731 this.byName(row, 'title').innerHTML = data.mvr.title();
\r
732 this.byName(row, 'author').innerHTML = data.mvr.author();
\r
733 this.byName(row, 'format').innerHTML = data.mvr.types_of_resource()[0];
\r
735 // hold is still pending
\r
736 this.byName(row, 'status').innerHTML = dojo.string.substitute(localeStrings.HOLD_STATUS_WAITING,[data.queue_position, data.potential_copies]);
\r
737 this.holdTbody.appendChild(row);
\r
742 SelfCheckManager.prototype.drawFinesPage = function() {
\r
743 this.keepMeLoggedIn();
\r
744 // TODO add option to hid scanBox
\r
745 // this.updateScanBox(...)
\r
747 //this.goToTab('fines');
\r
748 switchTo('step3','step3c');
\r
749 progressDialog.show(true);
\r
751 //if(this.creditPayableBalance > 0 && this.orgSettings[SET_CC_PAYMENT_ALLOWED])
\r
752 // openils.Util.show('oils-selfck-pay-fines-link', 'inline');
\r
755 this.finesTbody = dojo.byId('oils-selfck-fines-tbody');
\r
756 if(!this.finesTemplate)
\r
757 this.finesTemplate = this.finesTbody.removeChild(dojo.byId('oils-selfck-fines-row'));
\r
758 while(this.finesTbody.childNodes[0])
\r
759 this.finesTbody.removeChild(this.finesTbody.childNodes[0]);
\r
762 // when user clicks on a selector checkbox, update the total owed
\r
763 var updateSelected = function() {
\r
766 dojo.query('[name=selector]', this.finesTbody),
\r
769 total += Number(input.getAttribute('balance_owed'));
\r
773 total = total.toFixed(2);
\r
774 dojo.byId('oils-selfck-selected-total').innerHTML =
\r
775 dojo.string.substitute(localeStrings.TOTAL_FINES_SELECTED, [total]);
\r
778 // wire up the batch on/off selector
\r
779 var sel = dojo.byId('oils-selfck-fines-selector');
\r
780 sel.onchange = function() {
\r
782 dojo.query('[name=selector]', this.finesTbody),
\r
784 input.checked = sel.checked;
\r
790 var handler = function(dataList) {
\r
792 self.finesCount = dataList.length;
\r
793 self.finesData = dataList;
\r
795 for(var i in dataList) {
\r
797 var data = dataList[i];
\r
798 var row = self.finesTemplate.cloneNode(true);
\r
799 var type = data.transaction.xact_type();
\r
801 if(type == 'circulation') {
\r
802 self.byName(row, 'title').innerHTML = data.record.title();
\r
803 if(dojo.date.stamp.fromISOString(data.circ.due_date())<(new Date())) self.byName(row, 'due_date').style.color="red";
\r
804 self.byName(row, 'due_date').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(data.circ.due_date()), {selector: 'date', fullYear: true});
\r
805 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
807 } else if(type == 'grocery') {
\r
808 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
811 //self.byName(row, 'total_owed').innerHTML = data.transaction.total_owed();
\r
812 //self.byName(row, 'total_paid').innerHTML = data.transaction.total_paid();
\r
813 self.byName(row, 'balance').innerHTML = data.transaction.balance_owed();
\r
814 self.byName(row, 'selector').balance_owed = data.transaction.balance_owed();
\r
815 self.byName(row, 'selector').setAttribute('xact', data.transaction.id());
\r
818 var selector = self.byName(row, 'selector')
\r
819 selector.onchange = updateSelected;
\r
820 selector.setAttribute('xact', data.transaction.id());
\r
821 selector.setAttribute('balance_owed', data.transaction.balance_owed());
\r
822 selector.checked = true;
\r
824 self.finesTbody.appendChild(row);
\r
827 //updateSelected();
\r
831 fieldmapper.standardRequest(
\r
832 ['open-ils.actor', 'open-ils.actor.user.transactions.have_balance.fleshed'],
\r
834 params : [this.authtoken, this.patron.id()],
\r
835 oncomplete : function(r) {
\r
836 progressDialog.hide();
\r
837 handler(openils.Util.readResponse(r));
\r
843 SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
\r
844 var resp = fieldmapper.standardRequest(
\r
845 ['open-ils.circ', 'open-ils.circ.transit.abort'],
\r
846 {params : [this.authtoken, {barcode : barcode}]}
\r
849 // resp == 1 on success
\r
850 if(openils.Event.parse(resp))
\r
853 var resp = fieldmapper.standardRequest(
\r
854 ['open-ils.circ', 'open-ils.circ.checkin.override'],
\r
857 patron_id : this.patron.id(),
\r
858 copy_barcode : barcode,
\r
864 if(!resp.length) resp = [resp];
\r
865 for(var i = 0; i < resp.length; i++) {
\r
866 var tc = openils.Event.parse(resp[i]).textcode;
\r
867 if(tc == 'SUCCESS' || tc == 'NO_CHANGE') {
\r
878 * Check out a single item. If the item is already checked
\r
879 * out to the patron, redirect to renew()
\r
881 SelfCheckManager.prototype.checkout = function(barcode, override) {
\r
882 this.keepMeLoggedIn();
\r
883 this.prevCirc = null;
\r
886 this.updateScanbox(null, true);
\r
890 if(this.mockCheckouts) {
\r
891 // if we're in mock-checkout mode, just insert another
\r
892 // fake circ into the table and get out of here.
\r
893 this.displayCheckout(this.mockCheckout, 'checkout');
\r
897 // TODO see if it's a patron barcode
\r
898 // TODO see if this item has already been checked out in this session
\r
900 var method = 'open-ils.circ.checkout.full';
\r
901 if(override) method += '.override';
\r
903 console.log("Checkout out item " + barcode + " with method " + method);
\r
905 var result = fieldmapper.standardRequest(
\r
906 ['open-ils.circ', method],
\r
909 patron_id : this.patron.id(),
\r
910 copy_barcode : barcode
\r
915 var stat = this.handleXactResult('checkout', barcode, result);
\r
917 if(stat.override) {
\r
918 this.checkout(barcode, true);
\r
919 } else if(stat.doOver) {
\r
920 this.checkout(barcode);
\r
921 } else if(stat.renew) {
\r
922 this.renew(barcode);
\r
926 SelfCheckManager.prototype.failPartMessage = function(result) {
\r
927 if (result.payload && result.payload.fail_part) {
\r
928 var stringKey = "FAIL_PART_" +
\r
929 result.payload.fail_part.replace(/\./g, "_");
\r
930 return localeStrings[stringKey];
\r
936 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
\r
937 var displayText = '';
\r
939 // If true, the display message is important enough to pop up. Whether or not
\r
940 // an alert() actually occurs, depends on org unit settings
\r
941 var popup = false;
\r
942 var sound = ''; // sound file reference
\r
943 var payload = result.payload || {};
\r
944 var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];
\r
945 var blockStatuses = this.orgSettings[SET_BLOCK_CHECKOUT_ON_COPY_STATUS];
\r
946 result.payload = payload;
\r
948 if(result.textcode == 'NO_SESSION') {
\r
950 return this.logoutStaff();
\r
952 } else if(result.textcode == 'SUCCESS') {
\r
954 if(action == 'checkout') {
\r
956 displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);
\r
957 this.displayCheckout(result, 'checkout');
\r
959 if(payload.holds_fulfilled && payload.holds_fulfilled.length) {
\r
960 // A hold was fulfilled, update the hold numbers in the circ summary
\r
961 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");
\r
962 this.holdsSummary = null;
\r
963 this.updateHoldsSummary();
\r
966 this.updateCircSummary(true);
\r
968 } else if(action == 'renew') {
\r
970 displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);
\r
971 this.displayCheckout(result, 'renew');
\r
974 this.checkouts.push({circ : result.payload.circ.id()});
\r
975 sound = 'checkout-success';
\r
976 this.updateScanBox();
\r
978 } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {
\r
980 // Server says the item is already checked out. If it's checked out to the
\r
981 // current user, we may need to renew it.
\r
983 if(payload.old_circ) {
\r
986 old_circ refers to the previous checkout IFF it's for the same user.
\r
987 If no auto-renew interval is not defined, assume we should renew it
\r
988 If an auto-renew interval is defined and the payload comes back with
\r
989 auto_renew set to true, do the renewal. Otherwise, let the patron know
\r
990 the item is already checked out to them. */
\r
992 if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||
\r
993 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {
\r
994 this.prevCirc = payload.old_circ.id();
\r
995 return { renew : true };
\r
999 sound = 'checkout-failure';
\r
1000 displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);
\r
1004 if( // copy is marked lost. if configured to do so, check it in and try again.
\r
1005 result.payload.copy &&
\r
1006 result.payload.copy.status() == /* LOST */ 3 &&
\r
1007 overrideEvents && overrideEvents.length &&
\r
1008 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
\r
1010 if(this.checkin(item)) {
\r
1011 return { doOver : true };
\r
1016 // item is checked out to some other user
\r
1018 sound = 'checkout-failure';
\r
1019 displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
\r
1022 this.updateScanBox();
\r
1027 if(overrideEvents && overrideEvents.length) {
\r
1029 // see if the events we received are all in the list of
\r
1030 // events to override
\r
1032 if(!result.length) result = [result];
\r
1034 var override = true;
\r
1035 for(var i = 0; i < result.length; i++) {
\r
1037 var match = overrideEvents.filter(function(e) { return (e == result[i].textcode); })[0];
\r
1044 if(result[i].textcode == 'COPY_NOT_AVAILABLE' && blockStatuses && blockStatuses.length) {
\r
1046 var stat = result[i].payload.status(); // copy status
\r
1047 if(typeof stat == 'object') stat = stat.id();
\r
1049 var match2 = blockStatuses.filter(function(e) { return (e == stat); })[0];
\r
1051 if(match2) { // copy is in a blocked status
\r
1057 if(result[i].textcode == 'COPY_IN_TRANSIT') {
\r
1058 // to override a transit, we have to abort the transit and check it in first
\r
1059 if(this.checkin(item, true)) {
\r
1060 return { doOver : true };
\r
1068 return { override : true };
\r
1071 this.updateScanBox();
\r
1073 sound = 'checkout-failure';
\r
1075 if(action == 'renew')
\r
1076 this.checkouts.push({circ : this.prevCirc, renewal_failure : true});
\r
1078 if(result.length)
\r
1079 result = result[0];
\r
1081 switch(result.textcode) {
\r
1083 // TODO custom handler for blocking penalties
\r
1085 case 'MAX_RENEWALS_REACHED' :
\r
1086 displayText = dojo.string.substitute(
\r
1087 localeStrings.MAX_RENEWALS, [item]);
\r
1090 case 'ITEM_NOT_CATALOGED' :
\r
1091 displayText = dojo.string.substitute(
\r
1092 localeStrings.ITEM_NOT_CATALOGED, [item]);
\r
1095 case 'OPEN_CIRCULATION_EXISTS' :
\r
1096 displayText = dojo.string.substitute(
\r
1097 localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
\r
1102 console.error('Unhandled event ' + result.textcode);
\r
1104 if (!(displayText = this.failPartMessage(result))) {
\r
1105 if (action == 'checkout' || action == 'renew') {
\r
1106 displayText = dojo.string.substitute(
\r
1107 localeStrings.GENERIC_CIRC_FAILURE, [item]);
\r
1109 displayText = dojo.string.substitute(
\r
1110 localeStrings.UNKNOWN_ERROR, [result.textcode]);
\r
1116 this.handleAlert(displayText, popup, sound);
\r
1124 SelfCheckManager.prototype.renew = function(barcode, override) {
\r
1126 var method = 'open-ils.circ.renew';
\r
1127 if(override) method += '.override';
\r
1129 console.log("Renewing item " + barcode + " with method " + method);
\r
1131 var result = fieldmapper.standardRequest(
\r
1132 ['open-ils.circ', method],
\r
1135 patron_id : this.patron.id(),
\r
1136 copy_barcode : barcode
\r
1141 console.log(js2JSON(result));
\r
1143 var stat = this.handleXactResult('renew', barcode, result);
\r
1146 this.renew(barcode, true);
\r
1150 * Display the result of a checkout or renewal in the items out table
\r
1152 SelfCheckManager.prototype.displayCheckout = function(evt, type, itemsOut) {
\r
1153 var copy = evt.payload.copy;
\r
1154 var record = evt.payload.record;
\r
1155 var circ = evt.payload.circ;
\r
1156 var row = this.circTemplate.cloneNode(true);
\r
1158 //if(record.isbn()) {
\r
1159 // this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());
\r
1162 this.byName(row, 'barcode').innerHTML = copy.barcode();
\r
1163 this.byName(row, 'title').innerHTML = record.title();
\r
1164 //this.byName(row, 'author').innerHTML = record.author();
\r
1165 //this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();
\r
1166 openils.Util.show(this.byName(row, type));
\r
1168 var date = dojo.date.stamp.fromISOString(circ.due_date());
\r
1169 this.byName(row, 'due_date').innerHTML =
\r
1170 dojo.date.locale.format(date, {selector : 'date'});
\r
1172 // put new circs at the top of the list
\r
1173 var tbody = this.circTbody;
\r
1174 if(itemsOut) tbody = this.itemsOutTbody;
\r
1175 tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
\r
1179 SelfCheckManager.prototype.byName = function(node, name) {
\r
1180 return dojo.query('[name=' + name+']', node)[0];
\r
1184 SelfCheckManager.prototype.initPrinter = function() {
\r
1185 try { // Mozilla only
\r
1186 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
\r
1187 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
\r
1188 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
\r
1189 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
\r
1190 var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
\r
1192 pref.setBoolPref('print.always_print_silent', true);
\r
1194 console.log("Unable to initialize auto-printing");
\r
1199 * Print a receipt for this session's checkouts
\r
1201 SelfCheckManager.prototype.printSessionReceipt = function(callback) {
\r
1203 var circCtx = []; // circ context data. in this case, renewal_failure info
\r
1205 // collect the circs and failure info
\r
1209 circIds.push(blob.circ);
\r
1210 circCtx.push({renewal_failure:blob.renewal_failure});
\r
1216 this.staff.ws_ou(),
\r
1218 'format.selfcheck.checkout',
\r
1219 'print-on-demand',
\r
1225 fieldmapper.standardRequest(
\r
1226 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
\r
1230 oncomplete : function(r) {
\r
1231 var resp = openils.Util.readResponse(r);
\r
1232 var output = resp.template_output();
\r
1234 self.printData(output.data(), self.checkouts.length, callback);
\r
1236 var error = resp.error_output();
\r
1238 throw new Error("Error creating receipt: " + error.data());
\r
1240 throw new Error("No receipt data returned from server");
\r
1248 SelfCheckManager.prototype.printData = function(data, numItems, callback) {
\r
1249 var win = window.open('', '', 'resizable,width=350,height=250,scrollbars=1');
\r
1250 win.document.body.innerHTML = data;
\r
1254 * There is no way to know when the browser is done printing.
\r
1255 * Make a best guess at when to close the print window by basing
\r
1256 * the setTimeout wait on the number of items to be printed plus
\r
1259 var sleepTime = 1000;
\r
1261 sleepTime += (numItems / 2) * 1000;
\r
1265 win.close(); // close the print window
\r
1266 if(callback) callback(); // fire optional post-print callback
\r
1274 * Print a receipt for this user's items out
\r
1276 SelfCheckManager.prototype.printItemsOutReceipt = function(callback) {
\r
1277 if(!this.itemsOut.length) return;
\r
1279 progressDialog.show(true);
\r
1283 this.staff.ws_ou(),
\r
1285 'format.selfcheck.items_out',
\r
1286 'print-on-demand',
\r
1291 fieldmapper.standardRequest(
\r
1292 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
\r
1296 oncomplete : function(r) {
\r
1297 progressDialog.hide();
\r
1298 var resp = openils.Util.readResponse(r);
\r
1299 var output = resp.template_output();
\r
1301 self.printData(output.data(), self.itemsOut.length, callback);
\r
1303 var error = resp.error_output();
\r
1305 throw new Error("Error creating receipt: " + error.data());
\r
1307 throw new Error("No receipt data returned from server");
\r
1316 * Print a receipt for this user's items out
\r
1318 SelfCheckManager.prototype.printHoldsReceipt = function(callback) {
\r
1319 if(!this.holds.length) return;
\r
1321 progressDialog.show(true);
\r
1324 var holdData = [];
\r
1326 dojo.forEach(this.holds,
\r
1328 holdIds.push(data.hold.id());
\r
1329 if(data.status == 4) {
\r
1330 holdData.push({ready : true});
\r
1333 queue_position : data.queue_position,
\r
1334 potential_copies : data.potential_copies
\r
1342 this.staff.ws_ou(),
\r
1344 'format.selfcheck.holds',
\r
1345 'print-on-demand',
\r
1351 fieldmapper.standardRequest(
\r
1352 ['open-ils.circ', 'open-ils.circ.fire_hold_trigger_events'],
\r
1356 oncomplete : function(r) {
\r
1357 progressDialog.hide();
\r
1358 var resp = openils.Util.readResponse(r);
\r
1359 var output = resp.template_output();
\r
1361 self.printData(output.data(), self.holds.length, callback);
\r
1363 var error = resp.error_output();
\r
1365 throw new Error("Error creating receipt: " + error.data());
\r
1367 throw new Error("No receipt data returned from server");
\r
1376 SelfCheckManager.prototype.printPaymentReceipt = function(paymentIds, callback) {
\r
1378 progressDialog.show(true);
\r
1380 fieldmapper.standardRequest(
\r
1381 ['open-ils.circ', 'open-ils.circ.money.payment_receipt.print'],
\r
1384 params : [this.authtoken, paymentIds],
\r
1385 oncomplete : function(r) {
\r
1386 var resp = openils.Util.readResponse(r);
\r
1387 var output = resp.template_output();
\r
1388 progressDialog.hide();
\r
1390 self.printData(output.data(), 1, callback);
\r
1392 var error = resp.error_output();
\r
1394 throw new Error("Error creating receipt: " + error.data());
\r
1396 throw new Error("No receipt data returned from server");
\r
1405 * Print a receipt for this user's items out
\r
1407 SelfCheckManager.prototype.printFinesReceipt = function(callback) {
\r
1408 progressDialog.show(true);
\r
1412 this.staff.ws_ou(),
\r
1414 'format.selfcheck.fines',
\r
1415 'print-on-demand',
\r
1416 [this.patron.id()]
\r
1420 fieldmapper.standardRequest(
\r
1421 ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
\r
1425 oncomplete : function(r) {
\r
1426 progressDialog.hide();
\r
1427 var resp = openils.Util.readResponse(r);
\r
1428 var output = resp.template_output();
\r
1430 self.printData(output.data(), self.finesCount, callback);
\r
1432 var error = resp.error_output();
\r
1434 throw new Error("Error creating receipt: " + error.data());
\r
1436 throw new Error("No receipt data returned from server");
\r
1446 * Logout the patron and return to the login page
\r
1448 SelfCheckManager.prototype.logoutPatron = function(print) {
\r
1449 progressDialog.show(true); // prevent patron from clicking logout link twice
\r
1450 if(print && this.checkouts.length) {
\r
1451 this.printSessionReceipt(
\r
1453 location.href = location.href;
\r
1457 location.href = location.href;
\r
1462 function checkLogin() {
\r
1463 selfCheckMgr.keepMeLoggedIn();
\r
1464 if(selfCheckMgr.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
\r
1465 switchTo('step2');
\r
1466 try{dojo.byId('patron-login-password').focus();}catch(e){}
\r
1468 selfCheckMgr.loginPatron(dojo.byId('patron-login-username').value);
\r
1473 function cancelLogin() {
\r
1474 dojo.byId('oils-selfck-status-div').innerHTML = '';
\r
1475 dojo.byId('oils-selfck-status-div2').innerHTML = '';
\r
1476 dojo.byId('oils-selfck-status-div3').innerHTML = '';
\r
1477 dojo.byId('patron-login-password').value = '';
\r
1478 openils.Util.hide('back_to_login');
\r
1479 switchTo('step1');
\r
1481 dojo.byId('patron-login-username').focus();
\r
1482 dojo.byId('patron-login-username').select();
\r
1487 * Fire up the manager on page load
\r
1489 openils.Util.addOnLoad(
\r
1491 new SelfCheckManager().init();
\r
1492 openils.Util.registerEnterHandler(dojo.byId('patron-login-username'), function(){checkLogin();});
\r
1493 openils.Util.registerEnterHandler(dojo.byId('patron-login-password'), function(){selfCheckMgr.loginPatron(dojo.byId('patron-login-username').value,dojo.byId('patron-login-password').value);});
\r