Skip to content

RAMP: August-September features - #39

Merged
alex-vt merged 20 commits into
ramp-mainfrom
ramp-august
Oct 1, 2026
Merged

alex-vt merged 20 commits into
ramp-mainfrom
ramp-august

Conversation

@alex-vt

@alex-vt alex-vt commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

External credential (QR code) capture from Simprints ID responses, + RAMP-specific non-biometric features

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved correctness and compatibility issues can omit form fields, alter existing Simprints module IDs, and hide enrollment information.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 4 Medium severity

Open (5)
What changed in this PR

Adds RAMP-specific biometric credential handling, configurable search/form behavior, history visualization, scheduling, filters, and enrollment details.

Changes:

  • Captures and stores Simprints QR external credentials.
  • Adds RAMP-configurable charts, visit scheduling, search, and enrollment presentation.
  • Adds overdue/sync-error quick filters and extensive tests.
File Description
form/​src/​test/​java/​org/​dhis2/​form/​ui/​FormViewModelTest.kt Tests credential-save sequencing and failures.
form/​src/​test/​java/​org/​dhis2/​form/​simprints/​ramp/​ui/​IntegerAwareValueFormatterTest.kt Tests categorical chart labels.
form/​src/​test/​java/​org/​dhis2/​form/​simprints/​ramp/​data/​FormHistoryChartRepositoryTest.kt Tests categorical and inverted charts.
form/​src/​test/​java/​org/​dhis2/​form/​data/​FormValueStoreTest.kt Tests enrollment UID resolution.
form/​src/​test/​java/​org/​dhis2/​form/​data/​FormRepositoryImplTest.kt Tests capture-only and credential behavior.
form/​src/​test/​java/​org/​dhis2/​form/​data/​EnrollmentRepositoryTest.kt Tests capture-only biometrics fields.
form/​src/​main/​java/​org/​dhis2/​form/​ui/​intent/​FormIntent.kt Carries external credentials in intents.
form/​src/​main/​java/​org/​dhis2/​form/​ui/​FormViewModel.kt Saves credentials after biometric GUIDs.
form/​src/​main/​java/​org/​dhis2/​form/​ui/​FormView.kt Extracts credentials from callout results.
form/​src/​main/​java/​org/​dhis2/​form/​simprints/​ramp/​ui/​FormHistoryChartView.kt Renders categorical and inverted axes.
form/​src/​main/​java/​org/​dhis2/​form/​simprints/​ramp/​model/​FormHistoryChart.kt Models chart categories and mapping.
form/​src/​main/​java/​org/​dhis2/​form/​simprints/​ramp/​data/​FormHistoryChartRepository.kt Loads option-set chart categories.
form/​src/​main/​java/​org/​dhis2/​form/​di/​Injector.kt Wires RAMP form configuration.
form/​src/​main/​java/​org/​dhis2/​form/​data/​FormValueStore.kt Exposes enrollment UID.
form/​src/​main/​java/​org/​dhis2/​form/​data/​FormRepositoryImpl.kt Implements credential and visibility behavior.
form/​src/​main/​java/​org/​dhis2/​form/​data/​FormRepository.kt Adds credential-save API.
form/​src/​main/​java/​org/​dhis2/​form/​data/​EnrollmentRepository.kt Relocates capture-only biometrics fields.
commonskmm/​src/​androidMain/​kotlin/​org/​dhis2/​mobile/​commons/​customintents/​CustomIntentRepositoryImpl.kt Configures Simprints module IDs.
commons/​src/​test/​kotlin/​org/​dhis2/​commons/​simprints/​utils/​SimprintsExternalCredentialUtilsTest.kt Tests credential parsing.
commons/​src/​test/​kotlin/​org/​dhis2/​commons/​simprints/​usecases/​SimprintsResolveConfirmIdentityCalloutUseCaseTest.kt Tests biometric attribute propagation.
commons/​src/​test/​kotlin/​org/​dhis2/​commons/​simprints/​repository/​SimprintsD2RepositoryTest.kt Tests credential persistence validation.
commons/​src/​test/​kotlin/​org/​dhis2/​commons/​simprints/​ramp/​repository/​RampDatastoreRepositoryTest.kt Tests new RAMP settings.
commons/​src/​main/​res/​values/​strings.xml Adds sync-error filter label.
commons/​src/​main/​res/​layout/​item_filter_sync_error.xml Adds sync-error quick-filter UI.
commons/​src/​main/​res/​layout/​item_filter_overdue.xml Adds overdue quick-filter UI.
commons/​src/​main/​java/​org/​dhis2/​commons/​ui/​model/​ListCardUiModel.kt Removes custom styled values.
commons/​src/​main/​java/​org/​dhis2/​commons/​ui/​ListCardProvider.kt Simplifies emphasized rows.
commons/​src/​main/​java/​org/​dhis2/​commons/​simprints/​utils/​SimprintsIntentUtils.kt Tracks biometric attribute IDs.
commons/​src/​main/​java/​org/​dhis2/​commons/​simprints/​utils/​SimprintsExternalCredentialUtils.kt Parses QR credentials.
commons/​src/​main/​java/​org/​dhis2/​commons/​simprints/​usecases/​SimprintsResolveConfirmIdentityCalloutUseCase.kt Propagates biometric field IDs.
commons/​src/​main/​java/​org/​dhis2/​commons/​simprints/​repository/​SimprintsD2Repository.kt Persists validated external credentials.
commons/​src/​main/​java/​org/​dhis2/​commons/​simprints/​ramp/​repository/​RampDatastoreRepository.kt Exposes new RAMP options.
commons/​src/​main/​java/​org/​dhis2/​commons/​simprints/​ramp/​model/​RampDatastoreConfig.kt Models new RAMP configuration.
commons/​src/​main/​java/​org/​dhis2/​commons/​filters/​Filters.kt Registers quick-filter types.
commons/​src/​main/​java/​org/​dhis2/​commons/​filters/​FilterResources.kt Supplies quick-filter labels.
commons/​src/​main/​java/​org/​dhis2/​commons/​filters/​FilterManager.java Tracks quick-filter state.
commons/​src/​main/​java/​org/​dhis2/​commons/​filters/​FilterItem.kt Implements quick-filter models.
commons/​src/​main/​java/​org/​dhis2/​commons/​filters/​data/​FilterRepository.kt Inserts available quick filters.
app/​src/​test/​java/​org/​dhis2/​usescases/​teiDashboard/​teiProgramList/​TeiProgramListRepositoryImplTest.kt Tests enrollment detail formatting.
app/​src/​test/​java/​org/​dhis2/​usescases/​teiDashboard/​dialogs/​scheduling/​SchedulingViewModelTest.kt Updates scheduling dependencies.
app/​src/​test/​java/​org/​dhis2/​usescases/​searchTrackEntity/​ui/​mapper/​TEICardMapperTest.kt Removes obsolete detail-card tests.
app/​src/​test/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchTEIViewModelTest.kt Tests direct search and hidden lists.
app/​src/​test/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchRepositoryTest.kt Tests unfiltered-list configuration.
app/​src/​test/​java/​org/​dhis2/​usescases/​eventsWithoutRegistration/​eventDetails/​domain/​ConfigureEventReportDateTest.kt Tests anchored scheduling.
app/​src/​test/​java/​org/​dhis2/​usescases/​eventsWithoutRegistration/​eventDetails/​data/​EventDetailsRepositoryTest.kt Tests anchored visit context.
app/​src/​test/​java/​org/​dhis2/​simprints/​SimprintsEnrollmentViewModelTest.kt Tests enrollment credential sequencing.
app/​src/​test/​java/​org/​dhis2/​simprints/​ramp/​data/​EventHistoryTableRepositoryTest.kt Tests visit ranges and labels.
app/​src/​test/​java/​org/​dhis2/​simprints/​ramp/​data/​DetailedEnrollmentRepositoryTest.kt Tests enrollment detail mapping.
app/​src/​test/​java/​org/​dhis2/​data/​filter/​FilterRepositoryTest.kt Tests quick-filter availability.
app/​src/​main/​res/​values/​strings.xml Adds search and detail labels.
app/​src/​main/​res/​layout/​item_tei_programs_enrollment.xml Displays active enrollment details.
app/​src/​main/​res/​layout/​item_tei_programs_enrollment_inactive.xml Displays inactive enrollment details.
app/​src/​main/​res/​layout-land/​activity_search.xml Adds direct-search landscape styling.
app/​src/​main/​res/​drawable/​bg_landscape_direct_search_simprints_ramp.xml Defines direct-search background.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​teiProgramList/​TeiProgramListRepositoryImpl.java Maps detailed enrollment rows.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​teiProgramList/​TeiProgramListModule.java Wires detail repositories.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​teiProgramList/​TeiProgramListActivity.java Receives current program ID.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​teiProgramList/​EnrollmentViewModel.kt Adds formatted enrollment details.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​TeiDashboardMobileActivity.kt Passes program ID to enrollment list.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​dialogs/​scheduling/​SchedulingViewModelFactory.kt Injects RAMP scheduling settings.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​dialogs/​scheduling/​SchedulingViewModel.kt Configures anchored scheduling.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​dialogs/​scheduling/​SchedulingModule.kt Provides RAMP repository.
app/​src/​main/​java/​org/​dhis2/​usescases/​teiDashboard/​dashboardfragments/​teidata/​TeiDataRepositoryImpl.kt Sorts and expands visit lists.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​ui/​SearchTEUi.kt Supports full-width search buttons.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​ui/​SearchScreenConfigurator.kt Configures direct biometric search UI.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​ui/​mapper/​TEICardMapper.kt Removes detailed enrollment card rendering.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchTEModule.java Wires module-ID and credential dependencies.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchTEIViewModel.kt Adds direct search and list controls.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchTeiModel.java Removes detailed enrollment state.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchTEActivity.kt Handles credential results and UI state.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchRepositoryImplKt.kt Removes search-card enrollment details.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchRepositoryImpl.java Delegates unfiltered-list setting.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchRepository.java Adds unfiltered-list API.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​SearchJavaToCompose.kt Presents direct-search button.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​listView/​SearchTEList.kt Hides empty unfiltered results.
app/​src/​main/​java/​org/​dhis2/​usescases/​searchTrackEntity/​di/​SearchTEKoinModule.kt Injects credential repository.
app/​src/​main/​java/​org/​dhis2/​usescases/​eventsWithoutRegistration/​eventDetails/​models/​AnchoredScheduleContext.kt Models anchored visit context.
app/​src/​main/​java/​org/​dhis2/​usescases/​eventsWithoutRegistration/​eventDetails/​injection/​EventDetailsModule.kt Wires anchored scheduling.
app/​src/​main/​java/​org/​dhis2/​usescases/​eventsWithoutRegistration/​eventDetails/​domain/​ConfigureEventReportDate.kt Calculates anchored dates.
app/​src/​main/​java/​org/​dhis2/​usescases/​eventsWithoutRegistration/​eventDetails/​data/​EventDetailsRepository.kt Resolves initial and skipped visits.
app/​src/​main/​java/​org/​dhis2/​usescases/​enrollment/​EnrollmentModule.kt Configures enrollment callout module IDs.
app/​src/​main/​java/​org/​dhis2/​simprints/​SimprintsSearchViewModel.kt Saves credentials after confirmation.
app/​src/​main/​java/​org/​dhis2/​simprints/​SimprintsEnrollmentViewModel.kt Saves enrollment credentials.
app/​src/​main/​java/​org/​dhis2/​simprints/​ramp/​ui/​EventHistoryTableScreen.kt Displays configured visit labels.
app/​src/​main/​java/​org/​dhis2/​simprints/​ramp/​model/​EventHistoryTableModels.kt Adds visit-label state.
app/​src/​main/​java/​org/​dhis2/​simprints/​ramp/​model/​DetailedEnrollment.kt Simplifies detail model.
app/​src/​main/​java/​org/​dhis2/​simprints/​ramp/​data/​EventHistoryTableRepository.kt Applies configured visit ranges.
app/​src/​main/​java/​org/​dhis2/​simprints/​ramp/​data/​DetailedEnrollmentRepository.kt Maps SDK enrollments to details.
app/​src/​main/​java/​org/​dhis2/​simprints/​di/​SimprintsSearchViewModelFactory.kt Injects credential persistence.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt Outdated
Comment thread commons/src/main/java/org/dhis2/commons/ui/ListCardProvider.kt
resultCode: Int,
data: Intent? = null,
): PendingDashboardNavigation? {
val navigation = pendingDashboardNavigation.exchange(null) ?: return null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pending navigation is cleared (exchange(null)) before saveExternalCredential runs. If the save throws, SearchTEIViewModel.onConfirmIdentityResult catches it and shows custom_intent_error, but the navigation is already gone. The user confirmed their identity in SID and still ends up stuck on the search screen.

The credential is optional, so I'd wrap the save in its own try/catch (log it and carry on) and always return navigation when resultCode == RESULT_OK. Also, the Timber.e(...) in the SearchTEIViewModel catch doesn't pass the exception, so the cause is lost. Timber.e(e, "...") would keep it.

result.valueStoreResult in
listOf(ValueStoreResult.VALUE_CHANGED, ValueStoreResult.VALUE_HAS_NOT_CHANGED)
) {
val credentialResult = repository.saveSimprintsExternalCredential(it.uid, it.simprintsExternalCredential)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the credential save fails, saveSimprintsExternalCredential returns ERROR_UPDATING_VALUE for the biometric field's uid, and that result replaces the biometric save's own result. So even though the GUID saved fine, the form shows update_field_error, never posts _savedValue, and runs processCalculatedItems(true), which skips program rules.

Could we keep result as the returned value and handle a credential failure separately (log, or a non-blocking message)? It might still be worth using the credential result when it's VALUE_CHANGED and the biometric result was VALUE_HAS_NOT_CHANGED, so the list refreshes.

data?.extras,
)
if (externalCredentialValue != null && enrollmentUid != null) {
simprintsD2Repository.saveEnrollmentExternalCredential(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same pattern here. By this point the GUID is saved and SID has registered the person, but if saveEnrollmentExternalCredential throws, the outer catch clears the pending enrollment and rethrows. sessionRepository.clear() never runs and CONTINUE_FINISH is never returned. I'd wrap the credential save in its own try/catch so it can't turn a successful registration into an error.

.trackedEntityAttributeValues()
.value(attributeUid, teiUid)
if (attributeValue.blockingGet()?.value() != externalCredentialValue) {
attributeValue.blockingSet(externalCredentialValue)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This writes straight through trackedEntityAttributeValues().value(...).blockingSet(), which skips the unique-attribute check that FormValueStore normally does (UniqueAttributeController). A QR credential attribute is very likely configured as unique. If the same code gets scanned for two people, both writes succeed locally and one TEI then fails to sync with a non-unique error. The field is read-only in the form, so the user can't fix it.

Could we check attribute.unique() and look for an existing value on another TEI before writing? Skipping the write, or telling the user, would be better than storing a duplicate.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The QR code is not configured as unique, it is just a "fallback" info field to assist with secondary, non-biometric search. In the whole system, SID is the part where the uniqueness is enforced, it will be so for most of the normal use. I'd say we should avoid repeating the QR uniqueness duty as extra fork changes. At worst, there would be just more than 1 search results per code. If that becomes an issue as a case of duplicates, we can then clean those up via API or WebUI and revisit the fork change.

if (sourceField.isLoadingData || !sourceField.value.isNullOrEmpty()) {
return filterNot { it.uid == attributeId }
}
if (any { it.uid == attributeId }) return this

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, the only way the capture-only field is in itemList but missing from the list here is that applyRuleEffects removed it (HIDEFIELD or HIDESECTION). If so, this branch puts the field back in and overrides the program rule, so a hidden biometrics button still shows and can still be used.

If that's the only case, I think the insert branch can go: EnrollmentRepository already places the field, and this function only needs the filterNot for loading or has-value. If there's another case it's meant for, could we exclude fields the rule effects hid?

@alex-vt
alex-vt merged commit 3b45874 into ramp-main Oct 1, 2026
@alex-vt
alex-vt deleted the ramp-august branch October 1, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants