[open-ils-commits] [GIT] Evergreen ILS branch master updated. 03317dc6af32e23339f693592ead3e5993691277

Evergreen Git git at git.evergreen-ils.org
Thu Aug 30 12:40:18 EDT 2018


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  03317dc6af32e23339f693592ead3e5993691277 (commit)
       via  dab2a9b8b9f9092202ba53707bccb1ad6a2b68ed (commit)
       via  afaa47a266f72117d754c767317c4e159eb21142 (commit)
       via  c5e8a84e54af6fd925fe047c40e53559f6ae8d6e (commit)
       via  9e3e576bcd6ff3c215bd14cac26bc3ce568955f3 (commit)
       via  0f43da13c5c602e0bf62b9a3154db1f5f59fbc53 (commit)
      from  05968579768d066280a16350cc8abb01d013265b (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 03317dc6af32e23339f693592ead3e5993691277
Author: Kathy Lussier <klussier at masslnc.org>
Date:   Thu Aug 30 12:37:45 2018 -0400

    LP#1744756: Stamping upgrade script for custom perm group display
    
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 7bf7874..d975d9a 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -92,7 +92,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1120', :eg_version); -- bshum/berick
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('1121', :eg_version); -- khuckins/berick/kmlussier
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql b/Open-ILS/src/sql/Pg/upgrade/1121.schema.perm-group-display.sql
similarity index 91%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql
rename to Open-ILS/src/sql/Pg/upgrade/1121.schema.perm-group-display.sql
index a983ecb..4e3681e 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/1121.schema.perm-group-display.sql
@@ -1,5 +1,5 @@
 BEGIN;
-SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('1121', :eg_version);
 
 CREATE TABLE permission.grp_tree_display_entry (
     id      SERIAL PRIMARY KEY,
@@ -19,4 +19,4 @@ INSERT INTO permission.perm_list (id, code, description)
 VALUES (609, 'MANAGE_CUSTOM_PERM_GRP_TREE', oils_i18n_gettext( 609,
     'Allows a user to manage custom permission group lists.', 'ppl', 'description' ));
             
-COMMIT;
\ No newline at end of file
+COMMIT;

commit dab2a9b8b9f9092202ba53707bccb1ad6a2b68ed
Author: Kyle Huckins <khuckins at catalyte.io>
Date:   Wed Aug 29 15:20:17 2018 +0000

    lp1744756 Utilize Parent Trees
    
    - Allow Patron Edit interface to make use of Parent OU trees when
    expect tree doesn't exist.
    
    Signed-off-by: Kyle Huckins <khuckins at catalyte.io>
    
     Changes to be committed:
    	modified:   Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
    
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
index 640bb3b..6a2d092 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
@@ -483,14 +483,23 @@ angular.module('egCoreMod')
         }
     }
 
+    service.searchPermGroupEntries = function(org) {
+        return egCore.pcrud.search('pgtde', {org: org, parent: null},
+            {flesh: -1, flesh_fields: {pgtde: ['grp', 'children']}}, {atomic: true}
+        ).then(function(treeArray) {
+            if (!treeArray.length && egCore.org.get(org).parent_ou()) {
+                return service.searchPermGroupEntries(egCore.org.get(org).parent_ou());
+            }
+            return treeArray;
+        });
+    }
+
     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) {
+            return service.searchPermGroupEntries(egCore.auth.user().ws_ou()).then(function(treeArray) {
                 function compare(a,b) {
                   if (a.position() > b.position())
                     return -1;

commit afaa47a266f72117d754c767317c4e159eb21142
Author: Kyle Huckins <khuckins at catalyte.io>
Date:   Mon Aug 27 20:21:45 2018 +0000

    lp1744756 Docs fix and Permission change
    
    - Introduce MANAGE_CUSTOM_PERM_GRP_TREE permission.
    
    - Remove unnecessary references to unused "disabled"
    field for pgtdes.
    
    - Update documentation to correctly explain how removing
    and adding display entries works.
    
     Changes to be committed:
    	modified:   Open-ILS/examples/fm_IDL.xml
    	modified:   Open-ILS/src/sql/Pg/006.schema.permissions.sql
    	modified:   Open-ILS/src/sql/Pg/950.data.seed-values.sql
    	modified:   Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql
    	modified:   Open-ILS/web/js/ui/default/staff/admin/local/permission/app.js
    	modified:   Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
    	modified:   docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc
    
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index eb447a4..e60c5de 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -7651,7 +7651,6 @@ SELECT  usr,
 			<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>
@@ -7662,10 +7661,10 @@ SELECT  usr,
 		</links>
 		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
 			<actions>
-				<create permission="CREATE_PERM" global_required="true"/>
+				<create permission="MANAGE_CUSTOM_PERM_GRP_TREE" global_required="true"/>
 				<retrieve permission="STAFF_LOGIN" global_required="true"/>
-				<update permission="UPDATE_PERM" global_required="true"/>
-				<delete permission="DELETE_PERM" global_required="true"/>
+				<update permission="MANAGE_CUSTOM_PERM_GRP_TREE" global_required="true"/>
+				<delete permission="MANAGE_CUSTOM_PERM_GRP_TREE" global_required="true"/>
 			</actions>
 		</permacrud>
 	</class>
diff --git a/Open-ILS/src/sql/Pg/006.schema.permissions.sql b/Open-ILS/src/sql/Pg/006.schema.permissions.sql
index 3e1bbf2..564f785 100644
--- a/Open-ILS/src/sql/Pg/006.schema.permissions.sql
+++ b/Open-ILS/src/sql/Pg/006.schema.permissions.sql
@@ -624,7 +624,6 @@ CREATE TABLE permission.grp_tree_display_entry (
             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)
 );
 
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index aa0f163..d4ffab6 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -1911,7 +1911,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
  ( 607, 'EMERGENCY_CLOSING', oils_i18n_gettext( 607,
     'Create and manage Emergency Closings', 'ppl', 'description' )),
  (608, 'APPLY_WORKSTATION_SETTING',
-   oils_i18n_gettext(608, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description'))
+   oils_i18n_gettext(608, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description')),
+ ( 609, 'MANAGE_CUSTOM_PERM_GRP_TREE', oils_i18n_gettext( 609,
+    'Allows a user to manage custom permission group lists.', 'ppl', 'description' ))
 ;
 
 
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
index 2cbbd1f..a983ecb 100644
--- 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
@@ -8,12 +8,15 @@ CREATE TABLE permission.grp_tree_display_entry (
             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;
+
+INSERT INTO permission.perm_list (id, code, description)
+VALUES (609, 'MANAGE_CUSTOM_PERM_GRP_TREE', oils_i18n_gettext( 609,
+    'Allows a user to manage custom permission group lists.', 'ppl', 'description' ));
             
 COMMIT;
\ No newline at end of file
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
index 8c5d73f..3b7f19a 100644
--- 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
@@ -97,14 +97,7 @@ angular.module('egAdminPermGrpTreeApp',
             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.pgtde_array.push(entry);
             });
         });
     }
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
index d4b9728..640bb3b 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
@@ -501,11 +501,9 @@ angular.module('egCoreMod')
 
                 var list = [];
                 function squash(node) {
-                    if (node.disabled() == 'f') {
-                        node.children().sort(compare)
-                        list.push(node);
-                        angular.forEach(node.children(), squash);
-                    }
+                    node.children().sort(compare);
+                    list.push(node);
+                    angular.forEach(node.children(), squash);
                 }
 
                 angular.forEach(treeArray, squash);
diff --git a/docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc b/docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc
index d5a7198..a05c875 100644
--- a/docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc
+++ b/docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc
@@ -33,14 +33,15 @@ Removing an Entry
 +++++++++++++++++
 If you want a particular Org Unit to not have access to specific
 entries, you may remove an entry. Removing an entry will remove it from 
-view. The entry will remain in the database, marked as disabled.
+view. The entry will be removed from the database.
 
 * Select an entry and press the *Remove* button.
 
 Adding an Entry
 +++++++++++++++
-You may "add" entries that have been removed previously. This is useful
-for moving entries to different parents, or making them root entries.
+You may add entries from permission groups that are not currently
+reflected in the permission group tree. This is useful for moving 
+entries to different parents, or making them root entries.
 
 image::media/pgtde_02.png[Add Entry modal]
 

commit c5e8a84e54af6fd925fe047c40e53559f6ae8d6e
Author: Bill Erickson <berickxx at gmail.com>
Date:   Mon Aug 6 11:35:44 2018 -0400

    LP#1744756 Grp display tree minor fixes.
    
    1. Fix group tree display entry IDL sequence copy/paste issue:
    
    permission.grp_tree_id_seq => permission.grp_tree_display_entry_id_seq
    
    2. Apply consistent use of tabs in the IDL.
    
    3. Fix thinko use of grp as a function in the no-entries-exist scenario
       in the patron editor.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kyle Huckins <khuckins at catalyte.io>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index b12a043..eb447a4 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -7645,13 +7645,13 @@ SELECT  usr,
         </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">
+		<fields oils_persist:primary="id" oils_persist:sequence="permission.grp_tree_display_entry_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="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>
@@ -7660,14 +7660,14 @@ SELECT  usr,
 			<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>
+		<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">
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
index 85e5bdb..bd58914 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
@@ -465,7 +465,7 @@ within the "form" by name for validation.
             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'}">
+          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>

commit 9e3e576bcd6ff3c215bd14cac26bc3ce568955f3
Author: Kyle Huckins <khuckins at catalyte.io>
Date:   Thu Jun 14 16:11:34 2018 +0000

    lp1744756 Release Notes for PGTDEs
    
    - Include release notes for Permission Group Tree Display
    Entry UI and usage.
    - Include pictures of interface for documentation.
    
    Signed-off-by: Kyle Huckins <khuckins at catalyte.io>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc b/docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc
new file mode 100644
index 0000000..d5a7198
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc
@@ -0,0 +1,53 @@
+Permission Group Display Entries
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+In some cases, it is useful to have the ability to reorder permission, or to make
+only specific groups available in the permission group selector for specific
+Org Units. An interface has been made available to allow this.
+
+Group Tree Display Entry Interface
+++++++++++++++++++++++++++++++++++
+
+Permission Group Display Entries can be reordered, added, or removed via
+_Administration -> Local Admin -> Permission Tree Display Entries_.
+Select the Org Unit you wish to edit the entries in.
+
+Entries may be added using the Add functionality, creating entries based
+on permission groups that have not been added to the tree for the Org
+Unit you wish to add them to.
+
+image::media/pgtde_01.png[Group Tree Display Entry Admin UI]
+
+Moving an Entry
++++++++++++++++
+Moving an entry will shift its position up or down in the patron profile
+selector for a given Org Unit.
+
+* Select an entry
+* Press either the *Move Up* or *Move Down* button. The entry will be 
+moved up or down, accordingly.
+* Click *Save* to save your edits.  
+
+NOTE: You may only move up or down entries that have sibling entries.
+
+Removing an Entry
++++++++++++++++++
+If you want a particular Org Unit to not have access to specific
+entries, you may remove an entry. Removing an entry will remove it from 
+view. The entry will remain in the database, marked as disabled.
+
+* Select an entry and press the *Remove* button.
+
+Adding an Entry
++++++++++++++++
+You may "add" entries that have been removed previously. This is useful
+for moving entries to different parents, or making them root entries.
+
+image::media/pgtde_02.png[Add Entry modal]
+
+* If desired, select an entry to be used as the parent entry. 
+* Press the *Add* button. 
+* Select a permission group from the dropdown.
+* If you've selected a parent entry, you may check the *Add Root Entry*
+box to override that parent and add the entry on the root level. 
+* If you did not select a parent entry, the entry will be added on the root 
+level of the tree.
\ No newline at end of file
diff --git a/docs/media/pgtde_01.png b/docs/media/pgtde_01.png
new file mode 100644
index 0000000..b1dfb94
Binary files /dev/null and b/docs/media/pgtde_01.png differ
diff --git a/docs/media/pgtde_02.png b/docs/media/pgtde_02.png
new file mode 100644
index 0000000..54cf336
Binary files /dev/null and b/docs/media/pgtde_02.png differ

commit 0f43da13c5c602e0bf62b9a3154db1f5f59fbc53
Author: Kyle Huckins <khuckins at catalyte.io>
Date:   Thu Mar 15 18:54:13 2018 +0000

    lp1744756 Profile Tree Display Entry Admin UI
    
    - 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 at catalyte.io>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 2a7005f..b12a043 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -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"/>
diff --git a/Open-ILS/src/sql/Pg/006.schema.permissions.sql b/Open-ILS/src/sql/Pg/006.schema.permissions.sql
index df154fb..3e1bbf2 100644
--- a/Open-ILS/src/sql/Pg/006.schema.permissions.sql
+++ b/Open-ILS/src/sql/Pg/006.schema.permissions.sql
@@ -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
index 0000000..2cbbd1f
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.perm-group-display.sql
@@ -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
index 0000000..6b63ed5
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/permission/index.tt2
@@ -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
index 0000000..2436f18
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/permission/t_grp_tree_display_entry.tt2
@@ -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
index 0000000..ad30169
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/permission/t_pgtde_add_dialog.tt2
@@ -0,0 +1,36 @@
+<div class="modal-header">
+  <button type="button" class="close"
+    ng-click="cancel()" aria-hidden="true">×</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('<NONE>') %]</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
diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index 151f2bb..90ec95a 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -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" ]
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
index 4dff9ce..85e5bdb 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2
@@ -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
index 0000000..8c5d73f
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/permission/app.js
@@ -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
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
index d11c740..d4b9728 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js
@@ -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,

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |   24 ++
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/006.schema.permissions.sql     |   13 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |    4 +-
 .../Pg/upgrade/1121.schema.perm-group-display.sql  |   22 +
 .../staff/admin/local/permission/index.tt2         |   26 ++
 .../local/permission/t_grp_tree_display_entry.tt2  |   60 +++
 .../admin/local/permission/t_pgtde_add_dialog.tt2  |   36 ++
 .../src/templates/staff/admin/local/t_splash.tt2   |    1 +
 .../src/templates/staff/circ/patron/t_edit.tt2     |    8 +-
 .../ui/default/staff/admin/local/permission/app.js |  436 ++++++++++++++++++++
 .../web/js/ui/default/staff/circ/patron/regctl.js  |   93 ++++-
 docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc          |   54 +++
 docs/media/pgtde_01.png                            |  Bin 0 -> 56744 bytes
 docs/media/pgtde_02.png                            |  Bin 0 -> 31792 bytes
 15 files changed, 775 insertions(+), 4 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/1121.schema.perm-group-display.sql
 create mode 100644 Open-ILS/src/templates/staff/admin/local/permission/index.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/local/permission/t_grp_tree_display_entry.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/local/permission/t_pgtde_add_dialog.tt2
 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/local/permission/app.js
 create mode 100644 docs/RELEASE_NOTES_NEXT/Client/pgtde.adoc
 create mode 100644 docs/media/pgtde_01.png
 create mode 100644 docs/media/pgtde_02.png


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list