From 836cd7eb3e43793b2669664acccc61b863cf6da4 Mon Sep 17 00:00:00 2001 From: OrangeAndGreen Date: Fri, 28 Aug 2026 10:24:03 -0400 Subject: [PATCH 1/4] Added new single point of truth (SPOT) for isLearningComplete and updated readyToTransitionToDelivery. Using the new function in two places where we were only checking passedAssessment before. Updated two places with identical logic to use the new SPOT. Also keyed the learn progress bar's visibility on isLearningComplete rather than passedAssessment, so a user who passed the assessment with modules still outstanding sees their remaining module progress instead of a hidden bar. Co-Authored-By: Claude Opus 5 (1M context) --- .../connect/models/ConnectJobRecord.java | 10 +++++++++- ...nnectJobDetailBottomSheetDialogFragment.java | 2 +- .../connect/ConnectJobsListsFragment.java | 4 ++-- .../ConnectLearningProgressFragment.java | 17 +++++++++-------- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/org/commcare/android/database/connect/models/ConnectJobRecord.java b/app/src/org/commcare/android/database/connect/models/ConnectJobRecord.java index d146290831..dcd5a96ab3 100644 --- a/app/src/org/commcare/android/database/connect/models/ConnectJobRecord.java +++ b/app/src/org/commcare/android/database/connect/models/ConnectJobRecord.java @@ -541,6 +541,14 @@ public boolean passedAssessment() { || getAssessmentScore() >= getLearnAppInfo().getPassingScore(); } + /** + * Whether the user has finished everything requiresd before claiming the job: + * every learn module submitted, and a passing assessment score + */ + public boolean isLearningComplete() { + return getLearningPercentComplete(false) >= 100 && passedAssessment(); + } + public int getAssessmentScore() { int mostRecentFailingScore = 0; int firstPassingScore = -1; @@ -644,7 +652,7 @@ public void setPaymentUnits(List units) { } public boolean readyToTransitionToDelivery() { - return status == STATUS_LEARNING && passedAssessment(); + return status == STATUS_LEARNING && isLearningComplete(); } @Nullable diff --git a/app/src/org/commcare/fragments/connect/ConnectJobDetailBottomSheetDialogFragment.java b/app/src/org/commcare/fragments/connect/ConnectJobDetailBottomSheetDialogFragment.java index 497dc29919..e165b14962 100644 --- a/app/src/org/commcare/fragments/connect/ConnectJobDetailBottomSheetDialogFragment.java +++ b/app/src/org/commcare/fragments/connect/ConnectJobDetailBottomSheetDialogFragment.java @@ -133,7 +133,7 @@ public void handleProgressBarUI(ConnectJobRecord job) { R.drawable.ic_disabled_learn ); - boolean reviewEnabled = job.passedAssessment(); + boolean reviewEnabled = job.isLearningComplete(); setProgressIconState( binding.includeJobProgress.pbReview, binding.includeJobProgress.ivReview, diff --git a/app/src/org/commcare/fragments/connect/ConnectJobsListsFragment.java b/app/src/org/commcare/fragments/connect/ConnectJobsListsFragment.java index 994813837c..88b9eb5c3a 100644 --- a/app/src/org/commcare/fragments/connect/ConnectJobsListsFragment.java +++ b/app/src/org/commcare/fragments/connect/ConnectJobsListsFragment.java @@ -161,9 +161,9 @@ private void launchAppForJob(ConnectJobRecord job, boolean isLearning) { if (job.deliveryComplete()) { navigateToDeliveryProgress(); - } else if (!job.passedAssessment()) { + } else if (!job.isLearningComplete()) { navigateToLearnProgress(); - } else if (isLearning && job.passedAssessment()) { + } else if (isLearning) { navigateToDeliveryDetails(); } else if (AppUtils.isAppInstalled(appId)) { new ConnectAppLaunchController(this).launchApp(appId, isLearning); diff --git a/app/src/org/commcare/fragments/connect/ConnectLearningProgressFragment.java b/app/src/org/commcare/fragments/connect/ConnectLearningProgressFragment.java index d06f7d7b29..97c14c5b0e 100644 --- a/app/src/org/commcare/fragments/connect/ConnectLearningProgressFragment.java +++ b/app/src/org/commcare/fragments/connect/ConnectLearningProgressFragment.java @@ -108,10 +108,10 @@ private void updateLearningUI() { updateProgressViews( job.getLearningPercentComplete(true), - passedAssessment + job.isLearningComplete() ); - updateCertificateView(learningComplete, passedAssessment); - updateButtons(learningComplete, passedAssessment); + updateCertificateView(); + updateButtons(learningComplete); updateLearningStatus(learningComplete, passedAssessment, attemptedAssessment); } @@ -130,12 +130,13 @@ private void updateProgressViews(int learningProgressPercent, boolean hideProgre } } - private void updateCertificateView(boolean learningComplete, boolean passedAssessment) { + private void updateCertificateView() { + boolean showCertificate = job.isLearningComplete(); getBinding().connectLearningCertificateContainer.setVisibility( - learningComplete && passedAssessment ? View.VISIBLE : View.GONE + showCertificate ? View.VISIBLE : View.GONE ); - if (learningComplete && passedAssessment) { + if (showCertificate) { getBinding().connectLearnCertSubject.setText(job.getTitle()); getBinding().connectLearnCertPerson.setText( ConnectUserDatabaseUtil.getUser(requireContext()).getName() @@ -172,12 +173,12 @@ private Date getLatestCompletionDate() { return latestDate != null ? latestDate : new Date(); } - private void updateButtons(boolean learningComplete, boolean passedAssessment) { + private void updateButtons(boolean learningComplete) { getBinding().connectLearningReviewButton.setVisibility(View.GONE); // reserved for future logic getBinding().connectLearningButton.setVisibility(showAppLaunch ? View.VISIBLE : View.GONE); if (showAppLaunch) { - if (learningComplete && passedAssessment) { + if (job.isLearningComplete()) { configureJobDetailsButton(); } else if (!AppUtils.isAppInstalled(job.getLearnAppInfo().getAppId())) { // This case needs to come before any that would launch the learn app From 2f9cbb5f924e829a3a3c521405f3d12b7a2ca8c1 Mon Sep 17 00:00:00 2001 From: OrangeAndGreen Date: Fri, 28 Aug 2026 11:08:17 -0400 Subject: [PATCH 2/4] Add release notes for CI-911 Co-Authored-By: Claude Opus 5 (1M context) --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index e1d04ed5ae..271cd0bf0c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,7 @@ This file is meant as an easy way for us to collate notes and change logs across #### Important Bug Fixes - Fixed an issue where recovering a PersonalID account via backup code could result in the account being stored without a pin, causing authentication to fail after recovery. +- Fixed an issue where a worker who passed the learning assessment before completing all learn modules was shown as ready to claim the opportunity, and then hit a failure when trying to claim it. They are now directed back to finish the remaining modules first. ## CommCare 2.63.5 From 1eb3228d017eb356eac9099b2c34aea6c1eb3daa Mon Sep 17 00:00:00 2001 From: OrangeAndGreen Date: Fri, 28 Aug 2026 11:08:33 -0400 Subject: [PATCH 3/4] Add QA notes for CI-911 Co-Authored-By: Claude Opus 5 (1M context) --- RELEASES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 271cd0bf0c..40e0c62abd 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,6 +11,12 @@ This file is meant as an easy way for us to collate notes and change logs across - Fixed an issue where recovering a PersonalID account via backup code could result in the account being stored without a pin, causing authentication to fail after recovery. - Fixed an issue where a worker who passed the learning assessment before completing all learn modules was shown as ready to claim the opportunity, and then hit a failure when trying to claim it. They are now directed back to finish the remaining modules first. +### QA Notes + +- On an opportunity where the assessment can be reached before all learn modules are done, pass the assessment with modules still outstanding and confirm the app keeps directing you to the remaining learning rather than offering to claim the job. +- With all modules completed and the assessment passed, confirm claiming the opportunity and downloading the delivery app still works as before. +- Confirm a worker who passed the assessment with modules still outstanding now sees their module progress on the learning screen rather than a blank progress area. + ## CommCare 2.63.5 ### Release Notes From 5cbf3b13e7a2aab1287fcf5a86caffa89f14cef9 Mon Sep 17 00:00:00 2001 From: OrangeAndGreen Date: Tue, 25 Aug 2026 15:43:17 -0400 Subject: [PATCH 4/4] Lint the PR's own diff, not the release branch's divergence from master Super-linter picks its file set with a two-dot diff against DEFAULT_BRANCH, which was hardcoded to master. For a PR based on a release branch that means the file set is the whole master-to-release-branch divergence rather than the files the PR touches: PR #3881 changes 4 Kotlin files but got 144 linted, including master-only files that don't exist on the branch and are logged as "exists in commit data, but not found on file system, skipping...". The result is that every PR based on commcare_2.64 fails the Kotlin lint job on pre-existing violations it did not introduce and cannot fix in scope. #3880 and #3877 both fail it, with passing Builds. Pointing DEFAULT_BRANCH at github.base_ref makes the diff base the branch the PR actually targets. No change for PRs into master, where base_ref is master. --- .github/workflows/linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index a5670657e3..ea491b7352 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -28,7 +28,7 @@ jobs: env: # Only lint changed files in PR VALIDATE_ALL_CODEBASE: false - DEFAULT_BRANCH: master + DEFAULT_BRANCH: ${{ github.base_ref }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # File handling