From 985b408c4629f3dc5378e63a28a719ad0288911b Mon Sep 17 00:00:00 2001 From: brianbrix Date: Tue, 18 Aug 2026 09:00:38 +0300 Subject: [PATCH 01/11] AMP-31199 : Create API to fetch cumulative commitments by donor and portfolio --- .../publicportal/PublicEndpoint.java | 26 ++++ .../publicportal/PublicPortalService.java | 116 ++++++++++++++++++ .../dto/PublicDonorCommitment.java | 33 +++++ .../dto/PublicDonorCommitmentsByYear.java | 58 +++++++++ 4 files changed, 233 insertions(+) create mode 100644 amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitment.java create mode 100644 amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitmentsByYear.java diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java index 84a1250c578..fd166cd6822 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java @@ -11,6 +11,7 @@ import org.digijava.kernel.ampapi.endpoints.dashboards.services.PublicServices; import org.digijava.kernel.ampapi.endpoints.errors.ApiErrorResponse; import org.digijava.kernel.ampapi.endpoints.gis.SettingsAndFiltersParameters; +import org.digijava.kernel.ampapi.endpoints.publicportal.dto.PublicDonorCommitmentsByYear; import org.digijava.kernel.ampapi.endpoints.publicportal.dto.PublicTotalsByMeasure; import org.digijava.kernel.ampapi.endpoints.reports.ReportFormParameters; import org.digijava.kernel.ampapi.endpoints.reports.ReportsUtil; @@ -126,6 +127,31 @@ public Response describeTotalByMeasure() { } + @POST + @Path("/donorCommitmentsByYear") + @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") + @ApiMethod(ui = false, id = "donorCommitmentsByYear") + @ApiOperation("Commitments by donor up to selected year") + @ApiResponses(@ApiResponse(code = HttpServletResponse.SC_OK, message = "Donor commitments by year", + response = PublicDonorCommitmentsByYear.class)) + public Response getDonorCommitmentsByYear(SettingsAndFiltersParameters config, + @ApiParam(value = "Include commitments up to this year", required = false) + @DefaultValue("2025") @QueryParam("year") Integer year) { + return PublicServices.buildOkResponseWithOriginHeaders( + PublicPortalService.getDonorCommitmentsByYear(config, year)); + } + + + @OPTIONS + @Path("/donorCommitmentsByYear") + @ApiOperation( + value = "Describe options for endpoint", + notes = "Enables Cross-Origin Resource Sharing for endpoint") + public Response describeDonorCommitmentsByYear() { + return PublicServices.buildOkResponseWithOriginHeaders(""); + } + + @POST @Path("/projectCount") @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java index a7f663bda5d..93dfa223f88 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java @@ -20,6 +20,8 @@ import org.digijava.kernel.ampapi.endpoints.errors.ApiErrorResponse; import org.digijava.kernel.ampapi.endpoints.errors.ApiRuntimeException; import org.digijava.kernel.ampapi.endpoints.gis.SettingsAndFiltersParameters; +import org.digijava.kernel.ampapi.endpoints.publicportal.dto.PublicDonorCommitment; +import org.digijava.kernel.ampapi.endpoints.publicportal.dto.PublicDonorCommitmentsByYear; import org.digijava.kernel.ampapi.endpoints.publicportal.dto.PublicTotalsByMeasure; import org.digijava.kernel.ampapi.endpoints.reports.ReportFormParameters; import org.digijava.kernel.ampapi.endpoints.reports.ReportsUtil; @@ -32,6 +34,8 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -49,6 +53,14 @@ */ public class PublicPortalService { protected static final Logger logger = Logger.getLogger(PublicPortalService.class); + private static final String DONOR_AGENCY_FILTER = "donor-agency"; + private static final String DONOR_AGENCY_CAMEL_FILTER = "donorAgency"; + private static final String DONOR_FILTER = "donor"; + private static final String REPORTING_SYSTEM_FILTER = "reporting-system"; + private static final String REPORTING_SYSTEM_CAMEL_FILTER = "reportingSystem"; + private static final String DATE_FILTER = "date"; + private static final String DATE_START = "start"; + private static final String DATE_END = "end"; /** * Retrieves top 'count' projects based on fixed requirements.
@@ -376,6 +388,110 @@ private static void applyFilterRules(Map filters, ReportSpecific } } + public static PublicDonorCommitmentsByYear getDonorCommitmentsByYear(SettingsAndFiltersParameters config, + Integer year) { + PublicDonorCommitmentsByYear result = new PublicDonorCommitmentsByYear(); + int targetYear = year == null ? Calendar.getInstance().get(Calendar.YEAR) : year; + result.setYear(targetYear); + + Map settings = copyMap(config != null ? config.getSettings() : null); + if (!settings.containsKey(SettingsConstants.FUNDING_TYPE_ID)) { + settings.put(SettingsConstants.FUNDING_TYPE_ID, SettingsConstants.DEFAULT_FUNDING_TYPE_ID); + } + if (!settings.containsKey(SettingsConstants.CURRENCY_ID)) { + settings.put(SettingsConstants.CURRENCY_ID, "USD"); + } + result.setCurrency((String) settings.get(SettingsConstants.CURRENCY_ID)); + + Map filters = copyMap(config != null ? config.getFilters() : null); + normalizeKnownFilterAliases(filters); + + List donorIds = extractDonorIds(filters); + if (donorIds.isEmpty()) { + return result; + } + + for (Object donorId : donorIds) { + SettingsAndFiltersParameters donorConfig = new SettingsAndFiltersParameters(); + donorConfig.setSettings(copyMap(settings)); + + Map donorFilters = copyMap(filters); + donorFilters.put(DONOR_AGENCY_FILTER, Collections.singletonList(donorId)); + donorFilters.put(DATE_FILTER, buildDateFilter(donorFilters.get(DATE_FILTER), targetYear)); + donorConfig.setFilters(donorFilters); + + PublicTotalsByMeasure totals = getTotalByMeasure(donorConfig); + BigDecimal donorTotal = totals != null && totals.getTotal() != null ? totals.getTotal() : BigDecimal.ZERO; + + PublicDonorCommitment donorCommitment = new PublicDonorCommitment(); + donorCommitment.setDonorId(parseLong(donorId)); + donorCommitment.setTotal(donorTotal); + + result.getDonorTotals().add(donorCommitment); + result.setTotal(result.getTotal().add(donorTotal)); + } + + return result; + } + + private static Map copyMap(Map source) { + return source == null ? new HashMap<>() : new HashMap<>(source); + } + + private static void normalizeKnownFilterAliases(Map filters) { + if (filters.containsKey(DONOR_AGENCY_CAMEL_FILTER) && !filters.containsKey(DONOR_AGENCY_FILTER)) { + filters.put(DONOR_AGENCY_FILTER, filters.get(DONOR_AGENCY_CAMEL_FILTER)); + } + if (filters.containsKey(DONOR_FILTER) && !filters.containsKey(DONOR_AGENCY_FILTER)) { + filters.put(DONOR_AGENCY_FILTER, filters.get(DONOR_FILTER)); + } + if (filters.containsKey(REPORTING_SYSTEM_CAMEL_FILTER) && !filters.containsKey(REPORTING_SYSTEM_FILTER)) { + filters.put(REPORTING_SYSTEM_FILTER, filters.get(REPORTING_SYSTEM_CAMEL_FILTER)); + } + } + + private static List extractDonorIds(Map filters) { + Object rawDonors = filters.get(DONOR_AGENCY_FILTER); + if (rawDonors instanceof List) { + return new ArrayList<>((List) rawDonors); + } + if (rawDonors != null) { + List singleValue = new ArrayList<>(); + singleValue.add(rawDonors); + return singleValue; + } + return new ArrayList<>(); + } + + private static Map buildDateFilter(Object existingDateFilter, Integer year) { + Map dateFilter = new HashMap<>(); + if (existingDateFilter instanceof Map) { + ((Map) existingDateFilter).forEach((key, value) -> { + if (key != null) { + dateFilter.put(String.valueOf(key), value); + } + }); + } + + if (!dateFilter.containsKey(DATE_START)) { + dateFilter.put(DATE_START, "1900-01-01"); + } + dateFilter.put(DATE_END, year + "-12-31"); + return dateFilter; + } + + private static Long parseLong(Object donorId) { + if (donorId == null) { + return null; + } + + try { + return Long.parseLong(String.valueOf(donorId)); + } catch (NumberFormatException ex) { + return null; + } + } + public static PublicTotalsByMeasure getCountByMeasure(SettingsAndFiltersParameters config) { PublicTotalsByMeasure result = new PublicTotalsByMeasure(); result.getMeasure().put("original", "Total Activities"); diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitment.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitment.java new file mode 100644 index 00000000000..29be144db67 --- /dev/null +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitment.java @@ -0,0 +1,33 @@ +package org.digijava.kernel.ampapi.endpoints.publicportal.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; + +import java.math.BigDecimal; + +public class PublicDonorCommitment { + + @JsonProperty("donorId") + @ApiModelProperty(example = "43") + private Long donorId; + + @JsonProperty("total") + @ApiModelProperty(example = "1085.4") + private BigDecimal total; + + public Long getDonorId() { + return donorId; + } + + public void setDonorId(Long donorId) { + this.donorId = donorId; + } + + public BigDecimal getTotal() { + return total; + } + + public void setTotal(BigDecimal total) { + this.total = total; + } +} diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitmentsByYear.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitmentsByYear.java new file mode 100644 index 00000000000..6050c0b576f --- /dev/null +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/dto/PublicDonorCommitmentsByYear.java @@ -0,0 +1,58 @@ +package org.digijava.kernel.ampapi.endpoints.publicportal.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +public class PublicDonorCommitmentsByYear { + + @JsonProperty("year") + @ApiModelProperty(example = "2025") + private Integer year; + + @JsonProperty("currency") + @ApiModelProperty(example = "USD") + private String currency; + + @JsonProperty("total") + @ApiModelProperty(example = "30480.2") + private BigDecimal total = BigDecimal.ZERO; + + @JsonProperty("donorTotals") + private List donorTotals = new ArrayList<>(); + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + public BigDecimal getTotal() { + return total; + } + + public void setTotal(BigDecimal total) { + this.total = total; + } + + public List getDonorTotals() { + return donorTotals; + } + + public void setDonorTotals(List donorTotals) { + this.donorTotals = donorTotals; + } +} From 559b79484531acd10c13db63f0343e5569694c34 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Tue, 18 Aug 2026 09:18:26 +0300 Subject: [PATCH 02/11] AMP-31199 : Create API to fetch cumulative commitments by donor and portfolio --- .../publicportal/PublicEndpoint.java | 42 ++++++------- .../publicportal/PublicPortalService.java | 59 ++++++++++++++----- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java index fd166cd6822..9bbc65dfff3 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicEndpoint.java @@ -127,29 +127,29 @@ public Response describeTotalByMeasure() { } - @POST - @Path("/donorCommitmentsByYear") - @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") - @ApiMethod(ui = false, id = "donorCommitmentsByYear") - @ApiOperation("Commitments by donor up to selected year") - @ApiResponses(@ApiResponse(code = HttpServletResponse.SC_OK, message = "Donor commitments by year", - response = PublicDonorCommitmentsByYear.class)) - public Response getDonorCommitmentsByYear(SettingsAndFiltersParameters config, - @ApiParam(value = "Include commitments up to this year", required = false) - @DefaultValue("2025") @QueryParam("year") Integer year) { - return PublicServices.buildOkResponseWithOriginHeaders( - PublicPortalService.getDonorCommitmentsByYear(config, year)); - } + @POST + @Path("/donorCommitmentsByYear") + @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") + @ApiMethod(ui = false, id = "donorCommitmentsByYear") + @ApiOperation("Commitments by donor up to selected year") + @ApiResponses(@ApiResponse(code = HttpServletResponse.SC_OK, message = "Donor commitments by year", + response = PublicDonorCommitmentsByYear.class)) + public Response getDonorCommitmentsByYear(SettingsAndFiltersParameters config, + @ApiParam(value = "Include commitments up to this year", required = false) + @QueryParam("year") Integer year) { + return PublicServices.buildOkResponseWithOriginHeaders( + PublicPortalService.getDonorCommitmentsByYear(config, year)); + } - @OPTIONS - @Path("/donorCommitmentsByYear") - @ApiOperation( - value = "Describe options for endpoint", - notes = "Enables Cross-Origin Resource Sharing for endpoint") - public Response describeDonorCommitmentsByYear() { - return PublicServices.buildOkResponseWithOriginHeaders(""); - } + @OPTIONS + @Path("/donorCommitmentsByYear") + @ApiOperation( + value = "Describe options for endpoint", + notes = "Enables Cross-Origin Resource Sharing for endpoint") + public Response describeDonorCommitmentsByYear() { + return PublicServices.buildOkResponseWithOriginHeaders(""); + } @POST diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java index 93dfa223f88..04a64866fae 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java @@ -35,10 +35,10 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -411,20 +411,53 @@ public static PublicDonorCommitmentsByYear getDonorCommitmentsByYear(SettingsAnd return result; } + ReportSpecificationImpl spec = new ReportSpecificationImpl("PublicPortal_getDonorCommitmentsByYear", + ArConstants.DONOR_TYPE); + SettingsUtils.applyExtendedSettings(spec, settings); + + Map groupedFilters = copyMap(filters); + groupedFilters.put(DONOR_AGENCY_FILTER, donorIds); + groupedFilters.put(DATE_FILTER, buildDateFilter(groupedFilters.get(DATE_FILTER), targetYear)); + ReportsUtil.configureFilters(spec, groupedFilters); + + spec.addColumn(new ReportColumn(ColumnConstants.DONOR_ID)); + spec.setHierarchies(spec.getColumns()); + + Map totalsByDonorId = new HashMap<>(); + Set requestedDonorIds = new LinkedHashSet<>(); for (Object donorId : donorIds) { - SettingsAndFiltersParameters donorConfig = new SettingsAndFiltersParameters(); - donorConfig.setSettings(copyMap(settings)); + Long parsedDonorId = parseLong(donorId); + if (parsedDonorId != null) { + requestedDonorIds.add(parsedDonorId); + totalsByDonorId.put(parsedDonorId, BigDecimal.ZERO); + } + } + + GeneratedReport report = EndpointUtils.runReport(spec); + if (report != null && report.reportContents != null && report.reportContents.getChildren() != null) { + for (ReportArea row : report.reportContents.getChildren()) { + Long donorId = null; + BigDecimal donorTotal = BigDecimal.ZERO; + + for (Entry cell : row.getContents().entrySet()) { + if (ColumnConstants.DONOR_ID.equals(cell.getKey().originalColumnName)) { + donorId = parseLong(cell.getValue().value); + } else if (cell.getValue().value instanceof BigDecimal) { + donorTotal = donorTotal.add((BigDecimal) cell.getValue().value); + } + } - Map donorFilters = copyMap(filters); - donorFilters.put(DONOR_AGENCY_FILTER, Collections.singletonList(donorId)); - donorFilters.put(DATE_FILTER, buildDateFilter(donorFilters.get(DATE_FILTER), targetYear)); - donorConfig.setFilters(donorFilters); + if (donorId != null && totalsByDonorId.containsKey(donorId)) { + totalsByDonorId.put(donorId, donorTotal); + } + } + } - PublicTotalsByMeasure totals = getTotalByMeasure(donorConfig); - BigDecimal donorTotal = totals != null && totals.getTotal() != null ? totals.getTotal() : BigDecimal.ZERO; + for (Long donorId : requestedDonorIds) { + BigDecimal donorTotal = totalsByDonorId.getOrDefault(donorId, BigDecimal.ZERO); PublicDonorCommitment donorCommitment = new PublicDonorCommitment(); - donorCommitment.setDonorId(parseLong(donorId)); + donorCommitment.setDonorId(donorId); donorCommitment.setTotal(donorTotal); result.getDonorTotals().add(donorCommitment); @@ -467,15 +500,11 @@ private static Map buildDateFilter(Object existingDateFilter, In Map dateFilter = new HashMap<>(); if (existingDateFilter instanceof Map) { ((Map) existingDateFilter).forEach((key, value) -> { - if (key != null) { + if (key != null && !DATE_START.equals(String.valueOf(key))) { dateFilter.put(String.valueOf(key), value); } }); } - - if (!dateFilter.containsKey(DATE_START)) { - dateFilter.put(DATE_START, "1900-01-01"); - } dateFilter.put(DATE_END, year + "-12-31"); return dateFilter; } From 2f3314c55a874f0b597d31d26ae31aba60046644 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 19 Aug 2026 11:01:43 +0300 Subject: [PATCH 03/11] AMP-31199 : Change reporting system import column --- .../org/digijava/module/message/jobs/AmpDonorFundingJob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java index 84c625badb7..5d248609d35 100644 --- a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java +++ b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java @@ -408,7 +408,7 @@ private List> getTranslations() { fieldMappings.put("location", locationAdmLevel != null ? locationAdmLevel : ColumnConstants.LOCATION); fieldMappings.put("implementationLevel", ColumnConstants.IMPLEMENTATION_LEVEL); fieldMappings.put("status", ColumnConstants.STATUS); - fieldMappings.put("reportingSystem", ColumnConstants.PRIMARY_SECTOR); + fieldMappings.put("reportingSystem", ColumnConstants.REPORTING_SYSTEM); fieldMappings.put("typeOfAssistance", ColumnConstants.TYPE_OF_ASSISTANCE); fieldMappings.put("procurementSystem", ColumnConstants.PROCUREMENT_SYSTEM); fieldMappings.put("responsibleOrganization", ColumnConstants.RESPONSIBLE_ORGANIZATION); From ac5dfa2fc6fbb1ddd41ec352c4dde94f56a66bc0 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 19 Aug 2026 11:10:41 +0300 Subject: [PATCH 04/11] AMP-31199 : Change reporting system import column --- .../org/digijava/module/message/jobs/AmpDonorFundingJob.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java index 5d248609d35..800d318ecc4 100644 --- a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java +++ b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java @@ -121,7 +121,7 @@ private List processReportData(GeneratedReport report, String ReportOutputColumn impLocation = findColumnByName(report.leafHeaders, location_adm_level, 5); ReportOutputColumn status = findColumnByName(report.leafHeaders, ColumnConstants.STATUS, 6); ReportOutputColumn typeOfAssistance = findColumnByName(report.leafHeaders, ColumnConstants.TYPE_OF_ASSISTANCE, 7); - ReportOutputColumn reportingSystem = findColumnByName(report.leafHeaders, ColumnConstants.PRIMARY_SECTOR, 8); // Also called Forum + ReportOutputColumn reportingSystem = findColumnByName(report.leafHeaders, ColumnConstants.REPORTING_SYSTEM, 8); // Also called Forum ReportOutputColumn responsibleOrg = findColumnByName(report.leafHeaders, ColumnConstants.RESPONSIBLE_ORGANIZATION, 9); ReportOutputColumn secondarySector = findColumnByName(report.leafHeaders, ColumnConstants.SECONDARY_SECTOR, 10); ReportOutputColumn ampId = findColumnByName(report.leafHeaders, ColumnConstants.AMP_ID, 11); // AMP_ID is now after hierarchies @@ -357,7 +357,7 @@ private void addColumnsToSpecification(ReportSpecificationImpl spec) { spec.addColumn(new ReportColumn(ColumnConstants.STATUS)); spec.addColumn(new ReportColumn(ColumnConstants.TYPE_OF_ASSISTANCE)); //TODO for GGW this is reporting system, for others it is Sectors - spec.addColumn(new ReportColumn(ColumnConstants.PRIMARY_SECTOR)); + spec.addColumn(new ReportColumn(ColumnConstants.REPORTING_SYSTEM)); spec.addColumn(new ReportColumn(ColumnConstants.RESPONSIBLE_ORGANIZATION)); spec.addColumn(new ReportColumn(ColumnConstants.SECONDARY_SECTOR)); // Ensure AMP ID is part of the hierarchy so commitments roll up under each activity From 42351ff33ae3561c7f9e3e8df3b378dd15f3c040 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 19 Aug 2026 11:21:50 +0300 Subject: [PATCH 05/11] AMP-31199 : Change reporting system import column --- .../amp/newreports/ReportsDashboard.java | 17 +++++------ .../message/jobs/AmpDonorFundingJob.java | 30 ++++++++++++++----- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java b/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java index 4ea27503294..fba8891c328 100644 --- a/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java +++ b/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java @@ -14,7 +14,7 @@ public class ReportsDashboard { private String reportingSystem; private String typeOfAssistance; private String currency; - //private String year; + private String year; private BigDecimal actualCommitment; private BigDecimal actualDisbursement; private Long activityCount; @@ -64,15 +64,14 @@ public void setActivityIds(String activityIds) { this.activityIds = activityIds; } - /* - public String getYear() { - return year; - } + public String getYear() { + return year; + } + + public void setYear(String year) { + this.year = year; + } - public void setYear(String year) { - this.year = year; - } - */ public BigDecimal getActualCommitment() { return actualCommitment; } diff --git a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java index 800d318ecc4..a77b0b49e4c 100644 --- a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java +++ b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java @@ -38,7 +38,6 @@ import java.io.IOException; import java.io.OutputStream; -import java.math.BigDecimal; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -81,8 +80,6 @@ private List getFundingByCurrency(String currencyCode) { List ampDashboardFunding = processReportData(fundingReport, currencyCode); // The ampDashboardFunding data contains objects for commitments and disbursment differently in // separate objects. We need to combine them in same object combining commitment and disbursment values. - //Make year configurable - //+ "|" + report.getYear() return new ArrayList<>(ampDashboardFunding.stream() .collect(Collectors.toMap( report -> report.getDonorAgency() @@ -91,7 +88,7 @@ private List getFundingByCurrency(String currencyCode) { + "|" + report.getLocation() + "|" + report.getImplementationLevel() + "|" + report.getStatus() - //+ "|" + report.getYear() + + "|" + extractYear(report) + "|" + report.getReportingSystem() + "|" + report.getTypeOfAssistance() + "|" + report.getProcurementSystem() @@ -205,7 +202,7 @@ private List processReportData(GeneratedReport report, String for (Map.Entry content : responsibleOrgData.getContents().entrySet()) { ReportOutputColumn col = content.getKey(); if (col.originalColumnName.equals(MeasureConstants.ACTUAL_COMMITMENTS) || col.originalColumnName.equals(MeasureConstants.ACTUAL_DISBURSEMENTS)) { - if (col.parentColumn != null && col.parentColumn.originalColumnName.equals("Totals")) { + if (col.parentColumn != null && !col.parentColumn.originalColumnName.equals("Totals")) { ReportsDashboard fundingReport = new ReportsDashboard(); fundingReport.setDonorAgency(donorAgencyCell != null ? donorAgencyCell.value.toString() : null); fundingReport.setImplementingAgency(implementingAgencyCell != null ? implementingAgencyCell.value.toString() : null); @@ -218,6 +215,7 @@ private List processReportData(GeneratedReport report, String fundingReport.setProcurementSystem(procurementSystemAgencyCell != null ? procurementSystemAgencyCell.value.toString() : null); fundingReport.setResponsibleOrganization(responsibleOrgCell != null ? responsibleOrgCell.value.toString() : null); fundingReport.setSecondarySector(secondarySectorCell != null ? secondarySectorCell.value.toString() : null); + assignYear(fundingReport, col.parentColumn.originalColumnName); fundingReport.setActivityCount(activityCount); fundingReport.setCurrency(currencyCode); fundingReport.setActivityIds(ampIdsJoined); @@ -333,9 +331,8 @@ private GeneratedReport generateReport(String currencyCode) { ReportSpecificationImpl spec = new ReportSpecificationImpl("preview report", ArConstants.DONOR_TYPE); addColumnsToSpecification(spec); - spec.setSummaryReport(false); - //TODO broken by year configurable - spec.setGroupingCriteria(GroupingCriteria.GROUPING_TOTALS_ONLY); + spec.setSummaryReport(true); + spec.setGroupingCriteria(GroupingCriteria.GROUPING_YEARLY); spec.setShowOriginalCurrency(false); spec.setDisplayEmptyFundingRows(true); ReportSettingsImpl reportSettings = new ReportSettingsImpl(); @@ -376,6 +373,23 @@ private void addColumnsToSpecification(ReportSpecificationImpl spec) { logger.info("Report columns set for Donor Funding Report" + spec.getColumns().size()); } + private String extractYear(ReportsDashboard report) { + try { + Object year = ReportsDashboard.class.getMethod("getYear").invoke(report); + return year != null ? year.toString() : null; + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private void assignYear(ReportsDashboard report, String year) { + try { + ReportsDashboard.class.getMethod("setYear", String.class).invoke(report, year); + } catch (ReflectiveOperationException ignored) { + // Year accessor is optional in some builds. + } + } + /** * Gets translations for all field labels (returns once, not per record) */ From 98af3ad17665ac2b0d324dcd7cdd5dc26a2198cc Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 19 Aug 2026 11:50:07 +0300 Subject: [PATCH 06/11] AMP-31199 : Change reporting system import column --- .../message/jobs/AmpDonorFundingJob.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java index a77b0b49e4c..efa99086a7c 100644 --- a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java +++ b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java @@ -107,8 +107,7 @@ private List processReportData(GeneratedReport report, String logger.info("Number of leaf headers: " + report.leafHeaders.size()); // Find columns by name for robustness (works regardless of order) - // Note: Indices are based on the order in addColumnsToSpecification: - // Hierarchies: 0-10, then AMP_ID (11), ACTIVITY_COUNT (12), measures (13-14) + // Note: indices align with hierarchy columns first; non-hierarchy columns may be omitted from leaf headers ReportOutputColumn donorAgency = findColumnByName(report.leafHeaders, ColumnConstants.DONOR_AGENCY, 0); ReportOutputColumn implementingAgency = findColumnByName(report.leafHeaders, ColumnConstants.IMPLEMENTING_AGENCY, 1); ReportOutputColumn procurementSystemAgency = findColumnByName(report.leafHeaders, ColumnConstants.PROCUREMENT_SYSTEM, 2); @@ -121,7 +120,7 @@ private List processReportData(GeneratedReport report, String ReportOutputColumn reportingSystem = findColumnByName(report.leafHeaders, ColumnConstants.REPORTING_SYSTEM, 8); // Also called Forum ReportOutputColumn responsibleOrg = findColumnByName(report.leafHeaders, ColumnConstants.RESPONSIBLE_ORGANIZATION, 9); ReportOutputColumn secondarySector = findColumnByName(report.leafHeaders, ColumnConstants.SECONDARY_SECTOR, 10); - ReportOutputColumn ampId = findColumnByName(report.leafHeaders, ColumnConstants.AMP_ID, 11); // AMP_ID is now after hierarchies + ReportOutputColumn ampId = findOptionalColumnByName(report.leafHeaders, ColumnConstants.AMP_ID, 11); List ampDashboardFunding = new ArrayList<>(); @@ -181,7 +180,7 @@ private List processReportData(GeneratedReport report, String } // gather AMP IDs from children (leaf nodes) List ampIdsList = new ArrayList<>(); - if (responsibleOrgData.getChildren() != null) { + if (ampId != null && responsibleOrgData.getChildren() != null) { for (ReportArea ampIdData : responsibleOrgData.getChildren()) { TextCell ampIdCell = (TextCell) ampIdData.getContents().get(ampId); if (ampIdCell != null && ampIdCell.value != null) { @@ -190,13 +189,13 @@ private List processReportData(GeneratedReport report, String } } // fallback if AMP ID directly on this node and no children - if (ampIdsList.isEmpty()) { + if (ampId != null && ampIdsList.isEmpty()) { TextCell directAmpIdCell = (TextCell) responsibleOrgData.getContents().get(ampId); if (directAmpIdCell != null && directAmpIdCell.value != null) { ampIdsList.add(directAmpIdCell.value.toString()); } } - String ampIdsJoined = String.join(",", ampIdsList); + String ampIdsJoined = ampIdsList.isEmpty() ? null : String.join(",", ampIdsList); // now process measures for (Map.Entry content : responsibleOrgData.getContents().entrySet()) { @@ -311,6 +310,15 @@ private ReportOutputColumn findColumnByName(List leafHeaders leafHeaders.stream().map(c -> c.originalColumnName != null ? c.originalColumnName : "null").collect(Collectors.joining(", "))); } + private ReportOutputColumn findOptionalColumnByName(List leafHeaders, String columnName, int expectedIndex) { + try { + return findColumnByName(leafHeaders, columnName, expectedIndex); + } catch (RuntimeException ex) { + logger.warn("Optional column missing from report output: " + columnName + ". Continuing without it."); + return null; + } + } + private void addFilters(ReportSpecificationImpl spec) { if (spec.getFilters() == null) { spec.setFilters(new ReportFiltersImpl()); @@ -357,14 +365,12 @@ private void addColumnsToSpecification(ReportSpecificationImpl spec) { spec.addColumn(new ReportColumn(ColumnConstants.REPORTING_SYSTEM)); spec.addColumn(new ReportColumn(ColumnConstants.RESPONSIBLE_ORGANIZATION)); spec.addColumn(new ReportColumn(ColumnConstants.SECONDARY_SECTOR)); - // Ensure AMP ID is part of the hierarchy so commitments roll up under each activity - - // Set hierarchies - includes AMP_ID so commitments/disbursements roll up under each activity - // This is required for summary reports to include all columns in leaf headers + // Set hierarchy columns used to navigate the report output tree. Set hierarchyColumns = new LinkedHashSet<>(spec.getColumns()); spec.setHierarchies(hierarchyColumns); - // Add non-hierarchy columns after setHierarchies + // Add non-hierarchy columns after setHierarchies. + // AMP ID may be absent in some grouped outputs; parser handles it as optional. spec.addColumn(new ReportColumn(ColumnConstants.AMP_ID)); spec.addColumn(new ReportColumn(ColumnConstants.ACTIVITY_COUNT)); spec.addMeasure(new ReportMeasure(MeasureConstants.ACTUAL_COMMITMENTS)); From b5028f926bb2a41c854b1ede56f7a2e0f57adf4b Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 19 Aug 2026 12:18:07 +0300 Subject: [PATCH 07/11] AMP-31199 : Missing country values --- .../amp/newreports/ReportsDashboard.java | 13 +++- .../message/jobs/AmpDonorFundingJob.java | 76 ++++++++++++------- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java b/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java index fba8891c328..50024c1688e 100644 --- a/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java +++ b/amp/src/main/java/org/dgfoundation/amp/newreports/ReportsDashboard.java @@ -10,6 +10,7 @@ public class ReportsDashboard { private String implementationLevel; private String procurementSystem; private String location; + private String country; private String status; private String reportingSystem; private String typeOfAssistance; @@ -81,11 +82,21 @@ public void setActualCommitment(BigDecimal actualCommitment) { } public String getLocation() { - return location; + return location != null ? location : country; } public void setLocation(String location) { this.location = location; + this.country = location; + } + + public String getCountry() { + return country != null ? country : location; + } + + public void setCountry(String country) { + this.country = country; + this.location = country; } public String getImplementationLevel() { diff --git a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java index efa99086a7c..98ed8d816ba 100644 --- a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java +++ b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java @@ -167,38 +167,20 @@ private List processReportData(GeneratedReport report, String for (ReportArea secondarySectorData : responsibleOrgData.getChildren()) { TextCell secondarySectorCell = (TextCell) secondarySectorData.getContents().get(secondarySector); - Long activityCount = 0L; - // collect activity count - for (Map.Entry content : responsibleOrgData.getContents().entrySet()) { - ReportOutputColumn col = content.getKey(); - if (col.originalColumnName.equals(ColumnConstants.ACTIVITY_COUNT)) { - IntCell amount = (IntCell) content.getValue(); - if (amount != null && amount.value != null) { - activityCount = (Long) amount.value; - } - } + Long activityCount = extractActivityCount(secondarySectorData); + // In some outputs, Activity Count sits on the parent node. + if (activityCount == 0L) { + activityCount = extractActivityCount(responsibleOrgData); } - // gather AMP IDs from children (leaf nodes) - List ampIdsList = new ArrayList<>(); - if (ampId != null && responsibleOrgData.getChildren() != null) { - for (ReportArea ampIdData : responsibleOrgData.getChildren()) { - TextCell ampIdCell = (TextCell) ampIdData.getContents().get(ampId); - if (ampIdCell != null && ampIdCell.value != null) { - ampIdsList.add(ampIdCell.value.toString()); - } - } - } - // fallback if AMP ID directly on this node and no children - if (ampId != null && ampIdsList.isEmpty()) { - TextCell directAmpIdCell = (TextCell) responsibleOrgData.getContents().get(ampId); - if (directAmpIdCell != null && directAmpIdCell.value != null) { - ampIdsList.add(directAmpIdCell.value.toString()); - } + // Gather AMP IDs from the same area where measures are read. + List ampIdsList = extractAmpIds(secondarySectorData, ampId); + if (ampIdsList.isEmpty()) { + ampIdsList = extractAmpIds(responsibleOrgData, ampId); } String ampIdsJoined = ampIdsList.isEmpty() ? null : String.join(",", ampIdsList); // now process measures - for (Map.Entry content : responsibleOrgData.getContents().entrySet()) { + for (Map.Entry content : secondarySectorData.getContents().entrySet()) { ReportOutputColumn col = content.getKey(); if (col.originalColumnName.equals(MeasureConstants.ACTUAL_COMMITMENTS) || col.originalColumnName.equals(MeasureConstants.ACTUAL_DISBURSEMENTS)) { if (col.parentColumn != null && !col.parentColumn.originalColumnName.equals("Totals")) { @@ -310,6 +292,45 @@ private ReportOutputColumn findColumnByName(List leafHeaders leafHeaders.stream().map(c -> c.originalColumnName != null ? c.originalColumnName : "null").collect(Collectors.joining(", "))); } + private Long extractActivityCount(ReportArea reportArea) { + Long activityCount = 0L; + for (Map.Entry content : reportArea.getContents().entrySet()) { + ReportOutputColumn col = content.getKey(); + if (col.originalColumnName.equals(ColumnConstants.ACTIVITY_COUNT)) { + IntCell amount = (IntCell) content.getValue(); + if (amount != null && amount.value != null) { + activityCount = (Long) amount.value; + } + break; + } + } + return activityCount; + } + + private List extractAmpIds(ReportArea reportArea, ReportOutputColumn ampIdColumn) { + List ampIdsList = new ArrayList<>(); + if (ampIdColumn == null) { + return ampIdsList; + } + + if (reportArea.getChildren() != null) { + for (ReportArea ampIdData : reportArea.getChildren()) { + TextCell ampIdCell = (TextCell) ampIdData.getContents().get(ampIdColumn); + if (ampIdCell != null && ampIdCell.value != null) { + ampIdsList.add(ampIdCell.value.toString()); + } + } + } + + if (ampIdsList.isEmpty()) { + TextCell directAmpIdCell = (TextCell) reportArea.getContents().get(ampIdColumn); + if (directAmpIdCell != null && directAmpIdCell.value != null) { + ampIdsList.add(directAmpIdCell.value.toString()); + } + } + return ampIdsList; + } + private ReportOutputColumn findOptionalColumnByName(List leafHeaders, String columnName, int expectedIndex) { try { return findColumnByName(leafHeaders, columnName, expectedIndex); @@ -426,6 +447,7 @@ private List> getTranslations() { fieldMappings.put("implementingAgency", ColumnConstants.IMPLEMENTING_AGENCY); fieldMappings.put("pillar", ColumnConstants.NATIONAL_PLANNING_OBJECTIVES_LEVEL_1); fieldMappings.put("location", locationAdmLevel != null ? locationAdmLevel : ColumnConstants.LOCATION); + fieldMappings.put("country", locationAdmLevel != null ? locationAdmLevel : ColumnConstants.LOCATION); fieldMappings.put("implementationLevel", ColumnConstants.IMPLEMENTATION_LEVEL); fieldMappings.put("status", ColumnConstants.STATUS); fieldMappings.put("reportingSystem", ColumnConstants.REPORTING_SYSTEM); From fc5d698309413e54c1072787656bf47fd66f6e62 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 19 Aug 2026 13:15:24 +0300 Subject: [PATCH 08/11] AMP-31199 : Add reporting system filters --- .../endpoints/common/FiltersEndpoint.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/common/FiltersEndpoint.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/common/FiltersEndpoint.java index 15c5d623c94..c314f364578 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/common/FiltersEndpoint.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/common/FiltersEndpoint.java @@ -385,10 +385,23 @@ public Response getProcurementSystem() { } /** - * List the possible values of 'Funding Status' filter. + * List the possible values of 'Reporting System' filter. * - * @return filter definition and values of 'funding-status' filter. + * @return filter definition and values of 'reporting-system' filter. */ + @GET + @Path("/reportingSystem/") + @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") + @ApiMethod(id = FiltersConstants.REPORTING_SYSTEM, name = ColumnConstants.REPORTING_SYSTEM) + @ApiOperation(value = "Retrieve the data needed for building the 'Reporting System' filter.", + notes = "The response contains 2 objects - the filter definition and the values. \n" + + "The filter widget should create a tree for 'Reporting System' values.") + @FilterDefinition(tab = EPConstants.TAB_OTHER, columns = ColumnConstants.REPORTING_SYSTEM) + public Response getReportingSystem() { + return PublicServices.buildOkResponseWithOriginHeaders( + FiltersManager.getInstance().getCategoryValueFilter(FiltersConstants.REPORTING_SYSTEM)); + } + @OPTIONS @Path("/procurementSystem") @ApiOperation( @@ -397,6 +410,22 @@ public Response getProcurementSystem() { public Response describeProcurementSystem() { return PublicServices.buildOkResponseWithOriginHeaders(""); } + + @OPTIONS + @Path("/reportingSystem") + @ApiOperation( + value = "Describe options for endpoint", + notes = "Enables Cross-Origin Resource Sharing for endpoint") + public Response describeReportingSystem() { + return PublicServices.buildOkResponseWithOriginHeaders(""); + } + + /** + * List the possible values of 'Funding Status' filter. + * + * @return filter definition and values of 'funding-status' filter. + */ + @GET @Path("/fundingStatus/") @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") From 0843247453c735b001dc523b85771fa8a5caba90 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Thu, 20 Aug 2026 04:51:53 +0300 Subject: [PATCH 09/11] AMP-31199 : Fix reporting system filters --- .../endpoints/filters/CategoryValueFilterListManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java index 3d76fc018f8..e32b603829d 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java @@ -63,6 +63,9 @@ public final class CategoryValueFilterListManager implements FilterListManager { .put(FiltersConstants.PROCUREMENT_SYSTEM, new CategoryBean(ColumnConstants.PROCUREMENT_SYSTEM, CategoryConstants.PROCUREMENT_SYSTEM_KEY)) + .put(FiltersConstants.REPORTING_SYSTEM, + new CategoryBean(ColumnConstants.REPORTING_SYSTEM, + CategoryConstants.REPORTING_SYSTEM_KEY)) .build(); public static CategoryValueFilterListManager getInstance() { From 9ba987de40c73e4d3f90db2b9c0177b468bf5df0 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Thu, 20 Aug 2026 19:28:02 +0300 Subject: [PATCH 10/11] AMP-31199 : Fix reporting system filters --- .../CategoryValueFilterListManager.java | 4 +- .../publicportal/PublicPortalService.java | 62 +++++++++---------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java index e32b603829d..5522fb2d0a0 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/filters/CategoryValueFilterListManager.java @@ -63,9 +63,9 @@ public final class CategoryValueFilterListManager implements FilterListManager { .put(FiltersConstants.PROCUREMENT_SYSTEM, new CategoryBean(ColumnConstants.PROCUREMENT_SYSTEM, CategoryConstants.PROCUREMENT_SYSTEM_KEY)) - .put(FiltersConstants.REPORTING_SYSTEM, + .put(FiltersConstants.REPORTING_SYSTEM, new CategoryBean(ColumnConstants.REPORTING_SYSTEM, - CategoryConstants.REPORTING_SYSTEM_KEY)) + CategoryConstants.REPORTING_SYSTEM_KEY)) .build(); public static CategoryValueFilterListManager getInstance() { diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java index 04a64866fae..ff3ac01b46d 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java @@ -411,50 +411,21 @@ public static PublicDonorCommitmentsByYear getDonorCommitmentsByYear(SettingsAnd return result; } - ReportSpecificationImpl spec = new ReportSpecificationImpl("PublicPortal_getDonorCommitmentsByYear", - ArConstants.DONOR_TYPE); - SettingsUtils.applyExtendedSettings(spec, settings); - - Map groupedFilters = copyMap(filters); - groupedFilters.put(DONOR_AGENCY_FILTER, donorIds); - groupedFilters.put(DATE_FILTER, buildDateFilter(groupedFilters.get(DATE_FILTER), targetYear)); - ReportsUtil.configureFilters(spec, groupedFilters); - - spec.addColumn(new ReportColumn(ColumnConstants.DONOR_ID)); - spec.setHierarchies(spec.getColumns()); - - Map totalsByDonorId = new HashMap<>(); Set requestedDonorIds = new LinkedHashSet<>(); for (Object donorId : donorIds) { Long parsedDonorId = parseLong(donorId); if (parsedDonorId != null) { requestedDonorIds.add(parsedDonorId); - totalsByDonorId.put(parsedDonorId, BigDecimal.ZERO); } } - GeneratedReport report = EndpointUtils.runReport(spec); - if (report != null && report.reportContents != null && report.reportContents.getChildren() != null) { - for (ReportArea row : report.reportContents.getChildren()) { - Long donorId = null; - BigDecimal donorTotal = BigDecimal.ZERO; - - for (Entry cell : row.getContents().entrySet()) { - if (ColumnConstants.DONOR_ID.equals(cell.getKey().originalColumnName)) { - donorId = parseLong(cell.getValue().value); - } else if (cell.getValue().value instanceof BigDecimal) { - donorTotal = donorTotal.add((BigDecimal) cell.getValue().value); - } - } - - if (donorId != null && totalsByDonorId.containsKey(donorId)) { - totalsByDonorId.put(donorId, donorTotal); - } - } - } + Map baseFilters = copyMap(filters); + baseFilters.put(DATE_FILTER, buildDateFilter(baseFilters.get(DATE_FILTER), targetYear)); for (Long donorId : requestedDonorIds) { - BigDecimal donorTotal = totalsByDonorId.getOrDefault(donorId, BigDecimal.ZERO); + Map donorFilters = copyMap(baseFilters); + donorFilters.put(DONOR_AGENCY_FILTER, Collections.singletonList(donorId)); + BigDecimal donorTotal = getTotalForFilters(settings, donorFilters); PublicDonorCommitment donorCommitment = new PublicDonorCommitment(); donorCommitment.setDonorId(donorId); @@ -467,6 +438,29 @@ public static PublicDonorCommitmentsByYear getDonorCommitmentsByYear(SettingsAnd return result; } + private static BigDecimal getTotalForFilters(Map settings, Map filters) { + ReportSpecificationImpl spec = new ReportSpecificationImpl("PublicPortal_getDonorCommitmentsByYearSingle", + ArConstants.DONOR_TYPE); + + SettingsUtils.applyExtendedSettings(spec, settings); + ReportsUtil.configureFilters(spec, filters); + spec.setSummaryReport(true); + + GeneratedReport report = EndpointUtils.runReport(spec); + if (report == null || report.reportContents == null || report.reportContents.getContents() == null) { + return BigDecimal.ZERO; + } + + BigDecimal total = BigDecimal.ZERO; + for (ReportCell cell : report.reportContents.getContents().values()) { + if (cell != null && cell.value instanceof BigDecimal) { + total = total.add((BigDecimal) cell.value); + } + } + + return total; + } + private static Map copyMap(Map source) { return source == null ? new HashMap<>() : new HashMap<>(source); } From 159be267e23e2ac6317cbfb0fc6b3cfe83443df3 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Thu, 20 Aug 2026 19:59:11 +0300 Subject: [PATCH 11/11] AMP-31199 : Fix reporting system filters --- .../ampapi/endpoints/publicportal/PublicPortalService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java index ff3ac01b46d..528de9f188e 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/publicportal/PublicPortalService.java @@ -35,6 +35,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator;