Skip to content

fix(client): enable loan repayment navigation from client loan accounts screen - #2599

Merged
biplab1 merged 1 commit into
openMF:developmentfrom
Kartikey15dem:fix/loan-repayment-navigation
Mar 17, 2026
Merged

fix(client): enable loan repayment navigation from client loan accounts screen#2599
biplab1 merged 1 commit into
openMF:developmentfrom
Kartikey15dem:fix/loan-repayment-navigation

Conversation

@Kartikey15dem

@Kartikey15dem Kartikey15dem commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

Fixes - Jira-#MIFOSAC-640

##Description

This PR fixes the broken navigation flow for loan repayments from the Client Loan Accounts screen and introduces a more flexible navigation mechanism to handle both full entity objects and individual loan IDs.

Problem

  1. Empty Navigation Lambda: The lambda responsible for navigating to the loan repayment screen from ClientLoanAccountsScreen was empty, making the "Make Repayment" action non-functional.
  2. Type Mismatch: The existing navigation route required a LoanWithAssociationsEntity. While this works for the LoanAccountSummaryScreen, only the loanId (Int) is readily available in the context of the client loan list.
  3. Dependency Constraints: The LoanWithAssociationsEntity is deeply integrated into the current summary screen logic and could not be removed or replaced without breaking existing functionality.

Behavior Changes

Before After
repaymentBef.webm
repaymentAft.webm

Solution & Implementation

To resolve this without breaking existing features, I implemented a dual-support navigation strategy:

1. Navigation Overloading

I overloaded the navigateToLoanRepaymentScreen function. It now supports:

  • The original navigation using the full LoanWithAssociationsEntity.
  • A new navigation path that only requires a loanId: Int.

2. Route & ViewModel Enhancements

  • Optional Route Parameters: Updated LoanRepaymentScreenRoute to make fields like loanAccountNumber and clientName optional.
  • Data Fetching Logic: Modified LoanRepaymentViewModel to handle cases where only a loanId is provided. If the loan details are missing on initialization, the ViewModel now uses the LoanAccountSummaryRepository to fetch the complete loan data by ID.
  • Action Update: Updated ClientLoanAccountsScreen to correctly pass the loanId to the action handler.

How I Managed the Constraints

Instead of refactoring the entire navigation system—which would have affected the LoanAccountSummaryScreen—I chose to overload the navigation function. This allows the app to "lazy-load" the necessary loan details in the LoanRepaymentViewModel if they weren't provided during navigation. This ensures a seamless user experience regardless of which screen the repayment flow is started from.

Changes

  • ClientNavigation.kt: Connected the navigation lambda to the repayment screen.
  • LoanRepaymentScreenRoute.kt: Added loanId overload and default parameter values.
  • LoanRepaymentViewModel.kt: Added logic to fetch loan details by ID if parameters are missing.
  • ClientLoanAccountsScreen.kt / ViewModel.kt: Updated actions to pass loanId.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Corrected loan repayment process to properly identify the selected loan when initiating a repayment
  • Improvements

    • Loan account details now automatically load when accessing the repayment screen, reducing manual data entry requirements
    • Simplified and optimized navigation flow for initiating loan repayments

Copilot AI review requested due to automatic review settings February 3, 2026 17:12
@coderabbitai

coderabbitai Bot commented Feb 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

MakeRepayment actions now carry a loan ID and navigation is wired to pass that ID to LoanRepaymentScreen. LoanRepaymentViewModel gained a LoanAccountSummaryRepository, exposes route args via StateFlow, and auto-loads loan details when only loanId is provided.

Changes

Cohort / File(s) Summary
Client Loan Accounts (actions & UI)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsViewModel.kt
Changed MakeRepayment from parameterless to carry an Int (loanId). Screen dispatches MakeRepayment(loanId); ViewModel consumes action.loanId when emitting events.
Client Navigation
feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt
Wired repay action to navigateToLoanRepaymentScreen(...) so navigation sends loanId to loan repayment route.
Loan Repayment Screen & Route
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreen.kt, feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreenRoute.kt
Made loanAccountNumber and loanProductName optional with defaults; added NavController.navigateToLoanRepaymentScreen(loanId: Int) overload. Screen reads a local arg state and uses it to populate UI fields.
Loan Repayment ViewModel
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt
Added LoanAccountSummaryRepository dependency; introduced private _arg: MutableStateFlow and public arg: StateFlow; added loadLoanById() and auto-load in init when account number is empty; updated flows to use _arg.value.loanId for repayment logic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ClientScreen as Client Loan Accounts Screen
    participant ClientVM as Client Loan Accounts ViewModel
    participant NavController as Navigation Controller
    participant LoanScreen as Loan Repayment Screen
    participant LoanVM as Loan Repayment ViewModel
    participant SummaryRepo as Loan Summary Repository

    User->>ClientScreen: Tap "Make Repayment" (loan)
    ClientScreen->>ClientVM: Dispatch MakeRepayment(loanId)
    ClientVM->>NavController: navigateToLoanRepaymentScreen(loanId)
    NavController->>LoanScreen: Navigate with loanId
    LoanScreen->>LoanVM: Initialize with loanId
    alt loanAccountNumber is empty
        LoanVM->>SummaryRepo: getLoanById(loanId)
        SummaryRepo-->>LoanVM: LoanWithAssociationsEntity
        LoanVM->>LoanVM: update arg StateFlow with loan details
        LoanVM->>LoanVM: checkDatabaseLoanRepaymentByLoanId()
    end
    LoanVM-->>LoanScreen: emit updated state
    LoanScreen-->>User: show repayment UI (populated)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • biplab1
  • TheKalpeshPawar
  • revanthkumarJ

Poem

🐰 I hop with IDs in paw so spry,
Actions pass their numbers by,
StateFlows hum and screens align,
Repayment paths now thread the line,
Hooray — a rabbit's code-born sigh!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: enabling loan repayment navigation from the client loan accounts screen, which directly aligns with the primary objective of fixing the broken navigation flow.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

Pull request overview

Fixes the “Make Repayment” action from the Client Loan Accounts list by wiring navigation correctly and supporting repayment-screen navigation with either a full loan entity or just a loan ID (with lazy-loading in the repayment ViewModel).

Changes:

  • Wire ClientLoanAccountsScreen “Make Repayment” to actually navigate to the repayment screen.
  • Add an Int-based navigateToLoanRepaymentScreen(loanId) overload and make repayment route fields optional.
  • Update LoanRepaymentViewModel/UI to handle loanId-only navigation by fetching full loan details when needed.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt Adds lazy-loading of loan details via LoanAccountSummaryRepository when only loanId is provided.
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreenRoute.kt Makes route params optional and adds navigateToLoanRepaymentScreen(loanId: Int) overload.
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreen.kt Switches to observing route args via StateFlow and gates initial DB-check call.
feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt Connects the previously-empty repayment navigation lambda.
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsViewModel.kt Changes MakeRepayment action to carry a loanId and emits an event with that ID.
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt Passes loanId into the MakeRepayment action from the loan list item.
Comments suppressed due to low confidence (1)

feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreen.kt:116

  • onRetry always calls checkDatabaseLoanRepaymentByLoanId(). When this screen is reached via the new loanId-only navigation, the initial failure is likely in loadLoanById(), and retrying only the DB check won’t reload the loan details. Consider making retry branch based on whether arg.loanAccountNumber is empty (call loadLoanById() first) or centralize retry logic in the ViewModel so it repeats the last failing step.
        uiState = uiState,
        navigateBack = navigateBack,
        onRetry = { viewmodel.checkDatabaseLoanRepaymentByLoanId() },
        submitPayment = {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt (1)

172-179: ⚠️ Potential issue | 🟡 Minor

Avoid navigating with a synthetic loanId of 0.

Lines 174 and 178 fall back to 0 when loan.id is null. Since LoanAccountEntity.id is nullable (Int? = null), this synthetic ID can route to an invalid loan. Prefer guarding the action (or disabling the menu item) when loan.id is missing. This applies to both ViewAccount and MakeRepayment actions.

🛡️ Suggested change
-                                            is Actions.MakeRepayment -> onAction(
-                                                ClientLoanAccountsAction.MakeRepayment(loan.id ?: 0),
-                                            )
+                                            is Actions.MakeRepayment -> {
+                                                loan.id?.let { loanId ->
+                                                    onAction(
+                                                        ClientLoanAccountsAction.MakeRepayment(loanId),
+                                                    )
+                                                }
+                                            }
🤖 Fix all issues with AI agents
In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreen.kt`:
- Around line 99-115: The retry currently always calls
viewmodel.checkDatabaseLoanRepaymentByLoanId(), which does not repopulate
missing args like clientName or loanAccountNumber when arg.loanAccountNumber is
empty; update the onRetry logic in LoanRepaymentScreen usage to inspect
arg.loanAccountNumber (or arg.loanId) and call
viewmodel.loadLoanById(arg.loanId) when loanAccountNumber is blank, otherwise
call viewmodel.checkDatabaseLoanRepaymentByLoanId(), so retry will reload full
loan details and repopulate the UI.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt`:
- Around line 58-66: In the DataState.Success branch inside
LoanRepaymentViewModel, don't fall back to a new LoanWithAssociationsEntity()
when dataState.data is null; instead detect null and treat it as an error/early
return so you don't overwrite _arg.value with empty/zeroed fields. Update the
handler around the DataState.Success case (the block that currently declares
loanWithAssociations) to check if dataState.data == null and, if so, log or emit
an error and skip the DB check/assignment to _arg.value; only copy values into
_arg.value when loanWithAssociations is non-null.

@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch from d7e9e18 to 8138006 Compare February 8, 2026 10:18

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt`:
- Around line 46-79: loadLoanById() updates _arg on DataState.Success but never
advances the UI or triggers the next step, leaving the screen stuck on
ShowProgressbar; modify LoanRepaymentViewModel.loadLoanById() so that in the
DataState.Success branch after updating _arg.value you either set
_loanRepaymentUiState to the appropriate next state (e.g., a state that signals
navigation/check) or directly call the existing
checkDatabaseLoanRepaymentByLoanId() method to continue the repayment flow;
ensure this change uses the same loanAccountNumber/loanId from
loanWithAssociations and remove or gate the LaunchedEffect navigation call in
the Screen to avoid the race.
🧹 Nitpick comments (2)
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreenRoute.kt (1)

18-25: Consider using val instead of var for route data class fields.

All fields in LoanRepaymentScreenRoute are declared as var, but route arguments should be immutable after construction. The ViewModel already wraps this in a MutableStateFlow for mutability. Using val would better express intent and prevent accidental mutation.

feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt (1)

39-40: Exposing route object as mutable state works but is unconventional.

Using LoanRepaymentScreenRoute (a navigation route data class) as the ViewModel's state object conflates navigation concerns with presentation state. This works for now, but consider extracting a dedicated state class if this grows more complex.

@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch 3 times, most recently from 0c42b5b to bb8ae99 Compare March 3, 2026 07:31

@sahilshivekar sahilshivekar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM👍

@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch from bb8ae99 to 9f56889 Compare March 5, 2026 04:03
@Kartikey15dem
Kartikey15dem requested a review from a team March 5, 2026 04:03
@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch 2 times, most recently from de371f6 to 501ac72 Compare March 9, 2026 13:32
@biplab1

biplab1 commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai Improve the PR title following this: https://github.com/openMF/mobile-wallet/wiki/Commit-style-guide. Just give the title.

@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@biplab1: Based on the Commit Style Guide (type(scope): summary in present tense, lower case), here is the improved PR title:

fix(client): enable loan repayment navigation from client loan accounts screen

@biplab1 biplab1 changed the title fix(feature/client): correct loan repayment navigation flow fix(client): enable loan repayment navigation from client loan accounts screen Mar 9, 2026
@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch 4 times, most recently from 5251358 to 43f22f5 Compare March 11, 2026 13:02
@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch 4 times, most recently from c7f1ee1 to bd98618 Compare March 12, 2026 18:01
@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch 2 times, most recently from ee645e6 to 73513a2 Compare March 17, 2026 10:22
feat(feature/loan): enable loan repayment navigation flow

feat(feature/loan): enable loan repayment navigation flow

feat(feature/loan): enable loan repayment navigation flow

feat(feature/loan): enable loan repayment navigation flow
@Kartikey15dem
Kartikey15dem force-pushed the fix/loan-repayment-navigation branch from 73513a2 to 2f47937 Compare March 17, 2026 11:47
@sonarqubecloud

Copy link
Copy Markdown

@biplab1 biplab1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am merging this for now.

val loanWithAssociations = dataState.data
if (loanWithAssociations == null) {
_loanRepaymentUiState.value = LoanRepaymentUiState.ShowError(
Res.string.feature_loan_profile_error_details_not_found,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The error is still vague. This needs to be handled in a separate PR and also this module needs to be migrated from MVVM to MVI.

@biplab1
biplab1 merged commit 120053a into openMF:development Mar 17, 2026
8 of 10 checks passed
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.

4 participants