Skip to content

Conversation

@Arinyadav1
Copy link
Contributor

@Arinyadav1 Arinyadav1 commented Nov 21, 2025

Fixes - Jira-#545

Screenshot 2025-11-21 211906 Screenshot 2025-11-21 212247

Summary by CodeRabbit

  • New Features

    • Added charge management functionality for share accounts, including the ability to add, view, and edit charges with an enhanced dialog interface.
  • Bug Fixes

    • Fixed charge expansion toggle behavior in loan accounts to use index-based tracking.
    • Simplified field officer error display in savings account creation.
    • Made date field optional in charge listing component for improved flexibility.

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

@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

Walkthrough

The PR introduces share account charge management capabilities by extending the ShareTemplate model with charge options, refactoring the CreateShareAccount feature with renamed state/action/event types, adding charge-handling dialogs and UI flows, and updating related components for consistency.

Changes

Cohort / File(s) Summary
Data Model Updates
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ServerConfig.kt, core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt
Updated default ServerConfig tenant to "dev"; introduced new ChargeOptions data class and chargeOptions property to ShareTemplate for charge-related data.
UI Component Refinements
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/AddChargeDialog.kt, core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosActionsListingCardComponent.kt
Made showDatePicker default to false and selectedDate nullable; updated validation logic. Made date parameter optional in MifosActionsChargeListingComponent with conditional rendering.
String Resources
feature/client/src/commonMain/composeResources/values/strings.xml
Added 12 new string resources under feature_share_account_charge namespace for charge-related UI labels and actions.
Share Account Creation Refactoring
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt
Renamed ShareAccount types to CreateShareAccount across state/action/event; introduced new DialogState sealed interface, CreatedCharges data class, and charge management actions; added CreateShareAccountDialog orchestration with new dialog flows for adding/viewing charges.
Share Account Pages
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/ChargesPage.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/DetailsPage.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/TermsPage.kt
Refactored ChargesPage to state-driven architecture with charge UI and bottom button row; updated DetailsPage and TermsPage with renamed state/action types for consistency.
Other Feature Updates
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt, feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt, feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/DetailsPage.kt
Changed expansion tracking from id-based to index-based in NewLoanAccountScreen; removed fieldOfficerError handling from SavingsAccountViewModel and corresponding UI binding in DetailsPage.

Sequence Diagram

sequenceDiagram
    participant User
    participant CreateShareAccountScreen
    participant CreateShareAccountViewModel
    participant DialogState
    participant ChargesPage

    User->>CreateShareAccountScreen: Opens Share Account Creation
    CreateShareAccountScreen->>CreateShareAccountViewModel: Triggers state updates
    activate CreateShareAccountViewModel
    CreateShareAccountViewModel->>CreateShareAccountViewModel: Loads ShareTemplate with ChargeOptions
    CreateShareAccountViewModel->>CreateShareAccountScreen: Provides CreateShareAccountState
    deactivate CreateShareAccountViewModel

    User->>ChargesPage: Views/Adds charges
    ChargesPage->>CreateShareAccountScreen: Emits ShowAddChargeDialog action
    CreateShareAccountScreen->>DialogState: Transitions to AddNewCharge(edit=false)
    CreateShareAccountScreen->>User: Shows AddChargeBottomSheet dialog

    User->>CreateShareAccountScreen: Selects charge & enters amount
    CreateShareAccountScreen->>CreateShareAccountViewModel: Dispatches AddChargeToList action
    activate CreateShareAccountViewModel
    CreateShareAccountViewModel->>CreateShareAccountViewModel: Updates addedCharges list
    CreateShareAccountViewModel->>CreateShareAccountViewModel: Resets dialogState
    CreateShareAccountViewModel->>CreateShareAccountScreen: Returns updated state
    deactivate CreateShareAccountViewModel

    User->>ChargesPage: Clicks View Charges
    ChargesPage->>CreateShareAccountScreen: Emits ShowListOfChargesDialog action
    CreateShareAccountScreen->>DialogState: Transitions to ShowCharges
    CreateShareAccountScreen->>User: Shows charge list in MifosBottomSheet
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • CreateShareAccountViewModel.kt — Dense charge management logic with new DialogState sealed interface, CreatedCharges data class, and multiple new action handlers (AddChargeToList, EditCharge, DeleteChargeFromSelectedCharges, etc.); ensure state transitions are correct and charge edits/deletions properly update the list.
  • CreateShareAccountScreen.kt — Significant restructuring with new CreateShareAccountDialog orchestration, private composables for charge dialogs, and integration of charge management flows; verify dialog state transitions and action routing are correct.
  • ChargesPage.kt — Complete signature change from callback-based to state-driven architecture; validate UI layout, resource string wiring, and action emissions align with the new pattern.
  • SavingsAccountViewModel.kt — Removal of fieldOfficerError handling; confirm that the removal doesn't break validation or state consistency elsewhere in the savings flow.

Possibly related PRs

Suggested reviewers

  • sam-arth07
  • biplab1
  • revanthkumarJ

Poem

🐰 A charge arrives with options bright,
Share accounts gleam with dialogues' light,
State flows dance where actions meet,
Charges managed, refactored, complete! ✨

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 pull request title clearly and accurately summarizes the main change: implementing a new charges step for share account creation, which aligns with the substantial modifications across multiple files for charge management functionality.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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: 6

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/createShareAccount/pages/DetailsPage.kt (1)

107-122: Guard against out‑of‑bounds access on productOption

Here:

value = if (state.shareProductIndex == null) {
    ""
} else {
    state.productOption[state.shareProductIndex].name
}

shareProductIndex being non‑null doesn’t guarantee it’s within productOption.indices. If state ever gets out of sync (e.g., template reloaded, options list shrinks), this will crash with IndexOutOfBoundsException.

You’re already using getOrNull in similar places (e.g., savings accounts in TermsPage), so I’d align with that here:

- value = if (state.shareProductIndex == null) {
-     ""
- } else {
-     state.productOption[state.shareProductIndex].name
- }
+ value = state.shareProductIndex
+     ?.let { index -> state.productOption.getOrNull(index)?.name }
+     .orEmpty()
🧹 Nitpick comments (5)
core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt (1)

62-74: Verify that the all-nullable design matches the API contract.

All properties in ChargeOptions are nullable with default null values. While this provides flexibility for partial data from the API, it may lead to extensive null-checking in consuming code.

Please confirm that:

  1. The backend API for share account charges can indeed return partial/optional data for these fields
  2. This nullable design aligns with how other similar template models handle optional data in the codebase

Consider adding KDoc comments to document:

  • The purpose of ChargeOptions
  • Which properties are typically present vs. truly optional
  • Any business rules around these charge fields

Example:

/**
 * Represents charge configuration options available for share accounts.
 * All properties are nullable as the API may return partial charge data.
 *
 * @property id Unique identifier for the charge option
 * @property name Display name of the charge
 * ...
 */
@Serializable
data class ChargeOptions(
    ...
)
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt (1)

453-453: Use Int instead of Int? for expandedIndex.

The expandedIndex is declared as Int? but is never actually set to null—it's always either -1 or a valid index. This makes the nullable type misleading.

Apply this diff to clarify the type:

-    var expandedIndex: Int? by rememberSaveable { mutableStateOf(-1) }
+    var expandedIndex: Int by rememberSaveable { mutableStateOf(-1) }
feature/client/src/commonMain/composeResources/values/strings.xml (1)

572-583: Clarify and de‑duplicate “Add” vs “Add New” charge strings

The new charge strings look fine overall, but these are a bit confusing:

  • feature_share_account_charge_add_new → "Add New"
  • feature_share_account_charge_add → "Add"
  • feature_share_account_charge_btn_add_new → "Add"

You effectively have two different keys whose values are "Add". That can make translations and future maintenance harder.

Consider either:

  • Reusing a single "Add" key for both buttons, or
  • Renaming feature_share_account_charge_btn_add_new (and/or adjusting its value) to clearly distinguish it from the plain "Add" confirm action, e.g. "Add New Charge".
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/ChargesPage.kt (1)

47-52: Minor layout & copy improvements for ChargesPage

The new state‑driven ChargesPage looks functionally correct and well‑wired to CreateShareAccountAction. A couple of small refinements to consider:

  1. Honor the passed modifier at the root

Right now:

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

This means any padding/semantics passed via modifier only affect the scrollable content, not the entire page. To keep things composable-friendly:

- Column(modifier = Modifier.fillMaxSize()) {
+ Column(modifier = modifier.fillMaxSize()) {
-    Column(
-        modifier = modifier.weight(1f).verticalScroll(rememberScrollState()),
-    ) {
+    Column(
+        modifier = Modifier.weight(1f).verticalScroll(rememberScrollState()),
+    ) {
  1. Pluralization for active charges label
text = state.addedCharges.size.toString() + " " +
    stringResource(Res.string.feature_share_account_charge_active_charge)

This will show “0 Active Charge” / “2 Active Charge”. If possible, consider a plural resource or at least a basic pluralization check to improve UX.

Also applies to: 53-101, 102-112

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

574-577: Consider non-nullable fields with validation.

The CreatedCharges data class allows nullable chargeId and amount, which could result in invalid charges being added to the list. While this works with the current implementation, making these fields non-nullable and ensuring validation before creation (as suggested in earlier comments) would provide stronger type safety.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a762133 and 52f5654.

📒 Files selected for processing (13)
  • core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ServerConfig.kt (1 hunks)
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt (3 hunks)
  • core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/AddChargeDialog.kt (3 hunks)
  • core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosActionsListingCardComponent.kt (2 hunks)
  • feature/client/src/commonMain/composeResources/values/strings.xml (1 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt (6 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt (18 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/ChargesPage.kt (1 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/DetailsPage.kt (6 hunks)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/TermsPage.kt (12 hunks)
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt (2 hunks)
  • feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt (1 hunks)
  • feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/DetailsPage.kt (0 hunks)
💤 Files with no reviewable changes (1)
  • feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/pages/DetailsPage.kt
🧰 Additional context used
🧬 Code graph analysis (6)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/ChargesPage.kt (2)
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)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/DetailsPage.kt (1)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt (4)
  • MifosOutlinedTextField (66-124)
  • MifosOutlinedTextField (126-243)
  • MifosOutlinedTextField (245-308)
  • MifosOutlinedTextField (415-487)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/AddChargeDialog.kt (1)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt (1)
  • MifosDatePickerTextField (363-413)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt (1)
feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt (2)
  • moveToNextStep (617-626)
  • moveToPreviousStep (608-615)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt (7)
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt (2)
  • AddNewChargeDialog (378-445)
  • ShowChargesDialog (447-529)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosProgressIndicator.kt (2)
  • MifosProgressIndicator (41-68)
  • MifosProgressIndicatorOverlay (70-102)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosSweetError.kt (1)
  • MifosSweetError (40-83)
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 (598-730)
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/MifosActionsListingCardComponent.kt (1)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/PrintTextUtil.kt (1)
  • PrintTextUtil (18-27)
⏰ 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 (22)
core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/AddChargeDialog.kt (3)

53-55: Good separation of concerns for date handling.

The changes correctly separate two aspects:

  • showDatePicker controls the date picker modal state
  • selectedDate controls whether the date field should be rendered

Making selectedDate nullable with a default allows charges without date fields, which aligns with the PR objective of handling different charge types.


112-120: Clean conditional rendering of date field.

The date picker field and its associated spacing are correctly rendered only when selectedDate is not null, providing a clean UI for charge types that don't require dates.


166-166: Correct validation focusing on user input.

The validation logic appropriately checks only the user-provided chargeAmount field and its error state. Since chargeType is a read-only field populated by the system (lines 137-145), it doesn't need validation.

core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosActionsListingCardComponent.kt (2)

602-602: Consistent optional date handling.

Making the date parameter nullable with a default value aligns with the changes in AddChargeDialog.kt and supports displaying charges that don't have associated dates.


645-653: Proper conditional rendering of optional date.

The date is correctly displayed only when present, maintaining clean UI layout for charges without dates.

core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt (2)

32-32: LGTM! Good default value for optional charge options.

The chargeOptions field is properly added with an appropriate default empty list, following Kotlin conventions for optional collections.


12-15: Imports verified—all Charge-related types are available in the codebase.

The four imported types (ChargeAppliesTo, ChargeCalculationType, ChargePaymentMode, ChargeTimeType) from com.mifos.core.model.objects.template.client all exist and will resolve correctly. No issues with the imports.

core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ServerConfig.kt (1)

34-34: Verify that this tenant configuration change is intentional and necessary.

The git history confirms that the tenant value was changed from "default" to "dev" in this PR commit. However, this modification appears unrelated to the stated objective of implementing Share Account Charges functionality.

Concerns:

  • No explanation provided in the PR description for this configuration change
  • Could be an accidental commit or leftover from local development
  • Affects default behavior for all application users connecting to the demo server
  • While the change may be correct (dev tenant for tt.mifos.community), it requires explicit clarification

Please confirm:

  1. Is this change intentional and required for the Share Account Charges feature?
  2. If intentional, what is the reason for changing the default tenant?
  3. Should this be part of a separate configuration PR with proper documentation?
feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt (2)

1-627: Field officer validation was removed without explanation; verify if this change is intentional.

The commit removes the validation check for fieldOfficerIndex == -1 in handleOnDetailsSubmit() (previously lines 260-263) and removes the fieldOfficerError property from state, yet the UI still displays the field officer dropdown. When no field officer is selected, fieldOfficerId will be null in the payload (line 140). The SavingsPayload.fieldOfficerId is nullable, but the backend API requirement is unclear. The PR description does not explain this change.

Verify:

  • Whether the backend API accepts null fieldOfficerId
  • Whether field officer should be optional or remain required
  • If optional, the UI should clarify this (e.g., mark as "Optional" or remove the field entirely)

254-275: Field officer validation removed from Savings Account Details submission - verify intentionality.

Verification confirms the field officer validation has been removed from handleOnDetailsSubmit(). Unlike similar account types (RecurringDeposit and FixedDeposit), which still enforce field officer selection through both button enabling logic (fieldOfficerIndex != -1) and error validation, the Savings v2 implementation allows users to proceed without selecting a field officer. With fieldOfficerIndex defaulting to -1, users can submit without a valid selection, resulting in a null fieldOfficerId in the payload.

While the API payload defines fieldOfficerId as nullable (suggesting it's optional), this inconsistency with RecurringDeposit and FixedDeposit patterns warrants clarification: was this removal intentional as part of the Savings refactoring, or was validation inadvertently omitted during migration?

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

44-55: CreateShareAccount state/action refactor is consistent

The switch to CreateShareAccountState / CreateShareAccountAction here is consistent with the rest of the flow and keeps the event surface coherent across pages.

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

55-66: Terms page refactor to CreateShareAccount* looks solid

The migration to CreateShareAccountState / CreateShareAccountAction and the updated callbacks (application date, total shares, savings account, min active/lock‑in fields, dividends flag, step navigation) are consistent and use safer patterns (getOrNull, isNotBlank) for indexed options.

No issues spotted in the new wiring.

Also applies to: 77-183, 193-265, 267-275

feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt (2)

70-88: CreateShareAccount refactor and dialog orchestration are cohesive*

The migration of the screen to CreateShareAccountState / CreateShareAccountAction / CreateShareAccountEvent plus the extraction of CreateShareAccountDialog and CreateShareAccountContent reads clean and keeps responsibilities separated:

  • Event handling via EventsEffect is straightforward.
  • Steps wiring (DetailsPage, TermsPage, ChargesPage, PreviewPage) all run through a single onAction channel.
  • Screen state handling (Loading / Success / Error) and the overlay indicator are consistent with other features.

No behavioral red flags here.

Also applies to: 90-114, 116-147, 149-183


244-309: ShowChargesDialog implementation matches existing patterns and looks correct

ShowChargesDialog mirrors the loan charges listing pattern:

  • Uses MifosBottomSheet with a clear title (feature_share_account_charge_view_charges).
  • Resolves each charge’s metadata from state.chargeOptions by chargeId.
  • Delegates edit/delete actions through CreateShareAccountAction variants.
  • Uses rememberSaveable to keep the expanded row across recompositions and configuration changes.
  • Provides a bottom MifosTwoButtonRow to dismiss or jump to “Add charge”.

Given ChargesPage disables the “View” button when addedCharges is empty, you also avoid the empty-list edge case.

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

50-143: LGTM: Template loading correctly updated.

The changes to loadShareTemplateFromProduct and loadShareTemplate correctly:

  • Accept nullable productId for flexible template fetching
  • Update state transitions to CreateShareAccountState.ScreenState types
  • Manage isOverLayLoadingActive flag during async operations
  • Populate chargeOptions from the template response

165-177: LGTM: Detail navigation correctly loads product-specific template.

The function correctly passes state.productId to load the template for the selected product before advancing to the next step.


179-226: LGTM: Validation logic is correct.

The formatting adjustments improve readability while maintaining the correct validation logic for terms step.


375-381: LGTM: Charge management actions are correctly implemented.

The handlers for OnChargeAmountChange, OnChooseChargeIndexChange, OnDismissDialog, DeleteChargeFromSelectedCharges, ShowAddChargeDialog, and ShowListOfChargesDialog correctly update state for their respective operations.

Also applies to: 417-431, 433-440, 459-473


228-373: LGTM: Action handlers correctly renamed and updated.

All action handlers have been correctly migrated to use CreateShareAccountAction types, with appropriate logic preserved. The addition of productId tracking at line 284 supports the product-specific template loading.


478-534: LGTM: State model correctly extended for charge management.

The CreateShareAccountState data class correctly adds:

  • Dialog state management via DialogState sealed interface
  • Charge selection and input fields (chooseChargeIndex, chargeAmount)
  • Template data (chargeOptions, productId)
  • Loading state (isOverLayLoadingActive)
  • Added charges tracking (addedCharges)

All fields have appropriate defaults and types.


536-567: LGTM: Action interface comprehensively covers charge operations.

The CreateShareAccountAction sealed interface correctly defines all actions for:

  • Navigation and step management
  • Form field updates
  • Charge management (add, edit, delete, show dialogs)

Action naming is clear and parameters are appropriate.


569-572: LGTM: Event interface correctly defined.

The CreateShareAccountEvent sealed interface appropriately defines navigation events.

Comment on lines +187 to +242
@Composable
private fun AddNewChargeDialog(
isEdit: Boolean,
index: Int = -1,
state: CreateShareAccountState,
onAction: (CreateShareAccountAction) -> Unit,
) {
AddChargeBottomSheet(
title = if (isEdit) {
stringResource(Res.string.feature_share_account_charge_edit_charge)
} else {
stringResource(Res.string.feature_share_account_charge_add_new_charge)
},
confirmText = if (isEdit) {
stringResource(Res.string.feature_share_account_charge_edit_charge)
} else {
stringResource(Res.string.feature_share_account_charge_add)
},
dismissText = stringResource(Res.string.feature_share_account_back),
selectedChargeName = if (state.chooseChargeIndex == null) {
""
} else {
state.chargeOptions[state.chooseChargeIndex].name ?: ""
},
chargeAmount = state.chargeAmount,
chargeType = if (state.chooseChargeIndex == null) {
""
} else {
state.chargeOptions[state.chooseChargeIndex].chargeCalculationType?.value
?: ""
},
chargeCollectedOn = if (state.chooseChargeIndex == null) {
""
} else {
state.chargeOptions[state.chooseChargeIndex].chargeTimeType?.value
?: ""
},
chargeOptions = state.chargeOptions.map { it.name ?: "" },
onConfirm = {
if (isEdit) {
onAction(CreateShareAccountAction.EditCharge(index))
} else {
onAction(CreateShareAccountAction.AddChargeToList)
}
},
onDismiss = { onAction(CreateShareAccountAction.OnDismissDialog) },
onChargeSelected = { index, _ ->
onAction(CreateShareAccountAction.OnChooseChargeIndexChange(index))
},
onDatePick = {},
onDateChange = {},
onAmountChange = { amount ->
onAction(CreateShareAccountAction.OnChargeAmountChange(amount))
},
)
}
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

🧩 Analysis chain

Charge date handling in AddNewChargeDialog is effectively disabled

In AddNewChargeDialog you wire AddChargeBottomSheet like this:

AddChargeBottomSheet(
    // ...
    onDatePick = {},
    onDateChange = {},
    // no showDatePicker / selectedDate passed
)

Given the shared AddChargeBottomSheet API supports a charge date (with showDatePicker, selectedDate, onDatePick, onDateChange), this means the share-account flow currently cannot:

  • Toggle the date picker, or
  • Persist/update any chosen date for a charge.

If share-account charges never require an explicit date (and backend defaults are always correct), this might be intentional. If some charge types are “due on” a specific date, the current wiring will be insufficient.

I recommend either:

  • Explicitly deciding that share-account charges don’t use a date (and, if so, hiding the date UI in AddChargeBottomSheet for this flow), or
  • Adding date state to CreateShareAccountState and passing the proper showDatePicker, selectedDate, onDatePick, and onDateChange callbacks, mirroring the loan new-account implementation.

🏁 Script executed:

#!/bin/bash
# Verify the exact code at lines 187-242 in the CreateShareAccountScreen.kt file
cat feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt | sed -n '187,242p'

Length of output: 2048


🏁 Script executed:

#!/bin/bash
# Find and inspect the AddChargeBottomSheet composable definition
rg -n "fun AddChargeBottomSheet|@Composable.*AddChargeBottomSheet" -A 20 --type kotlin

Length of output: 4807


🏁 Script executed:

#!/bin/bash
# Search for CreateShareAccountState to understand its structure
rg -n "data class CreateShareAccountState|class CreateShareAccountState" -A 15 --type kotlin

Length of output: 2454


🏁 Script executed:

#!/bin/bash
# Find loan new-account implementation to compare date handling
fd -e kt -path "*/loan*" -path "*newAccount*" | head -10

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Search for date-related state or handling in share account charges
rg -n "chargeDate|datePicke|OnDate" -C 3 --type kotlin feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if AddChargeBottomSheet has showDatePicker and selectedDate parameters
rg -n "showDatePicker|selectedDate" -C 2 --type kotlin | grep -i "addcharge\|composable" -A 10 -B 5

Length of output: 5163


🏁 Script executed:

#!/bin/bash
# Find CreateShareAccountAction definition to see if date actions exist
rg -n "sealed class CreateShareAccountAction|data class.*Charge|object.*Charge" -A 3 --type kotlin feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/

Length of output: 3354


🏁 Script executed:

#!/bin/bash
# Search for loan feature charge handling implementation
rg -n "AddChargeBottomSheet" -B 5 -A 20 --type kotlin feature/loan/ | head -100

Length of output: 7547


🏁 Script executed:

#!/ab/bash
# Check if share account charges are used in any API call or submission
rg -n "chargeAmount|AddChargeToList|EditCharge" -C 3 --type kotlin feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/

Length of output: 15160


🏁 Script executed:

#!/bin/bash
# Check the complete AddChargeBottomSheet implementation to see if date field is always rendered
cat core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/AddChargeDialog.kt | sed -n '49,170p'

Length of output: 4580


Confirm date handling is intentionally disabled or implement date support for share-account charges

The current wiring in AddNewChargeDialog (lines 187-242) passes empty lambdas for onDatePick and onDateChange without providing showDatePicker or selectedDate parameters. Since AddChargeBottomSheet only renders the date field when selectedDate is not null, the date UI is completely hidden for share-account charges.

The data layer confirms this: CreateShareAccountState contains no date fields for charges, and both AddChargeToList and EditCharge actions only store chargeId and amount in CreatedCharges—no date.

By contrast, the loan new-account implementation passes showDatePicker = state.showChargesDatePick and selectedDate = state.chargeDate, demonstrating this feature already works elsewhere.

Either explicitly decide share-account charges don't require dates (and hide the date UI in AddChargeBottomSheet for this flow), or add date state to CreateShareAccountState and wire the callbacks properly to match the loan implementation.

🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt
around lines 187-242, the date callbacks (onDatePick/onDateChange) are no-ops
and selectedDate/showDatePicker are not passed, which hides the date UI while
the state/actions also lack charge date handling; either explicitly remove/hide
the date field in AddChargeBottomSheet when no date support is intended (modify
AddChargeBottomSheet call to pass null/flag to hide date and adjust its
rendering), or add date support by extending CreateShareAccountState with
chargeDate and showChargesDatePick, update CreateShareAccountAction to carry
date for AddChargeToList/EditCharge and CreatedCharges to store the date, and
wire onDatePick to toggle showChargesDatePick and onDateChange to dispatch
charge date changes, passing selectedDate = state.chargeDate and showDatePicker
= state.showChargesDatePick like the loan flow.

import com.mifos.core.network.model.share.SavingsAccountOption
import com.mifos.core.ui.util.BaseViewModel
import com.mifos.core.ui.util.TextFieldsValidator
import com.mifos.feature.loan.newLoanAccount.NewLoanAccountState.DialogState
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

Remove unused import from the loan feature.

This import references DialogState from NewLoanAccountState (loan feature), but the code defines its own DialogState sealed interface at lines 530-533. The import is unused and misleading.

Apply this diff:

-import com.mifos.feature.loan.newLoanAccount.NewLoanAccountState.DialogState
📝 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
import com.mifos.feature.loan.newLoanAccount.NewLoanAccountState.DialogState
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt
around line 27, remove the unused import "import
com.mifos.feature.loan.newLoanAccount.NewLoanAccountState.DialogState" because
this file defines its own DialogState sealed interface later (lines ~530-533);
delete that import line and run a build/IDE reformat to ensure there are no
unresolved references or unused-import warnings remaining.

Comment on lines +383 to +397
is CreateShareAccountAction.AddChargeToList -> {
val createdData = CreatedCharges(
chargeId = state.chargeOptions[state.chooseChargeIndex!!].id,
amount = state.chargeAmount.toDoubleOrNull(),
)

mutableStateFlow.update {
it.copy(
addedCharges = it.addedCharges + createdData,
chooseChargeIndex = null,
dialogState = null,
chargeAmount = "",
)
}
}
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

Add validation before creating charge.

The code has several validation gaps:

  1. Line 385: Force unwrap state.chooseChargeIndex!! without null check will crash if null
  2. Line 386: toDoubleOrNull() can return null with no validation
  3. No bounds check before accessing chargeOptions[state.chooseChargeIndex!!]

Consider validating that a charge is selected and amount is valid before creating the charge.

Apply this diff:

 is CreateShareAccountAction.AddChargeToList -> {
+    val chargeIndex = state.chooseChargeIndex
+    val amount = state.chargeAmount.toDoubleOrNull()
+    
+    if (chargeIndex == null || amount == null || amount <= 0) {
+        // Handle validation error - show error message or ignore
+        return
+    }
+    
     val createdData = CreatedCharges(
-        chargeId = state.chargeOptions[state.chooseChargeIndex!!].id,
-        amount = state.chargeAmount.toDoubleOrNull(),
+        chargeId = state.chargeOptions[chargeIndex].id,
+        amount = amount,
     )

     mutableStateFlow.update {
         it.copy(
             addedCharges = it.addedCharges + createdData,
             chooseChargeIndex = null,
             dialogState = null,
             chargeAmount = "",
         )
     }
 }
📝 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
is CreateShareAccountAction.AddChargeToList -> {
val createdData = CreatedCharges(
chargeId = state.chargeOptions[state.chooseChargeIndex!!].id,
amount = state.chargeAmount.toDoubleOrNull(),
)
mutableStateFlow.update {
it.copy(
addedCharges = it.addedCharges + createdData,
chooseChargeIndex = null,
dialogState = null,
chargeAmount = "",
)
}
}
is CreateShareAccountAction.AddChargeToList -> {
val chargeIndex = state.chooseChargeIndex
val amount = state.chargeAmount.toDoubleOrNull()
if (chargeIndex == null || amount == null || amount <= 0) {
// Handle validation error - show error message or ignore
return
}
val createdData = CreatedCharges(
chargeId = state.chargeOptions[chargeIndex].id,
amount = amount,
)
mutableStateFlow.update {
it.copy(
addedCharges = it.addedCharges + createdData,
chooseChargeIndex = null,
dialogState = null,
chargeAmount = "",
)
}
}

Comment on lines +399 to +415
is CreateShareAccountAction.EditCharge -> {
val createdData = CreatedCharges(
chargeId = state.chargeOptions[action.index].id,
amount = state.chargeAmount.toDoubleOrNull(),
)

val currentAddedCharges = state.addedCharges.toMutableList()
currentAddedCharges[action.index] = createdData
mutableStateFlow.update {
it.copy(
addedCharges = currentAddedCharges,
chooseChargeIndex = null,
dialogState = CreateShareAccountState.DialogState.ShowCharges,
chargeAmount = "",
)
}
}
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

Critical: Incorrect index used to access charge options.

Line 401 uses action.index to access chargeOptions, but action.index is the position in addedCharges, not chargeOptions. This will access the wrong charge or crash with an index out of bounds error.

The correct approach is to use state.chooseChargeIndex (set by EditChargeDialog at line 450) to access the selected charge from chargeOptions.

Additionally, the same validation issues from AddChargeToList apply here.

Apply this diff:

 is CreateShareAccountAction.EditCharge -> {
+    val chargeIndex = state.chooseChargeIndex
+    val amount = state.chargeAmount.toDoubleOrNull()
+    
+    if (chargeIndex == null || amount == null || amount <= 0) {
+        // Handle validation error
+        return
+    }
+    
     val createdData = CreatedCharges(
-        chargeId = state.chargeOptions[action.index].id,
-        amount = state.chargeAmount.toDoubleOrNull(),
+        chargeId = state.chargeOptions[chargeIndex].id,
+        amount = amount,
     )

     val currentAddedCharges = state.addedCharges.toMutableList()
     currentAddedCharges[action.index] = createdData
     mutableStateFlow.update {
         it.copy(
             addedCharges = currentAddedCharges,
             chooseChargeIndex = null,
             dialogState = CreateShareAccountState.DialogState.ShowCharges,
             chargeAmount = "",
         )
     }
 }
📝 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
is CreateShareAccountAction.EditCharge -> {
val createdData = CreatedCharges(
chargeId = state.chargeOptions[action.index].id,
amount = state.chargeAmount.toDoubleOrNull(),
)
val currentAddedCharges = state.addedCharges.toMutableList()
currentAddedCharges[action.index] = createdData
mutableStateFlow.update {
it.copy(
addedCharges = currentAddedCharges,
chooseChargeIndex = null,
dialogState = CreateShareAccountState.DialogState.ShowCharges,
chargeAmount = "",
)
}
}
is CreateShareAccountAction.EditCharge -> {
val chargeIndex = state.chooseChargeIndex
val amount = state.chargeAmount.toDoubleOrNull()
if (chargeIndex == null || amount == null || amount <= 0) {
// Handle validation error
return
}
val createdData = CreatedCharges(
chargeId = state.chargeOptions[chargeIndex].id,
amount = amount,
)
val currentAddedCharges = state.addedCharges.toMutableList()
currentAddedCharges[action.index] = createdData
mutableStateFlow.update {
it.copy(
addedCharges = currentAddedCharges,
chooseChargeIndex = null,
dialogState = CreateShareAccountState.DialogState.ShowCharges,
chargeAmount = "",
)
}
}
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt
around lines 399 to 415, the code incorrectly uses action.index to read from
state.chargeOptions (action.index is the position in addedCharges), which can
pick the wrong charge or throw an OOB; change it to read the charge id from
state.chargeOptions at state.chooseChargeIndex (null-check or return early if
null or OOB), parse/validate state.chargeAmount the same way as AddChargeToList
(ensure amount is non-null and positive), and then create the CreatedCharges and
replace the entry in currentAddedCharges at action.index; finally update the
state clearing chooseChargeIndex and dialogState as before. Ensure all index
bounds checks and null/parse validation mirror AddChargeToList to avoid crashes
or invalid entries.

Comment on lines +442 to +457
is CreateShareAccountAction.EditChargeDialog -> {
val selectedEditCharge = state.addedCharges[action.index]
val chooseChargeIndex = state.chargeOptions
.indexOfFirst { it.id == selectedEditCharge.chargeId }

mutableStateFlow.update {
it.copy(
chargeAmount = selectedEditCharge.amount.toString(),
chooseChargeIndex = chooseChargeIndex,
dialogState = CreateShareAccountState.DialogState.AddNewCharge(
true,
action.index,
),
)
}
}
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

Handle nullable amount safely when editing.

Line 449 calls toString() on selectedEditCharge.amount which is nullable (Double?). If the amount is null, this will produce the string "null" instead of an empty string, which would be confusing in the UI.

Apply this diff:

 mutableStateFlow.update {
     it.copy(
-        chargeAmount = selectedEditCharge.amount.toString(),
+        chargeAmount = selectedEditCharge.amount?.toString() ?: "",
         chooseChargeIndex = chooseChargeIndex,
         dialogState = CreateShareAccountState.DialogState.AddNewCharge(
             true,
             action.index,
         ),
     )
 }
🤖 Prompt for AI Agents
In
feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt
around lines 442 to 457, the code calls toString() on selectedEditCharge.amount
(Double?) which yields "null" when amount is null; change the assignment to
convert null to an empty string (for example using
selectedEditCharge.amount?.toString() ?: "") so the UI shows an empty field
instead of the literal "null", and update any related formatting if needed.

Comment on lines +477 to 480
isExpanded = expandedIndex == index,
onExpandToggle = {
expandedIndex = if (expandedIndex == it.id) -1 else it.id
expandedIndex = if (expandedIndex == index) -1 else index
},
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

Index-based expansion tracking may cause UI glitches after deletion.

When a charge is deleted (lines 494-498), the expandedIndex is not updated. This can cause the wrong charge to appear expanded because list indices shift after deletion. For example, if charge at index 1 is expanded and charge at index 0 is deleted, the charge previously at index 2 will now appear expanded.

Consider one of these solutions:

Solution 1: Reset expansion state on deletion

onActionClicked = { action ->
    when (action) {
        is Actions.Delete -> {
            expandedIndex = -1  // Reset expansion
            onAction(
                NewLoanAccountAction.DeleteChargeFromSelectedCharges(
                    index,
                ),
            )
        }
        // ...
    }
}

Solution 2: Use ID-based tracking (preferred for stability)

Replace index-based tracking with charge ID-based tracking:

-    var expandedIndex: Int? by rememberSaveable { mutableStateOf(-1) }
+    var expandedChargeId: Int? by rememberSaveable { mutableStateOf(null) }

Then update comparisons:

-    isExpanded = expandedIndex == index,
+    isExpanded = expandedChargeId == it.id,
     onExpandToggle = {
-        expandedIndex = if (expandedIndex == index) -1 else index
+        expandedChargeId = if (expandedChargeId == it.id) null else it.id
     },

Note: This requires that charge items have stable id properties.

Also applies to: 508-511

🤖 Prompt for AI Agents
In
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt
around lines 477-480 (and similarly 508-511), the UI uses index-based expansion
state which breaks when list items are removed; update the deletion handling or
the expansion tracking: either (quick fix) reset the expansion state before
performing a delete by setting expandedIndex = -1 immediately when handling
Actions.Delete, or (preferred) switch to ID-based tracking by replacing
expandedIndex with a nullable expandedId (type matching charge.id), update
onExpandToggle to set/unset expandedId using the item id, compare item.id to
expandedId when rendering isExpanded, and ensure you clear expandedId when the
deleted charge id equals expandedId; apply the same change to all places
mentioned.

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.

1 participant