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';
25 // This setting only comes into play if COPY_NOT_AVAILABLE is in the SET_AUTO_OVERRIDE_EVENTS list
26 const SET_BLOCK_CHECKOUT_ON_COPY_STATUS = 'circ.selfcheck.block_checkout_on_copy_status';
28 function SelfCheckManager() {
30 this.cgi = new openils.CGI();
32 this.workstation = null;
33 this.authtoken = null;
36 this.patronBarcodeRegex = null;
41 // During renewals, keep track of the ID of the previous circulation.
42 // Previous circ is used for tracking failed renewals (for receipts).
45 // current item barcode
46 this.itemBarcode = null;
48 // are we currently performing a renewal?
49 this.isRenewal = false;
51 // dict of org unit settings for "here"
52 this.orgSettings = {};
54 // Construct a mock checkout for debugging purposes
55 if(this.mockCheckouts = this.cgi.param('mock-circ')) {
59 record : new fieldmapper.mvr(),
60 copy : new fieldmapper.acp(),
61 circ : new fieldmapper.circ()
65 this.mockCheckout.payload.record.title('Jazz improvisation for guitar');
66 this.mockCheckout.payload.record.author('Wise, Les');
67 this.mockCheckout.payload.record.isbn('0634033565');
68 this.mockCheckout.payload.copy.barcode('123456789');
69 this.mockCheckout.payload.circ.renewal_remaining(1);
70 this.mockCheckout.payload.circ.parent_circ(1);
71 this.mockCheckout.payload.circ.due_date('2012-12-21');
80 * Fetch the org-unit settings, initialize the display, etc.
82 SelfCheckManager.prototype.init = function() {
84 this.staff = openils.User.user;
85 this.workstation = openils.User.workstation;
86 this.authtoken = openils.User.authtoken;
87 this.loadOrgSettings();
89 this.circTbody = dojo.byId('oils-selfck-circ-tbody');
90 this.itemsOutTbody = dojo.byId('oils-selfck-circ-out-tbody');
92 // workstation is required but none provided
93 if(this.orgSettings[SET_WORKSTATION_REQUIRED] && !this.workstation) {
94 if(confirm(dojo.string.substitute(localeStrings.WORKSTATION_REQUIRED))) {
95 this.registerWorkstation();
101 // connect onclick handlers to the various navigation links
103 'oils-selfck-hold-details-link' : function() { self.drawHoldsPage(); },
104 'oils-selfck-view-fines-link' : function() { self.drawFinesPage(); },
105 'oils-selfck-pay-fines-link' : function() {
106 self.goToTab("payment");
107 self.drawPayFinesPage(
109 self.getSelectedFinesTotal(),
110 self.getSelectedFineTransactions(),
112 var evt = openils.Event.parse(resp);
114 var message = evt + '';
115 if(evt.textcode == 'CREDIT_PROCESSOR_DECLINED_TRANSACTION' && evt.payload)
116 message += '\n' + evt.payload.error_message;
117 self.handleAlert(message, true, 'payment-failure');
120 self.printPaymentReceipt(
123 self.updateFinesSummary();
124 self.drawFinesPage();
130 'oils-selfck-nav-home' : function() { self.drawCircPage(); },
131 'oils-selfck-nav-logout' : function() { self.logoutPatron(); },
132 'oils-selfck-nav-logout-print' : function() { self.logoutPatron(true); },
133 'oils-selfck-items-out-details-link' : function() { self.drawItemsOutPage(); },
134 'oils-selfck-print-list-link' : function() { self.printList(); }
137 for(var id in linkHandlers)
138 dojo.connect(dojo.byId(id), 'onclick', linkHandlers[id]);
141 if(this.cgi.param('patron')) {
143 // Patron barcode via cgi param. Mainly used for debugging and
144 // only works if password is not required by policy
145 this.loginPatron(this.cgi.param('patron'));
148 this.drawLoginPage();
152 * To test printing, pass a URL param of 'testprint'. The value for the param
153 * should be a JSON string like so: [{circ:<circ_id>}, ...]
155 var testPrint = this.cgi.param('testprint');
157 this.checkouts = JSON2js(testPrint);
158 this.printSessionReceipt();
164 SelfCheckManager.prototype.getSelectedFinesTotal = function() {
167 dojo.query("[name=selector]", this.finesTbody),
170 total += Number(input.getAttribute("balance_owed"));
173 return total.toFixed(2);
176 SelfCheckManager.prototype.getSelectedFineTransactions = function() {
177 return dojo.query("[name=selector]", this.finesTbody).
178 filter(function (o) { return o.checked }).
182 o.getAttribute("xact"),
183 Number(o.getAttribute("balance_owed")).toFixed(2)
190 * Registers a new workstion
192 SelfCheckManager.prototype.registerWorkstation = function() {
194 oilsSelfckWsDialog.show();
196 new openils.User().buildPermOrgSelector(
197 'REGISTER_WORKSTATION',
198 oilsSelfckWsLocSelector,
204 dojo.connect(oilsSelfckWsSubmit, 'onClick',
207 oilsSelfckWsDialog.hide();
208 var name = oilsSelfckWsLocSelector.attr('displayedValue') + '-' + oilsSelfckWsName.attr('value');
210 var res = fieldmapper.standardRequest(
211 ['open-ils.actor', 'open-ils.actor.workstation.register'],
213 self.authtoken, name, oilsSelfckWsLocSelector.attr('value')
218 if(evt = openils.Event.parse(res)) {
219 if(evt.textcode == 'WORKSTATION_NAME_EXISTS') {
220 if(confirm(localeStrings.WORKSTATION_EXISTS)) {
221 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
223 self.registerWorkstation();
230 location.href = location.href.replace(/\?.*/, '') + '?ws=' + name;
237 * Loads the org unit settings
239 SelfCheckManager.prototype.loadOrgSettings = function() {
241 var settings = fieldmapper.aou.fetchOrgSettingBatch(
242 this.staff.ws_ou(), [
247 SET_AUTO_OVERRIDE_EVENTS,
248 SET_BLOCK_CHECKOUT_ON_COPY_STATUS,
249 SET_PATRON_PASSWORD_REQUIRED,
250 SET_AUTO_RENEW_INTERVAL,
251 SET_WORKSTATION_REQUIRED,
252 SET_CC_PAYMENT_ALLOWED
258 this.orgSettings[k] = settings[k].value;
261 if(settings[SET_BARCODE_REGEX])
262 this.patronBarcodeRegex = new RegExp(settings[SET_BARCODE_REGEX].value);
265 SelfCheckManager.prototype.drawLoginPage = function() {
268 var bcHandler = function(barcode) {
269 // handle patron barcode entry
271 if(self.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
273 // password is required. wire up the scan box to read it
275 msg : 'Please enter your password', // TODO i18n
276 handler : function(pw) { self.loginPatron(barcode, pw); },
281 // password is not required, go ahead and login
282 self.loginPatron(barcode);
287 msg : 'Please log in with your library barcode.', // TODO
295 SelfCheckManager.prototype.loginPatron = function(barcode, passwd) {
297 if(this.orgSettings[SET_PATRON_PASSWORD_REQUIRED]) {
300 // would only happen in dev/debug mode when using the patron= param
301 alert('password required by org setting. remove patron= from URL');
305 // patron password is required. Verify it.
307 var res = fieldmapper.standardRequest(
308 ['open-ils.actor', 'open-ils.actor.verify_user_password'],
309 {params : [this.authtoken, barcode, null, hex_md5(passwd)]}
313 // user-not-found results in login failure
315 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
316 false, 'login-failure'
318 this.drawLoginPage();
323 // retrieve the fleshed user by barcode
324 this.patron = fieldmapper.standardRequest(
325 ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve_by_barcode'],
326 {params : [this.authtoken, barcode]}
329 var evt = openils.Event.parse(this.patron);
332 dojo.string.substitute(localeStrings.LOGIN_FAILED, [barcode]),
333 false, 'login-failure'
335 this.drawLoginPage();
339 this.handleAlert('', false, 'login-success');
340 dojo.byId('oils-selfck-user-banner').innerHTML =
341 dojo.string.substitute(localeStrings.WELCOME_BANNER, [this.patron.first_given_name()]);
347 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
349 console.log("Handling alert " + message);
351 dojo.byId('oils-selfck-status-div').innerHTML = message;
353 if(shouldPopup && this.orgSettings[SET_ALERT_POPUP])
356 if(sound && this.orgSettings[SET_ALERT_SOUND])
357 openils.Util.playAudioUrl(SelfCheckManager.audioConfig[sound]);
362 * Manages the main input box
363 * @param msg The context message to display with the box
364 * @param clearOnly Don't update the context message, just clear the value and re-focus
365 * @param handler Optional "on-enter" handler.
367 SelfCheckManager.prototype.updateScanBox = function(args) {
371 selfckScanBox.domNode.select();
373 selfckScanBox.attr('value', '');
377 selfckScanBox.domNode.setAttribute('type', 'password');
379 selfckScanBox.domNode.setAttribute('type', '');
383 selfckScanBox.attr('value', args.value);
386 dojo.byId('oils-selfck-scan-text').innerHTML = args.msg;
388 if(selfckScanBox._lastHandler && (args.handler || args.clearHandler)) {
389 dojo.disconnect(selfckScanBox._lastHandler);
393 selfckScanBox._lastHandler = dojo.connect(
397 if(e.keyCode != dojo.keys.ENTER)
399 args.handler(selfckScanBox.attr('value'));
404 selfckScanBox.focus();
408 * Sets up the checkout/renewal interface
410 SelfCheckManager.prototype.drawCircPage = function() {
412 openils.Util.show('oils-selfck-circ-tbody', 'table-row-group');
413 this.goToTab('checkout');
415 while(this.itemsOutTbody.childNodes[0])
416 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
420 msg : 'Please enter an item barcode', // TODO i18n
421 handler : function(barcode) { self.checkout(barcode); }
424 if(!this.circTemplate)
425 this.circTemplate = this.circTbody.removeChild(dojo.byId('oils-selfck-circ-row'));
428 this.updateFinesSummary();
431 this.updateHoldsSummary();
434 this.updateCircSummary();
436 // render mock checkouts for debugging?
437 if(this.mockCheckouts) {
438 for(var i in [1,2,3])
439 this.displayCheckout(this.mockCheckout, 'checkout');
444 SelfCheckManager.prototype.updateFinesSummary = function() {
448 fieldmapper.standardRequest(
449 ['open-ils.actor', 'open-ils.actor.user.fines.summary'],
451 params : [this.authtoken, this.patron.id()],
452 oncomplete : function(r) {
454 var summary = openils.Util.readResponse(r);
456 dojo.byId('oils-selfck-fines-total').innerHTML =
457 dojo.string.substitute(
458 localeStrings.TOTAL_FINES_ACCOUNT,
459 [summary.balance_owed()]
462 self.creditPayableBalance = summary.balance_owed();
469 SelfCheckManager.prototype.drawItemsOutPage = function() {
470 openils.Util.hide('oils-selfck-circ-tbody');
472 this.goToTab('items_out');
474 while(this.itemsOutTbody.childNodes[0])
475 this.itemsOutTbody.removeChild(this.itemsOutTbody.childNodes[0]);
477 progressDialog.show(true);
480 fieldmapper.standardRequest(
481 ['open-ils.circ', 'open-ils.circ.actor.user.checked_out.atomic'],
484 params : [this.authtoken, this.patron.id()],
485 oncomplete : function(r) {
487 var resp = openils.Util.readResponse(r);
489 var circs = resp.sort(
491 if(a.circ.due_date() > b.circ.due_date())
497 progressDialog.hide();
502 self.itemsOut.push(circ.circ.id());
503 self.displayCheckout(
505 (circ.circ.parent_circ()) ? 'renew' : 'checkout',
516 SelfCheckManager.prototype.goToTab = function(name) {
519 openils.Util.hide('oils-selfck-fines-page');
520 openils.Util.hide('oils-selfck-payment-page');
521 openils.Util.hide('oils-selfck-holds-page');
522 openils.Util.hide('oils-selfck-circ-page');
523 openils.Util.hide('oils-selfck-pay-fines-link');
527 openils.Util.show('oils-selfck-circ-page');
530 openils.Util.show('oils-selfck-circ-page');
533 openils.Util.show('oils-selfck-holds-page');
536 openils.Util.show('oils-selfck-fines-page');
539 openils.Util.show('oils-selfck-payment-page');
545 SelfCheckManager.prototype.printList = function() {
546 switch(this.tabName) {
548 this.printSessionReceipt();
551 this.printItemsOutReceipt();
554 this.printHoldsReceipt();
557 this.printFinesReceipt();
562 SelfCheckManager.prototype.updateHoldsSummary = function() {
564 if(!this.holdsSummary) {
565 var summary = fieldmapper.standardRequest(
566 ['open-ils.circ', 'open-ils.circ.holds.user_summary'],
567 {params : [this.authtoken, this.patron.id()]}
570 this.holdsSummary = {};
571 this.holdsSummary.ready = Number(summary['4']);
572 this.holdsSummary.total = 0;
574 for(var i in summary)
575 this.holdsSummary.total += Number(summary[i]);
578 dojo.byId('oils-selfck-holds-total').innerHTML =
579 dojo.string.substitute(
580 localeStrings.TOTAL_HOLDS,
581 [this.holdsSummary.total]
584 dojo.byId('oils-selfck-holds-ready').innerHTML =
585 dojo.string.substitute(
586 localeStrings.HOLDS_READY_FOR_PICKUP,
587 [this.holdsSummary.ready]
592 SelfCheckManager.prototype.updateCircSummary = function(increment) {
594 if(!this.circSummary) {
596 var summary = fieldmapper.standardRequest(
597 ['open-ils.actor', 'open-ils.actor.user.checked_out.count'],
598 {params : [this.authtoken, this.patron.id()]}
602 total : Number(summary.out) + Number(summary.overdue),
603 overdue : Number(summary.overdue),
609 // local checkout occurred. Add to the total and the session.
610 this.circSummary.total += 1;
611 this.circSummary.session += 1;
614 dojo.byId('oils-selfck-circ-account-total').innerHTML =
615 dojo.string.substitute(
616 localeStrings.TOTAL_ITEMS_ACCOUNT,
617 [this.circSummary.total]
620 dojo.byId('oils-selfck-circ-session-total').innerHTML =
621 dojo.string.substitute(
622 localeStrings.TOTAL_ITEMS_SESSION,
623 [this.circSummary.session]
628 SelfCheckManager.prototype.drawHoldsPage = function() {
630 // TODO add option to hid scanBox
631 // this.updateScanBox(...)
633 this.goToTab('holds');
635 this.holdTbody = dojo.byId('oils-selfck-hold-tbody');
636 if(!this.holdTemplate)
637 this.holdTemplate = this.holdTbody.removeChild(dojo.byId('oils-selfck-hold-row'));
638 while(this.holdTbody.childNodes[0])
639 this.holdTbody.removeChild(this.holdTbody.childNodes[0]);
641 progressDialog.show(true);
644 fieldmapper.standardRequest( // fetch the hold IDs
646 ['open-ils.circ', 'open-ils.circ.holds.id_list.retrieve'],
648 params : [this.authtoken, this.patron.id()],
650 oncomplete : function(r) {
651 var ids = openils.Util.readResponse(r);
652 if(!ids || ids.length == 0) {
653 progressDialog.hide();
657 fieldmapper.standardRequest( // fetch the hold objects with fleshed details
658 ['open-ils.circ', 'open-ils.circ.hold.details.batch.retrieve.atomic'],
660 params : [self.authtoken, ids],
662 oncomplete : function(rr) {
663 self.drawHolds(openils.Util.readResponse(rr));
673 * Fetch and add a single hold to the list of holds
675 SelfCheckManager.prototype.drawHolds = function(holds) {
678 // sort available holds to the top of the list
679 // followed by queue position order
681 if(a.status == 4) return -1;
682 if(a.queue_position < b.queue_position) return -1;
689 progressDialog.hide();
691 for(var i in holds) {
694 var row = this.holdTemplate.cloneNode(true);
696 if(data.mvr.isbn()) {
697 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + data.mvr.isbn());
700 this.byName(row, 'title').innerHTML = data.mvr.title();
701 this.byName(row, 'author').innerHTML = data.mvr.author();
703 if(data.status == 4) {
705 // hold is ready for pickup
706 this.byName(row, 'status').innerHTML = localeStrings.HOLD_STATUS_READY;
710 // hold is still pending
711 this.byName(row, 'status').innerHTML =
712 dojo.string.substitute(
713 localeStrings.HOLD_STATUS_WAITING,
714 [data.queue_position, data.potential_copies]
718 this.holdTbody.appendChild(row);
723 SelfCheckManager.prototype.drawFinesPage = function() {
725 // TODO add option to hid scanBox
726 // this.updateScanBox(...)
728 this.goToTab('fines');
729 progressDialog.show(true);
731 if(this.creditPayableBalance > 0 && this.orgSettings[SET_CC_PAYMENT_ALLOWED]) {
732 openils.Util.show('oils-selfck-pay-fines-link', 'inline');
735 this.finesTbody = dojo.byId('oils-selfck-fines-tbody');
736 if(!this.finesTemplate)
737 this.finesTemplate = this.finesTbody.removeChild(dojo.byId('oils-selfck-fines-row'));
738 while(this.finesTbody.childNodes[0])
739 this.finesTbody.removeChild(this.finesTbody.childNodes[0]);
741 // when user clicks on a selector checkbox, update the total owed
742 var updateSelected = function() {
745 dojo.query('[name=selector]', this.finesTbody),
748 total += Number(input.getAttribute('balance_owed'));
752 total = total.toFixed(2);
753 dojo.byId('oils-selfck-selected-total').innerHTML =
754 dojo.string.substitute(localeStrings.TOTAL_FINES_SELECTED, [total]);
757 // wire up the batch on/off selector
758 var sel = dojo.byId('oils-selfck-fines-selector');
759 sel.onchange = function() {
761 dojo.query('[name=selector]', this.finesTbody),
763 input.checked = sel.checked;
769 var handler = function(dataList) {
771 self.finesCount = dataList.length;
772 self.finesData = dataList;
774 for(var i in dataList) {
776 var data = dataList[i];
777 var row = self.finesTemplate.cloneNode(true);
778 var type = data.transaction.xact_type();
780 if(type == 'circulation') {
781 self.byName(row, 'type').innerHTML = type;
782 self.byName(row, 'details').innerHTML = data.record.title();
784 } else if(type == 'grocery') {
785 self.byName(row, 'type').innerHTML = 'Miscellaneous'; // Go ahead and head off any confusion around "grocery". TODO i18n
786 self.byName(row, 'details').innerHTML = data.transaction.last_billing_type();
789 self.byName(row, 'total_owed').innerHTML = data.transaction.total_owed();
790 self.byName(row, 'total_paid').innerHTML = data.transaction.total_paid();
791 self.byName(row, 'balance').innerHTML = data.transaction.balance_owed();
794 var selector = self.byName(row, 'selector')
795 selector.onchange = updateSelected;
796 selector.setAttribute('xact', data.transaction.id());
797 selector.setAttribute('balance_owed', data.transaction.balance_owed());
798 selector.checked = true;
800 self.finesTbody.appendChild(row);
807 fieldmapper.standardRequest(
808 ['open-ils.actor', 'open-ils.actor.user.transactions.have_balance.fleshed'],
810 params : [this.authtoken, this.patron.id()],
811 oncomplete : function(r) {
812 progressDialog.hide();
813 handler(openils.Util.readResponse(r));
819 SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
821 var resp = fieldmapper.standardRequest(
822 ['open-ils.circ', 'open-ils.circ.transit.abort'],
823 {params : [this.authtoken, {barcode : barcode}]}
826 // resp == 1 on success
827 if(openils.Event.parse(resp))
830 var resp = fieldmapper.standardRequest(
831 ['open-ils.circ', 'open-ils.circ.checkin.override'],
834 patron_id : this.patron.id(),
835 copy_barcode : barcode,
841 if(!resp.length) resp = [resp];
842 for(var i = 0; i < resp.length; i++) {
843 var tc = openils.Event.parse(resp[i]).textcode;
844 if(tc == 'SUCCESS' || tc == 'NO_CHANGE') {
855 * Check out a single item. If the item is already checked
856 * out to the patron, redirect to renew()
858 SelfCheckManager.prototype.checkout = function(barcode, override) {
860 this.prevCirc = null;
863 this.updateScanbox(null, true);
867 if(this.mockCheckouts) {
868 // if we're in mock-checkout mode, just insert another
869 // fake circ into the table and get out of here.
870 this.displayCheckout(this.mockCheckout, 'checkout');
874 // TODO see if it's a patron barcode
875 // TODO see if this item has already been checked out in this session
877 var method = 'open-ils.circ.checkout.full';
878 if(override) method += '.override';
880 console.log("Checkout out item " + barcode + " with method " + method);
882 var result = fieldmapper.standardRequest(
883 ['open-ils.circ', method],
886 patron_id : this.patron.id(),
887 copy_barcode : barcode
892 var stat = this.handleXactResult('checkout', barcode, result);
895 this.checkout(barcode, true);
896 } else if(stat.doOver) {
897 this.checkout(barcode);
898 } else if(stat.renew) {
903 SelfCheckManager.prototype.failPartMessage = function(result) {
904 if (result.payload && result.payload.fail_part) {
905 var stringKey = "FAIL_PART_" +
906 result.payload.fail_part.replace(/\./g, "_");
907 return localeStrings[stringKey];
913 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
915 var displayText = '';
917 // If true, the display message is important enough to pop up. Whether or not
918 // an alert() actually occurs, depends on org unit settings
920 var sound = ''; // sound file reference
921 var payload = result.payload || {};
922 var overrideEvents = this.orgSettings[SET_AUTO_OVERRIDE_EVENTS];
923 var blockStatuses = this.orgSettings[SET_BLOCK_CHECKOUT_ON_COPY_STATUS];
925 if(result.textcode == 'NO_SESSION') {
927 return this.logoutStaff();
929 } else if(result.textcode == 'SUCCESS') {
931 if(action == 'checkout') {
933 displayText = dojo.string.substitute(localeStrings.CHECKOUT_SUCCESS, [item]);
934 this.displayCheckout(result, 'checkout');
936 if(payload.holds_fulfilled && payload.holds_fulfilled.length) {
937 // A hold was fulfilled, update the hold numbers in the circ summary
938 console.log("fulfilled hold " + payload.holds_fulfilled + " during checkout");
939 this.holdsSummary = null;
940 this.updateHoldsSummary();
943 this.updateCircSummary(true);
945 } else if(action == 'renew') {
947 displayText = dojo.string.substitute(localeStrings.RENEW_SUCCESS, [item]);
948 this.displayCheckout(result, 'renew');
951 this.checkouts.push({circ : result.payload.circ.id()});
952 sound = 'checkout-success';
953 this.updateScanBox();
955 } else if(result.textcode == 'OPEN_CIRCULATION_EXISTS' && action == 'checkout') {
957 // Server says the item is already checked out. If it's checked out to the
958 // current user, we may need to renew it.
960 if(payload.old_circ) {
963 old_circ refers to the previous checkout IFF it's for the same user.
964 If no auto-renew interval is not defined, assume we should renew it
965 If an auto-renew interval is defined and the payload comes back with
966 auto_renew set to true, do the renewal. Otherwise, let the patron know
967 the item is already checked out to them. */
969 if( !this.orgSettings[SET_AUTO_RENEW_INTERVAL] ||
970 (this.orgSettings[SET_AUTO_RENEW_INTERVAL] && payload.auto_renew) ) {
971 this.prevCirc = payload.old_circ.id();
972 return { renew : true };
976 sound = 'checkout-failure';
977 displayText = dojo.string.substitute(localeStrings.ALREADY_OUT, [item]);
981 if( // copy is marked lost. if configured to do so, check it in and try again.
982 result.payload.copy &&
983 result.payload.copy.status() == /* LOST */ 3 &&
984 overrideEvents && overrideEvents.length &&
985 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
987 if(this.checkin(item)) {
988 return { doOver : true };
993 // item is checked out to some other user
995 sound = 'checkout-failure';
996 displayText = dojo.string.substitute(localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
999 this.updateScanBox({select:true});
1004 if(overrideEvents && overrideEvents.length) {
1006 // see if the events we received are all in the list of
1007 // events to override
1009 if(!result.length) result = [result];
1011 var override = true;
1012 for(var i = 0; i < result.length; i++) {
1014 var match = overrideEvents.filter(function(e) { return (e == result[i].textcode); })[0];
1021 if(result[i].textcode == 'COPY_NOT_AVAILABLE' && blockStatuses && blockStatuses.length) {
1023 var stat = result[i].payload.status(); // copy status
1024 if(typeof stat == 'object') stat = stat.id();
1026 var match2 = blockStatuses.filter(function(e) { return (e == stat); })[0];
1028 if(match2) { // copy is in a blocked status
1034 if(result[i].textcode == 'COPY_IN_TRANSIT') {
1035 // to override a transit, we have to abort the transit and check it in first
1036 if(this.checkin(item, true)) {
1037 return { doOver : true };
1045 return { override : true };
1048 this.updateScanBox({select : true});
1050 sound = 'checkout-failure';
1052 if(action == 'renew')
1053 this.checkouts.push({circ : this.prevCirc, renewal_failure : true});
1058 switch(result.textcode) {
1060 // TODO custom handler for blocking penalties
1062 case 'MAX_RENEWALS_REACHED' :
1063 displayText = dojo.string.substitute(
1064 localeStrings.MAX_RENEWALS, [item]);
1067 case 'ITEM_NOT_CATALOGED' :
1068 displayText = dojo.string.substitute(
1069 localeStrings.ITEM_NOT_CATALOGED, [item]);
1072 case 'OPEN_CIRCULATION_EXISTS' :
1073 displayText = dojo.string.substitute(
1074 localeStrings.OPEN_CIRCULATION_EXISTS, [item]);
1079 console.error('Unhandled event ' + result.textcode);
1081 if (!(displayText = this.failPartMessage(result))) {
1082 if (action == 'checkout' || action == 'renew') {
1083 displayText = dojo.string.substitute(
1084 localeStrings.GENERIC_CIRC_FAILURE, [item]);
1086 displayText = dojo.string.substitute(
1087 localeStrings.UNKNOWN_ERROR, [result.textcode]);
1093 this.handleAlert(displayText, popup, sound);
1101 SelfCheckManager.prototype.renew = function(barcode, override) {
1103 var method = 'open-ils.circ.renew';
1104 if(override) method += '.override';
1106 console.log("Renewing item " + barcode + " with method " + method);
1108 var result = fieldmapper.standardRequest(
1109 ['open-ils.circ', method],
1112 patron_id : this.patron.id(),
1113 copy_barcode : barcode
1118 console.log(js2JSON(result));
1120 var stat = this.handleXactResult('renew', barcode, result);
1123 this.renew(barcode, true);
1127 * Display the result of a checkout or renewal in the items out table
1129 SelfCheckManager.prototype.displayCheckout = function(evt, type, itemsOut) {
1131 var copy = evt.payload.copy;
1132 var record = evt.payload.record;
1133 var circ = evt.payload.circ;
1134 var row = this.circTemplate.cloneNode(true);
1137 this.byName(row, 'jacket').setAttribute('src', '/opac/extras/ac/jacket/small/' + record.isbn());
1140 this.byName(row, 'barcode').innerHTML = copy.barcode();
1141 this.byName(row, 'title').innerHTML = record.title();
1142 this.byName(row, 'author').innerHTML = record.author();
1143 this.byName(row, 'remaining').innerHTML = circ.renewal_remaining();
1144 openils.Util.show(this.byName(row, type));
1146 var date = dojo.date.stamp.fromISOString(circ.due_date());
1147 this.byName(row, 'due_date').innerHTML =
1148 dojo.date.locale.format(date, {selector : 'date'});
1150 // put new circs at the top of the list
1151 var tbody = this.circTbody;
1152 if(itemsOut) tbody = this.itemsOutTbody;
1153 tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
1157 SelfCheckManager.prototype.byName = function(node, name) {
1158 return dojo.query('[name=' + name+']', node)[0];
1162 SelfCheckManager.prototype.initPrinter = function() {
1163 try { // Mozilla only
1164 netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
1165 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
1166 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesRead');
1167 netscape.security.PrivilegeManager.enablePrivilege('UniversalPreferencesWrite');
1168 var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
1170 pref.setBoolPref('print.always_print_silent', true);
1172 console.log("Unable to initialize auto-printing");
1177 * Print a receipt for this session's checkouts
1179 SelfCheckManager.prototype.printSessionReceipt = function(callback) {
1182 var circCtx = []; // circ context data. in this case, renewal_failure info
1184 // collect the circs and failure info
1188 circIds.push(blob.circ);
1189 circCtx.push({renewal_failure:blob.renewal_failure});
1197 'format.selfcheck.checkout',
1204 fieldmapper.standardRequest(
1205 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1209 oncomplete : function(r) {
1210 var resp = openils.Util.readResponse(r);
1211 var output = resp.template_output();
1213 self.printData(output.data(), self.checkouts.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");
1227 SelfCheckManager.prototype.printData = function(data, numItems, callback) {
1229 var win = window.open('', '', 'resizable,width=700,height=500,scrollbars=1');
1230 win.document.body.innerHTML = data;
1234 * There is no way to know when the browser is done printing.
1235 * Make a best guess at when to close the print window by basing
1236 * the setTimeout wait on the number of items to be printed plus
1239 var sleepTime = 1000;
1241 sleepTime += (numItems / 2) * 1000;
1245 win.close(); // close the print window
1247 callback(); // fire optional post-print callback
1255 * Print a receipt for this user's items out
1257 SelfCheckManager.prototype.printItemsOutReceipt = function(callback) {
1259 if(!this.itemsOut.length) return;
1261 progressDialog.show(true);
1267 'format.selfcheck.items_out',
1273 fieldmapper.standardRequest(
1274 ['open-ils.circ', 'open-ils.circ.fire_circ_trigger_events'],
1278 oncomplete : function(r) {
1279 progressDialog.hide();
1280 var resp = openils.Util.readResponse(r);
1281 var output = resp.template_output();
1283 self.printData(output.data(), self.itemsOut.length, callback);
1285 var error = resp.error_output();
1287 throw new Error("Error creating receipt: " + error.data());
1289 throw new Error("No receipt data returned from server");
1298 * Print a receipt for this user's items out
1300 SelfCheckManager.prototype.printHoldsReceipt = function(callback) {
1302 if(!this.holds.length) return;
1304 progressDialog.show(true);
1309 dojo.forEach(this.holds,
1311 holdIds.push(data.hold.id());
1312 if(data.status == 4) {
1313 holdData.push({ready : true});
1316 queue_position : data.queue_position,
1317 potential_copies : data.potential_copies
1327 'format.selfcheck.holds',
1334 fieldmapper.standardRequest(
1335 ['open-ils.circ', 'open-ils.circ.fire_hold_trigger_events'],
1339 oncomplete : function(r) {
1340 progressDialog.hide();
1341 var resp = openils.Util.readResponse(r);
1342 var output = resp.template_output();
1344 self.printData(output.data(), self.holds.length, callback);
1346 var error = resp.error_output();
1348 throw new Error("Error creating receipt: " + error.data());
1350 throw new Error("No receipt data returned from server");
1359 SelfCheckManager.prototype.printPaymentReceipt = function(paymentIds, callback) {
1362 progressDialog.show(true);
1364 fieldmapper.standardRequest(
1365 ['open-ils.circ', 'open-ils.circ.money.payment_receipt.print'],
1368 params : [this.authtoken, paymentIds],
1369 oncomplete : function(r) {
1370 var resp = openils.Util.readResponse(r);
1371 var output = resp.template_output();
1372 progressDialog.hide();
1374 self.printData(output.data(), 1, callback);
1376 var error = resp.error_output();
1378 throw new Error("Error creating receipt: " + error.data());
1380 throw new Error("No receipt data returned from server");
1389 * Print a receipt for this user's items out
1391 SelfCheckManager.prototype.printFinesReceipt = function(callback) {
1393 progressDialog.show(true);
1399 'format.selfcheck.fines',
1405 fieldmapper.standardRequest(
1406 ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
1410 oncomplete : function(r) {
1411 progressDialog.hide();
1412 var resp = openils.Util.readResponse(r);
1413 var output = resp.template_output();
1415 self.printData(output.data(), self.finesCount, callback);
1417 var error = resp.error_output();
1419 throw new Error("Error creating receipt: " + error.data());
1421 throw new Error("No receipt data returned from server");
1433 * Logout the patron and return to the login page
1435 SelfCheckManager.prototype.logoutPatron = function(print) {
1436 if(print && this.checkouts.length) {
1437 this.printSessionReceipt(
1439 location.href = location.href;
1443 location.href = location.href;
1449 * Fire up the manager on page load
1451 openils.Util.addOnLoad(
1453 new SelfCheckManager().init();