1 dojo.require('dojo.date.locale');
2 dojo.require('dojo.date.stamp');
3 dojo.require('dijit.form.CheckBox');
4 dojo.require('dijit.form.NumberSpinner');
5 dojo.require('openils.CGI');
6 dojo.require('openils.Util');
7 dojo.require('openils.User');
8 dojo.require('openils.Event');
9 dojo.require('openils.widget.ProgressDialog');
10 dojo.require('openils.widget.OrgUnitFilteringSelect');
12 dojo.requireLocalization('openils.circ', 'selfcheck');
13 var localeStrings = dojo.i18n.getLocalization('openils.circ', 'selfcheck');
16 const SET_BARCODE_REGEX = 'opac.barcode_regex';
17 const SET_PATRON_TIMEOUT = 'circ.selfcheck.patron_login_timeout';
18 const SET_AUTO_OVERRIDE_EVENTS = 'circ.selfcheck.auto_override_checkout_events';
19 const SET_PATRON_PASSWORD_REQUIRED = 'circ.selfcheck.patron_password_required';
20 const SET_AUTO_RENEW_INTERVAL = 'circ.checkout_auto_renew_age';
21 const SET_WORKSTATION_REQUIRED = 'circ.selfcheck.workstation_required';
22 const SET_ALERT_POPUP = 'circ.selfcheck.alert.popup';
23 const SET_ALERT_SOUND = 'circ.selfcheck.alert.sound';
24 const SET_CC_PAYMENT_ALLOWED = 'credit.payments.allow';
26 function SelfCheckManager() {
28 this.cgi = new openils.CGI();
30 this.workstation = null;
31 this.authtoken = null;
34 this.patronBarcodeRegex = null;
39 // During renewals, keep track of the ID of the previous circulation.
40 // Previous circ is used for tracking failed renewals (for receipts).
43 // current item barcode
44 this.itemBarcode = null;
46 // are we currently performing a renewal?
47 this.isRenewal = false;
49 // dict of org unit settings for "here"
50 this.orgSettings = {};
52 // Construct a mock checkout for debugging purposes
53 if(this.mockCheckouts = this.cgi.param('mock-circ')) {
57 record : new fieldmapper.mvr(),
58 copy : new fieldmapper.acp(),
59 circ : new fieldmapper.circ()
63 this.mockCheckout.payload.record.title('Jazz improvisation for guitar');
64 this.mockCheckout.payload.record.author('Wise, Les');
65 this.mockCheckout.payload.record.isbn('0634033565');
66 this.mockCheckout.payload.copy.barcode('123456789');
67 this.mockCheckout.payload.circ.renewal_remaining(1);
68 this.mockCheckout.payload.circ.parent_circ(1);
69 this.mockCheckout.payload.circ.due_date('2012-12-21');
78 * Fetch the org-unit settings, initialize the display, etc.
80 SelfCheckManager.prototype.init = function() {
82 this.staff = openils.User.user;
83 this.workstation = openils.User.workstation;
84 this.authtoken = openils.User.authtoken;
85 this.loadOrgSettings();
87 this.circTbody = dojo.byId('oils-selfck-circ-tbody');
88 this.itemsOutTbody = dojo.byId('oils-selfck-circ-out-tbody');
90 // workstation is required but none provided
91 if(this.orgSettings[SET_WORKSTATION_REQUIRED] && !this.workstation) {
92 if(confirm(dojo.string.substitute(localeStrings.WORKSTATION_REQUIRED))) {
93 this.registerWorkstation();
99 // connect onclick handlers to the various navigation links
101 'oils-selfck-hold-details-link' : function() { self.drawHoldsPage(); },
102 'oils-selfck-view-fines-link' : function() { self.drawFinesPage(); },
103 'oils-selfck-pay-fines-link' : function() { self.drawPayFinesPage(); },
104 'oils-selfck-nav-home' : function() { self.drawCircPage(); },
105 'oils-selfck-nav-logout' : function() { self.logoutPatron(); },
106 'oils-selfck-nav-logout-print' : function() { self.logoutPatron(true); },
107 'oils-selfck-items-out-details-link' : function() { self.drawItemsOutPage(); },
108 'oils-selfck-print-list-link' : function() { self.printList(); }
111 for(var id in linkHandlers)
112 dojo.connect(dojo.byId(id), 'onclick', linkHandlers[id]);
115 if(this.cgi.param('patron')) {
117 // Patron barcode via cgi param. Mainly used for debugging and
118 // only works if password is not required by policy
119 this.loginPatron(this.cgi.param('patron'));
122 this.drawLoginPage();
126 * To test printing, pass a URL param of 'testprint'. The value for the param
127 * should be a JSON string like so: [{circ:<circ_id>}, ...]
129 var testPrint = this.cgi.param('testprint');
131 this.checkouts = JSON2js(testPrint);
132 this.printSessionReceipt();
139 * Registers a new workstion
141 SelfCheckManager.prototype.registerWorkstation = function() {
143 oilsSelfckWsDialog.show();
145 new openils.User().buildPermOrgSelector(
146 'REGISTER_WORKSTATION',
147 oilsSelfckWsLocSelector,
153 dojo.connect(oilsSelfckWsSubmit, 'onClick',
156 oilsSelfckWsDialog.hide();
157 var name = oilsSelfckWsLocSelector.attr('displayedValue') + '-' + oilsSelfckWsName.attr('value');
159 var res = fieldmapper.standardRequest(
160 ['open-ils.actor', 'open-ils.actor.workstation.register'],
162 self.authtoken, name, oilsSelfckWsLocSelector.attr('value')
167 if(evt = openils.Event.parse(res)) {
168 if(evt.textcode == 'WORKSTATION_NAME_EXISTS') {
169 if(confirm(localeStrings.WORKSTATION_EXISTS)) {
170 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
172 self.registerWorkstation();
179 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
186 * Loads the org unit settings
188 SelfCheckManager.prototype.loadOrgSettings = function() {
190 var settings = fieldmapper.aou.fetchOrgSettingBatch(
191 this.staff.ws_ou(), [
196 SET_AUTO_OVERRIDE_EVENTS,
197 SET_PATRON_PASSWORD_REQUIRED,
198 SET_AUTO_RENEW_INTERVAL,
199 SET_WORKSTATION_REQUIRED,
200 SET_CC_PAYMENT_ALLOWED
206 this.orgSettings[k] = settings[k].value;
209 if(settings[SET_BARCODE_REGEX])
210 this.patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);
213 SelfCheckManager.prototype.drawLoginPage = function() {
216 var bcHandler = function(barcode) {
217 // handle patron barcode entry
219 if(self.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
221 // password is required. wire up the scan box to read it
223 msg : 'Please enter your password', // TODO i18n
224 handler : function(pw) { self.loginPatron(barcode, pw); },
229 // password is not required, go ahead and login
230 self.loginPatron(barcode);
235 msg : 'Please log in with your library barcode.', // TODO
243 SelfCheckManager.prototype.loginPatron = function(barcode, passwd) {
245 if(this.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
248 // would only happen in dev/debug mode when using the patron= param
249 alert('password required by org setting. remove patron= from URL');
253 // patron password is required. Verify it.
255 var res = fieldmapper.standardRequest(
256 ['open-ils.actor', 'open-ils.actor.verify_user_password'],
257 {params : [this.authtoken, barcode, null, hex_md5(passwd)]}
261 // user-not-found results in login failure
263 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
264 false, 'login-failure'
266 this.drawLoginPage();
271 // retrieve the fleshed user by barcode
272 this.patron = fieldmapper.standardRequest(
273 ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve_by_barcode'],
274 {params : [this.authtoken, barcode]}
277 var evt = openils.Event.parse(this.patron);
280 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
281 false, 'login-failure'
283 this.drawLoginPage();
287 this.handleAlert('', false, 'login-success');
288 dojo.byId('oils-selfck-user-banner').innerHTML = 'Welcome, ' + this.patron.first_given_name(); // TODO i18n
294 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
296 console.log("Handling alert " + message);
298 dojo.byId('oils-selfck-status-div').innerHTML = message;
300 if(shouldPopup && this.orgSettings[SET_ALERT_POPUP])
303 if(sound && this.orgSettings[SET_ALERT_SOUND])
304 openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);
309 * Manages the main input box
310 * @param msg The context message to display with the box
311 * @param clearOnly Don't update the context message, just clear the value and re-focus
312 * @param handler Optional "on-enter" handler.
314 SelfCheckManager.prototype.updateScanBox = function(args) {
318 selfckScanBox.domNode.select();
320 selfckScanBox.attr('value', '');
324 selfckScanBox.domNode.setAttribute('type', 'password');
326 selfckScanBox.domNode.setAttribute('type', '');
330 selfckScanBox.attr('value', args.value);
333 dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;
335 if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {
336 dojo.disconnect(selfckScanBox._lastHandler);
340 selfckScanBox._lastHandler = dojo.connect(
344 if(e.keyCode != dojo.keys.ENTER)
346 args.handler(selfckScanBox.attr('value'));
351 selfckScanBox.focus();
355 * Sets up the checkout/renewal interface
357 SelfCheckManager.prototype.drawCircPage = function() {
359 openils.Util.show('oils-selfck-circ-tbody', 'table-row-group');
360 this.goToTab('checkout');
362 while(this.itemsOutTbody.childNodes[0])
363 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
367 msg : 'Please enter an item barcode', // TODO i18n
368 handler : function(barcode) { self.checkout(barcode); }
371 if(!this.circTemplate)
372 this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));
375 this.updateFinesSummary();
378 this.updateHoldsSummary();
381 this.updateCircSummary();
383 // render mock checkouts for debugging?
384 if(this.mockCheckouts) {
385 for(var i in [1,2,3])
386 this.displayCheckout(this.mockCheckout, 'checkout');
391 SelfCheckManager.prototype.updateFinesSummary = function() {
395 fieldmapper.standardRequest(
396 ['open-ils.actor', 'open-ils.actor.user.fines.summary'],
398 params : [this.authtoken, this.patron.id()],
399 oncomplete : function(r) {
401 var summary = openils.Util.readResponse(r);
403 dojo.byId('oils-selfck-fines-total').innerHTML =
404 dojo.string.substitute(
405 localeStrings.TOTAL_FINES_ACCOUNT,
406 [summary.balance_owed()]
409 self.creditPayableBalance = summary.balance_owed();
416 SelfCheckManager.prototype.drawItemsOutPage = function() {
417 openils.Util.hide('oils-selfck-circ-tbody');
419 this.goToTab('items_out');
421 while(this.itemsOutTbody.childNodes[0])
422 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
424 progressDialog.show(true);
427 fieldmapper.standardRequest(
428 ['open-ils.circ', 'open-ils.circ.actor.user.checked_out.atomic'],
431 params : [this.authtoken, this.patron.id()],
432 oncomplete : function(r) {
434 var resp = openils.Util.readResponse(r);
436 var circs = resp.sort(
438 if(a.circ.due_date() > b.circ.due_date())
444 progressDialog.hide();
449 self.itemsOut.push(circ.circ.id());
450 self.displayCheckout(
452 (circ.circ.parent_circ()) ? 'renew' : 'checkout',
463 SelfCheckManager.prototype.goToTab = function(name) {
466 openils.Util.hide('oils-selfck-fines-page');
467 openils.Util.hide('oils-selfck-payment-page');
468 openils.Util.hide('oils-selfck-holds-page');
469 openils.Util.hide('oils-selfck-circ-page');
470 openils.Util.hide('oils-selfck-pay-fines-link');
474 openils.Util.show('oils-selfck-circ-page');
477 openils.Util.show('oils-selfck-circ-page');
480 openils.Util.show('oils-selfck-holds-page');
483 openils.Util.show('oils-selfck-fines-page');
486 openils.Util.show('oils-selfck-payment-page');
492 SelfCheckManager.prototype.printList = function() {
493 switch(this.tabName) {
495 this.printSessionReceipt();
498 this.printItemsOutReceipt();
501 this.printHoldsReceipt();
504 this.printFinesReceipt();
509 SelfCheckManager.prototype.updateHoldsSummary = function() {
511 if(!this.holdsSummary) {
512 var summary = fieldmapper.standardRequest(
513 ['open-ils.circ', 'open-ils.circ.holds.user_summary'],
514 {params : [this.authtoken, this.patron.id()]}
517 this.holdsSummary = {};
518 this.holdsSummary.ready = Number(summary['4']);
519 this.holdsSummary.total = 0;
521 for(var i in summary)
522 this.holdsSummary.total += Number(summary[i]);
525 dojo.byId('oils-selfck-holds-total').innerHTML =
526 dojo.string.substitute(
527 localeStrings.TOTAL_HOLDS,
528 [this.holdsSummary.total]
531 dojo.byId('oils-selfck-holds-ready').innerHTML =
532 dojo.string.substitute(
533 localeStrings.HOLDS_READY_FOR_PICKUP,
534 [this.holdsSummary.ready]
539 SelfCheckManager.prototype.updateCircSummary = function(increment) {
541 if(!this.circSummary) {
543 var summary = fieldmapper.standardRequest(
544 ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],
545 {params : [this.authtoken, this.patron.id()]}
549 total : Number(summary.out) + Number(summary.overdue),
550 overdue : Number(summary.overdue),
556 // local checkout occurred. Add to the total and the session.
557 this.circSummary.total += 1;
558 this.circSummary.session += 1;
561 dojo.byId('oils-selfck-circ-account-total').innerHTML =
562 dojo.string.substitute(
563 localeStrings.TOTAL_ITEMS_ACCOUNT,
564 [this.circSummary.total]
567 dojo.byId('oils-selfck-circ-session-total').innerHTML =
568 dojo.string.substitute(
569 localeStrings.TOTAL_ITEMS_SESSION,
570 [this.circSummary.session]
575 SelfCheckManager.prototype.drawHoldsPage = function() {
577 // TODO add option to hid scanBox
578 // this.updateScanBox(...)
580 this.goToTab('holds');
582 this.holdTbody = dojo.byId('oils-selfck-hold-tbody');
583 if(!this.holdTemplate)
584 this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));
585 while(this.holdTbody.childNodes[0])
586 this.holdTbody.removeChild(this.holdTbody.childNodes[0]);
588 progressDialog.show(true);
591 fieldmapper.standardRequest( // fetch the hold IDs
593 ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],
595 params : [this.authtoken, this.patron.id()],
597 oncomplete : function(r) {
598 var ids = openils.Util.readResponse(r);
599 if(!ids || ids.length == 0) {
600 progressDialog.hide();
604 fieldmapper.standardRequest( // fetch the hold objects with fleshed details
605 ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve.atomic'],
607 params : [self.authtoken, ids],
609 oncomplete : function(rr) {
610 self.drawHolds(openils.Util.readResponse(rr));
620 * Fetch and add a single hold to the list of holds
622 SelfCheckManager.prototype.drawHolds = function(holds) {
625 // sort available holds to the top of the list
626 // followed by queue position order
628 if(a.status == 4) return -1;
629 if(a.queue_position < b.queue_position) return -1;
636 progressDialog.hide();
638 for(var i in holds) {
641 var row = this.holdTemplate.cloneNode(true);
643 if(data.mvr.isbn()) {
644 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());
647 this.byName(row, 'title').innerHTML = data.mvr.title();
648 this.byName(row, 'author').innerHTML = data.mvr.author();
650 if(data.status == 4) {
652 // hold is ready for pickup
653 this.byName(row, 'status').innerHTML = localeStrings.HOLD_STATUS_READY;
657 // hold is still pending
658 this.byName(row, 'status').innerHTML =
659 dojo.string.substitute(
660 localeStrings.HOLD_STATUS_WAITING,
661 [data.queue_position, data.potential_copies]
665 this.holdTbody.appendChild(row);
670 SelfCheckManager.prototype.drawPayFinesPage = function() {
671 this.goToTab('payment');
673 // find the total selected amount
676 dojo.query('[name=selector]', this.finesTbody),
679 total += Number(input.getAttribute('balance_owed'));
682 total = total.toFixed(2);
684 dojo.byId('oils-selfck-cc-payment-summary').innerHTML =
685 dojo.string.substitute(
686 localeStrings.CC_PAYABLE_BALANCE,
690 oilsSelfckCCNumber.attr('value', '');
691 oilsSelfckCCMonth.attr('value', '01');
692 oilsSelfckCCYear.attr('value', new Date().getFullYear());
693 oilsSelfckCCFName.attr('value', this.patron.first_given_name());
694 oilsSelfckCCLName.attr('value', this.patron.family_name());
695 var addr = this.patron.billing_address() || this.patron.mailing_address();
698 oilsSelfckCCStreet.attr('value', addr.street1()+' '+addr.street2());
699 oilsSelfckCCCity.attr('value', addr.city());
700 oilsSelfckCCState.attr('value', addr.state());
701 oilsSelfckCCZip.attr('value', addr.post_code());
704 dojo.connect(oilsSelfckEditDetails, 'onChange',
714 function(dij) { dij.attr('disabled', !newVal); }
721 dojo.connect(oilsSelfckCCSubmit, 'onClick',
723 progressDialog.show(true);
724 self.sendCCPayment();
730 // In this form, this code only supports global on/off credit card
731 // payments and does not dissallow payments to transactions that started
732 // at remote locations or transactions that have accumulated billings at
733 // remote locations that dissalow credit card payments.
734 // TODO add per-transaction blocks for orgs that do not support CC payments
736 SelfCheckManager.prototype.sendCCPayment = function() {
739 userid : this.patron.id(),
740 payment_type : 'credit_card_payment',
744 number : oilsSelfckCCNumber.attr('value'),
745 expire_year : oilsSelfckCCYear.attr('value'),
746 expire_month : oilsSelfckCCMonth.attr('value'),
747 billing_first : oilsSelfckCCFName.attr('value'),
748 billing_last : oilsSelfckCCLName.attr('value'),
749 billing_address : oilsSelfckCCStreet.attr('value'),
750 billing_city : oilsSelfckCCCity.attr('value'),
751 billing_state : oilsSelfckCCState.attr('value'),
752 billing_zip : oilsSelfckCCZip.attr('value')
757 // find the selected transactions
759 dojo.query('[name=selector]', this.finesTbody),
763 input.getAttribute('xact'),
764 Number(input.getAttribute('balance_owed')).toFixed(2)
771 var resp = fieldmapper.standardRequest(
772 ['open-ils.circ', 'open-ils.circ.money.payment'],
773 {params : [this.authtoken, args]}
776 progressDialog.hide();
778 var evt = openils.Event.parse(resp);
782 this.updateFinesSummary();
783 this.drawFinesPage();
788 SelfCheckManager.prototype.drawFinesPage = function() {
790 // TODO add option to hid scanBox
791 // this.updateScanBox(...)
793 this.goToTab('fines');
794 progressDialog.show(true);
796 if(this.creditPayableBalance > 0 && this.orgSettings[SET_CC_PAYMENT_ALLOWED]) {
797 openils.Util.show('oils-selfck-pay-fines-link', 'inline');
800 this.finesTbody = dojo.byId('oils-selfck-fines-tbody');
801 if(!this.finesTemplate)
802 this.finesTemplate = this.finesTbody.removeChild(dojo.byId('oils-selfck-fines-row'));
803 while(this.finesTbody.childNodes[0])
804 this.finesTbody.removeChild(this.finesTbody.childNodes[0]);
806 // when user clicks on a selector checkbox, update the total owed
807 var updateSelected = function() {
810 dojo.query('[name=selector]', this.finesTbody),
813 total += Number(input.getAttribute('balance_owed'));
817 total = total.toFixed(2);
818 dojo.byId('oils-selfck-selected-total').innerHTML =
819 dojo.string.substitute(localeStrings.TOTAL_FINES_SELECTED, [total]);
822 // wire up the batch on/off selector
823 var sel = dojo.byId('oils-selfck-fines-selector');
824 sel.onchange = function() {
826 dojo.query('[name=selector]', this.finesTbody),
828 input.checked = sel.checked;
834 var handler = function(dataList) {
836 self.finesCount = dataList.length;
837 self.finesData = dataList;
839 for(var i in dataList) {
841 var data = dataList[i];
842 var row = self.finesTemplate.cloneNode(true);
843 var type = data.transaction.xact_type();
845 if(type == 'circulation') {
846 self.byName(row, 'type').innerHTML = type;
847 self.byName(row, 'details').innerHTML = data.record.title();
849 } else if(type == 'grocery') {
850 self.byName(row, 'type').innerHTML = 'Miscellaneous'; // Go ahead and head off any confusion around "grocery". TODO i18n
851 self.byName(row, 'details').innerHTML = data.transaction.last_billing_type();
854 self.byName(row, 'total_owed').innerHTML = data.transaction.total_owed();
855 self.byName(row, 'total_paid').innerHTML = data.transaction.total_paid();
856 self.byName(row, 'balance').innerHTML = data.transaction.balance_owed();
859 var selector = self.byName(row, 'selector')
860 selector.onchange = updateSelected;
861 selector.setAttribute('xact', data.transaction.id());
862 selector.setAttribute('balance_owed', data.transaction.balance_owed());
863 selector.checked = true;
865 self.finesTbody.appendChild(row);
872 fieldmapper.standardRequest(
873 ['open-ils.actor', 'open-ils.actor.user.transactions.have_balance.fleshed'],
875 params : [this.authtoken, this.patron.id()],
876 oncomplete : function(r) {
877 progressDialog.hide();
878 handler(openils.Util.readResponse(r));
884 SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
886 var resp = fieldmapper.standardRequest(
887 ['open-ils.circ', 'open-ils.circ.transit.abort'],
888 {params : [this.authtoken, {barcode : barcode}]}
891 // resp == 1 on success
892 if(openils.Event.parse(resp))
895 var resp = fieldmapper.standardRequest(
896 ['open-ils.circ', 'open-ils.circ.checkin.override'],
899 patron_id : this.patron.id(),
900 copy_barcode : barcode,
906 if(!resp.length) resp = [resp];
907 for(var i = 0; i < resp.length; i++) {
908 var tc = openils.Event.parse(resp[i]).textcode;
909 if(tc == 'SUCCESS' || tc == 'NO_CHANGE') {
920 * Check out a single item. If the item is already checked
921 * out to the patron, redirect to renew()
923 SelfCheckManager.prototype.checkout = function(barcode, override) {
925 this.prevCirc = null;
928 this.updateScanbox(null, true);
932 if(this.mockCheckouts) {
933 // if we're in mock-checkout mode, just insert another
934 // fake circ into the table and get out of here.
935 this.displayCheckout(this.mockCheckout, 'checkout');
939 // TODO see if it's a patron barcode
940 // TODO see if this item has already been checked out in this session
942 var method = 'open-ils.circ.checkout.full';
943 if(override) method += '.override';
945 console.log("Checkout out item " + barcode + " with method " + method);
947 var result = fieldmapper.standardRequest(
948 ['open-ils.circ', 'open-ils.circ.checkout.full'],
951 patron_id : this.patron.id(),
952 copy_barcode : barcode
957 var stat = this.handleXactResult('checkout', barcode, result);
960 this.checkout(barcode, true);
961 } else if(stat.doOver) {
962 this.checkout(barcode);
963 } else if(stat.renew) {
969 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
971 var displayText = '';
973 // If true, the display message is important enough to pop up. Whether or not
974 // an alert() actually occurs, depends on org unit settings
976 var sound = ''; // sound file reference
977 var payload = result.payload || {};
978 var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];
980 if(result.textcode == 'NO_SESSION') {
982 return this.logoutStaff();
984 } else if(result.textcode == 'SUCCESS') {
986 if(action == 'checkout') {
988 displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);
989 this.displayCheckout(result, 'checkout');
991 if(payload.holds_fulfilled && payload.holds_fulfilled.length) {
992 // A hold was fulfilled, update the hold numbers in the circ summary
993 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");
994 this.holdsSummary = null;
995 this.updateHoldsSummary();
998 this.updateCircSummary(true);
1000 } else if(action == 'renew') {
1002 displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);
1003 this.displayCheckout(result, 'renew');
1006 this.checkouts.push({circ : result.payload.circ.id()});
1007 sound = 'checkout-success';
1008 this.updateScanBox();
1010 } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {
1012 // Server says the item is already checked out. If it's checked out to the
1013 // current user, we may need to renew it.
1015 if(payload.old_circ) {
1018 old_circ refers to the previous checkout IFF it's for the same user.
1019 If no auto-renew interval is not defined, assume we should renew it
1020 If an auto-renew interval is defined and the payload comes back with
1021 auto_renew set to true, do the renewal. Otherwise, let the patron know
1022 the item is already checked out to them. */
1024 if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||
1025 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {
1026 this.prevCirc = payload.old_circ.id();
1027 return { renew : true };
1031 sound = 'checkout-failure';
1032 displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);
1036 if( // copy is marked lost. if configured to do so, check it in and try again.
1037 result.payload.copy &&
1038 result.payload.copy.status() == /* LOST */ 3 &&
1039 overrideEvents && overrideEvents.length &&
1040 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
1042 if(this.checkin(item)) {
1043 return { doOver : true };
1048 // item is checked out to some other user
1050 sound = 'checkout-failure';
1051 displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
1054 this.updateScanBox({select:true});
1059 if(overrideEvents && overrideEvents.length) {
1061 // see if the events we received are all in the list of
1062 // events to override
1064 if(!result.length) result = [result];
1066 var override = true;
1067 for(var i = 0; i < result.length; i++) {
1068 var match = overrideEvents.filter(
1069 function(e) { return (e == result[i].textcode); })[0];
1075 if(result[i].textcode == 'COPY_IN_TRANSIT') {
1076 // to override a transit, we have to abort the transit and check it in first
1077 if(this.checkin(item, true)) {
1078 return { doOver : true };
1087 return { override : true };
1090 this.updateScanBox({select : true});
1092 sound = 'checkout-failure';
1094 if(action == 'renew')
1095 this.checkouts.push({circ : this.prevCirc, renewal_failure : true});
1100 switch(result.textcode) {
1102 // TODO custom handler for blocking penalties
1104 case 'MAX_RENEWALS_REACHED' :
1105 displayText = dojo.string.substitute(
1106 localeStrings.MAX_RENEWALS, [item]);
1109 case 'ITEM_NOT_CATALOGED' :
1110 displayText = dojo.string.substitute(
1111 localeStrings.ITEM_NOT_CATALOGED, [item]);
1114 case 'OPEN_CIRCULATION_EXISTS' :
1115 displayText = dojo.string.substitute(
1116 localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
1121 console.error('Unhandled event ' + result.textcode);
1123 if(action == 'checkout' || action == 'renew') {
1124 displayText = dojo.string.substitute(
1125 localeStrings.GENERIC_CIRC_FAILURE, [item]);
1127 displayText = dojo.string.substitute(
1128 localeStrings.UNKNOWN_ERROR, [result.textcode]);
1133 this.handleAlert(displayText, popup, sound);
1141 SelfCheckManager.prototype.renew = function(barcode, override) {
1143 var method = 'open-ils.circ.renew';
1144 if(override) method += '.override';
1146 console.log("Renewing item " + barcode + " with method " + method);
1148 var result = fieldmapper.standardRequest(
1149 ['open-ils.circ', method],
1152 patron_id : this.patron.id(),
1153 copy_barcode : barcode
1158 console.log(js2JSON(result));
1160 var stat = this.handleXactResult('renew', barcode, result);
1163 this.renew(barcode, true);
1167 * Display the result of a checkout or renewal in the items out table
1169 SelfCheckManager.prototype.displayCheckout = function(evt, type, itemsOut) {
1171 var copy = evt.payload.copy;
1172 var record = evt.payload.record;
1173 var circ = evt.payload.circ;
1174 var row = this.circTemplate.cloneNode(true);
1177 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());
1180 this.byName(row, 'barcode').innerHTML = copy.barcode();
1181 this.byName(row, 'title').innerHTML = record.title();
1182 this.byName(row, 'author').innerHTML = record.author();
1183 this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();
1184 openils.Util.show(this.byName(row, type));
1186 var date = dojo.date.stamp.fromISOString(circ.due_date());
1187 this.byName(row, 'due_date').innerHTML =
1188 dojo.date.locale.format(date, {selector : 'date'});
1190 // put new circs at the top of the list
1191 var tbody = this.circTbody;
1192 if(itemsOut) tbody = this.itemsOutTbody;
1193 tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
1197 SelfCheckManager.prototype.byName = function(node, name) {
1198 return dojo.query('[name=' + name+']', node)[0];
1202 SelfCheckManager.prototype.initPrinter = function() {
1203 try { // Mozilla only
1204 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
1205 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
1206 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
1207 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
1208 var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
1210 pref.setBoolPref('print.always_print_silent', true);
1212 console.log("Unable to initialize auto-printing");
1217 * Print a receipt for this session's checkouts
1219 SelfCheckManager.prototype.printSessionReceipt = function(callback) {
1222 var circCtx = []; // circ context data. in this case, renewal_failure info
1224 // collect the circs and failure info
1228 circIds.push(blob.circ);
1229 circCtx.push({renewal_failure:blob.renewal_failure});
1237 'format.selfcheck.checkout',
1244 fieldmapper.standardRequest(
1245 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1249 oncomplete : function(r) {
1250 var resp = openils.Util.readResponse(r);
1251 var output = resp.template_output();
1253 self.printData(output.data(), self.checkouts.length, callback);
1255 var error = resp.error_output();
1257 throw new Error("Error creating receipt: " + error.data());
1259 throw new Error("No receipt data returned from server");
1267 SelfCheckManager.prototype.printData = function(data, numItems, callback) {
1269 var win = window.open('', '', 'resizable,width=700,height=500,scrollbars=1');
1270 win.document.body.innerHTML = data;
1274 * There is no way to know when the browser is done printing.
1275 * Make a best guess at when to close the print window by basing
1276 * the setTimeout wait on the number of items to be printed plus
1279 var sleepTime = 1000;
1281 sleepTime += (numItems / 2) * 1000;
1285 win.close(); // close the print window
1287 callback(); // fire optional post-print callback
1295 * Print a receipt for this user's items out
1297 SelfCheckManager.prototype.printItemsOutReceipt = function(callback) {
1299 if(!this.itemsOut.length) return;
1301 progressDialog.show(true);
1307 'format.selfcheck.items_out',
1313 fieldmapper.standardRequest(
1314 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1318 oncomplete : function(r) {
1319 progressDialog.hide();
1320 var resp = openils.Util.readResponse(r);
1321 var output = resp.template_output();
1323 self.printData(output.data(), self.itemsOut.length, callback);
1325 var error = resp.error_output();
1327 throw new Error("Error creating receipt: " + error.data());
1329 throw new Error("No receipt data returned from server");
1338 * Print a receipt for this user's items out
1340 SelfCheckManager.prototype.printHoldsReceipt = function(callback) {
1342 if(!this.holds.length) return;
1344 progressDialog.show(true);
1349 dojo.forEach(this.holds,
1351 holdIds.push(data.hold.id());
1352 if(data.status == 4) {
1353 holdData.push({ready : true});
1356 queue_position : data.queue_position,
1357 potential_copies : data.potential_copies
1367 'format.selfcheck.holds',
1374 fieldmapper.standardRequest(
1375 ['open-ils.circ', 'open-ils.circ.fire_hold_trigger_events'],
1379 oncomplete : function(r) {
1380 progressDialog.hide();
1381 var resp = openils.Util.readResponse(r);
1382 var output = resp.template_output();
1384 self.printData(output.data(), self.holds.length, callback);
1386 var error = resp.error_output();
1388 throw new Error("Error creating receipt: " + error.data());
1390 throw new Error("No receipt data returned from server");
1400 * Print a receipt for this user's items out
1402 SelfCheckManager.prototype.printFinesReceipt = function(callback) {
1404 progressDialog.show(true);
1410 'format.selfcheck.fines',
1416 fieldmapper.standardRequest(
1417 ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
1421 oncomplete : function(r) {
1422 progressDialog.hide();
1423 var resp = openils.Util.readResponse(r);
1424 var output = resp.template_output();
1426 self.printData(output.data(), self.finesCount, callback);
1428 var error = resp.error_output();
1430 throw new Error("Error creating receipt: " + error.data());
1432 throw new Error("No receipt data returned from server");
1444 * Logout the patron and return to the login page
1446 SelfCheckManager.prototype.logoutPatron = function(print) {
1447 if(print && this.checkouts.length) {
1448 this.printSessionReceipt(
1450 location.href = location.href;
1454 location.href = location.href;
1460 * Fire up the manager on page load
1462 openils.Util.addOnLoad(
1464 new SelfCheckManager().init();