holds shelf. actions / clear shelf
authorBill Erickson <berick@esilibrary.com>
Wed, 9 Jul 2014 15:03:31 +0000 (11:03 -0400)
committerBill Erickson <berick@esilibrary.com>
Wed, 9 Jul 2014 15:03:31 +0000 (11:03 -0400)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Open-ILS/src/templates/staff/circ/holds/index.tt2
Open-ILS/src/templates/staff/circ/holds/t_shelf.tt2
Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2
Open-ILS/src/templates/staff/css/style.css.tt2
Open-ILS/src/templates/staff/share/t_autogrid.tt2
Open-ILS/web/js/ui/default/staff/circ/holds/app.js
Open-ILS/web/js/ui/default/staff/circ/patron/holds.js
Open-ILS/web/js/ui/default/staff/services/grid.js

index 01d07b1..5bcac1c 100644 (file)
 [% INCLUDE 'staff/circ/share/hold_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/holds/app.js"></script>
 <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/circ.css" />
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+  s.CLEAR_SHELF_ACTION_shelf = "[% l('Reshelve') %]";
+}])
+</script>
 [% END %]
 
 <div ng-view></div>
index a465a6c..a3c4005 100644 (file)
@@ -2,18 +2,16 @@
   <div class="col-md-3">
     <div class="input-group">
       <span class="input-group-addon">[% l('Pickup Library') %]</span>
-      <eg-org-selector selected="pickup_ou"></eg-org-selector>
+      <eg-org-selector selected="pickup_ou" ng-disabled="is_clearing()"></eg-org-selector>
     </div>
   </div>
-  <div class="col-md-2">
-    <div class="checkbox">
-      <label>
-        <input ng-model="show_cleared" type="checkbox"/>
-        [% l('View Clearable Holds') %]
-      </label>
-    </div>
+  <div class="col-md-3" ng-show="is_clearing()">
+    <progressbar max="clear_progress.max" value="clear_progress.value">
+      <span class="progressbar-text">{{clear_progress.value}} / {{clear_progress.max}}</span>
+    </progressbar>
   </div>
 </div>
+
 <div class="pad-vert"></div>
 
 <div ng-if="!detail_hold_id">
index 0494b85..1ccf6b6 100644 (file)
@@ -3,11 +3,23 @@
   id-field="id"
   features="-sort,-multisort"
   items-provider="gridDataProvider"
+  grid-controls="gridControls"
   persist-key="circ.holds.shelf">
 
   <eg-grid-menu-item handler="detail_view" 
     label="[% l('Detail View') %]"></eg-grid-menu-item>
 
+  <eg-grid-menu-item handler="show_clearable" 
+    hidden="clear_mode" disabled="is_clearing"
+    label="[% l('Show Clearable Holds') %]"></eg-grid-menu-item>
+
+  <eg-grid-menu-item handler="show_active" 
+    hidden="active_mode" disabled="is_clearing"
+    label="[% l('Show All Holds') %]"></eg-grid-menu-item>
+
+  <eg-grid-menu-item handler="clear_holds" disabled="disable_clear"
+    label="[% l('Clear These Holds') %]"></eg-grid-menu-item>
+
   <eg-grid-action handler="show_recent_circs"
     label="[% l('Show Last Few Circulations') %]"></eg-grid-action>
   <eg-grid-action divider="true"></eg-grid-action>
@@ -52,6 +64,7 @@
   <eg-grid-field label="[% l('Available Date') %]" path='hold.shelf_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('Post-Clear') %]" path='post_clear'></eg-grid-field>
 
   <eg-grid-field label="[% l('Title') %]" path='mvr.title'>
     <a href="[% ctx.base_path %]/opac/record/{{item.mvr.doc_id()}}">
index ebfa1b1..6b1f31b 100644 (file)
@@ -163,6 +163,12 @@ table.list tr.selected td { /* deprecated? */
 /* bootstrap alerts are heavily padded.  use this to reduce */
 .alert-less-pad {padding: 5px;}
 
+/* text displayed inside a <progressbar>, typically the max/progress values */
+.progressbar-text {
+  color:black;
+  white-space:nowrap;
+}
+
 /* ----------------------------------------------------------------------
  * Grid
  * ---------------------------------------------------------------------- */
index cd5b3ad..136252c 100644 (file)
@@ -15,7 +15,7 @@
     </button>
     <ul class="dropdown-menu">
       <li ng-repeat="item in menuItems" ng-class="{divider: item.divider}">
-        <a ng-if="!item.divider" href
+        <a ng-if="!item.divider" href ng-disabled="item.disabled"
           ng-click="item.handler()">{{item.label}}</a>
       </li>
     </ul>
@@ -24,7 +24,9 @@
   <!-- if no menu label is present, present menu-items as a 
        horizontal row of buttons -->
   <div class="btn-group" ng-if="!menuLabel">
-    <button class="btn btn-default eg-grid-menu-item"
+    <button ng-if="!item.hidden()"
+      class="btn btn-default eg-grid-menu-item"
+      ng-disabled="item.disabled()"
       ng-repeat="item in menuItems"
       ng-click="item.handler(item, item.handlerData)">
       {{item.label}}
index b558358..09772b9 100644 (file)
@@ -30,6 +30,8 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
 
     var hold_ids = [];
     var holds = [];
+    var clear_mode = false;
+    $scope.gridControls = {};
 
     function fetch_holds(offset, count) {
         var ids = hold_ids.slice(offset, offset + count);
@@ -61,10 +63,8 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
         holds = [];
 
         var method = 'open-ils.circ.captured_holds.id_list.on_shelf.retrieve.authoritative.atomic';
-        /*
-        if ($scope.holds_display == 'alt')
-            method = 'open-ils.circ.holds.canceled.id_list.retrieve.authoritative';
-        */
+        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,
@@ -114,6 +114,138 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
         $scope.detail_hold_record_id = hold_data.mvr.doc_id();
     }
 
+    // manage active vs. clearable holds display
+    var clearing = false; // true if actively clearing holds (below)
+    $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.disable_clear = function() { return clearing || !clear_mode }
+
+    // action handlers
+    // TODO: These are copied directly from patron/holds.js.  
+    // Consider refactoring / consolidating.
+    $scope.cancel_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(refresh_page);
+    }
+
+    // jump to circ list for either 1) the targeted copy or
+    // 2) the hold target copy for copy-level holds
+    $scope.show_recent_circs = function(items) {
+        if (items.length && (copy = items[0].copy)) {
+            var url = $location.path(
+                '/cat/item/' + copy.id() + '/circ_list').absUrl();
+            $window.open(url, '_blank').focus();
+        }
+    }
+
+    function generic_update(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(refresh_page);
+    }
+
+    $scope.set_copy_quality = function(items) {
+        generic_update(items, 'set_copy_quality'); }
+    $scope.edit_pickup_lib = function(items) {
+        generic_update(items, 'edit_pickup_lib'); }
+    $scope.edit_notify_prefs = function(items) {
+        generic_update(items, 'edit_notify_prefs'); }
+    $scope.edit_dates = function(items) {
+        generic_update(items, 'edit_dates'); }
+    $scope.suspend = function(items) {
+        generic_update(items, 'suspend_holds'); }
+    $scope.activate = function(items) {
+        generic_update(items, 'activate_holds'); }
+    $scope.set_top_of_queue = function(items) {
+        generic_update(items, 'set_top_of_queue'); }
+    $scope.clear_top_of_queue = function(items) {
+        generic_update(items, 'clear_top_of_queue'); }
+    $scope.transfer_to_marked_title = function(items) {
+        generic_update(items, 'transfer_to_marked_title'); }
+
+    $scope.mark_damaged = function(items) {
+        var copy_ids = items
+            .filter(function(item) { return Boolean(item.copy) })
+            .map(function(item) { return item.copy.id() });
+        if (copy_ids.length) 
+            egCirc.mark_damaged(copy_ids).then(refresh_page);
+    }
+
+    $scope.mark_missing = function(items) {
+        var copy_ids = items
+            .filter(function(item) { return Boolean(item.copy) })
+            .map(function(item) { return item.copy.id() });
+        if (copy_ids.length) 
+            egCirc.mark_missing(copy_ids).then(refresh_page);
+    }
+
+    $scope.retarget = function(items) {
+        var hold_ids = items.map(function(item) { return item.hold.id() });
+        egHolds.retarget(hold_ids).then(refresh_page);
+    }
+
+    $scope.clear_holds = function() {
+        clearing = true;
+        $scope.clear_progress = { max : 0, value : 0 };
+
+        // we want to see all processed holds, so (effectively) remove
+        // the grid limit.
+        $scope.gridControls.setLimit(1000); 
+
+        // initiate clear shelf and grab cache key
+        egCore.net.request(
+            'open-ils.circ',
+            'open-ils.circ.hold.clear_shelf.process',
+            egCore.auth.token(), $scope.pickup_ou.id()
+
+        // request responses from the clear shelf cache
+        ).then(function(resp) {
+            console.debug('clear holds cache key is ' + resp.cache_key);
+            return egCore.net.request(
+                'open-ils.circ',
+                'open-ils.circ.hold.clear_shelf.get_cache',
+                egCore.auth.token(), resp.cache_key
+            )
+
+        // with each clear-shelf response, update the progress meter,
+        // hide it when done.
+        }).then(
+            function() { 
+                clearing = false;
+            },
+            null,
+            function(resp) {
+                console.debug('clear shelf said: ' + js2JSON(resp));
+                if (!angular.isArray(resp)) resp = [resp];
+                angular.forEach(resp, function(info) {
+                    if (info.maximum) 
+                        $scope.clear_progress.max = info.maximum;
+                    if (info.progress)
+                        $scope.clear_progress.value = info.progress;
+
+                    if (info.action) {
+                        var grid_item = 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];
+                        }
+                    }
+                });
+            }
+        );
+    }
+
+
     refresh_page();
 
 }]);
index 39ec836..9933c42 100644 (file)
@@ -107,32 +107,23 @@ function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,
     }
 
     $scope.set_copy_quality = function(items) {
-        generic_update(items, 'set_copy_quality');
-    }
+        generic_update(items, 'set_copy_quality'); }
     $scope.edit_pickup_lib = function(items) {
-        generic_update(items, 'edit_pickup_lib');
-    }
+        generic_update(items, 'edit_pickup_lib'); }
     $scope.edit_notify_prefs = function(items) {
-        generic_update(items, 'edit_notify_prefs');
-    }
+        generic_update(items, 'edit_notify_prefs'); }
     $scope.edit_dates = function(items) {
-        generic_update(items, 'edit_dates');
-    }
+        generic_update(items, 'edit_dates'); }
     $scope.suspend = function(items) {
-        generic_update(items, 'suspend_holds');
-    }
+        generic_update(items, 'suspend_holds'); }
     $scope.activate = function(items) {
-        generic_update(items, 'activate_holds');
-    }
+        generic_update(items, 'activate_holds'); }
     $scope.set_top_of_queue = function(items) {
-        generic_update(items, 'set_top_of_queue');
-    }
+        generic_update(items, 'set_top_of_queue'); }
     $scope.clear_top_of_queue = function(items) {
-        generic_update(items, 'clear_top_of_queue');
-    }
+        generic_update(items, 'clear_top_of_queue'); }
     $scope.transfer_to_marked_title = function(items) {
-        generic_update(items, 'transfer_to_marked_title');
-    }
+        generic_update(items, 'transfer_to_marked_title'); }
 
     $scope.mark_damaged = function(items) {
         var copy_ids = items
index 416afb6..a554e93 100644 (file)
@@ -216,6 +216,19 @@ angular.module('egGridMod',
                     grid.collect();
                 }
 
+                controls.setLimit = function(limit) {
+                    grid.limit = limit;
+                }
+                controls.getLimit = function() {
+                    return grid.limit;
+                }
+                controls.setOffset = function(offset) {
+                    grid.offset = offset;
+                }
+                controls.getOffset = function() {
+                    return grid.offset;
+                }
+
                 grid.dataProvider.refresh = controls.refresh;
                 grid.controls = controls;
             }
@@ -1450,13 +1463,17 @@ angular.module('egGridMod',
             label : '@',  
             handler : '=', // onclick handler function
             divider : '=', // if true, show a divider only
-            handlerData : '=' // if set, passed as second argument to handler
+            handlerData : '=', // if set, passed as second argument to handler
+            disabled : '=', // function
+            hidden : '=' // function
         },
         link : function(scope, element, attrs, egGridCtrl) {
             egGridCtrl.addMenuItem({
                 label : scope.label,
                 handler : scope.handler,
                 divider : scope.divider,
+                disabled : scope.disabled,
+                hidden : scope.hidden,
                 handlerData : scope.handlerData
             });
             scope.$destroy();