patron holds; view canceled; cancel
authorBill Erickson <berick@esilibrary.com>
Wed, 2 Jul 2014 20:12:20 +0000 (16:12 -0400)
committerBill Erickson <berick@esilibrary.com>
Wed, 2 Jul 2014 20:12:20 +0000 (16:12 -0400)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Open-ILS/src/templates/staff/circ/patron/index.tt2
Open-ILS/src/templates/staff/circ/patron/t_holds.tt2
Open-ILS/src/templates/staff/circ/share/hold_strings.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/circ/share/t_cancel_hold_dialog.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/circ/patron/app.js
Open-ILS/web/js/ui/default/staff/circ/patron/holds.js
Open-ILS/web/js/ui/default/staff/circ/patron/items_out.js
Open-ILS/web/js/ui/default/staff/circ/services/holds.js [new file with mode: 0644]

index 54258a0..1956411 100644 (file)
@@ -12,6 +12,8 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/billing.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
 [% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/holds.js"></script>
+[% INCLUDE 'staff/circ/share/hold_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/patron/app.js"></script>
 
 <!-- load the rest on demand? -->
@@ -43,7 +45,6 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
 
 [% END %]
 
-
 <div class="row">
   <div class="col-md-3">
     <div ng-show="patron()">
@@ -55,15 +56,15 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
                 '{{patron().first_given_name()}}',
                 '{{patron().second_given_name()}}') %]
           </div>
-          <div>
-            <a href ng-click="collapsePatronSummary=true" 
+          <div ng-show="tab != 'search'">
+            <a href ng-click="toggle_expand_summary()"
               title="[% l('Collapse Patron Summary Display') %]"
-              ng-hide="collapsePatronSummary">
+              ng-hide="collapse_summary()">
               <span class="glyphicon glyphicon-resize-small"></span>
             </a>
-            <a href ng-click="collapsePatronSummary=false" 
+            <a href ng-click="toggle_expand_summary()"
               title="[% l('Expand Patron Summary Display') %]"
-              ng-show="collapsePatronSummary">
+              ng-show="collapse_summary()">
               <span class="glyphicon glyphicon-resize-full"></span>
             </a>
           </div>
@@ -147,10 +148,10 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
 </div><!-- row -->
 
 <div class="row">
-  <div class="col-md-3" ng-hide="collapsePatronSummary">
+  <div class="col-md-3" ng-hide="collapse_summary()">
     [% INCLUDE 'staff/circ/patron/t_summary.tt2' %]
   </div>
-  <div ng-class="{'col-md-12' : collapsePatronSummary,'col-md-9' : !collapsePatronSummary}">
+  <div ng-class="{'col-md-12' : collapse_summary(),'col-md-9' : !collapse_summary()}">
     <div class="tab-content">
       <div class="tab-pane active">
         <div ng-view></div>
index 983f566..cce13f5 100644 (file)
@@ -1,19 +1,41 @@
 <!-- patron holds list -->
 
+<ul class="nav nav-tabs">
+  <li ng-class="{active : holds_display == 'main'}">
+    <a href ng-click="show_main_list()">[% l('Open Hold Requests') %]</a>
+  </li>
+  <li ng-class="{active : holds_display == 'alt'}">
+    <a href ng-click="show_alt_list()">[% l('Recently Canceled Holds') %]</a>
+  </li>
+</ul>
+
+<div class="pad-vert"></div>
+
 <eg-grid
   id-field="id"
-  features="-display,-sort,-multisort"
-  main-label="[% l('Items On Hold') %]"
+  features="-sort,-multisort"
   items-provider="gridDataProvider"
   persist-key="circ.patron.holds">
-  <eg-grid-field label="[% ('Hold ID') %]" path='hold.id' visible></eg-grid-field>
-  <eg-grid-field label="[% ('Current Copy') %]" path='hold.current_copy.barcode' visible></eg-grid-field>
-  <eg-grid-field label="[% l('Request Date') %]" path='hold.request_time' visible></eg-grid-field>
-  <eg-grid-field label="[% l('Capture Date') %]" path='hold.capture_time' visible></eg-grid-field>
-  <eg-grid-field label="[% l('Available Date') %]" path='hold.shelf_time' visible></eg-grid-field>
-  <eg-grid-field label="[% l('Hold Type') %]" path='hold.hold_type' visible></eg-grid-field>
-  <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pickup_lib.shortname' visible></eg-grid-field>
-  <eg-grid-field label="[% l('Title') %]" path='mvr.title' visible></eg-grid-field>
-  <eg-grid-field label="[% l('Author') %]" path='mvr.author' visible></eg-grid-field>
+
+  <eg-grid-action handler="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'></eg-grid-field>
+  <eg-grid-field label="[% l('Request Date') %]" path='hold.request_time'></eg-grid-field>
+  <eg-grid-field label="[% l('Capture Date') %]" path='hold.capture_time'></eg-grid-field>
+  <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('Title') %]" path='mvr.title'></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('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 path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
+  <eg-grid-field path='mvr.*' parent-idl-class="ahr" hidden></eg-grid-field>
 </eg-grid>
 
diff --git a/Open-ILS/src/templates/staff/circ/share/hold_strings.tt2 b/Open-ILS/src/templates/staff/circ/share/hold_strings.tt2
new file mode 100644 (file)
index 0000000..41718f8
--- /dev/null
@@ -0,0 +1,16 @@
+[%# Strings for circ/services/circ.js %]
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+s.HOLD_STATUS_1 = "[% l('Waiting for Copy') %]";
+s.HOLD_STATUS_2 = "[% l('Waiting for Capture') %]";
+s.HOLD_STATUS_3 = "[% l('In Transit') %]";
+s.HOLD_STATUS_4 = "[% l('Ready for Pickup') %]";
+s.HOLD_STATUS_5 = "[% l('Hold Shelf Delay') %]";
+s.HOLD_STATUS_6 = "[% l('Canceled') %]";
+s.HOLD_STATUS_7 = "[% l('Suspended') %]";
+s.HOLD_STATUS_8 = "[% l('Wrong Shelf') %]";
+}]);
+</script>
+
+
diff --git a/Open-ILS/src/templates/staff/circ/share/t_cancel_hold_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_cancel_hold_dialog.tt2
new file mode 100644 (file)
index 0000000..81417f2
--- /dev/null
@@ -0,0 +1,38 @@
+<form ng-submit="ok()" role="form" class="form-horizontal">
+  <div class="modal-content">
+    <div class="modal-header">
+      <button type="button" class="close" 
+        ng-click="cancel()" aria-hidden="true">&times;</button>
+      <h4 class="modal-title">
+        [% l('Cancel [_1] Hold(s)', '{{args.num_holds}}') %]
+      </h4>
+    </div>
+    <div class="modal-body">
+      <div class="form-group">
+        <label for="hold-cancel-reason" class="control-label col-md-4">
+          [% l('Cancel Reason:') %]
+        </label>
+        <div class="col-md-8">
+          <select class="form-control" id="hold-cancel-reason"
+            ng-model="args.cancel_reason"
+            ng-options="reason.label() for reason in args.cancel_reasons">
+          </select>
+        </div>
+      </div>
+      <div class="form-group">
+        <label for="hold-cancel-note" class="control-label col-md-4">
+          [% l('Note:') %]
+        </label>
+        <div class="col-md-8">
+          <textarea rows="3" class="form-control" placeholder="[% l('Note...') %]"
+            id="hold-cancel-note" ng-model="args.note"></textarea>
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <input type="submit" class="btn btn-success" value="[% l('Cancel Hold') %]"/>
+      <button class="btn btn-warning" ng-click="cancel($event)">[% l('Exit') %]</button>
+    </div>
+  </div>
+</div>
+
index 2b04291..ad53b2a 100644 (file)
@@ -539,6 +539,25 @@ function($scope,  $q,  $location , $filter,  egCore,  egUser,  patronSvc) {
             }
         });
     }
+
+    $scope.toggle_expand_summary = function() {
+        if ($scope.collapsePatronSummary) {
+            $scope.collapsePatronSummary = false;
+            egCore.hatch.removeItem('eg.circ.patron.summary.collapse');
+        } else {
+            $scope.collapsePatronSummary = true;
+            egCore.hatch.setItem('eg.circ.patron.summary.collapse', true);
+        }
+    }
+    
+    // always expand the patron summary in the search UI, regardless
+    // of stored preference.
+    $scope.collapse_summary = function() {
+        return $scope.tab != 'search' && $scope.collapsePatronSummary;
+    }
+
+    egCore.hatch.getItem('eg.circ.patron.summary.collapse')
+    .then(function(val) {$scope.collapsePatronSummary = Boolean(val)});
 }])
 
 .controller('PatronBarcodeSearchCtrl',
index 78031e1..da66ef8 100644 (file)
@@ -4,9 +4,33 @@
 
 angular.module('egPatronApp').controller('PatronHoldsCtrl',
 
-       ['$scope','$q','$routeParams','egCore','egUser','patronSvc','egGridDataProvider',
-function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,  egGridDataProvider) {
+       ['$scope','$q','$routeParams','egCore','egUser','patronSvc','egGridDataProvider','egHolds',
+function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,  egGridDataProvider , egHolds) {
     $scope.initTab('holds', $routeParams.id);
+    $scope.holds_display = 'main';
+
+    function refresh_all() {
+        patronSvc.refreshPrimary();
+        patronSvc.holds = [];
+        patronSvc.hold_ids = [];
+        provider.refresh() 
+    }
+
+    $scope.show_main_list = function() {
+        // don't need a full reset_page() to swap tabs
+        $scope.holds_display = 'main';
+        patronSvc.holds = [];
+        patronSvc.hold_ids = [];
+        provider.refresh();
+    }
+
+    $scope.show_alt_list = function() {
+        // don't need a full reset_page() to swap tabs
+        $scope.holds_display = 'alt';
+        patronSvc.holds = [];
+        patronSvc.hold_ids = [];
+        provider.refresh();
+    }
 
     var provider = egGridDataProvider.instance({});
     $scope.gridDataProvider = provider;
@@ -22,6 +46,11 @@ function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,  egGridDataPr
 
         ).then(null, null, function(hold_data) {
             $scope.loading = false;
+
+            hold_data.status_string = 
+                egCore.strings['HOLD_STATUS_' + hold_data.status] 
+                || hold_data.status;
+
             var hold = hold_data.hold;
             hold_data.id = hold.id();
             hold.pickup_lib(egCore.org.get(hold.pickup_lib())); // flesh
@@ -34,9 +63,7 @@ function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,  egGridDataPr
 
         // see if we have the requested range cached
         if (patronSvc.holds[offset]) {
-            return provider.arrayNotifier(
-                patronSvc.holds, offset, count
-            );
+            return provider.arrayNotifier(patronSvc.holds, offset, count);
         }
 
         // see if we have the holds IDs for this range already loaded
@@ -47,22 +74,32 @@ function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,  egGridDataPr
         var deferred = $q.defer();
         patronSvc.hold_ids = [];
 
+        var method = 'open-ils.circ.holds.id_list.retrieve.authoritative';
+        if ($scope.holds_display == 'alt')
+            method = 'open-ils.circ.holds.canceled.id_list.retrieve.authoritative';
+
         egCore.net.request(
-            'open-ils.circ',
-            'open-ils.circ.holds.id_list.retrieve.authoritative',
+            'open-ils.circ', method,
             egCore.auth.token(), $scope.patron_id
 
         ).then(function(hold_ids) {
-            console.log('fetched hold ids ' + hold_ids);
-            
             if (!hold_ids.length) { deferred.resolve(); return; }
 
             patronSvc.hold_ids = hold_ids;
-            fetchHolds(offset, count).then(null, null, deferred.notify);
+            fetchHolds(offset, count)
+            .then(deferred.resolve, null, deferred.notify);
         });
 
         return deferred.promise;
     }
+
+    $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_all);
+    }
 }])
 
 
index 297f2d2..fa68501 100644 (file)
@@ -75,6 +75,7 @@ function($scope,  $q,  $routeParams,  egCore , egUser,  patronSvc ,
     $scope.gridDataProvider = provider;
 
     function fetch_circs(id_list, offset, count) {
+        if (!id_list.length) return $q.when();
 
         // fetch the lot of circs and stream the results back via notify
         return egCore.pcrud.search('circ', {id : id_list},
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
new file mode 100644 (file)
index 0000000..e9535ef
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * Holds, yo
+ */
+
+angular.module('egCoreMod')
+
+.factory('egHolds',
+
+       ['$modal','$q','egCore','egAlertDialog','egConfirmDialog',
+function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
+
+    var service = {};
+
+    service.cancel_holds = function(hold_ids) {
+       
+        return $modal.open({
+            templateUrl : './circ/share/t_cancel_hold_dialog',
+            controller : 
+                ['$scope', '$modalInstance', 'cancel_reasons',
+                function($scope, $modalInstance, cancel_reasons) {
+                    $scope.args = {
+                        cancel_reasons : cancel_reasons,
+                        num_holds : hold_ids.length
+                    };
+                    
+                    $scope.cancel = function($event) {
+                        $modalInstance.dismiss();
+                        $event.preventDefault();
+                    }
+
+                    $scope.ok = function() {
+
+                        function cancel_one() {
+                            var hold_id = hold_ids.pop();
+                            if (!hold_id) {
+                                $modalInstance.close();
+                                return;
+                            }
+                            egCore.net.request(
+                                'open-ils.circ', 'open-ils.circ.hold.cancel',
+                                egCore.auth.token(), hold_id,
+                                $scope.args.cancel_reason.id(), 
+                                $scope.args.note
+                            ).then(function(resp) {
+                                if (evt = egCore.evt.parse(resp)) {
+                                    console.error('unable to cancel hold: ' 
+                                        + evt.toString());
+                                }
+                                cancel_one();
+                            });
+                        }
+
+                        cancel_one();
+                    }
+                }
+            ],
+            resolve : {
+                cancel_reasons : function() {
+                    return service.get_cancel_reasons();
+                }
+            }
+        }).result;
+    }
+
+    service.get_cancel_reasons = function() {
+        if (egCore.env.ahrcc) return $q.when(egCore.env.ahrcc.list);
+        return egCore.pcrud.retrieveAll('ahrcc', {}, {atomic : true})
+        .then(function(list) { 
+            egCore.env.absorbList(list, 'ahrcc') 
+            return list;
+        });
+    }
+
+    return service;
+}])
+