lp1744756 Profile Tree Display Entry Admin UI
authorKyle Huckins <khuckins@catalyte.io>
Thu, 15 Mar 2018 18:54:13 +0000 (18:54 +0000)
committerKathy Lussier <klussier@masslnc.org>
Thu, 30 Aug 2018 16:09:02 +0000 (12:09 -0400)
- Flesh out permission.group_tree_display_entries table.
- Create pgtde IDL class corresponding to new permission.group_tree_display_entries
table.
- Admin UI for Permission Group Tree Entries capable of viewing, adding, removing,
or changing position of entries within tree based on OU.
- Save functionality to persist any changes made.
- Persist changes in positions of permission group display
entries in Admin UI with the Profile Selector in the patron
edit interface.
- Make profile selector use original functionality if there are no
display entries.

Signed-off-by: Kyle Huckins <khuckins@catalyte.io>
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/sql/Pg/006.schema.permissions.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/local/permission/index.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/local/permission/t_grp_tree_display_entry.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/local/permission/t_pgtde_add_dialog.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/local/t_splash.tt2
Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
Open-ILS/web/js/ui/default/staff/admin/local/permission/app.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js

index 2a7005f..b12a043 100644 (file)
@@ -7644,6 +7644,31 @@ SELECT  usr,
             </actions>
         </permacrud>
        </class>
+       <class id="pgtde" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="permission::grp_tree_display_entry" oils_persist:tablename="permission.grp_tree_display_entry" reporter:label="Permission Group Tree Display Entry">
+               <fields oils_persist:primary="id" oils_persist:sequence="permission.grp_tree_id_seq">
+                       <field reporter:label="Entry ID" name="id" reporter:selector="name" reporter:datatype="id"/>
+                       <field reporter:label="Group ID" name="grp" reporter:datatype="link" oils_persist:i18n="true"/>
+                       <field reporter:label="Parent Group" name="parent" reporter:datatype="link"/>
+                       <field reporter:label="Org Unit" name="org" reporter:datatype="link"/>
+            <field reporter:label="Position" name="position" reporter:datatype="int"/>
+            <field reporter:label="Disabled" name="disabled" reporter:datatype="bool"/>
+                       <field reporter:label="Child Entries" name="children" oils_persist:virtual="true" reporter:datatype="link"/>
+               </fields>
+               <links>
+                       <link field="parent" reltype="has_a" key="id" map="" class="pgtde"/>
+                       <link field="grp" reltype="has_a" key="id" map="" class="pgt"/>
+                       <link field="org" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="children" reltype="has_many" key="parent" map="" class="pgtde"/>
+               </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="CREATE_PERM" global_required="true"/>
+                <retrieve permission="STAFF_LOGIN" global_required="true"/>
+                <update permission="UPDATE_PERM" global_required="true"/>
+                <delete permission="DELETE_PERM" global_required="true"/>
+            </actions>
+        </permacrud>
+       </class>
        <class id="asva" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::survey_answer" oils_persist:tablename="action.survey_answer" reporter:label="Survey Answer">
                <fields oils_persist:primary="id" oils_persist:sequence="action.survey_answer_id_seq">
                        <field reporter:label="Responses using this Answer" name="responses" oils_persist:virtual="true" reporter:datatype="link"/>
index df154fb..3e1bbf2 100644 (file)
@@ -617,6 +617,20 @@ RETURNS SETOF INTEGER AS $$
 SELECT DISTINCT * FROM permission.usr_has_perm_at_all_nd( $1, $2 );
 $$ LANGUAGE 'sql' ROWS 1;
 
+CREATE TABLE permission.grp_tree_display_entry (
+    id      SERIAL PRIMARY KEY,
+    position INTEGER NOT NULL,
+    org     INTEGER NOT NULL REFERENCES actor.org_unit (id)
+            DEFERRABLE INITIALLY DEFERRED,
+    grp     INTEGER NOT NULL REFERENCES permission.grp_tree (id)
+            DEFERRABLE INITIALLY DEFERRED,
+    disabled BOOLEAN NOT NULL DEFAULT FALSE,
+    CONSTRAINT pgtde_once_per_org UNIQUE (org, grp)
+);
+
+ALTER TABLE permission.grp_tree_display_entry
+    ADD COLUMN parent integer REFERENCES permission.grp_tree_display_entry (id)
+            DEFERRABLE INITIALLY DEFERRED;
 
 COMMIT;
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql
new file mode 100644 (file)
index 0000000..2cbbd1f
--- /dev/null
@@ -0,0 +1,19 @@
+BEGIN;
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+CREATE TABLE permission.grp_tree_display_entry (
+    id      SERIAL PRIMARY KEY,
+    position INTEGER NOT NULL,
+    org     INTEGER NOT NULL REFERENCES actor.org_unit (id)
+            DEFERRABLE INITIALLY DEFERRED,
+    grp     INTEGER NOT NULL REFERENCES permission.grp_tree (id)
+            DEFERRABLE INITIALLY DEFERRED,
+    disabled BOOLEAN NOT NULL DEFAULT FALSE,
+    CONSTRAINT pgtde_once_per_org UNIQUE (org, grp)
+);
+
+ALTER TABLE permission.grp_tree_display_entry
+    ADD COLUMN parent integer REFERENCES permission.grp_tree_display_entry (id)
+            DEFERRABLE INITIALLY DEFERRED;
+            
+COMMIT;
\ No newline at end of file
diff --git a/Open-ILS/src/templates/staff/admin/local/permission/index.tt2 b/Open-ILS/src/templates/staff/admin/local/permission/index.tt2
new file mode 100644 (file)
index 0000000..6b63ed5
--- /dev/null
@@ -0,0 +1,26 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Permission Groups");
+  ctx.page_app = "egAdminPermGrpTreeApp";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/local/permission/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+  s.ROOT_NODE_NAME = '[% l('Display Entries') %]';
+  s.UPDATE_SUCCESS = '[% l('Display Entry order succesfully updated') %]';
+  s.UPDATE_FAILURE = '[% l('Display Entry order failed to update') %]';
+  s.ADD_SUCCESS = '[% l('Display Entry succesfully added') %]';
+  s.ADD_FAILURE = '[% l('Failed to add Display Entry') %]';
+  s.REMOVE_SUCCESS = '[% l('Display Entry succesfully removed') %]';
+  s.REMOVE_FAILURE = '[% l('Failed to remove Display Entry') %]';
+}]);
+</script>
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
\ No newline at end of file
diff --git a/Open-ILS/src/templates/staff/admin/local/permission/t_grp_tree_display_entry.tt2 b/Open-ILS/src/templates/staff/admin/local/permission/t_grp_tree_display_entry.tt2
new file mode 100644 (file)
index 0000000..2436f18
--- /dev/null
@@ -0,0 +1,60 @@
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    [% l('Permission Group Tree Entries') %]
+  </div>
+</div>
+
+<div class="container">
+<div class="row">
+  <div class="col-md-4">
+    <div class="form-group">
+      <label>[% l('Permission Group Entries in Library:') %]</label>
+      <eg-org-selector onchange="org_changed" selected="selectedOrg"></eg-org-selector>
+    </div>
+  </div>
+  <div class="col-md-8">
+    <button class="btn btn-success"
+      ng-click="addChildEntry(selected_entry)">
+        <i class="glyphicon glyphicon-plus"></i> [% l('Add') %]
+    </button>
+    <button class="btn btn-danger"
+      ng-click="removeEntry(selected_entry)"
+      ng-disabled="!selected_entry || selected_entry.permanent">
+        <i class="glyphicon glyphicon-remove"></i> [% l('Remove') %]
+    </button>
+    <button class="btn btn-info"
+      ng-click="setPosition(selected_entry, 'up')"
+      ng-disabled="!selected_entry || selected_entry.permanent">
+        <i class="glyphicon glyphicon-arrow-up"></i> [% l('Move Up') %]
+    </button>
+    <button class="btn btn-info"
+      ng-click="setPosition(selected_entry, 'down')"
+      ng-disabled="!selected_entry || selected_entry.permanent">
+        <i class="glyphicon glyphicon-arrow-down"></i> [% l('Move Down') %]
+    </button>
+    <button class="btn btn-primary"
+      ng-click="saveEntries()">
+        <i class="glyphicon glyphicon-floppy-disk"></i> [% l('Save') %]
+    </button>
+  </div>
+</div>
+
+<div class="row">
+  <div class="col-md-4" ng-if="selectedOrg">
+    <treecontrol
+        class="tree-light"
+        tree-model="perm_tree"
+        options="tree_options"
+        on-selection="updateSelection(node, selected)"
+        selected-node="selected_entry"
+        order-by="orderby"
+        expanded-nodes="expanded_nodes"
+    >
+      {{node.grp().name()}}
+    </treecontrol>
+  </div>
+  <div class="col-md-12" ng-if="!selectedOrg">
+    <div class="alert alert-danger">[% l('No Org Unit Selected') %]</div>
+  </div>
+</div>
+</div>
\ No newline at end of file
diff --git a/Open-ILS/src/templates/staff/admin/local/permission/t_pgtde_add_dialog.tt2 b/Open-ILS/src/templates/staff/admin/local/permission/t_pgtde_add_dialog.tt2
new file mode 100644 (file)
index 0000000..ad30169
--- /dev/null
@@ -0,0 +1,36 @@
+<div class="modal-header">
+  <button type="button" class="close"
+    ng-click="cancel()" aria-hidden="true">&times;</button>
+  <h4 class="modal-title">
+    [% l('Add Display Entry') %]
+  </h4>
+</div>
+<div class="modal-body tight-vert-form" id="patron-pay-by-credit-form">
+  <div class="panel panel-default">
+    <div class="panel-heading" ng-if="context.selected_parent && !context.is_root && !context.selected_parent.permanent">
+    [% l('Adding Entry to ') %] {{context.selected_parent.grp().name()}}
+    </div>
+    <div class="panel-heading" ng-if="context.is_root || context.selected_parent.permanent">
+    [% l('Adding Root Entry') %]
+    </div>
+    <div class="panel-body">
+      <div class="row form-group">
+        <div class="col-md-4"><label>[% l('Available Entries') %]</label></div>
+        <div class="col-md-8">
+          <select class="form-control" ng-model="context.selected_grp"
+            ng-options="grp.name() disable when grp._filter_grp for grp in context.edit_profiles track by grp.id()">
+            <option value="" ng-if="!context.edit_profiles.length">[% l('&lt;NONE&gt;') %]</option>
+          </select>
+        </div>
+        <div class="col-md-4"><label>[% l('Add as root entry?') %]</label></div>
+        <div class="col-md-8">
+          <input type="checkbox" ng-model="context.is_root" ng-disabled="!context.selected_parent">
+        </div>
+      </div>
+    </div><!--panel-body-->
+  </div><!--panel-->
+</div><!--modal-body-->
+<div class="modal-footer">
+  <button class="btn btn-primary" ng-click="ok()" ng-disabled="!context.selected_grp.id()">[% l('Submit') %]</button>
+  <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+</div>
\ No newline at end of file
index 151f2bb..90ec95a 100644 (file)
@@ -30,6 +30,7 @@
     ,[ l('Non-Cataloged Types Editor'), "./admin/local/config/non_cat_types" ]
     ,[ l('Notifications / Action Triggers'), "./admin/local/action_trigger/event_definition" ]
     ,[ l('Patrons with Negative Balances'), "./admin/local/circ/neg_balance_users" ]
+    ,[ l('Permission Tree Display Entries'), "./admin/local/permission/grp_tree_display_entry" ]
     ,[ l('Search Filter Groups'), "./admin/local/actor/search_filter_group" ]
     ,[ l('Standing Penalties'), "./admin/local/config/standing_penalty" ]
     ,[ l('Statistical Categories Editor'), "./admin/local/asset/stat_cat_editor" ]
index 4dff9ce..85e5bdb 100644 (file)
@@ -458,8 +458,14 @@ within the "form" by name for validation.
         <span class="caret"></span>
       </button>
       <ul class="scrollable-menu" uib-dropdown-menu>
-        <li ng-repeat="grp in edit_profiles" 
-          ng-class="{disabled : grp.usergroup() == 'f'}">
+        <li ng-repeat="entry in edit_profile_entries" ng-if="edit_profile_entries.length"
+          ng-class="{disabled : entry.grp().usergroup() == 'f'}">
+          <a href 
+            style="padding-left: {{pgtde_depth(entry) * 10 + 5}}px"
+            ng-click="set_profile(entry.grp())">{{entry.grp().name()}}</a>
+        </li>
+        <li ng-repeat="grp in edit_profiles" ng-if="!edit_profile_entries.length"
+          ng-class="{disabled : grp().usergroup() == 'f'}">
           <a href 
             style="padding-left: {{pgt_depth(grp) * 10 + 5}}px"
             ng-click="set_profile(grp)">{{grp.name()}}</a>
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/permission/app.js b/Open-ILS/web/js/ui/default/staff/admin/local/permission/app.js
new file mode 100644 (file)
index 0000000..8c5d73f
--- /dev/null
@@ -0,0 +1,443 @@
+angular.module('egAdminPermGrpTreeApp',
+    ['ngRoute','ui.bootstrap','egCoreMod','egUiMod','treeControl'])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/);
+
+    var resolver = {delay :
+        ['egStartup', function(egStartup) {return egStartup.go()}]}
+
+    $routeProvider.when('/admin/local/permission/grp_tree_display_entry', {
+        templateUrl : './admin/local/permission/t_grp_tree_display_entry',
+        controller : 'PermGrpTreeCtrl',
+        resolve : resolver
+    });
+
+    $routeProvider.otherwise({redirectTo : '/admin/local/permission/grp_tree'});
+})
+
+.factory('egPermGrpTreeSvc',
+        ['$q','egCore', function($q , egCore) {
+    var service = {
+        pgtde_array: [],
+        display_entries: [],
+        disabled_entries: [],
+        profiles: [],
+        edit_profiles: []
+    };
+
+    // determine which user groups our user is not allowed to modify
+    service.set_edit_profiles = function() {
+        var all_app_perms = [];
+        var failed_perms = [];
+
+        // extract the application permissions
+        angular.forEach(service.profiles, function(grp) {
+            if (grp.application_perm())
+                all_app_perms.push(grp.application_perm());
+        }); 
+
+        // fill in service.edit_profiles by inspecting failed_perms
+        function traverse_grp_tree(grp, failed) {
+            failed = failed || 
+                failed_perms.indexOf(grp.application_perm()) > -1;
+
+            if (!failed) service.edit_profiles.push(grp);
+
+            angular.forEach(
+                service.profiles.filter( // children of grp
+                    function(p) { return p.parent() == grp.id() }),
+                function(child) {traverse_grp_tree(child, failed)}
+            );
+        }
+
+        return egCore.perm.hasPermAt(all_app_perms, true).then(
+            function(perm_orgs) {
+                angular.forEach(all_app_perms, function(p) {
+                    if (perm_orgs[p].length == 0)
+                        failed_perms.push(p);
+                });
+
+                traverse_grp_tree(egCore.env.pgt.tree);
+            }
+        );
+    }
+
+    service.get_perm_groups = function() {
+        if (egCore.env.pgt) {
+            service.profiles = egCore.env.pgt.list;
+            return service.set_edit_profiles();
+        } else {
+            return egCore.pcrud.search('pgt', {parent : null}, 
+                {flesh : -1, flesh_fields : {pgt : ['children']}}
+            ).then(
+                function(tree) {
+                    egCore.env.absorbTree(tree, 'pgt')
+                    service.profiles = egCore.env.pgt.list;
+                    return service.set_edit_profiles();
+                }
+            );
+        }
+    }
+
+    service.fetchDisplayEntries = function(ou_id) {
+        service.edit_profiles = [];
+        service.get_perm_groups();
+        return egCore.pcrud.search('pgtde',
+            {org : egCore.org.ancestors(ou_id, true)},
+            { flesh : 1,
+              flesh_fields : {
+                  pgtde: ['grp', 'org']
+              },
+              order_by: {pgtde : 'id'}
+            },
+            {atomic: true}
+        ).then(function(entries) {
+            service.pgtde_array = [];
+            service.disabled_entries = [];
+            angular.forEach(entries, function(entry) {
+                if (entry.disabled() == 'f') {
+                    entry.disabled(false);
+                    service.pgtde_array.push(entry);
+                }
+                if (entry.disabled() == 't') {
+                    entry.disabled(true);
+                    service.disabled_entries.push(entry);
+                }
+            });
+        });
+    }
+
+    service.organizeDisplayEntries = function(selectedOrg) {
+        service.display_entries = [];
+
+        angular.forEach(service.pgtde_array, function(pgtde) {
+            if (pgtde.org().id() == selectedOrg) {
+                if (!pgtde.child_entries) pgtde.child_entries = [];
+                var isChild = false;
+                angular.forEach(service.display_entries, function(entry) {
+                    if (pgtde.parent() && pgtde.parent() == entry.id()) {
+                        entry.child_entries.push(pgtde);
+                        isChild = true;
+                        return;
+                    } else {
+                        if (service.iterateChildEntries(pgtde, entry)) {
+                            isChild = true;
+                            return;
+                        }
+                    }
+                });
+                if (!pgtde.parent() || !isChild) {
+                    service.display_entries.push(pgtde);
+                }
+            }
+        });
+    }
+
+    service.iterateChildEntries = function(pgtde, entry) {
+        if (entry.child_entries.length) {
+            return angular.forEach(entry.child_entries, function(child) {
+                if (pgtde.parent() == child.id()) {
+                    child.child_entries.push(pgtde);
+                    return true;
+                } else {
+                    return service.iterateChildEntries(pgtde, child);
+                }
+            });
+        }
+    }
+
+    service.updateDisplayEntries = function(tree, ou_id) {
+        return egCore.pcrud.search('pgtde',
+            {org : ou_id},
+            { flesh : 1,
+              flesh_fields : {
+                  pgtde: ['grp', 'org']
+              },
+              order_by: {pgtde : 'id'}
+            },
+            {atomic: true}
+        ).then(function(entries) {
+            return egCore.pcrud.update(tree).then(function(res) {
+                return res;
+            });
+        });
+    }
+
+    service.removeDisplayEntries = function(entries) {
+        return egCore.pcrud.remove(entries).then(function(res) {
+            return res;
+        });
+    }
+
+    service.addDisplayEntries = function(entries) {
+        return egCore.pcrud.create(entries).then(function(res) {
+            return res;
+        });
+    }
+
+    service.findEntry = function(id, entries) {
+        var match;
+        angular.forEach(entries, function(entry) {
+            if (!match) {
+                if (!entry.child_entries) entry.child_entries = [];
+                if (id == entry.id()) {
+                    match = entry;
+                } else if (entry.child_entries.length) {
+                    match = service.findEntry(id, entry.child_entries);
+                }
+            }
+        });
+
+        return match;
+    }
+
+    return service;
+}])
+
+.controller('PermGrpTreeCtrl',
+    ['$scope','$q','$timeout','$location','$uibModal','egCore','egPermGrpTreeSvc', 'ngToast', 'egProgressDialog',
+function($scope , $q , $timeout , $location , $uibModal , egCore , egPermGrpTreeSvc, ngToast, egProgressDialog) {
+    $scope.perm_tree = [{
+        grp: function() {
+            return {
+                name: function() {return egCore.strings.ROOT_NODE_NAME;}
+            }
+        },
+        child_entries: [],
+        permanent: 'true'
+    }];
+    $scope.display_entries = [];
+    $scope.new_entries = [];
+    $scope.tree_options = {nodeChildren: 'child_entries'};
+    $scope.selected_entry;
+    $scope.expanded_nodes = [];
+    $scope.orderby = ['position()','grp().name()'];
+
+    if (!$scope.selectedOrg)
+        $scope.selectedOrg = egCore.org.get(egCore.auth.user().ws_ou());
+
+    $scope.updateSelection = function(node, selected) {
+        $scope.selected_entry = node;
+        if (!selected) $scope.selected_entry = null;
+    }
+
+    $scope.setPosition = function(node, direction) {
+        var newPos = node.position();
+        var siblings;
+        if (node.parent()) {
+            siblings = egPermGrpTreeSvc.findEntry(node.parent(), $scope.perm_tree[0].child_entries).child_entries;
+        } else {
+            siblings = $scope.perm_tree[0].child_entries;
+        }
+        if (direction == 'up' && newPos < siblings.length) newPos++;
+        if (direction == 'down' && newPos > 1) newPos--;
+
+        angular.forEach(siblings, function(entry) {
+            if (entry.position() == newPos) entry.position(node.position());
+            angular.forEach($scope.display_entries, function(display_entry) {
+                if (display_entry.id() == entry.id()) {
+                    if (display_entry.position() == newPos) {
+                        display_entry.position(node.position);
+                    };
+                }
+            });
+        });
+
+        angular.forEach($scope.display_entries, function(display_entry) {
+            if (display_entry.id() == node.id()) {
+                display_entry.position(newPos);
+            }
+        });
+
+        node.position(newPos);
+    }
+
+    $scope.addChildEntry = function(node) {
+
+        $scope.openAddDialog(node, $scope.disabled_entries, $scope.edit_profiles, $scope.display_entries, $scope.selectedOrg)
+        .then(function(res) {
+            if (res) {
+
+                var siblings = []
+                var new_entry = new egCore.idl.pgtde();
+                new_entry.org($scope.selectedOrg.id());
+                new_entry.grp(res.selected_grp);
+                new_entry.position(1);
+                new_entry.child_entries = [];
+                var is_expanded;
+                if (res.selected_parent) {
+                    new_entry.parent(res.selected_parent);
+                    angular.forEach($scope.expanded_nodes, function(expanded_node) {
+                        if (expanded_node == res.selected_parent) is_expanded = true;
+                    });
+                    if (!is_expanded) $scope.expanded_nodes.push(res.selected_parent);
+                } else {
+                    angular.forEach($scope.expanded_nodes, function(expanded_node) {
+                        if (expanded_node == $scope.perm_tree[0]) is_expanded = true;
+                    });
+                    if (!is_expanded) $scope.expanded_nodes.push($scope.perm_tree[0]);
+                }
+
+                $scope.display_entries.push(new_entry);
+                egPermGrpTreeSvc.addDisplayEntries([new_entry]).then(function(addRes) {
+                    if (addRes) {
+                        if (res.is_root || !res.selected_parent) {
+                            angular.forEach($scope.perm_tree[0].child_entries, function(entry) {
+                                var newPos = entry.position();
+                                newPos++;
+                                entry.position(newPos);
+                                siblings.push(entry);
+                            });
+                        } else {
+                            var parent = egPermGrpTreeSvc.findEntry(res.selected_parent.id(), $scope.perm_tree[0].child_entries);
+                            angular.forEach(parent.child_entries, function(entry) {
+                                var newPos = entry.position();
+                                newPos++;
+                                entry.position(newPos);
+                                siblings.push(entry);
+                            });
+                        }
+
+                        egPermGrpTreeSvc.updateDisplayEntries(siblings).then(function(updateRes) {
+                            ngToast.create(egCore.strings.ADD_SUCCESS);
+                            $scope.refreshTree($scope.selectedOrg, $scope.selected_entry);
+                        });
+                    } else {
+                        ngToast.create(egCore.strings.ADD_FAILURE);
+                    }
+                });
+            }
+        });
+    }
+
+    $scope.openAddDialog = function(node, disabled_entries, edit_profiles, display_entries, selected_org) {
+
+        return $uibModal.open({
+            templateUrl : './admin/local/permission/t_pgtde_add_dialog',
+            backdrop: 'static',
+            controller : [
+                        '$scope','$uibModalInstance',
+                function($scope , $uibModalInstance) {
+                    var getIsRoot = function() {
+                        if (!node || node.permanent) return true;
+                        return false;
+                    }
+
+                    var getSelectedParent = function() {
+                        if (!node || node.permanent) return $scope.perm_tree;
+                        return node;
+                    }
+
+                    var available_profiles = [];
+                    angular.forEach(edit_profiles, function(grp) {
+                        grp._filter_grp = false;
+                        angular.forEach(display_entries, function(entry) {
+                            if (entry.org().id() == selected_org.id()) {
+                                if (entry.grp().id() == grp.id()) grp._filter_grp = true;
+                            }
+                        });
+                        if (!grp._filter_grp) available_profiles.push(grp);
+                    });
+
+                    $scope.context = {
+                        is_root : getIsRoot(),
+                        selected_parent : getSelectedParent(),
+                        edit_profiles : available_profiles,
+                        display_entries : display_entries,
+                        selected_org : selected_org
+                    }
+
+                    $scope.context.selected_grp = $scope.context.edit_profiles[0];
+
+                    $scope.ok = function() {
+                        $uibModalInstance.close($scope.context);
+                    }
+
+                    $scope.cancel = function() {
+                        $uibModalInstance.dismiss();
+                    }
+                }
+            ]
+        }).result;
+    }
+
+    var iteratePermTree = function(arr1, arr2) {
+        angular.forEach(arr1, function(entry) {
+            arr2.push(entry);
+            if (entry.child_entries) iteratePermTree(entry.child_entries, arr2);
+        });
+    }
+
+    $scope.removeEntry = function(node) {
+        $scope.disabled_entries.push(node);
+        iteratePermTree(node.child_entries, $scope.disabled_entries);
+
+        var siblings;
+        if (node.parent()) {
+            siblings = egPermGrpTreeSvc.findEntry(node.parent(), $scope.perm_tree[0].child_entries).child_entries;
+        } else {
+            siblings = $scope.perm_tree[0].child_entries;
+        }
+        angular.forEach(siblings, function(sibling) {
+            var newPos = sibling.position();
+            if (node.position() < sibling.position()) {
+                newPos--;
+            }
+            sibling.position(newPos);
+        });
+
+        $scope.selected_entry = null;
+
+        egPermGrpTreeSvc.removeDisplayEntries($scope.disabled_entries).then(function(res) {
+            if (res) {
+                ngToast.create(egCore.strings.REMOVE_SUCCESS);
+                $scope.refreshTree($scope.selectedOrg);
+            } else {
+                ngToast.create(egCore.strings.REMOVE_FAILURE);
+            }
+        })
+    }
+
+    var getDisplayEntries = function() {
+        $scope.edit_profiles = egPermGrpTreeSvc.edit_profiles;
+        egPermGrpTreeSvc.organizeDisplayEntries($scope.selectedOrg.id());
+        $scope.perm_tree[0].child_entries = egPermGrpTreeSvc.display_entries;
+        $scope.display_entries = egPermGrpTreeSvc.pgtde_array;
+        $scope.new_entries = [];
+        $scope.disabled_entries = [];
+        $scope.selected_entry = $scope.perm_tree[0];
+        if (!$scope.expanded_nodes.length) iteratePermTree($scope.perm_tree, $scope.expanded_nodes);
+    }
+
+    $scope.saveEntries = function() {
+        egProgressDialog.open();
+
+        // Save Remaining Display Entries
+        egPermGrpTreeSvc.updateDisplayEntries($scope.display_entries, $scope.selectedOrg.id())
+        .then(function(res) {
+            if (res) {
+                ngToast.create(egCore.strings.UPDATE_SUCCESS);
+                $scope.refreshTree($scope.selectedOrg, $scope.selected_entry);
+            } else {
+                ngToast.create(egCore.strings.UPDATE_FAILURE);
+            }
+        }).finally(egProgressDialog.close);
+    }
+
+    $scope.org_changed = function(org) {
+        $scope.refreshTree(org.id());
+    }
+
+    $scope.refreshTree = function(ou_id, node) {
+        egPermGrpTreeSvc.fetchDisplayEntries(ou_id).then(function() {
+            getDisplayEntries();
+            if (node) $scope.selected_entry = node;
+        });
+    }
+
+    egCore.startup.go(function() {
+        $scope.refreshTree(egCore.auth.user().ws_ou());
+    });
+}])
\ No newline at end of file
index d11c740..d4b9728 100644 (file)
@@ -2,12 +2,14 @@
 angular.module('egCoreMod')
 // toss tihs onto egCoreMod since the page app may vary
 
-.factory('patronRegSvc', ['$q', 'egCore', 'egLovefield', function($q, egCore, egLovefield) {
+.factory('patronRegSvc', ['$q', '$filter', 'egCore', 'egLovefield', function($q, $filter, egCore, egLovefield) {
 
     var service = {
         field_doc : {},            // config.idl_field_doc
         profiles : [],             // permission groups
+        profile_entries : [],      // permission gorup display entries
         edit_profiles : [],        // perm groups we can modify
+        edit_profile_entries : [], // perm group display entries we can modify
         sms_carriers : [],
         user_settings : {},        // applied user settings
         user_setting_types : {},   // config.usr_setting_type
@@ -38,6 +40,7 @@ angular.module('egCoreMod')
             common_data = [
                 service.get_field_doc(),
                 service.get_perm_groups(),
+                service.get_perm_group_entries(),
                 service.get_ident_types(),
                 service.get_org_settings(),
                 service.get_stat_cats(),
@@ -200,6 +203,44 @@ angular.module('egCoreMod')
         );
     }
 
+    service.set_edit_profile_entries = function() {
+        var all_app_perms = [];
+        var failed_perms = [];
+
+        // extract the application permissions
+        angular.forEach(service.profile_entries, function(entry) {
+            if (entry.grp().application_perm())
+                all_app_perms.push(entry.grp().application_perm());
+        });
+
+        // fill in service.edit_profiles by inspecting failed_perms
+        function traverse_grp_tree(entry, failed) {
+            failed = failed ||
+                failed_perms.indexOf(entry.grp().application_perm()) > -1;
+
+            if (!failed) service.edit_profile_entries.push(entry);
+
+            angular.forEach(
+                service.profile_entries.filter( // children of grp
+                    function(p) { return p.parent() == entry.id() }),
+                function(child) {traverse_grp_tree(child, failed)}
+            );
+        }
+
+        return egCore.perm.hasPermAt(all_app_perms, true).then(
+            function(perm_orgs) {
+                angular.forEach(all_app_perms, function(p) {
+                    if (perm_orgs[p].length == 0)
+                        failed_perms.push(p);
+                });
+
+                angular.forEach(egCore.env.pgtde.tree, function(tree) {
+                    traverse_grp_tree(tree);
+                });
+            }
+        );
+    }
+
     // resolves to a hash of perm-name => boolean value indicating
     // wether the user has the permission at org_id.
     service.has_perms_for_org = function(org_id) {
@@ -442,6 +483,41 @@ angular.module('egCoreMod')
         }
     }
 
+    service.get_perm_group_entries = function() {
+        if (egCore.env.pgtde) {
+            service.profile_entries = egCore.env.pgtde.list;
+            return service.set_edit_profile_entries();
+        } else {
+            return egCore.pcrud.search('pgtde', {org: egCore.auth.user().ws_ou(), parent: null},
+                {flesh : -1, flesh_fields : {pgtde : ['grp', 'children']}}, {atomic : true}
+            ).then(function(treeArray) {
+                function compare(a,b) {
+                  if (a.position() > b.position())
+                    return -1;
+                  if (a.position() < b.position())
+                    return 1;
+                  return 0;
+                }
+
+                var list = [];
+                function squash(node) {
+                    if (node.disabled() == 'f') {
+                        node.children().sort(compare)
+                        list.push(node);
+                        angular.forEach(node.children(), squash);
+                    }
+                }
+
+                angular.forEach(treeArray, squash);
+                var blob = egCore.env.absorbList(list, 'pgtde');
+                blob.tree = treeArray;
+
+                service.profile_entries = egCore.env.pgtde.list;
+                return service.set_edit_profile_entries();
+            });
+        }
+    }
+
     service.get_field_doc = function() {
         var to_cache = [];
         return egCore.pcrud.search('fdoc', {
@@ -1227,6 +1303,7 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         $scope.patron = patron;
         $scope.field_doc = prs.field_doc;
         $scope.edit_profiles = prs.edit_profiles;
+        $scope.edit_profile_entries = prs.edit_profile_entries;
         $scope.ident_types = prs.ident_types;
         $scope.net_access_levels = prs.net_access_levels;
         $scope.user_setting_types = prs.user_setting_types;
@@ -1298,6 +1375,13 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore ,
         return d;
     }
 
+    // returns the tree depth of the selected profile group tree node.
+    $scope.pgtde_depth = function(entry) {
+        var d = 0;
+        while (entry = egCore.env.pgtde.map[entry.parent()]) d++;
+        return d;
+    }
+
     // IDL fields used for labels in the UI.
     $scope.idl_fields = {
         au  : egCore.idl.classes.au.field_map,