Skip to content

Conversation

@Arinyadav1
Copy link
Contributor

@Arinyadav1 Arinyadav1 commented Dec 15, 2025

Fixes - Jira-#562

Screenshot 2025-12-15 230048 Screenshot 2025-12-15 230100 Screenshot 2025-12-15 230108

Summary by CodeRabbit

  • New Features

    • Added fixed deposit account creation capability with multi-step workflow
    • Introduced Preview step for fixed deposit account setup to review details before submission
    • Added investing account and linked savings account selection during fixed deposit creation
    • Enhanced charge management interface with improved dialogs
    • Success confirmation message upon account creation
  • Bug Fixes

    • Corrected "Penal Interest" label formatting in fixed deposit settings
  • Improvements

    • Added currency symbol display for monetary values in recurring deposit accounts

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Walkthrough

This PR extends fixed deposit account creation functionality across network, data, and feature layers. It introduces a new FixedDepositPayload data class, adds a createFixedDepositAccount() API endpoint and repository method, refactors the fixed deposit UI with a preview step and state-driven charge dialogs, and reorganizes model classes into a dedicated fixedDeposit package. It also updates share account repository APIs and migrates shared charge item types.

Changes

Cohort / File(s) Summary
Network Model Reorganization
core/network/src/commonMain/kotlin/com/mifos/core/network/model/fixedDeposit/*
Moved FixedDepositTemplate and FixedDepositProductOption to new fixedDeposit package; added savingsAccounts property to FixedDepositTemplate; created new FixedDepositPayload data class with comprehensive fixed deposit account fields.
Network Service & Data Manager
core/network/src/commonMain/kotlin/com/mifos/core/network/services/FixedDepositService.kt, core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerFixedDeposit.kt
Added createFixedDepositAccount() API endpoint to service; implemented corresponding data manager method with error handling via extractErrorMessage and isSuccess validation.
Data Repository Layer
core/data/src/commonMain/kotlin/com/mifos/core/data/repository/FixedDepositRepository.kt, core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/FixedDepositRepositoryImpl.kt
Added createFixedDepositAccount() method to both repository interface and implementation; updated import paths for moved model classes.
Share Account Repository
core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ShareAccountRepository.kt, core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ShareAccountRepositoryImpl.kt
Removed suspend modifier from createShareAccount() method signature while preserving Flow<DataState<GenericResponse>> return type.
Share Account Charge Migration
core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareAccountPayload.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt
Migrated local ChargeItem definition to external import from com.mifos.core.model.objects.payloads.ChargeItem.
Fixed Deposit UI Core
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountScreen.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountRoute.kt
Added SnackbarHostState support; introduced PreviewPage step; refactored dialog handling for charges and success states; removed onFinish callback parameter.
Fixed Deposit Viewmodel
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt
Expanded state management with investing and linked saving accounts; introduced OnSettingNext, OnSubmitFixedAccount actions; replaced isOverlayLoading with isOverlayLoadingActive; added createFixedDepositAccount() submission logic; refactored charge handling to use state-driven dialogs.
Fixed Deposit Pages
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/PreviewPage.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/ChargesPage.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/SettingsPage.kt
Added new PreviewPage with details, terms, settings, and interest rate cards; refactored ChargesPage dialogs to state-driven approach with AddNewChargeDialog and revised ShowChargesDialog; enhanced SettingsPage with investing account and linked saving account dropdowns.
String Resources & Other
feature/client/src/commonMain/composeResources/values/strings.xml, feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/PreviewPage.kt
Added string resources for preview step, linked saving account field, and success message; fixed penal interest format; added currency symbol prefix to recurring deposit preview display values.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Fixed Deposit Screen
    participant VM as CreateFixedDepositAccountViewmodel
    participant Repo as FixedDepositRepository
    participant DM as DataManagerFixedDeposit
    participant Service as FixedDepositService
    participant API as Mifos API

    UI->>VM: OnSubmitFixedAccount()
    VM->>VM: createFixedDepositAccount()
    VM->>Repo: createFixedDepositAccount(payload)
    Repo->>DM: createFixedDepositAccount(payload)
    DM->>Service: createFixedDepositAccount(payload)
    Service->>API: POST /fixeddeposits (payload)
    
    alt Success
        API-->>Service: HttpResponse(success)
        Service-->>DM: Flow<HttpResponse>
        DM->>DM: map & validate isSuccess
        DM-->>Repo: Flow<Unit>
        Repo->>Repo: asDataStateFlow()
        Repo-->>VM: Flow<DataState<Unit>>
        VM->>VM: emit loading, then Success
        VM->>UI: dialogState = SuccessResponseStatus
        UI->>UI: Show snackbar
        UI->>UI: Navigate back after delay
    else Error
        API-->>Service: HttpResponse(error)
        Service-->>DM: Flow<HttpResponse>
        DM->>DM: extractErrorMessage, throw IllegalStateException
        DM-->>Repo: Exception
        Repo->>Repo: asDataStateFlow() catches
        Repo-->>VM: Flow<DataState.Error>
        VM->>UI: dialogState = updated error state
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Fixed Deposit Viewmodel (CreateFixedDepositAccountViewmodel.kt) — Extensive state management refactoring with new investing/linked saving account flows, charge payload handling, and submission logic; requires verification of state transitions, error handling paths, and action dispatch correctness.
  • Fixed Deposit Screens & Pages (CreateFixedDepositAccountScreen.kt, ChargesPage.kt, PreviewPage.kt) — Multiple UI layer changes including new PreviewPage composition, refactored charge dialogs transitioning from parameter-driven to state-driven approach, and snackbar integration; verify correct state consumption and action wiring.
  • Data Manager Error Handling (DataManagerFixedDeposit.kt) — New API method using extractErrorMessage and isSuccess for validation; confirm error message extraction and exception throwing align with upstream error-handling patterns.
  • Import Path ReorganizationFixedDepositTemplate and FixedDepositPayload moved to new package; verify all import statements updated consistently across network, data, and feature layers.
  • ShareAccountRepository Signature Changesuspend modifier removal from createShareAccount(); verify this change is compatible with all callers and doesn't break concurrency expectations.

Possibly related PRs

  • #2549: Modifies the createShareAccount() API signature and implementation in ShareAccountRepository and its impl — same method being updated here for the suspend modifier.
  • #2555: Reorganizes FixedDepositTemplate package path and related imports across network and data layers — overlaps with the package restructuring in this PR.
  • #2550: Modifies CreateFixedDepositAccountViewmodel.kt state management and overlay loading flags — related to the extensive viewmodel refactoring in this PR.

Suggested reviewers

  • revanthkumarJ

🐰 A preview step hop with savings linked tight,
Charges dance through dialogs—state done right!
Fixed deposits created, the snackbar shows delight,
New investing accounts bloom in autumn light!

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'New fixed deposits account preview step' accurately describes the main change - adding a preview step UI to the fixed deposit account creation flow.
✨ 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

@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: 9

Caution

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

⚠️ Outside diff range comments (1)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/PreviewPage.kt (1)

293-300: Duplicate map key will cause data loss.

Res.string.feature_recurring_deposit_period is used twice in this map (lines 293 and 300). In Kotlin maps, duplicate keys cause the second value to overwrite the first, so the pre-closure penal interest period type will never be displayed.

Consider using a distinct string resource for the minimum balance field:

-            Res.string.feature_recurring_deposit_period to state.template.currency?.displaySymbol.orEmpty() + " " + state.recurringDepositAccountSettings.preMatureClosure.minimumBalanceForInterestCalculation,
+            Res.string.feature_recurring_deposit_minimum_balance_for_interest to state.template.currency?.displaySymbol.orEmpty() + " " + state.recurringDepositAccountSettings.preMatureClosure.minimumBalanceForInterestCalculation,

You'll need to add the corresponding string resource.

🧹 Nitpick comments (2)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/PreviewPage.kt (1)

305-310: Logic appears inverted for rate chart display.

The naming isRateChartEmpty is confusing because when chartSlabs is NOT null/empty, isRateChartEmpty becomes true. This causes:

  • Text shows "Interest Rate Chart" when chart exists (correct text, but variable name is misleading)
  • Button enabled when chart exists

The variable is actually checking if the chart is not empty. Consider renaming to hasRateChart or isRateChartAvailable for clarity.

feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt (1)

921-927: Inconsistent parameter naming in action classes.

The parameter naming is confusing:

  • OnEditCharge(chargeIndex) - chargeIndex is actually the position in addedCharges list
  • EditChargeDialog(index) - correctly uses index
  • DeleteChargeFromSelectedCharges(index) - correctly uses index

Consider renaming for consistency:

-        data class OnEditCharge(val chargeIndex: Int) : NewFixedDepositAccountAction()
+        data class OnEditCharge(val index: Int) : NewFixedDepositAccountAction()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66dce5a and 202b444.

📒 Files selected for processing (19)
  • core/data/src/commonMain/kotlin/com/mifos/core/data/repository/FixedDepositRepository.kt (1 hunks)
  • core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ShareAccountRepository.kt (1 hunks)
  • core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/FixedDepositRepositoryImpl.kt (2 hunks)
  • core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ShareAccountRepositoryImpl.kt (1 hunks)
  • core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerFixedDeposit.kt (1 hunks)
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/fixedDeposit/FixedDepositPayload.kt (1 hunks)
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/fixedDeposit/FixedDepositProductOption.kt (1 hunks)
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/fixedDeposit/FixedDepositTemplate.kt (3 hunks)
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareAccountPayload.kt (1 hunks)
  • core/network/src/commonMain/kotlin/com/mifos/core/network/services/FixedDepositService.kt (2 hunks)
  • feature/client/src/commonMain/composeResources/values/strings.xml (3 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt (1 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountRoute.kt (0 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountScreen.kt (6 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt (15 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/ChargesPage.kt (6 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/PreviewPage.kt (1 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/SettingsPage.kt (4 hunks)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/PreviewPage.kt (2 hunks)
💤 Files with no reviewable changes (1)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountRoute.kt
🧰 Additional context used
🧬 Code graph analysis (4)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/SettingsPage.kt (1)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosTextFieldDropdown.kt (1)
  • MifosTextFieldDropdown (39-112)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/PreviewPage.kt (4)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/PreviewPage.kt (3)
  • DetailsCard (347-370)
  • TermsCard (306-345)
  • SettingCard (195-304)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosRowWithTextAndButton.kt (1)
  • MifosRowWithTextAndButton (35-95)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosTwoButtonRow.kt (1)
  • MifosTwoButtonRow (31-91)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosListingComponent.kt (1)
  • MifosDefaultListingComponentFromStringResources (244-267)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/ChargesPage.kt (3)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/AddChargeDialog.kt (1)
  • AddChargeBottomSheet (47-171)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosBottomSheet.kt (1)
  • MifosBottomSheet (47-89)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosActionsListingCardComponent.kt (1)
  • MifosActionsChargeListingComponent (601-735)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt (4)
core/data/src/commonMain/kotlin/com/mifos/core/data/repository/FixedDepositRepository.kt (1)
  • createFixedDepositAccount (23-25)
core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/FixedDepositRepositoryImpl.kt (1)
  • createFixedDepositAccount (31-33)
core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerFixedDeposit.kt (1)
  • createFixedDepositAccount (25-32)
feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt (1)
  • isOnline (58-70)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: PR Checks / Static Analysis Check
🔇 Additional comments (16)
core/network/src/commonMain/kotlin/com/mifos/core/network/model/fixedDeposit/FixedDepositProductOption.kt (1)

10-10: LGTM!

Package reorganization to com.mifos.core.network.model.fixedDeposit is consistent with the broader refactor consolidating fixed deposit models.

core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ShareAccountRepository.kt (1)

21-21: LGTM!

Removing suspend from a function returning Flow is correct. The Flow itself handles asynchronous execution, and suspension occurs when collecting. This also maintains consistency with getShareTemplate.

feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/PreviewPage.kt (1)

183-190: Verify the inverted boolean logic for rate chart display.

The logic appears counterintuitive: when isRateChartEmpty is true, the button is enabled and shows "interest rate chart" text. When it's false, the button is disabled with "no interest chart" text.

Semantically, this seems backwards—you'd expect to disable the "View" button when the chart is empty, not when it has data.

Please verify if isRateChartEmpty should be renamed to hasRateChart or if the boolean conditions should be inverted.

core/network/src/commonMain/kotlin/com/mifos/core/network/model/fixedDeposit/FixedDepositTemplate.kt (1)

10-10: LGTM!

Package reorganization aligns with the fixed deposit model consolidation. The new savingsAccounts field is backward-compatible (nullable with default) and supports the savings account linking feature in the preview step.

Also applies to: 23-23, 65-65

core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ShareAccountRepositoryImpl.kt (1)

30-36: LGTM!

Implementation correctly aligns with the interface change. The flow { emit(...) }.asDataStateFlow() pattern appropriately wraps the data manager call for reactive state handling.

feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt (1)

23-23: LGTM!

Import migration to the shared ChargeItem type is consistent with the payload layer changes.

core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerFixedDeposit.kt (1)

25-32: LGTM!

The error handling pattern using extractErrorMessage and IllegalStateException is consistent with how other data managers handle API errors. The Unit return type is intentional—the ViewModel only needs success/failure confirmation, not response body data, as evidenced by the generic success message shown in the UI state.

core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareAccountPayload.kt (1)

12-12: LGTM!

Migrating ChargeItem to a shared payloads package (com.mifos.core.model.objects.payloads.ChargeItem) promotes reuse across share and fixed deposit features. The shared ChargeItem maintains the expected serialization structure with chargeId and amount fields and is now consistently used across fixed deposit, recurring deposit, and share account modules.

core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/FixedDepositRepositoryImpl.kt (1)

30-33: LGTM!

The new createFixedDepositAccount method follows the established repository pattern, delegating to the data manager and converting the result via asDataStateFlow(). This is consistent with the existing getFixedDepositTemplate implementation.

core/data/src/commonMain/kotlin/com/mifos/core/data/repository/FixedDepositRepository.kt (1)

22-25: LGTM!

The new createFixedDepositAccount method is correctly defined in the repository interface. Using Flow<DataState<Unit>> is appropriate for a create operation where only the success/failure status matters.

core/network/src/commonMain/kotlin/com/mifos/core/network/services/FixedDepositService.kt (1)

29-33: LGTM!

The new createFixedDepositAccount API method is correctly defined with the appropriate @POST annotation and @Body parameter. Returning Flow<HttpResponse> allows the data manager layer to handle response parsing and error extraction flexibly.

feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountScreen.kt (2)

110-121: Verify launchEffectKey management prevents duplicate effects.

The success flow correctly shows a snackbar and navigates back after a delay. However, ensure that state.launchEffectKey is properly updated each time a new success response occurs to prevent the LaunchedEffect from being skipped on subsequent successes with the same key.


165-170: LGTM!

The PreviewPage is correctly integrated as the final step in the fixed deposit account creation flow, following the same pattern as other steps.

core/network/src/commonMain/kotlin/com/mifos/core/network/model/fixedDeposit/FixedDepositPayload.kt (1)

15-51: LGTM!

The FixedDepositPayload data class is well-structured with appropriate nullable fields and sensible defaults. The use of @Serializable annotation and default values for all fields provides good flexibility for partial payloads.

feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/ChargesPage.kt (2)

137-209: LGTM!

The AddNewChargeDialog composable properly implements the state-driven dialog pattern. The empty lambdas for onDatePick and onDateChange are appropriate since fixed deposits don't require date selection for charges.


211-284: LGTM!

The ShowChargesDialog properly uses firstOrNull for safe charge lookup by ID and handles the edit/delete actions appropriately through the state-driven action pattern.

<string name="feature_fixed_deposit_interest_yes">Yes</string>
<string name="feature_fixed_deposit_interest_no">No</string>
<string name="feature_fixed_deposit_interest_empty_date">Date Not Found</string>
<string name="feature_fixed_account_created_successfully">Share account created successfully</string>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect success message text.

The string says "Share account created successfully" but this resource is for fixed deposit account creation (feature_fixed_account_created_successfully). This appears to be a copy-paste error from share account strings.

-    <string name="feature_fixed_account_created_successfully">Share account created successfully</string>
+    <string name="feature_fixed_account_created_successfully">Fixed deposit account created successfully</string>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<string name="feature_fixed_account_created_successfully">Share account created successfully</string>
<string name="feature_fixed_account_created_successfully">Fixed deposit account created successfully</string>
🤖 Prompt for AI Agents
In feature/client/src/commonMain/composeResources/values/strings.xml around line
636, the string resource key feature_fixed_account_created_successfully has the
wrong copy-pasted value "Share account created successfully"; update the value
to the correct fixed deposit account success text (e.g., "Fixed deposit account
created successfully" or the product-approved phrasing) so the resource matches
the key and UI context.

Comment on lines +324 to +343
is NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.OnEditCharge -> {
val createdData = ChargeItem(
chargeId = state.template.chargeOptions?.get(action.chargeIndex)?.id,
amount = state.fixedDepositAccountCharges.chargeAmount.toDoubleOrNull(),
)

val currentAddedCharges =
state.fixedDepositAccountCharges.addedCharges.toMutableList()
currentAddedCharges[action.chargeIndex] = createdData
mutableStateFlow.update {
it.copy(
fixedDepositAccountCharges = it.fixedDepositAccountCharges.copy(
addedCharges = currentAddedCharges,
selectedChargeIndex = null,
chargeAmount = "",
),
dialogState = NewFixedDepositAccountState.DialogState.ShowCharges,
)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: Using wrong index to update charge list.

The OnEditCharge handler receives chargeIndex which represents the position in addedCharges list (passed from the dialog). However, line 326 incorrectly uses this index to look up the charge in template.chargeOptions. The correct behavior should use selectedChargeIndex (set during EditChargeDialog) for the template lookup.

             is NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.OnEditCharge -> {
                 val createdData = ChargeItem(
-                    chargeId = state.template.chargeOptions?.get(action.chargeIndex)?.id,
+                    chargeId = state.template.chargeOptions?.getOrNull(state.fixedDepositAccountCharges.selectedChargeIndex ?: -1)?.id,
                     amount = state.fixedDepositAccountCharges.chargeAmount.toDoubleOrNull(),
                 )

                 val currentAddedCharges =
                     state.fixedDepositAccountCharges.addedCharges.toMutableList()
-                currentAddedCharges[action.chargeIndex] = createdData
+                currentAddedCharges[action.index] = createdData

Note: The action class at line 923 uses chargeIndex but should be renamed to index for consistency with how it's used.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt
around lines 324 to 343, the handler for
NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.OnEditCharge is
using action.chargeIndex to pick the template chargeOption but chargeIndex
refers to the position in addedCharges; change the template lookup to use the
viewmodel state's selectedChargeIndex (or the selectedChargeIndex value saved
when opening the EditChargeDialog) so the correct template id is used when
building createdData, and after that update addedCharges as before; also rename
the action property at ~line 923 from chargeIndex to index for
clarity/consistency with its actual meaning.

Comment on lines +361 to 377
NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.AddChargeToList -> {
val createdData = ChargeItem(
chargeId = state.template.chargeOptions?.get(state.fixedDepositAccountCharges.selectedChargeIndex!!)?.id,
amount = state.fixedDepositAccountCharges.chargeAmount.toDoubleOrNull(),
)

NewFixedDepositAccountAction.OnDismissDialog -> {
mutableStateFlow.update {
it.copy(
fixedDepositAccountCharges = it.fixedDepositAccountCharges.copy(
addedCharges = it.fixedDepositAccountCharges.addedCharges + createdData,
chargeAmount = "",
selectedChargeIndex = null,
),
dialogState = null,
)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential NullPointerException with force unwrap.

The selectedChargeIndex!! force unwrap on line 363 will crash if selectedChargeIndex is null. This could happen if the user somehow triggers the add action without selecting a charge.

-            is NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.AddChargeToList -> {
-                val createdData = ChargeItem(
-                    chargeId = state.template.chargeOptions?.get(state.fixedDepositAccountCharges.selectedChargeIndex!!)?.id,
-                    amount = state.fixedDepositAccountCharges.chargeAmount.toDoubleOrNull(),
-                )
+            is NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.AddChargeToList -> {
+                val selectedIndex = state.fixedDepositAccountCharges.selectedChargeIndex ?: return
+                val createdData = ChargeItem(
+                    chargeId = state.template.chargeOptions?.getOrNull(selectedIndex)?.id,
+                    amount = state.fixedDepositAccountCharges.chargeAmount.toDoubleOrNull(),
+                )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.AddChargeToList -> {
val createdData = ChargeItem(
chargeId = state.template.chargeOptions?.get(state.fixedDepositAccountCharges.selectedChargeIndex!!)?.id,
amount = state.fixedDepositAccountCharges.chargeAmount.toDoubleOrNull(),
)
NewFixedDepositAccountAction.OnDismissDialog -> {
mutableStateFlow.update {
it.copy(
fixedDepositAccountCharges = it.fixedDepositAccountCharges.copy(
addedCharges = it.fixedDepositAccountCharges.addedCharges + createdData,
chargeAmount = "",
selectedChargeIndex = null,
),
dialogState = null,
)
}
}
NewFixedDepositAccountAction.NewFixedDepositAccountChargesAction.AddChargeToList -> {
val selectedIndex = state.fixedDepositAccountCharges.selectedChargeIndex ?: return
val createdData = ChargeItem(
chargeId = state.template.chargeOptions?.getOrNull(selectedIndex)?.id,
amount = state.fixedDepositAccountCharges.chargeAmount.toDoubleOrNull(),
)
mutableStateFlow.update {
it.copy(
fixedDepositAccountCharges = it.fixedDepositAccountCharges.copy(
addedCharges = it.fixedDepositAccountCharges.addedCharges + createdData,
chargeAmount = "",
selectedChargeIndex = null,
),
dialogState = null,
)
}
}
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt
around lines 361 to 377, the code force-unwraps selectedChargeIndex which can be
null and cause an NPE; change to safely handle a null index by using
selectedChargeIndex?.let { idx -> val chargeId =
state.template.chargeOptions?.getOrNull(idx)?.id ... } and only create and
append ChargeItem when idx is non-null (otherwise ignore the action or surface a
validation error), and ensure amount conversion uses toDoubleOrNull() as already
done so null amounts are handled consistently.

Comment on lines +442 to +443
depositAmount = state.fixedDepositAccountTerms.depositAmount.toDouble(),
depositPeriod = state.fixedDepositAccountTerms.depositPeriod.toInt(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential NumberFormatException on empty/invalid input.

toDouble() and toInt() will throw NumberFormatException if the strings are empty or contain non-numeric values. While validation should catch this earlier, defensive coding is safer here.

-            depositAmount = state.fixedDepositAccountTerms.depositAmount.toDouble(),
-            depositPeriod = state.fixedDepositAccountTerms.depositPeriod.toInt(),
+            depositAmount = state.fixedDepositAccountTerms.depositAmount.toDoubleOrNull() ?: 0.0,
+            depositPeriod = state.fixedDepositAccountTerms.depositPeriod.toIntOrNull() ?: 0,

Alternatively, add validation before calling createFixedDepositAccount() to ensure these fields have valid values, and consider making the payload fields non-nullable with proper validation.

🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt
around lines 442-443, the direct calls to toDouble() and toInt() can throw
NumberFormatException for empty or invalid strings; change to safe parsing
(e.g., toDoubleOrNull()/toIntOrNull()) and handle nulls by returning/propagating
a validation error or providing a sensible default before calling
createFixedDepositAccount(), or move validation earlier so these fields are
guaranteed non-null/valid and convert them only after validation so payload
fields can be non-nullable.

Comment on lines 858 to 861
data class ChargeData(
val id: Int,
val name: String,
val type: String,
val collectedOn: String,
val amount: Double,
val id: Int?,
val amount: Double?,
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused ChargeData class.

This class appears to be legacy code that has been replaced by ChargeItem. It's no longer referenced anywhere in the file.

-data class ChargeData(
-    val id: Int?,
-    val amount: Double?,
-)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data class ChargeData(
val id: Int,
val name: String,
val type: String,
val collectedOn: String,
val amount: Double,
val id: Int?,
val amount: Double?,
)
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt
around lines 858-861, remove the unused legacy data class ChargeData (val id:
Int?, val amount: Double?) since ChargeItem replaces it; delete the class
declaration and any now-unreferenced imports or references in this file to keep
code clean and compile without unused symbol warnings.

Comment on lines +177 to +181
Res.string.feature_fixed_interest_compounding_period to if (state.fixedDepositAccountTerms.interestPostingPeriodTypeIndex != -1) {
state.template.interestPostingPeriodTypeOptions?.getOrNull(state.fixedDepositAccountTerms.interestPostingPeriodTypeIndex)?.value.orEmpty()
} else {
""
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: Wrong index used for Interest Compounding Period.

The label feature_fixed_interest_compounding_period is mapped to interestPostingPeriodTypeIndex instead of interestCompoundingPeriodTypeIndex. This displays the wrong value.

-            Res.string.feature_fixed_interest_compounding_period to if (state.fixedDepositAccountTerms.interestPostingPeriodTypeIndex != -1) {
-                state.template.interestPostingPeriodTypeOptions?.getOrNull(state.fixedDepositAccountTerms.interestPostingPeriodTypeIndex)?.value.orEmpty()
+            Res.string.feature_fixed_interest_compounding_period to if (state.fixedDepositAccountTerms.interestCompoundingPeriodTypeIndex != -1) {
+                state.template.interestCompoundingPeriodTypeOptions?.getOrNull(state.fixedDepositAccountTerms.interestCompoundingPeriodTypeIndex)?.value.orEmpty()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Res.string.feature_fixed_interest_compounding_period to if (state.fixedDepositAccountTerms.interestPostingPeriodTypeIndex != -1) {
state.template.interestPostingPeriodTypeOptions?.getOrNull(state.fixedDepositAccountTerms.interestPostingPeriodTypeIndex)?.value.orEmpty()
} else {
""
},
Res.string.feature_fixed_interest_compounding_period to if (state.fixedDepositAccountTerms.interestCompoundingPeriodTypeIndex != -1) {
state.template.interestCompoundingPeriodTypeOptions?.getOrNull(state.fixedDepositAccountTerms.interestCompoundingPeriodTypeIndex)?.value.orEmpty()
} else {
""
},
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/PreviewPage.kt
around lines 177 to 181, the mapping for
Res.string.feature_fixed_interest_compounding_period incorrectly uses
interestPostingPeriodTypeIndex and interestPostingPeriodTypeOptions; change it
to use state.fixedDepositAccountTerms.interestCompoundingPeriodTypeIndex and
state.template.interestCompoundingPeriodTypeOptions?.getOrNull(state.fixedDepositAccountTerms.interestCompoundingPeriodTypeIndex)?.value.orEmpty()
(with the same -1 check) so the compounding period label displays the correct
option.

Comment on lines +206 to +210
Res.string.feature_fixed_deposit_setting_lock_in_period to state.lockInPeriodFrequency + " " + if (state.lockInPeriodTypeIndex != -1) {
state.template.lockinPeriodFrequencyTypeOptions?.get(state.lockInPeriodTypeIndex)?.value.orEmpty()
} else {
""
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use getOrNull() for safer list access.

Using get() with an index can throw IndexOutOfBoundsException if the index is invalid or the list has changed. This is inconsistent with the pattern used in TermsCard.

-            Res.string.feature_fixed_deposit_setting_lock_in_period to state.lockInPeriodFrequency + " " + if (state.lockInPeriodTypeIndex != -1) {
-                state.template.lockinPeriodFrequencyTypeOptions?.get(state.lockInPeriodTypeIndex)?.value.orEmpty()
+            Res.string.feature_fixed_deposit_setting_lock_in_period to state.lockInPeriodFrequency + " " + if (state.lockInPeriodTypeIndex != -1) {
+                state.template.lockinPeriodFrequencyTypeOptions?.getOrNull(state.lockInPeriodTypeIndex)?.value.orEmpty()

Apply similar changes to lines 212, 217, 222, 234, 239, 244, and 258 where get() is used.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Res.string.feature_fixed_deposit_setting_lock_in_period to state.lockInPeriodFrequency + " " + if (state.lockInPeriodTypeIndex != -1) {
state.template.lockinPeriodFrequencyTypeOptions?.get(state.lockInPeriodTypeIndex)?.value.orEmpty()
} else {
""
},
Res.string.feature_fixed_deposit_setting_lock_in_period to state.lockInPeriodFrequency + " " + if (state.lockInPeriodTypeIndex != -1) {
state.template.lockinPeriodFrequencyTypeOptions?.getOrNull(state.lockInPeriodTypeIndex)?.value.orEmpty()
} else {
""
},
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/PreviewPage.kt
around lines 206-210 (and similarly at 212, 217, 222, 234, 239, 244, 258), you
are using list.get(index) which can throw IndexOutOfBoundsException; replace
those get(...) calls with safe getOrNull(...) and null-safe access (e.g.,
getOrNull(index)?.value.orEmpty()) or fallback to an empty string when index is
invalid, following the same pattern used in TermsCard so the code won’t crash if
the list or index is out of sync.

Comment on lines +235 to +254
AnimatedVisibility(state.transferLinkedSavingAccountInterest) {
MifosTextFieldDropdown(
value = if (state.linkedSavingAccountIndex != -1) {
state.template.savingsAccounts?.get(state.linkedSavingAccountIndex)?.accountNo.orEmpty()
} else {
""
},
options = state.template.savingsAccounts?.map {
it.accountNo
} ?: emptyList(),
onValueChanged = {},
onOptionSelected = { index, name ->
onAction(
NewFixedDepositAccountAction.OnLinkedSavingAccount(index),
)
},
label = stringResource(Res.string.feature_fixed_deposit_setting_linked_saving_account_field),
errorMessage = state.linkedSavingAccountError?.let { stringResource(it) },
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential null safety issue in options mapping.

Line 243 uses it.accountNo directly in the options mapping, while line 238 uses it.accountNo.orEmpty() for displaying the selected value. If accountNo can be null, this could cause issues in the dropdown options list.

                     options = state.template.savingsAccounts?.map {
-                        it.accountNo
+                        it.accountNo.orEmpty()
                     } ?: emptyList(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AnimatedVisibility(state.transferLinkedSavingAccountInterest) {
MifosTextFieldDropdown(
value = if (state.linkedSavingAccountIndex != -1) {
state.template.savingsAccounts?.get(state.linkedSavingAccountIndex)?.accountNo.orEmpty()
} else {
""
},
options = state.template.savingsAccounts?.map {
it.accountNo
} ?: emptyList(),
onValueChanged = {},
onOptionSelected = { index, name ->
onAction(
NewFixedDepositAccountAction.OnLinkedSavingAccount(index),
)
},
label = stringResource(Res.string.feature_fixed_deposit_setting_linked_saving_account_field),
errorMessage = state.linkedSavingAccountError?.let { stringResource(it) },
)
}
AnimatedVisibility(state.transferLinkedSavingAccountInterest) {
MifosTextFieldDropdown(
value = if (state.linkedSavingAccountIndex != -1) {
state.template.savingsAccounts?.get(state.linkedSavingAccountIndex)?.accountNo.orEmpty()
} else {
""
},
options = state.template.savingsAccounts?.map {
it.accountNo.orEmpty()
} ?: emptyList(),
onValueChanged = {},
onOptionSelected = { index, name ->
onAction(
NewFixedDepositAccountAction.OnLinkedSavingAccount(index),
)
},
label = stringResource(Res.string.feature_fixed_deposit_setting_linked_saving_account_field),
errorMessage = state.linkedSavingAccountError?.let { stringResource(it) },
)
}
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/SettingsPage.kt
around lines 235 to 254, the options list maps savingsAccounts using
it.accountNo which may be null while the selected value uses
it.accountNo.orEmpty(); update the options mapping to avoid nulls by using
either it.accountNo.orEmpty() for each mapped entry or filter out null accountNo
entries before mapping so the dropdown receives non-null strings and remains
consistent with the selected-value handling.

Comment on lines +280 to +297
MifosTextFieldDropdown(
value = if (state.investingAccountIndex != -1) {
state.template.savingsAccounts?.get(state.investingAccountIndex)?.accountNo.orEmpty()
} else {
""
},
options = state.template.savingsAccounts?.map {
it.accountNo
} ?: emptyList(),
onValueChanged = {},
onOptionSelected = { index, name ->
onAction(
NewFixedDepositAccountAction.OnInvestingAccountChange(index),
)
},
label = stringResource(Res.string.feature_fixed_deposit_setting_investing_account),
errorMessage = state.investingAccountError?.let { stringResource(it) },
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same null safety inconsistency.

The options mapping at line 287 has the same issue as the linked saving account dropdown - inconsistent null handling between the displayed value (line 282 uses orEmpty()) and the options list (line 287 does not).

                 options = state.template.savingsAccounts?.map {
-                    it.accountNo
+                    it.accountNo.orEmpty()
                 } ?: emptyList(),
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/SettingsPage.kt
around lines 280 to 297, the options list mapping uses
state.template.savingsAccounts?.map { it.accountNo } which can produce null
entries while the displayed value uses orEmpty(); change the mapping to
state.template.savingsAccounts?.map { it.accountNo.orEmpty() } ?: emptyList() so
both the dropdown value and options handle nulls consistently.

@therajanmaurya therajanmaurya merged commit 39e9e02 into openMF:development Dec 16, 2025
3 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.

2 participants