LP#1653001 webstaff: Holds pull list sortable columns
authorBill Erickson <berick@esilibrary.com>
Wed, 20 Aug 2014 20:45:47 +0000 (16:45 -0400)
committerKathy Lussier <klussier@masslnc.org>
Mon, 6 Mar 2017 20:18:17 +0000 (15:18 -0500)
Retrieve holds for the pull list via the canned "ahopl" pull list IDL
class.  This lets the grid fetch the data via canned flattener query,
supporting server-side sort/limit/offset options.

To retain all previous UI behavior, primarily editing hold attributes
(e.g. notification prefs), hold details for each hold have to be fetched
(and cached) in addition to the main grid data.  The grid renders and
sorts the flattener data, then grid actions act upon the fleshed hold
details data.

Commit also includes:

1. Added some missing IDL links for the "ahopl" class.
2. Micro-optimization to egGrid to exit early when an invalid IDL path
   is provided.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2
Open-ILS/web/js/ui/default/staff/circ/holds/app.js
Open-ILS/web/js/ui/default/staff/services/grid.js

index 2ec0f20..86efb0d 100644 (file)
@@ -5658,7 +5658,7 @@ SELECT  usr,
                        <field reporter:label="Status" name="status" oils_persist:virtual="true" />
                        <field reporter:label="Transit" name="transit" oils_persist:virtual="true" />
                        <field reporter:label="Capture Date/Time" name="capture_time" reporter:datatype="timestamp"/>
-                       <field reporter:label="Currently Targeted Copy" name="current_copy" />
+                       <field reporter:label="Currently Targeted Copy" name="current_copy" reporter:datatype="link"/>
                        <field reporter:label="Notify by Email?" name="email_notify" reporter:datatype="bool"/>
                        <field reporter:label="Hold Expire Date/Time" name="expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Fulfilling Library" name="fulfillment_lib" reporter:datatype="org_unit"/>
@@ -5806,7 +5806,7 @@ SELECT  usr,
                        <field reporter:label="Status" name="status" oils_persist:virtual="true" />
                        <field reporter:label="Transit" name="transit" oils_persist:virtual="true" />
                        <field reporter:label="Capture Date/Time" name="capture_time" reporter:datatype="timestamp"/>
-                       <field reporter:label="Currently Targeted Copy" name="current_copy" />
+                       <field reporter:label="Currently Targeted Copy" name="current_copy" reporter:datatype="link"/>
                        <field reporter:label="Notify by Email?" name="email_notify" reporter:datatype="bool"/>
                        <field reporter:label="Hold Expire Date/Time" name="expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Fulfilling Library" name="fulfillment_lib" reporter:datatype="org_unit"/>
@@ -5891,7 +5891,7 @@ SELECT  usr,
                        <field reporter:label="Status" name="status" oils_persist:virtual="true" />
                        <field reporter:label="Transit" name="transit" oils_persist:virtual="true" />
                        <field reporter:label="Capture Date/Time" name="capture_time" reporter:datatype="timestamp"/>
-                       <field reporter:label="Currently Targeted Copy" name="current_copy" />
+                       <field reporter:label="Currently Targeted Copy" name="current_copy" reporter:datatype="link"/>
                        <field reporter:label="Notify by Email?" name="email_notify" reporter:datatype="bool"/>
                        <field reporter:label="Hold Expire Date/Time" name="expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Fulfilling Library" name="fulfillment_lib" reporter:datatype="org_unit"/>
@@ -5973,7 +5973,7 @@ SELECT  usr,
                <fields oils_persist:primary="id" oils_persist:sequence="action.hold_request_id_seq">
                        <field reporter:label="Status" name="status" oils_persist:virtual="true" />
                        <field reporter:label="Capture Date/Time" name="capture_time" reporter:datatype="timestamp"/>
-                       <field reporter:label="Currently Targeted Copy" name="current_copy" />
+                       <field reporter:label="Currently Targeted Copy" name="current_copy" reporter:datatype="link"/>
                        <field reporter:label="Notify by Email?" name="email_notify" reporter:datatype="bool"/>
                        <field reporter:label="Hold Expire Date/Time" name="expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Fulfilling Library" name="fulfillment_lib" reporter:datatype="org_unit"/>
@@ -6032,7 +6032,7 @@ SELECT  usr,
                <fields oils_persist:primary="id" oils_persist:sequence="action.hold_request_id_seq">
                        <field reporter:label="Status" name="status" oils_persist:virtual="true" />
                        <field reporter:label="Capture Date/Time" name="capture_time" reporter:datatype="timestamp"/>
-                       <field reporter:label="Currently Targeted Copy" name="current_copy" />
+                       <field reporter:label="Currently Targeted Copy" name="current_copy" reporter:datatype="link"/>
                        <field reporter:label="Notify by Email?" name="email_notify" reporter:datatype="bool"/>
                        <field reporter:label="Hold Expire Date/Time" name="expire_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Fulfilling Library" name="fulfillment_lib" reporter:datatype="org_unit"/>
index 4f4502f..91b8571 100644 (file)
@@ -4,8 +4,8 @@
 
 <eg-grid
   id-field="id"
-  features="-sort,-multisort"
-  items-provider="gridDataProvider"
+  idl-class="ahopl"
+  grid-controls="gridControls"
   persist-key="circ.holds.pull">
 
   <eg-grid-menu-item handler="detail_view" 
   <eg-grid-action handler="grid_actions.cancel_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()}}
-    </a>
+  <!-- Define the column using the sort-able copy location order
+       position, but display the location label in each cell -->
+  <eg-grid-field name="copy_location_order_position" required 
+    label="[% l('Shelving Location') %]">
+    <span>{{item.shelving_loc}}</span>
+  </eg-grid-field>
+  <eg-grid-field name="shelving_loc" path="current_copy.location.name" 
+    required hidden label="[% l('Shelving Location Label') %]">
   </eg-grid-field>
 
-  <eg-grid-field label="[% l('Part') %]" path='part.label'></eg-grid-field>
-  <eg-grid-field label="[% l('Request Date') %]" path='hold.request_time'></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('Copy Location') %]" path='copy.location.name'></eg-grid-field>
-  <eg-grid-field label="[% l('Call Number') %]" path='volume.label'></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>
+  <!-- Render the colum using the sort-able call number sort key, but 
+       display the call number label, complete with prefix and suffix 
+       in the cell. -->
+  <eg-grid-field name="call_number_sort_key" required
+    path="current_copy.call_number.label_sortkey" 
+    label="[% l('Call Number') %]">
+    <span>{{item.cn_prefix}} {{item.call_number_label}} {{item.cn_suffix}}</span>
   </eg-grid-field>
+  <eg-grid-field name="call_number_label" 
+    path="call_number_label" required hidden
+    label="[% l('Call Number Label') %]"></eg-grid-field>
+  <eg-grid-field name="cn_prefix" path="current_copy.call_number.prefix.label" 
+    hidden required label="[% l('Call Number Prefix') %]"></eg-grid-field>
+  <eg-grid-field name="cn_suffix" path="current_copy.call_number.suffix.label"
+    hidden required label="[% l('Call Number Suffix') %]"></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' hidden></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='hold.usr.*' parent-idl-class="ahr" hidden></eg-grid-field>
-  <eg-grid-field path='hold.usr.card.*' 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 path='hold.requestor.card.*' 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='mvr.*' parent-idl-class="mvr" hidden></eg-grid-field>
+  <eg-grid-field name="author" 
+    path="current_copy.call_number.record.simple_record.author" 
+    label="[% l('Author') %]"></eg-grid-field>
+  <eg-grid-field name="title" 
+    path="current_copy.call_number.record.simple_record.title" 
+    label="[% l('Title') %]">
+    <a target="_self" 
+      href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.record_id}}">
+      {{item.title}}
+    </a>
+  </eg-grid-field>
+  <eg-grid-field name="record_id" label="[% l('Bib Record ID') %]" 
+    required hidden path="current_copy.call_number.record.id"></eg-grid-field>
+  <eg-grid-field name="copy_id" 
+    path="current_copy.id" hidden required></eg-grid-field>
+  <eg-grid-field name="barcode" 
+    path="current_copy.barcode" label="[% l('Current Copy') %]">
+    <a href="./cat/item/{{item.copy_id}}/summary" target="_self">
+      {{item.barcode}}
+    </a>
+  </eg-grid-field>
+  <eg-grid-field name="parts" path="current_copy.parts.label" 
+    label="[% l('Parts') %]"></eg-grid-field>
+  <eg-grid-field name="copy_status" path="current_copy.status.name" 
+    label="[% l('Copy Status') %]"></eg-grid-field>
+  <eg-grid-field name="copy_circ_lib_id" path="current_copy.circ_lib.id" 
+    required hidden label="[% l('Copy Circ Lib ID') %]"></eg-grid-field>
+  <eg-grid-field name="notes" path="notes.body" 
+    hidden label="[% l('Hold Notes') %]"></eg-grid-field>
+  <eg-grid-field name="patron_id" path="usr.id" hidden required></eg-grid-field>
+  <eg-grid-field name="patron_barcode" path="usr.card.barcode" 
+    hidden label="[% l('Patron Barcode') %]">
+    <a href="./circ/patron/{{item.patron_id}}/holds" target="_self">
+      {{item.patron_barcode}}
+    </a>
+  </eg-grid-field>
+  <eg-grid-field name="pickup_lib_name" path="pickup_lib.name" 
+    hidden label="[% l('Pickup Library') %]"></eg-grid-field>
+  <eg-grid-field name="pickup_lib_shortname" path="pickup_lib.shortname" 
+    hidden label="[% l('Pickup Library (Shortname)') %]"></eg-grid-field>
+  <eg-grid-field name="request_lib_name" path="request_lib.name" 
+    hidden label="[% l('Request Library') %]"></eg-grid-field>
+  <eg-grid-field name="request_lib_shortname" path="request_lib.shortname" 
+    hidden label="[% l('Request Library (Shortname)') %]"></eg-grid-field>
+  <eg-grid-field name="selection_ou" path="selection_ou.shortname" 
+    hidden label="[% l('Selection Locus') %]"></eg-grid-field>
+  <eg-grid-field name="sms_carrier_name" path="sms_carrier.name" 
+    hidden label="[% l('SMS Carrier') %]"></eg-grid-field>
+  <eg-grid-field label="[% l('Potential Copies') %]" 
+    path='potential_copies'></eg-grid-field>
+  <eg-grid-field label="[% l('Queue Position') %]" 
+    path='queue_position' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Hold ID') %]" path='id' required hidden>
+  </eg-grid-field>
+  <eg-grid-field label="[% l('Request Date') %]" path='request_time' hidden>
+  </eg-grid-field>
 </eg-grid>
 
index 83bb529..0dccd94 100644 (file)
@@ -35,11 +35,6 @@ angular.module('egHoldsApp',
     $routeProvider.otherwise({redirectTo : '/circ/holds/shelf'});
 })
 
-.factory('holdUiSvc', function() {
-    return {
-        holds : [] // cache
-    }
-})
 
 .controller('HoldsShelfCtrl',
        ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egHoldGridActions','egCirc','egGridDataProvider',
@@ -249,43 +244,77 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
 }])
 
 .controller('HoldsPullListCtrl',
-       ['$scope','$q','$routeParams','$window','$location','egCore','egHolds','egCirc','egGridDataProvider','egHoldGridActions','holdUiSvc',
-function($scope , $q , $routeParams , $window , $location , egCore , egHolds , egCirc , egGridDataProvider , egHoldGridActions , holdUiSvc)  {
-    $scope.detail_hold_id = $routeParams.hold_id;
+       ['$scope','$q','$routeParams','$window','$location','egCore',
+        'egHolds','egCirc','egHoldGridActions',
+function($scope , $q , $routeParams , $window , $location , egCore , 
+         egHolds , egCirc , egHoldGridActions) {
 
-    var provider = egGridDataProvider.instance({});
-    $scope.gridDataProvider = provider;
+    $scope.detail_hold_id = $routeParams.hold_id;
 
-    $scope.grid_actions = egHoldGridActions;
-    $scope.grid_actions.refresh = function() {
-        holdUiSvc.holds = [];
-        provider.refresh();
+    var cached_details = {};
+    var details_needed = {};
+
+    $scope.gridControls = {
+        setQuery : function() {
+            return {'copy_circ_lib_id' : egCore.auth.user().ws_ou()}
+        },
+        setSort : function() {
+            return ['copy_location_order_position','call_number_sort_key']
+        },
+        itemRetrieved : function(item) {
+            if (!cached_details[item.id]) {
+                details_needed[item.id] = item;
+            }
+        },
+        allItemsRetrieved : flesh_holds
     }
 
-    provider.get = function(offset, count) {
 
-        if (holdUiSvc.holds[offset]) {
-            return provider.arrayNotifier(holdUiSvc.holds, offset, count);
-        }
+    // Fetches hold detail data for each hold in the grid and links
+    // the detail data to the related grid item so egHoldGridActions 
+    // and friends have access to holds data they understand.
+    // Only fetch not-yet-cached data.
+    function flesh_holds() {
 
-        var deferred = $q.defer();
-        var recv_index = 0;
+        // Start by fleshing hold details from our cached data.
+        var items = $scope.gridControls.allItems();
+        angular.forEach(items, function(item) {
+            if (!cached_details[item.id]) return;
+            angular.forEach(cached_details[item.id], 
+                function(val, key) { item[key] = val })
+        });
+
+        // Exit if all needed details were already cached
+        if (Object.keys(details_needed).length == 0) return;
+
+        $scope.print_list_loading = true;
+        $scope.print_list_progress = 0;
 
-        // fetch the IDs
         egCore.net.request(
             'open-ils.circ',
-            'open-ils.circ.hold_pull_list.fleshed.stream',
-            egCore.auth.token(), count, offset
+            'open-ils.circ.hold.details.batch.retrieve.authoritative',
+            egCore.auth.token(), Object.keys(details_needed)
         ).then(
-            deferred.resolve, null, 
-            function(hold_data) {
-                egHolds.local_flesh(hold_data);
-                holdUiSvc.holds[offset + recv_index++] = hold_data;
-                deferred.notify(hold_data);
+            function() {
+                $scope.print_list_loading = false;
+                $scope.print_list_progress = null;
+            }, null,
+            function(hold_info) {
+                var hold_id = hold_info.hold.id();
+                cached_details[hold_id] = hold_info;
+                var item = details_needed[hold_id];
+                delete details_needed[hold_id];
+                angular.forEach(hold_info, 
+                    function(val, key) { item[key] = val });
+                $scope.print_list_progress++;
             }
         );
+    }
 
-        return deferred.promise;
+    $scope.grid_actions = egHoldGridActions;
+    $scope.grid_actions.refresh = function() {
+        cached_details = {}; // un-cache details after edit actions.
+        $scope.gridControls.refresh();
     }
 
     $scope.detail_view = function(action, user_data, items) {
index 8828456..ff438f8 100644 (file)
@@ -1420,16 +1420,17 @@ angular.module('egGridMod',
                 idl_parent = idl_field;
                 idl_field = class_obj.field_map[part];
 
-                if (idl_field && idl_field['class'] && (
-                    idl_field.datatype == 'link' || 
-                    idl_field.datatype == 'org_unit')) {
-                    class_obj = egCore.idl.classes[idl_field['class']];
+                if (idl_field) {
+                    if (idl_field['class'] && (
+                        idl_field.datatype == 'link' || 
+                        idl_field.datatype == 'org_unit')) {
+                        class_obj = egCore.idl.classes[idl_field['class']];
+                    }
+                } else {
+                    return null;
                 }
-                // else, path is not in the IDL, which is fine
             }
 
-            if (!idl_field) return null;
-
             return {
                 idl_parent: idl_parent,
                 idl_field : idl_field,