Skip to content

fix(history): restore transfer detail fetch using self-service API#2040

Open
bhuvan-somisetty wants to merge 3 commits into
openMF:devfrom
bhuvan-somisetty:fix/restore-transfer-detail-fetch
Open

fix(history): restore transfer detail fetch using self-service API#2040
bhuvan-somisetty wants to merge 3 commits into
openMF:devfrom
bhuvan-somisetty:fix/restore-transfer-detail-fetch

Conversation

@bhuvan-somisetty

@bhuvan-somisetty bhuvan-somisetty commented May 17, 2026

Copy link
Copy Markdown

Fixes #2039

What broke and why

The SpecificTransactionsScreen has had a silent regression since the self-service API migration. When a user taps a transfer transaction in the history list, the screen opens but the Transfer Detail card never appears — sender, receiver, reference number, and transfer metadata are all missing.

The root cause was a commented-out block in SpecificTransactionsViewModel.handleTransferDetailReceive() left behind during migration with a note that the self-service endpoint was unavailable:

// TODO: below api not there for Self So Commented it
//        transaction.transferId?.let { transferId ->
//            accountRepository.getAccountTransfer(transferId) ...

The endpoint is available. AccountRepositoryImpl.getAccountTransfer() already routes through selfManager.accountTransfersApi — the repository layer was updated during migration but the ViewModel was never revisited. The result: detail was always null, the UI silently skipped the card, and no crash surfaced to signal the breakage.


Feature Module Architecture

graph TD
    subgraph feature_history["feature:history module"]
        TL["TransactionList\n(Composable)"]
        STS["SpecificTransactionsScreen\n(Composable)"]
        STVM["SpecificTransactionsViewModel"]
        TDC["TransactionDetail\nCard (Composable)"]
    end

    subgraph core_data["core:data module"]
        AR["AccountRepository\n(interface)"]
        ARI["AccountRepositoryImpl"]
    end

    subgraph network["Network Layer"]
        SM["SelfServiceApiManager"]
        ATA["AccountTransfersApi\n(self-service ✓)"]
    end

    TL -->|"user taps transaction"| STS
    STS -->|"observes state"| STVM
    STVM -->|"getTransaction()"| AR
    STVM -->|"getAccountTransfer()"| AR
    AR -->|"implemented by"| ARI
    ARI -->|"selfManager.accountTransfersApi"| SM
    SM --> ATA
    STVM -->|"ViewState.Content(tx, detail)"| STS
    STS -->|"detail != null"| TDC
Loading

Data Flow — Before the Fix

flowchart TD
    A([User taps transaction]) --> B[getTransaction API call]
    B --> C{DataState}
    C -->|Success| D[handleTransferDetailReceive]
    C -->|Error| E[ViewState.Error]
    C -->|Loading| F[ViewState.Loading]

    D --> G["⚠️ Content(transaction, detail = null)\nalways — transferId never checked"]
    G --> H["SpecificTransactionsScreen renders"]
    H --> I["❌ TransactionDetail card missing\nTransfer metadata never shown"]

    style G fill:#ff4444,color:#fff
    style I fill:#ff4444,color:#fff
Loading

Data Flow — After the Fix

flowchart TD
    A([User taps transaction]) --> B[getTransaction API call]
    B --> C{DataState}
    C -->|Success| D[handleTransferDetailReceive]
    C -->|Error| E[ViewState.Error]
    C -->|Loading| F[ViewState.Loading]

    D --> G{transaction.transferId?}

    G -->|"null — not a transfer"| H["Content(transaction, detail = null)\nnon-transfer path — correct ✓"]
    G -->|"non-null — is a transfer"| I["accountRepository.getAccountTransfer(transferId)\nselfManager.accountTransfersApi ✓"]

    I --> J{DataState}
    J -->|Loading| K[ViewState.Loading]
    J -->|Error| L[ViewState.Error]
    J -->|Success| M["✅ Content(transaction, transferDetail)\nfull transfer metadata available"]

    M --> N["SpecificTransactionsScreen renders"]
    N --> O["✅ TransactionDetail card visible\nSender · Receiver · Reference · Amount"]

    style M fill:#00aa44,color:#fff
    style O fill:#00aa44,color:#fff
Loading

ViewModel State Machine

stateDiagram-v2
    [*] --> Loading : screen opens

    Loading --> Loading : getAccountTransfer\n(if transferId present)
    Loading --> Content_with_detail : DataState.Success\n(transfer transaction)
    Loading --> Content_no_detail : no transferId\n(non-transfer transaction)
    Loading --> Error : DataState.Error

    Content_with_detail --> [*] : TransactionDetail card shown ✅
    Content_no_detail --> [*] : screen shown without detail card ✅
    Error --> [*] : error state shown

    note right of Content_with_detail
        detail = TransferDetail
        sender, receiver,
        reference, amount
        all populated
    end note

    note right of Content_no_detail
        detail = null
        non-transfer transactions
        unaffected — correct behavior
    end note
Loading

Repository Layer — Self-Service Routing

graph LR
    subgraph ViewModel
        STVM["SpecificTransactionsViewModel\ngetAccountTransfer(transferId)"]
    end

    subgraph Repository
        AR["AccountRepository\ninterface"]
        ARI["AccountRepositoryImpl"]
    end

    subgraph Network
        SM["SelfServiceApiManager"]
        API["accountTransfersApi\n/self-service/v1/accounttransfers"]
        RESP["TransferDetail response"]
    end

    STVM -->|calls| AR
    AR -->|delegates to| ARI
    ARI -->|"selfManager.accountTransfersApi\n✓ self-service endpoint"| SM
    SM --> API
    API --> RESP
    RESP -->|"DataState.Success(TransferDetail)"| STVM
Loading

Repository layer — confirming self-service availability

AccountRepositoryImpl.getAccountTransfer() was already migrated:

override fun getAccountTransfer(transferId: Long): Flow<DataState<TransferDetail>> {
    return selfManager.accountTransfersApi          // ← self-service ✓
        .getAccountTransfer(transferId.toInt())
        .asDataStateFlow().flowOn(ioDispatcher)
}

The API was always there. The ViewModel just never called it.


The fix

private fun handleTransferDetailReceive(transaction: Transaction) {
    transaction.transferId?.let { transferId ->
        accountRepository.getAccountTransfer(transferId)
            .onEach { result: DataState<TransferDetail> ->
                when (result) {
                    is DataState.Error -> mutableStateFlow.update {
                        it.copy(viewState = Error(result.exception.message.toString()))
                    }
                    is DataState.Loading -> mutableStateFlow.update {
                        it.copy(viewState = STState.ViewState.Loading)
                    }
                    is DataState.Success -> mutableStateFlow.update {
                        it.copy(viewState = Content(transaction, result.data))
                    }
                }
            }.launchIn(viewModelScope)
    } ?: run {
        mutableStateFlow.update {
            it.copy(viewState = Content(transaction, null))
        }
    }
}

Files changed

File Change
feature/history/src/commonMain/kotlin/org/mifospay/feature/history/transactions/SpecificTransactionsViewModel.kt Restored getAccountTransfer flow, removed stale TODO comment

Testing

  • Transfer transactions (CREDIT/DEBIT with a transferId): TransactionDetail card renders with sender name, receiver name, reference number, and transfer amount
  • Non-transfer transactions (no transferId): screen loads cleanly, no detail card, no errors
  • Error path verified: network failure surfaces ViewState.Error correctly
  • No regressions on the history list screen or other screens in the feature module

Screen recording was captured but could not be uploaded due to upload size constraints. Will attach in a follow-up comment.

Summary by CodeRabbit

  • Bug Fixes
    • Improved error messages when transaction details fail to load, showing a clearer fallback message instead of an unclear one.
    • Transaction history now loads transfer details when available, so related information can appear alongside the selected transaction.
    • When transfer details can’t be loaded, the transaction view still displays the main transaction information instead of leaving the screen incomplete.

Signed-off-by: bhuvan-somisetty <somisettybhuvan5@gmail.com>
@bhuvan-somisetty
bhuvan-somisetty requested a review from a team May 17, 2026 01:09
@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: de054c11-24d8-4aa3-9be5-3c6d6990a0c4

📥 Commits

Reviewing files that changed from the base of the PR and between ba72017 and 97dd4de.

📒 Files selected for processing (1)
  • feature/history/src/commonMain/kotlin/org/mifospay/feature/history/transactions/SpecificTransactionsViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/history/src/commonMain/kotlin/org/mifospay/feature/history/transactions/SpecificTransactionsViewModel.kt

📝 Walkthrough

Walkthrough

The PR updates transaction history detail state handling. It improves error message formatting and restores transfer-detail loading when a transaction has a transfer ID.

Changes

Transaction detail state updates

Layer / File(s) Summary
Transaction error message formatting
feature/history/src/commonMain/kotlin/org/mifospay/feature/history/transactions/SpecificTransactionsViewModel.kt
handleTransactionReceive now uses exception.message with a fallback string instead of exception.message.toString().
Transfer detail fetch flow
feature/history/src/commonMain/kotlin/org/mifospay/feature/history/transactions/SpecificTransactionsViewModel.kt
handleTransferDetailReceive now conditionally loads TransferDetail from accountRepository.getAccountTransfer(transferId), updates Content(transaction, detail) on Success, and keeps detail = null for Loading, Error, or missing transfer IDs.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • openMF/mifos-pay#2047: Also touches the history transaction detail flow and transfer-related data passed into the screen.

Suggested reviewers: biplab1

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: restoring transfer detail fetching in the history screen.
Linked Issues check ✅ Passed The change restores getAccountTransfer flow for transfer transactions and keeps null detail for non-transfer cases, matching #2039.
Out of Scope Changes check ✅ Passed The only additional changes are related error-message handling in the same ViewModel and remain in scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/history/src/commonMain/kotlin/org/mifospay/feature/history/transactions/SpecificTransactionsViewModel.kt`:
- Line 88: In SpecificTransactionsViewModel where you set viewState to Error
using result.exception.message (the line it.copy(viewState =
Error(result.exception.message.toString()))), avoid producing the literal "null"
by guarding the exception message: obtain a non-null string via a fallback
(e.g., use the exception message if non-null, else use exception.toString() or a
generic localized fallback like "Unknown error occurred") and pass that into
Error; update the it.copy call to construct the Error with this computed
safeMessage so users see a useful message when exception.message is null.
- Around line 86-90: The DataState.Error branch in SpecificTransactionsViewModel
currently replaces the entire viewState with
Error(result.exception.message.toString()), which hides the already-loaded
transaction; instead, update the existing mutableStateFlow to preserve the
current transaction and other viewState fields while clearing or nulling only
the transfer detail (e.g., set transferDetails = null or keep existing
viewState.transferDetails = null) and optionally record the error in a separate
field (e.g., transferDetailError) so UI can show the transaction without
transfer details; locate the DataState.Error handling in the method that
processes transfer detail results and modify the mutableStateFlow.update call to
copy the existing state rather than replacing it with Error(...).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56e7580f-65d7-4fab-b0d1-b06b20d4ac7c

📥 Commits

Reviewing files that changed from the base of the PR and between e8303e2 and ba72017.

📒 Files selected for processing (1)
  • feature/history/src/commonMain/kotlin/org/mifospay/feature/history/transactions/SpecificTransactionsViewModel.kt

@Nagarjuna0033

Copy link
Copy Markdown
Collaborator

@bhuvan-somisetty create a jira ticket for this and update pr

Preserve the loaded transaction when the transfer detail fetch errors
instead of replacing the whole screen with a generic error, and avoid
surfacing the literal string "null" when the exception has no message.
@bhuvan-somisetty

Copy link
Copy Markdown
Author

Pushed a follow-up commit addressing the CodeRabbit review feedback: the transaction now stays visible on the screen if the transfer detail fetch errors (only the detail card is dropped), and the error message no longer shows the literal string "null" when the exception has no message.

Will create the Jira ticket and link it here shortly.

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.

[Bug] Transaction transfer detail always empty on history detail screen

2 participants