Selfcheck Staff Mode - Deployment 2 selfcheck-staff-mode-deploy-2
authorBill Erickson <berick@esilibrary.com>
Fri, 1 Feb 2013 20:50:29 +0000 (15:50 -0500)
committerBill Erickson <berick@esilibrary.com>
Mon, 11 Feb 2013 21:40:50 +0000 (16:40 -0500)
No copying stuff around this time, just copy the files into place

Signed-off-by: Bill Erickson <berick@esilibrary.com>
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.selfcheck-staff-mode.sql [new file with mode: 0644]
Open-ILS/src/templates/circ/selfcheck/banner.tt2
Open-ILS/src/templates/circ/selfcheck/checkin_page.tt2 [new file with mode: 0644]
Open-ILS/src/templates/circ/selfcheck/main.tt2
Open-ILS/src/templates/circ/selfcheck/summary.tt2
Open-ILS/web/css/skin/default/selfcheck.css
Open-ILS/web/js/dojo/openils/Event.js
Open-ILS/web/js/dojo/openils/circ/nls/selfcheck.js
Open-ILS/web/js/ui/default/circ/selfcheck/selfcheck.js

index 8805181..a7c7cc2 100644 (file)
@@ -1573,7 +1573,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
  ( 541, 'ADMIN_TOOLBAR_FOR_WORKSTATION', oils_i18n_gettext( 541,
         'Allows a user to create, edit, and delete custom toolbars for workstations', 'ppl', 'description')),
  ( 542, 'ADMIN_TOOLBAR_FOR_USER', oils_i18n_gettext( 542,
-        'Allows a user to create, edit, and delete custom toolbars for users', 'ppl', 'description'))
+        'Allows a user to create, edit, and delete custom toolbars for users', 'ppl', 'description')),
+ ( 543, 'SELFCHECK_STAFF_MODE', oils_i18n_gettext( 543,
+        'Activates staff mode for the selcheck UI for users that have this permission', 'ppl', 'description')),
 ;
 
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.selfcheck-staff-mode.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.selfcheck-staff-mode.sql
new file mode 100644 (file)
index 0000000..c73ba20
--- /dev/null
@@ -0,0 +1,17 @@
+
+BEGIN;
+
+-- TODO version
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES (
+    543, -- verify
+    'SELFCHECK_STAFF_MODE',
+    oils_i18n_gettext(
+        543, -- verify
+        'Activates staff mode for the selcheck UI for users that have this permission',
+        'ppl',
+        'description'
+    )
+);
+
+COMMIT;
index cda4866..77a7104 100644 (file)
@@ -1,4 +1,12 @@
-<div id='oils-selfck-user-banner'></div>
+<div id='oils-selfck-user-info'>
+    <div id='oils-selfck-user-banner'></div>
+    <div id='oils-selfck-user-details' class='hidden'>
+        <div id='oils-selfck-user-name'></div>
+        <div id='oils-selfck-user-home-ou'></div>
+        <div id='oils-selfck-user-profile'></div>
+        <div id='oils-selfck-user-address'></div>
+    </div>
+</div>
 <div id='oils-selfck-logo-div'>
     <img src='[% ctx.media_prefix %]/images/eg_logo.jpg'/>
 </div>
diff --git a/Open-ILS/src/templates/circ/selfcheck/checkin_page.tt2 b/Open-ILS/src/templates/circ/selfcheck/checkin_page.tt2
new file mode 100644 (file)
index 0000000..97d0662
--- /dev/null
@@ -0,0 +1,63 @@
+<div id='oils-selfck-checkin-table-div'>
+    <div id='oils-selfck-checkin-mods'>
+        <span class='oils-selfck-checkin-mod'>
+            <span>[% l('Hold Slips') %]</span>
+            <span><input 
+                id='oils-selfchk-print-hold-slip'
+                type='checkbox' 
+                checked='checked'/>
+            </span>
+        </span>
+        <span class='oils-selfck-checkin-mod'>
+            <span>[% l('Transit Slips') %]</span>
+            <span><input 
+                id='oils-selfchk-print-transit-slip'
+                type='checkbox' 
+                checked='checked'/>
+            </span>
+        </span>
+        <span class='oils-selfck-checkin-mod'>
+            <span>[% l('Amnesty Mode') %]</span>
+            <span><input 
+                id='oils-selfchk-amnesty-mode' 
+                type='checkbox' 
+                checked='checked'/>
+            </span>
+        </span>
+        <span class='oils-selfck-checkin-mod'>
+            <span>[% l('Backdate') %]</span>
+            <span><input 
+                id='oils-selfchk-backdate'
+                style='width: 8em;'
+                constraints='{max: new Date()}'
+                dojoType='dijit.form.DateTextBox' 
+                jsId='checkinBackdateInput'/>
+            </span>
+        </span>
+    </div>
+    <table id='oils-selfck-checkin-table' class='oils-selfck-item-table'>
+        <thead>
+            <tr>
+                <td id='oils-self-checkin-pic-cell'></td>
+                <td>[% l('Barcode') %]</td>
+                <td>[% l('Title') %]</td>
+                <td>[% l('Author') %]</td>
+                <td>[% l('Due Date') %]</td>
+                <td>[% l('Copy Status') %]</td>
+                <td>[% l('Outcome') %]</td>
+            </tr>
+        </thead>
+        <tbody id='oils-selfck-checkin-tbody'>
+            <tr id='oils-selfck-checkin-row'>
+                <td><img class='oils-selfck-jacket' name='jacket'/></td>
+                <td name='barcode'></td>
+                <td name='title'><img src='/opac/images/progressbar_green.gif'/></td>
+                <td name='author'></td>
+                <td name='due_date'></td>
+                <td name='status'></td>
+                <td name='outcome'></td>
+            </tr>
+        </tbody>
+        <tbody id='oils-selfck-checkin-out-tbody' class='oils-selfck-item-table'></tbody>
+    </table>
+</div>
index 42aaf49..f5b3aaf 100644 (file)
             <!-- Checkout / renewal and items out interface -->
             [% INCLUDE 'circ/selfcheck/circ_page.tt2' %]
         </div>
+        <div id='oils-selfck-checkin-page' class='hidden'>
+            <!-- Checkin interface; Staff only -->
+            [% INCLUDE 'circ/selfcheck/checkin_page.tt2' %]
+        </div>
         <div id='oils-selfck-holds-page' class='hidden'>
             <!-- Patron holds interface -->
             [% INCLUDE 'circ/selfcheck/holds_page.tt2' %]
         [% INCLUDE 'circ/selfcheck/summary.tt2' %]
     </div>
 </div>
+<div id='oils-selfchk-staff-actions' class='hidden'>
+    <span>
+        <a id='oils-selfchk-staff-logout' 
+            href='javascript:;'>[% l('Staff Logout') %]</a>
+    </span>
+</div>
 <div dojoType='openils.widget.ProgressDialog' jsId='progressDialog'></div>
 <div dojoType="dijit.Dialog" jsId='oilsSelfckWsDialog' class='oils-login-dialog' style='display:none;'>
     <form>
index 652815c..c67f23a 100644 (file)
@@ -1,6 +1,7 @@
 <div id='oils-selfck-circ-info-div'>
     <div id='oils-selfck-info-nav'>
         <span><a id='oils-selfck-nav-home' href='javascript:void(0);' class='selected'>[% l('Home') %]</a></span>
+        <span class='hidden'><a id='oils-selfck-nav-checkin' href='javascript:void(0);'>[% l('Checkin') %]</a></span>
         <span><a id='oils-selfck-nav-logout-print' href='javascript:void(0);'>[% l('Logout') %]</a></span>
         <span><a id='oils-selfck-nav-logout' href='javascript:void(0);'>[% l('Logout (No Receipt)') %]</a></span>
     </div>
index 986b46f..063a846 100644 (file)
@@ -12,12 +12,17 @@ body {
     font-weight:bold;
 }
 
-#oils-selfck-user-banner {
+#oils-selfck-user-info {
     position:fixed;
     top:30px;
     right:30px;
 }
 
+#oils-selfck-user-details {
+    text-align: left;
+    padding-right: 15px;
+}
+
 #oils-selfck-logo-div {
     margin: 20px;
 }
@@ -74,9 +79,10 @@ body {
 
 #oils-selfck-content-header {
     margin: 8px 10px 15px 10px;
-    padding: 8px;
+    padding: 10px 0 3px 0;
     border-bottom: 1px dashed #888;
     text-align: right;
+    line-height: 200%;  /* to line up with #oils-selfck-info-nav span */
     width: 100%;
 }
 #oils-selfck-content-header span {
@@ -92,12 +98,23 @@ body {
 #oils-selfck-circ-info-div span {
     padding-right: 8px;
 }
+
+
 #oils-selfck-info-nav span {
-    padding-left: 8px;
+    /* padding-left: 8px; */
+    line-height: 200%;
+}
+
+#oils-selfck-info-nav a {
+    padding-left: 4px;
+    padding-right: 4px;
+    padding-top: 4px;
+    white-space: nowrap;
 }
 
-#oils-selfck-info-nav span.selected {
+#oils-selfck-info-nav .selected {
     background-color: #e0e0e0;
+    border: 1px solid #333;
 }
 
 #oils-selfck-circ-info-div fieldset {
@@ -132,4 +149,26 @@ body {
     padding: 5px;
 }
 
+#oils-selfck-checkin-mods {
+   text-align: right;
+   border-bottom: 1px solid #333;
+   background-color: #e0e0e0;
+   padding: 10px;
+}
+
+.oils-selfck-checkin-mod {
+    border-left: 1px solid #333;
+    padding-left: 8px;
+    padding-left: 8px;
+}
 
+#oils-selfchk-staff-actions {
+    position: absolute;
+    bottom: 0px;
+    right: 0px;
+    text-align: right;
+    padding-left: 10px;
+    padding: 10px;
+    background-color: #e0e0e0;
+    border-top: 2px solid #333;
+}
index b2bf745..9d518ad 100644 (file)
@@ -30,6 +30,7 @@ if(!dojo._hasResource["openils.Event"]) {
             this.ilsperm = kwargs.ilsperm;
             this.ilspermloc = kwargs.ilspermloc;
             this.note = kwargs.note;
+            this.source = kwargs; // capture anything unexpected
         },
 
         toString : function() {
index 766fcbc..1bc571c 100644 (file)
     "FAIL_PART_no_matchpoint": "System rules do not define how to handle this item",
     "FAIL_PART_no_user": "The system could not find this patron",
     "FAIL_PART_transit_range": "The item cannot transit this far",
-    "PAYMENT_INVALID_USER_XACT_ID" : "We cannot proceed with the payment, because your account was updated from another location.  Please refresh the interface or log out and back in to retrieve the latest account information"
+    "PAYMENT_INVALID_USER_XACT_ID" : "We cannot proceed with the payment, because your account was updated from another location.  Please refresh the interface or log out and back in to retrieve the latest account information",
+    "ADDRESS": "${0} ${1}<br/>${2}, ${3} ${4}",
+    "CHECKIN_SUCCESS" : "Successfully checked in ${0}",
+    "CHECKIN_NO_CHANGE" : "No change on checkin for ${0}",
+    "CHECKIN_ROUTE_ITEM" : "Item ${0} should be routed to ${1}",
+    "CHECKOUT_PROMPT" : "Please an item barcode to check out",
+    "CHECKIN_PROMPT" : "Please an item barcode to check in",
+    "HOLD_SHELF" : "Hold Shelf",
+    "ROUTE_MSG" : "This item needs to be routed to ${0}:<br/>",
+    "ORG_ADDRESS" : "${0}<br/>${1}<br/>", // name, address
+    "TRANSIT_SLIP" : "${0}${1}" + // shortname, address
+        "<br/>Barcode: ${2}<br/>Title: ${3}<br/>Author: ${4}"+
+        "<br/>${5}<br/>Slip Date: ${6}<br/>Printed by ${7} at ${8}",
+    "HOLD_SLIP" : "<br/>Hold for patron ${0}<br/>Barcode: ${1}<br/>" +
+        "Request Date: ${2}<br/>"
 }
 
index 0b47be3..8b3734c 100644 (file)
@@ -2,11 +2,13 @@ dojo.require('dojo.date.locale');
 dojo.require('dojo.cookie');
 dojo.require('dojo.date.stamp');
 dojo.require('dijit.form.CheckBox');
+dojo.require('dijit.form.DateTextBox');
 dojo.require('dijit.form.NumberSpinner');
 dojo.require('openils.CGI');
 dojo.require('openils.Util');
 dojo.require('openils.User');
 dojo.require('openils.Event');
+dojo.require('openils.PermaCrud');
 dojo.require('openils.widget.ProgressDialog');
 dojo.require('openils.widget.OrgUnitFilteringSelect');
 
@@ -56,6 +58,15 @@ function SelfCheckManager() {
     // dict of org unit settings for "here"
     this.orgSettings = {};
 
+    // true if we are exposing staff-only features (e.g. checkin)
+    this.staffMode = false;
+
+    // maps copy status IDs to status objects
+    this.copyStatusMap = {};
+
+    // maps org unit IDs to their mailing addresses
+    this.orgUnitAddrMap = {};
+
     // Construct a mock checkout for debugging purposes
     if(this.mockCheckouts = this.cgi.param('mock-circ')) {
 
@@ -87,18 +98,54 @@ SelfCheckManager.prototype.setupStaffLogin = function(verify) {
     this.authtoken = openils.User.authtoken;
 }
 
+SelfCheckManager.prototype.logoutStaff = function() {
+    // remove the authtoken
+    dojo.cookie('ses', null, {expires:-1, path:'/'}); 
+    // reload the page, which displays the login dialog
+    location.href = location.href;
+}
+
+SelfCheckManager.prototype.activateStaffMode = function(permorgs) {
+    var self = this;
+
+    // make sure this staff account has the needed permission here
+    if (permorgs.indexOf(Number(this.staff.ws_ou())) == -1) return;
+
+    this.staffMode = true;
+
+    openils.Util.show(
+        dojo.byId('oils-selfck-nav-checkin').parentNode, 
+        'inline'
+    );
+
+    openils.Util.show(dojo.byId('oils-selfchk-staff-actions'));
+
+    dojo.byId('oils-selfchk-staff-logout').onclick = function() {
+        self.logoutStaff();
+    };
+}
 
 
 /**
  * Fetch the org-unit settings, initialize the display, etc.
  */
 SelfCheckManager.prototype.init = function() {
+    var self = this;
 
     this.setupStaffLogin();
     this.loadOrgSettings();
+    this.loadCopyStatuses();
+
+    // are we in staff mode?
+    new openils.User().getPermOrgList(['SELFCHECK_STAFF_MODE'], 
+        function(orglist) { self.activateStaffMode(orglist) },
+        true, true
+    );
 
     this.circTbody = dojo.byId('oils-selfck-circ-tbody');
+    this.checkinTbody = dojo.byId('oils-selfck-checkin-tbody');
     this.itemsOutTbody = dojo.byId('oils-selfck-circ-out-tbody');
+    this.itemsCheckinTbody = dojo.byId('oils-selfck-checkin-out-tbody');
 
     // workstation is required but none provided
     if(this.orgSettings[SET_WORKSTATION_REQUIRED] && !this.workstation) {
@@ -108,7 +155,6 @@ SelfCheckManager.prototype.init = function() {
         return;
     }
     
-    var self = this;
     // connect onclick handlers to the various navigation links
     var linkHandlers = {
         'oils-selfck-hold-details-link' : function() { self.drawHoldsPage(); },
@@ -143,6 +189,7 @@ SelfCheckManager.prototype.init = function() {
             );
         },
         'oils-selfck-nav-home' : function() { self.drawCircPage(); },
+        'oils-selfck-nav-checkin' : function() { self.drawCheckinPage(); },
         'oils-selfck-nav-logout' : function() { self.logoutPatron(); },
         'oils-selfck-nav-logout-print' : function() { self.logoutPatron(true); },
         'oils-selfck-items-out-details-link' : function() { self.drawItemsOutPage(); },
@@ -176,6 +223,20 @@ SelfCheckManager.prototype.init = function() {
 }
 
 
+SelfCheckManager.prototype.loadCopyStatuses = function() {
+    var self = this;
+    var pcrud = new openils.PermaCrud();
+    pcrud.retrieveAll('ccs', {
+        async : true,
+        oncomplete : function(r) {
+            var list = openils.Util.readResponse(r);
+            dojo.forEach(list, function(stat) {
+                self.copyStatusMap[stat.id()] = stat;
+            });
+        }
+    });
+};
+
 SelfCheckManager.prototype.getSelectedFinesTotal = function() {
     var total = 0;
     dojo.forEach(
@@ -366,7 +427,15 @@ SelfCheckManager.prototype.fetchPatron = function(barcode, usrname) {
     // retrieve the fleshed user by id
     this.patron = fieldmapper.standardRequest(
         ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve.authoritative'],
-        {params : [this.authtoken, patron_id]}
+        {params : [this.authtoken, patron_id,
+            [   'card',                                                                
+                'cards',
+                'addresses',                                                           
+                'billing_address',                                                     
+                'mailing_address',  
+                'profile'
+            ]
+        ]}
     );
 
     var evt = openils.Event.parse(this.patron);
@@ -394,13 +463,57 @@ SelfCheckManager.prototype.fetchPatron = function(barcode, usrname) {
     } else {
 
         this.handleAlert('', false, 'login-success');
-        dojo.byId('oils-selfck-user-banner').innerHTML = 
-            dojo.string.substitute(localeStrings.WELCOME_BANNER, [this.patron.first_given_name()]);
+
+        if (this.staffMode) {
+            this.drawPatronInfo();
+
+        } else {
+            dojo.byId('oils-selfck-user-banner').innerHTML = 
+                dojo.string.substitute(localeStrings.WELCOME_BANNER, 
+                    [this.patron.first_given_name()]);
+        }
+
+
         this.drawCircPage();
     }
 }
 
 
+SelfCheckManager.prototype.drawPatronInfo = function() {
+
+    openils.Util.show('oils-selfck-user-details');
+
+    var patron = this.patron;
+
+    dojo.byId('oils-selfck-user-name').innerHTML = 
+        openils.User.formalName(patron);
+
+    dojo.byId('oils-selfck-user-home-ou').innerHTML = 
+        fieldmapper.aou.findOrgUnit(patron.home_ou()).shortname();
+
+    dojo.byId('oils-selfck-user-profile').innerHTML = 
+        patron.profile().name();
+
+    var addr = patron.mailing_address() || 
+               patron.billing_address() || 
+               patron.addresses()[0];
+
+    if (addr) {
+
+        dojo.byId('oils-selfck-user-address').innerHTML = 
+            dojo.string.substitute(localeStrings.ADDRESS, [
+                addr.street1(),
+                addr.street2() || '',
+                addr.city(),
+                addr.state(),
+                addr.post_code()
+            ]);
+
+    } else {
+        dojo.byId('oils-selfck-user-address').innerHTML = '';
+    }
+}
+
 SelfCheckManager.prototype.handleAlert = function(message, shouldPopup, sound) {
 
     console.log("Handling alert " + message);
@@ -479,8 +592,11 @@ SelfCheckManager.prototype.drawCircPage = function() {
 
     var self = this;
     this.updateScanBox({
-        msg : 'Please enter an item barcode', // TODO i18n
-        handler : function(barcode) { self.checkout(barcode); }
+        msg : dojo.string.substitute(localeStrings.CHECKOUT_PROMPT),
+        handler : function(barcode) { 
+            // staffMode jumps straight to override
+            self.checkout(barcode, self.staffMode); 
+        }
     });
 
     if(!this.circTemplate)
@@ -502,6 +618,62 @@ SelfCheckManager.prototype.drawCircPage = function() {
     }
 }
 
+/**
+ * Sets up the checkin page
+ */
+SelfCheckManager.prototype.drawCheckinPage = function() {
+    openils.Util.show('oils-selfck-checkin-tbody', 'table-row-group');
+    this.goToTab('checkin');
+
+    while(this.itemsCheckinTbody.childNodes[0])
+        this.itemsCheckinTbody.removeChild(
+            this.itemsCheckinTbody.childNodes[0]);
+
+    if(!this.checkinTemplate) {
+        this.checkinTemplate = 
+            this.checkinTbody.removeChild(
+                dojo.byId('oils-selfck-checkin-row'));
+    }
+
+    var self = this;
+    this.updateScanBox({
+        msg : dojo.string.substitute(localeStrings.CHECKIN_PROMPT),
+        handler : function(barcode) { self.checkin(barcode); }
+    });
+};
+
+
+SelfCheckManager.prototype.checkin = function(barcode) {
+    var self = this;
+
+    // clear the box now so checkins can continue
+    this.updateScanBox();
+
+    var backdate = checkinBackdateInput.attr('value') || null;
+    if (backdate) backdate = dojo.date.stamp.toISOString(backdate);
+
+    var row = this.checkinTemplate.cloneNode(true);
+    this.byName(row, 'barcode').innerHTML = barcode;
+
+    // put new circs at the top of the list
+    var tbody = this.checkinTbody;
+    tbody.insertBefore(row, tbody.getElementsByTagName('tr')[0]);
+
+    this.checkinCopy({
+        barcode : barcode, 
+        void_overdues : dojo.byId('oils-selfchk-amnesty-mode').checked,
+        backdate : backdate,
+        onload : function(evts) {
+            if (!evts.length) evts = [evts];
+            dojo.forEach(evts, 
+                function(evt) {
+                    self.handleCheckinResult(row, barcode, evt);
+                }
+            );
+        }
+    });
+};
+
 
 SelfCheckManager.prototype.updateFinesSummary = function() {
     var self = this; 
@@ -582,11 +754,21 @@ SelfCheckManager.prototype.goToTab = function(name) {
     openils.Util.hide('oils-selfck-payment-page');
     openils.Util.hide('oils-selfck-holds-page');
     openils.Util.hide('oils-selfck-circ-page');
+    openils.Util.hide('oils-selfck-checkin-page');
     openils.Util.hide('oils-selfck-pay-fines-link');
-    
+
+    dojo.removeClass('oils-selfck-nav-home', 'selected'); 
+    dojo.removeClass('oils-selfck-nav-checkin', 'selected'); 
+
+
     switch(name) {
         case 'checkout':
             openils.Util.show('oils-selfck-circ-page');
+            dojo.addClass('oils-selfck-nav-home', 'selected'); 
+            break;
+        case 'checkin':
+            openils.Util.show('oils-selfck-checkin-page');
+            dojo.addClass('oils-selfck-nav-checkin', 'selected'); 
             break;
         case 'items_out':
             openils.Util.show('oils-selfck-circ-page');
@@ -669,7 +851,7 @@ SelfCheckManager.prototype.updateCircSummary = function(increment) {
 
     if(increment) {
         // local checkout occurred.  Add to the total and the session.
-        this.circSummary.total += 1;
+        this.circSummary.total += increment;
         this.circSummary.session += 1;
     }
 
@@ -869,16 +1051,39 @@ SelfCheckManager.prototype.drawFinesPage = function() {
     );
 }
 
-SelfCheckManager.prototype.checkin = function(barcode, abortTransit) {
-
-    var resp = fieldmapper.standardRequest(
-        ['open-ils.circ', 'open-ils.circ.transit.abort'],
-        {params : [this.authtoken, {barcode : barcode}]}
+/** top-level checkin handler */
+SelfCheckManager.prototype.checkinCopy = function(args) {
+    fieldmapper.standardRequest(
+        ['open-ils.circ', 'open-ils.circ.checkin.override'],
+        {   async : true,
+            params : [
+                this.authtoken, {
+                    copy_barcode : args.barcode,
+                    backdate : args.backdate,
+                    void_overdues : args.void_overdues
+                }
+            ],
+            oncomplete : function(r) {
+                var resp = openils.Util.readResponse(r, true);
+                args.onload(resp);
+            }
+        }
     );
+};
 
-    // resp == 1 on success
-    if(openils.Event.parse(resp))
-        return false;
+/** used for checkins required to fullfil a checkout */
+SelfCheckManager.prototype.inlineCheckinCopy = function(barcode, abortTransit) {
+
+    if (abortTransit) {
+        var resp = fieldmapper.standardRequest(
+            ['open-ils.circ', 'open-ils.circ.transit.abort'],
+            {params : [this.authtoken, {barcode : barcode}]}
+        );
+    
+        // resp == 1 on success
+        if(openils.Event.parse(resp))
+            return false;
+    }
 
     var resp = fieldmapper.standardRequest(
         ['open-ils.circ', 'open-ils.circ.checkin.override'],
@@ -963,6 +1168,263 @@ SelfCheckManager.prototype.failPartMessage = function(result) {
     }
 }
 
+SelfCheckManager.prototype.displayCheckin = function(row, result, outcomeText) {
+    console.log('display checkin results ' + result);
+
+    var copy = result.payload.copy;
+    var record = result.payload.record;
+    var circ = result.payload.circ;
+
+    if(record.isbn()) {
+        this.byName(row, 'jacket').setAttribute('src', 
+            '/opac/extras/ac/jacket/small/' + record.isbn());
+    }
+
+    this.byName(row, 'barcode').innerHTML = copy.barcode();
+    this.byName(row, 'title').innerHTML = record.title();
+    this.byName(row, 'author').innerHTML = record.author();
+    this.byName(row, 'status').innerHTML = this.copyStatusMap[copy.status()].name();
+    this.byName(row, 'outcome').innerHTML = outcomeText || result.textcode;
+
+    if (circ) {
+        var date = dojo.date.stamp.fromISOString(circ.due_date());
+        this.byName(row, 'due_date').innerHTML = 
+            dojo.date.locale.format(date, {selector : 'date'});
+
+        // if a patron is loaded and we just checked an item
+        // in for this patron, decrement the items-out count by 1
+        if (this.patron && this.patron.id() == circ.usr())
+            this.updateCircSummary(-1);
+    }
+}
+
+SelfCheckManager.prototype.getOrgMailingAddress = function(orgId, callback) {
+    if (this.orgUnitAddrMap[orgId]) {
+        callback(this.orgUnitAddrMap[orgId]);
+        return;
+    }
+
+    var self = this;
+    var pcrud = new openils.PermaCrud();
+    pcrud.retrieve('aou', orgId, {
+        flesh : 1, 
+        flesh_fields : {aou : ['mailing_address', 'billing_address']},
+        async : true,
+        oncomplete : function(r) {
+            var org = openils.Util.readResponse(r);
+            var addr = org.mailing_address() || org.billing_address();
+            self.orgUnitAddrMap[orgId] = addr;
+            callback(self.orgUnitAddrMap[orgId]);
+        }
+    });
+}
+
+SelfCheckManager.prototype.getUser = function(userId, callback) {
+    if (!userId || typeof userId == 'object') 
+        callback(userId);
+
+    var self = this;
+    var pcrud = new openils.PermaCrud();
+    pcrud.retrieve('au', userId, {
+        flesh : 1, 
+        flesh_fields : {au : ['card']},
+        async : true,
+        oncomplete : function(r) {
+            callback(openils.Util.readResponse(r));
+        }
+    });
+}
+
+// non-transit hold slip
+SelfCheckManager.prototype.printHoldSlip = function(item, result) {
+    if (!dojo.byId('oils-selfchk-print-hold-slip').checked) return;
+
+    var self = this;
+    var payload = result.payload;
+    var hold = payload.hold;
+
+    // in some cases, payload.patron is null even when there is a hold
+    this.getUser(hold.usr(), function(patron) {
+
+        var routeMsg = dojo.string.substitute(localeStrings.HOLD_SHELF, []);
+
+        var routeMsg = dojo.string.substitute(
+            localeStrings.ROUTE_MSG, [localeStrings.HOLD_SHELF]);
+
+        var holdMsg = dojo.string.substitute(
+            localeStrings.HOLD_SLIP, [
+                // checkin could be for a different user than this.patron
+                openils.User.formalName(payload.patron), 
+                patron.card().barcode(),
+                openils.Util.timeStamp(hold.request_time())
+            ]
+        );
+
+        self.printData(
+            dojo.string.substitute(
+                localeStrings.TRANSIT_SLIP, [
+                    routeMsg,
+                    '', // destination address
+                    item,
+                    result.payload.record.title(),
+                    result.payload.record.author(),
+                    holdMsg,
+                    dojo.date.locale.format(new Date()),
+                    self.staff.usrname(),
+                    fieldmapper.aou.findOrgUnit(self.staff.ws_ou()).shortname()
+                ]
+            ), 1
+        );
+    });
+}
+
+
+// hold transit slip
+SelfCheckManager.prototype.printHoldTransitSlip = function(item, result, dest) {
+    if (!dojo.byId('oils-selfchk-print-transit-slip').checked) return;
+
+    var self = this;
+    var payload = result.payload;
+    var transit = payload.transit;
+    var hold = payload.hold;
+
+    // in some cases, payload.patron is null even when there is a hold
+    this.getUser(hold.usr(), function(patron) {
+
+        var holdMsg = dojo.string.substitute(
+            localeStrings.HOLD_SLIP, [
+                // checkin could be for a different user than this.patron
+                openils.User.formalName(payload.patron), 
+                patron.card().barcode(),
+                openils.Util.timeStamp(hold.request_time())
+            ]
+        );
+
+        // now we have hold recipient info, 
+        // continue with regular slip generation
+        self.printTransitSlip(item, result, dest, holdMsg);
+    });
+}
+
+// if this is a hold transit, holdMsg should contain the hold info
+SelfCheckManager.prototype.printTransitSlip = 
+        function(item, result, dest, holdMsg) {
+
+    if (!dojo.byId('oils-selfchk-print-transit-slip').checked) return;
+
+    var self = this;
+    var payload = result.payload;
+    var transit = payload.transit;
+    var hold = payload.hold;
+    holdMsg = holdMsg || '';
+
+    var routeMsg = dojo.string.substitute(
+        localeStrings.ROUTE_MSG, [dest.shortname()]);
+
+    // retrieve the address, then finish rendering the slip
+    this.getOrgMailingAddress(dest.id(), function(addr) {
+
+        var destAddr = dojo.string.substitute(
+            localeStrings.ORG_ADDRESS, [
+                dest.name(),
+                dojo.string.substitute(localeStrings.ADDRESS, [
+                    addr.street1(),
+                    addr.street2() || '',
+                    addr.city(),
+                    addr.state(),
+                    addr.post_code()
+                ])
+            ]
+        );
+
+        self.printData(
+            dojo.string.substitute(
+                localeStrings.TRANSIT_SLIP, [
+                    routeMsg,
+                    destAddr,
+                    item,
+                    result.payload.record.title(),
+                    result.payload.record.author(),
+                    holdMsg,
+                    dojo.date.locale.format(new Date()),
+                    self.staff.usrname(),
+                    fieldmapper.aou.findOrgUnit(self.staff.ws_ou()).shortname()
+                ]
+            ), 1
+        );
+    });
+}
+
+
+
+
+SelfCheckManager.prototype.handleCheckinResult = function(row, item, result) {
+    var displayText = '';
+    var popup = false;  
+    var sound = ''; // sound file reference
+    var payload = result.payload || {};
+    var tc = result.textcode;
+
+    console.log('checkin resulted in ' + tc);
+
+    if (tc == 'NO_SESSION') {
+
+        return this.logoutStaff();
+
+    } else if (tc == 'SUCCESS') {
+
+        displayText = dojo.string.substitute(
+            localeStrings.CHECKIN_SUCCESS, [item]);
+
+        this.displayCheckin(row, result);
+        if (payload.hold)
+            this.printHoldSlip(item, result);
+
+    } else if (tc == 'NO_CHANGE') {
+
+        displayText = dojo.string.substitute(
+            localeStrings.CHECKIN_NO_CHANGE, [item]);
+
+        this.displayCheckin(row, result);
+        if (payload.hold)
+            this.printHoldSlip(item, result);
+
+    } else if (tc == 'ROUTE_ITEM') {
+
+        var dest = fieldmapper.aou.findOrgUnit(result.source.org);
+
+        displayText = dojo.string.substitute(
+            localeStrings.CHECKIN_ROUTE_ITEM, [item, dest.shortname()]);
+
+        this.displayCheckin(row, result, tc + ' => ' + dest.shortname());
+
+        if (result.payload.hold) {
+            this.printHoldTransitSlip(item, result, dest);
+        } else {
+            this.printTransitSlip(item, result, dest);
+        }
+
+    } else if (tc == 'ASSET_COPY_NOT_FOUND') {
+
+        // remove the in-progress row
+        row.parentNode.removeChild(row);
+
+        displayText = dojo.string.substitute(
+            localeStrings.ITEM_NOT_CATALOGED, [item]);
+
+    } else {
+
+        // remove the in-progress row
+        row.parentNode.removeChild(row);
+
+        displayText = dojo.string.substitute(
+            localeStrings.UNKNOWN_ERROR, [tc]);
+    }
+
+    this.handleAlert(displayText, popup, sound);
+    return {};
+}
+
 SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
 
     var displayText = '';
@@ -993,7 +1455,7 @@ SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
                 this.updateHoldsSummary();
             }
 
-            this.updateCircSummary(true);
+            this.updateCircSummary(1);
 
         } else if(action == 'renew') {
 
@@ -1037,7 +1499,7 @@ SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
                 overrideEvents && overrideEvents.length &&
                 overrideEvents.indexOf('COPY_STATUS_LOST') != -1) {
 
-                    if(this.checkin(item)) {
+                    if(this.inlineCheckinCopy(item)) {
                         return { doOver : true };
                     }
             }
@@ -1053,6 +1515,20 @@ SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
 
     } else {
 
+
+        if (this.staffMode) {
+            // in staff mode we override everything.  Transits, however, 
+            // can't be overridden.  They must first be aborted and checked in.
+
+            if(!result.length) result = [result];
+
+            for(var i = 0; i < result.length; i++) {
+                if(result[i].textcode == 'COPY_IN_TRANSIT') {
+                    if(this.inlineCheckinCopy(item, true)) 
+                        return { override : true };
+                }
+            }
+        }
     
         if(overrideEvents && overrideEvents.length) {
             
@@ -1086,7 +1562,7 @@ SelfCheckManager.prototype.handleXactResult = function(action, item, result) {
 
                 if(result[i].textcode == 'COPY_IN_TRANSIT') {
                     // to override a transit, we have to abort the transit and check it in first
-                    if(this.checkin(item, true)) {
+                    if(this.inlineCheckinCopy(item, true)) {
                         return { doOver : true };
                     } else {
                         override = false;