group member details UI; initial
authorBill Erickson <berick@esilibrary.com>
Mon, 9 Jun 2014 20:52:44 +0000 (16:52 -0400)
committerBill Erickson <berick@esilibrary.com>
Mon, 9 Jun 2014 20:52:44 +0000 (16:52 -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_add_to_group_dialog.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/circ/patron/t_group.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
Open-ILS/web/js/ui/default/staff/circ/patron/app.js
Open-ILS/web/js/ui/default/staff/services/grid.js

index 853304f..c08791d 100644 (file)
@@ -32,6 +32,7 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
   s.CONFIRM_REFUND_PAYMENT = 
     "[% |l('{{xactIds}}') -%]Are you sure you would like to refund excess payment on bills [_1]?  This action will simply put the amount in the Payment Pending column as a negative value.  You must still select Apply Payment!  Certain types of payments may not be refunded.  The refund may be applied to checked transactions that follow the refunded transaction.[% END %]";
   s.EDIT_BILL_PAY_NOTE = "[% l('Enter new note for #[_1]:','{{ids}}') %]";
+  s.GROUP_ADD_USER = "[% l('Enter the patron barcode') %]";
 }]);
 </script>
 
@@ -102,6 +103,11 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
             </a>
           </li>
           <li>
+            <a href="./circ/patron/{{patron().id()}}/group">
+              [% l('Group Member Details') %]
+            </a>
+          </li>
+          <li>
             <a href="./circ/patron/{{patron().id()}}/credentials">
               [% l('Test Password') %]
             </a>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_add_to_group_dialog.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_add_to_group_dialog.tt2
new file mode 100644 (file)
index 0000000..caa1d84
--- /dev/null
@@ -0,0 +1,23 @@
+<div>
+  <div class="modal-header">
+    <button type="button" class="close" ng-click="cancel()" 
+      aria-hidden="true">&times;</button>
+    <h4 class="modal-title">[% l('Move user into this group?') %]</h4>
+  </div>
+  <div class="modal-body">
+    <a href="./circ/patron/{{user.id()}}/checkout" target="_self">
+      [% 
+        l('[_1], [_2] [_3] : [_4]', 
+          '{{user.family_name()}}',
+          '{{user.first_given_name()}}',
+          '{{user.second_given_name()}}',
+          '{{user.card().barcode()}}') 
+      %]
+    </a>
+  </div>
+  <div class="modal-footer">
+    <button class="btn btn-primary" ng-click="ok()">[% l('Move User') %]</button>
+    <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+  </div>
+</div>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_group.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_group.tt2
new file mode 100644 (file)
index 0000000..9a74166
--- /dev/null
@@ -0,0 +1,39 @@
+
+<div class="strong-text-2">[% l('Group Member Details') %]</div>
+<div class="pad-vert"></div>
+<eg-grid
+  idl-class="au"
+  query="gridQuery"
+  sort="gridSort"
+  revision="gridRevision"
+  grid-controls="gridControls"
+  menu-label="[% l('Group Actions') %]">
+
+  <eg-grid-menu-item handler="moveToGroup"
+    label="[% l('Move Another Patron To This Group') %]"></eg-grid-menu-item>
+
+  <eg-grid-action label="[% l('Remove Selected From Group') %]" 
+    handler="removeFromGroup"></eg-grid-action>
+
+  <eg-grid-field path="active"></eg-grid-field>
+  <eg-grid-field path="barred"></eg-grid-field>
+  <eg-grid-field path="dob"></eg-grid-field>
+  <eg-grid-field path="family_name"></eg-grid-field>
+  <eg-grid-field path="first_given_name"></eg-grid-field>
+  <eg-grid-field path="master_account"></eg-grid-field>
+  <eg-grid-field path="second_given_name"></eg-grid-field>
+  <eg-grid-field path="stats.fines.balance_owed" nonsortable label="[% l('Balance Owed') %]"></eg-grid-field>
+  <eg-grid-field path="stats.checkouts.count" nonsortable label="[% l('Items Out') %]"></eg-grid-field>
+  <eg-grid-field path="stats.checkouts.overdue" nonsortable label="[% l('Items Overdue') %]"></eg-grid-field>
+
+  <!-- needed for query, sorting -->
+  <eg-grid-field path="id" hidden required></eg-grid-field>
+  <eg-grid-field path="usrgroup" hidden required></eg-grid-field>
+  <eg-grid-field path="deleted" hidden required></eg-grid-field>
+  <eg-grid-field path="create_date" hidden required></eg-grid-field>
+
+  <!--
+  <eg-grid-field path=".*"></eg-grid-field>
+  -->
+
+</eg-grid>
index 50ba849..cbbc366 100644 (file)
         {{patron_stats().fines.balance_owed | currency}}
       </div>
     </div>
+    <div class="row"
+      ng-class="{'patron-summary-alert' : patron_stats().fines.group_balance_owed}">
+      <div class="col-md-5">[% l('Group Fines') %]</div>
+      <div class="col-md-7">
+        {{patron_stats().fines.group_balance_owed | currency}}
+      </div>
+    </div>
     <div class="row">
       <div class="col-md-5">[% l('Items Out') %]</div>
       <div class="col-md-7">{{patron_stats().checkouts.out}}</div>
index 1fd4db7..c312212 100644 (file)
@@ -136,6 +136,12 @@ angular.module('egPatronApp', ['ngRoute', 'ui.bootstrap',
         resolve : resolver
     });
 
+    $routeProvider.when('/circ/patron/:id/group', {
+        templateUrl: './circ/patron/t_group',
+        controller: 'PatronGroupCtrl',
+        resolve : resolver
+    });
+
     $routeProvider.otherwise({redirectTo : '/circ/patron/search'});
 })
 
@@ -310,13 +316,25 @@ function($q , $timeout , $location , egCore,  egUser , $locale) {
         return deferred.promise;
     }
 
+    service.fetchGroupFines = function() {
+        return egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.usergroup.members.balance_owed',
+            egCore.auth.token(), service.current.usrgroup()
+        ).then(function(list) {
+            var total = 0;
+            angular.forEach(list, function(u) { 
+                total += 100 * Number(u.balance_owed)
+            });
+            service.patron_stats.fines.group_balance_owed = total / 100;
+        });
+    }
 
-    // grab additional circ info
-    service.fetchUserStats = function() {
+    service.getUserStats = function(id) {
         return egCore.net.request(
             'open-ils.actor',
             'open-ils.actor.user.opac.vital_stats.authoritative', 
-            egCore.auth.token(), service.current.id()
+            egCore.auth.token(), id
         ).then(
             function(stats) {
                 // force numeric to ensure correct boolean handling in templates
@@ -325,9 +343,19 @@ function($q , $timeout , $location , egCore,  egUser , $locale) {
                 stats.checkouts.claims_returned = 
                     Number(stats.checkouts.claims_returned);
                 stats.checkouts.lost = Number(stats.checkouts.lost);
-                service.patron_stats = stats
+                return stats;
             }
-        )
+        );
+    }
+
+
+    // grab additional circ info
+    service.fetchUserStats = function() {
+        return service.getUserStats(service.current.id())
+        .then(function(stats) {
+            service.patron_stats = stats
+            return service.fetchGroupFines();
+        });
     }
 
     // Avoid using parens [e.g. (1.23)] to indicate negative numbers, 
@@ -1072,3 +1100,113 @@ function($scope,  $routeParams , $location , egCore , patronSvc , $modal) {
     refreshPage();
 }])
 
+.controller('PatronGroupCtrl',
+       ['$scope','$routeParams','$q','$location','egCore',
+        'patronSvc','$modal','egPromptDialog','egConfirmDialog',
+function($scope,  $routeParams , $q , $location , egCore ,
+         patronSvc , $modal , egPromptDialog , egConfirmDialog) {
+
+    var usr_id = $routeParams.id;
+
+    var statsCache = {};
+    var gridQuery = {};
+
+    $scope.gridQuery = function() {return gridQuery}
+    $scope.gridRevision = 0;
+    $scope.gridSort = ['create_date'];
+    $scope.gridControls = {
+        itemRetrieved : function(item) {
+
+            if (statsCache[item.id]) {
+                item.stats = statsCache[item.id];
+            } else {
+                // user retrieved, flesh their stats
+                patronSvc.getUserStats(item.id).then(function(stats) {
+                    item.stats = statsCache[item.id] = stats;
+                });
+            }
+        }
+    }
+
+    $scope.initTab('other', $routeParams.id).then(function() {
+        // let initTab() fetch the user first so we can know the usrgroup
+        gridQuery = {
+            usrgroup : patronSvc.current.usrgroup(),
+            deleted : 'f'
+        }
+    });
+
+    $scope.removeFromGroup = function(selected) {
+        var promises = [];
+        angular.forEach(selected, function(user) {
+            console.debug('removing user ' + user.id + ' from group');
+
+            promises.push(
+                egCore.net.request(
+                    'open-ils.actor',
+                    'open-ils.actor.usergroup.new',
+                    egCore.auth.token(), user.id, true
+                )
+            );
+        });
+
+        $q.all(promises).then(function() {$scope.gridRevision++});
+    }
+
+    function addUserToGroup(user) {
+        user.usrgroup(patronSvc.current.usrgroup());
+        user.ischanged(true);
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.patron.update',
+            egCore.auth.token(), user
+
+        ).then(function() {
+            $scope.gridRevision++;
+        });
+    }
+
+    function showMoveToGroupConfirm(barcode) {
+
+        // find the user
+        egCore.pcrud.search('ac', {barcode : barcode})
+
+        // fetch the fleshed user
+        .then(function(card) {
+
+            if (!card) return; // TODO: warn user
+
+            egCore.pcrud.retrieve('au', card.usr())
+            .then(function(user) {
+                user.card(card);
+                $modal.open({
+                    templateUrl: './circ/patron/t_add_to_group_dialog',
+                    controller: [
+                                '$scope','$modalInstance',
+                        function($scope , $modalInstance) {
+                            $scope.user = user;
+                            $scope.ok = 
+                                function(count) { $modalInstance.close() }
+                            $scope.cancel = 
+                                function () { $modalInstance.dismiss() }
+                        }
+                    ]
+                }).result.then(function() {
+                    addUserToGroup(user);
+                });
+            });
+        });
+    }
+
+    $scope.moveToGroup = function() {
+        egPromptDialog.open(
+            egCore.strings.GROUP_ADD_USER, '',
+            {ok : function(value) {
+                if (value) 
+                    showMoveToGroupConfirm(value);
+            }}
+        );
+    }
+
+}])
+
index 82f5a30..e47181a 100644 (file)
@@ -1143,13 +1143,8 @@ angular.module('egGridMod',
                     idl_field.datatype == 'link' || 
                     idl_field.datatype == 'org_unit')) {
                     class_obj = egCore.idl.classes[idl_field['class']];
-                } else {
-                    if (path_idx < (path_parts.length - 1)) {
-                        // we ran out of classes to hop through before
-                        // we ran out of path components
-                        console.error("egGrid: invalid IDL path: " + path);
-                    }
                 }
+                // else, path is not in the IDL, which is fine
             }
 
             if (!idl_field) return null;
@@ -1224,6 +1219,20 @@ angular.module('egGridMod',
             gridData.select = function(items) {
             }
 
+            // attempts a flat field lookup first.  If the column is not
+            // found on the top-level object, attempts a nested lookup
+            gridData.itemFieldValue = function(item, column) {
+                if (column.name in item) {
+                    if (typeof item[column.name] == 'function') {
+                        return item[column.name]();
+                    } else {
+                        return item[column.name];
+                    }
+                } else {
+                    return gridData.nestedItemFieldValue(item, column);
+                }
+            }
+
             gridData.flatItemFieldValue = function(item, column) {
                 return item[column.name];
             }
@@ -1320,7 +1329,7 @@ angular.module('egGridMod',
                         }
                     );
                 }
-                provider.itemFieldValue = provider.flatItemFieldValue;
+                //provider.itemFieldValue = provider.flatItemFieldValue;
                 return provider;
             }
         };