Skip to content

fix(feature/client): correct loan repayment navigation flow#2599

Open
Kartikey15dem wants to merge 1 commit intoopenMF:developmentfrom
Kartikey15dem:fix/loan-repayment-navigation
Open

fix(feature/client): correct loan repayment navigation flow#2599
Kartikey15dem wants to merge 1 commit intoopenMF:developmentfrom
Kartikey15dem:fix/loan-repayment-navigation

Conversation

@Kartikey15dem
Copy link
Contributor

@Kartikey15dem Kartikey15dem commented Feb 3, 2026

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
Copy link

coderabbitai bot commented Feb 3, 2026

📝 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 accurately describes the primary change: fixing a broken loan repayment navigation flow initiated from the Client Loan Accounts screen by wiring up the previously empty navigation lambda and implementing flexible navigation that accepts either a full LoanWithAssociationsEntity or a loanId.

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

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

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.

Copy link

Copilot AI left a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

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.

MutableStateFlow<LoanRepaymentUiState>(LoanRepaymentUiState.ShowProgressbar)
val loanRepaymentUiState: StateFlow<LoanRepaymentUiState> get() = _loanRepaymentUiState

init {

Choose a reason for hiding this comment

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

Call checkDatabaseLoanRepaymentByLoanId() in this init block only. (and remove it from the loadLoanById function)

it would look like this

init {
    if (_arg.loanAccountNumber.isEmpty()) {
        loadLoanById()
    }
    checkDatabaseLoanRepaymentByLoanId()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I observed that checkDatabaseLoanRepaymentByLoanId() can be called regardless of the fact whether the loan details are being loaded or we already have from the args as in both we are getting the loanId by args but I have not called the function in viewmodel and kept it like it was before my changes because calling it only in init block may display stale data until the screen is in backstack . Suppose , if we navigate to another screen and navigate back to this screen then launched effect will load the data again but the init block will not do so until the viewmodel is destroyed and created again.

Choose a reason for hiding this comment

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

"If we navigate to another screen and navigate back to this screen then launched effect will load the data again"

Are you sure about this? because on the internet I see that LaunchedEffect runs only two times 1. Entering in the composition, 2. when the keys are updated. Navigating to other screen doesn't remove current screen from composition, current screen stays in the backstack. So navigating back will not add LaunchedEffect again in the composition and hence checkDatabaseLoanRepaymentByLoanId will not be called again I think.
Have you tested that?

Because if that's not happening then we should add a button to load them

Copy link
Contributor Author

@Kartikey15dem Kartikey15dem Feb 8, 2026

Choose a reason for hiding this comment

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

@sahilshivekar
I cross checked by adding an empty notification screen and navigated to that screen and then navigated back and the launched effect code ran again . You can check once for account 053 in maria. Although I am still a bit confused as different LLMs are giving different answers and clarify it from any senior but keeping the launched effect is better in either case as it manually checked it .

Copy link
Contributor Author

@Kartikey15dem Kartikey15dem Feb 8, 2026

Choose a reason for hiding this comment

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

You can check for loan account 053 in maria.

Choose a reason for hiding this comment

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

I am also confused that why LaunchedEffect is again triggering if the composition is in the backstack.
Need to ask about it to a senior. Ask about it in daily standup tmrw if you are also unsure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sahilshivekar
But still I think it can be approved because using launchedeffect won't cause any harm, instead it will better.

fix(feature/client): correct loan repayment navigation flow

fix(feature/client): correct loan repayment navigation flow

fix(feature/client): correct loan repayment navigation flow
@Kartikey15dem Kartikey15dem force-pushed the fix/loan-repayment-navigation branch from d7e9e18 to 8138006 Compare February 8, 2026 10:18
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 8, 2026

Copy link

@coderabbitai coderabbitai bot left a comment

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.

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.

2 participants