← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 17379: Further approvals improvements.

 

------------------------------------------------------------
revno: 17379
committer: jimgrace@xxxxxxxxx
branch nick: dhis2
timestamp: Thu 2014-11-06 20:55:41 -0500
message:
  Further approvals improvements.
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataapproval/DataApprovalStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DataApprovalPermissionsEvaluator.java
  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/DefaultDataApprovalService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/hibernate/HibernateDataApprovalStore.java
  dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalController.java


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataapproval/DataApprovalStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataapproval/DataApprovalStore.java	2014-10-31 21:01:28 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataapproval/DataApprovalStore.java	2014-11-07 01:55:41 +0000
@@ -86,15 +86,19 @@
         OrganisationUnit organisationUnit, DataElementCategoryOptionCombo attributeOptionCombo );
 
     /**
-     * Returns a list of data approval results and corresponding states for a
-     * given organisation unit, either restricted to one attribute option combo
-     * or for all the category option combos that the user is allowed to see.
+     * Returns a list of data approval results and corresponding states for
+     * a collection of data sets and a given period. The list may be constrained
+     * to a given organisation unit, or it may be all the organisation units
+     * the user is allowed to see. The list may also be constrained to a given
+     * attribute category combination, or it may be all the attribute category
+     * combos the user is allowed to see.
      *
-     * @param orgUnit Organisation unit to look for
      * @param dataSets Data sets to look within
      * @param period Period to look within
-     * @param attributeOptionCombo (optional) attribute option combo to fetch
+     * @param orgUnit Organisation unit to look for (null means all)
+     * @param attributeOptionCombo Attribute option combo (null means all)
      * @return data approval status objects
      */
-    List<DataApprovalStatus> getDataApprovals( OrganisationUnit orgUnit, Set<DataSet> dataSets, Period period, DataElementCategoryOptionCombo attributeOptionCombo );
+    List<DataApprovalStatus> getDataApprovals( Set<DataSet> dataSets, Period period,
+        OrganisationUnit orgUnit, DataElementCategoryOptionCombo attributeOptionCombo );
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DataApprovalPermissionsEvaluator.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DataApprovalPermissionsEvaluator.java	2014-11-04 03:22:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DataApprovalPermissionsEvaluator.java	2014-11-07 01:55:41 +0000
@@ -63,8 +63,6 @@
     private boolean authorizedToAcceptAtLowerLevels;
     private boolean authorizedToViewUnapprovedData;
 
-    private Map<OrganisationUnit, DataApprovalLevel> userOrgUnitApprovalLevelsCache = new HashMap<>();
-
     int maxApprovalLevel;
 
     private DataApprovalPermissionsEvaluator()
@@ -131,11 +129,12 @@
             return permissions; // No permissions are set.
         }
 
-        DataApprovalLevel userApprovalLevel = getUserOrgUnitApprovalLevel( da.getOrganisationUnit() );
+        DataApprovalLevel userApprovalLevel = dataApprovalLevelService.getUserApprovalLevel( user, da.getOrganisationUnit(), false );
 
         if ( userApprovalLevel == null )
         {
-            tracePrint( "getPermissions userApprovalLevel is null" );
+            tracePrint( "getPermissions userApprovalLevel is null for user " + ( user == null ? "(null)" : user.getUsername() )
+                    + " orgUnit " +  da.getOrganisationUnit().getName() );
 
             return permissions; // Can't find user approval level, so no permissions are set.
         }
@@ -180,22 +179,8 @@
         return permissions;
     }
 
-    private DataApprovalLevel getUserOrgUnitApprovalLevel( OrganisationUnit orgUnit )
-    {
-        DataApprovalLevel level = userOrgUnitApprovalLevelsCache.get( orgUnit );
-
-        if ( level == null )
-        {
-            level = dataApprovalLevelService.getUserApprovalLevel( user, orgUnit, false );
-
-            userOrgUnitApprovalLevelsCache.put( orgUnit, level );
-        }
-
-        return level;
-    }
-
     private static void tracePrint( String s )
     {
-//        System.out.println( s );
+        System.out.println( s );
     }
 }

=== 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-11-02 13:30:47 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalLevelService.java	2014-11-07 01:55:41 +0000
@@ -678,7 +678,10 @@
 
         for ( DataApprovalLevel level : getAllDataApprovalLevels() )
         {
-            tracePrint("userApprovalLevel( " + orgUnit.getName() + "-" + orgUnitLevel + " ) approval level " + level.getName() + " " + securityService.canRead( level ) );
+            tracePrint("userApprovalLevel( " + orgUnit.getName() + "-" + orgUnitLevel + " ) approval level "
+                    + level.getName() + " " + securityService.canRead( level )
+                    + " COGS " + ( level.getCategoryOptionGroupSet() == null ? "(null)" : level.getCategoryOptionGroupSet().getName() )
+                    + " canReadCOGS " + canReadCOGS( user, level.getCategoryOptionGroupSet() ) );
 
             if ( level.getOrgUnitLevel() >= orgUnitLevel
                     && securityService.canRead( level )

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalService.java	2014-11-04 03:22:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalService.java	2014-11-07 01:55:41 +0000
@@ -319,29 +319,15 @@
     public DataApprovalStatus getDataApprovalStatus( DataSet dataSet, Period period, OrganisationUnit organisationUnit,
                                                      DataElementCategoryOptionCombo attributeOptionCombo )
     {
-        log.debug( "- getDataApprovalStatus( " + dataSet.getName() + ", "
+        log.debug( "getDataApprovalStatus( " + dataSet.getName() + ", "
                 + period.getPeriodType().getName() + " " + period.getName() + " " + period + ", "
                 + organisationUnit.getName() + ", "
                 + ( attributeOptionCombo == null ? "(null)" : attributeOptionCombo.getName() ) + " )" );
 
         period = periodService.reloadPeriod( period );
 
-        if ( attributeOptionCombo == null )
-        {
-            attributeOptionCombo = categoryService.getDefaultDataElementCategoryOptionCombo();
-        }
-
-        DataApprovalLevel dal = dataApprovalLevelService.getLowestDataApprovalLevel( organisationUnit, attributeOptionCombo );
-
-        if ( dal == null )
-        {
-            log.debug( "Null approval level for " + organisationUnit.getName() + ",  " + attributeOptionCombo.getName() );
-
-            return new DataApprovalStatus( DataApprovalState.UNAPPROVABLE, null, null, null );
-        }
-
-        List<DataApprovalStatus> statuses = dataApprovalStore.getDataApprovals( organisationUnit,
-                CollectionUtils.asSet( dataSet ), period, attributeOptionCombo );
+        List<DataApprovalStatus> statuses = dataApprovalStore.getDataApprovals( CollectionUtils.asSet( dataSet ),
+                period, organisationUnit, attributeOptionCombo );
 
         if ( statuses != null && !statuses.isEmpty() )
         {
@@ -378,7 +364,7 @@
     {
         DataApprovalPermissionsEvaluator permissionsEvaluator = makePermissionsEvaluator();
 
-        List<DataApprovalStatus> statusList = dataApprovalStore.getDataApprovals( orgUnit, dataSets, period, null );
+        List<DataApprovalStatus> statusList = dataApprovalStore.getDataApprovals( dataSets, period, orgUnit, null );
         
         for ( DataApprovalStatus status : statusList )
         {
@@ -437,16 +423,16 @@
         {
             Set<DataSet> dataSets = new HashSet<>();
 
+            for ( DataApproval da : entry.getValue() )
+            {
+                dataSets.add( da.getDataSet() );
+            }
+
             Period period = entry.getValue().get(0).getPeriod();
 
             OrganisationUnit orgUnit = entry.getValue().get(0).getOrganisationUnit();
 
-            for ( DataApproval da : entry.getValue() )
-            {
-                dataSets.add( da.getDataSet() );
-            }
-
-            List<DataApprovalStatus> statuses = dataApprovalStore.getDataApprovals( orgUnit, dataSets, period, null );
+            List<DataApprovalStatus> statuses = dataApprovalStore.getDataApprovals( dataSets, period, orgUnit, null );
 
             for ( DataApprovalStatus status : statuses )
             {
@@ -471,7 +457,7 @@
 
         for ( DataApproval approval : dataApprovalList )
         {
-            String key = approval != null ? approval.getOrganisationUnit().getId() + "-" + approval.getPeriod().getId() : null;
+            String key = approval == null ? null : approval.getOrganisationUnit().getId() + "-" + approval.getPeriod().getId();
             map.putValue( key, approval );
         }
 

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/hibernate/HibernateDataApprovalStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/hibernate/HibernateDataApprovalStore.java	2014-11-04 03:22:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/hibernate/HibernateDataApprovalStore.java	2014-11-07 01:55:41 +0000
@@ -30,6 +30,7 @@
 
 import static org.hisp.dhis.dataapproval.DataApprovalState.*;
 import static org.hisp.dhis.setting.SystemSettingManager.KEY_ACCEPTANCE_REQUIRED_FOR_APPROVAL;
+import static org.hisp.dhis.system.util.CollectionUtils.asSet;
 import static org.hisp.dhis.system.util.ConversionUtils.getIdentifiers;
 import static org.hisp.dhis.system.util.TextUtils.getCommaDelimitedString;
 
@@ -54,6 +55,7 @@
 import org.hisp.dhis.dataapproval.DataApprovalState;
 import org.hisp.dhis.dataapproval.DataApprovalStatus;
 import org.hisp.dhis.dataapproval.DataApprovalStore;
+import org.hisp.dhis.dataelement.DataElementCategoryCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataset.DataSet;
@@ -82,10 +84,14 @@
     implements DataApprovalStore
 {
     private static final Log log = LogFactory.getLog( HibernateDataApprovalStore.class );
-    
+
     private static Cache<Integer, DataElementCategoryOptionCombo> OPTION_COMBO_CACHE = CacheBuilder.newBuilder()
-        .expireAfterAccess( 10, TimeUnit.MINUTES ).initialCapacity( 10000 )
-        .maximumSize( 50000 ).build();
+            .expireAfterAccess( 10, TimeUnit.MINUTES ).initialCapacity( 10000 )
+            .maximumSize( 50000 ).build();
+
+    private static Cache<Integer, OrganisationUnit> ORG_UNIT_CACHE = CacheBuilder.newBuilder()
+            .expireAfterAccess( 10, TimeUnit.MINUTES ).initialCapacity( 10000 )
+            .maximumSize( 50000 ).build();
 
     // -------------------------------------------------------------------------
     // Dependencies
@@ -185,12 +191,16 @@
     }
 
     @Override
-    public List<DataApprovalStatus> getDataApprovals( OrganisationUnit orgUnit, Set<DataSet> dataSets, Period period, DataElementCategoryOptionCombo attributeOptionCombo )
+    public List<DataApprovalStatus> getDataApprovals( Set<DataSet> dataSets, Period period,
+        OrganisationUnit orgUnit, DataElementCategoryOptionCombo attributeOptionCombo )
     {
         final User user = currentUserService.getCurrentUser();
 
         if ( CollectionUtils.isEmpty( dataSets ) )
         {
+            log.warn( " No data sets specified for getting approvals, period " + period.getName()
+                    + " user " + ( user == null ? "(null)" : user.getUsername() ) );
+
             return new ArrayList<>();
         }
 
@@ -198,9 +208,9 @@
 
         Collection<Period> periods;
 
-        if ( period.getPeriodType() == dataSetPeriodType )
+        if ( period.getPeriodType().equals( dataSetPeriodType ) )
         {
-            periods = org.hisp.dhis.system.util.CollectionUtils.asSet( period );
+            periods = asSet( period );
         }
         else if ( period.getPeriodType().getFrequencyOrder() > dataSetPeriodType.getFrequencyOrder() )
         {
@@ -211,6 +221,10 @@
         }
         else
         {
+            log.warn( "Selected period type " + period.getPeriodType().getName() + " is incompatible with data set '"
+                    + dataSets.iterator().next().getName() + "' period type " + dataSetPeriodType.getName()
+                    + " user " + ( user == null ? "(null)" : user.getUsername() ) );
+
             return new ArrayList<>();
         }
 
@@ -224,30 +238,61 @@
             periodIds += ( periodIds.isEmpty() ? "" : ", " ) + periodService.reloadPeriod( p ).getId();
         }
 
+        boolean maySeeDefaultCategoryCombo = user == null || user.getUserCredentials() == null ||
+                ( CollectionUtils.isEmpty( user.getUserCredentials().getCogsDimensionConstraints() )
+                && CollectionUtils.isEmpty( user.getUserCredentials().getCatDimensionConstraints() ) );
+
+        DataElementCategoryCombo defaultCategoryCombo = categoryService.getDefaultDataElementCategoryCombo();
+
+        final String dataSetIds = getCommaDelimitedString( getIdentifiers( DataSet.class, dataSets ) );
+
         Set<Integer> categoryComboIds = new HashSet<>();
 
         for ( DataSet ds : dataSets )
         {
-            if ( ds.isApproveData() )
+            if ( ds.isApproveData() && ( maySeeDefaultCategoryCombo || ds.getCategoryCombo() != defaultCategoryCombo ) )
             {
                 categoryComboIds.add( ds.getCategoryCombo().getId() );
             }
         }
 
-        final String dataSetIds = getCommaDelimitedString( getIdentifiers( DataSet.class, dataSets ) );
+        if ( categoryComboIds.isEmpty() )
+        {
+            log.warn( "No dataset categorycombos to check for approval, user " + ( user == null ? "(null)" : user.getUsername() ) + " datasetIds " + dataSetIds );
+
+            return new ArrayList<>();
+        }
+
         final String dataSetCcIds = TextUtils.getCommaDelimitedString( categoryComboIds );
 
-        final int orgUnitLevel = organisationUnitService.getLevelOfOrganisationUnit( orgUnit );
+        int orgUnitLevel = 1;
+        String orgUnitJoinOn;
+
+        if ( orgUnit != null )
+        {
+            orgUnitLevel = organisationUnitService.getLevelOfOrganisationUnit( orgUnit );
+            orgUnitJoinOn = "o.organisationunitid = " + orgUnit.getId();
+        }
+        else
+        {
+            for ( DataApprovalLevel dal : dataApprovalLevelService.getAllDataApprovalLevels() )
+            {
+                orgUnitLevel = dal.getOrgUnitLevel(); // Get lowest (last level -> greatest number) level.
+            }
+            orgUnitJoinOn = "o.level = " + orgUnitLevel;
+        }
 
         boolean isSuperUser = currentUserService.currentUserIsSuper();
 
         DataApprovalLevel lowestApprovalLevelForOrgUnit = null;
 
-        String orgUnitAncestorLevels = "";
+        String joinAncestors = "";
+        String testAncestors = "";
 
         for ( int i = 1; i < orgUnitLevel; i++ )
         {
-            orgUnitAncestorLevels += ", ous.idlevel" + i;
+            joinAncestors += "left join _orgunitstructure o" + i + " on o" + i + ".idlevel" + orgUnitLevel + " = o.organisationunitid and o" + i + ".idlevel" + i + " = coo.organisationunitid ";
+            testAncestors += "or o" + i + ".organisationunitid is not null ";
         }
 
         String readyBelowSubquery = "true"; // Ready below if this is the lowest (highest number) approval orgUnit level.
@@ -274,7 +319,8 @@
                         "left join dataapproval da on da.organisationunitid = ous.organisationunitid " +
                         "and da.dataapprovallevelid = " + dal.getLevel() + " and da.periodid in (" + periodIds + ") " +
                         "and da.datasetid in (" + dataSetIds + ") " +
-                        "where ous.idlevel" + orgUnitLevel + " = " + orgUnit.getId() + " " +
+                        "and da.attributeoptioncomboid = a.categoryoptioncomboid " +
+                        "where ous.idlevel" + orgUnitLevel + " = a.organisationunitid " +
                         "and ous.level = " + dal.getOrgUnitLevel() + " " +
                         "and ( da.dataapprovalid is null " + ( acceptanceRequiredForApproval ? "or not da.accepted " : "" ) + ") )";
                 break;
@@ -285,50 +331,47 @@
 
         if ( orgUnitLevelAbove > 0 )
         {
-            OrganisationUnit ancestor = orgUnit;
-
-            for ( int i = orgUnitLevelAbove; i < orgUnitLevel; i++ )
-            {
-                ancestor = ancestor.getParent();
-            }
-            approvedAboveSubquery = "exists(select 1 from dataapproval da join dataapprovallevel dal on dal.dataapprovallevelid = da.dataapprovallevelid " +
-                    "where da.periodid in (" + periodIds + ") and da.datasetid in (" + dataSetIds + ") and da.organisationunitid = " + ancestor.getId() + ")";
+            approvedAboveSubquery = "exists(select 1 from dataapproval da " +
+                    "join dataapprovallevel dal on dal.dataapprovallevelid = da.dataapprovallevelid " +
+                    "join _orgunitstructure ou on ou.organisationunitid = a.organisationunitid and ou.idlevel" + orgUnitLevelAbove + " = da.organisationunitid " +
+                    "where da.periodid in (" + periodIds + ") and da.datasetid in (" + dataSetIds + ") and da.attributeoptioncomboid = a.categoryoptioncomboid)";
         }
 
         final String sql =
-                "select ccoc.categoryoptioncomboid, " +
+                "select a.categoryoptioncomboid, a.organisationunitid, " +
                 "(select min(coalesce(dal.level, 0)) from period p " +
                     "join dataset ds on ds.datasetid in (" + dataSetIds + ") " +
-                    "left join dataapproval da on da.datasetid = ds.datasetid and da.periodid = p.periodid and da.organisationunitid = " + orgUnit.getId() + " " +
+                    "left join dataapproval da on da.datasetid = ds.datasetid and da.periodid = p.periodid " +
+                        "and da.attributeoptioncomboid = a.categoryoptioncomboid and da.organisationunitid = a.organisationunitid " +
                     "left join dataapprovallevel dal on dal.dataapprovallevelid = da.dataapprovallevelid " +
                     "where p.periodid in (" + periodIds + ") " +
                 ") as highest_approved_level, " +
                 "(select substring(min(concat(100000 + coalesce(dal.level, 0), coalesce(da.accepted, FALSE))) from 7) from period p " +
                     "join dataset ds on ds.datasetid in (" + dataSetIds + ") " +
-                    "left join dataapproval da on da.datasetid = ds.datasetid and da.periodid = p.periodid and da.organisationunitid = " + orgUnit.getId() + " " +
+                    "left join dataapproval da on da.datasetid = ds.datasetid and da.periodid = p.periodid " +
+                        "and da.attributeoptioncomboid = a.categoryoptioncomboid and da.organisationunitid = a.organisationunitid " +
                     "left join dataapprovallevel dal on dal.dataapprovallevelid = da.dataapprovallevelid " +
                     "where p.periodid in (" + periodIds + ") " +
                 ") as accepted_at_highest_level, " +
                 readyBelowSubquery + " as ready_below, " +
                 approvedAboveSubquery + " as approved_above " +
-                "from categoryoptioncombo coc " +
-                "join categorycombos_optioncombos ccoc on ccoc.categoryoptioncomboid = coc.categoryoptioncomboid and ccoc.categorycomboid in (" + dataSetCcIds + ") " +
-                "where coc.categoryoptioncomboid in ( " + // subquery for category option restriction by date, organisation unit, and sharing
-                    "select distinct coc1.categoryoptioncomboid " +
-                    "from categoryoptioncombo coc1 " +
-                    "join categoryoptioncombos_categoryoptions cocco on cocco.categoryoptioncomboid = coc1.categoryoptioncomboid " +
+                "from ( " + // subquery to get combinations of organisation unit and category option combo
+                    "select distinct cocco.categoryoptioncomboid, o.organisationunitid " +
+                    "from categoryoptioncombos_categoryoptions cocco " +
+                    "join categorycombos_optioncombos ccoc on ccoc.categoryoptioncomboid = cocco.categoryoptioncomboid and ccoc.categorycomboid in (" + dataSetCcIds + ") " +
                     "join dataelementcategoryoption co on co.categoryoptionid = cocco.categoryoptionid " +
-                    "and (co.startdate is null or co.startdate <= '" + maxDate + "') and (co.enddate is null or co.enddate >= '" + minDate + "') " +
+                        "and (co.startdate is null or co.startdate <= '" + maxDate + "') and (co.enddate is null or co.enddate >= '" + minDate + "') " +
+                    "join _orgunitstructure o on " + orgUnitJoinOn + " " +
                     "left join categoryoption_organisationunits coo on coo.categoryoptionid = co.categoryoptionid " +
-                    "left join _orgunitstructure ous on ous.idlevel" + orgUnitLevel + " = " + orgUnit.getId() + " " +
-                    "and coo.organisationunitid in ( ous.organisationunitid" + orgUnitAncestorLevels + " ) " +
+                    "left join _orgunitstructure ous on ous.idlevel" + orgUnitLevel + "= o.organisationunitid and ous.organisationunitid = coo.organisationunitid " +
+                    joinAncestors +
                     "left join dataelementcategoryoptionusergroupaccesses couga on couga.categoryoptionid = cocco.categoryoptionid " +
                     "left join usergroupaccess uga on uga.usergroupaccessid = couga.usergroupaccessid " +
                     "left join usergroupmembers ugm on ugm.usergroupid = uga.usergroupid " +
-                    "where ( coo.categoryoptionid is null or ous.organisationunitid is not null ) " + // no org unit assigned, or matching org unit assigned
-                    ( isSuperUser || user == null ? "" : "and ( ugm.userid = " + user.getId() + " or co.userid = " + user.getId() + " or left(co.publicaccess, 1) = 'r' ) " ) +
-                    ( attributeOptionCombo == null ? "" : "and ccoc.categoryoptioncomboid = " + attributeOptionCombo.getId() + " " ) +
-                ")"; // End of subquery
+                    "where ( coo.categoryoptionid is null or ous.organisationunitid is not null " + testAncestors + ") " +
+                     ( isSuperUser || user == null ? "" : "and ( ugm.userid = " + user.getId() + " or co.userid = " + user.getId() + " or left(co.publicaccess, 1) = 'r' ) " ) +
+                     ( attributeOptionCombo == null ? "" : "and cocco.categoryoptioncomboid = " + attributeOptionCombo.getId() + " " ) +
+                ") as a";
 
         log.info( "Get approval SQL: " + sql );
 
@@ -345,15 +388,17 @@
             while ( rowSet.next() )
             {
                 final Integer aoc = rowSet.getInt( 1 );
-                final Integer level = rowSet.getInt( 2 );
-                final String acceptedString = rowSet.getString( 3 );
-                final boolean readyBelow = rowSet.getBoolean( 4 );
-                final boolean approvedAbove = rowSet.getBoolean( 5 );
+                final Integer ouId = rowSet.getInt( 2 );
+                final Integer level = rowSet.getInt( 3 );
+                final String acceptedString = rowSet.getString( 4 );
+                final boolean readyBelow = rowSet.getBoolean( 5 );
+                final boolean approvedAbove = rowSet.getBoolean( 6 );
 
                 final boolean accepted = ( acceptedString == null ? false : acceptedString.substring( 0, 1 ).equalsIgnoreCase( "t" ) );
 
-                DataApprovalLevel dataApprovalLevel = ( level == null || level == 0 ? lowestApprovalLevelForOrgUnit : levelMap.get( level ) );
-                
+                DataApprovalLevel statusLevel = ( level == null || level == 0 ? null : levelMap.get( level ) ); // null if not approved
+                DataApprovalLevel daLevel = ( statusLevel == null ? lowestApprovalLevelForOrgUnit : statusLevel );
+
                 DataElementCategoryOptionCombo optionCombo = ( aoc == null || aoc == 0 ? null : OPTION_COMBO_CACHE.get( aoc, new Callable<DataElementCategoryOptionCombo>()
                 {
                     public DataElementCategoryOptionCombo call() throws ExecutionException
@@ -362,10 +407,18 @@
                     }
                 } ) );
 
-                DataApproval da = new DataApproval( dataApprovalLevel, dataSet, period, orgUnit, optionCombo, accepted, null, null );
+                OrganisationUnit ou = ( orgUnit != null ? orgUnit : ORG_UNIT_CACHE.get( ouId, new Callable<OrganisationUnit>()
+                {
+                    public OrganisationUnit call() throws ExecutionException
+                    {
+                        return organisationUnitService.getOrganisationUnit( ouId );
+                    }
+                } ) );
+
+                DataApproval da = new DataApproval( daLevel, dataSet, period, ou, optionCombo, accepted, null, null );
 
                 DataApprovalState state = (
-                        level == null || level == 0 ?
+                        statusLevel == null ?
                                 readyBelow ?
                                         UNAPPROVED_READY :
                                         UNAPPROVED_WAITING :
@@ -375,9 +428,10 @@
                                                 ACCEPTED_HERE :
                                                 APPROVED_HERE );
 
-                statusList.add( new DataApprovalStatus( state, da, dataApprovalLevel, null ) );
+                statusList.add( new DataApprovalStatus( state, da, statusLevel, null ) );
 
-                log.debug( "Get approval result: level " + level + " dataApprovalLevel " + dataApprovalLevel
+                log.debug( "Get approval result: level " + level + " dataApprovalLevel " + daLevel.getLevel()
+                        + " approved " + ( statusLevel != null )
                         + " readyBelow " + readyBelow + " approvedAbove " + approvedAbove
                         + " accepted " + accepted + " state " + state.name() + " " + da );
             }

=== modified file 'dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java'
--- dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java	2014-10-31 20:46:12 +0000
+++ dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java	2014-11-07 01:55:41 +0000
@@ -187,6 +187,11 @@
         emptyTable( "indicator" );
         emptyTable( "indicatortype" );
 
+        emptyTable( "categoryoptiongroupsetmembers" );
+        emptyTable( "categoryoptiongroupset" );
+        emptyTable( "categoryoptiongroupmembers" );
+        emptyTable( "categoryoptiongroup" );
+
         emptyTable( "expression" );
         emptyTable( "categoryoptioncombo" );
         emptyTable( "categorycombo" );

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalController.java	2014-11-04 03:22:00 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataApprovalController.java	2014-11-07 01:55:41 +0000
@@ -254,7 +254,7 @@
     @RequestMapping( value = APPROVALS_PATH + "/categoryOptionCombos", method = RequestMethod.GET, produces = ContextUtils.CONTENT_TYPE_JSON )
     public void getApprovalByCategoryOptionCombos( 
         @RequestParam Set<String> ds, 
-        @RequestParam String pe, 
+        @RequestParam String pe,
         HttpServletResponse response ) throws IOException
     {
         Set<DataSet> dataSets = new HashSet<>( objectManager.getByUid( DataSet.class, ds ) );
@@ -266,7 +266,7 @@
             ContextUtils.conflictResponse( response, "Illegal period identifier: " + pe );
             return;
         }
-        
+
         List<DataApprovalStatus> statusList = dataApprovalService.getUserDataApprovalsAndPermissions( dataSets, period, null );
 
         List<Map<String, Object>> list = new ArrayList<>();