[open-ils-commits] [GIT] Evergreen ILS branch master updated. 46c2449ef4b498486852b46d1ef604b3dfcea0fe

Evergreen Git git at git.evergreen-ils.org
Tue Nov 22 14:11:52 EST 2016


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  46c2449ef4b498486852b46d1ef604b3dfcea0fe (commit)
       via  b6ff1f4c49b827c05bea998abfb9441a97fc2c7f (commit)
       via  88942864cfbb271a68bed9daa0aa7576230c367f (commit)
       via  ea4d93ee46da6d010ad9fc7fbd513cf01ed7a52d (commit)
       via  df4972887b8249bc9d078b32152d8f7aec58aa41 (commit)
       via  705bb8b53ab437124c769440ad569d0a89133138 (commit)
       via  e38d396f62f8ff880434e0a918f36490a65f279b (commit)
       via  cac16285aca52c70213cba9025bc38ca1bd1ae93 (commit)
       via  579d9d4434ed9af44482a6624ede42c982cc50a3 (commit)
       via  3434cf6e447f676df273454ad66834046d241316 (commit)
       via  9d8ae6174eaa6a22a3b113ba77061861379d7c5a (commit)
       via  0d443d4b65b00b409a909f5440e43e49708f9b99 (commit)
       via  e3888ae7d9641545c155ce67aa42735598854c2b (commit)
       via  3261b27da8f9b670e4ab5ea9ceb630525fa92d98 (commit)
       via  2755603bbe6752ee6c1112d1a36f1bd570e5f303 (commit)
       via  ad985ee1228e12be224aff79858e74c3073d71f8 (commit)
       via  b959115fab86a03f38e5a6bd417b57db1d14796c (commit)
       via  0e7377a1394ccee49819a30ba4a7f541408e8d56 (commit)
       via  426115805513e072bf63cd24b2d5aa4f5dbd7447 (commit)
       via  94b634c07c9a26355bde496a4e061f22eb7ea618 (commit)
       via  8d13d28c054f92dc8aacc48d3dbd652418411375 (commit)
       via  6745a4a3c573499af2b95870669ac199101fca95 (commit)
       via  7df921a5fe1b43730e1799bf766dc0b81de664bd (commit)
       via  38de6b12064fdc4fe2cf2b78f10adcceb08347d5 (commit)
       via  91e85f34024f193a886d74038e91a3f2ef1c44b1 (commit)
       via  df82edde99bf84f46d6e79be6ba6f19271c5a631 (commit)
       via  ef23a936b71736f1f44d7b1d16c89c57c7cd4003 (commit)
       via  6bbde46c74b10fcc3684292deedca715574ab135 (commit)
       via  6a14bac329a264804363da4df67f6f28a6b48cfe (commit)
       via  517c68ee3d6eef59f2f3621e0b925f87bc263d01 (commit)
       via  0430fe6b4fbf6562c6d9c76c1a910ae90b47ffd5 (commit)
       via  fec91b57dccdfd6fc40ac963564bbc97596a88b9 (commit)
       via  da642628f59629735597427e1e7aafb4026f0fb6 (commit)
       via  66e33a60da52b3510bdb5e74a56f68be6a6c25c7 (commit)
       via  91522b6386ede3cc82e76fb15f23394ee06e28ba (commit)
       via  14f3c772bf15979087bada5085c61f98b0186e46 (commit)
       via  fe731567ded646d81b2c2103a4fc22114e5887c4 (commit)
       via  b552d92b37829095cd16342b2de3cb7e2800c190 (commit)
       via  58f0446bef1f4d00e1529c69290fb5c3e444a69a (commit)
       via  653d47e74eab4bf4e097447e4de12556a73d23b0 (commit)
       via  f812b3e4093784ac4793cd8f99029083bf2f922f (commit)
       via  a493da6b44285ab18e9625a9e1717aafc2ddf511 (commit)
       via  95e3f60bbc64d15539712fc406ea8912086c9793 (commit)
       via  4be5e57515a6c14eeca59b78040012e5313c5564 (commit)
      from  1fbaa1dde02d7461ffceea542bec7deafd2a99fc (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 46c2449ef4b498486852b46d1ef604b3dfcea0fe
Author: Billy Horn <bhorn at catalystdevworks.com>
Date:   Wed Oct 5 10:41:33 2016 -0700

    LP#1522599 add visual indicator for an expired patron
    
    Add checkAlerts to getPrimary
    Add alert styling to index patron for any active alert
    Add alert styling to expire date in patron summary
    
    Signed-off-by: Billy Horn <bhorn at catalystdevworks.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/circ/patron/index.tt2 b/Open-ILS/src/templates/staff/circ/patron/index.tt2
index ebde904..50c8bd7 100644
--- a/Open-ILS/src/templates/staff/circ/patron/index.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/index.tt2
@@ -68,7 +68,8 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
     <div ng-show="patron()">
       <h4 title="{{patron().id()}}">
         <div class="flex-row">
-          <div class="flex-cell">
+          <div class="flex-cell"
+          ng-class="{'patron-summary-alert' : hasAlerts()}">
             [% l('[_1], [_2] [_3]', 
                 '{{patron().family_name()}}',
                 '{{patron().first_given_name()}}',
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
index 606687d..c67e1de 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
@@ -57,7 +57,8 @@
       <div class="col-md-5">[% l('Create Date') %]</div>
       <div class="col-md-7">{{patron().create_date() | date:'shortDate'}}</div>
     </div>
-    <div class="row">
+    <div class="row"
+      ng-class="{'patron-summary-alert' : isPatronExpired()}">
       <div class="col-md-5">[% l('Expire Date') %]</div>
       <div class="col-md-7">{{patron().expire_date() | date:'shortDate'}}</div>
     </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index bb514c4..bc2f390 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -298,6 +298,7 @@ function($q , $timeout , $location , egCore,  egUser , $locale) {
 
         service.getPrimary(id, user, force)
         .then(function() {
+            service.checkAlerts();
             var p = service.primaryUserPromise;
             service.primaryUserId = null;
             // clear before resolution just to be safe.
@@ -651,6 +652,8 @@ function($scope,  $q,  $location , $filter,  egCore,  egUser,  patronSvc) {
     $scope.patron = function() { return patronSvc.current }
     $scope.patron_stats = function() { return patronSvc.patron_stats }
     $scope.summary_stat_cats = function() { return patronSvc.summary_stat_cats }
+    $scope.hasAlerts = function() { return patronSvc.hasAlerts }
+    $scope.isPatronExpired = function() { return patronSvc.patronExpired }
 
     $scope.print_address = function(addr) {
         egCore.print.print({

commit b6ff1f4c49b827c05bea998abfb9441a97fc2c7f
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Nov 22 10:03:16 2016 -0500

    webstaff: Allow ACQ catalog and link-to-record, er, links to work in a browser
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/acq/common/li_table.js b/Open-ILS/web/js/ui/default/acq/common/li_table.js
index c54346b..851982d 100644
--- a/Open-ILS/web/js/ui/default/acq/common/li_table.js
+++ b/Open-ILS/web/js/ui/default/acq/common/li_table.js
@@ -1786,17 +1786,25 @@ function AcqLiTable() {
 
     this.generateMakeRecTab = function(bib_id,default_view, row) {
         return function() {
-            xulG.new_tab(
-                XUL_OPAC_WRAPPER,
-                {tab_name: localeStrings.XUL_RECORD_DETAIL_PAGE, browser:false},
-                {
-                    no_xulG : false, 
-                    show_nav_buttons : true, 
-                    show_print_button : true, 
-                    opac_url : xulG.url_prefix('opac_rdetail|' + bib_id),
-                    default_view : default_view
+            if(openils.XUL.isXUL() && !window.IAMBROWSER) {
+                xulG.new_tab(
+                    XUL_OPAC_WRAPPER,
+                    {tab_name: localeStrings.XUL_RECORD_DETAIL_PAGE, browser:false},
+                    {
+                        no_xulG : false, 
+                        show_nav_buttons : true, 
+                        show_print_button : true, 
+                        opac_url : xulG.url_prefix('opac_rdetail|' + bib_id),
+                        default_view : default_view
+                    }
+                );
+            } else {
+                var url = '/eg/staff/cat/catalog/record/' + bib_id;
+                if (default_view == 'copy_browser') {
+                    url += '/holdings';
                 }
-            );
+                window.open(url);
+            }
 
             if(row) nodeByName("action_none", row).selected = true;
         }
@@ -3742,9 +3750,14 @@ function AcqLiTable() {
             }
         );
 
-        win = window.open(
-            oilsBasePath + '/acq/lineitem/findbib?query=' + encodeURIComponent(query),
-            '', 'resizable,scrollbars=1,chrome');
+        if(openils.XUL.isXUL() && !window.IAMBROWSER) {
+            win = window.open(
+                oilsBasePath + '/acq/lineitem/findbib?query=' + encodeURIComponent(query),
+                '', 'resizable,scrollbars=1,chrome');
+        } else {
+            win = window.open( oilsBasePath + '/acq/lineitem/findbib?query=' + encodeURIComponent(query));
+        }
+
 
         win.window.recordFound = function(bibId) { 
             win.close();

commit 88942864cfbb271a68bed9daa0aa7576230c367f
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Tue Nov 22 07:41:25 2016 -0500

    webstaff: language tweak in Bill History receipt
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2
index c99cb4d..d8871fe 100644
--- a/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2
+++ b/Open-ILS/src/templates/staff/share/print_templates/t_bills_historical.tt2
@@ -1,5 +1,5 @@
 Welcome to {{current_location.name}}!<br/>
-You have the following bills:
+You had the following bills:
 <hr/>
 <dl>
   <div ng-repeat="xact in transactions">

commit ea4d93ee46da6d010ad9fc7fbd513cf01ed7a52d
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 21:36:15 2016 -0500

    webstaff: add import and export of print templates
    
    The workstation print template page now has Import
    and Export buttons to import or export any customized
    templates from/to JSON files. Toasts are emitted
    on import success or failure and export failure (if
    there are no customized templates to export).
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/workstation/index.tt2 b/Open-ILS/src/templates/staff/admin/workstation/index.tt2
index a0f53d2..2d8b357 100644
--- a/Open-ILS/src/templates/staff/admin/workstation/index.tt2
+++ b/Open-ILS/src/templates/staff/admin/workstation/index.tt2
@@ -9,6 +9,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/file.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/workstation/app.js"></script>
 <script>
 angular.module('egCoreMod').run(['egStrings', function(s) {
@@ -17,6 +18,9 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
   s.DEFAULT_WS_LABEL = '[% l('[_1] (Default)', '{{ws}}') %]';
   s.WS_EXISTS = '[% l("Workstation name already exists.  Use it anyway?") %]';
   s.WS_USED = '[% l("Workstation is already registered") %]';
+  s.PRINT_TEMPLATES_FAIL_EXPORT = "[% l('There are no customized print template to export') %]";
+  s.PRINT_TEMPLATES_SUCCESS_IMPORT = "[% l('Imported one or more print template(s)') %]";
+  s.PRINT_TEMPLATES_FAIL_IMPORT = "[% l('Failed to import any print template(s)') %]";
 }]);
 </script>
 [% END %]
diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2
index 1d6ab02..7448a46 100644
--- a/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2
+++ b/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2
@@ -28,8 +28,17 @@
     </select>
   </div>
   <div class="col-md-7">
-    <div class="pull-right">
-      <button class="btn btn-default" ng-click="save_locally()">[% l('Save Locally') %]</button>
+    <button class="btn btn-default pull-left" ng-click="save_locally()">[% l('Save Locally') %]</button>
+    <div class="btn-group pull-right">
+      <span class="btn btn-default btn-file">
+        [% l('Import') %]
+        <input type="file" eg-file-reader container="imported_print_templates.data">
+      </span>
+      <label class="btn btn-default"
+          eg-json-exporter generator="exportable_templates"
+          default-file-name="'[% l('print_templates.json') %]'">
+          [% l('Export Customized Templates') %]
+      </label>
     </div>
   </div>
   <!-- other stuff -->
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
index 750ed17..dd51bc8 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
@@ -47,6 +47,13 @@ angular.module('egWorkstationAdmin',
     });
 }])
 
+.config(['ngToastProvider', function(ngToastProvider) {
+  ngToastProvider.configure({
+    verticalPosition: 'bottom',
+    animation: 'fade'
+  });
+}])
+
 .factory('workstationSvc',
        ['$q','$timeout','$location','egCore','egConfirmDialog',
 function($q , $timeout , $location , egCore , egConfirmDialog) {
@@ -356,8 +363,8 @@ function($scope , egCore) {
 }])
 
 .controller('PrintTemplatesCtrl',
-       ['$scope','$q','egCore',
-function($scope , $q , egCore) {
+       ['$scope','$q','egCore','ngToast',
+function($scope , $q , egCore , ngToast) {
 
     $scope.print = {
         template_name : 'bills_current',
@@ -510,6 +517,44 @@ function($scope , $q , egCore) {
         );
     }
 
+    $scope.exportable_templates = function() {
+        var templates = {};
+        var deferred = $q.defer();
+        var promises = [];
+        egCore.hatch.getKeys('eg.print.template.').then(function(keys) {
+            angular.forEach(keys, function(key) {
+                promises.push(egCore.hatch.getItem(key).then(function(value) {
+                    templates[key.replace('eg.print.template.', '')] = value;
+                }));
+            });
+            $q.all(promises).then(function() {
+                if (Object.keys(templates).length) {
+                    deferred.resolve(templates);
+                } else {
+                    ngToast.warning(egCore.strings.PRINT_TEMPLATES_FAIL_EXPORT);
+                    deferred.reject();
+                }
+            });
+        });
+        return deferred.promise;
+    }
+
+    $scope.imported_print_templates = { data : '' };
+    $scope.$watch('imported_print_templates.data', function(newVal, oldVal) {
+        if (newVal && newVal != oldVal) {
+            try {
+                var templates = JSON.parse(newVal);
+                angular.forEach(templates, function(template_content, template_name) {
+                    egCore.print.storePrintTemplate(template_name, template_content);
+                });
+                $scope.template_changed(); // refresh
+                ngToast.create(egCore.strings.PRINT_TEMPLATES_SUCCESS_IMPORT);
+            } catch (E) {
+                ngToast.warning(egCore.strings.PRINT_TEMPLATES_FAIL_IMPORT);
+            }
+        }
+    });
+
     $scope.template_changed(); // load the default
 }])
 

commit df4972887b8249bc9d078b32152d8f7aec58aa41
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 21:20:24 2016 -0500

    webstaff: teach egJsonExporter about generators
    
    This patch adds an attribute called 'generator' to the eg-json-exporter
    directive as an alternative to using 'container' to pass a JavaScript
    object. 'generator' should be a function that returns a promise with
    the data to be exported.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/services/file.js b/Open-ILS/web/js/ui/default/staff/services/file.js
index 473f4fe..83e0d29 100644
--- a/Open-ILS/web/js/ui/default/staff/services/file.js
+++ b/Open-ILS/web/js/ui/default/staff/services/file.js
@@ -31,12 +31,20 @@ angular.module('egCoreMod')
     return {
         scope: {
             container: '=',
+            generator: '=',
             defaultFileName: '='
         },
         link: function (scope, element, attributes) {
             element.bind('click', function (clickEvent) {
-                var data = new Blob([JSON.stringify(scope.container)], {type : 'application/json'});
-                FileSaver.saveAs(data, scope.defaultFileName);
+                if (scope.generator) {
+                    scope.generator().then(function(value) {
+                        var data = new Blob([JSON.stringify(value)], {type : 'application/json'});
+                        FileSaver.saveAs(data, scope.defaultFileName);
+                    });
+                } else {
+                    var data = new Blob([JSON.stringify(scope.container)], {type : 'application/json'});
+                    FileSaver.saveAs(data, scope.defaultFileName);
+                }
             });
         }
     }

commit 705bb8b53ab437124c769440ad569d0a89133138
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Nov 21 08:57:58 2016 -0500

    webstaff: tweak default Hold Shelf List print template
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_hold_shelf_list.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_hold_shelf_list.tt2
index ce23fe4..34441cd 100644
--- a/Open-ILS/src/templates/staff/share/print_templates/t_hold_shelf_list.tt2
+++ b/Open-ILS/src/templates/staff/share/print_templates/t_hold_shelf_list.tt2
@@ -1,4 +1,4 @@
-<table id='pull-list-template-table'>
+<table id='shelf-list-template-table'>
   <style>
     #pull-list-template-table td,
     #pull-list-template-table th {
@@ -8,20 +8,28 @@
   </style>
   <thead>
     <tr>
+      <th>[% l('Patron') %]</th>
       <th>[% l('Type') %]</th>
       <th>[% l('Title') %]</th>
       <th>[% l('Author') %]</th>
-      <th>[% l('Shelf Location') %]</th>
       <th>[% l('Call Number') %]</th>
       <th>[% l('Barcode/Part') %]</th>
     </tr>
   </thead>
   <tbody>
     <tr ng-repeat="hold_data in holds">
+      <td>
+        <span ng-if="hold.patron_alias">{{hold_data.patron_alias}}</span>
+        <span ng-if="!hold.patron_alias">
+          [% l('[_1], [_2]',
+            '{{hold_data.patron_last}}',
+            '{{hold_data.patron_first}}') %]
+        </span>
+      </td>
+      </td>
       <td>{{hold_data.hold.hold_type}}</td>
       <td>{{hold_data.title}}</td>
       <td>{{hold_data.author}}</td>
-      <td>{{hold_data.copy.location.name}}</td>
       <td>{{hold_data.volume.label}}</td>
       <td>{{hold_data.copy.barcode}} {{hold_data.part.label}}</td>
     </tr>

commit e38d396f62f8ff880434e0a918f36490a65f279b
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Nov 21 08:09:19 2016 -0500

    webstaff: fix template ref for Holds for Bib Record
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2
index c3f17a1..1d6ab02 100644
--- a/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2
+++ b/Open-ILS/src/templates/staff/admin/workstation/t_print_templates.tt2
@@ -18,7 +18,7 @@
       <option value="checkout">[% l('Checkout') %]</option>
       <option value="hold_transit_slip">[% l('Hold Transit Slip') %]</option>
       <option value="hold_shelf_slip">[% l('Hold Shelf Slip') %]</option>
-      <option value="holds_for_bibs">[% l('Holds for Bib Record') %]</option>
+      <option value="holds_for_bib">[% l('Holds for Bib Record') %]</option>
       <option value="holds_for_patron">[% l('Holds for Patron') %]</option>
       <option value="hold_pull_list">[% l('Hold Pull List') %]</option>
       <option value="hold_shelf_list">[% l('Hold Shelf List') %]</option>

commit cac16285aca52c70213cba9025bc38ca1bd1ae93
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 18:43:53 2016 -0500

    webstaff: alphabetize entries on acq admin splash page
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
index f9670fd..a873724 100644
--- a/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
@@ -9,7 +9,13 @@
 
 [%
     interfaces = [
-     [ l('Currency Types'), "./admin/acq/currency_type/list" ]
+     [ l('Cancel Reasons'), "./admin/acq/conify/cancel_reason" ]
+    ,[ l('Claim Event Types'), "./admin/acq/conify/claim_event_type" ]
+    ,[ l('Claim Policies'), "./admin/acq/conify/claim_policy" ]
+    ,[ l('Claim Policy Actions'), "./admin/acq/conify/claim_policy_action" ]
+    ,[ l('Claim Types'), "./admin/acq/conify/claim_type" ]
+    ,[ l('Invoice Item Types'), "./admin/acq/conify/invoice_item_type" ]
+    ,[ l('Currency Types'), "./admin/acq/currency_type/list" ]
     ,[ l('Distribution Formulas'), "./admin/acq/conify/distribution_formula" ]
     ,[ l('EDI Accounts'), "./admin/acq/conify/edi_account" ]
     ,[ l('EDI Messages'), "./admin/acq/po/edi_messages" ]
@@ -17,16 +23,10 @@
     ,[ l('Fund Tags'), "./admin/acq/conify/fund_tag" ]
     ,[ l('Funding Sources'), "./admin/acq/funding_source/list" ]
     ,[ l('Funds'), "./admin/acq/fund/list" ]
-    ,[ l('Providers'), "./admin/acq/conify/provider" ]
-    ,[ l('Claim Event Types'), "./admin/acq/conify/claim_event_type" ]
-    ,[ l('Claim Policies'), "./admin/acq/conify/claim_policy" ]
-    ,[ l('Claim Policy Actions'), "./admin/acq/conify/claim_policy_action" ]
-    ,[ l('Claim Types'), "./admin/acq/conify/claim_type" ]
-    ,[ l('Invoice Item Types'), "./admin/acq/conify/invoice_item_type" ]
     ,[ l('Invoice Payment Method'), "./admin/acq/conify/invoice_payment_method" ]
-    ,[ l('Cancel Reasons'), "./admin/acq/conify/cancel_reason" ]
     ,[ l('Line Item Alerts'), "./admin/acq/conify/lineitem_alert" ]
     ,[ l('Line Item MARC Attribute Definitions'), "./admin/acq/conify/lineitem_marc_attr_def" ]
+    ,[ l('Providers'), "./admin/acq/conify/provider" ]
    ];
 
    USE table(interfaces, cols=3);

commit 579d9d4434ed9af44482a6624ede42c982cc50a3
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 18:42:15 2016 -0500

    webstaff: improve column collapse on acq admin splash page
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
index 4723795..f9670fd 100644
--- a/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
@@ -29,21 +29,24 @@
     ,[ l('Line Item MARC Attribute Definitions'), "./admin/acq/conify/lineitem_marc_attr_def" ]
    ];
 
-   USE table(interfaces, rows=9);
+   USE table(interfaces, cols=3);
 %]
 
-[% FOREACH row = table.rows %]
-  <div class="row new-entry">
-    [% FOREACH item = row %][% IF item.1 %]
+<div class="row">
+[% FOREACH col = table.cols %]
     <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="[% item.1 %]">
-        [% item.0 %]
-      </a>
-    </div>
+    [% FOREACH item = col %][% IF item.1 %]
+        <div class="row new-entry">
+            <div class="col-md-12">
+            <span class="glyphicon glyphicon-pencil"></span>
+            <a target="_self" href="[% item.1 %]">
+                [% item.0 %]
+            </a>
+            </div>
+        </div>
     [% END %][% END %]
-  </div>
+    </div>
 [% END %]
-
 </div>
 
+</div>

commit 3434cf6e447f676df273454ad66834046d241316
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 18:38:39 2016 -0500

    webstaff: improve column collapse on server admin splash page
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/server/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/server/t_splash.tt2
index 231dd5c..1932120 100644
--- a/Open-ILS/src/templates/staff/admin/server/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/server/t_splash.tt2
@@ -60,20 +60,24 @@
     ,[ l('Z39.50 Servers'), "./admin/server/config/z3950_source" ]
    ];
 
-   USE table(interfaces, rows=17);
+   USE table(interfaces, cols=3);
 %]
 
-[% FOREACH row = table.rows %]
-  <div class="row new-entry">
-    [% FOREACH item = row %][% IF item.1 %]
+<div class="row">
+[% FOREACH col = table.cols %]
     <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="[% item.1 %]">
-        [% item.0 %]
-      </a>
-    </div>
+    [% FOREACH item = col %][% IF item.1 %]
+        <div class="row new-entry">
+            <div class="col-md-12">
+            <span class="glyphicon glyphicon-pencil"></span>
+            <a target="_self" href="[% item.1 %]">
+                [% item.0 %]
+            </a>
+            </div>
+        </div>
     [% END %][% END %]
-  </div>
+    </div>
 [% END %]
+</div>
 
 </div>

commit 9d8ae6174eaa6a22a3b113ba77061861379d7c5a
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 18:30:33 2016 -0500

    webstaff: improve column collapse on local admin splash page
    
    With this patch, when the local admin splash page is displayed
    on small screens, the three columns are stacked better. E.g.,
    
    A  D  G
    B  E  H
    C  F
    
    becomes
    
    A
    B
    C
    D
    E
    F
    G
    H
    
    rather than
    
    A
    D
    G
    B
    E
    H
    C
    F
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index 58bc231..22878a1 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -38,21 +38,26 @@
     ,[ l('Work Log'), "./admin/workstation/log" ]
    ];
 
-   USE table(interfaces, rows=9);
+   USE table(interfaces, cols=3);
 %]
 
-[% FOREACH row = table.rows %]
-  <div class="row new-entry">
-    [% FOREACH item = row %][% IF item.1 %]
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="[% item.1 %]">
-        [% item.0 %]
-      </a>
-    </div>
-    [% END %][% END %]
-  </div>
-[% END %]
+<div class="row">
+    [% FOREACH col = table.cols %]
+        <div class="col-md-4">
+        [% FOREACH item = col %][% IF item.1 %]
+        <div class="row new-entry">
+            <div class="col-md-12">
+                <span class="glyphicon glyphicon-pencil"></span>
+                <a target="_self" href="[% item.1 %]">
+                    [% item.0 %]
+                </a>
+            </div>
+        </div>
+        [% END %]
+    [% END %]
+        </div>
+    [% END %]
+</div>
 
 </div>
 

commit 0d443d4b65b00b409a909f5440e43e49708f9b99
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Nov 17 15:38:17 2016 -0500

    add Print Bills to Bill History
    
    FIXME: duplicated a code chunk, should probably refactor into a shared printBills
    FIXME: disable actions or make otherwise obvious that they only work on selected rows
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_bill_history_xacts.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_bill_history_xacts.tt2
index 70f48b6..accbd36 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_bill_history_xacts.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_bill_history_xacts.tt2
@@ -10,6 +10,8 @@
       label="[% l('Add Billing') %]" handler="addBilling"></eg-grid-action>
     <eg-grid-action 
       label="[% l('Full Details') %]" handler="showFullDetails"></eg-grid-action>
+    <eg-grid-action
+      label="[% l('Print Bills') %]" handler="printBills"></eg-grid-action>
 
     <eg-grid-field path="summary.balance_owed"></eg-grid-field>
     <eg-grid-field path="id" label="[% l('Bill #') %]"></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
index 8d2801d..37c2a18 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
@@ -775,6 +775,39 @@ function($scope,  $q , egCore , patronSvc , billSvc , egPromptDialog , $location
             })
         }
     }
+
+    $scope.printBills = function(selected) { // FIXME: refactor me
+        if (!selected.length) return;
+        // bills print receipt assumes nested hashes, but our grid
+        // stores flattener data.  Fetch the selected xacts as
+        // fleshed pcrud objects and hashify.  
+        // (Consider an alternate approach..)
+        var ids = selected.map(function(t){ return t.id });
+        var xacts = [];
+        egCore.pcrud.search('mbt', 
+            {id : ids},
+            {flesh : 1, flesh_fields : {'mbt' : ['summary']}},
+            {authoritative : true}
+        ).then(
+            function() {
+                egCore.print.print({
+                    context : 'receipt', 
+                    template : 'bills_historical', 
+                    scope : {   
+                        transactions : xacts,
+                        current_location : egCore.idl.toHash(
+                            egCore.org.get(egCore.auth.user().ws_ou()))
+                    }
+                });
+            }, 
+            null, 
+            function(xact) {
+                xacts.push(egCore.idl.toHash(xact));
+            }
+        );
+    }
+
+
 }])
 
 .controller('BillPaymentHistoryCtrl',

commit e3888ae7d9641545c155ce67aa42735598854c2b
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Nov 17 14:39:22 2016 -0500

    webstaff: fix Payment Method in receipt
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
index 35bab20..8d2801d 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
@@ -331,6 +331,7 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
 
         // page data not yet refreshed, capture data from current scope
         var print_data = {
+            payment_type : type,
             payment_note : note,
             previous_balance : Number($scope.summary.balance_owed()),
             payment_total : Number($scope.payment_amount),

commit 3261b27da8f9b670e4ab5ea9ceb630525fa92d98
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 17:14:54 2016 -0500

    webstaff: toast on volume/copy template save and delete
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/index.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/index.tt2
index 132e21a..97996b7 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/index.tt2
@@ -11,6 +11,12 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/volcopy/app.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+    s.VOL_COPY_TEMPLATE_SUCCESS_SAVE = "[% l('Saved volume/copy template(s)') %]";
+    s.VOL_COPY_TEMPLATE_SUCCESS_DELETE = "[% l('Deleted volume/copy template') %]";
+}]);
+</script>
 [% END %]
 
 <style>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 79b568a..2f423de 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -11,6 +11,13 @@ angular.module('egVolCopy',
     }
 })
 
+.config(['ngToastProvider', function(ngToastProvider) {
+  ngToastProvider.configure({
+    verticalPosition: 'bottom',
+    animation: 'fade'
+  });
+}])
+
 .config(function($routeProvider, $locationProvider, $compileProvider) {
     $locationProvider.html5Mode(true);
     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
@@ -1604,8 +1611,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         scope: {
             editTemplates: '=',
         },
-        controller : ['$scope','$window','itemSvc','egCore',
-            function ( $scope , $window , itemSvc , egCore ) {
+        controller : ['$scope','$window','itemSvc','egCore','ngToast',
+            function ( $scope , $window , itemSvc , egCore , ngToast) {
 
                 $scope.defaults = { // If defaults are not set at all, allow everything
                     barcode_checkdigit : false,
@@ -1704,6 +1711,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                         $scope.template_name = '';
                         egCore.hatch.setItem('cat.copy.templates', $scope.templates);
                         $scope.$parent.fetchTemplates();
+                        ngToast.create(egCore.strings.VOL_COPY_TEMPLATE_SUCCESS_DELETE);
                     }
                 }
 
@@ -1732,6 +1740,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                         egCore.hatch.setItem('cat.copy.templates', $scope.templates);
                         $scope.$parent.fetchTemplates();
                     }
+                    ngToast.create(egCore.strings.VOL_COPY_TEMPLATE_SUCCESS_SAVE);
                 }
             
                 $scope.templates = {};

commit 2755603bbe6752ee6c1112d1a36f1bd570e5f303
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 16:59:21 2016 -0500

    webstaff: fix a couple thinkos is vol/copy template editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
index f17be86..38dde8f 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
@@ -6,7 +6,7 @@
         <div class="col-md-2">
             <eg-basic-combo-box list="template_name_list" selected="template_name"></eg-basic-combo-box>
         </div>
-        <div class="col-md-1" ng-if="!editTemplates">
+        <div class="col-md-1">
             <button class="btn btn-default " ng-click="applyTemplate(template_name)" type="button">[% l('Apply') %]</button>
         </div>
         <div class="col-md-6">
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
index 38ebb53..d033666 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
@@ -12,28 +12,30 @@ eg-navbar {
 
 <!-- tabbed copy data view -->
 
-<ul class="nav nav-tabs" ng-show="!embedded" ng-if="!edit_templates">
-  <li ng-class="{active : tab == 'edit'}">
-    <a ng-click="tab = 'edit'" >[% l('Edit') %]</a>
-  </li>
-  <li ng-class="{active : tab == 'templates'}">
-    <a ng-click="tab = 'templates'" >[% l('Copy Templates') %]</a>
-  </li>
-  <li ng-class="{active : tab == 'defaults'}">
-    <a ng-click="tab = 'defaults'" >[% l('Defaults') %]</a>
-  </li>
-</ul>
-
-<div class="tab-content" ng-if="!edit_templates">
-  <div class="tab-pane active">
-    <div ng-show="tab == 'edit'">
-      <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_edit'"></div>
-    </div>
-    <div ng-show="tab == 'templates'">
-      <eg-vol-template></eg-vol-template>
-    </div>
-    <div ng-show="tab == 'defaults'">
-      <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_defaults'"></div>
+<div ng-if="!edit_templates">
+  <ul class="nav nav-tabs" ng-show="!embedded">
+    <li ng-class="{active : tab == 'edit'}">
+      <a ng-click="tab = 'edit'" >[% l('Edit') %]</a>
+    </li>
+    <li ng-class="{active : tab == 'templates'}">
+      <a ng-click="tab = 'templates'" >[% l('Copy Templates') %]</a>
+    </li>
+    <li ng-class="{active : tab == 'defaults'}">
+      <a ng-click="tab = 'defaults'" >[% l('Defaults') %]</a>
+    </li>
+  </ul>
+  
+  <div class="tab-content">
+    <div class="tab-pane active">
+      <div ng-show="tab == 'edit'">
+        <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_edit'"></div>
+      </div>
+      <div ng-show="tab == 'templates'">
+        <eg-vol-template></eg-vol-template>
+      </div>
+      <div ng-show="tab == 'defaults'">
+        <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_defaults'"></div>
+      </div>
     </div>
   </div>
 </div>

commit ad985ee1228e12be224aff79858e74c3073d71f8
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 15:40:39 2016 -0500

    webstaff: tweaks to egPrint
    
    This patch attempts to unbreak receipt and CSV printing
    by fetching and injecting the print CSS into a style element,
    as a link element in the print div doesn't seem to get
    processed.  It also automatically clears the content
    of the print div so that after you print a receipt, the
    browser print command will print the page, not the receipt.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/services/print.js b/Open-ILS/web/js/ui/default/staff/services/print.js
index 960eb1d..30a1751 100644
--- a/Open-ILS/web/js/ui/default/staff/services/print.js
+++ b/Open-ILS/web/js/ui/default/staff/services/print.js
@@ -91,11 +91,12 @@ function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg , eg
                         // (absorption) for browser printing
                         return service.ingest_print_content(
                             args.content_type, args.content, args.scope
-                        ).then(function() { $window.print() });
+                        ).then(function() { $window.print(); service.clear_print_content(); });
                     } else {
                         // HTML content is already ingested and accessible
                         // within the page to the printer.  
                         $window.print();
+                        service.clear_print_content();
                     }
                 }
             );
@@ -152,7 +153,7 @@ function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg , eg
 // option will always result in empty pages.  Move the print CSS
 // out of the standalone CSS file and put it into a template file
 // for this directive.
-.directive('egPrintContainer', ['$compile', function($compile) {
+.directive('egPrintContainer', ['$compile', '$http', function($compile, $http) {
     return {
         restrict : 'AE',
         scope : {}, // isolate our scope
@@ -163,13 +164,34 @@ function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg , eg
                    ['$scope','$q','$window','$timeout','egHatch','egPrint','egEnv',
             function($scope , $q , $window , $timeout , egHatch , egPrint , egEnv) {
 
+                egPrint.clear_print_content = function() {
+                    $scope.elm.html('');
+                    $compile($scope.elm.contents())($scope.$new(true));
+                }
+
                 egPrint.ingest_print_content = function(type, content, printScope) {
 
                     if (type == 'text/csv' || type == 'text/plain') {
                         // preserve newlines, spaces, etc.
-                        content = '<link rel="stylesheet" href="'+ egEnv.basePath + 'css/print.css" type="text/css" media="print" /><pre>' + content + '</pre>';
+                        content = '<pre>' + content + '</pre>';
                     }
 
+                    return $http.get(egEnv.basePath + 'css/print.css').then(
+                        function(response) {
+                            content = '<style type="text/css" media="print">' +
+                                      response.data +
+                                      '</style>' +
+                                      content;
+                            return finish_ingest_print_content(type, content, printScope);
+                        },
+                        function() {
+                            return finish_ingest_print_content(type, content, printScope);
+                        }
+                    );
+
+                }
+
+                function finish_ingest_print_content(type, content, printScope) {
                     $scope.elm.html(content);
 
                     var sub_scope = $scope.$new(true);

commit b959115fab86a03f38e5a6bd417b57db1d14796c
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Nov 15 14:27:20 2016 -0500

    webstaff transit list: switch to egGridFlatDataProvider
    
    This gets us sorting "for free", or at least more easily than
    with a custom-written data provider.  The following fields
    are marked sortable for now:
    
    - barcode
    - title
    - source library
    - destination library
    - send date/time
    
    Because egGridFlatDataProvider gives us flattened hashes, not
    fieldmapper objects, various tweaks were required to compensate.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/circ/transits/t_list.tt2 b/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
index 5c240db..6d598f0 100644
--- a/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
@@ -29,7 +29,6 @@
   id-field="id"
   idl-class="atc"
   features="-sort,-multisort"
-  items-provider="grid_data_provider"
   grid-controls="grid_controls"
   persist-key="circ.transits.list"
 >
@@ -42,21 +41,24 @@
   <eg-grid-menu-item handler="abort_transit" 
     label="[% l('Abort Transit') %]"></eg-grid-menu-item>
 
-  <eg-grid-field path='id' hidden></eg-grid-field>
-  <eg-grid-field path='target_copy.barcode' label="[% l('Barcode') %]">
-    <a target="_self" href='./cat/item/{{item.target_copy().id()}}'>{{item.target_copy().barcode()}}</a>
+  <eg-grid-field path='id' hidden required></eg-grid-field>
+  <eg-grid-field path='target_copy.id' hidden required></eg-grid-field>
+  <eg-grid-field path='target_copy.call_number.record.simple_record.id' hidden required></eg-grid-field>
+  <eg-grid-field path='hold_transit_copy.hold.id' hidden required></eg-grid-field>
+  <eg-grid-field path='target_copy.barcode' label="[% l('Barcode') %]" sortable>
+    <a target="_self" href="./cat/item/{{item['target_copy.id']}}">{{item['target_copy.barcode']}}</a>
   </eg-grid-field>
   <eg-grid-field path='target_copy.circ_lib.shortname' hidden></eg-grid-field>
   <eg-grid-field path='target_copy.call_number.label' hidden></eg-grid-field>
-  <eg-grid-field path='target_copy.call_number.record.simple_record.title' label="[% l('Title') %]">
-    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.target_copy().call_number().record().simple_record().id()}}">
-      {{item.target_copy().call_number().record().simple_record().title()}}
+  <eg-grid-field path='target_copy.call_number.record.simple_record.title' label="[% l('Title') %]" sortable>
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item['target_copy.call_number.record.simple_record.id']}}">
+      {{item['target_copy.call_number.record.simple_record.title']}}
     </a>
   </eg-grid-field>
   <eg-grid-field path='target_copy.call_number.record.simple_record.author' hidden></eg-grid-field>
-  <eg-grid-field path='source.shortname' label="[% l('Source Library') %]"></eg-grid-field>
-  <eg-grid-field path='dest.shortname' label="[% l('Destination Library') %]"></eg-grid-field>
-  <eg-grid-field path='source_send_time' dateformat='short''></eg-grid-field>
+  <eg-grid-field path='source.shortname' label="[% l('Source Library') %]" sortable></eg-grid-field>
+  <eg-grid-field path='dest.shortname' label="[% l('Destination Library') %]" sortable></eg-grid-field>
+  <eg-grid-field path='source_send_time' dateformat='short' sortable></eg-grid-field>
   <eg-grid-field path='hold_transit_copy.hold.hold_type'></eg-grid-field>
   <eg-grid-field path='hold_transit_copy.hold.request_time' hidden></eg-grid-field>
   <eg-grid-field path='hold_transit_copy.hold.capture_time' hidden></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/transits.js b/Open-ILS/web/js/ui/default/staff/circ/services/transits.js
index ec2499e..001de63 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/transits.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/transits.js
@@ -22,7 +22,7 @@ function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
                     $scope.num_transits = transits.length;
                     $scope.num_hold_transits = 0;
                     angular.forEach(transits, function(t) {
-                        if (t.hold_transit_copy()) {
+                        if (t['hold_transit_copy.hold.id']) {
                             $scope.num_hold_transits++;
                         }
                     });
@@ -42,7 +42,7 @@ function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
                             }
                             egCore.net.request(
                                 'open-ils.circ', 'open-ils.circ.transit.abort',
-                                egCore.auth.token(), { 'transitid' : transit.id() }
+                                egCore.auth.token(), { 'transitid' : transit['id'] }
                             ).then(function(resp) {
                                 if (evt = egCore.evt.parse(resp)) {
                                     egCore.audio.play('warning.transit.abort_failed');
diff --git a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
index e11a77a..5e6550a 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
@@ -21,9 +21,6 @@ angular.module('egTransitListApp',
        ['$scope','$q','$routeParams','$window','egCore','egTransits','egGridDataProvider','$uibModal','$timeout',
 function($scope , $q , $routeParams , $window , egCore , egTransits , egGridDataProvider , $uibModal , $timeout) {
 
-    var transits = [];
-    var provider = egGridDataProvider.instance({});
-    $scope.grid_data_provider = provider;
     $scope.transit_direction = 'to';
 
     function init_dates() {
@@ -90,10 +87,10 @@ function($scope , $q , $routeParams , $window , egCore , egTransits , egGridData
         abort_transit(transits);
     }
 
-    $scope.add_copies_to_bucket = function(transits) {
+    $scope.add_copies_to_bucket = function() {
         var copy_list = [];
         angular.forEach($scope.grid_controls.selectedItems(), function(transit) {
-            copy_list.push(transit.target_copy().id());
+            copy_list.push(transit['target_copy.id']);
         });
         if (copy_list.length == 0) return;
 
@@ -167,8 +164,8 @@ function($scope , $q , $routeParams , $window , egCore , egTransits , egGridData
         angular.forEach(
             $scope.grid_controls.selectedItems(),
             function (item) {
-                if (rid_list.indexOf(item.target_copy().call_number().record().simple_record().id()) == -1)
-                    rid_list.push(item.target_copy().call_number().record().simple_record().id());
+                if (rid_list.indexOf(item['target_copy.call_number.record.simple_record.id']) == -1)
+                    rid_list.push(item['target_copy.call_number.record.simple_record.id']);
             }
         );
         return rid_list;
@@ -178,8 +175,8 @@ function($scope , $q , $routeParams , $window , egCore , egTransits , egGridData
         angular.forEach(
             $scope.grid_controls.selectedItems(),
             function (item) {
-                if (rid && item.target_copy().call_number().record().simple_record().id() != rid) return;
-                cp_id_list.push(item.target_copy().id());
+                if (rid && item['target_copy.call_number.record.simple_record.id'] != rid) return;
+                cp_id_list.push(item['target_copy.id']);
             }
         );
         return cp_id_list;
@@ -212,54 +209,24 @@ function($scope , $q , $routeParams , $window , egCore , egTransits , egGridData
         spawnHoldingsEdit(true, false);
     }
 
-    $scope.grid_controls = {
-        activateItem : load_item
-    }
-
-    function refresh_page() {
-        transits = [];
-        provider.refresh();
-    }
-
-    provider.get = function(offset, count) {
-        var deferred = $q.defer();
-        var recv_index = 0;
-
+    function current_query() {
         var filter = {
             'source_send_time' : { 'between' : date_range() },
             'dest_recv_time'   : null
         };
         if ($scope.transit_direction == 'to') { filter['dest'] = $scope.context_org.id(); }
         if ($scope.transit_direction == 'from') { filter['source'] = $scope.context_org.id(); }
+        return filter;
+    }
 
-        egCore.pcrud.search('atc',
-            filter, {
-                'flesh' : 5,
-                // atc -> target_copy       -> call_number -> record -> simple_record
-                // atc -> hold_transit_copy -> hold        -> usr    -> card
-                'flesh_fields' : {
-                    'atc' : ['target_copy','dest','source','hold_transit_copy'],
-                    'acp' : ['call_number','location','circ_lib'],
-                    'acn' : ['record'],
-                    'bre' : ['simple_record'],
-                    'ahtc' : ['hold'],
-                    'ahr' : ['usr'],
-                    'au' : ['card']
-                },
-                'select' : { 'bre' : ['id'] },
-                order_by : { atc : 'source_send_time' },
-                limit  : count,
-                offset : offset,
-            }
-        ).then(
-            deferred.resolve, null, 
-            function(transit) {
-                transits[offset + recv_index++] = transit;
-                deferred.notify(transit);
-            }
-        );
+    $scope.grid_controls = {
+        activateItem : load_item,
+        setQuery : current_query
+    }
 
-        return deferred.promise;
+    function refresh_page() {
+        $scope.grid_controls.setQuery(current_query());
+        $scope.grid_controls.refresh();
     }
 
     $scope.context_org = egCore.org.get(egCore.auth.user().ws_ou());

commit 0e7377a1394ccee49819a30ba4a7f541408e8d56
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Nov 14 16:44:25 2016 -0500

    better idea with Check Number
    
    unset default Check Number, disable widget if not Check Payment,
    and disable Apply Payment button if Check Payment with invalid Check Number
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2
index 99abe6f..4378e32 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_bills.tt2
@@ -64,7 +64,8 @@
           </label>
           <div class="col-md-6">
             <input type="number" min="1" step="any" id="check-input" 
-              ng-model="check_number" focus-me="focus_check" 
+              ng-model="check_number" focus-me="focus_check"
+              ng-disabled="payment_type!='check_payment'"
               value="" class="form-control col-md-6 "/>
           </div>
         <div class="form-group">
@@ -86,7 +87,7 @@
             <button
                 type="submit"
                 class="btn btn-default"
-                ng-disabled="!gridControls.selectedItems().length"
+                ng-disabled="invalid_check_number() || !gridControls.selectedItems().length"
             >[% l('Apply Payment') %]</button>
           </div>
         </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
index 4412c97..35bab20 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
@@ -140,7 +140,7 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
     billSvc.userId = $routeParams.id;
 
     // set up some defaults
-    $scope.check_number = 1;
+    $scope.check_number = null;
     $scope.payment_amount = null;
     $scope.session_voided = 0;
     $scope.payment_type = 'cash_payment';
@@ -229,6 +229,9 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
         });
         return -(amount / 100);
     }
+    $scope.invalid_check_number = function() { 
+        return $scope.payment_type == 'check_payment' && ! $scope.check_number; 
+    }
 
     // update the item.payment_pending value each time the user
     // selects different transactions to pay against.

commit 426115805513e072bf63cd24b2d5aa4f5dbd7447
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Nov 14 16:03:56 2016 -0500

    webstaff: default Check Number to 1
    
    instead of 0, so that it doesn't speedbump payments (of any type)
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
index 1245a2b..4412c97 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/bills.js
@@ -140,7 +140,7 @@ function($scope , $q , $routeParams , egCore , egConfirmDialog , $location,
     billSvc.userId = $routeParams.id;
 
     // set up some defaults
-    $scope.check_number = 0;
+    $scope.check_number = 1;
     $scope.payment_amount = null;
     $scope.session_voided = 0;
     $scope.payment_type = 'cash_payment';

commit 94b634c07c9a26355bde496a4e061f22eb7ea618
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Nov 4 13:55:23 2016 -0400

    Allow system printing to work normally by delaying stylesheet inclusion
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/base.tt2 b/Open-ILS/src/templates/staff/base.tt2
index 7f4c64a..93ada15 100644
--- a/Open-ILS/src/templates/staff/base.tt2
+++ b/Open-ILS/src/templates/staff/base.tt2
@@ -20,7 +20,6 @@
     [% END %]
     <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/cat.css" />
     <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/style.css" />
-    <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/print.css" type="text/css" media="print" />
   </head>
   <body>
     <toast></toast>
diff --git a/Open-ILS/web/js/ui/default/staff/services/print.js b/Open-ILS/web/js/ui/default/staff/services/print.js
index fa60193..960eb1d 100644
--- a/Open-ILS/web/js/ui/default/staff/services/print.js
+++ b/Open-ILS/web/js/ui/default/staff/services/print.js
@@ -6,8 +6,8 @@
 angular.module('egCoreMod')
 
 .factory('egPrint',
-       ['$q','$window','$timeout','$http','egHatch','egAuth','egIDL','egOrg',
-function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg) {
+       ['$q','$window','$timeout','$http','egHatch','egAuth','egIDL','egOrg','egEnv',
+function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg , egEnv) {
 
     var service = {};
 
@@ -160,14 +160,14 @@ function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg) {
             scope.elm = element;
         },
         controller : 
-                   ['$scope','$q','$window','$timeout','egHatch','egPrint',
-            function($scope , $q , $window , $timeout , egHatch , egPrint) {
+                   ['$scope','$q','$window','$timeout','egHatch','egPrint','egEnv',
+            function($scope , $q , $window , $timeout , egHatch , egPrint , egEnv) {
 
                 egPrint.ingest_print_content = function(type, content, printScope) {
 
                     if (type == 'text/csv' || type == 'text/plain') {
                         // preserve newlines, spaces, etc.
-                        content = '<pre>' + content + '</pre>';
+                        content = '<link rel="stylesheet" href="'+ egEnv.basePath + 'css/print.css" type="text/css" media="print" /><pre>' + content + '</pre>';
                     }
 
                     $scope.elm.html(content);

commit 8d13d28c054f92dc8aacc48d3dbd652418411375
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Nov 4 13:17:24 2016 -0400

    implement item attributes editor AKA volume/copy template editor
    
    Add a specialization of the volume/copy editor that is restricted
    just to managing volume/copy templates; this can be accessed
    at the path /eg/staff/cat/volcopy/edit_templates.  A link is added
    to the local administration splash page with the label "Volume/Copy
    Template Editor"
    
    In attempt to improve terminology, on the local administration page,
    the previous "Copy Template Editor" is now called "Serial Copy Template
    Editor".
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index aba2290..58bc231 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -20,7 +20,6 @@
     ,[ l('Copy Location Groups'), "./admin/local/asset/copy_location_group" ]
     ,[ l('Copy Location Order'), "./admin/local/asset/copy_location_order" ]
     ,[ l('Copy Locations Editor'), "./admin/local/asset/copy_locations" ]
-    ,[ l('Copy Template Editor'), "./admin/local/asset/copy_template" ]
     ,[ l('Field Documentation'), "./admin/local/config/idl_field_doc" ]
     ,[ l('Group Penalty Thresholds'), "./admin/local/permission/grp_penalty_threshold" ]
     ,[ l('Hold Policies'), "./admin/local/config/hold_matrix_matchpoint" ]
@@ -29,11 +28,13 @@
     ,[ l('Notifications / Action Triggers'), "./admin/local/action_trigger/event_definition" ]
     ,[ l('Patrons with Negative Balances'), "./admin/local/circ/neg_balance_users" ]
     ,[ l('Search Filter Groups'), "./admin/local/actor/search_filter_group" ]
+    ,[ l('Serial Copy Template Editor'), "./admin/local/asset/copy_template" ]
     ,[ l('Standing Penalties'), "./admin/local/config/standing_penalty" ]
     ,[ l('Statistical Categories Editor'), "./admin/local/asset/stat_cat_editor" ]
     ,[ l('Statistical Popularity Badges'), "./admin/local/rating/badge" ]
     ,[ l('Surveys'), "./admin/local/action/survey" ]
     ,[ l('Transit List'), "./circ/transits/list" ]
+    ,[ l('Volume/Copy Template Editor'), "./cat/volcopy/edit_templates" ]
     ,[ l('Work Log'), "./admin/workstation/log" ]
    ];
 
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
index 38dde8f..f17be86 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
@@ -6,7 +6,7 @@
         <div class="col-md-2">
             <eg-basic-combo-box list="template_name_list" selected="template_name"></eg-basic-combo-box>
         </div>
-        <div class="col-md-1">
+        <div class="col-md-1" ng-if="!editTemplates">
             <button class="btn btn-default " ng-click="applyTemplate(template_name)" type="button">[% l('Apply') %]</button>
         </div>
         <div class="col-md-6">
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
index 1cfa4e0..38ebb53 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
@@ -7,12 +7,12 @@ eg-navbar {
 }
 </style>
 
-<eg-record-summary ng-if="!embedded && record_id"
+<eg-record-summary ng-if="!embedded && !edit_templates && record_id"
      record-id="record_id" record="summaryRecord"></eg-record-summary>
 
 <!-- tabbed copy data view -->
 
-<ul class="nav nav-tabs" ng-show="!embedded">
+<ul class="nav nav-tabs" ng-show="!embedded" ng-if="!edit_templates">
   <li ng-class="{active : tab == 'edit'}">
     <a ng-click="tab = 'edit'" >[% l('Edit') %]</a>
   </li>
@@ -24,7 +24,7 @@ eg-navbar {
   </li>
 </ul>
 
-<div class="tab-content">
+<div class="tab-content" ng-if="!edit_templates">
   <div class="tab-pane active">
     <div ng-show="tab == 'edit'">
       <div ng-include="'[% ctx.base_path %]/staff/cat/volcopy/t_edit'"></div>
@@ -38,3 +38,11 @@ eg-navbar {
   </div>
 </div>
 
+<div ng-if="edit_templates">
+  <div class="row">
+    <h2>[% l("Edit Volume/Copy Templates") %]</h2>
+  </div>
+  <div class="row">
+    <eg-vol-template edit-templates="edit_templates"></eg-vol-template>
+  </div>
+</div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 0b109da..79b568a 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -19,6 +19,12 @@ angular.module('egVolCopy',
         delay : ['egStartup', function(egStartup) { return egStartup.go(); }]
     };
 
+    $routeProvider.when('/cat/volcopy/edit_templates', {
+        templateUrl: './cat/volcopy/t_view',
+        controller: 'EditCtrl',
+        resolve : resolver
+    });
+
     $routeProvider.when('/cat/volcopy/:dataKey', {
         templateUrl: './cat/volcopy/t_view',
         controller: 'EditCtrl',
@@ -783,6 +789,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
     };
 
     $scope.embedded = ($routeParams.mode && $routeParams.mode == 'embedded') ? true : false;
+    $scope.edit_templates = ($location.path().match(/edit_template/)) ? true : false;
 
     $scope.saveDefaults = function () {
         egCore.hatch.setItem('cat.copy.defaults', $scope.defaults);
@@ -973,7 +980,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
     var dataKey = $routeParams.dataKey;
     console.debug('dataKey: ' + dataKey);
 
-    if (dataKey && dataKey.length > 0) {
+    if ((dataKey && dataKey.length > 0) || $scope.edit_templates) {
 
         $scope.templates = {};
         $scope.template_name = '';
@@ -1594,7 +1601,9 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         restrict: 'E',
         replace: true,
         template: '<div ng-include="'+"'/eg/staff/cat/volcopy/t_attr_edit'"+'"></div>',
-        scope: { },
+        scope: {
+            editTemplates: '=',
+        },
         controller : ['$scope','$window','itemSvc','egCore',
             function ( $scope , $window , itemSvc , egCore ) {
 

commit 6745a4a3c573499af2b95870669ac199101fca95
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Nov 4 13:25:31 2016 -0400

    Put the toaster at the bottom right everywhere
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/services/core.js b/Open-ILS/web/js/ui/default/staff/services/core.js
index e61aceb..dcc077d 100644
--- a/Open-ILS/web/js/ui/default/staff/services/core.js
+++ b/Open-ILS/web/js/ui/default/staff/services/core.js
@@ -3,4 +3,11 @@
  * egCoreMod houses all of the services, etc. required by all pages
  * for basic functionality.
  */
-angular.module('egCoreMod', ['cfp.hotkeys', 'ngFileSaver', 'ngCookies', 'ngToast']);
+angular.module('egCoreMod', ['cfp.hotkeys', 'ngFileSaver', 'ngCookies', 'ngToast'])
+
+.config(['ngToastProvider', function(ngToastProvider) {
+  ngToastProvider.configure({
+    verticalPosition: 'bottom',
+    animation: 'fade'
+  });
+}]);

commit 7df921a5fe1b43730e1799bf766dc0b81de664bd
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Fri Nov 4 10:26:42 2016 -0400

    webstaff: CSS tweak for chrome/webkit
    
    Specifically, in the Copy Location Group interface, make sure the disabled
    position textbox in the New location group dialog is visible within Chrome.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2 b/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2
index 6088342..560a4c4 100644
--- a/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2
+++ b/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2
@@ -48,7 +48,7 @@
         background-color:#E7A555;
         border: 1px solid #4A4747;
     }
-    .tundra .dijitComboBoxDisabled {
+    .tundra .dijitComboBoxDisabled, .tundra .dijitTextBoxDisabled {
             color: gray !important;
     }
 </style>

commit 38de6b12064fdc4fe2cf2b78f10adcceb08347d5
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Fri Nov 4 10:18:25 2016 -0400

    webstaff: CSS tweak for chrome/webkit
    
    Specifically, in the Copy Location Group interface, make sure the disabled org
    selector in the New location group dialog is visible within Chrome.
    
    This also happened with Custom Org Unit Trees, and if we run across it again,
    I'll put more effort into injecting this more globally for the embedded Dojo
    interfaces.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2 b/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2
index 5466e11..6088342 100644
--- a/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2
+++ b/Open-ILS/src/templates/conify/global/asset/copy_location_group.tt2
@@ -48,6 +48,9 @@
         background-color:#E7A555;
         border: 1px solid #4A4747;
     }
+    .tundra .dijitComboBoxDisabled {
+            color: gray !important;
+    }
 </style>
 
 <div id='acplg-header'>

commit 91e85f34024f193a886d74038e91a3f2ef1c44b1
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Fri Nov 4 09:55:10 2016 -0400

    webstaff: CSS tweak for chrome/webkit
    
    Specifically, for Custom Org Unit Trees, make sure the "OPAC" selection in the
    disabled combobox is not too light to see in Chrome.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2 b/Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2
index 9d259b0..5e0c1d8 100644
--- a/Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2
+++ b/Open-ILS/src/templates/conify/global/actor/org_unit_custom_tree.tt2
@@ -17,6 +17,9 @@
         background-color:#E7A555;
         border-bottom: 2px solid #4A4747;
     }
+    .tundra .dijitComboBoxDisabled {
+            color: gray !important;
+    }
 </style>
 
 <h2>[% l('Org Unit Custom Tree') %]</h2>

commit df82edde99bf84f46d6e79be6ba6f19271c5a631
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 25 17:23:13 2016 -0400

    webstaff: implement patron merge interface
    
    This patch adds a 'Merge Patrons' button to the patron
    search grid. If the user selects two patron records, the
    button can be clicked to present a dialog that allows
    the user to pick a lead record and confirm a merge of the
    patrons.
    
    This patch also adds an egPatronSummary directive that
    uses the existing patron summary template with a couple
    modifications.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/circ/patron/index.tt2 b/Open-ILS/src/templates/staff/circ/patron/index.tt2
index 810950d..ebde904 100644
--- a/Open-ILS/src/templates/staff/circ/patron/index.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/index.tt2
@@ -11,6 +11,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/date.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/patrons.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/billing.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
 [% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2
index f7378f7..a5a58af 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2
@@ -20,6 +20,11 @@
   grid-controls="gridControls"
   items-provider="patronSearchGridProvider"
   persist-key="circ.patron.search">
+
+  <eg-grid-menu-item handler="merge_patrons"
+    disabled="need_two_selected"
+    label="[% l('Merge Patrons') %]"></eg-grid-menu-item>
+
   <eg-grid-field label="[% ('ID') %]" path='id' visible></eg-grid-field>
   <eg-grid-field label="[% ('Card') %]" path='card.barcode' visible></eg-grid-field>
   <eg-grid-field label="[% ('Profile') %]" path='profile.name' visible></eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
index 83a5957..606687d 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
@@ -18,6 +18,19 @@
       <div class="col-md-5">[% l('Profile') %]</div>
       <div class="col-md-7">{{patron().profile().name()}}</div>
     </div>
+    <div class="row" ng-if="show_name()">
+      <div class="col-md-5">[% l('ID') %]</div>
+      <div class="col-md-7">{{patron().id()}}</div>
+    </div>
+    <div class="row" ng-if="show_name()">
+      <div class="col-md-5">[% l('Name') %]</div>
+      <div class="col-md-7">
+                    [% l('[_1], [_2] [_3]',
+                '{{patron().family_name()}}',
+                '{{patron().first_given_name()}}',
+                '{{patron().second_given_name()}}') %]
+      </div>
+    </div>
     <div class="row">
       <div class="col-md-5">[% l('Home Library') %]</div>
       <div class="col-md-7">{{patron().home_ou().shortname()}}</div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index 31b2358..bb514c4 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -789,8 +789,10 @@ function($scope , $location , egCore , egConfirmDialog , egUser , patronSvc) {
 .controller('PatronSearchCtrl',
        ['$scope','$q','$routeParams','$timeout','$window','$location','egCore',
        '$filter','egUser', 'patronSvc','egGridDataProvider','$document',
+       'egPatronMerge',
 function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egCore,
-        $filter,  egUser,  patronSvc , egGridDataProvider , $document) {
+        $filter,  egUser,  patronSvc , egGridDataProvider , $document,
+        egPatronMerge) {
 
     $scope.initTab('search');
     $scope.focusMe = true;
@@ -1080,6 +1082,27 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egCore,
         // force the grid to load the url-based search on page load
         provider.refresh();
     }
+
+    $scope.need_two_selected = function() {
+        var items = $scope.gridControls.selectedItems();
+        return (items.length == 2) ? false : true;
+    }
+    $scope.merge_patrons = function() {
+        var items = $scope.gridControls.selectedItems();
+        if (items.length != 2) return false;
+
+        var patron_ids = [];
+        angular.forEach(items, function(i) {
+            patron_ids.push(i.id());
+        });
+        egPatronMerge.do_merge(patron_ids).then(function() {
+            // ensure that we're not drawing from cached
+            // resuts, as a successful merge just deleted a
+            // record
+            delete patronSvc.lastSearch;
+            $scope.gridControls.refresh();
+        });
+    }
    
 }])
 
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/patrons.js b/Open-ILS/web/js/ui/default/staff/circ/services/patrons.js
new file mode 100644
index 0000000..08e5548
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/patrons.js
@@ -0,0 +1,89 @@
+angular.module('egCoreMod')
+
+.factory('egPatronMerge',
+       ['$uibModal','$q','egCore',
+function($uibModal , $q , egCore) {
+
+    var service = {};
+
+    service.do_merge = function(patron_ids) {
+        var deferred = $q.defer();
+        $uibModal.open({
+            templateUrl: './circ/share/t_merge_patrons',
+            size: 'lg',
+            windowClass: 'eg-wide-modal',
+            controller:
+                ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+                    $scope.lead_id = 0;
+                    $scope.patron_ids = patron_ids;
+                    $scope.ok = function() {
+                        $uibModalInstance.close({ lead_id : $scope.lead_id });
+                    }
+                    $scope.cancel = function () { $uibModalInstance.dismiss() }
+                }]
+        }).result.then(function (args) {
+            if (args.lead_id == 0) return;
+            var sub_id = (args.lead_id == patron_ids[0]) ?
+                patron_ids[1] :
+                patron_ids[0];
+            egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.user.merge',
+                egCore.auth.token(),
+                args.lead_id,
+                [ sub_id ]
+            ).then(function(resp) {
+                var evt = egCore.evt.parse(resp);
+                if (evt) {
+                    console.debug(evt);
+                    deferred.reject(evt);
+                    return;
+                } else {
+                    deferred.resolve(); 
+                }
+            });
+        });
+        return deferred.promise;
+    }
+
+    return service;
+
+}])
+
+.directive('egPatronSummary', ['egUser','patronSvc', function(egUser, patronSvc) {
+    return {
+        restrict : 'E',
+        transclude: true,
+        templateUrl : './circ/patron/t_summary',
+        scope : {
+            patronId : '='
+        },
+        controller : [
+                    '$scope','egCore',
+            function($scope , egCore) {
+                var user;
+                var user_stats;
+                egUser.get($scope.patronId).then(function(u) {
+                    user = u;
+                    patronSvc.localFlesh(user);
+                });
+                patronSvc.getUserStats($scope.patronId).then(function(s) {
+                    user_stats = s;
+                });
+                $scope.patron = function() {
+                    return user;
+                }
+                $scope.patron_stats = function() {
+                    return user_stats;
+                }
+
+                // needed because this directive shares a template with
+                // the patron summary in circ app, but the circ app
+                // displays the patron name elsewhere. 
+                $scope.show_name = function() {
+                    return true;
+                }
+            }
+        ]
+    }
+}]);

commit ef23a936b71736f1f44d7b1d16c89c57c7cd4003
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 25 14:38:05 2016 -0400

    Protect XUL-ish code when not running under XULRunner
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/dojo/openils/XUL.js b/Open-ILS/web/js/dojo/openils/XUL.js
index b13b96d..459681f 100644
--- a/Open-ILS/web/js/dojo/openils/XUL.js
+++ b/Open-ILS/web/js/dojo/openils/XUL.js
@@ -3,6 +3,13 @@ if(!dojo._hasResource["openils.XUL"]) {
     dojo.provide("openils.XUL");
     dojo.declare('openils.XUL', null, {});
 
+    openils.XUL.Component_copy;
+    try {
+        openils.XUL.Component_copy = Components;
+    } catch (e) {
+        openils.XUL.Component_copy = null;
+    };
+
     openils.XUL.isXUL = function() {
         if(location.protocol == 'chrome:' || location.protocol == 'oils:') return true;
         return Boolean(window.IAMXUL);
@@ -14,9 +21,9 @@ if(!dojo._hasResource["openils.XUL"]) {
     }
     
     openils.XUL.getStash = function() {
-        if(openils.XUL.isXUL()) {
+        if(openils.XUL.Component_copy) {
             try {
-                var CacheClass = Components.classes["@open-ils.org/openils_data_cache;1"].getService();
+                var CacheClass = openils.XUL.Component_copy.classes["@open-ils.org/openils_data_cache;1"].getService();
                 return CacheClass.wrappedJSObject.data;
             } catch(e) {
                 console.log("Error loading XUL stash: " + e);
@@ -78,34 +85,38 @@ if(!dojo._hasResource["openils.XUL"]) {
     /* This class cuts down on the obscenely long incantations needed to
      * use XPCOM components. */
     openils.XUL.SimpleXPCOM = function() {};
-    openils.XUL.SimpleXPCOM.prototype = {
-        "FP": {
-            "iface": Components.interfaces.nsIFilePicker,
-            "cls": "@mozilla.org/filepicker;1"
-        },
-        "FIS": {
-            "iface": Components.interfaces.nsIFileInputStream,
-            "cls": "@mozilla.org/network/file-input-stream;1"
-        },
-        "SIS": {
-            "iface": Components.interfaces.nsIScriptableInputStream,
-            "cls": "@mozilla.org/scriptableinputstream;1"
-        },
-        "FOS": {
-            "iface": Components.interfaces.nsIFileOutputStream,
-            "cls": "@mozilla.org/network/file-output-stream;1"
-        },
-        "COS": {
-            "iface": Components.interfaces.nsIConverterOutputStream,
-            "cls": "@mozilla.org/intl/converter-output-stream;1"
-        },
-        "create": function(key) {
-            return Components.classes[this[key].cls].
-                createInstance(this[key].iface);
-        }
-    };
+    try {
+        openils.XUL.SimpleXPCOM.prototype = {
+            "FP": {
+                "iface": openils.XUL.Component_copy.interfaces.nsIFilePicker,
+                "cls": "@mozilla.org/filepicker;1"
+            },
+            "FIS": {
+                "iface": openils.XUL.Component_copy.interfaces.nsIFileInputStream,
+                "cls": "@mozilla.org/network/file-input-stream;1"
+            },
+            "SIS": {
+                "iface": openils.XUL.Component_copy.interfaces.nsIScriptableInputStream,
+                "cls": "@mozilla.org/scriptableinputstream;1"
+            },
+            "FOS": {
+                "iface": openils.XUL.Component_copy.interfaces.nsIFileOutputStream,
+                "cls": "@mozilla.org/network/file-output-stream;1"
+            },
+            "COS": {
+                "iface": openils.XUL.Component_copy.interfaces.nsIConverterOutputStream,
+                "cls": "@mozilla.org/intl/converter-output-stream;1"
+            },
+            "create": function(key) {
+                return openils.XUL.Component_copy.classes[this[key].cls].
+                    createInstance(this[key].iface);
+            }
+        };
+    } catch (e) { /* not XUL */ };
 
     openils.XUL.contentFromFileOpenDialog = function(windowTitle, sizeLimit) {
+        if (!openils.XUL.Component_copy) return null;
+
         var api = new openils.XUL.SimpleXPCOM();
 
         var picker = api.create("FP");
@@ -126,6 +137,8 @@ if(!dojo._hasResource["openils.XUL"]) {
     };
 
     openils.XUL.contentToFileSaveDialog = function(content, windowTitle, dispositionArgs) {
+        if (!openils.XUL.Component_copy) return null;
+
         var api = new openils.XUL.SimpleXPCOM();
 
         var picker = api.create("FP");
@@ -181,20 +194,20 @@ if(!dojo._hasResource["openils.XUL"]) {
     openils.XUL.localStorage = function() {
 
         // in browser mode, use the standard localStorage interface
-        if (!openils.XUL.isXUL()) 
+        if (!openils.XUL.Component_copy) 
             return window.localStorage;
 
         var url = location.protocol + '//' + location.hostname;
-        var ios = Components.classes["@mozilla.org/network/io-service;1"]
-                  .getService(Components.interfaces.nsIIOService);
-        var ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
-                  .getService(Components.interfaces.nsIScriptSecurityManager);
-        var dsm = Components.classes["@mozilla.org/dom/storagemanager;1"]
-                  .getService(Components.interfaces.nsIDOMStorageManager);
+        var ios = openils.XUL.Component_copy.classes["@mozilla.org/network/io-service;1"]
+                  .getService(openils.XUL.Component_copy.interfaces.nsIIOService);
+        var ssm = openils.XUL.Component_copy.classes["@mozilla.org/scriptsecuritymanager;1"]
+                  .getService(openils.XUL.Component_copy.interfaces.nsIScriptSecurityManager);
+        var dsm = openils.XUL.Component_copy.classes["@mozilla.org/dom/storagemanager;1"]
+                  .getService(openils.XUL.Component_copy.interfaces.nsIDOMStorageManager);
         var uri = ios.newURI(url, "", null);
         var principal = ssm.getCodebasePrincipal(uri);
         return dsm.getLocalStorageForPrincipal(principal, "");
     };
 
- }catch (e) {/*meh*/}
+ }catch (e) { console.log('Failed to load openils.XUL: ' + e) }
 }

commit 6bbde46c74b10fcc3684292deedca715574ab135
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 25 14:17:54 2016 -0400

    add limit and sorting to transit list fetch
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
index 6a876b4..e11a77a 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
@@ -246,7 +246,10 @@ function($scope , $q , $routeParams , $window , egCore , egTransits , egGridData
                     'ahr' : ['usr'],
                     'au' : ['card']
                 },
-                'select' : { 'bre' : ['id'] }
+                'select' : { 'bre' : ['id'] },
+                order_by : { atc : 'source_send_time' },
+                limit  : count,
+                offset : offset,
             }
         ).then(
             deferred.resolve, null, 

commit 6a14bac329a264804363da4df67f6f28a6b48cfe
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Oct 20 16:57:54 2016 -0400

    webstaff: provide login type options for operator change
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/share/t_opchange.tt2 b/Open-ILS/src/templates/staff/share/t_opchange.tt2
index 030418a..3872441 100644
--- a/Open-ILS/src/templates/staff/share/t_opchange.tt2
+++ b/Open-ILS/src/templates/staff/share/t_opchange.tt2
@@ -26,6 +26,19 @@
         <input ng-keyup="$event.keyCode == 13 ? ok() : null" type='password' ng-model="args.password" class="form-control"/>
       </div>
     </div>
+    <div class="row">
+      <div class="col-md-4">
+        [% l('Login Type:') %]
+      </div>
+      <div class="col-md-1"></div>
+      <div class="col-md-7">
+        <select class="form-control" required ng-model="args.type">
+          <option value="temp">[% l('Temporary') %]</option>
+          <option value="staff" selected>[% l('Staff') %]</option>
+          <option value="persist" selected>[% l('Persistent') %]</option>
+        </select>
+      </div>
+    </div>
   </div>
   <div class="modal-footer">
     [% dialog_footer %]
diff --git a/Open-ILS/web/js/ui/default/staff/services/navbar.js b/Open-ILS/web/js/ui/default/staff/services/navbar.js
index 2bc6522..305a62c 100644
--- a/Open-ILS/web/js/ui/default/staff/services/navbar.js
+++ b/Open-ILS/web/js/ui/default/staff/services/navbar.js
@@ -83,13 +83,14 @@ angular.module('egCoreMod')
                         templateUrl: './share/t_opchange',
                         controller:
                             ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
-                            $scope.args = {username : '', password : ''};
+                            $scope.args = {username : '', password : '', type : 'temp'};
                             $scope.focus = true;
                             $scope.ok = function() { $uibModalInstance.close($scope.args) }
                             $scope.cancel = function () { $uibModalInstance.dismiss() }
                         }]
                     }).result.then(function (args) {
                         if (!args || !args.username || !args.password) return;
+                        args.type = args.type || 'temp';
                         args.workstation = egCore.auth.workstation();
                         egCore.auth.opChange(args).then(
                             function() {

commit 517c68ee3d6eef59f2f3621e0b925f87bc263d01
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 20 13:08:58 2016 -0400

    changes to transit list interface
    
    * convert barcode column to hyperlink to item status
      page (and remove the 'Item Status' action; note that
      default double-click action remains going to the
      item status page)
    * convert title column to hyperlink to record details page
    * add 'Add Items to Bucket' button
    * add 'Edit Item Attributes' button
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/circ/transits/t_list.tt2 b/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
index d6726bb..5c240db 100644
--- a/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
@@ -34,17 +34,25 @@
   persist-key="circ.transits.list"
 >
 
-  <eg-grid-menu-item handler="load_item" 
-    label="[% l('Item Status') %]"></eg-grid-menu-item>
+  <eg-grid-menu-item handler="add_copies_to_bucket" 
+    label="[% l('Add Items to Bucket') %]"></eg-grid-menu-item>
+  <eg-grid-menu-item handler="edit_copies" 
+    label="[% l('Edit Item Attributes') %]"></eg-grid-menu-item>
 
   <eg-grid-menu-item handler="abort_transit" 
     label="[% l('Abort Transit') %]"></eg-grid-menu-item>
 
   <eg-grid-field path='id' hidden></eg-grid-field>
-  <eg-grid-field path='target_copy.barcode'></eg-grid-field>
+  <eg-grid-field path='target_copy.barcode' label="[% l('Barcode') %]">
+    <a target="_self" href='./cat/item/{{item.target_copy().id()}}'>{{item.target_copy().barcode()}}</a>
+  </eg-grid-field>
   <eg-grid-field path='target_copy.circ_lib.shortname' hidden></eg-grid-field>
   <eg-grid-field path='target_copy.call_number.label' hidden></eg-grid-field>
-  <eg-grid-field path='target_copy.call_number.record.simple_record.title'></eg-grid-field>
+  <eg-grid-field path='target_copy.call_number.record.simple_record.title' label="[% l('Title') %]">
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.target_copy().call_number().record().simple_record().id()}}">
+      {{item.target_copy().call_number().record().simple_record().title()}}
+    </a>
+  </eg-grid-field>
   <eg-grid-field path='target_copy.call_number.record.simple_record.author' hidden></eg-grid-field>
   <eg-grid-field path='source.shortname' label="[% l('Source Library') %]"></eg-grid-field>
   <eg-grid-field path='dest.shortname' label="[% l('Destination Library') %]"></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
index fd19018..6a876b4 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
@@ -18,8 +18,8 @@ angular.module('egTransitListApp',
 })
 
 .controller('TransitListCtrl',
-       ['$scope','$q','$routeParams','$window','egCore','egTransits','egGridDataProvider',
-function($scope , $q , $routeParams , $window , egCore , egTransits , egGridDataProvider) {
+       ['$scope','$q','$routeParams','$window','egCore','egTransits','egGridDataProvider','$uibModal','$timeout',
+function($scope , $q , $routeParams , $window , egCore , egTransits , egGridDataProvider , $uibModal , $timeout) {
 
     var transits = [];
     var provider = egGridDataProvider.instance({});
@@ -90,6 +90,128 @@ function($scope , $q , $routeParams , $window , egCore , egTransits , egGridData
         abort_transit(transits);
     }
 
+    $scope.add_copies_to_bucket = function(transits) {
+        var copy_list = [];
+        angular.forEach($scope.grid_controls.selectedItems(), function(transit) {
+            copy_list.push(transit.target_copy().id());
+        });
+        if (copy_list.length == 0) return;
+
+        // FIXME what follows ought to be refactored into a factory
+        return $uibModal.open({
+            templateUrl: './cat/catalog/t_add_to_bucket',
+            animation: true,
+            size: 'md',
+            controller:
+                   ['$scope','$uibModalInstance',
+            function($scope , $uibModalInstance) {
+
+                $scope.bucket_id = 0;
+                $scope.newBucketName = '';
+                $scope.allBuckets = [];
+
+                egCore.net.request(
+                    'open-ils.actor',
+                    'open-ils.actor.container.retrieve_by_class.authoritative',
+                    egCore.auth.token(), egCore.auth.user().id(),
+                    'copy', 'staff_client'
+                ).then(function(buckets) { $scope.allBuckets = buckets; });
+
+                $scope.add_to_bucket = function() {
+                    var promises = [];
+                    angular.forEach(copy_list, function (cp) {
+                        var item = new egCore.idl.ccbi()
+                        item.bucket($scope.bucket_id);
+                        item.target_copy(cp);
+                        promises.push(
+                            egCore.net.request(
+                                'open-ils.actor',
+                                'open-ils.actor.container.item.create',
+                                egCore.auth.token(), 'copy', item
+                            )
+                        );
+
+                        return $q.all(promises).then(function() {
+                            $uibModalInstance.close();
+                        });
+                    });
+                }
+
+                $scope.add_to_new_bucket = function() {
+                    var bucket = new egCore.idl.ccb();
+                    bucket.owner(egCore.auth.user().id());
+                    bucket.name($scope.newBucketName);
+                    bucket.description('');
+                    bucket.btype('staff_client');
+
+                    return egCore.net.request(
+                        'open-ils.actor',
+                        'open-ils.actor.container.create',
+                        egCore.auth.token(), 'copy', bucket
+                    ).then(function(bucket) {
+                        $scope.bucket_id = bucket;
+                        $scope.add_to_bucket();
+                    });
+                }
+
+                $scope.cancel = function() {
+                    $uibModalInstance.dismiss();
+                }
+            }]
+        });
+    }
+
+
+    function gatherSelectedRecordIds () {
+        var rid_list = [];
+        angular.forEach(
+            $scope.grid_controls.selectedItems(),
+            function (item) {
+                if (rid_list.indexOf(item.target_copy().call_number().record().simple_record().id()) == -1)
+                    rid_list.push(item.target_copy().call_number().record().simple_record().id());
+            }
+        );
+        return rid_list;
+    }
+    function gatherSelectedHoldingsIds (rid) {
+        var cp_id_list = [];
+        angular.forEach(
+            $scope.grid_controls.selectedItems(),
+            function (item) {
+                if (rid && item.target_copy().call_number().record().simple_record().id() != rid) return;
+                cp_id_list.push(item.target_copy().id());
+            }
+        );
+        return cp_id_list;
+    }
+
+    var spawnHoldingsEdit = function (hide_vols, hide_copies){
+        angular.forEach(gatherSelectedRecordIds(), function (r) {
+            egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.anon_cache.set_value',
+                null, 'edit-these-copies', {
+                    record_id: r,
+                    copies: gatherSelectedHoldingsIds(r),
+                    raw: {},
+                    hide_vols : hide_vols,
+                    hide_copies : hide_copies
+                }
+            ).then(function(key) {
+                if (key) {
+                    var url = egCore.env.basePath + 'cat/volcopy/' + key;
+                    $timeout(function() { $window.open(url, '_blank') });
+                } else {
+                    alert('Could not create anonymous cache key!');
+                }
+            });
+        });
+    }
+   
+    $scope.edit_copies = function() { 
+        spawnHoldingsEdit(true, false);
+    }
+
     $scope.grid_controls = {
         activateItem : load_item
     }

commit 0430fe6b4fbf6562c6d9c76c1a910ae90b47ffd5
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Tue Oct 18 13:48:50 2016 -0400

    webstaff: Acquisitions Administration
    
    FIXME:
    
    * Acq Admin -> Distribution Formulas -> Formula Detail Page : render problem, no uncaught exceptions
    * Acq Admin -> Funds : TypeError: openils.XUL.localStorage is not a function
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/acq/index.tt2 b/Open-ILS/src/templates/staff/admin/acq/index.tt2
new file mode 100644
index 0000000..f5a8b7a
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/acq/index.tt2
@@ -0,0 +1,15 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Acquisitions Administration"); 
+  ctx.page_app = "egAcqAdmin";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/acq/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
new file mode 100644
index 0000000..4723795
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
@@ -0,0 +1,49 @@
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    <span>[% l('Acquisitions Administration') %]</span>
+  </div>
+</div>
+
+<div class="container admin-splash-container">
+
+[%
+    interfaces = [
+     [ l('Currency Types'), "./admin/acq/currency_type/list" ]
+    ,[ l('Distribution Formulas'), "./admin/acq/conify/distribution_formula" ]
+    ,[ l('EDI Accounts'), "./admin/acq/conify/edi_account" ]
+    ,[ l('EDI Messages'), "./admin/acq/po/edi_messages" ]
+    ,[ l('Exchange Rates'), "./admin/acq/conify/exchange_rate" ]
+    ,[ l('Fund Tags'), "./admin/acq/conify/fund_tag" ]
+    ,[ l('Funding Sources'), "./admin/acq/funding_source/list" ]
+    ,[ l('Funds'), "./admin/acq/fund/list" ]
+    ,[ l('Providers'), "./admin/acq/conify/provider" ]
+    ,[ l('Claim Event Types'), "./admin/acq/conify/claim_event_type" ]
+    ,[ l('Claim Policies'), "./admin/acq/conify/claim_policy" ]
+    ,[ l('Claim Policy Actions'), "./admin/acq/conify/claim_policy_action" ]
+    ,[ l('Claim Types'), "./admin/acq/conify/claim_type" ]
+    ,[ l('Invoice Item Types'), "./admin/acq/conify/invoice_item_type" ]
+    ,[ l('Invoice Payment Method'), "./admin/acq/conify/invoice_payment_method" ]
+    ,[ l('Cancel Reasons'), "./admin/acq/conify/cancel_reason" ]
+    ,[ l('Line Item Alerts'), "./admin/acq/conify/lineitem_alert" ]
+    ,[ l('Line Item MARC Attribute Definitions'), "./admin/acq/conify/lineitem_marc_attr_def" ]
+   ];
+
+   USE table(interfaces, rows=9);
+%]
+
+[% FOREACH row = table.rows %]
+  <div class="row new-entry">
+    [% FOREACH item = row %][% IF item.1 %]
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="[% item.1 %]">
+        [% item.0 %]
+      </a>
+    </div>
+    [% END %][% END %]
+  </div>
+[% END %]
+
+</div>
+
diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index 953f2d1..262ceef 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -369,6 +369,12 @@
             </a>
           </li>
           <li>
+            <a href="./admin/acq/index" target="_self">
+              <span class="glyphicon glyphicon-usd"></span>
+              [% l('Acquisitions Administration') %]
+            </a>
+          </li>
+          <li>
             <a href="./reporter/legacy/main" target="_self">
               <span class="glyphicon glyphicon-object-align-bottom"></span>
               [% l('Reports') %]
diff --git a/Open-ILS/web/js/ui/default/staff/admin/acq/app.js b/Open-ILS/web/js/ui/default/staff/admin/acq/app.js
new file mode 100644
index 0000000..245725e
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/acq/app.js
@@ -0,0 +1,60 @@
+angular.module('egAcqAdmin',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
+
+.config(['$routeProvider','$locationProvider','$compileProvider', 
+ function($routeProvider , $locationProvider , $compileProvider) {
+
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    var eframe_template = 
+        '<eg-embed-frame url="acq_admin_url" handlers="funcs"></eg-embed-frame>';
+
+    $routeProvider.when('/admin/acq/:noun/:verb/:extra?', {
+        template: eframe_template,
+        controller: 'EmbedAcqCtl',
+        resolve : resolver
+    });
+
+    // default page 
+    $routeProvider.otherwise({
+        templateUrl : './admin/acq/t_splash',
+        resolve : resolver
+    });
+}])
+
+.controller('EmbedAcqCtl',
+       ['$scope','$routeParams','$location','egCore',
+function($scope , $routeParams , $location , egCore) {
+
+    $scope.funcs = {
+        ses : egCore.auth.token(),
+    }
+
+    var acq_path = '/eg/';
+
+    if ($routeParams.noun == 'conify') {
+        acq_path += 'conify/global/acq/' + $routeParams.verb
+            + (typeof $routeParams.extra != 'undefined'
+                ? '/' + $routeParams.extra
+                : '')
+            + location.search;
+    } else {
+        acq_path += 'acq/'
+            + $routeParams.noun + '/' + $routeParams.verb
+            + (typeof $routeParams.extra != 'undefined'
+                ? '/' + $routeParams.extra
+                : '')
+            + location.search;
+    }
+
+    // embed URL must include protocol/domain or it will be loaded via
+    // push-state, resulting in an infinitely nested pages.
+    $scope.acq_admin_url =
+        $location.absUrl().replace(/\/eg\/staff.*/, acq_path);
+
+    console.log('Loading Admin Acq URL: ' + $scope.acq_admin_url);
+
+}])
+

commit fec91b57dccdfd6fc40ac963564bbc97596a88b9
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Oct 17 17:15:12 2016 -0400

    webstaff: various Acq menu entries
    
    * Purchase Orders
    * Create Purchase Order
    * Claim-Ready Items
    * Open Invoices
    * Create Invoice
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index ce0bf3c..953f2d1 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -306,6 +306,37 @@
               [% l('Load MARC Order Records') %]
             </a>
           </li>
+          <li>
+            <a href="./acq/legacy/search/unified?ca=po" target="_self">
+              <span class="glyphicon glyphicon-shopping-cart"></span>
+              [% l('Purchase Orders') %]
+            </a>
+          </li>
+          <li>
+            <a href="./acq/legacy/po/create" target="_self">
+              <span class="glyphicon glyphicon-plus"></span>
+              [% l('Create Purchase Order') %]
+            </a>
+          </li>
+          <li class="divider"></li>
+          <li>
+            <a href="./acq/legacy/financial/claim_eligible" target="_self">
+              <span class="glyphicon glyphicon-question-sign"></span>
+              [% l('Claim-Ready Items') %]
+            </a>
+          </li>
+          <li>
+            <a href="./acq/legacy/search/unified?ca=inv" target="_self">
+              <span class="glyphicon glyphicon-usd"></span>
+              [% l('Open Invoices') %]
+            </a>
+          </li>
+          <li>
+            <a href="./acq/legacy/invoice/view?create=1" target="_self">
+              <span class="glyphicon glyphicon-credit-card"></span>
+              [% l('Create Invoice') %]
+            </a>
+          </li>
        </ul>
       </li>
 

commit da642628f59629735597427e1e7aafb4026f0fb6
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Oct 17 16:12:02 2016 -0400

    webstaff: Load MARC Order Records
    
    FIXME:
    
    TypeError: openils.XUL.localStorage is not a function
    at vlagent.js:56
    
    Having trouble working around this.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index ea1e35f..ce0bf3c 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -299,6 +299,13 @@
               [% l('Load Catalog Record IDs') %]
             </a>
           </li>
+          <li class="divider"></li>
+          <li>
+            <a href="./acq/legacy/picklist/upload" target="_self">
+              <span class="glyphicon glyphicon-cloud-upload"></span>
+              [% l('Load MARC Order Records') %]
+            </a>
+          </li>
        </ul>
       </li>
 

commit 66e33a60da52b3510bdb5e74a56f68be6a6c25c7
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Oct 17 14:36:25 2016 -0400

    webstaff: various Acq menu entries
    
    * My Selection Lists
    * New Brief Record
    * Patron Requests
    * MARC Federated Search
    * Load Catalog Record IDs
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index 4a80382..ea1e35f 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -269,6 +269,36 @@
             </a>
           </li>
           <li class="divider"></li>
+          <li>
+            <a href="./acq/legacy/search/unified?ca=pl" target="_self">
+              <span class="glyphicon glyphicon-list"></span>
+              [% l('My Selection Lists') %]
+            </a>
+          </li>
+          <li>
+            <a href="./acq/legacy/picklist/brief_record" target="_self">
+              <span class="glyphicon glyphicon-pencil"></span>
+              [% l('New Brief Record') %]
+            </a>
+          </li>
+          <li>
+            <a href="./acq/legacy/picklist/user_request" target="_self">
+              <span class="glyphicon glyphicon-thumbs-up"></span>
+              [% l('Patron Requests') %]
+            </a>
+          </li>
+          <li>
+            <a href="./acq/legacy/picklist/bib_search" target="_self">
+              <span class="glyphicon glyphicon-cloud-download"></span>
+              [% l('MARC Federated Search') %]
+            </a>
+          </li>
+          <li>
+            <a href="./acq/legacy/picklist/from_bib" target="_self">
+              <span class="glyphicon glyphicon-import"></span>
+              [% l('Load Catalog Record IDs') %]
+            </a>
+          </li>
        </ul>
       </li>
 
diff --git a/Open-ILS/web/js/ui/default/staff/acq/app.js b/Open-ILS/web/js/ui/default/staff/acq/app.js
index 902da6e..d5fd847 100644
--- a/Open-ILS/web/js/ui/default/staff/acq/app.js
+++ b/Open-ILS/web/js/ui/default/staff/acq/app.js
@@ -33,7 +33,7 @@ function($scope , $routeParams , $location , egCore) {
     }
 
     var acq_path = '/eg/acq/' + 
-        $routeParams.noun + '/' + $routeParams.verb;
+        $routeParams.noun + '/' + $routeParams.verb + location.search;
 
     // embed URL must include protocol/domain or it will be loaded via
     // push-state, resulting in an infinitely nested pages.

commit 91522b6386ede3cc82e76fb15f23394ee06e28ba
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Oct 17 14:14:40 2016 -0400

    tweak to vlagent.js
    
    Otherwise, within "General Search", we'll get this error:
    
    TypeError: openils.XUL.localStorage is not a function from vlagent.js:6
    
    FIXME: brittle workaround?
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/acq/common/vlagent.js b/Open-ILS/web/js/ui/default/acq/common/vlagent.js
index 827e183..5ed906f 100644
--- a/Open-ILS/web/js/ui/default/acq/common/vlagent.js
+++ b/Open-ILS/web/js/ui/default/acq/common/vlagent.js
@@ -3,7 +3,7 @@ dojo.require('openils.PermaCrud');
 dojo.require('openils.XUL');
 dojo.require('dojox.form.CheckedMultiSelect');
 
-var xulStorage = openils.XUL.localStorage();
+var xulStorage;
 var storekey = 'eg.acq.upload.';
 var osetkey = 'acq.upload.default.';
 var persistOrgSettings;
@@ -53,6 +53,8 @@ function VLAgent(args) {
     this.init = function(oncomplete) {
         var self = this;
 
+	xulStorage = openils.XUL.localStorage();
+
         // load org unit persist setting values
         fieldmapper.standardRequest(
             ['open-ils.actor','open-ils.actor.ou_setting.ancestor_default.batch'],

commit 14f3c772bf15979087bada5085c61f98b0186e46
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Oct 17 13:53:36 2016 -0400

    webstaff: first Acq menu-entry and embedded UI
    
    * General Search
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/acq/index.tt2 b/Open-ILS/src/templates/staff/acq/index.tt2
new file mode 100644
index 0000000..7c26ae3
--- /dev/null
+++ b/Open-ILS/src/templates/staff/acq/index.tt2
@@ -0,0 +1,21 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Acquisitions");
+  ctx.page_app = "egAcquisitions";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/dojo/opensrf/md5.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
+<!--<script src="[% ctx.media_prefix %]/js/ui/default/staff/reporter/services/template.js"></script>-->
+<!--[% INCLUDE 'staff/reporter/share/report_strings.tt2' %]-->
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/acq/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/acq.css" />
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index 7430f6a..4a80382 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -257,6 +257,21 @@
        </ul>
       </li>
 
+      <!-- acquisitions -->
+      <li class="dropdown" uib-dropdown>
+        <a href uib-dropdown-toggle>[% l('Acquisitions') %]<b class="caret"></b>
+        </a>
+        <ul uib-dropdown-menu>
+          <li>
+            <a href="./acq/legacy/search/unified" target="_self">
+              <span class="glyphicon glyphicon-search"></span>
+              [% l('General Search') %]
+            </a>
+          </li>
+          <li class="divider"></li>
+       </ul>
+      </li>
+
       <!-- admin -->
       <li class="dropdown" uib-dropdown>
         <a href uib-dropdown-toggle>[% l('Administration') %]<b class="caret"></b></a>
diff --git a/Open-ILS/web/js/ui/default/staff/acq/app.js b/Open-ILS/web/js/ui/default/staff/acq/app.js
new file mode 100644
index 0000000..902da6e
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/acq/app.js
@@ -0,0 +1,46 @@
+angular.module('egAcquisitions',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
+
+.config(['$routeProvider','$locationProvider','$compileProvider', 
+ function($routeProvider , $locationProvider , $compileProvider) {
+
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    var eframe_template = 
+        '<eg-embed-frame url="acq_url" handlers="funcs"></eg-embed-frame>';
+
+    $routeProvider.when('/acq/legacy/:noun/:verb', {
+        template: eframe_template,
+        controller: 'EmbedAcqCtl',
+        resolve : resolver
+    });
+
+    // default page 
+    $routeProvider.otherwise({
+        templateUrl : './t_splash',
+        resolve : resolver
+    });
+}])
+
+.controller('EmbedAcqCtl', 
+       ['$scope','$routeParams','$location','egCore',
+function($scope , $routeParams , $location , egCore) {
+
+    $scope.funcs = {
+        ses : egCore.auth.token(),
+    }
+
+    var acq_path = '/eg/acq/' + 
+        $routeParams.noun + '/' + $routeParams.verb;
+
+    // embed URL must include protocol/domain or it will be loaded via
+    // push-state, resulting in an infinitely nested pages.
+    $scope.acq_url = 
+        $location.absUrl().replace(/\/eg\/staff.*/, acq_path);
+
+    console.log('Loading Acq URL: ' + $scope.acq_url);
+
+}])
+

commit fe731567ded646d81b2c2103a4fc22114e5887c4
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 17 18:20:04 2016 -0400

    webstaff: tweaks to transit list
    
    - filter out transits that have been received, thus matching
      previous XUL functionality
    - remove the Receive Date/Time column, as it is superfluous
    - display the time component of the Send Date/Time
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/circ/transits/t_list.tt2 b/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
index 34b19c4..d6726bb 100644
--- a/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/transits/t_list.tt2
@@ -48,8 +48,7 @@
   <eg-grid-field path='target_copy.call_number.record.simple_record.author' hidden></eg-grid-field>
   <eg-grid-field path='source.shortname' label="[% l('Source Library') %]"></eg-grid-field>
   <eg-grid-field path='dest.shortname' label="[% l('Destination Library') %]"></eg-grid-field>
-  <eg-grid-field path='source_send_time'></eg-grid-field>
-  <eg-grid-field path='dest_recv_time'></eg-grid-field>
+  <eg-grid-field path='source_send_time' dateformat='short''></eg-grid-field>
   <eg-grid-field path='hold_transit_copy.hold.hold_type'></eg-grid-field>
   <eg-grid-field path='hold_transit_copy.hold.request_time' hidden></eg-grid-field>
   <eg-grid-field path='hold_transit_copy.hold.capture_time' hidden></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
index 9dd066d..fd19018 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/transits/list.js
@@ -104,7 +104,8 @@ function($scope , $q , $routeParams , $window , egCore , egTransits , egGridData
         var recv_index = 0;
 
         var filter = {
-            'source_send_time' : { 'between' : date_range() }
+            'source_send_time' : { 'between' : date_range() },
+            'dest_recv_time'   : null
         };
         if ($scope.transit_direction == 'to') { filter['dest'] = $scope.context_org.id(); }
         if ($scope.transit_direction == 'from') { filter['source'] = $scope.context_org.id(); }

commit b552d92b37829095cd16342b2de3cb7e2800c190
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 17 17:34:30 2016 -0400

    webstaff: implement some workstation preferences
    
    - Default search library
    - Preferred library
    - Advanced search pane
    
    Because AngularJS has no way of adding HTTP request headers to
    user interactions with the contents of an iframe, the search
    library and preferred library are passed to TPAC via two new
    session cookies, eg_pref_lib and eg_search_lib.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
index 69bb898..fdf51f7 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm
@@ -548,6 +548,9 @@ sub _get_search_lib {
     if ($self->apache->headers_in->get('OILS-Search-Lib')) {
         return $self->apache->headers_in->get('OILS-Search-Lib');
     }
+    if ($self->cgi->cookie('eg_search_lib')) {
+        return $self->cgi->cookie('eg_search_lib');
+    }
 
     my $pref_lib = $self->_get_pref_lib();
     return $pref_lib if $pref_lib;
@@ -566,6 +569,9 @@ sub _get_pref_lib {
     if ($self->apache->headers_in->get('OILS-Pref-Lib')) {
         return $self->apache->headers_in->get('OILS-Pref-Lib');
     }
+    if ($self->cgi->cookie('eg_pref_lib')) {
+        return $self->cgi->cookie('eg_pref_lib');
+    }
 
     if ($ctx->{user}) {
         # See if the user has a search library preference
diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
index 8c8da45..4e60199 100644
--- a/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
@@ -48,6 +48,46 @@
   </div>
 
   <div class="row new-entry">
+    <div class="col-md-4">
+        <label for="search_lib_selector">[% ('Default Search Library') %]</label>
+        <p>[% l('The default search library setting determines what library is searched from the advanced search screen and portal page by default. Manual selection of a search library will override it. One recommendation is to set the search library to the highest point you would normally want to search.') %]</p>
+    </div>
+    <div class="col-md-2">
+      <eg-org-selector id="search_lib_selector"
+        selected="search_lib"
+        onchange="handle_search_lib_changed">
+      </eg-org-selector>
+    </div>
+  </div>
+
+  <div class="row new-entry">
+    <div class="col-md-4">
+        <label for="pref_lib_selector">[% ('Preferred Library') %]</label>
+        <p>[% l('The preferred library is used to show copies and URIs regardless of the library searched. One recommendation is to set this to your workstation library so that local copies show up first in search results.') %]</p>
+    </div>
+    <div class="col-md-2">
+      <eg-org-selector id="pref_lib_selector"
+        selected="pref_lib"
+        onchange="handle_pref_lib_changed">
+      </eg-org-selector>
+    </div>
+  </div>
+
+  <div class="row new-entry">
+    <div class="col-md-4">
+        <label for="adv_pane_selector">[% ('Advanced Search Default Pane') %]</label>
+        <p>[% l('Advanced search has secondary panes for Numeric and MARC Expert searching. You can change which one is loaded by default when opening a new catalog window here.') %]</p>
+    </div>
+    <div class="col-md-2">
+      <select id="adv_pane_selector" ng-model="adv_pane">
+        <option value="advanced">[% l('Advanced (default)') %]</option>
+        <option value="numeric" >[% l('Numeric') %]</option>
+        <option value="expert"  >[% l('MARC Expert') %]</option>
+      </select>
+    </div>
+  </div>
+
+  <div class="row new-entry">
     <div class="col-md-6">
       <span class="glyphicon glyphicon-pushpin"></span>
       <a target="_self" href="./admin/workstation/workstations">
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
index 78b6d03..750ed17 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
@@ -178,6 +178,30 @@ function($scope , $window , $location , egCore , egConfirmDialog) {
         $scope.disable_sound = val;
     });
 
+    egCore.hatch.getItem('eg.search.search_lib').then(function(val) {
+        $scope.search_lib = egCore.org.get(val);
+    });
+    $scope.handle_search_lib_changed = function(org) {
+        egCore.hatch.setItem('eg.search.search_lib', org.id());
+    };
+
+    egCore.hatch.getItem('eg.search.pref_lib').then(function(val) {
+        $scope.pref_lib = egCore.org.get(val);
+    });
+    $scope.handle_pref_lib_changed = function(org) {
+        egCore.hatch.setItem('eg.search.pref_lib', org.id());
+    };
+
+    $scope.adv_pane = 'advanced'; // default value if not explicitly set
+    egCore.hatch.getItem('eg.search.adv_pane').then(function(val) {
+        $scope.adv_pane = val;
+    });
+    $scope.$watch('adv_pane', function(newVal, oldVal) {
+        if (newVal != oldVal) {
+            egCore.hatch.setItem('eg.search.adv_pane', newVal);
+        }
+    });
+
     $scope.apply_sound = function() {
         if ($scope.disable_sound) {
             egCore.hatch.setItem('eg.audio.disable', true);
diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
index 2272ae7..9423e50 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
@@ -247,8 +247,11 @@ function($scope , $routeParams , $location , $window , $q , egCore) {
 .controller('CatalogCtrl',
        ['$scope','$routeParams','$location','$window','$q','egCore','egHolds','egCirc','egConfirmDialog','ngToast',
         'egGridDataProvider','egHoldGridActions','$timeout','$uibModal','holdingsSvc','egUser','conjoinedSvc',
+        '$cookies',
 function($scope , $routeParams , $location , $window , $q , egCore , egHolds , egCirc , egConfirmDialog , ngToast ,
-         egGridDataProvider , egHoldGridActions , $timeout , $uibModal , holdingsSvc , egUser , conjoinedSvc) {
+         egGridDataProvider , egHoldGridActions , $timeout , $uibModal , holdingsSvc , egUser , conjoinedSvc,
+         $cookies
+) {
 
     var holdingsSvcInst = new holdingsSvc();
 
@@ -259,6 +262,14 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     if ($routeParams.record_id) $scope.from_route = true;
     else $scope.from_route = false;
 
+    // set search and preferred library cookies
+    egCore.hatch.getItem('eg.search.search_lib').then(function(val) {
+        $cookies.put('eg_search_lib', val, { path : '/' });
+    });
+    egCore.hatch.getItem('eg.search.pref_lib').then(function(val) {
+        $cookies.put('eg_pref_lib', val, { path : '/' });
+    });
+
     // will hold a ref to the opac iframe
     $scope.opac_iframe = null;
     $scope.parts_iframe = null;
@@ -1440,6 +1451,16 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
             });
         }
 
+        // if we're displaying the advanced search form, select
+        // whatever default pane the user has chosen via workstation
+        // preference
+        if (url.match(/\/opac\/advanced$/)) {
+            var adv_pane = egCore.hatch.getLocalItem('eg.search.adv_pane');
+            if (adv_pane) {
+                url += '?pane=' + encodeURIComponent(adv_pane);
+            }
+        }
+
         $scope.catalog_url = url;
     }
 

commit 58f0446bef1f4d00e1529c69290fb5c3e444a69a
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Oct 6 12:00:18 2016 -0400

    Webstaff: implement Operator Change (and Operator Restore)
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/base_js.tt2 b/Open-ILS/src/templates/staff/base_js.tt2
index af92b80..bf812ba 100644
--- a/Open-ILS/src/templates/staff/base_js.tt2
+++ b/Open-ILS/src/templates/staff/base_js.tt2
@@ -77,6 +77,8 @@
     s.EG_WORK_LOG_REQUESTED_HOLD = '[% l('Hold Request') %]';
     s.EG_CONFIRM_DELETE_RECORD_TITLE = '[% l('Confirm Record Deletion') %]';
     s.EG_CONFIRM_DELETE_RECORD_BODY = "[% l('Delete record {{id}}?') %]";
+    s.OP_CHANGE_SUCCESS = "[% l('Operator Change Succeeded') %]";
+    s.OP_CHANGE_FAILURE = "[% l('Operator Change Failed') %]";
   }]);
 </script>
 
diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index db4ae08..7430f6a 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -298,7 +298,7 @@
     <!-- entries along the right side of the navbar -->
     <ul class="nav navbar-nav navbar-right" style='margin-right: 6px;'>
       <li>
-        <a ng-cloak ng-show="username" 
+        <a ng-cloak ng-show="username" title="{{currentToken()}}"
           ng-init="workstation = '[% l('<no workstation>') %]'">
             [% l('{{username}} @ {{workstation}}') %]
         </a>
@@ -329,12 +329,18 @@
         <a href class="glyphicon glyphicon-list" 
           uib-dropdown-toggle></a>
         <ul uib-dropdown-menu>
-          <li class="disabled">
-            <a href="" ng-click="" target="_self">
+          <li ng-if="!op_changed">
+            <a href="" ng-click="changeOperator()">
               <span class="glyphicon glyphicon-random"></span>
               [% l('Change Operator') %]
             </a>
           </li>
+          <li ng-if="op_changed">
+            <a href="" ng-click="changeOperatorUndo()">
+              <span class="glyphicon glyphicon-random"></span>
+              [% l('Restore Operator') %]
+            </a>
+          </li>
           <li>
             <a href="./login" ng-click="logout()" target="_self">
               <span class="glyphicon glyphicon-log-out"></span>
diff --git a/Open-ILS/src/templates/staff/share/t_opchange.tt2 b/Open-ILS/src/templates/staff/share/t_opchange.tt2
new file mode 100644
index 0000000..030418a
--- /dev/null
+++ b/Open-ILS/src/templates/staff/share/t_opchange.tt2
@@ -0,0 +1,36 @@
+<!--
+  Username/password prompt for operator change
+-->
+<div>
+  <div class="modal-header">
+    <button type="button" class="close" 
+      ng-click="cancel()" aria-hidden="true">×</button>
+    <h4 class="modal-title alert alert-info">[% l('Operator Change') %]</h4> 
+  </div>
+  <div class="modal-body">
+    <div class="row">
+      <div class="col-md-4">
+        [% l('Username:') %]
+      </div>
+      <div class="col-md-1"></div>
+      <div class="col-md-7">
+        <input ng-keyup="$event.keyCode == 13 ? ok() : null" type='text' ng-model="args.username" class="form-control" focus-me="focus"/>
+      </div>
+    </div>
+    <div class="row">
+      <div class="col-md-4">
+        [% l('Password:') %]
+      </div>
+      <div class="col-md-1"></div>
+      <div class="col-md-7">
+        <input ng-keyup="$event.keyCode == 13 ? ok() : null" type='password' ng-model="args.password" class="form-control"/>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    [% dialog_footer %]
+    <input type="submit" class="btn btn-primary" 
+      ng-click="ok()" value="[% l('OK/Continue') %]"/>
+    <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+  </div>
+</div>
diff --git a/Open-ILS/web/js/ui/default/staff/services/auth.js b/Open-ILS/web/js/ui/default/staff/services/auth.js
index 3446f4a..638ce7d 100644
--- a/Open-ILS/web/js/ui/default/staff/services/auth.js
+++ b/Open-ILS/web/js/ui/default/staff/services/auth.js
@@ -11,10 +11,31 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
 
     var service = {
         // the currently active user (au) object
-        user : function() {
+        user : function(u) {
+            if (u) {
+                this._user = u;
+            }
             return this._user;
         },
 
+        // the user hidden by an operator change
+        OCuser : function(u) {
+            if (u) {
+                this._OCuser = u;
+            }
+            return this._OCuser;
+        },
+
+        // the Op Change hidden auth token string
+        OCtoken : function() {
+            return egHatch.getLoginSessionItem('eg.auth.token.oc');
+        },
+
+        // Op Change hidden authtime in seconds
+        OCauthtime : function() {
+            return egHatch.getLoginSessionItem('eg.auth.time.oc');
+        },
+
         // the currently active auth token string
         token : function() {
             return egHatch.getLoginSessionItem('eg.auth.token');
@@ -47,7 +68,7 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
             .then(function(user) {
                 if (user && user.classname) {
                     // authtoken test succeeded
-                    service._user = user;
+                    service.user(user);
                     service.poll();
                     service.check_workstation(deferred);
 
@@ -156,6 +177,49 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
         return ops.deferred.promise;
     }
 
+    /**
+     * Returns a promise, which is resolved on successful 
+     * login and rejected on failed login.
+     */
+    service.opChange = function(args) {
+        // avoid modifying the caller's data structure.
+        args = angular.copy(args);
+        args.workstation = service.workstation();
+
+        var deferred = $q.defer();
+
+        service.login_api(args).then(function(evt) {
+
+            if (evt.textcode == 'SUCCESS') {
+                service.OCuser(service.user());
+                egHatch.setLoginSessionItem('eg.auth.token.oc', service.token());
+                egHatch.setLoginSessionItem('eg.auth.time.oc', service.authtime());
+                service.handle_login_ok(args, evt);
+                deferred.resolve();
+
+            } else {
+                // note: the likely outcome here is a NO_SESION
+                // server event, which results in broadcasting an 
+                // egInvalidAuth by egNet. 
+                console.error('operator change failed ' + js2JSON(evt));
+                deferred.reject();
+            }
+        });
+
+        return deferred.promise;
+    }
+
+    service.opChangeUndo = function() {
+        if (service.OCtoken()) {
+            service.user(service.OCuser());
+            egHatch.setLoginSessionItem('eg.auth.token', service.OCtoken());
+            egHatch.setLoginSessionItem('eg.auth.time', service.OCauthtime());
+            egHatch.removeLoginSessionItem('eg.auth.token.oc');
+            egHatch.removeLoginSessionItem('eg.auth.time.oc');
+        }
+        return service.testAuthToken();
+    }
+
     service.login_api = function(args) {
         return egNet.request(
             'open-ils.auth',
diff --git a/Open-ILS/web/js/ui/default/staff/services/core.js b/Open-ILS/web/js/ui/default/staff/services/core.js
index dc5ef6c..e61aceb 100644
--- a/Open-ILS/web/js/ui/default/staff/services/core.js
+++ b/Open-ILS/web/js/ui/default/staff/services/core.js
@@ -3,4 +3,4 @@
  * egCoreMod houses all of the services, etc. required by all pages
  * for basic functionality.
  */
-angular.module('egCoreMod', ['cfp.hotkeys', 'ngFileSaver', 'ngCookies']);
+angular.module('egCoreMod', ['cfp.hotkeys', 'ngFileSaver', 'ngCookies', 'ngToast']);
diff --git a/Open-ILS/web/js/ui/default/staff/services/navbar.js b/Open-ILS/web/js/ui/default/staff/services/navbar.js
index 67c117d..2bc6522 100644
--- a/Open-ILS/web/js/ui/default/staff/services/navbar.js
+++ b/Open-ILS/web/js/ui/default/staff/services/navbar.js
@@ -26,8 +26,8 @@ angular.module('egCoreMod')
             inspect(element);
         },
 
-        controller:['$scope','$window','$location','$timeout','hotkeys','egCore',
-            function($scope , $window , $location , $timeout , hotkeys , egCore) {
+        controller:['$scope','$window','$location','$timeout','hotkeys','egCore','$uibModal','ngToast',
+            function($scope , $window , $location , $timeout , hotkeys , egCore , $uibModal , ngToast) {
 
                 function navTo(path) {                                           
                     // Strip the leading "./" if any.
@@ -72,6 +72,43 @@ angular.module('egCoreMod')
                         );
                 }
 
+                $scope.changeOperatorUndo = function() {
+                        egCore.auth.opChangeUndo();
+                        $scope.op_changed = false;
+                        ngToast.create(egCore.strings.OP_CHANGE_SUCCESS);
+                }
+
+                $scope.changeOperator = function() {
+                    $uibModal.open({
+                        templateUrl: './share/t_opchange',
+                        controller:
+                            ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+                            $scope.args = {username : '', password : ''};
+                            $scope.focus = true;
+                            $scope.ok = function() { $uibModalInstance.close($scope.args) }
+                            $scope.cancel = function () { $uibModalInstance.dismiss() }
+                        }]
+                    }).result.then(function (args) {
+                        if (!args || !args.username || !args.password) return;
+                        args.workstation = egCore.auth.workstation();
+                        egCore.auth.opChange(args).then(
+                            function() {
+                                console.log('op change success');
+                                $scope.op_changed = true;
+                                ngToast.create(egCore.strings.OP_CHANGE_SUCCESS);
+                            }, // note success with toast?
+                            function() {
+                                console.log('op change failure');
+                                ngToast.warning(egCore.strings.OP_CHANGE_FAILURE);
+                            }  // note failure with toast?
+                        );
+                    });
+                }
+
+                $scope.currentToken = function () {
+                    return egCore.auth.token();
+                }
+
                 // tied to logout link
                 $scope.logout = function() {
                     egCore.auth.logout();
@@ -81,6 +118,7 @@ angular.module('egCoreMod')
                 egCore.startup.go().then(
                     function() {
                         if (egCore.auth.user()) {
+                            $scope.op_changed = egCore.auth.OCtoken() ? true : false;
                             $scope.username = egCore.auth.user().usrname();
                             $scope.workstation = egCore.auth.workstation();
                         }

commit 653d47e74eab4bf4e097447e4de12556a73d23b0
Author: Bill Erickson <berickxx at gmail.com>
Date:   Wed Sep 28 12:22:44 2016 -0400

    LP#1467663 webstaff: login requires valid workstation
    
    Hide the workstation selector when no workstations are registered.
    After successful login, direct the user to the new workstation admin
    page to create a new workstation.
    
    After successful login with an invalid workstation, direct the user
    to the workstation admin page, issuing a 'remove' command to un-register
    the offending WS.  On the WS admin page, the user can create a new
    workstation or select from their existing workstations.
    
    Any attempt to access a browser client interface (minus the WS admin
    page) without a valid workstation will cause the page to redirect to
    the workstation admin page.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/t_login.tt2 b/Open-ILS/src/templates/staff/t_login.tt2
index 135e9da..ce06e6b0 100644
--- a/Open-ILS/src/templates/staff/t_login.tt2
+++ b/Open-ILS/src/templates/staff/t_login.tt2
@@ -29,7 +29,7 @@
               </div>
             </div>
 
-            <div class="form-group">
+            <div class="form-group" ng-show="workstations.length > 0">
               <label class="col-md-4 control-label" 
                 for="login-workstation">[% l('Workstation') %]</label>
               <div class="col-md-8">
diff --git a/Open-ILS/web/js/ui/default/staff/app.js b/Open-ILS/web/js/ui/default/staff/app.js
index 41910b9..4cf388c 100644
--- a/Open-ILS/web/js/ui/default/staff/app.js
+++ b/Open-ILS/web/js/ui/default/staff/app.js
@@ -43,6 +43,8 @@ function($routeProvider , $locationProvider) {
            ['$scope','$location','$window','egCore',
     function($scope , $location , $window , egCore) {
         $scope.focusMe = true;
+        $scope.args = {};
+        $scope.workstations = [];
 
         // if the user is already logged in, jump to splash page
         if (egCore.auth.user()) $location.path('/');
@@ -92,17 +94,37 @@ function($routeProvider , $locationProvider) {
 
             if (! (args.username && args.password) ) return;
 
+            // if at least one workstation exists, it must be used.
+            if (!args.workstation && $scope.workstations.length > 0) return;
+
             args.type = 'staff';
             egCore.auth.login(args).then(
 
-                function() { 
-                    // after login, send the user back to the originally
-                    // requested page or, if none, the home page.
-                    // TODO: this is a little hinky because it causes 2 
-                    // redirects if no route_to is defined.  Improve.
-                    $window.location.href = 
-                        $location.search().route_to || 
-                        $location.path('/').absUrl()
+                function(result) { 
+                    // After login, send the user to:
+                    // 1. The WS admin page for WS maintenance.
+                    // 2. The page originally requested by the caller
+                    // 3. Home page.
+
+                    // NOTE: using $location.path(...) results in
+                    // confusing intermediate page loads, since
+                    // path(...) is a setter function.  Build the URL by
+                    // hand instead from the configured base path.
+                    var route_to = egCore.env.basePath;
+
+                    if (result.invalid_workstation) {
+                        // route to WS admin page to delete the offending
+                        // WS and create a new one.
+                        route_to += 
+                            'admin/workstation/workstations?remove=' 
+                                + encodeURIComponent(args.workstation);
+
+                    } else if ($location.search().route_to) {
+                        // Route to the originally requested page.
+                        route_to = $location.search().route_to;
+                    }
+
+                    $window.location.href = route_to;
                 },
                 function() {
                     $scope.args.password = '';
diff --git a/Open-ILS/web/js/ui/default/staff/services/auth.js b/Open-ILS/web/js/ui/default/staff/services/auth.js
index a677ff4..3446f4a 100644
--- a/Open-ILS/web/js/ui/default/staff/services/auth.js
+++ b/Open-ILS/web/js/ui/default/staff/services/auth.js
@@ -6,8 +6,8 @@
 angular.module('egCoreMod')
 
 .factory('egAuth', 
-       ['$q','$timeout','$rootScope','egNet','egHatch', 
-function($q , $timeout , $rootScope , egNet , egHatch) {
+       ['$q','$timeout','$rootScope','$window','$location','egNet','egHatch',
+function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
 
     var service = {
         // the currently active user (au) object
@@ -49,23 +49,8 @@ function($q , $timeout , $rootScope , egNet , egHatch) {
                     // authtoken test succeeded
                     service._user = user;
                     service.poll();
-                   
-                    if (user.wsid()) {
-                        // user previously logged in with a workstation. 
-                        // Find the workstation name from the list 
-                        // of configured workstations
-                        egHatch.getItem('eg.workstation.all')
-                        .then(function(all) { 
-                            if (all) {
-                                var ws = all.filter(
-                                    function(w) {return w.id == user.wsid()})[0];
-                                if (ws) service.ws = ws.name;
-                            }
-                            deferred.resolve(); // found WS
-                        });
-                    } else {
-                        deferred.resolve(); // no WS
-                    }
+                    service.check_workstation(deferred);
+
                 } else {
                     // authtoken test failed
                     egHatch.clearLoginSessionItems();
@@ -75,60 +60,125 @@ function($q , $timeout , $rootScope , egNet , egHatch) {
 
         } else {
             // no authtoken to test
-            deferred.reject();
+            deferred.reject('No authtoken found');
         }
 
         return deferred.promise;
     };
 
+    service.check_workstation = function(deferred) {
+
+        var user = service.user();
+        var ws_path = '/admin/workstation/workstations';
+
+        return egHatch.getItem('eg.workstation.all')
+        .then(function(workstations) { 
+            if (!workstations) workstations = [];
+
+            // If the user is authenticated with a workstation, get the
+            // name from the locally registered version of the workstation.
+
+            if (user.wsid()) {
+
+                var ws = workstations.filter(
+                    function(w) {return w.id == user.wsid()})[0];
+
+                if (ws) { // success
+                    service.ws = ws.name;
+                    deferred.resolve();
+                    return;
+                }
+            }
+
+            if ($location.path() == ws_path) {
+                // User is on the workstation admin page.  No need
+                // to redirect.
+                deferred.resolve();
+                return;
+            }
+
+            // At this point, the user is trying to access a page
+            // besides the workstation admin page without a valid
+            // registered workstation.  Send them back to the 
+            // workstation admin page.
+
+            // NOTE: egEnv also defines basePath, but we cannot import
+            // egEnv here becuase it creates a circular reference.
+            $window.location.href = '/eg/staff' + ws_path;
+            deferred.resolve();
+        });
+    }
+
     /**
      * Returns a promise, which is resolved on successful 
      * login and rejected on failed login.
      */
-    service.login = function(args) {
-        var deferred = $q.defer();
+    service.login = function(args, ops) {
+        // avoid modifying the caller's data structure.
+        args = angular.copy(args);
+
+        if (!ops) { // only set on redo attempts.
+            ops = {deferred : $q.defer()};
+
+            // Clear old LoginSession keys that were left in localStorage
+            // when the previous user closed the browser without logging
+            // out.  Under normal circumstance, LoginSession data would
+            // have been cleared by now, either during logout or cookie
+            // expiration.  But, if for some reason the user manually
+            // removed the auth token cookie w/o closing the browser
+            // (say, for testing), then this serves double duty to ensure
+            // LoginSession data cannot persist across logins.
+            egHatch.clearLoginSessionItems();
+        }
 
-        // Clear old LoginSession keys that were left in localStorage
-        // when the previous user closed the browser without logging
-        // out.  Under normal circumstance, LoginSession data would
-        // have been cleared by now, either during logout or cookie
-        // expiration.  But, if for some reason the user manually
-        // removed the auth token cookie w/o closing the browser
-        // (say, for testing), then this serves double duty to ensure
-        // LoginSession data cannot persist across logins.
-        egHatch.clearLoginSessionItems();
+        service.login_api(args).then(function(evt) {
 
-        egNet.request(
+            if (evt.textcode == 'SUCCESS') {
+                service.handle_login_ok(args, evt);
+                ops.deferred.resolve({
+                    invalid_workstation : ops.invalid_workstation
+                });
+
+            } else if (evt.textcode == 'WORKSTATION_NOT_FOUND') {
+                ops.invalid_workstation = true;
+                delete args.workstation;
+                service.login(args, ops); // redo w/o workstation
+
+            } else {
+                // note: the likely outcome here is a NO_SESION
+                // server event, which results in broadcasting an 
+                // egInvalidAuth by egNet. 
+                console.error('login failed ' + js2JSON(evt));
+                ops.deferred.reject();
+            }
+        });
+
+        return ops.deferred.promise;
+    }
+
+    service.login_api = function(args) {
+        return egNet.request(
             'open-ils.auth',
-            'open-ils.auth.authenticate.init', args.username).then(
-            function(seed) {
-                args.password = hex_md5(seed + hex_md5(args.password))
-                egNet.request(
+            'open-ils.auth.authenticate.init', args.username)
+        .then(function(seed) {
+                // avoid clobbering the bare password in case
+                // we need it for a login redo attempt.
+                var login_args = angular.copy(args);
+                login_args.password = hex_md5(seed + hex_md5(args.password));
+
+                return egNet.request(
                     'open-ils.auth',
-                    'open-ils.auth.authenticate.complete', args).then(
-                    function(evt) {
-                        if (evt.textcode == 'SUCCESS') {
-                            service.ws = args.workstation; 
-                            service.poll();
-                            egHatch.setLoginSessionItem(
-                                'eg.auth.token', evt.payload.authtoken);
-                            egHatch.setLoginSessionItem(
-                                'eg.auth.time', evt.payload.authtime);
-                            deferred.resolve();
-                        } else {
-                            // note: the likely outcome here is a NO_SESION
-                            // server event, which results in broadcasting an 
-                            // egInvalidAuth by egNet. 
-                            console.error('login failed ' + js2JSON(evt));
-                            deferred.reject();
-                        }
-                    }
-                )
+                    'open-ils.auth.authenticate.complete', login_args)
             }
         );
+    }
 
-        return deferred.promise;
-    };
+    service.handle_login_ok = function(args, evt) {
+        service.ws = args.workstation; 
+        egHatch.setLoginSessionItem('eg.auth.token', evt.payload.authtoken);
+        egHatch.setLoginSessionItem('eg.auth.time', evt.payload.authtime);
+        service.poll();
+    }
 
     /**
      * Force-check the validity of the authtoken on occasion. 

commit f812b3e4093784ac4793cd8f99029083bf2f922f
Author: Bill Erickson <berickxx at gmail.com>
Date:   Wed Sep 28 12:21:56 2016 -0400

    LP#1467663 webstaff: dedicated workstation admin page
    
    Adds a new standalone workstations admin page, accessible from the main
    Workstation Administration page.
    
    Adds the ability to delete a workstation by name both within the page
    and via URL (with permission).
    
    Fixes the "Use Now" button so that it actually logs the user out before
    redirecting to the login page with the desired workstation
    pre-selected.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/workstation/index.tt2 b/Open-ILS/src/templates/staff/admin/workstation/index.tt2
index 3f927a4..a0f53d2 100644
--- a/Open-ILS/src/templates/staff/admin/workstation/index.tt2
+++ b/Open-ILS/src/templates/staff/admin/workstation/index.tt2
@@ -5,6 +5,7 @@
 %]
 
 [% BLOCK APP_JS %]
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
@@ -15,6 +16,7 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
     '[% l('Delete content for key "[_1]"?', '{{deleteKey}}') %]';
   s.DEFAULT_WS_LABEL = '[% l('[_1] (Default)', '{{ws}}') %]';
   s.WS_EXISTS = '[% l("Workstation name already exists.  Use it anyway?") %]';
+  s.WS_USED = '[% l("Workstation is already registered") %]';
 }]);
 </script>
 [% END %]
diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
index 360f6d7..8c8da45 100644
--- a/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
@@ -1,16 +1,5 @@
-<br/>
-<style>
-  #admin-workstation-container .row {
-    margin-top: 5px;
-  }
-  #admin-workstation-container .new-entry {
-    margin-top: 10px;
-    padding-top: 10px;
-    border-top: 2px solid #F5F5F5;
-  }
-</style>
 
-<div class="container" id="admin-workstation-container">
+<div class="container admin-splash-container">
 
   <div class="row">
     <div class="col-md-6">
@@ -33,63 +22,6 @@
     </div>
   </div>
 
-  <div class="row new-entry">
-    <div class="col-md-6">
-      [% l('Workstations Registered With This Computer') %]
-    </div>
-  </div>
-  <div class="row">
-    <div class="col-md-6">
-      <select class="form-control" ng-model="selectedWS">
-        <option ng-repeat="ws in workstations" value="{{ws}}"
-          ng-selected="ws == selectedWS">
-          {{getWSLabel(ws)}}
-        </option>
-      </select>
-    </div>
-  </div>
-
-  <div class="row">
-    <div class="col-md-6">
-      <button class="btn btn-default" ng-click="useWS()">
-        [% l('Use Now') %]
-      </button>
-      <button class="btn btn-default" ng-click="setDefaultWS()">
-        [% l('Mark As Default') %]
-      </button>
-      <button class="btn btn-default btn-danger disabled">
-        [% l('Delete') %]
-      </button>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-6">
-      [% l('Register a New Workstation For This Computer') %]
-    </div>
-  </div>
-  <div class="row">
-    <div class="col-md-6">
-      <div class="input-group">
-        <div class="input-group-btn">
-          <eg-org-selector 
-            selected="contextOrg"
-            hidden-test="wsOrgHidden">
-            disable-test="cant_have_users">
-          </eg-org-selector>
-        </div>
-        <input type='text' class='form-control'  
-          title="[% l('Workstation Name') %]"
-          placeholder="[% l('Workstation Name') %]"
-          ng-model='newWSName'/>
-        <div class="input-group-btn">
-          <button class="btn btn-default" ng-click="registerWS()">
-            [% l('Register') %]
-          </button>
-        </div>
-      </div>
-    </div>
-  </div>
 
   <div class="row new-entry">
     <div class="col-md-2">
@@ -117,6 +49,15 @@
 
   <div class="row new-entry">
     <div class="col-md-6">
+      <span class="glyphicon glyphicon-pushpin"></span>
+      <a target="_self" href="./admin/workstation/workstations">
+        [% l('Registered Workstations') %]
+      </a>
+    </div>
+  </div>
+
+  <div class="row new-entry">
+    <div class="col-md-6">
       <span class="glyphicon glyphicon-print"></span>
       <a target="_self" href="./admin/workstation/print/config">
         [% l('Printer Settings') %]
diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_workstations.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_workstations.tt2
new file mode 100644
index 0000000..6210395
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/workstation/t_workstations.tt2
@@ -0,0 +1,74 @@
+<div class="container admin-splash-container">
+
+  <div class="alert alert-warning" ng-show="removing_ws">
+    [% l('Workstation "[_1]" is no longer valid.  Removing registration.', 
+        '{{removing_ws}}'); %]
+  </div>
+
+  <div class="alert alert-danger" 
+    ng-show="page_loaded && workstations.length == 0">
+    [% l('Please register a workstation.') %]
+  </div>
+
+  <div class="row">
+    <div class="col-md-6">
+      [% l('Register a New Workstation For This Browser') %]
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-md-6">
+      <div class="input-group">
+        <div class="input-group-btn">
+          <eg-org-selector 
+            selected="contextOrg"
+            hidden-test="wsOrgHidden">
+            disable-test="cant_have_users">
+          </eg-org-selector>
+        </div>
+        <input type='text' class='form-control'  
+          title="[% l('Workstation Name') %]"
+          placeholder="[% l('Workstation Name') %]"
+          ng-model='newWSName'/>
+        <div class="input-group-btn">
+          <button class="btn btn-default" ng-click="register_ws()"
+            ng-disabled="is_registering">
+            [% l('Register') %]
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <div class="row new-entry">
+    <div class="col-md-6">
+      [% l('Workstations Registered With This Browser') %]
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-md-6">
+      <select class="form-control" ng-model="selectedWS">
+        <option ng-repeat="ws in workstations" value="{{ws}}"
+          ng-selected="ws == selectedWS">
+          {{get_ws_label(ws)}}
+        </option>
+      </select>
+    </div>
+  </div>
+
+  <div class="row">
+    <div class="col-md-6">
+      <button class="btn btn-success" ng-click="use_now()">
+        [% l('Use Now') %]
+      </button>
+      <button class="btn btn-default" ng-click="set_default_ws(selectedWS)">
+        [% l('Mark As Default') %]
+      </button>
+      <button class="btn btn-default btn-danger" 
+        ng-click="remove_ws(selectedWS)"
+        ng-disabled="is_removing || !can_delete_ws(selectedWS)">
+        [% l('Remove') %]
+      </button>
+    </div>
+  </div>
+</div>
+
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
index d31c5fd..78b6d03 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
@@ -14,6 +14,12 @@ angular.module('egWorkstationAdmin',
     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
     var resolver = {delay : function(egStartup) {return egStartup.go()}};
 
+    $routeProvider.when('/admin/workstation/workstations', {
+        templateUrl: './admin/workstation/t_workstations',
+        controller: 'WSRegCtrl',
+        resolve : resolver
+    });
+
     $routeProvider.when('/admin/workstation/print/config', {
         templateUrl: './admin/workstation/t_print_config',
         controller: 'PrintConfigCtrl',
@@ -41,71 +47,35 @@ angular.module('egWorkstationAdmin',
     });
 }])
 
-.controller('SplashCtrl',
-       ['$scope','$window','$location','egCore','egConfirmDialog',
-function($scope , $window , $location , egCore , egConfirmDialog) {
-
-    var allWorkstations = [];
-    var permMap = {};
-    $scope.contextOrg = egCore.org.get(egCore.auth.user().ws_ou());
-
-    egCore.perm.hasPermAt('REGISTER_WORKSTATION', true)
-    .then(function(orgList) { 
+.factory('workstationSvc',
+       ['$q','$timeout','$location','egCore','egConfirmDialog',
+function($q , $timeout , $location , egCore , egConfirmDialog) {
+    
+    var service = {};
 
-        // hide orgs in the context org selector where this login
-        // does not have the reg_ws perm
-        $scope.wsOrgHidden = function(id) {
-            return orgList.indexOf(id) == -1;
-        }
-        $scope.userHasRegPerm = 
-            orgList.indexOf($scope.contextOrg.id()) > -1;
-    });
+    service.get_all = function() {
+        return egCore.hatch.getItem('eg.workstation.all')
+        .then(function(all) { return all || [] });
+    }
 
-    // fetch the stored WS info
-    egCore.hatch.getItem('eg.workstation.all')
-    .then(function(all) {
-        allWorkstations = all || [];
-        $scope.workstations = 
-            allWorkstations.map(function(w) { return w.name });
+    service.get_default = function() {
         return egCore.hatch.getItem('eg.workstation.default');
-    })
-    .then(function(def) { 
-        $scope.defaultWS = def;
-        $scope.activeWS = $scope.selectedWS = egCore.auth.workstation() || def;
-    });
-
-    $scope.getWSLabel = function(ws) {
-        return ws == $scope.defaultWS ? 
-            egCore.strings.$replace(egCore.strings.DEFAULT_WS_LABEL, {ws:ws}) : ws;
     }
 
-    $scope.setDefaultWS = function() {
-        egCore.hatch.setItem(
-            'eg.workstation.default', $scope.selectedWS)
-        .then(function() { $scope.defaultWS = $scope.selectedWS });
+    service.set_default = function(name) {
+        return egCore.hatch.setItem('eg.workstation.default', name);
     }
 
-    $scope.cant_have_users = function (id) { return !egCore.org.CanHaveUsers(id); };
-    $scope.cant_have_volumes = function (id) { return !egCore.org.CanHaveVolumes(id); };
-
-    // redirect the user to the login page using the current
-    // workstation as the workstation URL param
-    $scope.useWS = function() {
-        $window.location.href = $location
-            .path('/login')
-            .search({ws : $scope.selectedWS})
-            .absUrl();
-    }
-
-    $scope.registerWS = function() {
-        register_workstation(
-            $scope.newWSName,
-            $scope.contextOrg.shortname() + '-' + $scope.newWSName,
-            $scope.contextOrg.id()
-        );
-    }
+    service.register_workstation = function(base_name, name, org_id) {
+        return service.register_ws_api(base_name, name, org_id)
+        .then(function(ws_id) {
+            return service.track_new_ws(ws_id, name, org_id);
+        });
+    };
 
-    function register_workstation(base_name, name, org_id, override) {
+    service.register_ws_api = 
+        function(base_name, name, org_id, override, deferred) {
+        if (!deferred) deferred = $q.defer();
 
         var method = 'open-ils.actor.workstation.register';
         if (override) method += '.override';
@@ -114,49 +84,80 @@ function($scope , $window , $location , egCore , egConfirmDialog) {
             'open-ils.actor', method, egCore.auth.token(), name, org_id)
 
         .then(function(resp) {
+
             if (evt = egCore.evt.parse(resp)) {
                 console.log('register returned ' + evt.toString());
 
                 if (evt.textcode == 'WORKSTATION_NAME_EXISTS' && !override) {
+
                     egConfirmDialog.open(
                         egCore.strings.WS_EXISTS, base_name, {  
                             ok : function() {
-                                register_workstation(base_name, name, org_id, true);
+                                service.register_ws_api(
+                                    base_name, name, org_id, true, deferred)
                             },
-                            cancel : function() {} 
+                            cancel : function() {deferred.reject()} 
                         }
                     );
 
                 } else {
-                    // TODO: provide permission error display
                     alert(evt.toString());
+                    deferred.reject();
                 }
             } else if (resp) {
-                $scope.workstations.push(name);
-
-                allWorkstations.push({   
-                    id : resp,
-                    name : name,
-                    owning_lib : org_id
-                });
-
-                egCore.hatch.setItem(
-                    'eg.workstation.all', allWorkstations)
-                .then(function() {
-                    if (allWorkstations.length == 1) {
-                        // first one registerd, also mark it as the default
-                        $scope.selectedWS = name;
-                        $scope.setDefaultWS();
-                    }
-                });
+                console.log('Resolving register promise with: ' + resp);
+                deferred.resolve(resp);
             }
         });
+
+        return deferred.promise;
+    }
+
+    service.track_new_ws = function(ws_id, ws_name, owning_lib) {
+        console.log('Tracking newly created WS with ID ' + ws_id);
+        var new_ws = {id : ws_id, name : ws_name, owning_lib : owning_lib};
+
+        return service.get_all()
+        .then(function(all) {
+            all.push(new_ws);
+            return egCore.hatch.setItem('eg.workstation.all', all)
+            .then(function() { return new_ws });
+        });
     }
 
-    $scope.wsOrgChanged = function(org) {
-        $scope.contextOrg = org;
+    // Remove all traces of the workstation locally.
+    // This does not remove the WS from the server.
+    service.remove_workstation = function(name) {
+        console.debug('Removing workstation: ' + name);
+
+        return egCore.hatch.getItem('eg.workstation.all')
+
+        // remove from list of all workstations
+        .then(function(all) {
+            if (!all) all = [];
+            var keep = all.filter(function(ws) {return ws.name != name});
+            return egCore.hatch.setItem('eg.workstation.all', keep)
+
+        }).then(function() { 
+
+            return service.get_default()
+
+        }).then(function(def) {
+            if (def == name) {
+                console.debug('Removing default workstation: ' + name);
+                return egCore.hatch.removeItem('eg.workstation.default');
+            }
+        });
     }
 
+    return service;
+}])
+
+
+.controller('SplashCtrl',
+       ['$scope','$window','$location','egCore','egConfirmDialog',
+function($scope , $window , $location , egCore , egConfirmDialog) {
+
     // ---------------------
     // Hatch Configs
     $scope.hatchURL = egCore.hatch.hatchURL();
@@ -575,3 +576,145 @@ function($scope , $q , egCore , egConfirmDialog) {
         );
     }
 }])
+
+.controller('WSRegCtrl',
+       ['$scope','$q','$window','$location','egCore','egAlertDialog','workstationSvc',
+function($scope , $q , $window , $location , egCore , egAlertDialog , workstationSvc) {
+
+    var all_workstations = [];
+    var reg_perm_orgs = [];
+
+    $scope.page_loaded = false;
+    $scope.contextOrg = egCore.org.get(egCore.auth.user().ws_ou());
+    $scope.wsOrgChanged = function(org) { $scope.contextOrg = org; }
+
+    console.log('set context org to ' + $scope.contextOrg);
+
+    // fetch workstation reg perms
+    egCore.perm.hasPermAt('REGISTER_WORKSTATION', true)
+    .then(function(orgList) { 
+        reg_perm_orgs = orgList;
+
+        // hide orgs in the context org selector where this login
+        // does not have the reg_ws perm
+        $scope.wsOrgHidden = function(id) {
+            return reg_perm_orgs.indexOf(id) == -1;
+        }
+
+    // fetch the locally stored workstation data
+    }).then(function() {
+        return workstationSvc.get_all()
+        
+    }).then(function(all) {
+        all_workstations = all || [];
+        $scope.workstations = 
+            all_workstations.map(function(w) { return w.name });
+        return workstationSvc.get_default()
+
+    // fetch the default workstation
+    }).then(function(def) { 
+        $scope.defaultWS = def;
+        $scope.activeWS = $scope.selectedWS = egCore.auth.workstation() || def;
+
+    // Handle any URL commands.
+    }).then(function() {
+        var remove = $location.search().remove;
+         if (remove) {
+            console.log('Removing WS via URL request: ' + remove);
+            return $scope.remove_ws(remove).then(
+                function() { $scope.page_loaded = true; });
+        }
+        $scope.page_loaded = true;
+    });
+
+    $scope.get_ws_label = function(ws) {
+        return ws == $scope.defaultWS ? 
+            egCore.strings.$replace(egCore.strings.DEFAULT_WS_LABEL, {ws:ws}) : ws;
+    }
+
+    $scope.set_default_ws = function(name) {
+        delete $scope.removing_ws;
+        $scope.defaultWS = name;
+        workstationSvc.set_default(name);
+    }
+
+    $scope.cant_have_users = 
+        function (id) { return !egCore.org.CanHaveUsers(id); };
+    $scope.cant_have_volumes = 
+        function (id) { return !egCore.org.CanHaveVolumes(id); };
+
+    // Log out and return to login page with selected WS 
+    $scope.use_now = function() {
+        egCore.auth.logout();
+        $window.location.href = $location
+            .path('/login')
+            .search({ws : $scope.selectedWS})
+            .absUrl();
+    }
+
+    $scope.can_delete_ws = function(name) {
+        var ws = all_workstations.filter(
+            function(ws) { return ws.name == name })[0];
+        return ws && reg_perm_orgs.indexOf(ws.owning_lib);
+    }
+
+    $scope.remove_ws = function(remove_me) {
+        $scope.removing_ws = remove_me;
+
+        // Perm is used to disable Remove button in UI, but have to check
+        // again here in case we're removing a WS based on URL params.
+        if (!$scope.can_delete_ws(remove_me)) return $q.when();
+
+        $scope.is_removing = true;
+        return workstationSvc.remove_workstation(remove_me)
+        .then(function() {
+
+            all_workstations = all_workstations.filter(
+                function(ws) { return ws.name != remove_me });
+
+            $scope.workstations = $scope.workstations.filter(
+                function(ws) { return ws != remove_me });
+
+            if ($scope.selectedWS == remove_me) 
+                $scope.selectedWS = $scope.workstations[0];
+
+            if ($scope.defaultWS == remove_me) 
+                $scope.defaultWS = '';
+
+            $scope.is_removing = false;
+        });
+    }
+
+    $scope.register_ws = function() {
+        delete $scope.removing_ws;
+
+        var full_name = 
+            $scope.contextOrg.shortname() + '-' + $scope.newWSName;
+
+        if ($scope.workstations.indexOf(full_name) > -1) {
+            // avoid duplicate local registrations
+            return egAlertDialog.open(egCore.strings.WS_USED);
+        }
+
+        $scope.is_registering = true;
+        workstationSvc.register_workstation(
+            $scope.newWSName, full_name,
+            $scope.contextOrg.id()
+
+        ).then(function(new_ws) {
+            $scope.workstations.push(new_ws.name);
+            all_workstations.push(new_ws);  
+            $scope.is_registering = false;
+
+            if (!$scope.selectedWS) {
+                $scope.selectedWS = new_ws.name;
+            }
+            if (!$scope.defaultWS) {
+                return $scope.set_default_ws(new_ws.name);
+            }
+            $scope.newWSName = '';
+        });
+    }
+}])
+
+

commit a493da6b44285ab18e9625a9e1717aafc2ddf511
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Sep 13 17:21:51 2016 -0400

    make egAudio respect eg.audio.disable
    
    This patch also removes some console.debug noise.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/services/audio.js b/Open-ILS/web/js/ui/default/staff/services/audio.js
index 1b88978..8cab872 100644
--- a/Open-ILS/web/js/ui/default/staff/services/audio.js
+++ b/Open-ILS/web/js/ui/default/staff/services/audio.js
@@ -40,37 +40,41 @@ angular.module('egCoreMod')
 
     service.play_url = function(path, orig_path) {
 
-        var url = service.url_cache[path] || 
-            service.base_url + path.replace(/\./g, '/') + '.wav';
+        egHatch.getItem('eg.audio.disable').then(function(audio_disabled) {
+            if (!audio_disabled) {
+        
+                var url = service.url_cache[path] || 
+                    service.base_url + path.replace(/\./g, '/') + '.wav';
 
-        var player = new Audio(url);
+                var player = new Audio(url);
 
-        player.onloadeddata = function() {
-            console.debug('Playing audio URL: ' + url);
-            service.url_cache[orig_path] = url;
-            player.play();
-        };
+                player.onloadeddata = function() {
+                    service.url_cache[orig_path] = url;
+                    player.play();
+                };
 
-        if (service.url_cache[path]) {
-            // when serving from the cache, avoid secondary URL lookups.
-            return;
-        }
+                if (service.url_cache[path]) {
+                    // when serving from the cache, avoid secondary URL lookups.
+                    return;
+                }
 
-        player.onerror = function() {
-            // Unable to play path at the requested URL.
+                player.onerror = function() {
+                    // Unable to play path at the requested URL.
             
-            if (!path.match(/\./)) {
-                // all fall-through options have been exhausted.
-                // No path to play.
-                console.warn(
-                    "No suitable URL found for path '" + orig_path + "'");
-                return;
-            }
+                    if (!path.match(/\./)) {
+                        // all fall-through options have been exhausted.
+                        // No path to play.
+                        console.warn(
+                            "No suitable URL found for path '" + orig_path + "'");
+                        return;
+                    }
 
-            // Fall through to the next (more generic) option
-            path = path.replace(/\.[^\.]+$/, '');
-            service.play_url(path, orig_path);
-        }
+                    // Fall through to the next (more generic) option
+                    path = path.replace(/\.[^\.]+$/, '');
+                    service.play_url(path, orig_path);
+                }
+            }
+        });
     }
 
     return service;

commit 95e3f60bbc64d15539712fc406ea8912086c9793
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Sep 12 12:43:45 2016 -0400

    webstaff: Add reports menu option
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index cce234b..db4ae08 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -285,6 +285,12 @@
               [% l('Local Administration') %]
             </a>
           </li>
+          <li>
+            <a href="./reporter/legacy/main" target="_self">
+              <span class="glyphicon glyphicon-object-align-bottom"></span>
+              [% l('Reports') %]
+            </a>
+          </li>
         </ul> <!-- admin dropdown -->
       </li>
     </ul> <!-- end left side entries -->

commit 4be5e57515a6c14eeca59b78040012e5313c5564
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Sep 12 11:30:07 2016 -0400

    webstaff: Supply a back link on tabular report output
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/reporter/clark-kent.pl b/Open-ILS/src/reporter/clark-kent.pl
index 9fbf602..72edf18 100755
--- a/Open-ILS/src/reporter/clark-kent.pl
+++ b/Open-ILS/src/reporter/clark-kent.pl
@@ -569,7 +569,7 @@ sub build_html {
 			<link rel="stylesheet" href="/js/sortable/sortable-theme-minimal.css" />
 		CSS
 
-		print $raw "</head><body><table class='sortable-theme-minimal' data-sortable>";
+		print $raw "</head><body><a href='report-data.html'>Back to output index</a><br/><table class='sortable-theme-minimal' data-sortable>";
 
 		{	no warnings;
 			print $raw "<thead><tr><th>".join('</th><th>', @{$r->{column_labels}})."</th></tr></thead>\n<tbody>";

-----------------------------------------------------------------------

Summary of changes:
 .../perlmods/lib/OpenILS/WWW/EGCatLoader/Util.pm   |    6 +
 Open-ILS/src/reporter/clark-kent.pl                |    2 +-
 .../conify/global/actor/org_unit_custom_tree.tt2   |    3 +
 .../conify/global/asset/copy_location_group.tt2    |    3 +
 Open-ILS/src/templates/staff/acq/index.tt2         |   21 ++
 Open-ILS/src/templates/staff/admin/acq/index.tt2   |   15 +
 .../src/templates/staff/admin/acq/t_splash.tt2     |   52 +++
 .../src/templates/staff/admin/local/t_splash.tt2   |   34 +-
 .../src/templates/staff/admin/server/t_splash.tt2  |   24 +-
 .../templates/staff/admin/workstation/index.tt2    |    6 +
 .../staff/admin/workstation/t_print_templates.tt2  |   15 +-
 .../templates/staff/admin/workstation/t_splash.tt2 |  119 +++----
 .../staff/admin/workstation/t_workstations.tt2     |   74 ++++
 Open-ILS/src/templates/staff/base.tt2              |    1 -
 Open-ILS/src/templates/staff/base_js.tt2           |    2 +
 Open-ILS/src/templates/staff/cat/volcopy/index.tt2 |    6 +
 .../src/templates/staff/cat/volcopy/t_view.tt2     |   56 ++--
 Open-ILS/src/templates/staff/circ/patron/index.tt2 |    4 +-
 .../staff/circ/patron/t_bill_history_xacts.tt2     |    2 +
 .../src/templates/staff/circ/patron/t_bills.tt2    |    5 +-
 .../staff/circ/patron/t_search_results.tt2         |    5 +
 .../src/templates/staff/circ/patron/t_summary.tt2  |   16 +-
 .../src/templates/staff/circ/transits/t_list.tt2   |   29 +-
 Open-ILS/src/templates/staff/navbar.tt2            |  107 ++++++-
 .../share/print_templates/t_bills_historical.tt2   |    2 +-
 .../share/print_templates/t_hold_shelf_list.tt2    |   14 +-
 Open-ILS/src/templates/staff/share/t_opchange.tt2  |   49 +++
 Open-ILS/src/templates/staff/t_login.tt2           |    2 +-
 Open-ILS/web/js/dojo/openils/XUL.js                |   85 +++--
 Open-ILS/web/js/ui/default/acq/common/li_table.js  |   39 ++-
 Open-ILS/web/js/ui/default/acq/common/vlagent.js   |    4 +-
 Open-ILS/web/js/ui/default/staff/acq/app.js        |   46 +++
 Open-ILS/web/js/ui/default/staff/admin/acq/app.js  |   60 ++++
 .../js/ui/default/staff/admin/workstation/app.js   |  370 +++++++++++++++-----
 Open-ILS/web/js/ui/default/staff/app.js            |   38 ++-
 .../web/js/ui/default/staff/cat/catalog/app.js     |   23 ++-
 .../web/js/ui/default/staff/cat/volcopy/app.js     |   26 ++-
 .../web/js/ui/default/staff/circ/patron/app.js     |   28 ++-
 .../web/js/ui/default/staff/circ/patron/bills.js   |   39 ++-
 .../js/ui/default/staff/circ/services/patrons.js   |   89 +++++
 .../js/ui/default/staff/circ/services/transits.js  |    4 +-
 .../web/js/ui/default/staff/circ/transits/list.js  |  169 +++++++--
 Open-ILS/web/js/ui/default/staff/services/audio.js |   54 ++--
 Open-ILS/web/js/ui/default/staff/services/auth.js  |  232 +++++++++---
 Open-ILS/web/js/ui/default/staff/services/core.js  |    9 +-
 Open-ILS/web/js/ui/default/staff/services/file.js  |   12 +-
 .../web/js/ui/default/staff/services/navbar.js     |   43 +++-
 Open-ILS/web/js/ui/default/staff/services/print.js |   34 ++-
 48 files changed, 1656 insertions(+), 422 deletions(-)
 create mode 100644 Open-ILS/src/templates/staff/acq/index.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/acq/index.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/acq/t_splash.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/workstation/t_workstations.tt2
 create mode 100644 Open-ILS/src/templates/staff/share/t_opchange.tt2
 create mode 100644 Open-ILS/web/js/ui/default/staff/acq/app.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/acq/app.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/circ/services/patrons.js


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list