From: Kyle Huckins <khuckins@catalyte.io> Date: Thu, 15 Mar 2018 18:54:13 +0000 (+0000) Subject: lp1744756 Profile Tree Display Entry Admin UI X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=0f43da13c5c602e0bf62b9a3154db1f5f59fbc53;p=evergreen%2Fjoelewis.git 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@catalyte.io> Signed-off-by: Bill Erickson <berickxx@gmail.com> Signed-off-by: Kathy Lussier <klussier@masslnc.org> --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 2a7005f2f8..b12a0438f9 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 df154fbf6c..3e1bbf2af6 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 0000000000..2cbbd1f227 --- /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 0000000000..6b63ed5f19 --- /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 0000000000..2436f18c72 --- /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 0000000000..ad30169c6a --- /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 151f2bb414..90ec95af66 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 4dff9ce57d..85e5bdb44f 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 0000000000..8c5d73f8ca --- /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 d11c7408b3..d4b9728da7 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,