From d04f69ed2ddfde3587ecedafa1d1de77eae2d521 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Fri, 27 Mar 2026 16:32:14 +0300 Subject: [PATCH 01/10] AMP-31091: The disaggregation order doesnt match AMP-31089: Disaggregated vs Regular Value Tracking values in the new Indicator Manager --- .../modals/AddNewIndicatorModal.tsx | 32 +++++++++---- .../components/modals/EditIndicatorModal.tsx | 46 +++++++++++++------ .../config/initialTranslations.json | 1 + 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/AddNewIndicatorModal.tsx b/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/AddNewIndicatorModal.tsx index 9c8065cac4f..28a4d24032b 100644 --- a/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/AddNewIndicatorModal.tsx +++ b/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/AddNewIndicatorModal.tsx @@ -374,6 +374,7 @@ const AddNewIndicatorModal: React.FC = (props) => { } })); + const hasDisagg = values.disaggregation && values.disaggregation.length > 0; const indicatorData = { name, description, @@ -382,18 +383,18 @@ const AddNewIndicatorModal: React.FC = (props) => { programId: programId ? programId : null, ascending, creationDate: creationDate ? formatDate(new Date(creationDate)) : null, - base: checkObjectIsNull(base) ? null : { + base: hasDisagg ? null : (checkObjectIsNull(base) ? null : { originalValue: base.originalValue ? lodash.toNumber(base.originalValue): null, originalValueDate: base.originalValueDate ? formatDate(base.originalValueDate) : null, revisedValue: base.revisedValue ? lodash.toNumber(base.revisedValue) : null, revisedValueDate: base.revisedValueDate ? formatDate(base.revisedValueDate) : null, - }, - target: checkObjectIsNull(target) ? null : { + }), + target: hasDisagg ? null : (checkObjectIsNull(target) ? null : { originalValue: target.originalValue ? lodash.toNumber(target.originalValue) : null, originalValueDate: target.originalValueDate ? formatDate(target.originalValueDate) : null, revisedValue: target.revisedValue ? lodash.toNumber(target.revisedValue) : null, revisedValueDate: target.revisedValueDate ? formatDate(target.revisedValueDate) : null, - }, + }), indicatorsCategory, outputId: values.outputId, outcomeId: values.outcomeId, @@ -407,7 +408,7 @@ const AddNewIndicatorModal: React.FC = (props) => { calculationMethod: values.calculationMethod, responsibleOrganizations: values.responsibleOrganizations, frequency: values.frequency, - disaggregationValues: formattedDisaggregationValues, + disaggregationValues: hasDisagg ? formattedDisaggregationValues : [], }; dispatch(createIndicator(indicatorData)); @@ -754,7 +755,7 @@ const AddNewIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} className={`basic-multi-select ${(props.errors.disaggregation && props.touched.disaggregation) && styles.text_is_invalid}`} classNamePrefix="select" - value={disaggregationOptions.filter(opt => props.values.disaggregation?.includes(opt.value))} + value={(props.values.disaggregation || []).map(id => disaggregationOptions.find(opt => opt.value === id)).filter(Boolean)} /> @@ -1107,6 +1108,15 @@ const AddNewIndicatorModal: React.FC = (props) => { {/* Value Tracking */}
{translations["amp.indicatormanager:value-tracking"]}
+ {props.values.disaggregation?.length > 0 && ( + + +
+ {translations["amp.indicatormanager:value-tracking-disabled-disaggregation"] || "Regular base/target values are disabled when disaggregation is active and will be cleared on save."} +
+ +
+ )}

{translations["amp.indicatormanager:base-values"]}

@@ -1121,6 +1131,7 @@ const AddNewIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} name="base.originalValue" type="number" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.base?.originalValue && props.touched.base?.originalValue) && styles.text_is_invalid}`} placeholder={translations["amp.indicatormanager:enter-original-value"]} /> @@ -1144,7 +1155,7 @@ const AddNewIndicatorModal: React.FC = (props) => { props.setFieldValue('base.originalValueDate', null); }} onBlur={props.handleBlur} - disabled={baseOriginalValueDateDisabled} + disabled={props.values.disaggregation?.length > 0 || baseOriginalValueDateDisabled} className={`${styles.input_field} ${(props.errors.base?.originalValueDate && props.touched.base?.originalValueDate) && styles.text_is_invalid}`}/> @@ -1162,6 +1173,7 @@ const AddNewIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} name="base.revisedValue" type="number" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.base?.revisedValue && props.touched.base?.revisedValue) && styles.text_is_invalid}`} placeholder={translations["amp.indicatormanager:enter-revised-value"]} /> @@ -1185,6 +1197,7 @@ const AddNewIndicatorModal: React.FC = (props) => { }} onBlur={props.handleBlur} name="base.revisedValueDate" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.base?.revisedValueDate && props.touched.base?.revisedValueDate) && styles.text_is_invalid}`} /> @@ -1206,6 +1219,7 @@ const AddNewIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} name="target.originalValue" type="number" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.target?.originalValue && props.touched.target?.originalValue) && styles.text_is_invalid}`} placeholder={translations["amp.indicatormanager:enter-target-value"]} /> @@ -1227,7 +1241,7 @@ const AddNewIndicatorModal: React.FC = (props) => { props.setFieldValue('target.originalValueDate', null); }} onBlur={props.handleBlur} - disabled={targetOriginalValueDateDisabled} + disabled={props.values.disaggregation?.length > 0 || targetOriginalValueDateDisabled} className={`${styles.input_field} ${(props.errors.target?.originalValueDate && props.touched.target?.originalValueDate) && styles.text_is_invalid}`} /> @@ -1245,6 +1259,7 @@ const AddNewIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} name="target.revisedValue" type="number" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.target?.revisedValue && props.touched.target?.revisedValue) && styles.text_is_invalid}`} placeholder={translations["amp.indicatormanager:enter-revised-value"]} /> @@ -1268,6 +1283,7 @@ const AddNewIndicatorModal: React.FC = (props) => { }} onBlur={props.handleBlur} name="target.revisedValueDate" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.target?.revisedValueDate && props.touched.target?.revisedValueDate) && styles.text_is_invalid}`} /> diff --git a/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/EditIndicatorModal.tsx b/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/EditIndicatorModal.tsx index 8930618fce6..e4eb987912d 100644 --- a/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/EditIndicatorModal.tsx +++ b/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/components/modals/EditIndicatorModal.tsx @@ -469,6 +469,7 @@ const EditIndicatorModal: React.FC = (props) => { revisedValueDate: dv.target?.revisedValueDate ? formatDate(dv.target.revisedValueDate) : null, } })); + const hasDisagg = values.disaggregation && values.disaggregation.length > 0; const updatedIndicatorData = { id: indicator.id, name: values.name, @@ -488,21 +489,21 @@ const EditIndicatorModal: React.FC = (props) => { programId: values.programId ? values.programId: null, ascending: values.ascending, creationDate: values.creationDate && formatDate(values.creationDate), - base: checkObjectIsNull(values.base) ? null : { + base: hasDisagg ? null : (checkObjectIsNull(values.base) ? null : { originalValue: values.base.originalValue ? lodash.toNumber(values.base.originalValue) : null, originalValueDate: values.base.originalValueDate ? formatDate(values.base.originalValueDate) : null, revisedValue: values.base.revisedValue ? lodash.toNumber(values.base.revisedValue) : null, revisedValueDate: values.base.revisedValueDate ? formatDate(values.base.revisedValueDate) : null, - }, - target: checkObjectIsNull(values.target) ? null : { + }), + target: hasDisagg ? null : (checkObjectIsNull(values.target) ? null : { originalValue: values.target.originalValue ? lodash.toNumber(values.target.originalValue) : null, originalValueDate: values.target.originalValueDate ? formatDate(values.target.originalValueDate) : null, revisedValue: values.target.revisedValue ? lodash.toNumber(values.target.revisedValue) : null, revisedValueDate: values.target.revisedValueDate ? formatDate(values.target.revisedValueDate) : null, - }, + }), outcomeId: values.outcomeId, outputId: values.outputId, - disaggregationValues: formattedDisaggregationValues, + disaggregationValues: hasDisagg ? formattedDisaggregationValues : [], indicatorsCategory: indicator.indicatorsCategory || undefined, }; @@ -843,7 +844,7 @@ const EditIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} className={`basic-multi-select ${(props.errors.disaggregation && props.touched.disaggregation) && styles.text_is_invalid}`} classNamePrefix="select" - value={disaggregationOptions.filter(opt => props.values.disaggregation?.includes(opt.value))} + value={(props.values.disaggregation || []).map(id => disaggregationOptions.find(opt => opt.value === id)).filter(Boolean)} />
@@ -1208,6 +1209,15 @@ const EditIndicatorModal: React.FC = (props) => { {/* Value Tracking */}
{translations["amp.indicatormanager:value-tracking"]}
+ {props.values.disaggregation?.length > 0 && ( + + +
+ {translations["amp.indicatormanager:value-tracking-disabled-disaggregation"] || "Regular base/target values are disabled when disaggregation is active and will be cleared on save."} +
+ +
+ )}

{translations["amp.indicatormanager:base-values"]}

@@ -1217,13 +1227,14 @@ const EditIndicatorModal: React.FC = (props) => { {translations['amp.indicatormanager:original-value']} + defaultValue={props.values.base?.originalValue} + onChange={props.handleChange} + onBlur={props.handleBlur} + name="base.originalValue" + type="number" + disabled={props.values.disaggregation?.length > 0} + className={`${styles.input_field} ${(props.errors.base?.originalValue && props.touched.base?.originalValue) && styles.text_is_invalid}`} + placeholder={translations["amp.indicatormanager:enter-original-value"]} /> {props.errors.base?.originalValue} @@ -1245,7 +1256,7 @@ const EditIndicatorModal: React.FC = (props) => { }} onBlur={props.handleBlur} name="base.originalValueDate" - disabled={baseOriginalValueDateDisabled} + disabled={props.values.disaggregation?.length > 0 || baseOriginalValueDateDisabled} className={`${styles.input_field} ${(props.errors.base?.originalValueDate && props.touched.base?.originalValueDate) && styles.text_is_invalid}`} id="baseOriginalValueDate" inputRef={baseOriginalValueDateRef} @@ -1266,6 +1277,7 @@ const EditIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} name="base.revisedValue" type="number" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.base?.revisedValue && props.touched.base?.revisedValue) && styles.text_is_invalid}`} placeholder={translations["amp.indicatormanager:enter-revised-value"]} /> @@ -1289,6 +1301,7 @@ const EditIndicatorModal: React.FC = (props) => { }} onBlur={props.handleBlur} name="base.revisedValueDate" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.base?.revisedValueDate && props.touched.base?.revisedValueDate) && styles.text_is_invalid}`} id="baseRevisedValueDate" inputRef={baseRevisedValueDateRef} @@ -1312,6 +1325,7 @@ const EditIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} name="target.originalValue" type="number" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.target?.originalValue && props.touched.target?.originalValue) && styles.text_is_invalid}`} placeholder={translations["amp.indicatormanager:enter-target-value"]} /> @@ -1332,7 +1346,7 @@ const EditIndicatorModal: React.FC = (props) => { onClear={() => { props.setFieldValue("target.originalValueDate", null); }} - disabled={targetOriginalValueDateDisabled} + disabled={props.values.disaggregation?.length > 0 || targetOriginalValueDateDisabled} onBlur={props.handleBlur} name="target.originalValueDate" className={`${styles.input_field} ${(props.errors.target?.originalValueDate && props.touched.target?.originalValueDate) && styles.text_is_invalid}`} @@ -1351,6 +1365,7 @@ const EditIndicatorModal: React.FC = (props) => { onBlur={props.handleBlur} name="target.revisedValue" type="number" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.target?.revisedValue && props.touched.target?.revisedValue) && styles.text_is_invalid}`} placeholder={translations["amp.indicatormanager:enter-revised-value"]} /> @@ -1374,6 +1389,7 @@ const EditIndicatorModal: React.FC = (props) => { }} onBlur={props.handleBlur} name="target.revisedValueDate" + disabled={props.values.disaggregation?.length > 0} className={`${styles.input_field} ${(props.errors.target?.revisedValueDate && props.touched.target?.revisedValueDate) && styles.text_is_invalid}`} id="targetRevisedValueDate" inputRef={targetRevisedValueDateRef} diff --git a/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/config/initialTranslations.json b/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/config/initialTranslations.json index bb2d9cc9864..19df53e1d08 100644 --- a/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/config/initialTranslations.json +++ b/amp/TEMPLATE/reampv2/packages/reampv2-app/src/modules/admin/indicator_manager/config/initialTranslations.json @@ -163,6 +163,7 @@ "amp.indicatormanager:indicator-type": "Indicator Type", "amp.indicatormanager:link-logframe": "Link to Logframe (Program Scheme)", "amp.indicatormanager:value-tracking": "Value Tracking", + "amp.indicatormanager:value-tracking-disabled-disaggregation": "Regular base/target values are disabled when disaggregation is active and will be cleared on save.", "amp.indicatormanager:other-considerations": "Other Considerations", "amp.indicatormanager:categorization-linkage-info": "Categorization and Linkage", "amp.indicatormanager:output": "Output", From 2c40b10c630d1b6a372c71747a64a65cbd6f8ae9 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 11:25:58 +0300 Subject: [PATCH 02/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../me/singlecountry/AmpMEFormSectionFeature.java | 14 +++++++++++++- .../me/singlecountry/AmpMEItemFeaturePanel.java | 11 +++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java index 140362042f7..451f4003e43 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java @@ -36,10 +36,13 @@ */ public class AmpMEFormSectionFeature extends AmpFormSectionFeaturePanel { private final ListView list; + private final IModel activityModel; + private AmpAutocompleteFieldPanel searchIndicators; public AmpMEFormSectionFeature(String id, String fmName, final IModel am) throws Exception { super(id, fmName, am); + this.activityModel = am; this.fmType = AmpFMTypes.MODULE; if (am.getObject().getIndicators() == null) { @@ -91,7 +94,7 @@ public void onClick(AjaxRequestTarget target) { add(list); - final AmpAutocompleteFieldPanel searchIndicators = + searchIndicators = new AmpAutocompleteFieldPanel("search", "Search Indicators", AmpMEIndicatorSearchModel.class) { @@ -127,4 +130,13 @@ public Integer getChoiceLevel(AmpIndicator choice) { add(UpdateEventBehavior.of(ProgramSelectedEvent.class)); add(searchIndicators); } + + @Override + protected void onBeforeRender() { + super.onBeforeRender(); + searchIndicators.getModelParams().put( + AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, + activityModel.getObject().getActPrograms() + ); + } } diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java index fead2905d84..3cce33da615 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java @@ -140,6 +140,7 @@ protected String load() { add(indicatorTargetDateLabel); AmpMEActualValuesFormTableFeaturePanel valuesTable = new AmpMEActualValuesFormTableFeaturePanel("valuesSubsection", indicator, conn, "Actual Values", false, 7); + valuesTable.setOutputMarkupPlaceholderTag(true); add(valuesTable); AmpAjaxLinkField addActualValue = new AmpAjaxLinkField("addActualValue", "Add Actual Value", "Add Actual Value") { @@ -155,6 +156,7 @@ public void onClick(AjaxRequestTarget target) { } }; + addActualValue.setOutputMarkupPlaceholderTag(true); add(addActualValue); AmpAjaxLinkField setValue = new AmpAjaxLinkField("setValues", "Set Value", "Set Value") { @@ -191,10 +193,11 @@ protected void onClick(AjaxRequestTarget art) { AmpMEDisaggregationValuesFeaturePanel disaggPanel = new AmpMEDisaggregationValuesFeaturePanel("disaggregationValuesSubsection","Disaggregation Values", indicator); disaggPanel.setOutputMarkupId(true); disaggPanel.setOutputMarkupPlaceholderTag(true); - // Add disaggregation values subsection - if (indicator.getObject().getDisaggregation()== null || indicator.getObject().getDisaggregation().isEmpty()){ - disaggPanel.setVisible(false); - } + boolean hasDisaggregation = indicator.getObject().getDisaggregation() != null + && !indicator.getObject().getDisaggregation().isEmpty(); + disaggPanel.setVisible(hasDisaggregation); + valuesTable.setVisible(!hasDisaggregation); + addActualValue.setVisible(!hasDisaggregation); add(disaggPanel); From 14d979e7c448ade16de1bf255a7b94485c1cdcbc Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 12:18:02 +0300 Subject: [PATCH 03/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../AmpMEFormSectionFeature.java | 18 +++++++++-- .../singlecountry/AmpMEItemFeaturePanel.java | 20 +++++++++---- .../models/AmpMEIndicatorSearchModel.java | 30 +++++++++---------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java index 451f4003e43..2b04ed21b68 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java @@ -25,8 +25,11 @@ import org.digijava.module.aim.dbentity.IndicatorActivity; import org.digijava.module.aim.util.DbUtil; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; +import org.digijava.module.aim.dbentity.AmpActivityProgram; /** * M&E section @@ -126,17 +129,28 @@ public Integer getChoiceLevel(AmpIndicator choice) { }; - searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, am.getObject().getActPrograms()); + searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, extractProgramThemeIds(am.getObject().getActPrograms())); add(UpdateEventBehavior.of(ProgramSelectedEvent.class)); add(searchIndicators); } + private static Set extractProgramThemeIds(Set programs) { + if (programs == null || programs.isEmpty()) return Collections.emptySet(); + Set ids = new HashSet<>(); + for (AmpActivityProgram ap : programs) { + if (ap.getProgram() != null && ap.getProgram().getAmpThemeId() != null) { + ids.add(ap.getProgram().getAmpThemeId()); + } + } + return ids; + } + @Override protected void onBeforeRender() { super.onBeforeRender(); searchIndicators.getModelParams().put( AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, - activityModel.getObject().getActPrograms() + extractProgramThemeIds(activityModel.getObject().getActPrograms()) ); } } diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java index 3cce33da615..6992b03eca5 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java @@ -139,7 +139,15 @@ protected String load() { }); add(indicatorTargetDateLabel); - AmpMEActualValuesFormTableFeaturePanel valuesTable = new AmpMEActualValuesFormTableFeaturePanel("valuesSubsection", indicator, conn, "Actual Values", false, 7); + final boolean hasDisaggregation = indicator.getObject().getDisaggregation() != null + && !indicator.getObject().getDisaggregation().isEmpty(); + AmpMEActualValuesFormTableFeaturePanel valuesTable = new AmpMEActualValuesFormTableFeaturePanel("valuesSubsection", indicator, conn, "Actual Values", false, 7) { + @Override + protected void onConfigure() { + super.onConfigure(); + if (isVisible()) setVisible(!hasDisaggregation); + } + }; valuesTable.setOutputMarkupPlaceholderTag(true); add(valuesTable); @@ -154,6 +162,12 @@ public void onClick(AjaxRequestTarget target) { target.add(valuesTable); target.appendJavaScript(QuarterInformationPanel.getJSUpdate(getSession())); } + + @Override + protected void onConfigure() { + super.onConfigure(); + if (isVisible()) setVisible(!hasDisaggregation); + } }; addActualValue.setOutputMarkupPlaceholderTag(true); @@ -193,11 +207,7 @@ protected void onClick(AjaxRequestTarget art) { AmpMEDisaggregationValuesFeaturePanel disaggPanel = new AmpMEDisaggregationValuesFeaturePanel("disaggregationValuesSubsection","Disaggregation Values", indicator); disaggPanel.setOutputMarkupId(true); disaggPanel.setOutputMarkupPlaceholderTag(true); - boolean hasDisaggregation = indicator.getObject().getDisaggregation() != null - && !indicator.getObject().getDisaggregation().isEmpty(); disaggPanel.setVisible(hasDisaggregation); - valuesTable.setVisible(!hasDisaggregation); - addActualValue.setVisible(!hasDisaggregation); add(disaggPanel); diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java index aec1e7b63a2..b14d996c746 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java @@ -49,7 +49,7 @@ protected Collection load() { Criteria crit = session.createCriteria(AmpIndicator.class); - Set ampActivityPrograms = (Set) getParam(PARAM.ACTIVITY_PROGRAM); + Set activityProgramThemeIds = (Set) getParam(PARAM.ACTIVITY_PROGRAM); // Get activity location crit.setCacheable(false); @@ -67,23 +67,23 @@ protected Collection load() { filterAmpIndicators = ret; // Check if the indicator filter by program is active boolean filterByProgram = FeaturesUtil.isVisibleModule(IndicatorManagerService.FILTER_BY_PROGRAM); - if(filterByProgram) { - // If not activity programs then do not return any indicator - if (ampActivityPrograms != null && !ampActivityPrograms.isEmpty()) { - Set programThemes = ampActivityPrograms.stream() - .map(AmpActivityProgram::getProgram) - .collect(Collectors.toSet()); - Set programThemesClone = new HashSet<>(programThemes); - // Check if program has siblings and add them to themes to get all indicators for objectives in a program - for (AmpTheme program : programThemes) { - if (program.getSiblings() != null) { - programThemesClone.addAll(program.getSiblings()); + if (filterByProgram) { + if (activityProgramThemeIds != null && !activityProgramThemeIds.isEmpty()) { + // Include siblings (children in AMP hierarchy) via fresh session to avoid lazy-load issues + Set allProgramIds = new HashSet<>(activityProgramThemeIds); + for (Long themeId : activityProgramThemeIds) { + AmpTheme theme = session.get(AmpTheme.class, themeId); + if (theme != null && theme.getSiblings() != null) { + for (AmpTheme sibling : theme.getSiblings()) { + if (sibling.getAmpThemeId() != null) { + allProgramIds.add(sibling.getAmpThemeId()); + } + } } } - - filterAmpIndicators = ret.stream() - .filter(indicator -> programThemesClone.contains(indicator.getProgram())) + .filter(ind -> ind.getProgram() != null + && allProgramIds.contains(ind.getProgram().getAmpThemeId())) .collect(Collectors.toList()); } } From 56c285cf691e4aca2c51d316ad80f6c93d50f32c Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 13:27:11 +0300 Subject: [PATCH 04/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../AmpMEFormSectionFeature.java | 32 ++----------------- .../models/AmpMEIndicatorSearchModel.java | 8 +++++ 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java index 2b04ed21b68..afc6571ffdf 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java @@ -25,11 +25,8 @@ import org.digijava.module.aim.dbentity.IndicatorActivity; import org.digijava.module.aim.util.DbUtil; -import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Set; -import org.digijava.module.aim.dbentity.AmpActivityProgram; /** * M&E section @@ -39,13 +36,10 @@ */ public class AmpMEFormSectionFeature extends AmpFormSectionFeaturePanel { private final ListView list; - private final IModel activityModel; - private AmpAutocompleteFieldPanel searchIndicators; public AmpMEFormSectionFeature(String id, String fmName, final IModel am) throws Exception { super(id, fmName, am); - this.activityModel = am; this.fmType = AmpFMTypes.MODULE; if (am.getObject().getIndicators() == null) { @@ -97,7 +91,7 @@ public void onClick(AjaxRequestTarget target) { add(list); - searchIndicators = + final AmpAutocompleteFieldPanel searchIndicators = new AmpAutocompleteFieldPanel("search", "Search Indicators", AmpMEIndicatorSearchModel.class) { @@ -129,28 +123,8 @@ public Integer getChoiceLevel(AmpIndicator choice) { }; - searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, extractProgramThemeIds(am.getObject().getActPrograms())); + searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, am.getObject().getActPrograms()); add(UpdateEventBehavior.of(ProgramSelectedEvent.class)); add(searchIndicators); } - - private static Set extractProgramThemeIds(Set programs) { - if (programs == null || programs.isEmpty()) return Collections.emptySet(); - Set ids = new HashSet<>(); - for (AmpActivityProgram ap : programs) { - if (ap.getProgram() != null && ap.getProgram().getAmpThemeId() != null) { - ids.add(ap.getProgram().getAmpThemeId()); - } - } - return ids; - } - - @Override - protected void onBeforeRender() { - super.onBeforeRender(); - searchIndicators.getModelParams().put( - AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, - extractProgramThemeIds(activityModel.getObject().getActPrograms()) - ); - } -} +} \ No newline at end of file diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java index b14d996c746..cbb4ebebfc7 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java @@ -15,6 +15,7 @@ import java.util.*; import java.util.stream.Collectors; +import org.apache.log4j.Logger; /** * @author aartimon@dginternational.org @@ -31,6 +32,8 @@ public AmpMEIndicatorSearchModel(String input, String language, private static final long serialVersionUID = 8211300754918658832L; private Session session; + private static final Logger logger = Logger.getLogger(AmpMEIndicatorSearchModel.class); + public enum PARAM implements AmpAutoCompleteModelParam { ACTIVITY_PROGRAM @@ -62,11 +65,16 @@ protected Collection load() { if (maxResults != null && maxResults != 0) crit.setMaxResults(maxResults); ret = crit.list(); + ret.forEach(ind -> logger.info("Indicator: " + ind.getName() + + " | programId: " + (ind.getProgram() != null ? ind.getProgram().getAmpThemeId() : "null"))); // Re assign all indicators as filtered filterAmpIndicators = ret; // Check if the indicator filter by program is active boolean filterByProgram = FeaturesUtil.isVisibleModule(IndicatorManagerService.FILTER_BY_PROGRAM); + logger.info("Filter by program "+filterByProgram); + logger.info("All programs :" +activityProgramThemeIds); + if (filterByProgram) { if (activityProgramThemeIds != null && !activityProgramThemeIds.isEmpty()) { // Include siblings (children in AMP hierarchy) via fresh session to avoid lazy-load issues From f3ceb5b7f7e4ad1392b2015a85cff631d3eee9df Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 14:09:11 +0300 Subject: [PATCH 05/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../endpoints/indicator/manager/IndicatorManagerService.java | 2 +- .../kernel/ampapi/endpoints/settings/SettingsConstants.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java index bad04679b80..e67ae5f76df 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java @@ -46,7 +46,7 @@ public class IndicatorManagerService { private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy"); - public static final String FILTER_BY_PROGRAM = "Filter By Program"; + public static final String FILTER_BY_PROGRAM = "Filter by Program"; public static final String FILTER_BY_SECTOR = "Filter By Sector"; diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java index 6264e3ea473..cc460beb867 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java @@ -109,7 +109,7 @@ public class SettingsConstants { put(YEAR_ALL, "All"); put(PROGRAM_SETTINGS, "Program Settings"); put(INDICATOR_FILTER_BY_SECTOR, "Filter By Sector"); - put(INDICATOR_FILTER_BY_PROGRAM, "Filter By Program"); + put(INDICATOR_FILTER_BY_PROGRAM, "Filter by Program"); put(NUMBER_OF_INDICATORS_IN_DASHBOARD, "Number of indicators in M&E Dashboard"); put(SORT_COLUMN, GlobalSettingsConstants.DEFAULT_RESOURCES_SORT_COLUMN); put(MAXIMUM_FILE_SIZE, GlobalSettingsConstants.CR_MAX_FILE_SIZE ); From b330756550e02ccbf27960d54651065fe8b6a909 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 14:29:46 +0300 Subject: [PATCH 06/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../models/AmpMEIndicatorSearchModel.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java index cbb4ebebfc7..2618f391588 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java @@ -52,7 +52,7 @@ protected Collection load() { Criteria crit = session.createCriteria(AmpIndicator.class); - Set activityProgramThemeIds = (Set) getParam(PARAM.ACTIVITY_PROGRAM); + Set activityProgramThemeIds = toProgramThemeIds(getParam(PARAM.ACTIVITY_PROGRAM)); // Get activity location crit.setCacheable(false); @@ -102,4 +102,23 @@ protected Collection load() { } } + @SuppressWarnings("unchecked") + private static Set toProgramThemeIds(Object param) { + if (param == null) return Collections.emptySet(); + Set raw = (Set) param; + if (raw.isEmpty()) return Collections.emptySet(); + Object first = raw.iterator().next(); + if (first instanceof Long) { + return (Set) raw; + } + // Legacy: stale Wicket session still holds Set + Set ids = new HashSet<>(); + for (AmpActivityProgram ap : (Set) raw) { + if (ap.getProgram() != null && ap.getProgram().getAmpThemeId() != null) { + ids.add(ap.getProgram().getAmpThemeId()); + } + } + return ids; + } + } From db1b1641b65822487433783068168efc3d5f0e04 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 14:52:35 +0300 Subject: [PATCH 07/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../me/singlecountry/AmpMEItemFeaturePanel.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java index 6992b03eca5..ea118c96a5d 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java @@ -97,12 +97,16 @@ public AmpMEItemFeaturePanel(String id, String fmName, final IModel() { @Override protected String load() { return globalBaseVal.getOriginalValue() != null ? String.valueOf(globalBaseVal.getOriginalValue()) : "N/A"; } }); + indicatorBaseValueLabel.setVisible(!hasDisaggregation); add(indicatorBaseValueLabel); final Label indicatorBaseDateLabel = new Label("baseDate", new LoadableDetachableModel() { @@ -116,6 +120,7 @@ protected String load() { } } }); + indicatorBaseDateLabel.setVisible(!hasDisaggregation); add(indicatorBaseDateLabel); final Label indicatorTargetValueLabel = new Label("target", new LoadableDetachableModel() { @@ -124,6 +129,7 @@ protected String load() { return globalTargetVal.getOriginalValue() != null ? String.valueOf(globalTargetVal.getOriginalValue()) : "N/A"; } }); + indicatorTargetValueLabel.setVisible(!hasDisaggregation); add(indicatorTargetValueLabel); final Label indicatorTargetDateLabel = new Label("targetDate", new LoadableDetachableModel() { @@ -137,10 +143,8 @@ protected String load() { } } }); + indicatorTargetDateLabel.setVisible(!hasDisaggregation); add(indicatorTargetDateLabel); - - final boolean hasDisaggregation = indicator.getObject().getDisaggregation() != null - && !indicator.getObject().getDisaggregation().isEmpty(); AmpMEActualValuesFormTableFeaturePanel valuesTable = new AmpMEActualValuesFormTableFeaturePanel("valuesSubsection", indicator, conn, "Actual Values", false, 7) { @Override protected void onConfigure() { From e22ff15961357f612f1abefd02ce1e2d40b0cab3 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 15:21:03 +0300 Subject: [PATCH 08/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../singlecountry/AmpMEItemFeaturePanel.html | 2 +- .../singlecountry/AmpMEItemFeaturePanel.java | 30 +++++++++---------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.html b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.html index d881223f476..0fc3e013053 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.html +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.html @@ -24,7 +24,7 @@
-
+
Base Values : diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java index ea118c96a5d..0c96405d0a9 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEItemFeaturePanel.java @@ -7,6 +7,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; @@ -100,16 +101,17 @@ public AmpMEItemFeaturePanel(String id, String fmName, final IModel() { + WebMarkupContainer baseTargetContainer = new WebMarkupContainer("baseTargetContainer"); + baseTargetContainer.setVisible(!hasDisaggregation); + + baseTargetContainer.add(new Label("base", new LoadableDetachableModel() { @Override protected String load() { return globalBaseVal.getOriginalValue() != null ? String.valueOf(globalBaseVal.getOriginalValue()) : "N/A"; } - }); - indicatorBaseValueLabel.setVisible(!hasDisaggregation); - add(indicatorBaseValueLabel); + })); - final Label indicatorBaseDateLabel = new Label("baseDate", new LoadableDetachableModel() { + baseTargetContainer.add(new Label("baseDate", new LoadableDetachableModel() { @Override protected String load() { if (globalBaseVal.getOriginalValueDate() != null) { @@ -119,20 +121,16 @@ protected String load() { return "N/A"; } } - }); - indicatorBaseDateLabel.setVisible(!hasDisaggregation); - add(indicatorBaseDateLabel); + })); - final Label indicatorTargetValueLabel = new Label("target", new LoadableDetachableModel() { + baseTargetContainer.add(new Label("target", new LoadableDetachableModel() { @Override protected String load() { return globalTargetVal.getOriginalValue() != null ? String.valueOf(globalTargetVal.getOriginalValue()) : "N/A"; } - }); - indicatorTargetValueLabel.setVisible(!hasDisaggregation); - add(indicatorTargetValueLabel); + })); - final Label indicatorTargetDateLabel = new Label("targetDate", new LoadableDetachableModel() { + baseTargetContainer.add(new Label("targetDate", new LoadableDetachableModel() { @Override protected String load() { if (globalTargetVal.getOriginalValueDate() != null) { @@ -142,9 +140,9 @@ protected String load() { return "N/A"; } } - }); - indicatorTargetDateLabel.setVisible(!hasDisaggregation); - add(indicatorTargetDateLabel); + })); + + add(baseTargetContainer); AmpMEActualValuesFormTableFeaturePanel valuesTable = new AmpMEActualValuesFormTableFeaturePanel("valuesSubsection", indicator, conn, "Actual Values", false, 7) { @Override protected void onConfigure() { From f3de200aa483dbd1b6fe22026529f8912f45bdb9 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Mon, 30 Mar 2026 16:20:09 +0300 Subject: [PATCH 09/10] AMP-31090: Resolving Errors in the AF with disaggregated indicators. --- .../amp/onepager/models/AmpMEIndicatorSearchModel.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java index 2618f391588..2a3b9ba124c 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java @@ -65,15 +65,10 @@ protected Collection load() { if (maxResults != null && maxResults != 0) crit.setMaxResults(maxResults); ret = crit.list(); - ret.forEach(ind -> logger.info("Indicator: " + ind.getName() - + " | programId: " + (ind.getProgram() != null ? ind.getProgram().getAmpThemeId() : "null"))); - // Re assign all indicators as filtered filterAmpIndicators = ret; // Check if the indicator filter by program is active boolean filterByProgram = FeaturesUtil.isVisibleModule(IndicatorManagerService.FILTER_BY_PROGRAM); - logger.info("Filter by program "+filterByProgram); - logger.info("All programs :" +activityProgramThemeIds); if (filterByProgram) { if (activityProgramThemeIds != null && !activityProgramThemeIds.isEmpty()) { From b48fe85130a9ad06d381bb58373ba49682966408 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Tue, 31 Mar 2026 12:40:01 +0300 Subject: [PATCH 10/10] AMP-31090: Correct Filter by Sector --- .../AmpMEFormSectionFeature.java | 45 ++++++++++++++++++- .../models/AmpMEIndicatorSearchModel.java | 28 ++++++++++-- .../manager/IndicatorManagerService.java | 2 +- .../endpoints/settings/SettingsConstants.java | 2 +- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java index afc6571ffdf..c5a5a2ba4fd 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/components/features/me/singlecountry/AmpMEFormSectionFeature.java @@ -20,13 +20,17 @@ import org.dgfoundation.amp.onepager.translation.TranslatorUtil; import org.dgfoundation.amp.onepager.util.AmpFMTypes; import org.dgfoundation.amp.onepager.yui.AmpAutocompleteFieldPanel; +import org.digijava.module.aim.dbentity.AmpActivityProgram; +import org.digijava.module.aim.dbentity.AmpActivitySector; import org.digijava.module.aim.dbentity.AmpActivityVersion; import org.digijava.module.aim.dbentity.AmpIndicator; import org.digijava.module.aim.dbentity.IndicatorActivity; import org.digijava.module.aim.util.DbUtil; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; /** * M&E section @@ -36,10 +40,13 @@ */ public class AmpMEFormSectionFeature extends AmpFormSectionFeaturePanel { private final ListView list; + private final IModel activityModel; + private AmpAutocompleteFieldPanel searchIndicators; public AmpMEFormSectionFeature(String id, String fmName, final IModel am) throws Exception { super(id, fmName, am); + this.activityModel = am; this.fmType = AmpFMTypes.MODULE; if (am.getObject().getIndicators() == null) { @@ -91,7 +98,7 @@ public void onClick(AjaxRequestTarget target) { add(list); - final AmpAutocompleteFieldPanel searchIndicators = + searchIndicators = new AmpAutocompleteFieldPanel("search", "Search Indicators", AmpMEIndicatorSearchModel.class) { @@ -123,8 +130,42 @@ public Integer getChoiceLevel(AmpIndicator choice) { }; - searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, am.getObject().getActPrograms()); + searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, + extractProgramThemeIds(am.getObject().getActPrograms())); + searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_SECTOR, + extractSectorIds(am.getObject().getSectors())); add(UpdateEventBehavior.of(ProgramSelectedEvent.class)); add(searchIndicators); } + + private static Set extractProgramThemeIds(Set programs) { + if (programs == null || programs.isEmpty()) return Collections.emptySet(); + Set ids = new HashSet<>(); + for (AmpActivityProgram ap : programs) { + if (ap.getProgram() != null && ap.getProgram().getAmpThemeId() != null) { + ids.add(ap.getProgram().getAmpThemeId()); + } + } + return ids; + } + + private static Set extractSectorIds(Set sectors) { + if (sectors == null || sectors.isEmpty()) return Collections.emptySet(); + Set ids = new HashSet<>(); + for (AmpActivitySector as : sectors) { + if (as.getSectorId() != null && as.getSectorId().getAmpSectorId() != null) { + ids.add(as.getSectorId().getAmpSectorId()); + } + } + return ids; + } + + @Override + protected void onBeforeRender() { + super.onBeforeRender(); + searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_PROGRAM, + extractProgramThemeIds(activityModel.getObject().getActPrograms())); + searchIndicators.getModelParams().put(AmpMEIndicatorSearchModel.PARAM.ACTIVITY_SECTOR, + extractSectorIds(activityModel.getObject().getSectors())); + } } \ No newline at end of file diff --git a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java index 2a3b9ba124c..d1a5eb1ffd4 100644 --- a/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java +++ b/amp/src/main/java/org/dgfoundation/amp/onepager/models/AmpMEIndicatorSearchModel.java @@ -36,7 +36,7 @@ public AmpMEIndicatorSearchModel(String input, String language, public enum PARAM implements AmpAutoCompleteModelParam { - ACTIVITY_PROGRAM + ACTIVITY_PROGRAM, ACTIVITY_SECTOR } @@ -53,10 +53,12 @@ protected Collection load() { Criteria crit = session.createCriteria(AmpIndicator.class); Set activityProgramThemeIds = toProgramThemeIds(getParam(PARAM.ACTIVITY_PROGRAM)); + Set activitySectorIds = toSectorIds(getParam(PARAM.ACTIVITY_SECTOR)); + logger.info("All sectors: " + activitySectorIds); // Get activity location crit.setCacheable(false); - if (input.trim().length() > 0) { + if (!input.trim().isEmpty()) { Junction junction = Restrictions.conjunction().add(getTextCriterion("name", input)); crit.add(junction); } @@ -71,7 +73,7 @@ protected Collection load() { boolean filterByProgram = FeaturesUtil.isVisibleModule(IndicatorManagerService.FILTER_BY_PROGRAM); if (filterByProgram) { - if (activityProgramThemeIds != null && !activityProgramThemeIds.isEmpty()) { + if (!activityProgramThemeIds.isEmpty()) { // Include siblings (children in AMP hierarchy) via fresh session to avoid lazy-load issues Set allProgramIds = new HashSet<>(activityProgramThemeIds); for (Long themeId : activityProgramThemeIds) { @@ -91,12 +93,32 @@ protected Collection load() { } } + // Check if the indicator filter by sector is active + boolean filterBySector = FeaturesUtil.isVisibleModule(IndicatorManagerService.FILTER_BY_SECTOR); + if (filterBySector) { + if (!activitySectorIds.isEmpty()) { + filterAmpIndicators = filterAmpIndicators.stream() + .filter(ind -> ind.getSectors() != null && ind.getSectors().stream() + .anyMatch(s -> s.getAmpSectorId() != null + && activitySectorIds.contains(s.getAmpSectorId()))) + .collect(Collectors.toList()); + } + } + return filterAmpIndicators; } catch (HibernateException e) { throw new RuntimeException(e); } } + @SuppressWarnings("unchecked") + private static Set toSectorIds(Object param) { + if (param == null) return Collections.emptySet(); + Set raw = (Set) param; + if (raw.isEmpty()) return Collections.emptySet(); + return (Set) raw; + } + @SuppressWarnings("unchecked") private static Set toProgramThemeIds(Object param) { if (param == null) return Collections.emptySet(); diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java index e67ae5f76df..08f836e21d7 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/indicator/manager/IndicatorManagerService.java @@ -48,7 +48,7 @@ public class IndicatorManagerService { public static final String FILTER_BY_PROGRAM = "Filter by Program"; - public static final String FILTER_BY_SECTOR = "Filter By Sector"; + public static final String FILTER_BY_SECTOR = "Filter by Sector"; public static String INDICATOR_CATEGORY_KEY = "core_indicator_type"; diff --git a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java index cc460beb867..d6656886156 100644 --- a/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java +++ b/amp/src/main/java/org/digijava/kernel/ampapi/endpoints/settings/SettingsConstants.java @@ -108,7 +108,7 @@ public class SettingsConstants { put(YEAR_TO, "To:"); put(YEAR_ALL, "All"); put(PROGRAM_SETTINGS, "Program Settings"); - put(INDICATOR_FILTER_BY_SECTOR, "Filter By Sector"); + put(INDICATOR_FILTER_BY_SECTOR, "Filter by Sector"); put(INDICATOR_FILTER_BY_PROGRAM, "Filter by Program"); put(NUMBER_OF_INDICATORS_IN_DASHBOARD, "Number of indicators in M&E Dashboard"); put(SORT_COLUMN, GlobalSettingsConstants.DEFAULT_RESOURCES_SORT_COLUMN);