Skip to content

Conversation

@Arinyadav1
Copy link
Contributor

@Arinyadav1 Arinyadav1 commented Dec 6, 2025

Fixes - Jira-#551

Screenshot 2025-12-06 184054 image

Summary by CodeRabbit

Release Notes

  • New Features

    • Added overlay loading indicator during account creation and submission.
    • Introduced field-level validation with error messaging for deposit account forms.
  • UI/UX Improvements

    • Reorganized deposit account creation forms with improved scrollable layouts.
    • Enhanced error visibility for account creation fields.
    • Improved spacing and layout consistency across savings account pages.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 6, 2025

Walkthrough

This PR restructures the recurring deposit account creation flow with overlay loading state management, integrated form validation on submission, expanded error handling via StringResource fields, and comprehensive UI layout redesigns. It also refactors the fixed deposit account view model to track overlay loading states and adds minor padding adjustments to savings account pages.

Changes

Cohort / File(s) Summary
Validation & State Infrastructure
core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt
Added dropDownEmptyValidator(input: Boolean): StringResource? function to validate empty dropdown selections.
Fixed Deposit Account Loading
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt
Introduced isOverlayLoading state flag to track template loading overlay; updated state transitions on DataState.Loading/Success/Error.
String Resources
feature/client/src/commonMain/composeResources/values/strings.xml
Added HTML comment markers for fixed deposit account string grouping.
Recurring Deposit - Screens & State Management
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt, RecurringAccountViewModel.kt
Added overlay loading UI rendering; refactored state management with ScreenState sealed interface (Error, Loading, Success); integrated per-field error handling via StringResource types; added OnDetailNext and OnSettingNext actions with validation logic; replaced transient UI flags with unified loading/error state mechanism.
Recurring Deposit - Dependency Injection
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/di/RecurringDepositModule.kt
Removed Koin module file and its viewModelOf(::RecurringAccountViewModel) provider.
Recurring Deposit - Page Components
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/DetailsPage.kt, SettingsPage.kt, TermsPage.kt
Restructured layouts with scrollable columns, added vertical spacing via DesignToken; introduced date-picking integration for submission date; updated field labels, error messaging, and validation feedback; replaced inline field logic with modular MifosOutlinedTextField and MifosTextFieldDropdown components; wired OnDetailNext and OnSettingNext actions.
Savings Account - Layout Updates
feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/ChargesPage.kt, DetailsPage.kt, PreviewPage.kt, TermsPage.kt
Applied bottom padding (DesignToken.padding.large) to outer Column containers without altering internal layout or behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • RecurringAccountViewModel.kt: Substantial state refactoring with new ScreenState sealed interface, per-field error handling, validation logic on OnDetailNext/OnSettingNext actions, and date/submission date handling—requires careful review of state transition paths and error propagation.
  • DetailsPage.kt & SettingsPage.kt: Significant UI restructuring with new layout primitives, error field rendering, date-picker integration, and action wiring—verify scroll behavior, input field bindings, and callback chains.
  • RecurringDepositModule.kt removal: Confirm DI wiring is replaced elsewhere and no orphaned ViewModel instantiation occurs.
  • State class changes: New error fields (depositAmountError, depositPeriodTypeError, etc.) and isOverlayLoading flag additions across RecurringAccountState and nested data classes—ensure consistent initialization and usage.

Possibly related PRs

Suggested reviewers

  • revanthkumarJ
  • biplab1
  • TheKalpeshPawar

Poem

🐰 With validation's gentle thud,
We sculpt the deposits in the mud—
Overlay states now dance with care,
While scrolling pages float through air.
Form fields bloom with error grace,
A rabbit's touch on UI space. 🌿

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 accurately reflects the main changes in the pull request, which focus on enhancing the Terms and Settings pages of the recurring deposit account feature.
✨ 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: 3

Caution

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

⚠️ Outside diff range comments (3)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt (3)

76-122: Wire productId in details state before submitting payload

In createRecurringDepositAccount you send:

productId = s.recurringDepositAccountDetail.productId,

but in RecurringAccountDetailsState:

data class RecurringAccountDetailsState(
    val productId: Int = -1,
    ...
)

there’s no code in this viewmodel that ever updates productId from the selected product (loanProductSelected / template.productOptions). As written, productId remains -1 for all submissions, which is very likely invalid for the backend.

Given you already load the product-specific template based on selection, a simple fix would be to set productId when the product changes, e.g.:

private fun handleProductNameChange(action: RecurringAccountAction.RecurringAccountDetailsAction.OnProductNameChange) {
    mutableStateFlow.update {
        val product = it.template.productOptions?.getOrNull(action.index)
        it.copy(
            recurringDepositAccountDetail = it.recurringDepositAccountDetail.copy(
                loanProductSelected = action.index,
                productId = product?.id ?: -1,
                productError = null,
                fieldOfficerError = null,
            ),
        )
    }
    loadRecurringAccountTemplateWithProduct(
        state.clientId,
        product?.id ?: -1,
    )
}

or derive the ID directly when creating the payload from loanProductSelected and template.productOptions. Without this, account creation will almost certainly fail.

Also applies to: 713-729


206-221: Retry currently drops clientId and restarts with an invalid state

resetForRetry() sets:

clientId = -1,
...
screenState = ScreenState.Loading,
...

and then immediately calls loadRecurringAccountTemplate(), which uses state.clientId as the argument. This means any Retry after an error will call the repository with clientId = -1, not the original route client ID, and likely never recover.

Prefer preserving the original clientId and other immutable identity fields on retry, for example:

private fun resetForRetry() {
    val currentClientId = state.clientId
    mutableStateFlow.update {
        it.copy(
            isOnline = false,
            clientId = currentClientId,
            currentStep = 0,
            screenState = ScreenState.Loading,
            recurringDepositAccountDetail = RecurringAccountDetailsState(),
            template = RecurringDepositAccountTemplate(),
            recurringDepositAccountSettings = RecurringDepositAccountSettingsState(),
            currencyIndex = -1,
            currencyError = null,
        )
    }
    loadRecurringAccountTemplate()
}

so retry can actually reload for the same client.


663-671: Back button should move to the previous step, not hard-code step 3

OnBackPress currently does:

RecurringAccountAction.OnBackPress -> {
    mutableStateFlow.update {
        it.copy(currentStep = 3)
    }
}

This hard-codes step index 3 regardless of the current step. Since “Back” is used on multiple steps (e.g., Terms and Settings), this will jump the user to the Interest page rather than the previous step.

A more predictable behavior is to step back by one within bounds:

RecurringAccountAction.OnBackPress -> {
    mutableStateFlow.update {
        it.copy(currentStep = (state.currentStep - 1).coerceAtLeast(0))
    }
}

You can still expose NavigateBack as the action for leaving the flow entirely.

🧹 Nitpick comments (4)
feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/TermsPage.kt (1)

66-66: Bottom padding addition looks good; consider threading through the modifier (optional).

Adding padding(bottom = DesignToken.padding.large) on the outer Column should help keep the bottom buttons and content away from the edge and aligns with the pattern used on other savings pages. Functionally this is fine.

If you ever need external customization of this screen’s padding or behavior, you might consider applying the passed-in modifier on the outer container instead, for example:

Column(
    modifier = modifier
        .fillMaxSize()
        .padding(bottom = DesignToken.padding.large),
) {
    Column(
        modifier = Modifier
            .weight(1f)
            .verticalScroll(rememberScrollState()),
    ) {
        ...
    }
}

Not required for this PR, just a small consistency/extensibility tweak you may want to consider.

feature/client/src/commonMain/composeResources/values/strings.xml (1)

596-606: Minor: normalize XML comment spacing for consistency

Everywhere else section headers use a space after the opening marker (e.g., <!-- Client Products -->), but this one is <!--Fixed Deposit Account -->. Consider changing to <!-- Fixed Deposit Account --> for consistency.

core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt (1)

58-63: dropDownEmptyValidator behavior matches usage

The implementation (trueerror_field_empty, otherwise null) fits the current call sites that pass conditions like index == -1. You might consider renaming the parameter to something like isEmpty for clarity, but functionally this is fine.

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

43-131: Scrollable layout is good; consider safer option indexing

The new full-height, scrollable layout with a step header reads well and keeps the four interest-related dropdowns intact.

For the value props you’re doing:

state.template.interestCompoundingPeriodTypeOptions
    ?.get(state.recurringDepositAccountInterestChart.interestCompoundingPeriodType)
    ?.value ?: ""

If template options change (e.g., after a template reload) while the stored index is still pointing at an old position, this can throw. Using getOrNull(...) instead of get(...) would fail gracefully and let the user reselect:

state.template.interestCompoundingPeriodTypeOptions
    ?.getOrNull(state.recurringDepositAccountInterestChart.interestCompoundingPeriodType)
    ?.value ?: ""

Same comment applies to the other three dropdowns.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8e9954 and 51e5b68.

📒 Files selected for processing (13)
  • core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt (1 hunks)
  • feature/client/src/commonMain/composeResources/values/strings.xml (1 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt (2 hunks)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt (2 hunks)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt (19 hunks)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/di/RecurringDepositModule.kt (0 hunks)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/DetailsPage.kt (5 hunks)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/SettingsPage.kt (2 hunks)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/TermsPage.kt (2 hunks)
  • feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/ChargesPage.kt (1 hunks)
  • feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/DetailsPage.kt (1 hunks)
  • feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/PreviewPage.kt (1 hunks)
  • feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/TermsPage.kt (1 hunks)
💤 Files with no reviewable changes (1)
  • feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/di/RecurringDepositModule.kt
🧰 Additional context used
🧬 Code graph analysis (4)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/DetailsPage.kt (3)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosTextFieldDropdown.kt (1)
  • MifosTextFieldDropdown (39-112)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt (5)
  • MifosDatePickerTextField (363-413)
  • MifosOutlinedTextField (66-124)
  • MifosOutlinedTextField (126-243)
  • MifosOutlinedTextField (245-308)
  • MifosOutlinedTextField (415-487)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosTwoButtonRow.kt (1)
  • MifosTwoButtonRow (31-91)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt (1)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosProgressIndicator.kt (1)
  • MifosProgressIndicatorOverlay (70-102)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/TermsPage.kt (2)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosTextFieldDropdown.kt (1)
  • MifosTextFieldDropdown (39-112)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosTwoButtonRow.kt (1)
  • MifosTwoButtonRow (31-91)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt (1)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/CreateFixedDepositAccountViewmodel.kt (6)
  • moveToNextStep (254-265)
  • handleSubmissionDateChange (120-128)
  • handleSubmissionDatePick (134-142)
  • handleFieldOfficerChange (155-163)
  • handleExternalIdChange (165-173)
  • handleInterestCalculationType (224-232)
⏰ 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 (7)
feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/DetailsPage.kt (1)

101-101: LGTM! Bottom padding improves layout consistency.

The addition of bottom padding using DesignToken.padding.large follows best practices and aligns with similar spacing improvements across other pages in the savings feature.

feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/ChargesPage.kt (1)

52-52: LGTM! Bottom padding improves page layout.

The addition of bottom padding to the outer Column ensures proper spacing below the navigation buttons, preventing them from sitting at the screen edge. This aligns with similar UI enhancements applied to other savings account pages.

feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/PreviewPage.kt (1)

132-132: LGTM! Consistent padding improvement.

The bottom padding addition improves the layout spacing and prevents the button row from being too close to the screen edge, which is consistent with similar UI improvements across related screens.

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

106-145: Overlay loader integration looks correct

Using MifosProgressIndicatorOverlay() under if (state.isOverlayLoading) at the same scaffold level as the main content is a good pattern; in a Box-based scaffold it will render above the content without changing existing screenState-based loading/error handling.

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

92-99: Overlay loading state management for fixed deposits looks consistent

DataState.Loading now flips isOverlayLoading = true, while both success and error paths clear it and set an appropriate screenState. This gives you a hard “initial load” via ScreenState.Loading and softer overlay for subsequent template fetches, which is a reasonable UX split.

Also applies to: 103-116, 268-278

feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt (2)

264-291: Overlay loading wiring for product-specific template loading is solid

loadRecurringAccountTemplateWithProduct cleanly toggles isOverlayLoading on DataState.Loading and resets it on both success and error, while also updating screenState. Combined with RecurringAccountState.isOverlayLoading and the overlay composable in RecurringAccountScreen, this should give a responsive UX for product changes without regressing the base template load behavior.

Also applies to: 293-327, 692-711


329-367: Action hierarchy and state modeling are well-structured

The split into RecurringAccountDetailsAction, RecurringAccountSettingsAction, and RecurringAccountInterestChartAction, plus the new OnDetailNext / OnSettingNext actions and ScreenState sealed type, makes the flow much clearer and easier to validate per step. Combined with the new error fields (productError, depositAmountError, etc.), this is a good structural improvement.

Also applies to: 811-866

Comment on lines +143 to +197
if (!state.template.fieldOfficerOptions.isNullOrEmpty()) {
MifosDatePickerTextField(
value = state.recurringDepositAccountDetail.submissionDate,
label = stringResource(Res.string.feature_recurring_deposit_submitted_on) + "*",
openDatePicker = {
onAction(
RecurringAccountAction.RecurringAccountDetailsAction.OnSubmissionDatePick(
true,
),
)
},
)

Spacer(Modifier.height(DesignToken.padding.large))
MifosTextFieldDropdown(
value = if (state.recurringDepositAccountDetail.fieldOfficerIndex == -1) {
""
} else {
state.template.fieldOfficerOptions
?.get(state.recurringDepositAccountDetail.fieldOfficerIndex)?.displayName
?: ""
},
onValueChanged = {},
onOptionSelected = { index, value ->
onAction(
RecurringAccountAction.RecurringAccountDetailsAction.OnFieldOfficerChange(
index,
),
)
},
options = state.template.fieldOfficerOptions?.map {
it.displayName ?: ""
} ?: emptyList(),
label = stringResource(Res.string.feature_recurring_deposit_field_officer) + "*",
errorMessage = state.recurringDepositAccountDetail.fieldOfficerError?.let { stringResource(it) },
)

if (state.recurringDepositAccountDetail.isMiniLoaderActive) {
MifosProgressIndicatorMini()
MifosOutlinedTextField(
value = state.recurringDepositAccountDetail.externalId,
onValueChange = {
onAction(
RecurringAccountAction.RecurringAccountDetailsAction.OnExternalIdChange(
it,
),
)
},
label = stringResource(Res.string.feature_recurring_deposit_external_id),
config = MifosTextFieldConfig(
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
),
),
)
Spacer(Modifier.height(DesignToken.padding.large))
}
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

Align field officer validation with conditional UI

Here the field officer dropdown (and even the date picker/external ID block) is only rendered when state.template.fieldOfficerOptions is not null or empty. However, in RecurringAccountViewModel, OnDetailNext currently always validates:

val fieldOfficerError = TextFieldsValidator.dropDownEmptyValidator(
    state.recurringDepositAccountDetail.fieldOfficerIndex == -1,
)

If a template comes back with no field officer options, the UI won’t show a field officer field but the validation will still fail, blocking navigation.

Consider making the field-officer validation conditional on there actually being options, e.g. skip it when fieldOfficerOptions.isNullOrEmpty(), or always render the field when it’s required.

Comment on lines +132 to +148
value = if (settingsState.lockInPeriod.frequencyTypeIndex != -1) {
state.template.lockinPeriodFrequencyTypeOptions
?.getOrNull(settingsState.lockInPeriod.frequencyTypeIndex)?.value.orEmpty()
} else {
""
},
options = state.template.lockinPeriodFrequencyTypeOptions?.map {
it.value.orEmpty()
} ?: emptyList(),
onValueChanged = {},
onOptionSelected = { id, name ->
onAction(
RecurringAccountAction.RecurringAccountSettingsAction.SetLockInPeriodType(id),
)
},
label = stringResource(Res.string.feature_recurring_deposit_type),
)
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

Fix mismatch between dropdown indices and stored IDs in settings state

Multiple dropdowns here treat the stored value as a list index:

value = if (settingsState.lockInPeriod.frequencyTypeIndex != -1) {
    state.template.lockinPeriodFrequencyTypeOptions
        ?.getOrNull(settingsState.lockInPeriod.frequencyTypeIndex)?.value.orEmpty()
} else ""

But in RecurringAccountViewModel you set these fields to option IDs rather than indices, for example:

lockInPeriod = lockInPeriod.copy(
    frequencyTypeIndex = state.template.lockinPeriodFrequencyTypeOptions
        ?.get(action.frequencyTypeIndex)?.id ?: -1,
)

The same pattern appears for:

  • depositPeriod.periodType vs periodFrequencyTypeOptions
  • minimumDepositTerm.frequencyTypeIndex and frequencyTypeIndexAfterInMultiplesOf
  • maxDepositTerm.frequencyTypeIndex
  • preMatureClosure.interestPeriodIndex

Using IDs as indices means:

  • The displayed label may not correspond to the selected ID.
  • If IDs are not zero-based and contiguous, getOrNull(id) can select the wrong item or nothing at all.

You probably want to either:

  • Store the dropdown index in state and only translate to .id when building the payload, or
  • Keep storing IDs, but derive the label as options.firstOrNull { it.id == storedId }?.value.orEmpty() instead of indexing into the list.

I’d fix this before relying on these values for UX or payloads.

Also applies to: 205-227, 260-278, 305-323, 350-368, 405-424

🤖 Prompt for AI Agents
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/SettingsPage.kt
lines 132-148 (also apply same fix to 205-227, 260-278, 305-323, 350-368,
405-424): the UI is treating stored IDs as list indices when rendering dropdown
labels; instead of using getOrNull(storedId) use options.firstOrNull { it.id ==
storedId }?.value.orEmpty() to derive the displayed label (or change state to
store the index consistently), and keep options = state.template...map {
it.value.orEmpty() } as-is and ensure onOptionSelected continues to dispatch the
selected option ID; update all listed ranges to match this approach so labels
reflect the selected IDs correctly.

Comment on lines +577 to +599
RecurringAccountAction.RecurringAccountSettingsAction.OnSettingNext -> {
val depositAmountError =
TextFieldsValidator.stringValidator(state.recurringDepositAccountSettings.recurringDepositDetails.depositAmount)
val depositPeriodTypeError = TextFieldsValidator.dropDownEmptyValidator(
state.recurringDepositAccountSettings.depositPeriod.periodType == -1,
)
val depositPeriodError =
TextFieldsValidator.stringValidator(state.recurringDepositAccountSettings.depositPeriod.period)

if (depositAmountError != null || depositPeriodTypeError != null || depositPeriodError != null) {
mutableStateFlow.update {
it.copy(
recurringDepositAccountSettings = it.recurringDepositAccountSettings.copy(
depositPeriodError = depositPeriodError,
depositAmountError = depositAmountError,
depositPeriodTypeError = depositPeriodTypeError,
),
)
}
} else {
moveToNextStep()
}
}
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

Good addition of step-level validation; refine conditions for nullable fields

The new validation branches are a solid improvement:

  • Settings step (OnSettingNext) now enforces non-empty deposit amount, deposit period, and a selected period type.
  • Details step (OnDetailNext) enforces product selection and field officer selection via dropDownEmptyValidator.

Two refinements to consider:

  1. Field officer optionality

    OnDetailNext always validates fieldOfficerIndex == -1, but the UI only shows the field officer dropdown when template.fieldOfficerOptions is not empty. If that list is empty, the user will be blocked from progressing even though they were never shown a choice.

    You can align this with the UI by making the field officer check conditional:

    val productError = TextFieldsValidator.dropDownEmptyValidator(
        state.recurringDepositAccountDetail.loanProductSelected == -1,
    )
    
    val fieldOfficerError =
        if (!state.template.fieldOfficerOptions.isNullOrEmpty()) {
            TextFieldsValidator.dropDownEmptyValidator(
                state.recurringDepositAccountDetail.fieldOfficerIndex == -1,
            )
        } else {
            null
        }
  2. Numeric validation

    For depositAmountError and depositPeriodError you’re using stringValidator, which only checks for emptiness and invalid characters. If these fields must be numeric and > 0, you may want to use numberValidator/doubleNumberValidator instead to enforce that more strictly. That’s optional but would give more precise error feedback.

Overall the pattern is good; just tighten the conditions to match what the screens actually present.

Also applies to: 625-646

🤖 Prompt for AI Agents
In
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt
around lines 577-599 and also 625-646, the validation currently unconditionally
treats some fields as required and uses stringValidator for numeric fields;
update the logic so the field-officer dropdown is only validated when
template.fieldOfficerOptions is not null or empty (i.e. skip/return null for
fieldOfficerError if no options are present), and replace stringValidator for
depositAmount and depositPeriod with the appropriate numeric validators
(numberValidator or doubleNumberValidator) if those fields must be numeric and >
0, then wire those errors into the same mutableStateFlow update branch as the
other validators.

@therajanmaurya therajanmaurya merged commit 38c179b into openMF:development Dec 6, 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