=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataapproval/DataApprovalLevelService.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataapproval/DataApprovalLevelService.java 2014-04-07 11:28:21 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataapproval/DataApprovalLevelService.java 2014-04-13 20:43:32 +0000 @@ -28,7 +28,10 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.hisp.dhis.organisationunit.OrganisationUnit; + import java.util.List; +import java.util.Map; /** * @author Jim Grace @@ -38,6 +41,28 @@ String ID = DataApprovalLevelService.class.getName(); /** + * Integer that can be used in place of approval level + * for data that has not been approved at any level. + */ + public static final int APPROVAL_LEVEL_UNAPPROVED = 999; + + /** + * Gets the data approval level with the given id. + * + * @param id the id. + * @return a data approval level. + */ + DataApprovalLevel getDataApprovalLevel( int id ); + + /** + * Gets the data approval level with the given name. + * + * @param name the name. + * @return a data approval level. + */ + DataApprovalLevel getDataApprovalLevelByName( String name ); + + /** * Gets a list of all data approval levels. * * @return List of all data approval levels, ordered from 1 to n. @@ -45,15 +70,6 @@ List getAllDataApprovalLevels(); /** - * Gets a list of the data approval levels for which the user has - * permission to perform at least one operation (approve, unapprove, - * accept, unaccept) for some selection of data at this approval level. - * - * @return List of selected user data approval levels, in ascending order. - */ - List getUserDataApprovalLevels(); - - /** * Gets data approval levels by org unit level. * * @param orgUnitLevel the org unit level. @@ -123,33 +139,18 @@ void deleteDataApprovalLevel( DataApprovalLevel dataApprovalLevel ); /** - * Gets the lowest data approval level that the current user may view. + * By organisation unit subhierarchy, returns the lowest data approval + * level at which the user may see data within that subhierarchy, if + * data viewing is being restricted to approved data from lower levels. + *

+ * Returns the value APPROVAL_LEVEL_UNAPPROVED for a subhierarchy if + * the user may see unapproved data. + *

* (Note that the "lowest" approval level means the "highest" approval * level number.) - *

- * Look at all the levels, starting from the lowest level (highest level - * number.) If the level has no category option group, or if it has a - * category option group where the user can see at least one category - * option within the group, then the user may see the level and all - * higher levels. * - * @return level number of the lowest level the user can view. - */ - int getLowestUserViewDataApprovalLevel(); - - /** - * Gets the data approval level with the given id. - * - * @param id the id. - * @return a data approval level. - */ - DataApprovalLevel getDataApprovalLevel( int id ); - - /** - * Gets the data approval level with the given name. - * - * @param name the name. - * @return a data approval level. - */ - DataApprovalLevel getDataApprovalLevelByName( String name ); + * @return For each organisation unit subhierarchy available to the user, + * the minimum data approval level within that subhierarchy. + */ + Map getUserReadApprovalLevels(); } === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalLevelService.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalLevelService.java 2014-04-10 22:52:55 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalLevelService.java 2014-04-13 20:43:32 +0000 @@ -29,6 +29,7 @@ */ import org.hisp.dhis.dataelement.CategoryOptionGroup; +import org.hisp.dhis.dataelement.CategoryOptionGroupSet; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.organisationunit.OrganisationUnitLevel; import org.hisp.dhis.organisationunit.OrganisationUnitService; @@ -38,9 +39,12 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -86,6 +90,16 @@ // DataApprovalLevel // ------------------------------------------------------------------------- + public DataApprovalLevel getDataApprovalLevel( int id ) + { + return dataApprovalLevelStore.get( id ); + } + + public DataApprovalLevel getDataApprovalLevelByName( String name ) + { + return dataApprovalLevelStore.getByName( name ); + } + public List getAllDataApprovalLevels() { List dataApprovalLevels = dataApprovalLevelStore.getAllDataApprovalLevels(); @@ -104,7 +118,7 @@ return dataApprovalLevels; } - public List getUserDataApprovalLevels() + public List getUserDataApprovalLevels_OLD() { List userDataApprovalLevels = new ArrayList(); @@ -296,39 +310,43 @@ } } - public int getLowestUserViewDataApprovalLevel() - { - List levels = getAllDataApprovalLevels(); - - for ( int i = levels.size() - 1; i <= 0; i-- ) - { - DataApprovalLevel level = levels.get( i ); - - if ( level.getCategoryOptionGroupSet() == null || level.getCategoryOptionGroupSet().getMembers() == null ) - { - return level.getLevel(); - } - - for ( CategoryOptionGroup group : level.getCategoryOptionGroupSet().getMembers() ) - { - if ( securityService.canRead( group ) ) - { - return level.getLevel(); - } - } - } - - return 0; - } - - public DataApprovalLevel getDataApprovalLevel( int id ) - { - return dataApprovalLevelStore.get( id ); - } - - public DataApprovalLevel getDataApprovalLevelByName( String name ) - { - return dataApprovalLevelStore.getByName( name ); + public Map getUserReadApprovalLevels() + { + Map map = new HashMap(); + + User user = currentUserService.getCurrentUser(); + + if ( user.getUserCredentials().isAuthorized( DataApproval.AUTH_APPROVE_LOWER_LEVELS ) ) + { + for ( OrganisationUnit orgUnit : user.getOrganisationUnits() ) + { + map.put( orgUnit, APPROVAL_LEVEL_UNAPPROVED ); + } + } + else + { + for ( OrganisationUnit orgUnit : user.getOrganisationUnits() ) + { + map.put( orgUnit, requiredApprovalLevel( orgUnit ) ); + } + } + + Collection dataViewOrgUnits = user.getDataViewOrganisationUnits(); + + if ( dataViewOrgUnits == null || dataViewOrgUnits.isEmpty() ) + { + dataViewOrgUnits = organisationUnitService.getRootOrganisationUnits(); + } + + for ( OrganisationUnit orgUnit : dataViewOrgUnits ) + { + if ( !map.containsKey( orgUnit ) ) + { + map.put(orgUnit, requiredApprovalLevel( orgUnit ) ); + } + } + + return map; } // ------------------------------------------------------------------------- @@ -414,4 +432,55 @@ } return i + 1; } + + /** + * Get the approval level for an organisation unit that is required + * in order for the user to see the data -- if user is limited to seeing + * approved data only from lower approval levels. + * + * @param orgUnit organisation unit to test. + * @return required approval level for user to see the data. + */ + private int requiredApprovalLevel( OrganisationUnit orgUnit ) + { + int orgUnitLevel = orgUnit.getLevel() != 0 ? + orgUnit.getLevel() : + organisationUnitService.getLevelOfOrganisationUnit( orgUnit.getUid() ); + + int required = APPROVAL_LEVEL_UNAPPROVED; + + for ( DataApprovalLevel level : getAllDataApprovalLevels() ) + { + if ( level.getOrgUnitLevel() >= orgUnitLevel + && securityService.canRead( level ) + && ( level.getCategoryOptionGroupSet() == null || canReadSomeCategory( level.getCategoryOptionGroupSet() ) ) + && level.getLevel() < getAllDataApprovalLevels().size() ) + { + required = level.getLevel() + 1; + break; + } + } + + return required; + } + + /** + * Can the user read at least one category from inside a category option + * group set? + * + * @param cogs The category option group set to test + * @return true if user can read at least one category option group. + */ + private boolean canReadSomeCategory( CategoryOptionGroupSet cogs ) + { + for ( CategoryOptionGroup cog : cogs.getMembers() ) + { + if ( securityService.canRead(cog) ) + { + return true; + } + } + return false; + } + } === modified file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/dataapproval/DataApprovalLevelServiceTest.java' --- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/dataapproval/DataApprovalLevelServiceTest.java 2014-04-07 10:18:57 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/dataapproval/DataApprovalLevelServiceTest.java 2014-04-13 20:43:32 +0000 @@ -30,13 +30,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import static org.hisp.dhis.dataapproval.DataApprovalLevelService.APPROVAL_LEVEL_UNAPPROVED; + import org.hisp.dhis.DhisSpringTest; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.dataelement.CategoryOptionGroupSet; @@ -96,10 +100,19 @@ private DataApprovalLevel level4C; private DataApprovalLevel level4D; + private DataApprovalLevel level5; + private OrganisationUnit organisationUnitA; private OrganisationUnit organisationUnitB; private OrganisationUnit organisationUnitC; private OrganisationUnit organisationUnitD; + private OrganisationUnit organisationUnitE; + private OrganisationUnit organisationUnitF; + private OrganisationUnit organisationUnitG; + private OrganisationUnit organisationUnitH; + private OrganisationUnit organisationUnitI; + private OrganisationUnit organisationUnitJ; + private OrganisationUnit organisationUnitK; // ------------------------------------------------------------------------- // Set up/tear down @@ -149,10 +162,38 @@ level4C = new DataApprovalLevel( "4C", 4, setC ); level4D = new DataApprovalLevel( "4D", 4, setD ); + level5 = new DataApprovalLevel( "5", 5, null ); + + // + // Org Organisation + // unit unit + // level: hierarchy: + // + // 1 A + // | + // 2 B + // / | \ + // 3 C F I + // | | | + // 4 D G J + // | | | + // 5 E H K + // + // Note: E through K are optionally added by the test if desired. + organisationUnitA = createOrganisationUnit( 'A' ); organisationUnitB = createOrganisationUnit( 'B', organisationUnitA ); organisationUnitC = createOrganisationUnit( 'C', organisationUnitB ); organisationUnitD = createOrganisationUnit( 'D', organisationUnitC ); + organisationUnitE = createOrganisationUnit( 'E', organisationUnitD ); + + organisationUnitF = createOrganisationUnit( 'F', organisationUnitB ); + organisationUnitG = createOrganisationUnit( 'G', organisationUnitF ); + organisationUnitH = createOrganisationUnit( 'H', organisationUnitG ); + + organisationUnitI = createOrganisationUnit( 'I', organisationUnitB ); + organisationUnitJ = createOrganisationUnit( 'J', organisationUnitI ); + organisationUnitK = createOrganisationUnit( 'K', organisationUnitJ ); organisationUnitService.addOrganisationUnit( organisationUnitA ); organisationUnitService.addOrganisationUnit( organisationUnitB ); @@ -435,27 +476,180 @@ } @Test - public void testGetUserDataApprovalLevelsNoAuthorities() throws Exception - { - dataApprovalLevelService.addDataApprovalLevel( level4B ); - dataApprovalLevelService.addDataApprovalLevel( level4A ); - dataApprovalLevelService.addDataApprovalLevel( level4 ); - dataApprovalLevelService.addDataApprovalLevel( level3B ); - dataApprovalLevelService.addDataApprovalLevel( level3A ); - dataApprovalLevelService.addDataApprovalLevel( level3 ); - dataApprovalLevelService.addDataApprovalLevel( level2B ); - dataApprovalLevelService.addDataApprovalLevel( level2A ); - dataApprovalLevelService.addDataApprovalLevel( level2 ); - dataApprovalLevelService.addDataApprovalLevel( level1B ); - dataApprovalLevelService.addDataApprovalLevel( level1A ); - dataApprovalLevelService.addDataApprovalLevel( level1 ); - - Set units = new HashSet(); - units.add( organisationUnitB ); - createUserAndInjectSecurityContext( units, false ); - - List levels = dataApprovalLevelService.getUserDataApprovalLevels(); - assertEquals( 0, levels.size() ); + public void testGetUserReadApprovalLevels_1A() throws Exception + { + // + // Test 1: Like when a user may capture data within their own district + // but view data in other districts within their province. + // + // Variation A: User does *not* have approval at lower levels authority. + // + organisationUnitService.addOrganisationUnit( organisationUnitE ); + organisationUnitService.addOrganisationUnit( organisationUnitF ); + organisationUnitService.addOrganisationUnit( organisationUnitG ); + organisationUnitService.addOrganisationUnit( organisationUnitH ); + + dataApprovalLevelService.addDataApprovalLevel( level1 ); + dataApprovalLevelService.addDataApprovalLevel( level2 ); + dataApprovalLevelService.addDataApprovalLevel( level3 ); + dataApprovalLevelService.addDataApprovalLevel( level4 ); + dataApprovalLevelService.addDataApprovalLevel( level5 ); + + Set assignedOrgUnits = new HashSet(); + assignedOrgUnits.add( organisationUnitC ); + + Set dataViewOrgUnits = new HashSet(); + dataViewOrgUnits.add( organisationUnitB ); + + createUserAndInjectSecurityContext( assignedOrgUnits, dataViewOrgUnits, false ); + + Map readApprovalLevels = dataApprovalLevelService.getUserReadApprovalLevels(); + assertEquals( 2, readApprovalLevels.size() ); + + assertEquals( 4, (int) readApprovalLevels.get( organisationUnitC ) ); + assertEquals( 3, (int) readApprovalLevels.get( organisationUnitB ) ); + } + + @Test + public void testGetUserReadApprovalLevels_1B() throws Exception + { + // + // Test 1: Like when a user may capture data within their own district + // but view data in other districts within their province. + // + // Variation B: User *has* approval at lower levels authority. + // + organisationUnitService.addOrganisationUnit( organisationUnitE ); + organisationUnitService.addOrganisationUnit( organisationUnitF ); + organisationUnitService.addOrganisationUnit( organisationUnitG ); + organisationUnitService.addOrganisationUnit( organisationUnitH ); + + dataApprovalLevelService.addDataApprovalLevel( level1 ); + dataApprovalLevelService.addDataApprovalLevel( level2 ); + dataApprovalLevelService.addDataApprovalLevel( level3 ); + dataApprovalLevelService.addDataApprovalLevel( level4 ); + dataApprovalLevelService.addDataApprovalLevel( level5 ); + + Set assignedOrgUnits = new HashSet(); + assignedOrgUnits.add( organisationUnitC ); + + Set dataViewOrgUnits = new HashSet(); + dataViewOrgUnits.add( organisationUnitB ); + + createUserAndInjectSecurityContext( assignedOrgUnits, dataViewOrgUnits, false, DataApproval.AUTH_APPROVE_LOWER_LEVELS ); + + Map readApprovalLevels = dataApprovalLevelService.getUserReadApprovalLevels(); + assertEquals( 2, readApprovalLevels.size() ); + + assertEquals( APPROVAL_LEVEL_UNAPPROVED, (int) readApprovalLevels.get( organisationUnitC ) ); + assertEquals( 3, (int) readApprovalLevels.get( organisationUnitB ) ); + } + + @Test + public void testGetUserReadApprovalLevels_1C() throws Exception + { + // + // Test 1: Like when a user may capture data within their own district + // but view data in other districts within their province. + // + // Variation C: No approval level for org unit level 4. + // + organisationUnitService.addOrganisationUnit( organisationUnitE ); + organisationUnitService.addOrganisationUnit( organisationUnitF ); + organisationUnitService.addOrganisationUnit( organisationUnitG ); + organisationUnitService.addOrganisationUnit( organisationUnitH ); + + dataApprovalLevelService.addDataApprovalLevel( level1 ); // 1st approval level + dataApprovalLevelService.addDataApprovalLevel( level2 ); // 2nd approval level + dataApprovalLevelService.addDataApprovalLevel( level3 ); // 3rd approval level + dataApprovalLevelService.addDataApprovalLevel( level5 ); // 4th approval level + + Set assignedOrgUnits = new HashSet(); + assignedOrgUnits.add( organisationUnitC ); + + Set dataViewOrgUnits = new HashSet(); + dataViewOrgUnits.add( organisationUnitB ); + + createUserAndInjectSecurityContext( assignedOrgUnits, dataViewOrgUnits, false ); + + Map readApprovalLevels = dataApprovalLevelService.getUserReadApprovalLevels(); + assertEquals( 2, readApprovalLevels.size() ); + + assertEquals( 4, (int) readApprovalLevels.get( organisationUnitC ) ); + assertEquals( 3, (int) readApprovalLevels.get( organisationUnitB ) ); + } + + @Test + public void testGetUserReadApprovalLevels_1D() throws Exception + { + // + // Test 1: Like when a user may capture data within their own district + // but view data in other districts within their province. + // + // Variation D: User is assigned to two districts + // + organisationUnitService.addOrganisationUnit( organisationUnitE ); + organisationUnitService.addOrganisationUnit( organisationUnitF ); + organisationUnitService.addOrganisationUnit( organisationUnitG ); + organisationUnitService.addOrganisationUnit( organisationUnitH ); + organisationUnitService.addOrganisationUnit( organisationUnitI ); + organisationUnitService.addOrganisationUnit( organisationUnitJ ); + organisationUnitService.addOrganisationUnit( organisationUnitK ); + + dataApprovalLevelService.addDataApprovalLevel( level1 ); + dataApprovalLevelService.addDataApprovalLevel( level2 ); + dataApprovalLevelService.addDataApprovalLevel( level3 ); + dataApprovalLevelService.addDataApprovalLevel( level4 ); + dataApprovalLevelService.addDataApprovalLevel( level5 ); + + Set assignedOrgUnits = new HashSet(); + assignedOrgUnits.add( organisationUnitC ); + assignedOrgUnits.add( organisationUnitF ); + + Set dataViewOrgUnits = new HashSet(); + dataViewOrgUnits.add( organisationUnitB ); + + createUserAndInjectSecurityContext( assignedOrgUnits, dataViewOrgUnits, false ); + + Map readApprovalLevels = dataApprovalLevelService.getUserReadApprovalLevels(); + assertEquals( 3, readApprovalLevels.size() ); + + assertEquals( 4, (int) readApprovalLevels.get( organisationUnitC ) ); + assertEquals( 4, (int) readApprovalLevels.get( organisationUnitF ) ); + assertEquals( 3, (int) readApprovalLevels.get( organisationUnitB ) ); + } + + /* + @Test + public void testGetUserReadApprovalLevels_2A() throws Exception + { + // + // Test 2... TBD + // + organisationUnitService.addOrganisationUnit( organisationUnitE ); + organisationUnitService.addOrganisationUnit( organisationUnitF ); + organisationUnitService.addOrganisationUnit( organisationUnitG ); + organisationUnitService.addOrganisationUnit( organisationUnitH ); + + dataApprovalLevelService.addDataApprovalLevel( level1 ); + dataApprovalLevelService.addDataApprovalLevel( level2 ); + dataApprovalLevelService.addDataApprovalLevel( level3 ); + dataApprovalLevelService.addDataApprovalLevel( level4 ); + dataApprovalLevelService.addDataApprovalLevel( level5 ); + + Set assignedOrgUnits = new HashSet(); + assignedOrgUnits.add( organisationUnitC ); + + Set dataViewOrgUnits = new HashSet(); + dataViewOrgUnits.add( organisationUnitB ); + + createUserAndInjectSecurityContext( assignedOrgUnits, dataViewOrgUnits, false ); + + Map readApprovalLevels = dataApprovalLevelService.getUserReadApprovalLevels(); + assertEquals( 2, readApprovalLevels.size() ); + + assertEquals( 4, (int) readApprovalLevels.get( organisationUnitC ) ); + assertEquals( 3, (int) readApprovalLevels.get( organisationUnitB ) ); } @Test @@ -581,4 +775,6 @@ assertEquals( "2B", levels.get( 1 ).getName() ); assertEquals( "3", levels.get( 2 ).getName() ); } + */ + } === modified file 'dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java' --- dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java 2014-03-28 01:54:06 +0000 +++ dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java 2014-04-13 20:43:32 +0000 @@ -1193,7 +1193,7 @@ } /** - * @param uniqueCharacter A unique character to identify the object. + * @param uniqueChar A unique character to identify the object. * @return TrackedEntityAttribute */ public static TrackedEntityAttribute createTrackedEntityAttribute( char uniqueChar ) @@ -1208,7 +1208,7 @@ } /** - * @param uniqueCharacter A unique character to identify the object. + * @param uniqueChar A unique character to identify the object. * @return TrackedEntityAttribute */ public static TrackedEntityAttribute createTrackedEntityAttribute( char uniqueChar, String type ) @@ -1223,7 +1223,7 @@ } /** - * @param uniqueCharacter A unique character to identify the object. + * @param uniqueChar A unique character to identify the object. * @return TrackedEntityAttributeGroup */ public static TrackedEntityAttributeGroup createTrackedEntityAttributeGroup( char uniqueChar, List attributes ) @@ -1256,7 +1256,7 @@ } /** - * @param uniqueCharacter A unique character to identify the object. + * @param uniqueChar A unique character to identify the object. * @return RelationshipType */ public static RelationshipType createRelationshipType( char uniqueChar ) @@ -1440,6 +1440,22 @@ */ public User createUserAndInjectSecurityContext( Set organisationUnits, boolean allAuth, String... auths ) { + return createUserAndInjectSecurityContext( organisationUnits, null, allAuth, auths ); + } + + /** + * Creates a user and injects into the security context with username + * "username". Requires identifiableObjectManager and + * userService to be injected into the test. + * + * @param organisationUnits the organisation units of the user. + * @param dataViewOrganisationUnits user's data view organisation units. + * @param allAuth whether to grant the ALL authority. + * @param auths authorities to grant to user. + * @return the user. + */ + public User createUserAndInjectSecurityContext( Set organisationUnits, Set dataViewOrganisationUnits, boolean allAuth, String... auths ) + { Assert.notNull( identifiableObjectManager, "IdentifiableObjectManager must be injected in test" ); Assert.notNull( userService, "UserService must be injected in test" ); @@ -1468,6 +1484,11 @@ user.setOrganisationUnits( organisationUnits ); } + if ( dataViewOrganisationUnits != null ) + { + user.setDataViewOrganisationUnits( dataViewOrganisationUnits ); + } + user.getUserCredentials().getUserAuthorityGroups().add( userAuthorityGroup ); userService.addUser( user ); user.getUserCredentials().setUser( user );