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 = 'Welcome, ' + this.patron.first_given_name(); // TODO i18n
302 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
304 console.log("Handling alert " + message);
306 dojo.byId('oils-selfck-status-div').innerHTML = message;
308 if(shouldPopup && this.orgSettings[SET_ALERT_POPUP])
311 if(sound && this.orgSettings[SET_ALERT_SOUND])
312 openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);
317 * Manages the main input box
318 * @param msg The context message to display with the box
319 * @param clearOnly Don't update the context message, just clear the value and re-focus
320 * @param handler Optional "on-enter" handler.
322 SelfCheckManager.prototype.updateScanBox = function(args) {
326 selfckScanBox.domNode.select();
328 selfckScanBox.attr('value', '');
332 selfckScanBox.domNode.setAttribute('type', 'password');
334 selfckScanBox.domNode.setAttribute('type', '');
338 selfckScanBox.attr('value', args.value);
341 dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;
343 if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {
344 dojo.disconnect(selfckScanBox._lastHandler);
348 selfckScanBox._lastHandler = dojo.connect(
352 if(e.keyCode != dojo.keys.ENTER)
354 args.handler(selfckScanBox.attr('value'));
359 selfckScanBox.focus();
363 * Sets up the checkout/renewal interface
365 SelfCheckManager.prototype.drawCircPage = function() {
367 openils.Util.show('oils-selfck-circ-tbody', 'table-row-group');
368 this.goToTab('checkout');
370 while(this.itemsOutTbody.childNodes[0])
371 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
375 msg : 'Please enter an item barcode', // TODO i18n
376 handler : function(barcode) { self.checkout(barcode); }
379 if(!this.circTemplate)
380 this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));
383 this.updateFinesSummary();
386 this.updateHoldsSummary();
389 this.updateCircSummary();
391 // render mock checkouts for debugging?
392 if(this.mockCheckouts) {
393 for(var i in [1,2,3])
394 this.displayCheckout(this.mockCheckout, 'checkout');
399 SelfCheckManager.prototype.updateFinesSummary = function() {
403 fieldmapper.standardRequest(
404 ['open-ils.actor', 'open-ils.actor.user.fines.summary'],
406 params : [this.authtoken, this.patron.id()],
407 oncomplete : function(r) {
409 var summary = openils.Util.readResponse(r);
411 dojo.byId('oils-selfck-fines-total').innerHTML =
412 dojo.string.substitute(
413 localeStrings.TOTAL_FINES_ACCOUNT,
414 [summary.balance_owed()]
417 self.creditPayableBalance = summary.balance_owed();
424 SelfCheckManager.prototype.drawItemsOutPage = function() {
425 openils.Util.hide('oils-selfck-circ-tbody');
427 this.goToTab('items_out');
429 while(this.itemsOutTbody.childNodes[0])
430 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
432 progressDialog.show(true);
435 fieldmapper.standardRequest(
436 ['open-ils.circ', 'open-ils.circ.actor.user.checked_out.atomic'],
439 params : [this.authtoken, this.patron.id()],
440 oncomplete : function(r) {
442 var resp = openils.Util.readResponse(r);
444 var circs = resp.sort(
446 if(a.circ.due_date() > b.circ.due_date())
452 progressDialog.hide();
457 self.itemsOut.push(circ.circ.id());
458 self.displayCheckout(
460 (circ.circ.parent_circ()) ? 'renew' : 'checkout',
471 SelfCheckManager.prototype.goToTab = function(name) {
474 openils.Util.hide('oils-selfck-fines-page');
475 openils.Util.hide('oils-selfck-payment-page');
476 openils.Util.hide('oils-selfck-holds-page');
477 openils.Util.hide('oils-selfck-circ-page');
478 openils.Util.hide('oils-selfck-pay-fines-link');
482 openils.Util.show('oils-selfck-circ-page');
485 openils.Util.show('oils-selfck-circ-page');
488 openils.Util.show('oils-selfck-holds-page');
491 openils.Util.show('oils-selfck-fines-page');
494 openils.Util.show('oils-selfck-payment-page');
500 SelfCheckManager.prototype.printList = function() {
501 switch(this.tabName) {
503 this.printSessionReceipt();
506 this.printItemsOutReceipt();
509 this.printHoldsReceipt();
512 this.printFinesReceipt();
517 SelfCheckManager.prototype.updateHoldsSummary = function() {
519 if(!this.holdsSummary) {
520 var summary = fieldmapper.standardRequest(
521 ['open-ils.circ', 'open-ils.circ.holds.user_summary'],
522 {params : [this.authtoken, this.patron.id()]}
525 this.holdsSummary = {};
526 this.holdsSummary.ready = Number(summary['4']);
527 this.holdsSummary.total = 0;
529 for(var i in summary)
530 this.holdsSummary.total += Number(summary[i]);
533 dojo.byId('oils-selfck-holds-total').innerHTML =
534 dojo.string.substitute(
535 localeStrings.TOTAL_HOLDS,
536 [this.holdsSummary.total]
539 dojo.byId('oils-selfck-holds-ready').innerHTML =
540 dojo.string.substitute(
541 localeStrings.HOLDS_READY_FOR_PICKUP,
542 [this.holdsSummary.ready]
547 SelfCheckManager.prototype.updateCircSummary = function(increment) {
549 if(!this.circSummary) {
551 var summary = fieldmapper.standardRequest(
552 ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],
553 {params : [this.authtoken, this.patron.id()]}
557 total : Number(summary.out) + Number(summary.overdue),
558 overdue : Number(summary.overdue),
564 // local checkout occurred. Add to the total and the session.
565 this.circSummary.total += 1;
566 this.circSummary.session += 1;
569 dojo.byId('oils-selfck-circ-account-total').innerHTML =
570 dojo.string.substitute(
571 localeStrings.TOTAL_ITEMS_ACCOUNT,
572 [this.circSummary.total]
575 dojo.byId('oils-selfck-circ-session-total').innerHTML =
576 dojo.string.substitute(
577 localeStrings.TOTAL_ITEMS_SESSION,
578 [this.circSummary.session]
583 SelfCheckManager.prototype.drawHoldsPage = function() {
585 // TODO add option to hid scanBox
586 // this.updateScanBox(...)
588 this.goToTab('holds');
590 this.holdTbody = dojo.byId('oils-selfck-hold-tbody');
591 if(!this.holdTemplate)
592 this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));
593 while(this.holdTbody.childNodes[0])
594 this.holdTbody.removeChild(this.holdTbody.childNodes[0]);
596 progressDialog.show(true);
599 fieldmapper.standardRequest( // fetch the hold IDs
601 ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],
603 params : [this.authtoken, this.patron.id()],
605 oncomplete : function(r) {
606 var ids = openils.Util.readResponse(r);
607 if(!ids || ids.length == 0) {
608 progressDialog.hide();
612 fieldmapper.standardRequest( // fetch the hold objects with fleshed details
613 ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve.atomic'],
615 params : [self.authtoken, ids],
617 oncomplete : function(rr) {
618 self.drawHolds(openils.Util.readResponse(rr));
628 * Fetch and add a single hold to the list of holds
630 SelfCheckManager.prototype.drawHolds = function(holds) {
633 // sort available holds to the top of the list
634 // followed by queue position order
636 if(a.status == 4) return -1;
637 if(a.queue_position < b.queue_position) return -1;
644 progressDialog.hide();
646 for(var i in holds) {
649 var row = this.holdTemplate.cloneNode(true);
651 if(data.mvr.isbn()) {
652 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());
655 this.byName(row, 'title').innerHTML = data.mvr.title();
656 this.byName(row, 'author').innerHTML = data.mvr.author();
658 if(data.status == 4) {
660 // hold is ready for pickup
661 this.byName(row, 'status').innerHTML = localeStrings.HOLD_STATUS_READY;
665 // hold is still pending
666 this.byName(row, 'status').innerHTML =
667 dojo.string.substitute(
668 localeStrings.HOLD_STATUS_WAITING,
669 [data.queue_position, data.potential_copies]
673 this.holdTbody.appendChild(row);
678 SelfCheckManager.prototype.drawFinesPage = function() {
680 // TODO add option to hid scanBox
681 // this.updateScanBox(...)
683 this.goToTab('fines');
684 progressDialog.show(true);
686 if(this.creditPayableBalance > 0 && this.orgSettings[SET_CC_PAYMENT_ALLOWED]) {
687 openils.Util.show('oils-selfck-pay-fines-link', 'inline');
690 this.finesTbody = dojo.byId('oils-selfck-fines-tbody');
691 if(!this.finesTemplate)
692 this.finesTemplate = this.finesTbody.removeChild(dojo.byId('oils-selfck-fines-row'));
693 while(this.finesTbody.childNodes[0])
694 this.finesTbody.removeChild(this.finesTbody.childNodes[0]);
696 // when user clicks on a selector checkbox, update the total owed
697 var updateSelected = function() {
700 dojo.query('[name=selector]', this.finesTbody),
703 total += Number(input.getAttribute('balance_owed'));
707 total = total.toFixed(2);
708 dojo.byId('oils-selfck-selected-total').innerHTML =
709 dojo.string.substitute(localeStrings.TOTAL_FINES_SELECTED, [total]);
712 // wire up the batch on/off selector
713 var sel = dojo.byId('oils-selfck-fines-selector');
714 sel.onchange = function() {
716 dojo.query('[name=selector]', this.finesTbody),
718 input.checked = sel.checked;
724 var handler = function(dataList) {
726 self.finesCount = dataList.length;
727 self.finesData = dataList;
729 for(var i in dataList) {
731 var data = dataList[i];
732 var row = self.finesTemplate.cloneNode(true);
733 var type = data.transaction.xact_type();
735 if(type == 'circulation') {
736 self.byName(row, 'type').innerHTML = type;
737 self.byName(row, 'details').innerHTML = data.record.title();
739 } else if(type == 'grocery') {
740 self.byName(row, 'type').innerHTML = 'Miscellaneous'; // Go ahead and head off any confusion around "grocery". TODO i18n
741 self.byName(row, 'details').innerHTML = data.transaction.last_billing_type();
744 self.byName(row, 'total_owed').innerHTML = data.transaction.total_owed();
745 self.byName(row, 'total_paid').innerHTML = data.transaction.total_paid();
746 self.byName(row, 'balance').innerHTML = data.transaction.balance_owed();
749 var selector = self.byName(row, 'selector')
750 selector.onchange = updateSelected;
751 selector.setAttribute('xact', data.transaction.id());
752 selector.setAttribute('balance_owed', data.transaction.balance_owed());
753 selector.checked = true;
755 self.finesTbody.appendChild(row);
762 fieldmapper.standardRequest(
763 ['open-ils.actor', 'open-ils.actor.user.transactions.have_balance.fleshed'],
765 params : [this.authtoken, this.patron.id()],
766 oncomplete : function(r) {
767 progressDialog.hide();
768 handler(openils.Util.readResponse(r));
774 SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
776 var resp = fieldmapper.standardRequest(
777 ['open-ils.circ', 'open-ils.circ.transit.abort'],
778 {params : [this.authtoken, {barcode : barcode}]}
781 // resp == 1 on success
782 if(openils.Event.parse(resp))
785 var resp = fieldmapper.standardRequest(
786 ['open-ils.circ', 'open-ils.circ.checkin.override'],
789 patron_id : this.patron.id(),
790 copy_barcode : barcode,
796 if(!resp.length) resp = [resp];
797 for(var i = 0; i < resp.length; i++) {
798 var tc = openils.Event.parse(resp[i]).textcode;
799 if(tc == 'SUCCESS' || tc == 'NO_CHANGE') {
810 * Check out a single item. If the item is already checked
811 * out to the patron, redirect to renew()
813 SelfCheckManager.prototype.checkout = function(barcode, override) {
815 this.prevCirc = null;
818 this.updateScanbox(null, true);
822 if(this.mockCheckouts) {
823 // if we're in mock-checkout mode, just insert another
824 // fake circ into the table and get out of here.
825 this.displayCheckout(this.mockCheckout, 'checkout');
829 // TODO see if it's a patron barcode
830 // TODO see if this item has already been checked out in this session
832 var method = 'open-ils.circ.checkout.full';
833 if(override) method += '.override';
835 console.log("Checkout out item " + barcode + " with method " + method);
837 var result = fieldmapper.standardRequest(
838 ['open-ils.circ', 'open-ils.circ.checkout.full'],
841 patron_id : this.patron.id(),
842 copy_barcode : barcode
847 var stat = this.handleXactResult('checkout', barcode, result);
850 this.checkout(barcode, true);
851 } else if(stat.doOver) {
852 this.checkout(barcode);
853 } else if(stat.renew) {
859 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
861 var displayText = '';
863 // If true, the display message is important enough to pop up. Whether or not
864 // an alert() actually occurs, depends on org unit settings
866 var sound = ''; // sound file reference
867 var payload = result.payload || {};
868 var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];
870 if(result.textcode == 'NO_SESSION') {
872 return this.logoutStaff();
874 } else if(result.textcode == 'SUCCESS') {
876 if(action == 'checkout') {
878 displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);
879 this.displayCheckout(result, 'checkout');
881 if(payload.holds_fulfilled && payload.holds_fulfilled.length) {
882 // A hold was fulfilled, update the hold numbers in the circ summary
883 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");
884 this.holdsSummary = null;
885 this.updateHoldsSummary();
888 this.updateCircSummary(true);
890 } else if(action == 'renew') {
892 displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);
893 this.displayCheckout(result, 'renew');
896 this.checkouts.push({circ : result.payload.circ.id()});
897 sound = 'checkout-success';
898 this.updateScanBox();
900 } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {
902 // Server says the item is already checked out. If it's checked out to the
903 // current user, we may need to renew it.
905 if(payload.old_circ) {
908 old_circ refers to the previous checkout IFF it's for the same user.
909 If no auto-renew interval is not defined, assume we should renew it
910 If an auto-renew interval is defined and the payload comes back with
911 auto_renew set to true, do the renewal. Otherwise, let the patron know
912 the item is already checked out to them. */
914 if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||
915 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {
916 this.prevCirc = payload.old_circ.id();
917 return { renew : true };
921 sound = 'checkout-failure';
922 displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);
926 if( // copy is marked lost. if configured to do so, check it in and try again.
927 result.payload.copy &&
928 result.payload.copy.status() == /* LOST */ 3 &&
929 overrideEvents && overrideEvents.length &&
930 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
932 if(this.checkin(item)) {
933 return { doOver : true };
938 // item is checked out to some other user
940 sound = 'checkout-failure';
941 displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
944 this.updateScanBox({select:true});
949 if(overrideEvents && overrideEvents.length) {
951 // see if the events we received are all in the list of
952 // events to override
954 if(!result.length) result = [result];
957 for(var i = 0; i < result.length; i++) {
958 var match = overrideEvents.filter(
959 function(e) { return (e == result[i].textcode); })[0];
965 if(result[i].textcode == 'COPY_IN_TRANSIT') {
966 // to override a transit, we have to abort the transit and check it in first
967 if(this.checkin(item, true)) {
968 return { doOver : true };
977 return { override : true };
980 this.updateScanBox({select : true});
982 sound = 'checkout-failure';
984 if(action == 'renew')
985 this.checkouts.push({circ : this.prevCirc, renewal_failure : true});
990 switch(result.textcode) {
992 // TODO custom handler for blocking penalties
994 case 'MAX_RENEWALS_REACHED' :
995 displayText = dojo.string.substitute(
996 localeStrings.MAX_RENEWALS, [item]);
999 case 'ITEM_NOT_CATALOGED' :
1000 displayText = dojo.string.substitute(
1001 localeStrings.ITEM_NOT_CATALOGED, [item]);
1004 case 'OPEN_CIRCULATION_EXISTS' :
1005 displayText = dojo.string.substitute(
1006 localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
1011 console.error('Unhandled event ' + result.textcode);
1013 if(action == 'checkout' || action == 'renew') {
1014 displayText = dojo.string.substitute(
1015 localeStrings.GENERIC_CIRC_FAILURE, [item]);
1017 displayText = dojo.string.substitute(
1018 localeStrings.UNKNOWN_ERROR, [result.textcode]);
1023 this.handleAlert(displayText, popup, sound);
1031 SelfCheckManager.prototype.renew = function(barcode, override) {
1033 var method = 'open-ils.circ.renew';
1034 if(override) method += '.override';
1036 console.log("Renewing item " + barcode + " with method " + method);
1038 var result = fieldmapper.standardRequest(
1039 ['open-ils.circ', method],
1042 patron_id : this.patron.id(),
1043 copy_barcode : barcode
1048 console.log(js2JSON(result));
1050 var stat = this.handleXactResult('renew', barcode, result);
1053 this.renew(barcode, true);
1057 * Display the result of a checkout or renewal in the items out table
1059 SelfCheckManager.prototype.displayCheckout = function(evt, type, itemsOut) {
1061 var copy = evt.payload.copy;
1062 var record = evt.payload.record;
1063 var circ = evt.payload.circ;
1064 var row = this.circTemplate.cloneNode(true);
1067 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());
1070 this.byName(row, 'barcode').innerHTML = copy.barcode();
1071 this.byName(row, 'title').innerHTML = record.title();
1072 this.byName(row, 'author').innerHTML = record.author();
1073 this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();
1074 openils.Util.show(this.byName(row, type));
1076 var date = dojo.date.stamp.fromISOString(circ.due_date());
1077 this.byName(row, 'due_date').innerHTML =
1078 dojo.date.locale.format(date, {selector : 'date'});
1080 // put new circs at the top of the list
1081 var tbody = this.circTbody;
1082 if(itemsOut) tbody = this.itemsOutTbody;
1083 tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
1087 SelfCheckManager.prototype.byName = function(node, name) {
1088 return dojo.query('[name=' + name+']', node)[0];
1092 SelfCheckManager.prototype.initPrinter = function() {
1093 try { // Mozilla only
1094 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
1095 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
1096 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
1097 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
1098 var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
1100 pref.setBoolPref('print.always_print_silent', true);
1102 console.log("Unable to initialize auto-printing");
1107 * Print a receipt for this session's checkouts
1109 SelfCheckManager.prototype.printSessionReceipt = function(callback) {
1112 var circCtx = []; // circ context data. in this case, renewal_failure info
1114 // collect the circs and failure info
1118 circIds.push(blob.circ);
1119 circCtx.push({renewal_failure:blob.renewal_failure});
1127 'format.selfcheck.checkout',
1134 fieldmapper.standardRequest(
1135 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1139 oncomplete : function(r) {
1140 var resp = openils.Util.readResponse(r);
1141 var output = resp.template_output();
1143 self.printData(output.data(), self.checkouts.length, callback);
1145 var error = resp.error_output();
1147 throw new Error("Error creating receipt: " + error.data());
1149 throw new Error("No receipt data returned from server");
1157 SelfCheckManager.prototype.printData = function(data, numItems, callback) {
1159 var win = window.open('', '', 'resizable,width=700,height=500,scrollbars=1');
1160 win.document.body.innerHTML = data;
1164 * There is no way to know when the browser is done printing.
1165 * Make a best guess at when to close the print window by basing
1166 * the setTimeout wait on the number of items to be printed plus
1169 var sleepTime = 1000;
1171 sleepTime += (numItems / 2) * 1000;
1175 win.close(); // close the print window
1177 callback(); // fire optional post-print callback
1185 * Print a receipt for this user's items out
1187 SelfCheckManager.prototype.printItemsOutReceipt = function(callback) {
1189 if(!this.itemsOut.length) return;
1191 progressDialog.show(true);
1197 'format.selfcheck.items_out',
1203 fieldmapper.standardRequest(
1204 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1208 oncomplete : function(r) {
1209 progressDialog.hide();
1210 var resp = openils.Util.readResponse(r);
1211 var output = resp.template_output();
1213 self.printData(output.data(), self.itemsOut.length, callback);
1215 var error = resp.error_output();
1217 throw new Error("Error creating receipt: " + error.data());
1219 throw new Error("No receipt data returned from server");
1228 * Print a receipt for this user's items out
1230 SelfCheckManager.prototype.printHoldsReceipt = function(callback) {
1232 if(!this.holds.length) return;
1234 progressDialog.show(true);
1239 dojo.forEach(this.holds,
1241 holdIds.push(data.hold.id());
1242 if(data.status == 4) {
1243 holdData.push({ready : true});
1246 queue_position : data.queue_position,
1247 potential_copies : data.potential_copies
1257 'format.selfcheck.holds',
1264 fieldmapper.standardRequest(
1265 ['open-ils.circ', 'open-ils.circ.fire_hold_trigger_events'],
1269 oncomplete : function(r) {
1270 progressDialog.hide();
1271 var resp = openils.Util.readResponse(r);
1272 var output = resp.template_output();
1274 self.printData(output.data(), self.holds.length, callback);
1276 var error = resp.error_output();
1278 throw new Error("Error creating receipt: " + error.data());
1280 throw new Error("No receipt data returned from server");
1290 * Print a receipt for this user's items out
1292 SelfCheckManager.prototype.printFinesReceipt = function(callback) {
1294 progressDialog.show(true);
1300 'format.selfcheck.fines',
1306 fieldmapper.standardRequest(
1307 ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
1311 oncomplete : function(r) {
1312 progressDialog.hide();
1313 var resp = openils.Util.readResponse(r);
1314 var output = resp.template_output();
1316 self.printData(output.data(), self.finesCount, callback);
1318 var error = resp.error_output();
1320 throw new Error("Error creating receipt: " + error.data());
1322 throw new Error("No receipt data returned from server");
1334 * Logout the patron and return to the login page
1336 SelfCheckManager.prototype.logoutPatron = function(print) {
1337 if(print && this.checkouts.length) {
1338 this.printSessionReceipt(
1340 location.href = location.href;
1344 location.href = location.href;
1350 * Fire up the manager on page load
1352 openils.Util.addOnLoad(
1354 new SelfCheckManager().init();