← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 21918: Data approval. Impl support for data sets with category combos / attributes.

 

Merge authors:
  Lars Helge Øverland (larshelge)
------------------------------------------------------------
revno: 21918 [merge]
committer: Lars Helge Overland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Tue 2016-02-09 22:30:25 +0100
message:
  Data approval. Impl support for data sets with category combos / attributes.
modified:
  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/resources/i18n_global.properties
  dhis-2/dhis-web/dhis-web-reporting/src/main/java/org/hisp/dhis/reporting/dataset/action/GenerateDataSetReportAction.java
  dhis-2/dhis-web/dhis-web-reporting/src/main/resources/org/hisp/dhis/reporting/i18n_module.properties
  dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/dataApprovalForm.vm
  dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/javascript/dataApproval.js
  dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/style/dhis-web-reporting.css


--
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-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	2016-01-11 03:14:37 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/dataapproval/DefaultDataApprovalService.java	2016-02-09 21:30:25 +0000
@@ -130,7 +130,7 @@
         {
             DataApprovalStatus status = statusMap.get( daKey( da ) );
 
-            if ( da.getDataApprovalLevel() == null ) // Determine the approval level
+            if ( status != null && da.getDataApprovalLevel() == null ) // Determine the approval level
             {
                 if ( status.getState().isApproved() ) // If approved already, approve at next level up (lower level number)
                 {
@@ -470,16 +470,16 @@
         {
             List<DataApproval> dataApprovals = listMap.get( key );
             
-            DataApproval da0 = dataApprovals.get( 0 );
+            DataApproval da = dataApprovals.get( 0 );
 
-            List<DataApprovalStatus> statuses = dataApprovalStore.getDataApprovals( da0.getWorkflow(),
-                da0.getPeriod(), da0.getOrganisationUnit(), null, getCategoryOptionCombos( dataApprovals ) );
+            List<DataApprovalStatus> statuses = dataApprovalStore.getDataApprovals( da.getWorkflow(),
+                da.getPeriod(), da.getOrganisationUnit(), null, getCategoryOptionCombos( dataApprovals ) );
 
             for ( DataApprovalStatus status : statuses )
             {
-                status.setPermissions( evaluator.getPermissions( status, da0.getOrganisationUnit(), da0.getWorkflow() ) );
+                status.setPermissions( evaluator.getPermissions( status, da.getOrganisationUnit(), da.getWorkflow() ) );
 
-                statusMap.put( daKey( da0 ), status );
+                statusMap.put( daKey( da ), status );
             }
         }
 
@@ -511,7 +511,7 @@
      * Approval status with these three values in common can be fetched in
      * one call for many values of attributeOptionCombo.
      */
-    private String statusKey ( DataApproval approval )
+    private String statusKey( DataApproval approval )
     {
         return approval == null ? null : approval.getOrganisationUnit().getId() +
             IdentifiableObjectUtils.SEPARATOR + approval.getPeriod().getId() +

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/i18n_global.properties'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/i18n_global.properties	2016-01-07 16:26:11 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/i18n_global.properties	2016-02-09 21:30:25 +0000
@@ -208,6 +208,8 @@
 variables=Variables
 open=Open
 normal=Normal
+item=Item
+action=Action
 
 #-- Relative periods -----------------------------------------------------------#
 

=== modified file 'dhis-2/dhis-web/dhis-web-reporting/src/main/java/org/hisp/dhis/reporting/dataset/action/GenerateDataSetReportAction.java'
--- dhis-2/dhis-web/dhis-web-reporting/src/main/java/org/hisp/dhis/reporting/dataset/action/GenerateDataSetReportAction.java	2016-02-08 15:37:12 +0000
+++ dhis-2/dhis-web/dhis-web-reporting/src/main/java/org/hisp/dhis/reporting/dataset/action/GenerateDataSetReportAction.java	2016-02-08 20:47:34 +0000
@@ -150,6 +150,10 @@
         this.ou = ou;
     }
 
+    /**
+     * Dimensional parameters, follows the standard analytics format, e.g.
+     * <dim-id>:<dim-item>;<dim-item>
+     */
     private Set<String> dimension;
 
     public void setDimension( Set<String> dimension )

=== modified file 'dhis-2/dhis-web/dhis-web-reporting/src/main/resources/org/hisp/dhis/reporting/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-reporting/src/main/resources/org/hisp/dhis/reporting/i18n_module.properties	2015-09-26 16:08:33 +0000
+++ dhis-2/dhis-web/dhis-web-reporting/src/main/resources/org/hisp/dhis/reporting/i18n_module.properties	2016-02-09 21:30:25 +0000
@@ -6,30 +6,32 @@
 select_data_set= Please select a data set
 select_period=Please select a period
 select_organisation_unit=Please select an organisation unit
-report_organisation_unit= Report organisation unit
-dataset_report= Data Set Report
+select_items=Select items
+select_items_for=Select items for
+report_organisation_unit=Report organisation unit
+dataset_report=Data Set Report
 data_approval=Data Approval
-add_selected= Add selected
-add_all= Add all
+add_selected=Add selected
+add_all=Add all
 add=Add
-create_report_table= Create report table
-available_indicators= Available indicators
-select_indicatorgroup_all= Select indicator group / View all
-selected_indicators= Selected indicators
+create_report_table=Create report table
+available_indicators=Available indicators
+select_indicatorgroup_all=Select indicator group / View all
+selected_indicators=Selected indicators
 select_organisationunit_level_all=Select organsation unit level / View all
 select_option_view_all=Select option / View all
-level= Level
-available_organisationunits= Available organisation units
-selected_organisationunits= Selected organisation units
-available_periods= Available periods
-selected_periods= Selected periods
+level=Level
+available_organisationunits=Available organisation units
+selected_organisationunits=Selected organisation units
+available_periods=Available periods
+selected_periods=Selected periods
 add_children= Add children
-create= Create
-indicators= Indicators
-organisation_units= Organisation units
-periods= Periods
-relative_periods= Relative periods
-reporting_month= Reporting month
+create=Create
+indicators=Indicators
+organisation_units=Organisation units
+periods=Periods
+relative_periods=Relative periods
+reporting_month=Reporting month
 this_year=This year
 reporting_sixmonth=Reporting six-month
 last_2_sixmonths=Last 2 six-months
@@ -40,52 +42,53 @@
 quarters_last_year=Quarters last year
 last_year=Last year
 report_table= Report Table
-crosstab_dimensions= Crosstab dimensions
-confirm_delete_table= Are you sure you want to remove table?
-operations= Operations
-done= Done
-crosstab_indicators= Crosstab indicators
-crosstab_periods= Crosstab periods
-crosstab_organisation_units= Crosstab organisation units
+crosstab_dimensions=Crosstab dimensions
+confirm_delete_table=Are you sure you want to remove table?
+operations=Operations
+organisation_unit=Organisation unit
+done=Done
+crosstab_indicators=Crosstab indicators
+crosstab_periods=Crosstab periods
+crosstab_organisation_units=Crosstab organisation units
 must_select_at_least_one_indictor_data_element_data_set=Please select at least one indicator, data element or data set
 must_select_at_least_one_unit= Please select at least one organisation unit
-must_select_at_least_one_period= Please select at least one period
+must_select_at_least_one_period=Please select at least one period
 cannot_include_more_organisation_unit_regression=Cannot include more that one organisation unit with regression
-cannot_select_orgunit_and_parent_orgunit_param= Cannot select both current and parent organisation unit parameter
-must_enter_name= Please enter a name
-specify_name= Please specify a name
-name_in_use= The name is already in use
+cannot_select_orgunit_and_parent_orgunit_param=Cannot select both current and parent organisation unit parameter
+must_enter_name=Please enter a name
+specify_name=Please specify a name
+name_in_use=The name is already in use
 design=Design
-home= Home
-directory= Directory
-create_new_report= Create new report
-create_report= Create report
-standard_report= Standard Report
-confirm_remove_report= Are you sure you will delete the report?
-report= Report
-completed= completed
-remove_report= Remove report
-submit= Submit
-edit_report= Edit report
-processing= Processing
+home=Home
+directory=Directory
+create_new_report=Create new report
+create_report=Create report
+standard_report=Standard Report
+confirm_remove_report=Are you sure you will delete the report?
+report=Report
+completed=completed
+remove_report=Remove report
+submit=Submit
+edit_report=Edit report
+processing=Processing
 indicators_data_elements_data_sets=Indicators / data elements / data sets
-dataset= Data set
-organisation_unit= Organisation unit
-select_dataset_all= Select data set / View all
-monthly= Monthly
-quarterly= Quarterly
-yearly= Yearly
-omit_report_table_explanation= If selecting no report table, the user is responsible for providing the data source
-dataset_completeness= Data set Completeness
-report_parameters= Report parameters
-parent_organisation_unit= Parent organisation unit
-available_dataelements= Available data elements
-select_dataelementgroup_all= Select data element group / View all
-selected_dataelements= Selected data elements
-dataelements= Data elements
-report_table_parameters= Report table parameters
-months_this_year= Months this year
-quarters_this_year= Quarters this year
+dataset=Data set
+organisation_unit=Organisation unit
+select_dataset_all=Select data set / View all
+monthly=Monthly
+quarterly=Quarterly
+yearly=Yearly
+omit_report_table_explanation=If selecting no report table, the user is responsible for providing the data source
+dataset_completeness=Data set Completeness
+report_parameters=Report parameters
+parent_organisation_unit=Parent organisation unit
+available_dataelements=Available data elements
+select_dataelementgroup_all=Select data element group / View all
+selected_dataelements=Selected data elements
+dataelements=Data elements
+report_table_parameters=Report table parameters
+months_this_year=Months this year
+quarters_this_year=Quarters this year
 add_report_table=Add Report Table
 add_dataelement_dimension_table=Add Dataelement Dimension Table
 generate= Generate
@@ -239,6 +242,7 @@
 unapprove=Unapprove
 accept=Accept
 unaccept=Unaccept
+items=items
 waiting_for_lower_level_approval=Waiting for lower levels to approve
 approved_at_a_higher_level=Approved at a higher level
 approved=Approved
@@ -246,6 +250,7 @@
 ready_for_approval_at_a_higher_level=Ready for approval at a higher level
 ready_for_approval=Ready for approval
 approval_not_relevant=Approval not relevant
+nothing_to_do=Nothing to do
 waiting_for_approval_elsewhere=Waiting for approval elsewhere
 approved_elsewhere=Approved elsewhere
 accepted_elsewhere=Accepted elsewhere

=== modified file 'dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/dataApprovalForm.vm'
--- dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/dataApprovalForm.vm	2016-02-08 15:37:12 +0000
+++ dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/dataApprovalForm.vm	2016-02-09 21:30:25 +0000
@@ -15,8 +15,14 @@
 var i18n_select_period_type = '$encoder.jsEscape( $i18n.getString( "select_period_type" ), "'")';
 var i18n_select_period = '$encoder.jsEscape( $i18n.getString( "select_period" ), "'")';
 var i18n_select_organisation_unit = '$encoder.jsEscape( $i18n.getString( "select_organisation_unit" ), "'")';
+var i18n_select_items = '$encoder.jsEscape( $i18n.getString( "select_items" ), "'")';
+var i18n_select_items_for = '$encoder.jsEscape( $i18n.getString( "select_items_for" ), "'")';
+var i18n_item = '$encoder.jsEscape( $i18n.getString( "item" ), "'")';
+var i18n_organisation_unit = '$encoder.jsEscape( $i18n.getString( "organisation_unit" ), "'")';
+var i18n_action = '$encoder.jsEscape( $i18n.getString( "action" ), "'")';
 var i18n_generating_report = '$encoder.jsEscape( $i18n.getString( "generating_report" ), "'")';    
 var i18n_select_option_view_all = '$encoder.jsEscape( $i18n.getString( "select_option_view_all" ) , "'")';
+var i18n_nothing_to_do = '$encoder.jsEscape( $i18n.getString( "nothing_to_do" ) , "'")';
 var i18n_waiting_for_lower_level_approval = '$encoder.jsEscape( $i18n.getString( "waiting_for_lower_level_approval" ) , "'")';
 var i18n_ready_for_approval_at_a_higher_level = '$encoder.jsEscape( $i18n.getString( "ready_for_approval_at_a_higher_level" ) , "'")';
 var i18n_ready_for_approval = '$encoder.jsEscape( $i18n.getString( "ready_for_approval" ) , "'")';
@@ -35,6 +41,11 @@
 var i18n_confirm_unapproval = '$encoder.jsEscape( $i18n.getString( "confirm_unapproval" ) , "'")';
 var i18n_confirm_accept = '$encoder.jsEscape( $i18n.getString( "confirm_accept" ) , "'")';
 var i18n_confirm_unaccept = '$encoder.jsEscape( $i18n.getString( "confirm_unaccept" ) , "'")';
+var i18n_approve = '$encoder.jsEscape( $i18n.getString( "approve" ), "'")';
+var i18n_unapprove = '$encoder.jsEscape( $i18n.getString( "i18n_unapprove" ), "'")';
+var i18n_accept = '$encoder.jsEscape( $i18n.getString( "accept" ), "'")';
+var i18n_unaccept = '$encoder.jsEscape( $i18n.getString( "unaccept" ), "'")';
+var i18n_items = '$encoder.jsEscape( $i18n.getString( "items" ), "'")';
 </script>
 
 <style type="text/css">
@@ -83,7 +94,8 @@
 <select id="dataSetId" name="dataSetId" style="width:330px" onchange="dhis2.appr.dataSetSelected()">
   <option value="">[ $i18n.getString( "select" ) ]</option>
   #foreach( $dataSet in $dataSets )
-  <option value="$dataSet.uid" data-pt="${dataSet.periodType.name}" data-cc="${dataSet.hasCategoryCombo()}">
+  <option value="$dataSet.uid" data-pt="${dataSet.periodType.name}" data-cc="${dataSet.categoryCombo.uid}" 
+    data-hc="${dataSet.hasCategoryCombo()}" data-cn="${dataSet.categoryCombo.optionCombos.size()}">
   $encoder.htmlEncode( $dataSet.displayName )
   </option>
   #end
@@ -108,6 +120,11 @@
 #parse( "/dhis-web-commons/ouwt/orgunittree.vm" )
 </div>
 
+<!-- Attribute option combo -->
+
+<div id="attributeOptionComboDiv" class="inputSection" style="display:none">
+</div>
+
 <div class="inputSection">
 <input type="button" value='$i18n.getString( "get_data" )' style="width:120px" onclick="dhis2.appr.generateDataReport()">
 <input type="button" value='$i18n.getString( "cancel" )' style="width:120px" onclick="dhis2.dsr.hideCriteria()">
@@ -119,3 +136,15 @@
 
 <div id="content"></div>
 
+<!-- Attribute option combo dialog -->
+
+<div id="attributeOptionComboDialog" class="page" style="display:none">
+<div id="attributeOptionComboHeaderDiv"></div>
+<div id="attributeOptionComboItemDiv"></div>
+<div id="attributeOptionComboButtonDiv">
+  <input type="button" value="$i18n.getString( 'done' )" style="width:80px" onclick='javascript:dhis2.appr.closeItemsDialog()'>
+  <span style="padding-left: 10px"><a href="javascript:dhis2.appr.selectAllItems()">$i18n.getString( 'select_all' )</a></span>
+  <span style="padding-left: 10px"><a href="javascript:dhis2.appr.unselectAllItems()">$i18n.getString( 'unselect_all' )</a></span>
+</div>
+</div>
+

=== modified file 'dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/javascript/dataApproval.js'
--- dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/javascript/dataApproval.js	2016-02-08 15:37:12 +0000
+++ dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/javascript/dataApproval.js	2016-02-09 21:30:25 +0000
@@ -3,12 +3,39 @@
 
 dhis2.appr.currentPeriodOffset = 0;
 dhis2.appr.permissions = null;
+dhis2.appr.dataSets = {};
+
+/**
+ * Object with properties: ds, pe, ou, array of approvals with aoc, ou.
+ */
+dhis2.appr.uiState = {};
 
 //------------------------------------------------------------------------------
 // Report
 //------------------------------------------------------------------------------
 
 /**
+ * Page init.
+ */
+$( document ).ready( function() 
+{
+	$.getJSON( "../api/dataSets.json?fields=id,displayName,periodType,workflow,categoryCombo[id,displayName]", function( json ) {
+				
+		var dsHtml = "<option value=''>[ Select ]</option>";
+		
+		$.each( json.dataSets, function( inx, ds ) {
+			if ( ds.workflow ) {
+				ds.hasCategoryCombo = !!( ds.categoryCombo.displayName !== "default" );
+				dsHtml += "<option value='" + ds.id + "'>" + ds.displayName + "</option>";
+				dhis2.appr.dataSets[ds.id] = ds;
+			}
+		} );
+		
+		$( "#dataSetId" ).html( dsHtml );
+	} );
+} );
+
+/**
  * Callback for changes in data set. Displays a list of period types starting
  * with the data set's period as the shortest type, and including all longer
  * types so that approvals can be made for multiple periods. If there is a
@@ -17,7 +44,8 @@
  */
 dhis2.appr.dataSetSelected = function()
 {
-    var dataSetPeriodType = $( "#dataSetId :selected" ).data( "pt" );
+    var ds = dhis2.appr.dataSets[$( "#dataSetId :selected" ).val()];
+    var dataSetPeriodType = dhis2.appr.dataSets[ds.id].periodType;
 
     if ( $( "#periodType" ).val() != dataSetPeriodType ) {
         var periodTypeToSelect = $( "#periodType" ).val() || dataSetPeriodType;
@@ -29,7 +57,8 @@
                 var selected = ( this == periodTypeToSelect ) ? " selected" : "";
                 html += "<option value='" + this + "'" + selected + ">" + this + "</option>";
                 foundDataSetPeriodType = true;
-            } else if ( this == periodTypeToSelect ) {
+            } 
+            else if ( this == periodTypeToSelect ) {
                 periodTypeToSelect = dataSetPeriodType;
             }
         } );
@@ -38,7 +67,164 @@
         $( "#periodType" ).removeAttr( "disabled" );
         dhis2.appr.displayPeriods();
     }
-}
+    
+    dhis2.appr.setCategoryComboDiv( ds );
+    dhis2.appr.clearItemsDialog();
+};
+
+/**
+ * Sets the category combo div HTML content. 
+ * @param ds a data set object.
+ */
+dhis2.appr.setCategoryComboDiv = function( ds )
+{
+	if ( ds.hasCategoryCombo ) {
+		var cc = ds.categoryCombo;
+		var html = cc.displayName + "<br><a href='javascript:dhis2.appr.showItemsDialog()'>" + i18n_select_items + "</a>";
+		$( "#attributeOptionComboDiv" ).show().html( html );
+	}
+	else {
+		$( "#attributeOptionComboDiv" ).html( "" ).hide();
+	}
+}
+
+/**
+ * Sets the attribute option combo div HTML content.
+ */
+dhis2.appr.setItemsDialog = function()
+{
+	dhis2.appr.uiState = dhis2.appr.getUiState();
+	ui = dhis2.appr.uiState;
+	
+	if ( !ui.ds || !ui.pe || !ui.ou ) {
+		return false;
+	}
+		
+	var cc = dhis2.appr.dataSets[ui.ds].categoryCombo,
+		ccUrl = "../api/categoryCombos/" + cc.id + ".json?fields=id,displayName,categoryOptionCombos[id,displayName]",
+		apUrl = "../api/dataApprovals/categoryOptionCombos?ds=" + ui.ds + "&pe=" + ui.pe + "&ou=" + ui.ou,
+		cocs = {},
+		ccName,
+		html;
+	
+	$.getJSON( ccUrl, function( cc ) {
+		
+		ccName = cc.displayName.toLowerCase();
+		
+		$( "#attributeOptionComboHeaderDiv" ).html( "<h4 style='margin: 12px 0'>" + i18n_select_items_for + " " + ccName + "</h4>" );
+		
+		$.each( cc.categoryOptionCombos, function( inx, coc ) {
+			cocs[coc.id] = coc;
+		} );
+		
+		$.getJSON( apUrl, function( approvals ) {
+		
+			html = "<table id='attributeOptionComboTable'>";
+			html += "<tr><th>" + i18n_item + "</th><th>" + i18n_organisation_unit + "</th><th>" + i18n_action + "</th></tr>";
+			
+			$.each( approvals, function( inx, ap ) {
+				
+				var cocName = cocs[ap.id].displayName,
+					pm = ap.permissions;
+				
+				html += "<tr>";
+				html += "<td><input type='checkbox' class='itemCheckbox' id='coc-" + ap.id + "-" + ap.ou + "' data-coc='" + ap.id + "' data-ou='" + ap.ou + "' ";
+				html += "data-approve='" + pm.mayApprove + "' data-unapprove='" + pm.mayUnapprove + "' data-accept='" + pm.mayAccept + "' data-unaccept='" + pm.mayUnaccept + "'>";
+				html += "<label for='coc-" + ap.id + "-" + ap.ou + "'>" + cocName + "</label></td>";
+				html += "<td>" + ap.ou + "</td>";
+				html += "<td>" + dhis2.appr.getPermissions( ap ) + "</td>";
+				html += "</tr>";
+			} );
+			
+			html += "</table>";
+			
+			$( "#attributeOptionComboItemDiv" ).html( html );
+		} );
+	} );
+};
+
+/**
+ * Supportive method for getting the permission text.
+ * @param ap a data approval object. 
+ */
+dhis2.appr.getPermissions = function( ap )
+{
+	var s = "";
+	
+	if ( ap.permissions.mayApprove ) {
+		s += i18n_approve + ", ";
+	}
+	if ( ap.permissions.mayUnapprove ) {
+		s += i18n_unapprove + ", ";
+	}
+	if ( ap.permissions.mayAccept ) {
+		s += i18n_accept + ", ";
+	}
+	if ( ap.permissions.mayUnaccept ) {
+		s += i18n_unaccept + ", ";
+	}
+	
+	if ( s.length > 2 ) {
+		s = s.slice(0,-2);
+	}
+	else {
+		s = "Pending";
+	}
+	
+	return s;
+};
+
+/**
+ * Checks all attribute option combo checkboxes.
+ */
+dhis2.appr.selectAllItems = function()
+{
+	$( ".itemCheckbox" ).prop( "checked", true );
+};
+
+/**
+ * Unchecks all attribute option combo checkboxes.
+ */
+dhis2.appr.unselectAllItems = function()
+{
+	$( ".itemCheckbox" ).prop( "checked", false );
+};
+
+/**
+ * Opens the attribute option combo dialog. Sets the attribute option
+ * combos if necessary.
+ */
+dhis2.appr.showItemsDialog = function()
+{
+	var c = $( "#attributeOptionComboItemDiv" ).html();
+	
+	if ( $( "#attributeOptionComboItemDiv" ).html().length == 0 ) {
+		dhis2.appr.setItemsDialog();
+	}
+	
+	$( "#attributeOptionComboDialog" ).dialog( {
+		modal : true,
+		width : 700,
+		height : 600,
+		title : i18n_select_items
+	} );
+};
+
+/**
+ * Clears the attribute option combo dialog content.
+ */
+dhis2.appr.clearItemsDialog = function()
+{
+	$( "#attributeOptionComboItemDiv" ).html( "" );
+}
+
+/**
+ * Closes the attribute option combo dialog.
+ */
+dhis2.appr.closeItemsDialog = function()
+{
+	$( "#attributeOptionComboDialog" ).dialog( "close" );
+};
 
 dhis2.appr.displayPeriods = function()
 {
@@ -63,19 +249,97 @@
 
 dhis2.appr.periodSelected = function()
 {
-};
-
+	dhis2.appr.clearItemsDialog();
+};
+
+dhis2.appr.orgUnitSelected = function()
+{
+	dhis2.appr.clearItemsDialog();
+};
+
+/**
+ * Gets the state of the selected UI values.
+ */
 dhis2.appr.getDataReport = function()
 {	
-    var dataReport = {
-        ds: $( "#dataSetId" ).val(),
-        pe: $( "#periodId" ).val(),
-        ou: selection.getSelected()[0]
-    };
+	var ui = dhis2.appr.getUiState(),
+		aocs = [],
+		aoc;
+	
+	var dataReport = {
+		ds: ui.ds,
+		pe: ui.pe,
+		ou: ui.ou
+	};
         
+    $.each( $( ".itemCheckbox:checkbox:checked" ), function() {
+		aoc = $( this ).data( "coc" );
+		
+		if ( aocs.indexOf( aoc ) == -1 ) {
+			aocs.push( aoc );
+		}
+	} );
+		
+	if ( aocs && aocs.length ) {
+		dataReport.dimension = "ao:" + aocs.join( ";" );
+	}	
+    
     return dataReport;
 };
 
+/**
+ * Gets the state of the user interface selections.
+ */
+dhis2.appr.getUiState = function()
+{		
+	var ui = {
+		ds: $( "#dataSetId" ).val(),
+		pe: $( "#periodId" ).val(),
+		ou: selection.getSelected()[0],
+		approvals: []
+	};
+    
+    $.each( $( ".itemCheckbox:checkbox:checked" ), function() {		
+		ui.approvals.push( {
+			aoc: $( this ).data( "coc" ),
+			ou: $( this ).data( "ou" ),
+			permissions: {
+				mayApprove: JSON.parse( $( this ).data( "approve" ) ),
+				mayUnapprove: JSON.parse( $( this ).data( "unapprove" ) ),
+				mayAccept: JSON.parse( $( this ).data( "accept" ) ),
+				mayUnaccept: JSON.parse( $( this ).data( "unaccept" ) )
+			}				
+		} );
+	} );
+	    
+    return ui;
+};
+
+/**
+ * Gets an approval payload object based on the given state.
+ * @param ui the ui state.
+ */
+dhis2.appr.getApprovalPayload = function( ui ) {
+	
+	var json = {
+		ds: [ui.ds],
+		pe: [ui.pe],
+		approvals: []
+	};
+	
+	$.each( ui.approvals, function( inx, ap ) {
+		json.approvals.push( {
+			ou: ap.ou,
+			aoc: ap.aoc
+		} );
+	} );
+	
+	return json;
+};	
+
+/**
+ * Generates the data set report for the currently selected parameters.
+ */
 dhis2.appr.generateDataReport = function()
 {
 	var dataReport = dhis2.appr.getDataReport();
@@ -85,16 +349,20 @@
 		setHeaderDelayMessage( i18n_select_data_set );
         return false;
     }
+    
     if ( !dataReport.pe )
     {
     	setHeaderDelayMessage( i18n_select_period );
         return false;
     }
+    
     if ( !selection.isSelected() )
     {
     	setHeaderDelayMessage( i18n_select_organisation_unit );
         return false;
     }
+    
+    dhis2.appr.uiState = dhis2.appr.getUiState();
 
     hideHeaderMessage();
     dhis2.appr.hideCriteria();
@@ -111,6 +379,9 @@
     } );
 };
 
+/**
+ * Hides the data criteria div.
+ */
 dhis2.appr.hideCriteria = function()
 {
 	$( "#criteria" ).hide( "fast" );
@@ -121,13 +392,99 @@
 // Approval
 //------------------------------------------------------------------------------
 
+/**
+ * Sets the state of the approval buttons and notification.
+ */
 dhis2.appr.setApprovalState = function()
 {
-	var data = dhis2.appr.getDataReport();
-	
-    $( "#approvalDiv" ).hide();
-		
-	$.getJSON( "../api/dataApprovals", data, function( json ) {	
+	var ui = dhis2.appr.uiState,
+		ds = dhis2.appr.dataSets[ui.ds];
+	
+	if ( ds.hasCategoryCombo ) {
+		dhis2.appr.setAttributeOptionComboApprovalState();
+	}
+	else {
+		dhis2.appr.setRegularApprovalState();
+	}
+};
+
+/**
+ * Sets the state of the approval buttons and notification for data sets
+ * with category combinations.
+ */
+dhis2.appr.setAttributeOptionComboApprovalState = function() 
+{	
+    var ui = dhis2.appr.uiState,
+		notification = "";
+    
+    var counts = {
+		approve: 0,
+		unapprove: 0,
+		accept: 0,
+		unaccept: 0
+	};
+    
+    var param = {
+		ds: ui.ds,
+		pe: ui.pe,
+		ou: ui.ou
+	};
+	
+	$.each( ui.approvals, function( inx, ap ) {
+		if ( ap.permissions.mayApprove ) {
+			counts.approve++;
+		}
+		if ( ap.permissions.mayUnapprove ) {
+			counts.unapprove++;
+		}
+		if ( ap.permissions.mayAccept ) {
+			counts.accept++;
+		}
+		if ( ap.permissions.mayUnaccept ) {
+			counts.unaccept++;
+		}
+	} );
+	
+	if ( counts.approve > 0 ) {
+		notification += i18n_approve + ": " + counts.approve + " items, ";
+        $( "#approveButton" ).show();
+    }
+	if ( counts.unapprove > 0 ) {
+		notification += i18n_unapprove + ": " + counts.unapprove + " items, ";
+        $( "#unapproveButton" ).show();
+    }
+	if ( counts.accept > 0 ) {
+		notification += i18n_accept + ": " + counts.accept + " items, ";
+        $( "#acceptButton" ).show();
+    }
+	if ( counts.unaccept > 0 ) {
+		notification += i18n_unaccept + ": " + counts.unaccept + " items, ";
+        $( "#unacceptButton" ).show();
+    }
+    
+    if ( notification && notification.length > 2 ) {
+		notification = notification.slice( 0, -2 );
+		$( "#approvalNotification" ).html( notification );
+	}
+	else {
+		$( "#approvalNotification" ).html( i18n_nothing_to_do );
+	}	
+};
+
+/**
+ * Sets the state of the approval buttons and notification for regular
+ * data sets.
+ */
+dhis2.appr.setRegularApprovalState = function( ui ) 
+{    
+    var ui = dhis2.appr.uiState;
+    var param = {
+		ds: ui.ds,
+		pe: ui.pe,
+		ou: ui.ou
+	};
+	
+	$.getJSON( "../api/dataApprovals", param, function( json ) {	
 		if ( !json || !json.state ) {
 			return;
 		}
@@ -157,7 +514,6 @@
 		        $( "#approvalNotification" ).html( i18n_ready_for_approval );
 
 		        if ( json.mayApprove ) {
-		            $( "#approvalDiv" ).show();
 		            $( "#approveButton" ).show();
 		        }
 		        
@@ -167,12 +523,10 @@
                 $( "#approvalNotification" ).html( i18n_approved_for_part_of_this_period );
 
                 if ( json.mayApprove ) {
-                    $( "#approvalDiv" ).show();
                     $( "#approveButton" ).show();
                 }
 
                 if ( json.mayUnapprove )  {
-                    $( "#approvalDiv" ).show();
                     $( "#unapproveButton" ).show();
                 }
 
@@ -186,12 +540,10 @@
 		        $( "#approvalNotification" ).html( i18n_approved );
 		        
 		        if ( json.mayUnapprove )  {
-		            $( "#approvalDiv" ).show();
 		            $( "#unapproveButton" ).show();
 		        }
 		        
 		        if ( json.mayAccept )  {
-		            $( "#approvalDiv" ).show();
 		            $( "#acceptButton" ).show();
 		        }
 		        
@@ -209,17 +561,14 @@
                 $( "#approvalNotification" ).html( i18n_accepted_for_part_of_this_period );
 
                 if ( json.mayUnapprove )  {
-                    $( "#approvalDiv" ).show();
                     $( "#unapproveButton" ).show();
                 }
 
                 if ( json.mayAccept )  {
-                    $( "#approvalDiv" ).show();
                     $( "#acceptButton" ).show();
                 }
 
                 if ( json.mayUnaccept )  {
-                    $( "#approvalDiv" ).show();
                     $( "#unacceptButton" ).show();
                 }
 
@@ -229,12 +578,10 @@
 		        $( "#approvalNotification" ).html( i18n_approved_and_accepted );
 		        
 		        if ( json.mayUnapprove )  {
-		            $( "#approvalDiv" ).show();
 		            $( "#unapproveButton" ).show();
 		        }
 		        
 		        if ( json.mayUnaccept )  {
-		            $( "#approvalDiv" ).show();
 		            $( "#unacceptButton" ).show();
 		        }
 		        
@@ -252,88 +599,166 @@
 		} );	
 };
 
+/**
+ * Approve data.
+ */
 dhis2.appr.approveData = function()
 {
 	if ( !confirm( i18n_confirm_approval ) ) {
 		return false;
 	}
 	
-	$.ajax( {
-		url: dhis2.appr.getApprovalUrl(),
-		type: "post",
-		success: function() {
-            dhis2.appr.setApprovalState();
-        },
-		error: function( xhr, status, error ) {
-			alert( xhr.responseText );
-		}
-	} );
+	var ui = dhis2.appr.getUiState(),
+		ds = dhis2.appr.dataSets[ui.ds],
+		json = dhis2.appr.getApprovalPayload( ui );
+	
+	if ( ds.hasCategoryCombo ) {
+		$.ajax( {
+			url: "../api/dataApprovals/approvals",
+			type: "post",
+			contentType: "application/json",
+			data: JSON.stringify( json ),
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );			
+	}
+	else {
+		$.ajax( {
+			url: "../api/dataApprovals?ds=" + ui.ds + "&pe=" + ui.pe + "&ou=" + ui.ou,
+			type: "post",
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );
+	}
 };
 
+/**
+ * Unapprove data.
+ */
 dhis2.appr.unapproveData = function()
 {
 	if ( !confirm( i18n_confirm_unapproval ) ) {
 		return false;
 	}
 
-	$.ajax( {
-		url: dhis2.appr.getApprovalUrl(),
-		type: "delete",
-		success: function() {
-            dhis2.appr.setApprovalState();
-        },
-		error: function( xhr, status, error ) {
-			alert( xhr.responseText );
-		}
-	} );
+	var ui = dhis2.appr.getUiState(),
+		ds = dhis2.appr.dataSets[ui.ds],
+		json = dhis2.appr.getApprovalPayload( ui );
+	
+	if ( ds.hasCategoryCombo ) {
+		$.ajax( {
+			url: "../api/dataApprovals/unapprovals",
+			type: "post",
+			contentType: "application/json",
+			data: JSON.stringify( json ),
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );
+	}
+	else {
+		$.ajax( {
+			url: "../api/dataApprovals?ds=" + ui.ds + "&pe=" + ui.pe + "&ou=" + ui.ou,
+			type: "delete",
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );
+	}
 };
 
+/**
+ * Accept data.
+ */
 dhis2.appr.acceptData = function()
 {
     if ( !confirm( i18n_confirm_accept ) ) {
         return false;
     }
 
-    $.ajax( {
-		url: dhis2.appr.getAcceptanceUrl(),
-        type: "post",
-        success: function() {
-            dhis2.appr.setApprovalState();
-        },
-        error: function( xhr, status, error ) {
-            alert( xhr.responseText );
-        }
-    } );
+	var ui = dhis2.appr.getUiState(),
+		ds = dhis2.appr.dataSets[ui.ds],
+		json = dhis2.appr.getApprovalPayload( ui );
+	
+	if ( ds.hasCategoryCombo ) {
+		$.ajax( {
+			url: "../api/dataAcceptances/acceptances",
+			type: "post",
+			contentType: "application/json",
+			data: JSON.stringify( json ),
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );
+	}
+	else {
+		$.ajax( {
+			url: "../api/dataAcceptances?ds=" + ui.ds + "&pe=" + ui.pe + "&ou=" + ui.ou,
+			type: "post",
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );
+	}
 };
 
+/**
+ * Unaccept data.
+ */
 dhis2.appr.unacceptData = function()
 {
     if ( !confirm( i18n_confirm_unaccept ) ) {
         return false;
     }
 
-    $.ajax( {
-		url: dhis2.appr.getAcceptanceUrl(),
-        type: "delete",
-        success: function() {
-            dhis2.appr.setApprovalState();
-        },
-        error: function( xhr, status, error ) {
-            alert( xhr.responseText );
-        }
-  } );
-};
-
-dhis2.appr.getApprovalUrl = function()
-{
-	var data = dhis2.appr.getDataReport();
-	var url = "../api/dataApprovals?ds=" + data.ds + "&pe=" + data.pe + "&ou=" + data.ou;
-	return url;
-};
-
-dhis2.appr.getAcceptanceUrl = function()
-{
-	var data = dhis2.appr.getDataReport();
-	var url = "../api/dataAcceptances?ds=" + data.ds + "&pe=" + data.pe + "&ou=" + data.ou;
-	return url;
+	var ui = dhis2.appr.getUiState(),
+		ds = dhis2.appr.dataSets[ui.ds],
+		json = dhis2.appr.getApprovalPayload( ui );
+		
+	if ( ds.hasCategoryCombo ) {
+		$.ajax( {
+			url: "../api/dataAcceptances/unacceptances",
+			type: "post",
+			contentType: "application/json",
+			data: JSON.stringify( json ),
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );
+	}
+	else {
+		$.ajax( {
+			url: "../api/dataAcceptances?ds=" + ui.ds + "&pe=" + ui.pe + "&ou=" + ui.ou,
+			type: "delete",
+			success: function() {
+				dhis2.appr.setApprovalState();
+			},
+			error: function( xhr, status, error ) {
+				alert( xhr.responseText );
+			}
+		} );
+	}
 };

=== modified file 'dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/style/dhis-web-reporting.css'
--- dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/style/dhis-web-reporting.css	2014-05-26 10:46:17 +0000
+++ dhis-2/dhis-web/dhis-web-reporting/src/main/webapp/dhis-web-reporting/style/dhis-web-reporting.css	2016-02-09 21:30:25 +0000
@@ -29,6 +29,23 @@
   margin-left: 10px;
 }
 
+#attributeOptionComboTable
+{
+  width: 600px;
+}
+
+#attributeOptionComboItemDiv
+{
+  height: 460px;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+
+#attributeOptionComboButtonDiv
+{
+  padding: 10px 0;
+}
+
 @media print
 {
 	div#control