[open-ils-commits] [GIT] Evergreen ILS branch master updated. 4dbdd7bba989fe9826a2f28d07dfd5823c4e5e2d

Evergreen Git git at git.evergreen-ils.org
Tue Sep 4 14:07:39 EDT 2018


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  4dbdd7bba989fe9826a2f28d07dfd5823c4e5e2d (commit)
       via  b63e80d18dc87f30d66fdd4914ea77bd24d6a75e (commit)
       via  5ef57330c9a49db91f5794e53874a47ce6432ff7 (commit)
       via  0c4238dfce4cf2375ff02099b6dab55b692ec8a4 (commit)
       via  5cbc171516a9ed68bbbfa1d6058c97255e907f25 (commit)
       via  3af031b4a1449d5e28bbaa15e5bdcf7a451c2c5f (commit)
       via  4b4e283e30c332f6dca9dea997e58d0e935f0808 (commit)
       via  14af809466039334348ba38b8417fb462d57f55b (commit)
      from  c4d1f82da6bb1f3cf73974153fa60644bbfa5e18 (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 4dbdd7bba989fe9826a2f28d07dfd5823c4e5e2d
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Sep 4 13:37:33 2018 -0400

    LP#1712854 Stamping DB upgrade for hold UIs sort/speed
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index c2ace32..5fb343d 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -92,7 +92,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1123', :eg_version); -- cesardv/berick/gmcharlt
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1124', :eg_version); -- miker/kmlussier/berick
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.wide_holds-workstation-settings.sql b/Open-ILS/src/sql/Pg/upgrade/1124.data.wide_holds-workstation-settings.sql
similarity index 90%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.data.wide_holds-workstation-settings.sql
rename to Open-ILS/src/sql/Pg/upgrade/1124.data.wide_holds-workstation-settings.sql
index e693d68..d45c4d7 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.wide_holds-workstation-settings.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/1124.data.wide_holds-workstation-settings.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
---SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('1124', :eg_version);
 
 INSERT into config.workstation_setting_type (name, grp, datatype, label)
 VALUES (

commit b63e80d18dc87f30d66fdd4914ea77bd24d6a75e
Author: Kathy Lussier <klussier at masslnc.org>
Date:   Wed Aug 29 17:00:39 2018 -0400

    LP#1712854: Add wide_hold grid settings to server storage
    
    Adds the new wide_hold grid settings used in the holds shelf and record holds
    interfaces to server storage, as is done with other grid settings. Also removes
    the legacy eg.grid.circ.holds.shelf and eg.grid.cat.catalog.holds settings.
    
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index cf51b60..7973a4d 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -18714,10 +18714,10 @@ VALUES (
         'cwst', 'label'
     )
 ), (
-    'eg.grid.cat.catalog.holds', 'gui', 'object',
+    'eg.grid.cat.catalog.wide_holds', 'gui', 'object',
     oils_i18n_gettext(
-        'eg.grid.cat.catalog.holds',
-        'Grid Config: cat.catalog.holds',
+        'eg.grid.cat.catalog.wide_holds',
+        'Grid Config: cat.catalog.wide_holds',
         'cwst', 'label'
     )
 ), (
@@ -18735,10 +18735,10 @@ VALUES (
         'cwst', 'label'
     )
 ), (
-    'eg.grid.circ.holds.shelf', 'gui', 'object',
+    'eg.grid.circ.wide_holds.shelf', 'gui', 'object',
     oils_i18n_gettext(
-        'eg.grid.circ.holds.shelf',
-        'Grid Config: circ.holds.shelf',
+        'eg.grid.circ.wide_holds.shelf',
+        'Grid Config: circ.wide_holds.shelf',
         'cwst', 'label'
     )
 ), (
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.wide_holds-workstation-settings.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.wide_holds-workstation-settings.sql
new file mode 100644
index 0000000..e693d68
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.wide_holds-workstation-settings.sql
@@ -0,0 +1,25 @@
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT into config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'eg.grid.circ.wide_holds.shelf', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.grid.circ.wide_holds.shelf',
+        'Grid Config: circ.wide_holds.shelf',
+        'cwst', 'label'
+    )
+), (
+    'eg.grid.cat.catalog.wide_holds', 'gui', 'object',
+    oils_i18n_gettext(
+        'eg.grid.cat.catalog.wide_holds',
+        'Grid Config: cat.catalog.wide_holds',
+        'cwst', 'label'
+    )
+);
+
+DELETE from config.workstation_setting_type
+WHERE name = 'eg.grid.cat.catalog.holds' OR name = 'eg.grid.circ.holds.shelf';
+
+COMMIT;

commit 5ef57330c9a49db91f5794e53874a47ce6432ff7
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Aug 30 16:48:23 2018 -0400

    LP#1712854: Provide context-relevant default sort orders
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

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 d778ec2..43af4d5 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
@@ -1728,7 +1728,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
                 pickup_lib       : egCore.org.descendants($scope.pickup_ou.id(), true)
         };
 
-        var order_by = [{ capture_time : null }];
+        var order_by = [{ request_time : null }];
         if (provider.sort && provider.sort.length) {
             order_by = [];
             angular.forEach(provider.sort, function (c) {
diff --git a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
index 27f76d1..5ce21aa 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
@@ -88,7 +88,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
                 current_shelf_lib : $scope.pickup_ou.id()
         };
 
-        var order_by = [{ capture_time : null }];
+        var order_by = [{ shelf_expire_time : null }];
         if (provider.sort && provider.sort.length) {
             order_by = [];
             angular.forEach(provider.sort, function (c) {

commit 0c4238dfce4cf2375ff02099b6dab55b692ec8a4
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Aug 30 16:37:21 2018 -0400

    LP#1712854: Include shelf-expired holds in the "clearable" set
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
index 46a9ed0..238746c 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
@@ -2173,7 +2173,7 @@ SELECT  h.id, h.request_time, h.capture_time, h.fulfillment_time, h.checkin_time
              ELSE 5
         END AS hold_status,
 
-        (h.cancel_time IS NOT NULL OR (h.current_shelf_lib IS NOT NULL AND h.current_shelf_lib <> h.pickup_lib)) AS clear_me,
+        (h.shelf_expire_time < NOW() OR h.cancel_time IS NOT NULL OR (h.current_shelf_lib IS NOT NULL AND h.current_shelf_lib <> h.pickup_lib)) AS clear_me,
 
         (h.usr <> h.requestor) AS is_staff_hold,
 

commit 5cbc171516a9ed68bbbfa1d6058c97255e907f25
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Aug 28 12:23:50 2018 -0400

    LP#1712854: Make use of local cache when moving to All Holds list
    
    Clearing holds takes care of local updates to the hold data, so there is no
    need to refetch the hold list when moving from Clearable to All.  Actions
    that change hold state otherwise do cause a refetch, however.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
index d41e963..27f76d1 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
@@ -41,7 +41,6 @@ angular.module('egHoldsApp',
 function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egHoldGridActions , egCirc , egGridDataProvider , egProgressDialog)  {
     $scope.detail_hold_id = $routeParams.hold_id;
 
-    var hold_ids = [];
     var holds = [];
     var clear_mode = false;
     $scope.gridControls = {};
@@ -53,7 +52,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
     function refresh_page() {
         hold_count = 0;
         holds = [];
-        hold_ids = [];
+        all_holds = [];
         provider.refresh();
     }
     // called after any egHoldGridActions action occurs
@@ -61,16 +60,23 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
 
     provider.get = function(offset, count) {
 
+        // if in clear mode...
+        if (clear_mode && holds.length) {
+            if (!all_holds.legnth) all_holds = holds;
+            holds = holds.filter(function(h) { return h.hold.clear_me });
+            hold_count = holds.length;
+            return provider.arrayNotifier(holds, offset, count);
+        } else if (all_holds.length) {
+            holds = all_holds;
+            hold_count = holds.length;
+            all_holds = [];
+        }
+
         // see if we have the requested range cached
         if (holds[offset]) {
             return provider.arrayNotifier(holds, offset, count);
         }
 
-        // if in clear mode...
-        if (clear_mode && holds.length) {
-            holds = holds.filter(function(h) { return h.hold.clear_me == 't' });
-        } 
-
         hold_count = 0;
         holds = [];
         var restrictions = {
@@ -128,7 +134,8 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
                         || hold_data.hold_status;
 
                     if (clear_mode) {
-                        if (hold_data.clear_me == 't') holds.push(new_item);
+                        if (hold_data.clear_me) holds.push(new_item);
+                        all_holds.push(new_item);
                     } else {
                         holds.push(new_item);
                     }
@@ -165,8 +172,8 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
     $scope.is_clearing = function() { return clearing };
     $scope.active_mode = function() {return !clear_mode}
     $scope.clear_mode = function() {return clear_mode}
-    $scope.show_clearable = function() { clear_mode = true; refresh_page() }
-    $scope.show_active = function() { clear_mode = false; refresh_page() }
+    $scope.show_clearable = function() { clear_mode = true; provider.refresh() }
+    $scope.show_active = function() { clear_mode = false; provider.refresh() }
     $scope.disable_clear = function() { return clearing || !clear_mode }
 
     // udpate the in-grid hold with the clear-shelf cached response info.
@@ -178,10 +185,16 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
                     return item.hold.id == info.hold_details.id
                 })[0];
 
+                var all_hold_item = all_holds.filter(function(item) {
+                    return item.hold.id == info.hold_details.id
+                })[0];
+
                 // there will be no grid item if the hold is off-page
                 if (grid_item) {
                     grid_item.post_clear = 
                         egCore.strings['CLEAR_SHELF_ACTION_' + info.action];
+                    all_hold_item.post_clear = 
+                        egCore.strings['CLEAR_SHELF_ACTION_' + info.action];
                 }
             }
         });

commit 3af031b4a1449d5e28bbaa15e5bdcf7a451c2c5f
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Aug 28 11:45:11 2018 -0400

    LP#1712854: Provide a "only last captured copy" option to filter holds shelf
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
index e0e96f9..46a9ed0 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
@@ -2131,7 +2131,23 @@ sub wide_hold_data {
     # force this to either 'true' or 'false'
     my $is_staff_request = delete($$restrictions{is_staff_request}) || 'false';
     $is_staff_request = 'false' if (!grep {$is_staff_request eq $_} qw/true false/);
-    $log->info('is_staff_request: '. $is_staff_request);
+
+    # option to filter for the latest captured hold for a given copy
+    my $last_captured_hold = delete($$restrictions{last_captured_hold}) || 'false';
+    $last_captured_hold = $last_captured_hold eq 'true' ? 1 : 0;
+
+    my $initial_condition = 'TRUE';
+    if ($last_captured_hold) {
+        $initial_condition = <<"        SQL";
+            (h.capture_time IS NULL OR (h.id = (
+                SELECT  id
+                  FROM  action.hold_request recheck
+                  WHERE recheck.current_copy = cp.id
+                  ORDER BY capture_time DESC
+                  LIMIT 1
+            )))
+        SQL
+    }
 
     my $select = <<"    SQL";
 WITH
@@ -2353,7 +2369,7 @@ SELECT  h.id, h.request_time, h.capture_time, h.fulfillment_time, h.checkin_time
                     LEFT JOIN config.circ_modifier cm ON (cp.circ_modifier = cm.code)
               WHERE m.hold = h.id
         ) AS hold_wait
-  WHERE TRUE
+  WHERE $initial_condition
     SQL
 
     my %field_map = (
diff --git a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
index 6456aad..d41e963 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
@@ -75,6 +75,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
         holds = [];
         var restrictions = {
                 is_staff_request  : 'true',
+                last_captured_hold: 'true',
                 capture_time      : { not : null },
                 cs_id             : 8, // on holds shelf
                 fulfillment_time  : null,

commit 4b4e283e30c332f6dca9dea997e58d0e935f0808
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Aug 28 11:09:06 2018 -0400

    LP#1712854: Fix typo in function names for cancel and uncancel of wide holds
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2
index 533b120..f401c1e 100644
--- a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2
@@ -53,9 +53,9 @@
   <eg-grid-action divider="true"></eg-grid-action>
   <eg-grid-action handler="grid_actions.retarget_wide"
     label="[% l('Find Another Target') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.cancel_hold_wide"
+  <eg-grid-action handler="grid_actions.cancel_wide_hold"
     label="[% l('Cancel Hold') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.uncancel_hold_wide"
+  <eg-grid-action handler="grid_actions.uncancel_wide_hold"
     label="[% l('Uncancel Hold') %]"></eg-grid-action>
 
   <eg-grid-field label="[% l('Hold ID') %]" path='hold.id'></eg-grid-field>

commit 14af809466039334348ba38b8417fb462d57f55b
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Jul 18 20:52:25 2018 -0400

    LP#1712854: Speed improvements for two hold interfaces
    
    The Hold Shelf and Record -> View Holds interfaces are painfully slow when
    faced with a large set of holds.  The main reason is the 2-stage process used
    to gather the data: 1) find hold IDs for the context 2) for each hold, fetch
    all the requisite data using a relatively slow API.
    
    Here we create a new API that provides a streaming response of pre-inflated
    hold data.  The full result set is loaded immediately, and once loaded, the
    grid makes use of the "clientsort" feature to provide fast sorting without
    the need to go back to the server when only changing the sort columns and
    direction, and when paging through results.  The Hold Shelf UI now has a
    progress indicator that appears whenever the hold list is retrieved from the
    server, and the progress indicator for the Record -> View Holds UI is enhanced
    to provide more granular updates.
    
    It is expected that other hold interfaces will be able to make use of this new
    API for performance and functionality improvements.
    
    NOTE: This includes an additional change required to protect localStorage from
    overlarge values when attempting to save the "last printed value" for future
    reprinting.  Previously, when attempting to print a 2000+ hold list,
    localStorage throws an error and the print fails.  Now the print will succeed,
    but the value is not stored for reprinting and a message is logged to the JS
    console.
    
    NOTE: The original development plan was base this a new API on a native
    Postgres materialized view, and provide a LISTEN/NOTIFY daemon to monitor for
    events that should trigger a refresh of that view.  As it happens, the use of
    other new Postgres features (primarily the LATERAL join type) allows us to
    avoid that maintenance and run-time complication.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    
    Conflicts:
    	Open-ILS/web/js/ui/default/staff/services/hatch.js
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
index b15de7c..457b8aa 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
@@ -3347,6 +3347,49 @@ sub all_rec_holds {
     return $resp;
 }
 
+__PACKAGE__->register_method(
+    method           => 'stream_wide_holds',
+    authoritative    => 1,
+    stream           => 1,
+    api_name         => 'open-ils.circ.hold.wide_hash.stream'
+);
+
+sub stream_wide_holds {
+    my($self, $client, $auth, $restrictions, $order_by, $limit, $offset) = @_;
+
+    my $e = new_editor(authtoken=>$auth);
+    $e->checkauth or return $e->event;
+    $e->allowed('VIEW_HOLD') or return $e->event;
+
+    my $st = OpenSRF::AppSession->create('open-ils.storage');
+    my $req = $st->request(
+        'open-ils.storage.action.live_holds.wide_hash',
+        $restrictions, $order_by, $limit, $offset
+    );
+
+    my $count = $req->recv;
+    if(!$count) {
+        return 0;
+    }
+
+    if(UNIVERSAL::isa($count,"Error")) {
+        throw $count ($count->stringify);
+    }
+
+    $count = $count->content;
+
+    # Force immediate send of count response
+    my $mbc = $client->max_bundle_count;
+    $client->max_bundle_count(1);
+    $client->respond($count);
+    $client->max_bundle_count($mbc);
+
+    while (my $hold = $req->recv) {
+        $client->respond($hold->content) if $hold->content;
+    }
+
+    $client->respond_complete;
+}
 
 
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
index e489e26..e0e96f9 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm
@@ -2115,4 +2115,314 @@ sub title_hold_capture {
 }
 
 
+sub wide_hold_data {
+    my $self = shift;
+    my $client = shift;
+    my $restrictions = shift; # hashref of field restrictions {f1=>undef,f2=>[1,2,3],f3=>'foo',f4=>{not=>undef}}
+    my $order_by = shift; # arrayref of hashrefs of ORDER BY clause, [{field =>{dir=>'desc',nulls=>'last'}}]
+    my $limit = shift;
+    my $offset = shift;
+
+    $order_by = [$order_by] if (ref($order_by) !~ /ARRAY/);
+    
+    $log->info('Received '. keys(%$restrictions) .' restrictions');
+    return 0 unless (ref $restrictions and keys %$restrictions);
+
+    # force this to either 'true' or 'false'
+    my $is_staff_request = delete($$restrictions{is_staff_request}) || 'false';
+    $is_staff_request = 'false' if (!grep {$is_staff_request eq $_} qw/true false/);
+    $log->info('is_staff_request: '. $is_staff_request);
+
+    my $select = <<"    SQL";
+WITH
+    t_field AS (SELECT field FROM config.display_field_map WHERE name = 'title'),
+    a_field AS (SELECT field FROM config.display_field_map WHERE name = 'author')
+SELECT  h.id, h.request_time, h.capture_time, h.fulfillment_time, h.checkin_time,
+        h.return_time, h.prev_check_time, h.expire_time, h.cancel_time, h.cancel_cause,
+        h.cancel_note, h.target, h.current_copy, h.fulfillment_staff, h.fulfillment_lib,
+        h.request_lib, h.requestor, h.usr, h.selection_ou, h.selection_depth, h.pickup_lib,
+        h.hold_type, h.holdable_formats, h.phone_notify, h.email_notify, h.sms_notify,
+        h.sms_carrier, h.frozen, h.thaw_date, h.shelf_time, h.cut_in_line, h.mint_condition,
+        h.shelf_expire_time, h.current_shelf_lib, h.behind_desk,
+
+        CASE WHEN h.cancel_time IS NOT NULL THEN 6
+             WHEN h.frozen AND h.capture_time IS NULL THEN 7
+             WHEN h.current_shelf_lib IS NOT NULL AND h.current_shelf_lib <> h.pickup_lib THEN 8
+             WHEN h.fulfillment_time IS NOT NULL THEN 9
+             WHEN h.current_copy IS NULL THEN 1
+             WHEN h.capture_time IS NULL THEN 2
+             WHEN cp.status = 6 THEN 3
+             WHEN EXTRACT(EPOCH FROM COALESCE(NULLIF(BTRIM(hold_wait_time.value,'"'),''),'0 seconds')::INTERVAL) = 0 THEN 4
+             WHEN NOW() + COALESCE(NULLIF(BTRIM(hold_wait_time.value,'"'),''),'0 seconds')::INTERVAL > NOW() THEN 5
+             ELSE 5
+        END AS hold_status,
+
+        (h.cancel_time IS NOT NULL OR (h.current_shelf_lib IS NOT NULL AND h.current_shelf_lib <> h.pickup_lib)) AS clear_me,
+
+        (h.usr <> h.requestor) AS is_staff_hold,
+
+        cc.id AS cc_id, cc.label AS cc_label,
+
+        pl.id AS pl_id, pl.parent_ou AS pl_parent_ou, pl.ou_type AS pl_ou_type,
+        pl.ill_address AS pl_ill_address, pl.holds_address AS pl_holds_address,
+        pl.mailing_address AS pl_mailing_address, pl.billing_address AS pl_billing_address,
+        pl.shortname AS pl_shortname, pl.name AS pl_name, pl.email AS pl_email,
+        pl.phone AS pl_phone, pl.opac_visible AS pl_opac_visible, pl.fiscal_calendar AS pl_fiscal_calendar,
+
+        tr.id AS tr_id, tr.source_send_time AS tr_source_send_time, tr.dest_recv_time AS tr_dest_recv_time,
+        tr.target_copy AS tr_target_copy, tr.source AS tr_source, tr.dest AS tr_dest, tr.prev_hop AS tr_prev_hop,
+        tr.copy_status AS tr_copy_status, tr.persistant_transfer AS tr_persistant_transfer,
+        tr.prev_dest AS tr_prev_dest, tr.hold AS tr_hold, tr.cancel_time AS tr_cancel_time,
+
+        notes.count AS note_count,
+
+        u.id AS usr_id, u.card AS usr_card, u.profile AS usr_profile, u.usrname AS usr_usrname,
+        u.email AS usr_email, u.standing AS usr_standing, u.ident_type AS usr_ident_type,
+        u.ident_value AS usr_ident_value, u.ident_type2 AS usr_ident_type2,
+        u.ident_value2 AS usr_ident_value2, u.net_access_level AS usr_net_access_level,
+        u.photo_url AS usr_photo_url, u.prefix AS usr_prefix, u.first_given_name AS usr_first_given_name,
+        u.second_given_name AS usr_second_given_name, u.family_name AS usr_family_name,
+        u.suffix AS usr_suffix, u.alias AS usr_alias, u.day_phone AS usr_day_phone,
+        u.evening_phone AS usr_evening_phone, u.other_phone AS usr_other_phone,
+        u.mailing_address AS usr_mailing_address, u.billing_address AS usr_billing_address,
+        u.home_ou AS usr_home_ou, u.dob AS usr_dob, u.active AS usr_active,
+        u.master_account AS usr_master_account, u.super_user AS usr_super_user,
+        u.barred AS usr_barred, u.deleted AS usr_deleted, u.juvenile AS usr_juvenile,
+        u.usrgroup AS usr_usrgroup, u.claims_returned_count AS usr_claims_returned_count,
+        u.credit_forward_balance AS usr_credit_forward_balance, u.last_xact_id AS usr_last_xact_id,
+        u.alert_message AS usr_alert_message, u.create_date AS usr_create_date,
+        u.expire_date AS usr_expire_date, u.claims_never_checked_out_count AS usr_claims_never_checked_out_count,
+        u.last_update_time AS usr_last_update_time,
+
+        CASE WHEN u.alias IS NOT NULL THEN
+            u.alias
+        ELSE
+            u.first_given_name
+        END AS usr_alias_or_first_given_name,
+
+        CASE WHEN u.alias IS NOT NULL THEN
+            u.alias
+        ELSE
+            REGEXP_REPLACE(ARRAY_TO_STRING(ARRAY[
+                COALESCE(u.family_name, ''),
+                COALESCE(u.suffix, ''),
+                ', ',
+                COALESCE(u.prefix, ''),
+                COALESCE(u.first_given_name, ''),
+                COALESCE(u.second_given_name, '')
+            ], ' '), E'\\s+,', ',')
+        END AS usr_alias_or_display_name,
+
+        REGEXP_REPLACE(ARRAY_TO_STRING(ARRAY[
+            COALESCE(u.family_name, ''),
+            COALESCE(u.suffix, ''),
+            ', ',
+            COALESCE(u.prefix, ''),
+            COALESCE(u.first_given_name, ''),
+            COALESCE(u.second_given_name, '')
+        ], ' '), E'\\s+,', ',') AS usr_display_name,
+
+        uc.id AS ucard_id, uc.barcode AS ucard_barcode, uc.usr AS ucard_usr, uc.active AS ucard_active,
+
+        ru.id AS rusr_id, ru.card AS rusr_card, ru.profile AS rusr_profile, ru.usrname AS rusr_usrname,
+        ru.email AS rusr_email, ru.standing AS rusr_standing, ru.ident_type AS rusr_ident_type,
+        ru.ident_value AS rusr_ident_value, ru.ident_type2 AS rusr_ident_type2,
+        ru.ident_value2 AS rusr_ident_value2, ru.net_access_level AS rusr_net_access_level,
+        ru.photo_url AS rusr_photo_url, ru.prefix AS rusr_prefix, ru.first_given_name AS rusr_first_given_name,
+        ru.second_given_name AS rusr_second_given_name, ru.family_name AS rusr_family_name,
+        ru.suffix AS rusr_suffix, ru.alias AS rusr_alias, ru.day_phone AS rusr_day_phone,
+        ru.evening_phone AS rusr_evening_phone, ru.other_phone AS rusr_other_phone,
+        ru.mailing_address AS rusr_mailing_address, ru.billing_address AS rusr_billing_address,
+        ru.home_ou AS rusr_home_ou, ru.dob AS rusr_dob, ru.active AS rusr_active,
+        ru.master_account AS rusr_master_account, ru.super_user AS rusr_super_user,
+        ru.barred AS rusr_barred, ru.deleted AS rusr_deleted, ru.juvenile AS rusr_juvenile,
+        ru.usrgroup AS rusr_usrgroup, ru.claims_returned_count AS rusr_claims_returned_count,
+        ru.credit_forward_balance AS rusr_credit_forward_balance, ru.last_xact_id AS rusr_last_xact_id,
+        ru.alert_message AS rusr_alert_message, ru.create_date AS rusr_create_date,
+        ru.expire_date AS rusr_expire_date, ru.claims_never_checked_out_count AS rusr_claims_never_checked_out_count,
+        ru.last_update_time AS rusr_last_update_time,
+
+        ruc.id AS rucard_id, ruc.barcode AS rucard_barcode, ruc.usr AS rucard_usr, ruc.active AS rucard_active,
+
+        cp.id AS cp_id, cp.circ_lib AS cp_circ_lib, cp.creator AS cp_creator, cp.call_number AS cp_call_number,
+        cp.editor AS cp_editor, cp.create_date AS cp_create_date, cp.edit_date AS cp_edit_date,
+        cp.copy_number AS cp_copy_number, cp.status AS cp_status, cp.location AS cp_location,
+        cp.loan_duration AS cp_loan_duration, cp.fine_level AS cp_fine_level, cp.age_protect AS cp_age_protect,
+        cp.circulate AS cp_circulate, cp.deposit AS cp_deposit, cp.ref AS cp_ref, cp.holdable AS cp_holdable,
+        cp.deposit_amount AS cp_deposit_amount, cp.price AS cp_price, cp.barcode AS cp_barcode,
+        cp.circ_modifier AS cp_circ_modifier, cp.circ_as_type AS cp_circ_as_type, cp.dummy_title AS cp_dummy_title,
+        cp.dummy_author AS cp_dummy_author, cp.alert_message AS cp_alert_message, cp.opac_visible AS cp_opac_visible,
+        cp.deleted AS cp_deleted, cp.floating AS cp_floating, cp.dummy_isbn AS cp_dummy_isbn,
+        cp.status_changed_time AS cp_status_change_time, cp.active_date AS cp_active_date,
+        cp.mint_condition AS cp_mint_condition, cp.cost AS cp_cost,
+
+        cs.id AS cs_id, cs.name AS cs_name, cs.holdable AS cs_holdable, cs.opac_visible AS cs_opac_visible,
+        cs.copy_active AS cs_copy_active, cs.restrict_copy_delete AS cs_restrict_copy_delete,
+        cs.is_available AS cs_is_available,
+
+        siss.label AS issuance_label,
+
+        cn.id AS cn_id, cn.creator AS cn_creator, cn.create_date AS cn_create_date, cn.editor AS cn_editor,
+        cn.edit_date AS cn_edit_date, cn.record AS cn_record, cn.owning_lib AS cn_owning_lib, cn.label AS cn_label,
+        cn.deleted AS cn_deleted, cn.prefix AS cn_prefix, cn.suffix AS cn_suffix, cn.label_class AS cn_label_class,
+        cn.label_sortkey AS cn_label_sortkey,
+
+        p.id AS p_id, p.record AS p_record, p.label AS p_label, p.label_sortkey AS p_label_sortkey, p.deleted AS p_deleted,
+
+        acnp.label AS ancp_label, acns.label AS ancs_label,
+        TRIM(acnp.label || ' ' || cn.label || ' ' || acns.label) AS cn_full_label,
+
+        r.bib_record AS record_id,
+
+        t.value AS title,
+        a.value AS author,
+
+        acpl.id AS acpl_id, acpl.name AS acpl_name, acpl.owning_lib AS acpl_owning_lib, acpl.holdable AS acpl_holdable,
+        acpl.hold_verify AS acpl_hold_verify, acpl.opac_visible AS acpl_opac_visible, acpl.circulate AS acpl_circulate,
+        acpl.label_prefix AS acpl_label_prefix, acpl.label_suffix AS acpl_label_suffix,
+        acpl.checkin_alert AS acpl_checkin_alert, acpl.deleted AS acpl_deleted, acpl.url AS acpl_url,
+
+        COALESCE(acplo.position, acpl_ordered.fallback_position) AS copy_location_order_position,
+
+        ROW_NUMBER() OVER (
+            PARTITION BY r.bib_record
+            ORDER BY h.cut_in_line DESC NULLS LAST, h.request_time DESC
+        ) AS relative_queue_position,
+
+        EXTRACT(EPOCH FROM COALESCE(
+            NULLIF(BTRIM(default_estimated_wait_interval.value,'"'),''),
+            '0 seconds'
+        )::INTERVAL) AS default_estimated_wait,
+
+        EXTRACT(EPOCH FROM COALESCE(
+            NULLIF(BTRIM(min_estimated_wait_interval.value,'"'),''),
+            '0 seconds'
+        )::INTERVAL) AS min_estimated_wait,
+
+        COALESCE(hold_wait.potenials,0) AS potentials,
+        COALESCE(hold_wait.other_holds,0) AS other_holds,
+        COALESCE(hold_wait.total_wait_time,0) AS total_wait_time,
+
+        n.count AS notification_count,
+        n.max AS last_notification_time
+
+  FROM  action.hold_request h
+        JOIN reporter.hold_request_record r ON (r.id = h.id)
+        JOIN actor.usr u ON (u.id = h.usr)
+        JOIN actor.card uc ON (uc.id = u.card)
+        JOIN actor.usr ru ON (ru.id = h.requestor)
+        JOIN actor.card ruc ON (ruc.id = ru.card)
+        JOIN actor.org_unit pl ON (h.pickup_lib = pl.id)
+        JOIN t_field ON TRUE
+        JOIN a_field ON TRUE
+        LEFT JOIN action.hold_request_cancel_cause cc ON (h.cancel_cause = cc.id)
+        LEFT JOIN biblio.monograph_part p ON (h.hold_type = 'P' AND p.id = h.target)
+        LEFT JOIN serial.issuance siss ON (h.hold_type = 'I' AND siss.id = h.target)
+        LEFT JOIN asset.copy cp ON (h.current_copy = cp.id OR (h.hold_type IN ('C','F','R') AND cp.id = h.target))
+        LEFT JOIN config.copy_status cs ON (cp.status = cs.id)
+        LEFT JOIN asset.copy_location acpl ON (cp.location = acpl.id)
+        LEFT JOIN asset.copy_location_order acplo ON (cp.location = acplo.location AND cp.circ_lib = acplo.org)
+        LEFT JOIN (
+            SELECT *, (ROW_NUMBER() OVER (ORDER BY name) + 1000000) AS fallback_position
+            FROM asset.copy_location
+        ) acpl_ordered ON (acpl_ordered.id = cp.location)
+        LEFT JOIN asset.call_number cn ON (cn.id = cp.call_number OR (h.hold_type = 'V' AND cn.id = h.target))
+        LEFT JOIN asset.call_number_prefix acnp ON (cn.prefix = acnp.id)
+        LEFT JOIN asset.call_number_suffix acns ON (cn.suffix = acns.id)
+        LEFT JOIN LATERAL (SELECT * FROM action.hold_transit_copy WHERE h.id = hold ORDER BY id DESC LIMIT 1) tr ON TRUE
+        LEFT JOIN LATERAL (SELECT COUNT(*) FROM action.hold_request_note WHERE h.id = hold AND (pub = TRUE OR staff = $is_staff_request)) notes ON TRUE
+        LEFT JOIN LATERAL (SELECT COUNT(*), MAX(notify_time) FROM action.hold_notification WHERE h.id = hold) n ON TRUE
+        LEFT JOIN LATERAL (SELECT FIRST(value) AS value FROM metabib.display_entry WHERE source = r.bib_record AND field = t_field.field) t ON TRUE
+        LEFT JOIN LATERAL (SELECT FIRST(value) AS value FROM metabib.display_entry WHERE source = r.bib_record AND field = a_field.field) a ON TRUE
+        LEFT JOIN LATERAL actor.org_unit_ancestor_setting('circ.holds.default_estimated_wait_interval',u.home_ou) AS default_estimated_wait_interval ON TRUE
+        LEFT JOIN LATERAL actor.org_unit_ancestor_setting('circ.holds.min_estimated_wait_interval',u.home_ou) AS min_estimated_wait_interval ON TRUE
+        LEFT JOIN LATERAL actor.org_unit_ancestor_setting('circ.hold_shelf_status_delay',h.pickup_lib) AS hold_wait_time ON TRUE,
+        LATERAL (
+            SELECT  COUNT(*) AS potenials,
+                    COUNT(DISTINCT hold) AS other_holds,
+                    SUM(
+                        EXTRACT(EPOCH FROM
+                            COALESCE(
+                                cm.avg_wait_time,
+                                COALESCE(NULLIF(BTRIM(default_estimated_wait_interval.value,'"'),''),'0 seconds')::INTERVAL
+                            )
+                        )
+                    ) AS total_wait_time
+              FROM  action.hold_copy_map m
+                    JOIN asset.copy cp ON (cp.id = m.target_copy)
+                    LEFT JOIN config.circ_modifier cm ON (cp.circ_modifier = cm.code)
+              WHERE m.hold = h.id
+        ) AS hold_wait
+  WHERE TRUE
+    SQL
+
+    my %field_map = (
+        record_id => 'r.bib_record',
+        usr_id => 'u.id',
+        cs_id => 'cs.id',
+        cp_id => 'cp.id',
+        cancel_time => 'h.cancel_time',
+        tr_cancel_time => 'tr.cancel_time',
+    );
+
+    my $restricted = 0;
+    for my $r (keys %$restrictions) {
+        my $real = $field_map{$r} || $r;
+        next if ($r =~ /[^a-z_.]/); # skip obvious bad inputs
+
+        my $not = '';
+        if (ref($$restrictions{$r}) and ref($$restrictions{$r}) =~ /HASH/) {
+            $not = 'NOT';
+            $$restrictions{$r} = $$restrictions{$r}{not};
+        }
+
+        if (!defined($$restrictions{$r})) { 
+            $select .= " AND $real IS $not NULL ";
+        } elsif (ref($$restrictions{$r})) { 
+            $select .= " AND $real $not IN (\$_$$\$" . join("\$_$$\$,\$_$$\$", @{$$restrictions{$r}}) . "\$_$$\$)";
+        } else {
+            $not = '!' if $not;
+            $select .= " AND $real $not= \$_$$\$$$restrictions{$r}\$_$$\$";
+        }
+
+        $restricted++;
+    }
+
+    return 0 unless $restricted;
+
+    my @ob;
+    for my $o (@$order_by) {
+        next unless $o;
+        my ($r) = keys %$o;
+        next if ($r =~ /[^a-z_.]/); # skip obvious bad inputs
+        my $real = $field_map{$r} || $r;
+        push(@ob, $real);
+        $ob[-1] .= ' DESC' if ($$o{$r}->{dir} and $$o{$r}->{dir} =~ /^d/i);
+        $ob[-1] .= ' NULLS LAST' if ($$o{$r}->{nulls} and $$o{$r}->{nulls} =~ /^l/i);
+        $ob[-1] .= ' NULLS FIRST' if ($$o{$r}->{nulls} and $$o{$r}->{nulls} =~ /^f/i);
+    }
+
+    $select .= ' ORDER BY ' . join(', ', @ob) if (@ob);
+    $select .= ' LIMIT ' . $limit if ($limit and $limit =~ /^\d+$/);
+    $select .= ' OFFSET ' . $offset if ($offset and $offset =~ /^\d+$/);
+
+    my $sth = action::hold_request->db_Main->prepare($select);
+    $sth->execute();
+
+    my @list = $sth->fetchall_hash;
+    $client->respond(scalar(@list)); # send the row count first, for progress tracking
+    $client->respond( $_ ) for (@list);
+
+    $client->respond_complete;
+}
+__PACKAGE__->register_method(
+    api_name        => 'open-ils.storage.action.live_holds.wide_hash',
+    api_level       => 1,
+    stream          => 1,
+    max_bundle_count=> 1,
+    method          => 'wide_hold_data',
+);
+
+
 1;
+
diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
index b73c858..17fa5f0 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
@@ -12,10 +12,10 @@
 
   <eg-grid
     id-field="id"
-    features="-sort,-multisort"
+    features="clientsort,"
     items-provider="hold_grid_data_provider"
     grid-controls="hold_grid_controls"
-    persist-key="cat.catalog.holds"
+    persist-key="cat.catalog.wide_holds"
     dateformat="{{$root.egDateAndTimeFormat}}">
 
     <eg-grid-menu-item handler="detail_view" 
@@ -25,81 +25,190 @@
 	<eg-grid-menu-item handler="transfer_holds_to_marked"
       label="[% l('Transfer All Title Holds') %]"></eg-grid-menu-item> -->
 
-    <eg-grid-action handler="grid_actions.show_recent_circs" group="[% l('Copy') %]"
+    <eg-grid-action handler="grid_actions.show_recent_circs_wide" group="[% l('Copy') %]"
       label="[% l('Show Last Few Circulations') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.show_patrons" group="[% l('Patron') %]"
+    <eg-grid-action handler="grid_actions.show_patrons_wide" group="[% l('Patron') %]"
       label="[% l('Retrieve Patron') %]"></eg-grid-action>
-    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.set_copy_quality"
+    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.set_copy_quality_wide"
       label="[% l('Set Desired Copy Quality') %]"></eg-grid-action>
-    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.edit_pickup_lib"
+    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.edit_pickup_lib_wide"
       label="[% l('Edit Pickup Library') %]"></eg-grid-action>
-    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.edit_notify_prefs"
+    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.edit_notify_prefs_wide"
       label="[% l('Edit Notification Settings') %]"></eg-grid-action>
-    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.edit_dates"
+    <eg-grid-action group="[% l('Hold') %]" handler="grid_actions.edit_dates_wide"
       label="[% l('Edit Hold Dates') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.activate" group="[% l('Hold') %]"
+    <eg-grid-action handler="grid_actions.activate_wide" group="[% l('Hold') %]"
       label="[% l('Activate') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.suspend" group="[% l('Hold') %]"
+    <eg-grid-action handler="grid_actions.suspend_wide" group="[% l('Hold') %]"
       label="[% l('Suspend') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.set_top_of_queue" group="[% l('Hold') %]"
+    <eg-grid-action handler="grid_actions.set_top_of_queue_wide" group="[% l('Hold') %]"
       label="[% l('Set Top of Queue') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.clear_top_of_queue" group="[% l('Hold') %]"
+    <eg-grid-action handler="grid_actions.clear_top_of_queue_wide" group="[% l('Hold') %]"
       label="[% l('Un-Set Top of Queue') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.transfer_to_marked_title" group="[% l('Hold') %]"
+    <eg-grid-action handler="grid_actions.transfer_to_marked_title_wide" group="[% l('Hold') %]"
       label="[% l('Transfer To Marked Title') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.mark_damaged" group="[% l('Copy') %]"
+    <eg-grid-action handler="grid_actions.mark_damaged_wide" group="[% l('Copy') %]"
       label="[% l('Mark Item Damaged') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.mark_missing" group="[% l('Copy') %]"
+    <eg-grid-action handler="grid_actions.mark_missing_wide" group="[% l('Copy') %]"
       label="[% l('Mark Item Missing') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.retarget" group="[% l('Hold') %]"
+    <eg-grid-action handler="grid_actions.retarget_wide" group="[% l('Hold') %]"
       label="[% l('Find Another Target') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.cancel_hold" group="[% l('Hold') %]"
+    <eg-grid-action handler="grid_actions.cancel_hold_wide" group="[% l('Hold') %]"
       label="[% l('Cancel Hold') %]"></eg-grid-action>
 
     <eg-grid-field label="[% l('Hold ID') %]" path='hold.id'></eg-grid-field>
     <eg-grid-field label="[% l('Current Copy') %]" 
-      path='hold.current_copy.barcode'>
-      <a href="./cat/item/{{item.hold.current_copy().id()}}/summary" target="_self">
-        {{item.hold.current_copy().barcode()}}
+      path='hold.cp_barcode'>
+      <a href="./cat/item/{{item.hold.cp_id}}/summary" target="_self">
+        {{item.hold.cp_barcode}}
       </a>
     </eg-grid-field>
 
-    <eg-grid-field label="[% l('Patron Barcode') %]">{{item.patron_barcode}}</eg-grid-field>
-    <eg-grid-field label="[% l('Patron alias') %]">{{item.patron_alias}}</eg-grid-field>
+    <eg-grid-field label="[% l('Patron Barcode') %]" path='hold.ucard_barcode' hidden></eg-grid-field>
+    <eg-grid-field label="[% l('Patron alias') %]">{{item.usr_alias}}</eg-grid-field>
     <eg-grid-field label="[% l('Request Date') %]" path='hold.request_time' datatype="timestamp"></eg-grid-field>
     <eg-grid-field label="[% l('Capture Date') %]" path='hold.capture_time' datatype="timestamp"></eg-grid-field>
     <eg-grid-field label="[% l('Available Date') %]" path='hold.shelf_time' datatype="timestamp"></eg-grid-field>
     <eg-grid-field label="[% l('Hold Type') %]" path='hold.hold_type'></eg-grid-field>
-    <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pickup_lib.shortname'></eg-grid-field>
+    <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pl_shortname'></eg-grid-field>
 
-    <eg-grid-field label="[% l('Title') %]" path='mvr.title'>
-      <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.mvr.doc_id()}}">
-        {{item.mvr.title()}}
-      </a>
-    </eg-grid-field>
+  <eg-grid-field label="[% l('Title') %]" path='hold.title'>
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.hold.record_id}}">
+      {{item.hold.title}}
+    </a>
+  </eg-grid-field>
 
-    <eg-grid-field label="[% l('Author') %]" path='mvr.author'></eg-grid-field>
-    <eg-grid-field label="[% l('Potential Copies') %]" path='potential_copies'></eg-grid-field>
-    <eg-grid-field label="[% l('Status') %]" path='status_string'></eg-grid-field>
+  <eg-grid-field label="[% l('Author') %]" path='hold.author'></eg-grid-field>
+  <eg-grid-field label="[% l('Potential Copies') %]" path='hold.potentials'></eg-grid-field>
+  <eg-grid-field label="[% l('Status') %]" path='status_string'></eg-grid-field>
 
-    <eg-grid-field label="[% l('Queue Position') %]" path='queue_position' hidden></eg-grid-field>
-    <eg-grid-field path='hold.*' parent-idl-class="ahr" hidden></eg-grid-field>
-    <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Queue Position') %]" path='hold.relative_queue_position' hidden></eg-grid-field>
 
-    <eg-grid-field path='hold.usr.id' parent-idl-class="ahr" label="[% l('User ID') %]" hidden></eg-grid-field>
-    <eg-grid-field path='hold.usr.usrname' parent-idl-class="ahr" label="[% l('Username') %]" hidden></eg-grid-field>
-    <eg-grid-field path='hold.usr.first_given_name' parent-idl-class="ahr" label="[% l('First Name') %]" hidden></eg-grid-field>
-    <eg-grid-field path='hold.usr.family_name' parent-idl-class="ahr" label="[% l('Last Name') %]" hidden></eg-grid-field>
-    <eg-grid-field path='hold.requestor.id' parent-idl-class="ahr" label="[% l('Requestor ID') %]" hidden></eg-grid-field>
-    <eg-grid-field path='hold.requestor.usrname' parent-idl-class="ahr" label="[% l('Requestor Username') %]" hidden></eg-grid-field>
+    <eg-grid-field path='hold.usr_id' label="[% l('User ID') %]" hidden></eg-grid-field>
+    <eg-grid-field path='hold.usr_usrname' label="[% l('Username') %]" hidden></eg-grid-field>
+    <eg-grid-field path='hold.usr_first_given_name' label="[% l('First Name') %]" hidden></eg-grid-field>
+    <eg-grid-field path='hold.usr_family_name' label="[% l('Last Name') %]" hidden></eg-grid-field>
+    <eg-grid-field path='hold.rusr_id' label="[% l('Requestor ID') %]" hidden></eg-grid-field>
+    <eg-grid-field path='hold.rusr_usrname' label="[% l('Requestor Username') %]" hidden></eg-grid-field>
 
-   <eg-grid-field label="[% l('Copy Status') %]" path="hold.current_copy.status.name" hidden>
-    </eg-grid-field>
+   <eg-grid-field label="[% l('Copy Status') %]" path="hold.cs_name" hidden></eg-grid-field>
 
-    <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
-    <eg-grid-field path='volume.prefix.label' label="[% l('CN Prefix') %]" parent-idl-class="acn" hidden></eg-grid-field>
-    <eg-grid-field path='volume.suffix.label' label="[% l('CN Suffix') %]" parent-idl-class="acn" hidden></eg-grid-field>
+    <eg-grid-field path='hold.acnp_label' label="[% l('CN Prefix') %]" hidden></eg-grid-field>
+    <eg-grid-field path='hold.acns_label' label="[% l('CN Suffix') %]" hidden></eg-grid-field>
     <eg-grid-field path='mvr.*' parent-idl-class="mvr" hidden></eg-grid-field>
+
+  <eg-grid-field label="[% l('Copy Status') %]" path="hold.cs_name" hidden></eg-grid-field>
+
+  <eg-grid-field label="[% l('Fulfillment Date/Time') %]" path='hold.fulfillment_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Checkin Time') %]" path='hold.checkin_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Return Time') %]" path='hold.return_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Last Targeting Date/Time') %]" path='hold.prev_check_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Expire Time') %]" path='hold.expire_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Hold Cancel Date/Time') %]" path='hold.cancel_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cancelation note') %]" path='hold.cancel_note' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Hold Target') %]" path='hold.target' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Current Copy') %]" path='hold.current_copy' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Fulfilling Staff') %]" path='hold.fulfillment_staff' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Fulfilling Library') %]" path='hold.fulfillment_lib' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Requesting Library') %]" path='hold.request_lib' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Requesting User') %]" path='hold.requestor' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('User') %]" path='hold.usr' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Selection Library') %]" path='hold.selection_ou' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Item Selection Depth') %]" path='hold.selection_depth' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Holdable Formats (for M-type hold)') %]" path='hold.holdable_formats' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notifications Phone Number') %]" path='hold.phone_notify' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notifications SMS Number') %]" path='hold.sms_notify' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notify by Email?') %]" path='hold.email_notify' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('SMS Carrier') %]" path='hold.sms_carrier' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Currently Frozen') %]" path='hold.frozen' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Activation Date') %]" path='hold.thaw_date' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Top of Queue') %]" path='hold.cut_in_line' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Mint Condition') %]" path='hold.mint_condition' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Shelf Expire Time') %]" path='hold.shelf_expire_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Current Shelf Library') %]" path='hold.current_shelf_lib' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Behind Desk') %]" path='hold.behind_desk' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status') %]" path='hold.hold_status' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Clearable') %]" path='hold.clear_me' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Staff-placed Hold') %]" path='hold.is_staff_hold' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cancelation Cause ID') %]" path='hold.cc_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cancelation Cause') %]" path='hold.cc_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pl_shortname'></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Name') %]" path='hold.pl_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Email') %]" path='hold.pl_email' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Phone') %]" path='hold.pl_phone' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Opac Visible') %]" path='hold.pl_opac_visible' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit ID') %]" path='hold.tr_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Send Time') %]" path='hold.tr_source_send_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Receive Time') %]" path='hold.tr_dest_recv_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Copy') %]" path='hold.tr_target_copy' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Source') %]" path='hold.tr_source' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Destination') %]" path='hold.tr_dest' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Copy Status') %]" path='hold.tr_copy_status' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Hold') %]" path='hold.tr_hold' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Cancel Time') %]" path='hold.tr_cancel_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Hold Note Count') %]" path='hold.note_count' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('User Display Name') %]" path='hold.usr_display_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Requestor Username') %]" path='hold.rusr_usrname' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy ID') %]" path='hold.cp_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Number on Volume') %]" path='hold.cp_copy_number' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Fine Level') %]" path='hold.cp_fine_level' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Can Circulate') %]" path='hold.cp_circulate' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Deposit Amount') %]" path='hold.cp_deposit_amount' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Deposit Required') %]" path='hold.cp_deposit' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Reference') %]" path='hold.cp_ref' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Holdable') %]" path='hold.cp_holdable' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Price') %]" path='hold.cp_price' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Barcode') %]" path='hold.cp_barcode' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Circulation Modifier') %]" path='hold.cp_circ_modifier' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Circulate as MARC Type') %]" path='hold.cp_circ_as_type' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Precat Dummy Title') %]" path='hold.cp_dummy_title' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Precat Dummy Author') %]" path='hold.cp_dummy_author' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Alert Message (deprecated)') %]" path='hold.cp_alert_message' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy OPAC Visible') %]" path='hold.cp_opac_visible' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Deleted') %]" path='hold.cp_deleted' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Floating Group') %]" path='hold.cp_floating' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Precat Dummy ISBN') %]" path='hold.cp_dummy_isbn' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Status Change Time') %]" path='hold.cp_status_change_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Active Date') %]" path='hold.cp_active_date' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Is Mint Condition') %]" path='hold.cp_mint_condition' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cost') %]" path='hold.cp_cost' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Status') %]" path='hold.cs_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Holdable') %]" path='hold.cs_holdable' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is OPAC Visible') %]" path='hold.cs_opac_visible' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Copy-Active') %]" path='hold.cs_copy_active' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Deleted') %]" path='hold.cs_restrict_copy_delete' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Available') %]" path='hold.cs_is_available' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Issuance Label') %]" path='hold.issuance_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Call Number ID') %]" path='hold.cn_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Label') %]" path='hold.cn_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Label Class') %]" path='hold.cn_label_class' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Sort Key') %]" path='hold.cn_label_sortkey' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part ID') %]" path='hold.p_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part Label') %]" path='hold.p_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part Sort Key') %]" path='hold.p_label_sortkey' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part Is Deleted') %]" path='hold.p_deleted' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Full Label') %]" path='hold.cn_full_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Record ID') %]" path='hold.record_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location ID') %]" path='hold.acpl_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location') %]" path='hold.acpl_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Holdable') %]" path='hold.acpl_holdable' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Hold-Verify') %]" path='hold.acpl_hold_verify' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location OPAC Visible') %]" path='hold.acpl_opac_visible' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Can Circulate') %]" path='hold.acpl_circulate' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Prefix') %]" path='hold.acpl_label_prefix' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Suffix') %]" path='hold.acpl_label_suffix' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Checkin Alert') %]" path='hold.acpl_checkin_alert' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Is Deleted') %]" path='hold.acpl_deleted' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location URL') %]" path='hold.acpl_url' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Order') %]" path='hold.copy_location_order_position' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Default Estimated Wait Time') %]" path='hold.default_estimated_wait' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Minimum Estimated Wait Time') %]" path='hold.min_estimated_wait' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Peer Hold Count') %]" path='hold.other_holds' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Total Wait Time') %]" path='hold.total_wait_time' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notify Count') %]" path='hold.notification_count' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Last Notify Time') %]" path='hold.last_notification_time' datatype="timestamp" hidden></eg-grid-field>
+
+
   </eg-grid>
 
   <div class="flex-row pad-vert">
diff --git a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2
index 649e346..533b120 100644
--- a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2
@@ -1,13 +1,9 @@
-<div ng-if="print_list_progress !== null" class="strong-text-2">
-  [% l('Loading... [_1]', '{{print_list_progress}}') %]
-</div>
-
 <eg-grid
   id-field="id"
-  features="-sort,-multisort"
+  features="clientsort"
   items-provider="gridDataProvider"
   grid-controls="gridControls"
-  persist-key="circ.holds.shelf"
+  persist-key="circ.wide_holds.shelf"
   dateformat="{{$root.egDateAndTimeFormat}}">
 
   <eg-grid-menu-item handler="detail_view" 
@@ -25,48 +21,48 @@
     label="[% l('Clear These Holds') %]"></eg-grid-menu-item>
 
   <eg-grid-menu-item handler="print_shelf_list"
-    label="[% l('Print Full List') %]"></eg-grid-menu-item>
+    label="[% l('Print Full List') %]"></eg-grid-menu-item>  
 
-  <eg-grid-action handler="grid_actions.show_recent_circs"
+  <eg-grid-action handler="grid_actions.show_recent_circs_wide"
     label="[% l('Show Last Few Circulations') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.show_patrons"
+  <eg-grid-action handler="grid_actions.show_patrons_wide"
     label="[% l('Retrieve Patron') %]"></eg-grid-action>
   <eg-grid-action divider="true"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.set_copy_quality"
+  <eg-grid-action handler="grid_actions.set_copy_quality_wide"
     label="[% l('Set Desired Copy Quality') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.edit_pickup_lib"
+  <eg-grid-action handler="grid_actions.edit_pickup_lib_wide"
     label="[% l('Edit Pickup Library') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.edit_notify_prefs"
+  <eg-grid-action handler="grid_actions.edit_notify_prefs_wide"
     label="[% l('Edit Notification Settings') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.edit_dates"
+  <eg-grid-action handler="grid_actions.edit_dates_wide"
     label="[% l('Edit Hold Dates') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.activate"
+  <eg-grid-action handler="grid_actions.activate_wide"
     label="[% l('Activate') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.suspend"
+  <eg-grid-action handler="grid_actions.suspend_wide"
     label="[% l('Suspend') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.set_top_of_queue"
+  <eg-grid-action handler="grid_actions.set_top_of_queue_wide"
     label="[% l('Set Top of Queue') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.clear_top_of_queue"
+  <eg-grid-action handler="grid_actions.clear_top_of_queue_wide"
     label="[% l('Un-Set Top of Queue') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.transfer_to_marked_title"
+  <eg-grid-action handler="grid_actions.transfer_to_marked_tit_widele"
     label="[% l('Transfer To Marked Title') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.mark_damaged"
+  <eg-grid-action handler="grid_actions.mark_damaged_wide"
     label="[% l('Mark Item Damaged') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.mark_missing"
+  <eg-grid-action handler="grid_actions.mark_missing_wide"
     label="[% l('Mark Item Missing') %]"></eg-grid-action>
   <eg-grid-action divider="true"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.retarget"
+  <eg-grid-action handler="grid_actions.retarget_wide"
     label="[% l('Find Another Target') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.cancel_hold"
+  <eg-grid-action handler="grid_actions.cancel_hold_wide"
     label="[% l('Cancel Hold') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.uncancel_hold"
+  <eg-grid-action handler="grid_actions.uncancel_hold_wide"
     label="[% l('Uncancel Hold') %]"></eg-grid-action>
 
   <eg-grid-field label="[% l('Hold ID') %]" path='hold.id'></eg-grid-field>
   <eg-grid-field label="[% l('Current Copy') %]" 
-    path='hold.current_copy.barcode'>
-    <a href="./cat/item/{{item.hold.current_copy().id()}}/summary" target="_self">
-      {{item.hold.current_copy().barcode()}}
+    path='hold.cp_barcode'>
+    <a href="./cat/item/{{item.hold.cp_id}}/summary" target="_self">
+      {{item.hold.cp_barcode}}
     </a>
   </eg-grid-field>
 
@@ -74,29 +70,130 @@
   <eg-grid-field label="[% l('Capture Date') %]" path='hold.capture_time' datatype="timestamp"></eg-grid-field>
   <eg-grid-field label="[% l('Available Date') %]" path='hold.shelf_time' datatype="timestamp"></eg-grid-field>
   <eg-grid-field label="[% l('Hold Type') %]" path='hold.hold_type'></eg-grid-field>
-  <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pickup_lib.shortname'></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pl_shortname'></eg-grid-field>
   <eg-grid-field label="[% l('Post-Clear') %]" path='post_clear'></eg-grid-field>
 
-  <eg-grid-field label="[% l('Title') %]" path='mvr.title'>
-    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.mvr.doc_id()}}">
-      {{item.mvr.title()}}
+  <eg-grid-field label="[% l('Title') %]" path='hold.title'>
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.hold.record_id}}">
+      {{item.hold.title}}
     </a>
   </eg-grid-field>
 
-  <eg-grid-field label="[% l('Author') %]" path='mvr.author'></eg-grid-field>
-  <eg-grid-field label="[% l('Potential Copies') %]" path='potential_copies'></eg-grid-field>
+  <eg-grid-field label="[% l('Author') %]" path='hold.author'></eg-grid-field>
+  <eg-grid-field label="[% l('Potential Copies') %]" path='hold.potentials'></eg-grid-field>
   <eg-grid-field label="[% l('Status') %]" path='status_string'></eg-grid-field>
 
-  <eg-grid-field label="[% l('Queue Position') %]" path='queue_position' hidden></eg-grid-field>
-  <eg-grid-field path='hold.usr.*' parent-idl-class="ahr" hidden></eg-grid-field>
-  <eg-grid-field path='hold.requestor.*' parent-idl-class="ahr" hidden></eg-grid-field>
-  <eg-grid-field label="[% l('Copy Status') %]" path="hold.current_copy.status.name" hidden>
-  </eg-grid-field>
-  <eg-grid-field path='hold.*' parent-idl-class="ahr" hidden></eg-grid-field>
-  <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
-  <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
-  <eg-grid-field path='volume.prefix.label' label="[% l('CN Prefix') %]" parent-idl-class="acn" hidden></eg-grid-field>
-  <eg-grid-field path='volume.suffix.label' label="[% l('CN Suffix') %]" parent-idl-class="acn" hidden></eg-grid-field>
-  <eg-grid-field path='mvr.*' parent-idl-class="mvr" hidden></eg-grid-field>
-</eg-grid>
+  <eg-grid-field label="[% l('Queue Position') %]" path='hold.relative_queue_position' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Status') %]" path="hold.cs_name" hidden></eg-grid-field>
 
+  <eg-grid-field label="[% l('Fulfillment Date/Time') %]" path='hold.fulfillment_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Checkin Time') %]" path='hold.checkin_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Return Time') %]" path='hold.return_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Last Targeting Date/Time') %]" path='hold.prev_check_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Expire Time') %]" path='hold.expire_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Hold Cancel Date/Time') %]" path='hold.cancel_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cancelation note') %]" path='hold.cancel_note' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Hold Target') %]" path='hold.target' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Current Copy') %]" path='hold.current_copy' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Fulfilling Staff') %]" path='hold.fulfillment_staff' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Fulfilling Library') %]" path='hold.fulfillment_lib' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Requesting Library') %]" path='hold.request_lib' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Requesting User') %]" path='hold.requestor' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('User') %]" path='hold.usr' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Selection Library') %]" path='hold.selection_ou' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Item Selection Depth') %]" path='hold.selection_depth' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Holdable Formats (for M-type hold)') %]" path='hold.holdable_formats' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notifications Phone Number') %]" path='hold.phone_notify' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notifications SMS Number') %]" path='hold.sms_notify' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notify by Email?') %]" path='hold.email_notify' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('SMS Carrier') %]" path='hold.sms_carrier' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Currently Frozen') %]" path='hold.frozen' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Activation Date') %]" path='hold.thaw_date' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Top of Queue') %]" path='hold.cut_in_line' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Mint Condition') %]" path='hold.mint_condition' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Shelf Expire Time') %]" path='hold.shelf_expire_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Current Shelf Library') %]" path='hold.current_shelf_lib' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Behind Desk') %]" path='hold.behind_desk' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status') %]" path='hold.hold_status' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Clearable') %]" path='hold.clear_me' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Staff-placed Hold') %]" path='hold.is_staff_hold' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cancelation Cause ID') %]" path='hold.cc_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cancelation Cause') %]" path='hold.cc_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pl_shortname'></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Name') %]" path='hold.pl_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Email') %]" path='hold.pl_email' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Phone') %]" path='hold.pl_phone' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Pickup Library Opac Visible') %]" path='hold.pl_opac_visible' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit ID') %]" path='hold.tr_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Send Time') %]" path='hold.tr_source_send_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Receive Time') %]" path='hold.tr_dest_recv_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Copy') %]" path='hold.tr_target_copy' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Source') %]" path='hold.tr_source' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Destination') %]" path='hold.tr_dest' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Copy Status') %]" path='hold.tr_copy_status' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Hold') %]" path='hold.tr_hold' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Transit Cancel Time') %]" path='hold.tr_cancel_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Hold Note Count') %]" path='hold.note_count' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('User Display Name') %]" path='hold.usr_display_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('User Barcode') %]" path='hold.ucard_barcode' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Requestor Username') %]" path='hold.rusr_usrname' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy ID') %]" path='hold.cp_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Number on Volume') %]" path='hold.cp_copy_number' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Fine Level') %]" path='hold.cp_fine_level' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Can Circulate') %]" path='hold.cp_circulate' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Deposit Amount') %]" path='hold.cp_deposit_amount' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Deposit Required') %]" path='hold.cp_deposit' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Reference') %]" path='hold.cp_ref' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Is Holdable') %]" path='hold.cp_holdable' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Price') %]" path='hold.cp_price' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Barcode') %]" path='hold.cp_barcode' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Circulation Modifier') %]" path='hold.cp_circ_modifier' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Circulate as MARC Type') %]" path='hold.cp_circ_as_type' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Precat Dummy Title') %]" path='hold.cp_dummy_title' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Precat Dummy Author') %]" path='hold.cp_dummy_author' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Alert Message (deprecated)') %]" path='hold.cp_alert_message' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy OPAC Visible') %]" path='hold.cp_opac_visible' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Deleted') %]" path='hold.cp_deleted' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Floating Group') %]" path='hold.cp_floating' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Precat Dummy ISBN') %]" path='hold.cp_dummy_isbn' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Status Change Time') %]" path='hold.cp_status_change_time' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Active Date') %]" path='hold.cp_active_date' datatype="timestamp" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Is Mint Condition') %]" path='hold.cp_mint_condition' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cost') %]" path='hold.cp_cost' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Status') %]" path='hold.cs_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Holdable') %]" path='hold.cs_holdable' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is OPAC Visible') %]" path='hold.cs_opac_visible' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Copy-Active') %]" path='hold.cs_copy_active' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Deleted') %]" path='hold.cs_restrict_copy_delete' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Status Is Available') %]" path='hold.cs_is_available' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Issuance Label') %]" path='hold.issuance_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Call Number ID') %]" path='hold.cn_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Label') %]" path='hold.cn_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Label Class') %]" path='hold.cn_label_class' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Sort Key') %]" path='hold.cn_label_sortkey' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part ID') %]" path='hold.p_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part Label') %]" path='hold.p_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part Sort Key') %]" path='hold.p_label_sortkey' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Part Is Deleted') %]" path='hold.p_deleted' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('CN Full Label') %]" path='hold.cn_full_label' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Record ID') %]" path='hold.record_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location ID') %]" path='hold.acpl_id' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location') %]" path='hold.acpl_name' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Holdable') %]" path='hold.acpl_holdable' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Hold-Verify') %]" path='hold.acpl_hold_verify' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location OPAC Visible') %]" path='hold.acpl_opac_visible' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Can Circulate') %]" path='hold.acpl_circulate' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Prefix') %]" path='hold.acpl_label_prefix' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Suffix') %]" path='hold.acpl_label_suffix' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Checkin Alert') %]" path='hold.acpl_checkin_alert' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Is Deleted') %]" path='hold.acpl_deleted' datatype="bool" hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location URL') %]" path='hold.acpl_url' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Copy Location Order') %]" path='hold.copy_location_order_position' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Default Estimated Wait Time') %]" path='hold.default_estimated_wait' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Minimum Estimated Wait Time') %]" path='hold.min_estimated_wait' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Peer Hold Count') %]" path='hold.other_holds' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Total Wait Time') %]" path='hold.total_wait_time' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Notify Count') %]" path='hold.notification_count' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Last Notify Time') %]" path='hold.last_notification_time' datatype="timestamp" hidden></eg-grid-field>
+
+</eg-grid>
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 b2435e9..d778ec2 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
@@ -589,7 +589,6 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
             conjoinedSvc.fetch($scope.record_id).then(function(){
                 $scope.conjoinedGridDataProvider.refresh();
             });
-            egHolds.fetch_holds(hold_ids).then($scope.hold_grid_data_provider.refresh);
             init_parts_url();
             $location.update_path('/cat/catalog/record/' + $scope.record_id);
             // update_path() bypasses the controller for path 
@@ -1706,69 +1705,84 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     var provider = egGridDataProvider.instance({});
     $scope.hold_grid_data_provider = provider;
     $scope.grid_actions = egHoldGridActions;
-    $scope.grid_actions.refresh = function () { provider.refresh() };
+    $scope.grid_actions.refresh = function () { holds = []; hold_count = 0; provider.refresh() };
     $scope.hold_grid_controls = {};
 
-    var hold_ids = []; // current list of holds
-    function fetchHolds(offset, count) {
-        var ids = hold_ids.slice(offset, offset + count);
-
-        return egHolds.fetch_holds(ids).then(null, null,
-            function(hold_data) { 
-                return hold_data;
-            }
-        );
-    }
-
+    var holds = []; // current list of holds
+    var hold_count = 0;
     provider.get = function(offset, count) {
         if ($scope.record_tab != 'holds') return $q.when();
-        var deferred = $q.defer();
-        hold_ids = []; // no caching ATM
 
-        // open a determinate progress dialog, max value set below.
-        egProgressDialog.open({max : 1, value : 0});
+        // see if we have the requested range cached
+        if (holds[offset]) {
+            return provider.arrayNotifier(holds, offset, count);
+        }
 
-        // fetch the IDs
-        egCore.net.request(
-            'open-ils.circ',
-            'open-ils.circ.holds.retrieve_all_from_title',
-            egCore.auth.token(), $scope.record_id, 
-            {pickup_lib : egCore.org.descendants($scope.pickup_ou.id(), true)}
-        ).then(
-            function(hold_data) {
-                hold_ids = []; // clear the list of ids, hack to avoid dups
-                // TODO: fix the underlying problem, which is that
-                // this gets called twice when switching to the holds
-                // tab; once explicitly, and once via the change handler
-                // on the OU selector
-                angular.forEach(hold_data, function(list, type) {
-                    hold_ids = hold_ids.concat(list);
-                });
+        hold_count = 0;
+        holds = [];
+        var restrictions = {
+                is_staff_request : 'true',
+                fulfillment_time : null,
+                cancel_time      : null,
+                record_id        : $scope.record_id,
+                pickup_lib       : egCore.org.descendants($scope.pickup_ou.id(), true)
+        };
 
-                // Set the max value of the progress bar to the lesser of
-                // the total number of holds to fetch or the page size
-                // of the grid.
-                egProgressDialog.update(
-                    {max : Math.min(hold_ids.length, count)});
-
-                var holds_fetched = 0;
-                fetchHolds(offset, count)
-                .then(deferred.resolve, null, 
-                    function(hold_data) {
-                        holds_fetched++;
-                        deferred.notify(hold_data);
-                        egProgressDialog.increment();
+        var order_by = [{ capture_time : null }];
+        if (provider.sort && provider.sort.length) {
+            order_by = [];
+            angular.forEach(provider.sort, function (c) {
+                if (!angular.isObject(c)) {
+                    if (c.match(/^hold\./)) {
+                        var i = c.replace('hold.','');
+                        var ob = {};
+                        ob[i] = null;
+                        order_by.push(ob);
                     }
-                )['finally'](egProgressDialog.close);
+                } else {
+                    var i = Object.keys(c)[0];
+                    var direction = c[i];
+                    if (i.match(/^hold\./)) {
+                        i = i.replace('hold.','');
+                        var ob = {}
+                        ob[i] = {dir:direction};
+                        order_by.push(ob);
+                    }
+                }
+            });
+        }
+
+        egProgressDialog.open({max : 1, value : 0});
+        var first = true;
+        return egHolds.fetch_wide_holds(
+            restrictions,
+            order_by
+        ).then(function () {
+                return provider.arrayNotifier(holds, offset, count);
+            },
+            null,
+            function(hold_data) {
+                if (first) {
+                    hold_count = hold_data;
+                    first = false;
+                    egProgressDialog.update({max:hold_count});
+                } else {
+                    egProgressDialog.increment();
+                    var new_item = { id : hold_data.id, hold : hold_data };
+                    new_item.status_string =
+                        egCore.strings['HOLD_STATUS_' + hold_data.hold_status]
+                        || hold_data.hold_status;
+
+                    holds.push(new_item);
+                }
             }
-        );
+        ).finally(egProgressDialog.close);
 
-        return deferred.promise;
     }
 
     $scope.detail_view = function(action, user_data, items) {
         if (h = items[0]) {
-            $scope.detail_hold_id = h.hold.id();
+            $scope.detail_hold_id = h.hold.id;
         }
     }
 
@@ -1780,28 +1794,43 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     $scope.pickup_ou = egCore.org.get(egCore.auth.user().ws_ou());
     $scope.pickup_ou_changed = function(org) {
         $scope.pickup_ou = org;
+        holds = []
+        hold_count = 0;
         provider.refresh();
     }
 
+    function map_prefix_to_subhash (h,pf) {
+        var newhash = {};
+        angular.forEach(Object.keys(h), function (k) {
+            if (k.startsWith(pf)) {
+                var nk = k.substr(pf.length);
+                newhash[nk] = h[k];
+            }
+        });
+        return newhash;
+    }
+
     $scope.print_holds = function() {
-        var holds = [];
-        angular.forEach($scope.hold_grid_controls.allItems(), function(item) {
-            holds.push({
-                hold : egCore.idl.toHash(item.hold),
-                patron_last : item.patron_last,
-                patron_alias : item.patron_alias,
-                patron_barcode : item.patron_barcode,
-                copy : egCore.idl.toHash(item.copy),
-                volume : egCore.idl.toHash(item.volume),
-                title : item.mvr.title(),
-                author : item.mvr.author()
+        var pholds = [];
+        angular.forEach(holds, function(item) {
+            pholds.push({
+                hold : item.hold,
+                status_string : item.status_string,
+                patron_first : item.hold.usr_first_given_name,
+                patron_last : item.hold.usr_family_name,
+                patron_alias : item.hold.usr_alias,
+                patron_barcode : item.hold.ucard_barcode,
+                copy : map_prefix_to_subhash(item.hold,'cp_'),
+                volume : map_prefix_to_subhash(item.hold,'cn_'),
+                title : item.hold.title,
+                author : item.hold.author
             });
         });
 
         egCore.print.print({
             context : 'receipt', 
             template : 'holds_for_bib', 
-            scope : {holds : holds}
+            scope : {holds : pholds}
         });
     }
 
@@ -1817,7 +1846,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     // UI presents this option as "all holds"
     $scope.transfer_holds_to_marked = function() {
         var hold_ids = $scope.hold_grid_controls.allItems().map(
-            function(hold_data) {return hold_data.hold.id()});
+            function(hold_data) {return hold_data.hold.id});
         egHolds.transfer_to_marked_title(hold_ids);
     }
 
diff --git a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
index fc9ea34..6456aad 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
@@ -37,8 +37,8 @@ angular.module('egHoldsApp',
 
 
 .controller('HoldsShelfCtrl',
-       ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egHoldGridActions','egCirc','egGridDataProvider',
-function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egHoldGridActions , egCirc , egGridDataProvider)  {
+       ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egHoldGridActions','egCirc','egGridDataProvider','egProgressDialog',
+function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egHoldGridActions , egCirc , egGridDataProvider , egProgressDialog)  {
     $scope.detail_hold_id = $routeParams.hold_id;
 
     var hold_ids = [];
@@ -47,20 +47,11 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
     $scope.gridControls = {};
     $scope.grid_actions = egHoldGridActions;
 
-    function fetch_holds(offset, count) {
-        var ids = hold_ids.slice(offset, offset + count);
-        return egHolds.fetch_holds(ids).then(null, null,
-            function(hold_data) { 
-                holds.push(hold_data);
-                return hold_data; // to the grid
-            }
-        );
-    }
-
     var provider = egGridDataProvider.instance({});
     $scope.gridDataProvider = provider;
 
     function refresh_page() {
+        hold_count = 0;
         holds = [];
         hold_ids = [];
         provider.refresh();
@@ -75,35 +66,74 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
             return provider.arrayNotifier(holds, offset, count);
         }
 
-        // see if we have the holds IDs for this range already loaded
-        if (hold_ids[offset]) {
-            return fetch_holds(offset, count);
-        }
+        // if in clear mode...
+        if (clear_mode && holds.length) {
+            holds = holds.filter(function(h) { return h.hold.clear_me == 't' });
+        } 
 
-        var deferred = $q.defer();
-        hold_ids = [];
+        hold_count = 0;
         holds = [];
+        var restrictions = {
+                is_staff_request  : 'true',
+                capture_time      : { not : null },
+                cs_id             : 8, // on holds shelf
+                fulfillment_time  : null,
+                current_shelf_lib : $scope.pickup_ou.id()
+        };
+
+        var order_by = [{ capture_time : null }];
+        if (provider.sort && provider.sort.length) {
+            order_by = [];
+            angular.forEach(provider.sort, function (c) {
+                if (!angular.isObject(c)) {
+                    if (c.match(/^hold\./)) {
+                        var i = c.replace('hold.','');
+                        var ob = {};
+                        ob[i] = null;
+                        order_by.push(ob);
+                    }
+                } else {
+                    var i = Object.keys(c)[0];
+                    var direction = c[i];
+                    if (i.match(/^hold\./)) {
+                        i = i.replace('hold.','');
+                        var ob = {}
+                        ob[i] = {dir:direction};
+                        order_by.push(ob);
+                    }
+                }
+            });
+        }
 
-        var method = 'open-ils.circ.captured_holds.id_list.on_shelf.retrieve.authoritative.atomic';
-        if (clear_mode) 
-            method = 'open-ils.circ.captured_holds.id_list.expired_on_shelf_or_wrong_shelf.retrieve.atomic';
-
-        egCore.net.request(
-            'open-ils.circ', method,
-            egCore.auth.token(), $scope.pickup_ou.id()
-
-        ).then(function(ids) {
-            if (!ids.length) { 
-                deferred.resolve(); 
-                return; 
+        egProgressDialog.open({max : 1, value : 0});
+        var first = true;
+        return egHolds.fetch_wide_holds(
+            restrictions,
+            order_by
+        ).then(function () {
+                return provider.arrayNotifier(holds, offset, count);
+            },
+            null,
+            function(hold_data) { 
+                if (first) {
+                    hold_count = hold_data;
+                    first = false;
+                    egProgressDialog.update({max:hold_count});
+                } else {
+                    egProgressDialog.increment();
+                    var new_item = { id : hold_data.id, hold : hold_data };
+                    new_item.status_string =
+                        egCore.strings['HOLD_STATUS_' + hold_data.hold_status]
+                        || hold_data.hold_status;
+
+                    if (clear_mode) {
+                        if (hold_data.clear_me == 't') holds.push(new_item);
+                    } else {
+                        holds.push(new_item);
+                    }
+                }
             }
-
-            hold_ids = ids;
-            fetch_holds(offset, count)
-            .then(deferred.resolve, null, deferred.notify);
-        });
-
-        return deferred.promise;
+        ).finally(egProgressDialog.close);
     }
 
     // re-draw the grid when user changes the org selector
@@ -115,7 +145,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
 
     $scope.detail_view = function(action, user_data, items) {
         if (h = items[0]) {
-            $location.path('/circ/holds/shelf/' + h.hold.id());
+            $location.path('/circ/holds/shelf/' + h.hold.id);
         }
     }
 
@@ -126,7 +156,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
     // when the detail hold is fetched (and updated), update the bib
     // record summary display record id.
     $scope.set_hold = function(hold_data) {
-        $scope.detail_hold_record_id = hold_data.mvr.doc_id();
+        $scope.detail_hold_record_id = hold_data.hold.record_id;
     }
 
     // manage active vs. clearable holds display
@@ -144,7 +174,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
         angular.forEach(resp, function(info) {
             if (info.action) {
                 var grid_item = holds.filter(function(item) {
-                    return item.hold.id() == info.hold_details.id
+                    return item.hold.id == info.hold_details.id
                 })[0];
 
                 // there will be no grid item if the hold is off-page
@@ -197,48 +227,45 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
         );
     }
 
-    $scope.print_list_progress = null;
+    function map_prefix_to_subhash (h,pf) {
+        var newhash = {};
+        angular.forEach(Object.keys(h), function (k) {
+            if (k.startsWith(pf)) {
+                var nk = k.substr(pf.length);
+                newhash[nk] = h[k];
+            }
+        });
+        return newhash;
+    }
+
     $scope.print_shelf_list = function() {
         var print_holds = [];
-        $scope.print_list_loading = true;
-        $scope.print_list_progress = 0;
+        angular.forEach(holds, function(hold_data) {
+            var phold = {};
+            print_holds.push(phold);
 
-        // collect the full list of holds
-        var method = 'open-ils.circ.captured_holds.id_list.on_shelf.retrieve.authoritative.atomic';
-        if (clear_mode)
-            method = 'open-ils.circ.captured_holds.id_list.expired_on_shelf_or_wrong_shelf.retrieve.atomic';
-        egCore.net.request(
-            'open-ils.circ',
-            method,
-            egCore.auth.token(), $scope.pickup_ou.id()
-        ).then( function(idlist) {
-
-            egHolds.fetch_holds(idlist).then(
-                function () {
-                    console.debug('printing ' + print_holds.length + ' holds');
-                    // holds fetched, send to print
-                    egCore.print.print({
-                        context : 'default', 
-                        template : 'hold_shelf_list', 
-                        scope : {holds : print_holds}
-                    })
-                },
-                null,
-                function(hold_data) {
-                    $scope.print_list_progress++;
-                    egHolds.local_flesh(hold_data);
-                    print_holds.push(hold_data);
-                    hold_data.title = hold_data.mvr.title();
-                    hold_data.author = hold_data.mvr.author();
-                    hold_data.hold = egCore.idl.toHash(hold_data.hold);
-                    hold_data.copy = egCore.idl.toHash(hold_data.copy);
-                    hold_data.volume = egCore.idl.toHash(hold_data.volume);
-                    hold_data.part = egCore.idl.toHash(hold_data.part);
-                }
-            )
-        }).finally(function() {
-            $scope.print_list_loading = false;
-            $scope.print_list_progress = null;
+            phold.status_string = hold_data.status_string;
+
+            phold.patron_first = hold_data.hold.usr_first_given_name;
+            phold.patron_last = hold_data.hold.usr_family_name;
+            phold.patron_alias = hold_data.hold.usr_alias;
+            phold.patron_barcode = hold_data.hold.ucard_barcode;
+
+            phold.title = hold_data.hold.title;
+            phold.author = hold_data.hold.author;
+
+            phold.hold = hold_data.hold;
+            phold.copy = map_prefix_to_subhash(hold_data.hold, 'cp_');
+            phold.volume = map_prefix_to_subhash(hold_data.hold, 'cn_');
+            phold.part = map_prefix_to_subhash(hold_data.hold, 'p_');
+        });
+
+        console.log(print_holds);
+
+        return egCore.print.print({
+            context : 'default', 
+            template : 'hold_shelf_list', 
+            scope : {holds : print_holds}
         });
     }
 
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
index 656e7c4..f76d92b 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
@@ -11,6 +11,15 @@ function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
 
     var service = {};
 
+    service.fetch_wide_holds = function(restrictions, order_by, limit, offset) {
+        return egCore.net.request(
+            'open-ils.circ',
+            'open-ils.circ.hold.wide_hash.stream',
+            egCore.auth.token(),
+            restrictions, order_by, limit, offset
+        );
+    }
+
     service.fetch_holds = function(hold_ids) {
         var deferred = $q.defer();
 
@@ -531,6 +540,14 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
         return egHolds.cancel_holds(hold_ids).then(service.refresh);
     }
 
+    service.cancel_wide_hold = function(items) {
+        var hold_ids = items.filter(function(item) {
+            return !item.hold.cancel_time;
+        }).map(function(item) {return item.hold.id});
+
+        return egHolds.cancel_holds(hold_ids).then(service.refresh);
+    }
+
     service.uncancel_hold = function(items) {
         var hold_ids = items.filter(function(item) {
             return item.hold.cancel_time();
@@ -539,6 +556,14 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
         return egHolds.uncancel_holds(hold_ids).then(service.refresh);
     }
 
+    service.uncancel_wide_hold = function(items) {
+        var hold_ids = items.filter(function(item) {
+            return item.hold.cancel_time;
+        }).map(function(item) {return item.hold.id});
+
+        return egHolds.uncancel_holds(hold_ids).then(service.refresh);
+    }
+
     // jump to circ list for either 1) the targeted copy or
     // 2) the hold target copy for copy-level holds
     service.show_recent_circs = function(items) {
@@ -554,6 +579,21 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
         });
     }
 
+    // jump to circ list for either 1) the targeted copy or
+    // 2) the hold target copy for copy-level holds
+    service.show_recent_circs_wide = function(items) {
+        var focus = items.length == 1;
+        angular.forEach(items, function(item) {
+            if (item.hold.cp_id) {
+                var url = egCore.env.basePath +
+                          '/cat/item/' +
+                          item.hold.cp_id +
+                          '/circ_list';
+                $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
+            }
+        });
+    }
+
     service.show_patrons = function(items) {
         var focus = items.length == 1;
         angular.forEach(items, function(item) {
@@ -565,6 +605,17 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
         });
     }
 
+    service.show_patrons_wide = function(items) {
+        var focus = items.length == 1;
+        angular.forEach(items, function(item) {
+            var url = egCore.env.basePath +
+                      'circ/patron/' +
+                      item.hold.usr_id +
+                      '/holds';
+            $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
+        });
+    }
+
     service.show_holds_for_title = function(items) {
         var focus = items.length == 1;
         angular.forEach(items, function(item) {
@@ -576,6 +627,17 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
         });
     }
 
+    service.show_holds_for_title_wide = function(items) {
+        var focus = items.length == 1;
+        angular.forEach(items, function(item) {
+            var url = egCore.env.basePath +
+                      'cat/catalog/record/' +
+                      item.hold.record_id +
+                      '/holds';
+            $timeout(function() { var x = $window.open(url, '_blank'); if (focus) x.focus() });
+        });
+    }
+
 
     function generic_update(items, action) {
         if (!items.length) return $q.when();
@@ -583,6 +645,12 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
         return egHolds[action](hold_ids).then(service.refresh);
     }
 
+    function generic_update_wide(items, action) {
+        if (!items.length) return $q.when();
+        var hold_ids = items.map(function(item) {return item.hold.id});
+        return egHolds[action](hold_ids).then(service.refresh);
+    }
+
     service.set_copy_quality = function(items) {
         generic_update(items, 'set_copy_quality'); }
     service.edit_pickup_lib = function(items) {
@@ -602,6 +670,25 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
     service.transfer_to_marked_title = function(items) {
         generic_update(items, 'transfer_to_marked_title'); }
 
+    service.set_copy_quality_wide = function(items) {
+        generic_update_wide(items, 'set_copy_quality'); }
+    service.edit_pickup_lib_wide = function(items) {
+        generic_update_wide(items, 'edit_pickup_lib'); }
+    service.edit_notify_prefs_wide = function(items) {
+        generic_update_wide(items, 'edit_notify_prefs'); }
+    service.edit_dates_wide = function(items) {
+        generic_update_wide(items, 'edit_dates'); }
+    service.suspend_wide = function(items) {
+        generic_update_wide(items, 'suspend_holds'); }
+    service.activate_wide = function(items) {
+        generic_update_wide(items, 'activate_holds'); }
+    service.set_top_of_queue_wide = function(items) {
+        generic_update_wide(items, 'set_top_of_queue'); }
+    service.clear_top_of_queue_wide = function(items) {
+        generic_update_wide(items, 'clear_top_of_queue'); }
+    service.transfer_to_marked_title_wide = function(items) {
+        generic_update_wide(items, 'transfer_to_marked_title'); }
+
     service.mark_damaged = function(items) {
         angular.forEach(items, function(item) {
             if (item.copy) {
@@ -613,6 +700,17 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
         });
     }
 
+    service.mark_damaged_wide = function(items) {
+        angular.forEach(items, function(item) {
+            if (item.copy) {
+                egCirc.mark_damaged({
+                    id: item.hold.cp_id,
+                    barcode: item.hold.cp_barcode
+                }).then(service.refresh);
+            }
+        });
+    }
+
     service.mark_missing = function(items) {
         var copy_ids = items
             .filter(function(item) { return Boolean(item.copy) })
@@ -621,11 +719,24 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
             egCirc.mark_missing(copy_ids).then(service.refresh);
     }
 
+    service.mark_missing_wide = function(items) {
+        var copy_ids = items
+            .filter(function(item) { return Boolean(item.hold.cp_id) })
+            .map(function(item) { return item.hold.cp_id });
+        if (copy_ids.length) 
+            egCirc.mark_missing(copy_ids).then(service.refresh);
+    }
+
     service.retarget = function(items) {
         var hold_ids = items.map(function(item) { return item.hold.id() });
         egHolds.retarget(hold_ids).then(service.refresh);
     }
 
+    service.retarget_wide = function(items) {
+        var hold_ids = items.map(function(item) { return item.hold.id });
+        egHolds.retarget(hold_ids).then(service.refresh);
+    }
+
     return service;
 }])
 
diff --git a/Open-ILS/web/js/ui/default/staff/services/hatch.js b/Open-ILS/web/js/ui/default/staff/services/hatch.js
index 12b144d..bb12479 100644
--- a/Open-ILS/web/js/ui/default/staff/services/hatch.js
+++ b/Open-ILS/web/js/ui/default/staff/services/hatch.js
@@ -595,7 +595,11 @@ angular.module('egCoreMod')
         } else if (value === undefined) {
             return;
         }
-        $window.localStorage.setItem(key, jsonified);
+        try {
+            $window.localStorage.setItem(key, jsonified);
+        } catch (e) {
+            console.log('localStorage.setItem (overwrite) failed for '+key+': ', e);
+        }
     }
 
     service.appendItem = function(key, value) {
@@ -631,7 +635,11 @@ angular.module('egCoreMod')
             jsonified = JSON.stringify(value);
 
         var old_value = $window.localStorage.getItem(key) || '';
-        $window.localStorage.setItem( key, old_value + jsonified );
+        try {
+            $window.localStorage.setItem( key, old_value + jsonified );
+        } catch (e) {
+            console.log('localStorage.setItem (append) failed for '+key+': ', e);
+        }
     }
 
     // Set the value for the given key.  

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

Summary of changes:
 .../perlmods/lib/OpenILS/Application/Circ/Holds.pm |   43 +++
 .../Application/Storage/Publisher/action.pm        |  326 ++++++++++++++++++++
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |   12 +-
 .../1124.data.wide_holds-workstation-settings.sql  |   25 ++
 .../src/templates/staff/cat/catalog/t_holds.tt2    |  199 +++++++++---
 .../templates/staff/circ/holds/t_shelf_list.tt2    |  185 +++++++++---
 .../web/js/ui/default/staff/cat/catalog/app.js     |  155 ++++++----
 Open-ILS/web/js/ui/default/staff/circ/holds/app.js |  209 ++++++++-----
 .../web/js/ui/default/staff/circ/services/holds.js |  111 +++++++
 Open-ILS/web/js/ui/default/staff/services/hatch.js |   12 +-
 11 files changed, 1034 insertions(+), 245 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/1124.data.wide_holds-workstation-settings.sql


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list