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() {
104 self.goToTab("payment");
105 self.drawPayFinesPage(
106 self.patron, function() {
107 self.updateFinesSummary();
108 self.drawFinesPage();
112 'oils-selfck-nav-home' : function() { self.drawCircPage(); },
113 'oils-selfck-nav-logout' : function() { self.logoutPatron(); },
114 'oils-selfck-nav-logout-print' : function() { self.logoutPatron(true); },
115 'oils-selfck-items-out-details-link' : function() { self.drawItemsOutPage(); },
116 'oils-selfck-print-list-link' : function() { self.printList(); }
119 for(var id in linkHandlers)
120 dojo.connect(dojo.byId(id), 'onclick', linkHandlers[id]);
123 if(this.cgi.param('patron')) {
125 // Patron barcode via cgi param. Mainly used for debugging and
126 // only works if password is not required by policy
127 this.loginPatron(this.cgi.param('patron'));
130 this.drawLoginPage();
134 * To test printing, pass a URL param of 'testprint'. The value for the param
135 * should be a JSON string like so: [{circ:<circ_id>}, ...]
137 var testPrint = this.cgi.param('testprint');
139 this.checkouts = JSON2js(testPrint);
140 this.printSessionReceipt();
147 * Registers a new workstion
149 SelfCheckManager.prototype.registerWorkstation = function() {
151 oilsSelfckWsDialog.show();
153 new openils.User().buildPermOrgSelector(
154 'REGISTER_WORKSTATION',
155 oilsSelfckWsLocSelector,
161 dojo.connect(oilsSelfckWsSubmit, 'onClick',
164 oilsSelfckWsDialog.hide();
165 var name = oilsSelfckWsLocSelector.attr('displayedValue') + '-' + oilsSelfckWsName.attr('value');
167 var res = fieldmapper.standardRequest(
168 ['open-ils.actor', 'open-ils.actor.workstation.register'],
170 self.authtoken, name, oilsSelfckWsLocSelector.attr('value')
175 if(evt = openils.Event.parse(res)) {
176 if(evt.textcode == 'WORKSTATION_NAME_EXISTS') {
177 if(confirm(localeStrings.WORKSTATION_EXISTS)) {
178 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
180 self.registerWorkstation();
187 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
194 * Loads the org unit settings
196 SelfCheckManager.prototype.loadOrgSettings = function() {
198 var settings = fieldmapper.aou.fetchOrgSettingBatch(
199 this.staff.ws_ou(), [
204 SET_AUTO_OVERRIDE_EVENTS,
205 SET_PATRON_PASSWORD_REQUIRED,
206 SET_AUTO_RENEW_INTERVAL,
207 SET_WORKSTATION_REQUIRED,
208 SET_CC_PAYMENT_ALLOWED
214 this.orgSettings[k] = settings[k].value;
217 if(settings[SET_BARCODE_REGEX])
218 this.patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);
221 SelfCheckManager.prototype.drawLoginPage = function() {
224 var bcHandler = function(barcode) {
225 // handle patron barcode entry
227 if(self.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
229 // password is required. wire up the scan box to read it
231 msg : 'Please enter your password', // TODO i18n
232 handler : function(pw) { self.loginPatron(barcode, pw); },
237 // password is not required, go ahead and login
238 self.loginPatron(barcode);
243 msg : 'Please log in with your library barcode.', // TODO
251 SelfCheckManager.prototype.loginPatron = function(barcode, passwd) {
253 if(this.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
256 // would only happen in dev/debug mode when using the patron= param
257 alert('password required by org setting. remove patron= from URL');
261 // patron password is required. Verify it.
263 var res = fieldmapper.standardRequest(
264 ['open-ils.actor', 'open-ils.actor.verify_user_password'],
265 {params : [this.authtoken, barcode, null, hex_md5(passwd)]}
269 // user-not-found results in login failure
271 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
272 false, 'login-failure'
274 this.drawLoginPage();
279 // retrieve the fleshed user by barcode
280 this.patron = fieldmapper.standardRequest(
281 ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve_by_barcode'],
282 {params : [this.authtoken, barcode]}
285 var evt = openils.Event.parse(this.patron);
288 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
289 false, 'login-failure'
291 this.drawLoginPage();
295 this.handleAlert('', false, 'login-success');
296 dojo.byId('oils-selfck-user-banner').innerHTML =
297 dojo.string.substitute(localeStrings.WELCOME_BANNER, [this.patron.usrname()]);
303 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
305 console.log("Handling alert " + message);
307 dojo.byId('oils-selfck-status-div').innerHTML = message;
309 if(shouldPopup && this.orgSettings[SET_ALERT_POPUP])
312 if(sound && this.orgSettings[SET_ALERT_SOUND])
313 openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);
318 * Manages the main input box
319 * @param msg The context message to display with the box
320 * @param clearOnly Don't update the context message, just clear the value and re-focus
321 * @param handler Optional "on-enter" handler.
323 SelfCheckManager.prototype.updateScanBox = function(args) {
327 selfckScanBox.domNode.select();
329 selfckScanBox.attr('value', '');
333 selfckScanBox.domNode.setAttribute('type', 'password');
335 selfckScanBox.domNode.setAttribute('type', '');
339 selfckScanBox.attr('value', args.value);
342 dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;
344 if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {
345 dojo.disconnect(selfckScanBox._lastHandler);
349 selfckScanBox._lastHandler = dojo.connect(
353 if(e.keyCode != dojo.keys.ENTER)
355 args.handler(selfckScanBox.attr('value'));
360 selfckScanBox.focus();
364 * Sets up the checkout/renewal interface
366 SelfCheckManager.prototype.drawCircPage = function() {
368 openils.Util.show('oils-selfck-circ-tbody', 'table-row-group');
369 this.goToTab('checkout');
371 while(this.itemsOutTbody.childNodes[0])
372 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
376 msg : 'Please enter an item barcode', // TODO i18n
377 handler : function(barcode) { self.checkout(barcode); }
380 if(!this.circTemplate)
381 this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));
384 this.updateFinesSummary();
387 this.updateHoldsSummary();
390 this.updateCircSummary();
392 // render mock checkouts for debugging?
393 if(this.mockCheckouts) {
394 for(var i in [1,2,3])
395 this.displayCheckout(this.mockCheckout, 'checkout');
400 SelfCheckManager.prototype.updateFinesSummary = function() {
404 fieldmapper.standardRequest(
405 ['open-ils.actor', 'open-ils.actor.user.fines.summary'],
407 params : [this.authtoken, this.patron.id()],
408 oncomplete : function(r) {
410 var summary = openils.Util.readResponse(r);
412 dojo.byId('oils-selfck-fines-total').innerHTML =
413 dojo.string.substitute(
414 localeStrings.TOTAL_FINES_ACCOUNT,
415 [summary.balance_owed()]
418 self.creditPayableBalance = summary.balance_owed();
425 SelfCheckManager.prototype.drawItemsOutPage = function() {
426 openils.Util.hide('oils-selfck-circ-tbody');
428 this.goToTab('items_out');
430 while(this.itemsOutTbody.childNodes[0])
431 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
433 progressDialog.show(true);
436 fieldmapper.standardRequest(
437 ['open-ils.circ', 'open-ils.circ.actor.user.checked_out.atomic'],
440 params : [this.authtoken, this.patron.id()],
441 oncomplete : function(r) {
443 var resp = openils.Util.readResponse(r);
445 var circs = resp.sort(
447 if(a.circ.due_date() > b.circ.due_date())
453 progressDialog.hide();
458 self.itemsOut.push(circ.circ.id());
459 self.displayCheckout(
461 (circ.circ.parent_circ()) ? 'renew' : 'checkout',
472 SelfCheckManager.prototype.goToTab = function(name) {
475 openils.Util.hide('oils-selfck-fines-page');
476 openils.Util.hide('oils-selfck-payment-page');
477 openils.Util.hide('oils-selfck-holds-page');
478 openils.Util.hide('oils-selfck-circ-page');
479 openils.Util.hide('oils-selfck-pay-fines-link');
483 openils.Util.show('oils-selfck-circ-page');
486 openils.Util.show('oils-selfck-circ-page');
489 openils.Util.show('oils-selfck-holds-page');
492 openils.Util.show('oils-selfck-fines-page');
495 openils.Util.show('oils-selfck-payment-page');
501 SelfCheckManager.prototype.printList = function() {
502 switch(this.tabName) {
504 this.printSessionReceipt();
507 this.printItemsOutReceipt();
510 this.printHoldsReceipt();
513 this.printFinesReceipt();
518 SelfCheckManager.prototype.updateHoldsSummary = function() {
520 if(!this.holdsSummary) {
521 var summary = fieldmapper.standardRequest(
522 ['open-ils.circ', 'open-ils.circ.holds.user_summary'],
523 {params : [this.authtoken, this.patron.id()]}
526 this.holdsSummary = {};
527 this.holdsSummary.ready = Number(summary['4']);
528 this.holdsSummary.total = 0;
530 for(var i in summary)
531 this.holdsSummary.total += Number(summary[i]);
534 dojo.byId('oils-selfck-holds-total').innerHTML =
535 dojo.string.substitute(
536 localeStrings.TOTAL_HOLDS,
537 [this.holdsSummary.total]
540 dojo.byId('oils-selfck-holds-ready').innerHTML =
541 dojo.string.substitute(
542 localeStrings.HOLDS_READY_FOR_PICKUP,
543 [this.holdsSummary.ready]
548 SelfCheckManager.prototype.updateCircSummary = function(increment) {
550 if(!this.circSummary) {
552 var summary = fieldmapper.standardRequest(
553 ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],
554 {params : [this.authtoken, this.patron.id()]}
558 total : Number(summary.out) + Number(summary.overdue),
559 overdue : Number(summary.overdue),
565 // local checkout occurred. Add to the total and the session.
566 this.circSummary.total += 1;
567 this.circSummary.session += 1;
570 dojo.byId('oils-selfck-circ-account-total').innerHTML =
571 dojo.string.substitute(
572 localeStrings.TOTAL_ITEMS_ACCOUNT,
573 [this.circSummary.total]
576 dojo.byId('oils-selfck-circ-session-total').innerHTML =
577 dojo.string.substitute(
578 localeStrings.TOTAL_ITEMS_SESSION,
579 [this.circSummary.session]
584 SelfCheckManager.prototype.drawHoldsPage = function() {
586 // TODO add option to hid scanBox
587 // this.updateScanBox(...)
589 this.goToTab('holds');
591 this.holdTbody = dojo.byId('oils-selfck-hold-tbody');
592 if(!this.holdTemplate)
593 this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));
594 while(this.holdTbody.childNodes[0])
595 this.holdTbody.removeChild(this.holdTbody.childNodes[0]);
597 progressDialog.show(true);
600 fieldmapper.standardRequest( // fetch the hold IDs
602 ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],
604 params : [this.authtoken, this.patron.id()],
606 oncomplete : function(r) {
607 var ids = openils.Util.readResponse(r);
608 if(!ids || ids.length == 0) {
609 progressDialog.hide();
613 fieldmapper.standardRequest( // fetch the hold objects with fleshed details
614 ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve.atomic'],
616 params : [self.authtoken, ids],
618 oncomplete : function(rr) {
619 self.drawHolds(openils.Util.readResponse(rr));
629 * Fetch and add a single hold to the list of holds
631 SelfCheckManager.prototype.drawHolds = function(holds) {
634 // sort available holds to the top of the list
635 // followed by queue position order
637 if(a.status == 4) return -1;
638 if(a.queue_position < b.queue_position) return -1;
645 progressDialog.hide();
647 for(var i in holds) {
650 var row = this.holdTemplate.cloneNode(true);
652 if(data.mvr.isbn()) {
653 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());
656 this.byName(row, 'title').innerHTML = data.mvr.title();
657 this.byName(row, 'author').innerHTML = data.mvr.author();
659 if(data.status == 4) {
661 // hold is ready for pickup
662 this.byName(row, 'status').innerHTML = localeStrings.HOLD_STATUS_READY;
666 // hold is still pending
667 this.byName(row, 'status').innerHTML =
668 dojo.string.substitute(
669 localeStrings.HOLD_STATUS_WAITING,
670 [data.queue_position, data.potential_copies]
674 this.holdTbody.appendChild(row);
679 SelfCheckManager.prototype.drawFinesPage = function() {
681 // TODO add option to hid scanBox
682 // this.updateScanBox(...)
684 this.goToTab('fines');
685 progressDialog.show(true);
687 if(this.creditPayableBalance > 0 && this.orgSettings[SET_CC_PAYMENT_ALLOWED]) {
688 openils.Util.show('oils-selfck-pay-fines-link', 'inline');
691 this.finesTbody = dojo.byId('oils-selfck-fines-tbody');
692 if(!this.finesTemplate)
693 this.finesTemplate = this.finesTbody.removeChild(dojo.byId('oils-selfck-fines-row'));
694 while(this.finesTbody.childNodes[0])
695 this.finesTbody.removeChild(this.finesTbody.childNodes[0]);
697 // when user clicks on a selector checkbox, update the total owed
698 var updateSelected = function() {
701 dojo.query('[name=selector]', this.finesTbody),
704 total += Number(input.getAttribute('balance_owed'));
708 total = total.toFixed(2);
709 dojo.byId('oils-selfck-selected-total').innerHTML =
710 dojo.string.substitute(localeStrings.TOTAL_FINES_SELECTED, [total]);
713 // wire up the batch on/off selector
714 var sel = dojo.byId('oils-selfck-fines-selector');
715 sel.onchange = function() {
717 dojo.query('[name=selector]', this.finesTbody),
719 input.checked = sel.checked;
725 var handler = function(dataList) {
727 self.finesCount = dataList.length;
728 self.finesData = dataList;
730 for(var i in dataList) {
732 var data = dataList[i];
733 var row = self.finesTemplate.cloneNode(true);
734 var type = data.transaction.xact_type();
736 if(type == 'circulation') {
737 self.byName(row, 'type').innerHTML = type;
738 self.byName(row, 'details').innerHTML = data.record.title();
740 } else if(type == 'grocery') {
741 self.byName(row, 'type').innerHTML = 'Miscellaneous'; // Go ahead and head off any confusion around "grocery". TODO i18n
742 self.byName(row, 'details').innerHTML = data.transaction.last_billing_type();
745 self.byName(row, 'total_owed').innerHTML = data.transaction.total_owed();
746 self.byName(row, 'total_paid').innerHTML = data.transaction.total_paid();
747 self.byName(row, 'balance').innerHTML = data.transaction.balance_owed();
750 var selector = self.byName(row, 'selector')
751 selector.onchange = updateSelected;
752 selector.setAttribute('xact', data.transaction.id());
753 selector.setAttribute('balance_owed', data.transaction.balance_owed());
754 selector.checked = true;
756 self.finesTbody.appendChild(row);
763 fieldmapper.standardRequest(
764 ['open-ils.actor', 'open-ils.actor.user.transactions.have_balance.fleshed'],
766 params : [this.authtoken, this.patron.id()],
767 oncomplete : function(r) {
768 progressDialog.hide();
769 handler(openils.Util.readResponse(r));
775 SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
777 var resp = fieldmapper.standardRequest(
778 ['open-ils.circ', 'open-ils.circ.transit.abort'],
779 {params : [this.authtoken, {barcode : barcode}]}
782 // resp == 1 on success
783 if(openils.Event.parse(resp))
786 var resp = fieldmapper.standardRequest(
787 ['open-ils.circ', 'open-ils.circ.checkin.override'],
790 patron_id : this.patron.id(),
791 copy_barcode : barcode,
797 if(!resp.length) resp = [resp];
798 for(var i = 0; i < resp.length; i++) {
799 var tc = openils.Event.parse(resp[i]).textcode;
800 if(tc == 'SUCCESS' || tc == 'NO_CHANGE') {
811 * Check out a single item. If the item is already checked
812 * out to the patron, redirect to renew()
814 SelfCheckManager.prototype.checkout = function(barcode, override) {
816 this.prevCirc = null;
819 this.updateScanbox(null, true);
823 if(this.mockCheckouts) {
824 // if we're in mock-checkout mode, just insert another
825 // fake circ into the table and get out of here.
826 this.displayCheckout(this.mockCheckout, 'checkout');
830 // TODO see if it's a patron barcode
831 // TODO see if this item has already been checked out in this session
833 var method = 'open-ils.circ.checkout.full';
834 if(override) method += '.override';
836 console.log("Checkout out item " + barcode + " with method " + method);
838 var result = fieldmapper.standardRequest(
839 ['open-ils.circ', 'open-ils.circ.checkout.full'],
842 patron_id : this.patron.id(),
843 copy_barcode : barcode
848 var stat = this.handleXactResult('checkout', barcode, result);
851 this.checkout(barcode, true);
852 } else if(stat.doOver) {
853 this.checkout(barcode);
854 } else if(stat.renew) {
860 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
862 var displayText = '';
864 // If true, the display message is important enough to pop up. Whether or not
865 // an alert() actually occurs, depends on org unit settings
867 var sound = ''; // sound file reference
868 var payload = result.payload || {};
869 var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];
871 if(result.textcode == 'NO_SESSION') {
873 return this.logoutStaff();
875 } else if(result.textcode == 'SUCCESS') {
877 if(action == 'checkout') {
879 displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);
880 this.displayCheckout(result, 'checkout');
882 if(payload.holds_fulfilled && payload.holds_fulfilled.length) {
883 // A hold was fulfilled, update the hold numbers in the circ summary
884 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");
885 this.holdsSummary = null;
886 this.updateHoldsSummary();
889 this.updateCircSummary(true);
891 } else if(action == 'renew') {
893 displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);
894 this.displayCheckout(result, 'renew');
897 this.checkouts.push({circ : result.payload.circ.id()});
898 sound = 'checkout-success';
899 this.updateScanBox();
901 } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {
903 // Server says the item is already checked out. If it's checked out to the
904 // current user, we may need to renew it.
906 if(payload.old_circ) {
909 old_circ refers to the previous checkout IFF it's for the same user.
910 If no auto-renew interval is not defined, assume we should renew it
911 If an auto-renew interval is defined and the payload comes back with
912 auto_renew set to true, do the renewal. Otherwise, let the patron know
913 the item is already checked out to them. */
915 if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||
916 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {
917 this.prevCirc = payload.old_circ.id();
918 return { renew : true };
922 sound = 'checkout-failure';
923 displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);
927 if( // copy is marked lost. if configured to do so, check it in and try again.
928 result.payload.copy &&
929 result.payload.copy.status() == /* LOST */ 3 &&
930 overrideEvents && overrideEvents.length &&
931 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
933 if(this.checkin(item)) {
934 return { doOver : true };
939 // item is checked out to some other user
941 sound = 'checkout-failure';
942 displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
945 this.updateScanBox({select:true});
950 if(overrideEvents && overrideEvents.length) {
952 // see if the events we received are all in the list of
953 // events to override
955 if(!result.length) result = [result];
958 for(var i = 0; i < result.length; i++) {
959 var match = overrideEvents.filter(
960 function(e) { return (e == result[i].textcode); })[0];
966 if(result[i].textcode == 'COPY_IN_TRANSIT') {
967 // to override a transit, we have to abort the transit and check it in first
968 if(this.checkin(item, true)) {
969 return { doOver : true };
978 return { override : true };
981 this.updateScanBox({select : true});
983 sound = 'checkout-failure';
985 if(action == 'renew')
986 this.checkouts.push({circ : this.prevCirc, renewal_failure : true});
991 switch(result.textcode) {
993 // TODO custom handler for blocking penalties
995 case 'MAX_RENEWALS_REACHED' :
996 displayText = dojo.string.substitute(
997 localeStrings.MAX_RENEWALS, [item]);
1000 case 'ITEM_NOT_CATALOGED' :
1001 displayText = dojo.string.substitute(
1002 localeStrings.ITEM_NOT_CATALOGED, [item]);
1005 case 'OPEN_CIRCULATION_EXISTS' :
1006 displayText = dojo.string.substitute(
1007 localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
1012 console.error('Unhandled event ' + result.textcode);
1014 if(action == 'checkout' || action == 'renew') {
1015 displayText = dojo.string.substitute(
1016 localeStrings.GENERIC_CIRC_FAILURE, [item]);
1018 displayText = dojo.string.substitute(
1019 localeStrings.UNKNOWN_ERROR, [result.textcode]);
1024 this.handleAlert(displayText, popup, sound);
1032 SelfCheckManager.prototype.renew = function(barcode, override) {
1034 var method = 'open-ils.circ.renew';
1035 if(override) method += '.override';
1037 console.log("Renewing item " + barcode + " with method " + method);
1039 var result = fieldmapper.standardRequest(
1040 ['open-ils.circ', method],
1043 patron_id : this.patron.id(),
1044 copy_barcode : barcode
1049 console.log(js2JSON(result));
1051 var stat = this.handleXactResult('renew', barcode, result);
1054 this.renew(barcode, true);
1058 * Display the result of a checkout or renewal in the items out table
1060 SelfCheckManager.prototype.displayCheckout = function(evt, type, itemsOut) {
1062 var copy = evt.payload.copy;
1063 var record = evt.payload.record;
1064 var circ = evt.payload.circ;
1065 var row = this.circTemplate.cloneNode(true);
1068 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());
1071 this.byName(row, 'barcode').innerHTML = copy.barcode();
1072 this.byName(row, 'title').innerHTML = record.title();
1073 this.byName(row, 'author').innerHTML = record.author();
1074 this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();
1075 openils.Util.show(this.byName(row, type));
1077 var date = dojo.date.stamp.fromISOString(circ.due_date());
1078 this.byName(row, 'due_date').innerHTML =
1079 dojo.date.locale.format(date, {selector : 'date'});
1081 // put new circs at the top of the list
1082 var tbody = this.circTbody;
1083 if(itemsOut) tbody = this.itemsOutTbody;
1084 tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
1088 SelfCheckManager.prototype.byName = function(node, name) {
1089 return dojo.query('[name=' + name+']', node)[0];
1093 SelfCheckManager.prototype.initPrinter = function() {
1094 try { // Mozilla only
1095 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
1096 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
1097 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
1098 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
1099 var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
1101 pref.setBoolPref('print.always_print_silent', true);
1103 console.log("Unable to initialize auto-printing");
1108 * Print a receipt for this session's checkouts
1110 SelfCheckManager.prototype.printSessionReceipt = function(callback) {
1113 var circCtx = []; // circ context data. in this case, renewal_failure info
1115 // collect the circs and failure info
1119 circIds.push(blob.circ);
1120 circCtx.push({renewal_failure:blob.renewal_failure});
1128 'format.selfcheck.checkout',
1135 fieldmapper.standardRequest(
1136 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1140 oncomplete : function(r) {
1141 var resp = openils.Util.readResponse(r);
1142 var output = resp.template_output();
1144 self.printData(output.data(), self.checkouts.length, callback);
1146 var error = resp.error_output();
1148 throw new Error("Error creating receipt: " + error.data());
1150 throw new Error("No receipt data returned from server");
1158 SelfCheckManager.prototype.printData = function(data, numItems, callback) {
1160 var win = window.open('', '', 'resizable,width=700,height=500,scrollbars=1');
1161 win.document.body.innerHTML = data;
1165 * There is no way to know when the browser is done printing.
1166 * Make a best guess at when to close the print window by basing
1167 * the setTimeout wait on the number of items to be printed plus
1170 var sleepTime = 1000;
1172 sleepTime += (numItems / 2) * 1000;
1176 win.close(); // close the print window
1178 callback(); // fire optional post-print callback
1186 * Print a receipt for this user's items out
1188 SelfCheckManager.prototype.printItemsOutReceipt = function(callback) {
1190 if(!this.itemsOut.length) return;
1192 progressDialog.show(true);
1198 'format.selfcheck.items_out',
1204 fieldmapper.standardRequest(
1205 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1209 oncomplete : function(r) {
1210 progressDialog.hide();
1211 var resp = openils.Util.readResponse(r);
1212 var output = resp.template_output();
1214 self.printData(output.data(), self.itemsOut.length, callback);
1216 var error = resp.error_output();
1218 throw new Error("Error creating receipt: " + error.data());
1220 throw new Error("No receipt data returned from server");
1229 * Print a receipt for this user's items out
1231 SelfCheckManager.prototype.printHoldsReceipt = function(callback) {
1233 if(!this.holds.length) return;
1235 progressDialog.show(true);
1240 dojo.forEach(this.holds,
1242 holdIds.push(data.hold.id());
1243 if(data.status == 4) {
1244 holdData.push({ready : true});
1247 queue_position : data.queue_position,
1248 potential_copies : data.potential_copies
1258 'format.selfcheck.holds',
1265 fieldmapper.standardRequest(
1266 ['open-ils.circ', 'open-ils.circ.fire_hold_trigger_events'],
1270 oncomplete : function(r) {
1271 progressDialog.hide();
1272 var resp = openils.Util.readResponse(r);
1273 var output = resp.template_output();
1275 self.printData(output.data(), self.holds.length, callback);
1277 var error = resp.error_output();
1279 throw new Error("Error creating receipt: " + error.data());
1281 throw new Error("No receipt data returned from server");
1291 * Print a receipt for this user's items out
1293 SelfCheckManager.prototype.printFinesReceipt = function(callback) {
1295 progressDialog.show(true);
1301 'format.selfcheck.fines',
1307 fieldmapper.standardRequest(
1308 ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
1312 oncomplete : function(r) {
1313 progressDialog.hide();
1314 var resp = openils.Util.readResponse(r);
1315 var output = resp.template_output();
1317 self.printData(output.data(), self.finesCount, callback);
1319 var error = resp.error_output();
1321 throw new Error("Error creating receipt: " + error.data());
1323 throw new Error("No receipt data returned from server");
1335 * Logout the patron and return to the login page
1337 SelfCheckManager.prototype.logoutPatron = function(print) {
1338 if(print && this.checkouts.length) {
1339 this.printSessionReceipt(
1341 location.href = location.href;
1345 location.href = location.href;
1351 * Fire up the manager on page load
1353 openils.Util.addOnLoad(
1355 new SelfCheckManager().init();