Skip to content

feat(loan): implement dynamic datatable steps for new loan creation#2693

Open
TheKalpeshPawar wants to merge 15 commits into
openMF:devfrom
TheKalpeshPawar:feat/loan-datatable-dynamic-steps
Open

feat(loan): implement dynamic datatable steps for new loan creation#2693
TheKalpeshPawar wants to merge 15 commits into
openMF:devfrom
TheKalpeshPawar:feat/loan-datatable-dynamic-steps

Conversation

@TheKalpeshPawar

@TheKalpeshPawar TheKalpeshPawar commented May 25, 2026

Copy link
Copy Markdown
Contributor

Fixes - Jira-#MIFOSAC-706

screen-20260525-215619-1779726171393.2.mp4
AUTOEMPLEO PARA MUJERES EN EL MARXO ....1 AUTOEMPLEO PARA MUJERES EN EL MARXO ....2
AUTOEMPLEO PARA MUJERES EN EL MARXO DEL PLAN MICHOACAN POR LA PAX Y LA JUSTICIA_1 AUTOEMPLEO PARA MUJERES EN EL MARXO DEL PLAN MICHOACAN POR LA PAX Y LA JUSTICIA_2
PROGRAMA DEL APOYO DE MOTORES 1 PROGRAMA DEL APOYO DE MOTORES 2
PROGRAMA DEL APOYO DE MOTORES_2 PROGRAMA DEL APOYO DE MOTORES_1
REST
CREDITO SOLIDARIO

Loan products in Fineract can require one or more populated datatables
before the loan can be created. The Android client previously ignored
this and POSTed `/loans` directly, which returned 403:
  "The datatable [DATOS_RAZON_SOCIAL, DATOS_DIRECCION, …]
   needs to be filled in before the current action can be proceeded"

This change adds inline datatable steps to NewLoanAccountScreen's
stepper, between Schedule and Preview. The step count and step labels
come from `loanTemplate.dataTables` (filtered for nulls). Each step
renders a fully dynamic form derived from `columnHeaderData`, with
per-column widgets keyed by `columnDisplayType` (STRING, INTEGER,
DECIMAL, DATE, BOOLEAN, CODELOOKUP/CODEVALUE). Dropdown values are
sourced from each column's `columnValues`; the picked code id is
persisted in submit payload, not the display string. PK columns and
`created_at`/`updated_at` audit columns are filtered out.

Products with no datatables (e.g. products 2-6, 9, 15) see the same
5-step flow as before. Products with mixed null entries (7) or all
null entries (8, 10) handle gracefully via `.filterNotNull()`. Web-app
parity verified against ~/OpenSource/Mifos/web-app/
src/app/loans/create-loans-account/create-loans-account.component.{ts,html}.

Wire fixes along the way:
- @SerialName("datatables") on both LoanTemplate.dataTables (decode) and
  LoansPayload.dataTables (encode) — Fineract uses lowercase, Kotlin
  field was camelCase, so the field defaulted to empty.
- LoanTemplate.dataTables typed as ArrayList<DataTableEntity?> — some
  products return [null,null,...] which previously threw inside
  kotlinx-serialization and bubbled up as deserialization failure.
- @SerialName on ColumnHeader for columnName / isColumnNullable /
  isColumnPrimaryKey — without these, dataTableColumnName + filter
  conditions were always null/false, making the dynamic form blank.
- GetDataTablesResponseMapper now propagates columnName (legacy path).
- MifosStepper already supports dynamic step count + LazyRow scroll;
  no UI-component changes needed.

Files:
- feature/loan/.../pages/DatatableStepPage.kt  NEW
  Fully dynamic per-table form renderer with Back/Next.
- feature/loan/.../NewLoanAccountViewModel.kt
  + datatableValues state (Map<Int, Map<String, Any>>)
  + UpdateDatatableField action
  + computed totalSteps based on filtered datatable count
  + inline datatable-payload assembly in submitLoanApplication
- feature/loan/.../NewLoanAccountScreen.kt
  Dynamic step list (buildList) with datatable steps between
  Schedule and Preview.
- core/database/.../ColumnHeader.kt   @SerialName mappings
- core/database/.../LoanTemplate.kt   @SerialName + nullable element
- core/network/.../LoansPayload.kt    @SerialName for submit side
- core/network/.../GetDataTablesResponseMapper.kt  legacy mapper fix
- feature/data-table/.../*  encoder-side polymorphic SerializersModule
  on DataTableNavigation.navigateDataTableList; DataTableListScreen
  stateful inputs + columnPrimaryKey filter fix; DataTableColumnType
  constants replacing the missing legacy BaseFormWidget.SCHEMA_KEY_*.
  (The DataTableListScreen path remains used by the client-creation
  flow; the loan-creation path is now inline.)

.gitignore: exclude .claude/, .mcp.json, CLAUDE.md, graphify-out/
Three follow-up fixes after device testing of the dynamic datatable
stepper:

1. **Label heuristic (v3, longer-wins).** Codelookup columns are stored
   as `{columnCode}_cd_{descriptiveName}`. Either half can be the more
   descriptive one — and Fineract sometimes truncates the suffix to the
   column-length limit. v1 (suffix only) collapsed `YesNo_cd_*` pairs
   in product 7; v2 (suffix only) truncated `CREDITO_DE_ALGUNA_I`.
   v3 picks whichever side is LONGER, which gets all three cases right:
     STATE_cd_ESTADO                                       → ESTADO
     YesNo_cd_YA_CUENTA_CON_UN_NEGOCIO                     → YA_CUENTA_CON_UN_NEGOCIO
     YesNo_cd_QUIERE_EMPRENDER_UN_NEGOCIO                  → QUIERE_EMPRENDER_UN_NEGOCIO
     CREDITO_…_FINANCIERA_cd_CREDITO_DE_ALGUNA_I           → CREDITO_…_FINANCIERA

2. **FK column blacklist.** `loan_id` was rendering as an INTEGER input
   on INSTITUCION_FINANCIERA (product 1). The column has
   `isColumnPrimaryKey: false` so the PK filter didn't catch it — it's
   actually a foreign key to the parent m_loan, auto-populated server-
   side. Added the conventional Fineract FK column names
   (loan_id / client_id / group_id / savings_id / share_id / office_id)
   to SYSTEM_COLUMNS in both DatatableStepPage and the ViewModel
   payload assembly.

3. **Date payload format.** CONDICION.FECHA was rejected with "invalid
   value". Root cause: payload declared
   `dateFormat = ApiDateFormatter.DATE_FORMAT` ("dd MMMM yyyy") but the
   DateFieldRow widget actually emits `dd-MM-yyyy` (via
   DateHelper.getDateAsStringFromLong → SHORT_MONTH). The two must
   match. Switched the datatable payload to
   `dateFormat = DateHelper.SHORT_MONTH` to mirror what the existing
   loan POST does (NewLoanAccountViewModel.kt:275 was already using
   DateHelper.SHORT_MONTH for expectedDisbursementDate etc.).

Files: NewLoanAccountViewModel.kt (date format + FK blacklist) and
pages/DatatableStepPage.kt (label v3 heuristic + FK blacklist).

Not addressed (upstream): geolocalizacion-del-registro and
region-del-producto are not present in product 7's
/loans/template response — verified via recursive search across all
nested fields + cross-checked against web-app source. Both clients
(Android + web-app) leave those steps empty; fix belongs in the
Fineract product schema.
Manual cleanups on top of the dynamic-datatable feature:
  - Drop verbose GAP-DT-* markers from comments; keep the explanations
    that still aid future readers.
  - Rename `ColumnHeader.isUserEditable()` →
    `ColumnHeader.isNotAutoGeneratedOrNull()`; the new name describes
    what is being checked rather than the intent of the caller.
  - Remove unused imports (`SelectableDates`, `ExperimentalTime`).
  - Inline doc trimming.
  - `displayLabel()` reverted from the longer-wins heuristic back to
    `substringBefore("_cd_")` — prefer the columnCode prefix as the
    label. Trade-off accepted: YesNo_cd_* pairs collapse to the same
    "YesNo" label, but the simpler rule is easier to reason about and
    matches the bulk of the field labels seen in real product
    templates.
The dynamic datatable form stored every cell as `Any`, which forced
unchecked `as? String` / `as? Boolean` / `as? Int` casts at every read
and made the storage→submit boundary easy to get wrong (e.g. caller
emits a Boolean for an INTEGER column, deferred-fails at the server).

Introduce a sealed interface with three concrete variants:
  - Text(text: String)  : STRING / TEXT / INTEGER / DECIMAL / FLOAT / DATE
                          (numeric parsing is deferred to submit time)
  - Bool(checked: Bool) : BOOLEAN
  - Code(id: Int)       : CODELOOKUP / CODEVALUE  (Fineract code id, not
                          the display string)

Plus `asPayloadValue(): Any` as the single boundary where the typed
form value crosses into the `Map<String, Any>` shape the
`DataTablePayload.data` Fineract contract requires.

Touch points:
  - NEW  pages/DatatableFieldValue.kt
  - pages/DatatableStepPage.kt
      values: Map<String, DatatableFieldValue>
      onValueChange: (String, DatatableFieldValue) -> Unit
      Each inner Row helper keeps its concrete type (String / Boolean /
      Int); wrapping into the sealed variant happens at the
      DatatableField dispatch. Unsafe `as? String` casts on the cell
      value are now defensive casts to the specific variant.
  - NewLoanAccountViewModel.kt
      State.datatableValues: Map<Int, Map<String, DatatableFieldValue>>
      Action.UpdateDatatableField.value: DatatableFieldValue
      buildDatatablePayloadMap now consumes the typed map.
      Replaced `coerceDatatableValue(Any, String?): Any` with
      `ColumnHeader.encodePayloadValue(DatatableFieldValue): Any`,
      which does both variant dispatch AND column-aware numeric
      coercion (INTEGER -> toIntOrNull, DECIMAL/FLOAT -> toDoubleOrNull)
      in one place.

Wire format unchanged — same primitives reach the POST /loans body.
Verified on device with product 1 (5 datatables, all categories of
column display type exercised).

Trade-offs considered:
  - Plain enum + side fields → reintroduces the unsafety we removed.
  - @JvmInline value classes on a sealed interface → boxed immediately
    when upcast to the interface type (the common case here), and
    Compose stability tracking is rougher with value classes. Plain
    data classes are correct.
Signed-off-by: thekalpeshpawar <pawarkalpesh@proton.me>
Signed-off-by: thekalpeshpawar <pawarkalpesh@proton.me>
@TheKalpeshPawar TheKalpeshPawar requested a review from a team May 25, 2026 16:42
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Refactors datatable flows: removes widget DTO plumbing, introduces typed per-cell values, reworks DataTable list UI/ViewModel to collect per-table field state and coerce payloads, and updates navigation/serialization and loan/client flows to support inline datatable editing.

Changes

A single coherent refactor touching serialization, navigation, DataTable list UI/VM, typed cell values, NewLoan integration, datatable step UI, and small supporting edits.

Developer checkpoints (expand)
  • Serialization & schema alignment
  • Navigation args & callback refactor
  • DataTableList UI and ViewModel field handling
  • Type-safe datatable cell values
  • NewLoan inline datatable steps & ViewModel integration
  • DatatableStepPage UI components
  • Supporting constants, routing fixes, and .gitignore
Datatable Field Editing Refactoring — developer checkpoints
Layer / File(s) Summary
Serialization and Schema Updates
core/database/.../ColumnHeader.kt, core/database/.../LoanTemplate.kt, core/network/.../LoansPayload.kt, core/network/.../GetDataTablesResponseMapper.kt
@SerialName annotations added; LoanTemplate.dataTables element made nullable; network mapper sets ColumnHeader.dataTableColumnName.
Navigation Callback Refactoring
feature/data-table/.../DataTableNavigation.kt, feature/data-table/.../DataTableListNavArgs.kt, feature/client/.../ClientNavigation.kt, cmp-navigation/.../AuthenticatedNavigation.kt
Removed legacy formWidget/FormWidgetDTO from navigation signatures; added polymorphic navArgJson serializer; updated client/authenticated navigation to call navigateDataTableList with new args.
DataTable List: UI & ViewModel
feature/data-table/.../DataTableListViewModel.kt, feature/data-table/.../DataTableListScreen.kt
ViewModel now stores per-table formValues and exposes actions for OnFieldChanged; screen wires onFieldChanged and renders stateful inputs; payload building centralized in buildPayloadMap/coerce.
Type-safe Datatable Field Value
feature/loan/.../DatatableFieldValue.kt
Adds sealed interface DatatableFieldValue with Text, Bool, Code variants used for typed cell state and submission.
New Loan: Datatable Step Pages & ViewModel Integration
feature/loan/.../DatatableStepPage.kt, feature/loan/.../NewLoanAccountScreen.kt, feature/loan/.../NewLoanAccountViewModel.kt
Adds DatatableStepPage composable; NewLoanAccountScreen builds dynamic steps per datatable; ViewModel tracks datatableValues, adds UpdateDatatableField action, and serializes datatable payloads (filters system columns, coerces numeric types).
DatatableStepPage UI components
feature/loan/.../DatatableStepPage.kt
Composable input rows for text, numeric, date, boolean, dropdown; helpers to filter system/primary-key columns and compute display labels.
Supporting Constants & Misc
feature/data-table/.../DataTableColumnType.kt, feature/loan/.../LoanAccountScreen.kt, .gitignore
Introduces DataTableColumnType constants, filters null datatables before routing in LoanAccountScreen, and ignores additional developer artifacts in .gitignore.

Sequence Diagram (high-level flow)

sequenceDiagram
  participant User
  participant NewLoanAccountScreen
  participant NavController
  participant DataTableListScreen
  participant DataTableListViewModel

  User->>NewLoanAccountScreen: tap Submit when loan has datatables
  NewLoanAccountScreen->>NavController: navigateDataTableList(dataTables, payload, requestType)
  NavController->>DataTableListScreen: open with encoded args
  DataTableListScreen->>DataTableListViewModel: initial state (data tables, payload)
  User->>DataTableListScreen: edit cell -> onFieldChanged(tableIdx,column,value)
  DataTableListScreen->>DataTableListViewModel: dispatch OnFieldChanged(tableIdx,column,value)
  DataTableListViewModel->>DataTableListViewModel: buildPayloadMap() -> create*Account() request
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • biplab1
  • niyajali
  • revanthkumarJ

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Jira Link And Before/After Sections ❌ Error PR description includes Jira link (MIFOSAC-706) and an asset, but lacks explicit "Before:" and "After:" sections in table format as required by the PR template for UI changes. Add a "Before" and "After" section with a table format (as shown in .github/PULL_REQUEST_TEMPLATE.md) containing screenshots/media demonstrating the UI changes for datatable steps.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title follows the required commit format (feat scope subject) and clearly describes the main change: dynamic datatable steps for new loan creation.
Description check ✅ Passed PR description references a Jira issue (MIFOSAC-706) and includes screenshots showing the UI implementation, which aligns with the changeset's datatable and loan-related modifications.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt (1)

892-916: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset datatable input state when loading a new product template.

In NewLoanAccountViewModel.kt, datatableValues is preserved across template reloads. Switching product can keep stale table-index/column values and submit incorrect datatable data. Clear datatableValues (and ideally reset step index) when productId changes/template loads.

Also applies to: 1080-1080

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

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt`
around lines 892 - 916, When loading a new product template in
loadLoanAccountTemplate, the ViewModel currently preserves stale datatableValues
(and step index) which can submit incorrect datatable data; update the
mutableStateFlow when handling DataState.Loading and DataState.Success to
clear/reset datatableValues and reset any step index (e.g., datatableValues =
emptyMap() and step index property such as currentStep or stepIndex = 0)
alongside existing updates to productId, isOverLayLoadingActive, and
loanTemplate so the state is fresh for the new template.
🧹 Nitpick comments (2)
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt (1)

625-627: ⚡ Quick win

Remove unnecessary ArrayList conversion.

The dataTable function accepts List<DataTableEntity>, and filterNotNull() already returns a List. The ArrayList wrapper on line 627 is redundant.

♻️ Suggested simplification
 val realDataTables = loanTemplate.dataTables.filterNotNull()
 if (realDataTables.isNotEmpty()) {
-    dataTable(ArrayList(realDataTables), loadPayload)
+    dataTable(realDataTables, loadPayload)
 } else {
     createLoanAccount(loadPayload)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt`
around lines 625 - 627, The code constructs an unnecessary ArrayList when
calling dataTable; remove the redundant conversion by passing the kotlin List
returned from loanTemplate.dataTables.filterNotNull() (realDataTables) directly
to the dataTable(...) call, i.e., replace dataTable(ArrayList(realDataTables),
loadPayload) with dataTable(realDataTables, loadPayload) so you call dataTable
with the existing List<DataTableEntity> from realDataTables.
feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt (1)

96-104: 🏗️ Heavy lift

Collapse the content-layer callbacks into a single onAction.

This change adds another top-level callback to a Layer-2 screen that already takes several separate lambdas. That makes the route/content boundary harder to maintain and keeps pushing this screen away from the repo's state + onAction contract. As per coding guidelines, "Layer 2 (Stateful/Content Composable — *Screen or *ScreenContent) MUST only take state ... and a single onAction lambda."

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

In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`
around lines 96 - 104, The DataTableListScreen currently accepts multiple
top-level callbacks (clientCreated, onSaveClicked, onFieldChanged,
onBackPressed) which violates the project's Layer-2 contract; replace these with
a single onAction: (DataTableListAction) -> Unit parameter on
DataTableListScreen and implement a sealed class or interface
DataTableListAction (e.g., BackPressed, ClientCreated(payload:
ClientPayloadEntity), SaveClicked, FieldChanged(tableIndex: Int, columnName:
String, value: Any)) to represent each event; update DataTableListScreen
usages/children to dispatch DataTableListAction values instead of calling the
separate lambdas and update callers to handle the single onAction instead of the
multiple callbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`:
- Around line 292-297: The date picker currently forces future-only selection in
the rememberDatePickerState by supplying a SelectableDates implementation whose
isSelectableDate returns utcTimeMillis >=
Clock.System.now().toEpochMilliseconds(), which blocks valid past dates for
schema-driven fields; update the logic in DataTableListScreen where
rememberDatePickerState is created (and where selectedDate is passed) to stop
enforcing a global future-only rule — either remove the selectableDates
parameter or make it conditional based on the datatable column metadata (e.g., a
futureOnly flag from the column schema) so past dates remain selectable for
columns that are not explicitly future-only.
- Around line 141-146: The current composition calls onBackPressed() outside the
LaunchedEffect (when uiState.message != null), which can trigger premature
navigation; move the onBackPressed() call into the LaunchedEffect that displays
the snackbar so navigation happens after snackBarHostState.showSnackbar
completes. Locate the block using uiState.message, the LaunchedEffect(key1 =
message) and snackBarHostState.showSnackbar(message) and invoke onBackPressed()
inside that LaunchedEffect after showSnackbar returns (or after handling its
result) so the back navigation runs as a side-effect, not during composition.
- Around line 256-279: The dropdown currently passes the visible label to
onFieldChanged; change it so UI still displays columnHeader.columnValues.map {
it.value } but when an option is selected call onFieldChanged(name, selectedId)
where selectedId = columnHeader.columnValues[index].id (use
onOptionSelected(index, item) instead of ignoring the index), and set
selectedValue to the display label; also when initializing selectedValue
(selectedValue by rememberSaveable...) if there is an existing form value id,
resolve it to the matching columnHeader.columnValues.find { it.id == existingId
}?.value so the label shows while the payload stores the id. Ensure you update
the MifosTextFieldDropdown handlers (value/onValueChanged/onOptionSelected) and
references to selectedValue, columnHeader.columnValues, MifosTextFieldDropdown,
and onFieldChanged accordingly.

In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt`:
- Around line 149-173: The buildPayloadMap/coerce logic currently converts
unparsable numeric strings into 0/0.0 which incorrectly serializes cleared
optional fields; change coerce to return null for empty or unparsable numeric
strings (e.g., if value is "" or toIntOrNull()/toDoubleOrNull() returns null)
and update buildPayloadMap to only put entries when coerce(...) != null (use the
ColumnHeader.dataTableColumnName and header.columnDisplayType to locate the
field); this will omit cleared numeric inputs instead of serializing them as
zero.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt`:
- Line 157: Replace the hardcoded fallback title "Datatable ${idx + 1}" with a
localized formatted string resource and use it in the Step call: change the
expression inside Step(...) where table.registeredTableName ?: "Datatable ${idx
+ 1}" is used to fetch a string resource (with a single integer format argument)
and pass (idx + 1) as the format parameter; add the corresponding localized
string resource (e.g., "datatable_title" = "Datatable %d") and ensure the code
calls the platform-appropriate string lookup (e.g., stringResource/getString or
the project’s i18n helper) so Step(...) receives the localized fallback when
table.registeredTableName is null.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt`:
- Around line 271-329: The payload assembly in NewLoanAccountViewModel uses
direct indexing like
state.loanTemplate?.loanOfficerOptions[state.loanOfficerIndex]?.id (and similar
for repaymentFrequencyDaysOfWeekType, repaymentFrequencyNthDayType,
loanPurposeOptions, fundOptions, accountLinkingOptions,
transactionProcessingStrategyOptions, etc.) after only checking for -1, which
can still throw IndexOutOfBoundsException if the options list changed; fix by
replacing direct indexing with safe access using getOrNull(e.g.
state.loanTemplate?.loanOfficerOptions?.getOrNull(state.loanOfficerIndex)?.id)
for every place that currently does options[index] so each optional list access
returns null instead of crashing and keep the existing -1 checks or remove them
if redundant.
- Around line 1229-1232: The current mapping in the DatatableFieldValue.Text
branch silently converts invalid numeric strings to 0/0.0 (see
DatatableFieldValue.Text, columnDisplayType and value.text), which can corrupt
data; change the INTEGER/DECIMAL/FLOAT arms to stop coercing to zero and instead
return a null/absent value or propagate a validation error (e.g., return null or
throw a controlled ValidationException) so the caller or NewLoanAccountViewModel
can surface an error or omit the field; update any callers to handle the
null/validation result accordingly.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableStepPage.kt`:
- Around line 274-277: DatatableStepPage currently hardcodes the DatePicker
action labels ("OK" and "Cancel"); update the Text/TextButton usages inside the
DatePicker dialog (in DatatableStepPage, where showDatePicker is toggled) to use
localized string resources instead of hardcoded literals, add corresponding
entries for these keys in the project's strings (e.g., OK and Cancel
translations), and replace the literals with calls to the platform's string
lookup (stringResource or equivalent) so the Text composables and TextButton
onClick labels pull from resources for translation consistency.
- Around line 319-325: The code creates options by dropping null labels but
leaves ids intact (variables options, ids, currentDisplay), causing index
misalignment; fix by building a single filtered list of pairs from
header.columnValues (e.g., mapNotNull { cv -> cv.value?.let { v -> cv.id to v }
}) and derive both ids and labels from that filteredPairs so indices always
match, then update the remember block (currentDisplay) to use filteredPairs for
lookup; apply the same change to the other occurrence that also constructs
options/ids (the second block around the dropdown logic).
- Around line 285-286: DatatableStepPage is rendering a fallback date via `value
= currentValue ?: DateHelper.getDateAsStringFromLong(selectedMillis)`, which
shows a date even when `currentValue` is null; change this so the UI renders
empty when no value exists (e.g., use an empty string fallback) or explicitly
initialize the state when the page is first shown. Update the logic around
`currentValue` and `selectedMillis` in DatatableStepPage: either set
`currentValue` from `DateHelper.getDateAsStringFromLong(selectedMillis)` at
initialization (so the field is truly stored) or keep `currentValue` nullable
and use `value = currentValue ?: ""` (and ensure submit logic treats empty
string as no-value). Ensure the change touches the same symbols (`currentValue`,
`selectedMillis`, and `DateHelper.getDateAsStringFromLong`) so the renderer and
submit path remain consistent.

---

Outside diff comments:
In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt`:
- Around line 892-916: When loading a new product template in
loadLoanAccountTemplate, the ViewModel currently preserves stale datatableValues
(and step index) which can submit incorrect datatable data; update the
mutableStateFlow when handling DataState.Loading and DataState.Success to
clear/reset datatableValues and reset any step index (e.g., datatableValues =
emptyMap() and step index property such as currentStep or stepIndex = 0)
alongside existing updates to productId, isOverLayLoadingActive, and
loanTemplate so the state is fresh for the new template.

---

Nitpick comments:
In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`:
- Around line 96-104: The DataTableListScreen currently accepts multiple
top-level callbacks (clientCreated, onSaveClicked, onFieldChanged,
onBackPressed) which violates the project's Layer-2 contract; replace these with
a single onAction: (DataTableListAction) -> Unit parameter on
DataTableListScreen and implement a sealed class or interface
DataTableListAction (e.g., BackPressed, ClientCreated(payload:
ClientPayloadEntity), SaveClicked, FieldChanged(tableIndex: Int, columnName:
String, value: Any)) to represent each event; update DataTableListScreen
usages/children to dispatch DataTableListAction values instead of calling the
separate lambdas and update callers to handle the single onAction instead of the
multiple callbacks.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt`:
- Around line 625-627: The code constructs an unnecessary ArrayList when calling
dataTable; remove the redundant conversion by passing the kotlin List returned
from loanTemplate.dataTables.filterNotNull() (realDataTables) directly to the
dataTable(...) call, i.e., replace dataTable(ArrayList(realDataTables),
loadPayload) with dataTable(realDataTables, loadPayload) so you call dataTable
with the existing List<DataTableEntity> from realDataTables.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b2125ba5-36c0-4660-9733-5af19ebabfd3

📥 Commits

Reviewing files that changed from the base of the PR and between ca68ef2 and 50def2a.

📒 Files selected for processing (17)
  • .gitignore
  • cmp-navigation/src/commonMain/kotlin/cmp/navigation/authenticated/AuthenticatedNavigation.kt
  • core/database/src/commonMain/kotlin/com/mifos/room/entities/noncore/ColumnHeader.kt
  • core/database/src/commonMain/kotlin/com/mifos/room/entities/templates/loans/LoanTemplate.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/dataTable/GetDataTablesResponseMapper.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/LoansPayload.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableColumnType.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListNavArgs.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/navigation/DataTableNavigation.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableFieldValue.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableStepPage.kt
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/{core-base,core}/**/*.kt

⚙️ CodeRabbit configuration file

**/{core-base,core}/**/*.kt: Critical Module Change Detection:

  • Changes in core-base module must be treated as high-impact.
  • Flag any PR that modifies files inside core-base for careful review.
  • Verify that modifications in core-base are necessary and minimal.
    Output:
  • Clearly highlight that core-base is a shared foundational module and requires extra review attention.

Files:

  • core/database/src/commonMain/kotlin/com/mifos/room/entities/noncore/ColumnHeader.kt
  • core/database/src/commonMain/kotlin/com/mifos/room/entities/templates/loans/LoanTemplate.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/dataTable/GetDataTablesResponseMapper.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/LoansPayload.kt
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: Additional Code Review Guidelines:

  1. Null Safety & Stability
  • Avoid using !! operator
  • Handle null cases explicitly using safe calls or proper state handling
  • Do not assume values are always non-null without guarantees
  1. Architecture Boundaries
  • ViewModel must not depend on specific network/library implementations
  • Ensure proper separation between data, domain, and presentation layers
  • Do not format data (currency, dates, calculations) inside the UI layer
  • All formatting must be handled in the ViewModel and exposed via state (e.g., StateFlow)
  1. Performance Considerations
  • Avoid unnecessary recompositions in Compose
  • Do not attach heavy logic to frequently changing states (e.g., scrollState)
  • Prefer lifting state up instead of recomputing in child composables
  1. Compose & Navigation Best Practices
  • NEVER trigger navigation functions or side-effects directly during composition
  • Always wrap navigation calls inside LaunchedEffect or EventsEffect to avoid repeated execution on recomposition
  • Avoid triggering intensive side-effects during recomposition
  • Navigation routes must be type-safe.
  • Ensure all route classes or objects used for navigation are annotated with @Serializable.
  1. UI Structure
  • Dialogs must be separated into their own composables
  • Do not embed dialogs inline within complex main screens
  1. Localization Consistency
  • Ensure all supported languages are updated consistently across modules
  • Verify translations exist for newly added UI strings
  1. Code Cleanliness
  • Avoid unnecessary inline comments unless critical
  • Remove leftover debug or commented code
  1. Focus on correctness, readability, and maintainability over cosmetic nitpicks.
  • Avoid reviewing README, config, or asset files.
  • Prioritize identifying bugs, performance issues, and architectural concerns.
  1. Naming & Intent Rules:

Files:

  • core/database/src/commonMain/kotlin/com/mifos/room/entities/noncore/ColumnHeader.kt
  • core/database/src/commonMain/kotlin/com/mifos/room/entities/templates/loans/LoanTemplate.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableColumnType.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableFieldValue.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListNavArgs.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/dataTable/GetDataTablesResponseMapper.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/navigation/DataTableNavigation.kt
  • cmp-navigation/src/commonMain/kotlin/cmp/navigation/authenticated/AuthenticatedNavigation.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableStepPage.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/LoansPayload.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt
**/*Screen.kt

⚙️ CodeRabbit configuration file

**/*Screen.kt: Screen architecture rules:

Each screen must follow a 2-layer structure:

  1. Layer 1 (Entry/Route Composable):

    • Function Name: Typically *ScreenRoute or the entry-point composable.
    • Responsibilities: Inject ViewModel (e.g., koinViewModel), take navigation lambdas, collect the StateFlow, and handle ViewModel events.
    • Logic: Should only handle ViewModel interaction, state collection, and triggering navigation in response to ViewModel events.
  2. Layer 2 (Stateful/Content Composable — *Screen or *ScreenContent):

    • Parameters: MUST only take state (the UI state object) and a single onAction lambda (e.g., onAction: (FeatureAction) -> Unit).
    • Responsibilities: Render the UI based strictly on the provided state.
    • Rule: MUST NOT pass multiple separate lambda functions for different UI interactions; consolidate them into the single onAction.
    • Constraint: Must NOT contain any business logic or ViewModel/Navigation references.

Internal/private helper composables (e.g., dialogs, sections, sub-components):

  • These are NOT subject to the single onAction rule.
  • They may accept specific, focused lambdas (e.g., onRetry: () -> Unit) or a narrowed onAction as appropriate.
  • However, they must NOT be passed the ViewModel or navigation controllers directly.

UI consistency:

  • Avoid hardcoded values (dp, sp, padding, fontSize, colors)
  • Use DesignToken, KptTheme, AppColors, MifosTypography for spacing, typography, and colors

Code quality:

  • Keep Composables small and readable
  • Avoid deeply nested UI

Flag:

  • Missing Layer 2 (*Screen/*ScreenContent) separation from the entry-point composable
  • Layer 2 (*Screen/*ScreenContent) receiving multiple separate lambdas instead of a single onAction
  • UI logic inside the entry-point composable
  • Business logic inside any Composable
  • Hardcoded strings instead of using string resources
  • Hardcoded dp/sp values
  • Direct styling instead of using DesignToken or KptTheme

Files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
**/network/**/*.kt

⚙️ CodeRabbit configuration file

**/network/**/*.kt: Network Layer API Guidelines:

  • One-shot API calls MUST use suspend, return raw response T, and MUST NOT use Flow or DataState.
  • Streaming APIs MUST return Flow<T> and MUST NOT use suspend or DataState.

Files:

  • core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/dataTable/GetDataTablesResponseMapper.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/LoansPayload.kt
**/*ViewModel.kt

⚙️ CodeRabbit configuration file

**/*ViewModel.kt: MVI architecture rules:

  • All new features must follow MVI
  • ViewModel must extend BaseViewModel
  • The ViewModel MUST maintain a single UI state (e.g., a single StateFlow)
    instead of multiple separate state variables.

Required:

  • Use *State, *Event, *Action
  • Naming must be consistent (FeatureViewModel, FeatureState, FeatureEvent, FeatureAction)
  • Follow unidirectional flow:
    Action → ViewModel → State → UI

Internal reducer/action architecture rules:

  • Async operations MUST NOT directly mutate UI state repeatedly inside
    Flow collectors, suspend callbacks, or repository result handlers.

  • Repository/network/database results MUST be converted into internal
    actions using sendAction(...).

  • handleAction(...) must act as the primary reducer responsible for:

    • state mutation
    • reducer-style state transitions
    • triggering follow-up actions
  • Large async methods must be split into:

    1. async collection layer
    2. internal action dispatching
    3. reducer/state handling
  • Avoid directly calling another business/data-loading method from
    repository collectors or async callbacks.
    Prefer dispatching follow-up internal actions instead.

Preferred pattern:
repository result
-> sendAction(...)
-> handleAction(...)
-> mutableStateFlow.update { ... }

Anti-pattern examples:
mutableStateFlow.update { ... } inside collect { }
fetchX() -> directly calls fetchY() inside async collector
large methods mixing:
- collection
- state mutation
- navigation
- business chaining

Flag:

  • Multiple mutableStateFlow.update {} calls inside collect { }
  • Direct state mutation inside async repository callbacks
  • Async methods performing both collection and reducer logic
  • Direct business-flow chaining from async collectors
  • Missing internal reducer actions for async results
  • Missing *State / *Event / *Action
  • ViewModel not...

Files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt
🧠 Learnings (3)
📚 Learning: 2026-02-06T13:15:16.968Z
Learnt from: sahilshivekar
Repo: openMF/android-client PR: 2603
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt:43-106
Timestamp: 2026-02-06T13:15:16.968Z
Learning: Guideline: When a Kotlin function parameter is nullable (e.g., balance: Double?, currencyCode: String?, maximumFractionDigits: Int?) and downstream calls require a non-null value, add null-safety handling in all implementations. Specifically, avoid calling Currency.getInstance(currencyCode) with a possibly null currencyCode; provide a safe default (e.g., currencyCode ?: "$") or validate before use. Ensure all platform targets (Android/Desktop/Native) follow consistent null handling, and consider centralizing currencyCode normalization in the common layer if feasible. Add tests covering null currencyCode to prevent NPEs.

Applied to files:

  • core/database/src/commonMain/kotlin/com/mifos/room/entities/noncore/ColumnHeader.kt
  • core/database/src/commonMain/kotlin/com/mifos/room/entities/templates/loans/LoanTemplate.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableColumnType.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableFieldValue.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListNavArgs.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/dataTable/GetDataTablesResponseMapper.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/navigation/DataTableNavigation.kt
  • cmp-navigation/src/commonMain/kotlin/cmp/navigation/authenticated/AuthenticatedNavigation.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableStepPage.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/LoansPayload.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt
📚 Learning: 2026-04-01T05:03:14.323Z
Learnt from: kartikey004
Repo: openMF/mifos-x-field-officer-app PR: 2659
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanDisbursement/LoanAccountDisbursementScreen.kt:190-195
Timestamp: 2026-04-01T05:03:14.323Z
Learning: In this repo, existing `SelectableDates` / `SelectableDates.isSelectableDate(utcTimeMillis: Long)` implementations use `Clock.System.now().toEpochMilliseconds()` (UTC epoch millis) for date-boundary checks. During PR reviews, do not flag these checks for not using a timezone-aware `LocalDate` start-of-day approach; treat it as an established project-wide pattern. If a change is desired, handle it as a coordinated project-wide improvement rather than as a per-PR review issue.

Applied to files:

  • core/database/src/commonMain/kotlin/com/mifos/room/entities/noncore/ColumnHeader.kt
  • core/database/src/commonMain/kotlin/com/mifos/room/entities/templates/loans/LoanTemplate.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccount/LoanAccountScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableColumnType.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableFieldValue.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListNavArgs.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/dataTable/GetDataTablesResponseMapper.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/navigation/DataTableNavigation.kt
  • cmp-navigation/src/commonMain/kotlin/cmp/navigation/authenticated/AuthenticatedNavigation.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableStepPage.kt
  • core/network/src/commonMain/kotlin/com/mifos/core/network/model/LoansPayload.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt
📚 Learning: 2026-02-16T08:37:28.351Z
Learnt from: kartikey004
Repo: openMF/android-client PR: 2610
File: feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientIdentifiersList/ClientIdentifiersListScreen.kt:60-60
Timestamp: 2026-02-16T08:37:28.351Z
Learning: In Kotlin files under the android-client module, replace MaterialTheme references with the established KptTheme import: import template.core.base.designsystem.theme.KptTheme. This should be applied consistently across files where KptTheme is used, replacing any MaterialTheme imports with the correct KptTheme import path.

Applied to files:

  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt
🔇 Additional comments (11)
feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableColumnType.kt (1)

1-33: LGTM!

.gitignore (1)

101-107: LGTM!

feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableFieldValue.kt (1)

28-34: LGTM!

core/database/src/commonMain/kotlin/com/mifos/room/entities/noncore/ColumnHeader.kt (1)

14-15: LGTM!

Also applies to: 39-40, 44-45, 47-48

core/database/src/commonMain/kotlin/com/mifos/room/entities/templates/loans/LoanTemplate.kt (1)

43-44: LGTM!

Also applies to: 121-122

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

13-14: LGTM!

Also applies to: 48-49

core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/dataTable/GetDataTablesResponseMapper.kt (1)

36-37: LGTM!

feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt (1)

118-119: LGTM!

Also applies to: 128-129, 523-525, 533-534

feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListNavArgs.kt (1)

16-24: LGTM!

cmp-navigation/src/commonMain/kotlin/cmp/navigation/authenticated/AuthenticatedNavigation.kt (1)

16-17: LGTM!

Also applies to: 22-23, 168-176

feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/navigation/DataTableNavigation.kt (1)

134-142: Polymorphic nav args: encode/decode Json config is symmetric
navigateDataTableList encodes DataTableListNavArgs with navArgJson using polymorphic(Any::class) and the LoansPayload/GroupLoanPayload/ClientPayloadEntity subclasses in DataTableNavigation.kt. DataTableListViewModel decodes with its own json configured with the exact same polymorphic module before calling json.decodeFromString<DataTableListNavArgs>(...) in DataTableListViewModel.kt.

)
realDataTables.forEachIndexed { idx, table ->
add(
Step(table.registeredTableName ?: "Datatable ${idx + 1}") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace hardcoded datatable fallback title with a string resource.

In NewLoanAccountScreen.kt, "Datatable ${idx + 1}" should use a localized resource (with format arg) to keep translations consistent.

As per coding guidelines, "Flag: Hardcoded strings instead of using string resources."

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

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountScreen.kt`
at line 157, Replace the hardcoded fallback title "Datatable ${idx + 1}" with a
localized formatted string resource and use it in the Step call: change the
expression inside Step(...) where table.registeredTableName ?: "Datatable ${idx
+ 1}" is used to fetch a string resource (with a single integer format argument)
and pass (idx + 1) as the format parameter; add the corresponding localized
string resource (e.g., "datatable_title" = "Datatable %d") and ensure the code
calls the platform-appropriate string lookup (e.g., stringResource/getString or
the project’s i18n helper) so Step(...) receives the localized fallback when
table.registeredTableName is null.

@biplab1

biplab1 commented May 26, 2026

Copy link
Copy Markdown
Contributor

@TheKalpeshPawar Please address the CodeRabbitAI suggestions relevant to this PR.

@biplab1

biplab1 commented May 26, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai Improve PR title based on this: https://github.com/openMF/mifos-pay/wiki/Commit-style-guide.

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the commit style guide wiki page
curl -s "https://github.com/openMF/mifos-pay/wiki/Commit-style-guide" | python3 -c "
import sys, re
html = sys.stdin.read()
# Extract text between wiki-body div
match = re.search(r'<div[^>]*class=\"[^\"]*markdown-body[^\"]*\"[^>]*>(.*?)</div>', html, re.DOTALL)
if match:
    text = match.group(1)
    # Strip HTML tags
    text = re.sub(r'<[^>]+>', '', text)
    # Clean up whitespace
    text = re.sub(r'\n{3,}', '\n\n', text)
    print(text.strip())
else:
    # Try to find any substantial text
    text = re.sub(r'<[^>]+>', '', html)
    text = re.sub(r'\n{3,}', '\n\n', text)
    # Print first 3000 chars
    print(text[:3000])
"

Length of output: 2380


🏁 Script executed:

#!/bin/bash
gh pr edit 2693 --repo openMF/mifos-x-field-officer-app \
  --title "feat(loan): implement dynamic datatable steps for new loan creation"

Length of output: 691



@biplab1 The automated title update was blocked due to workflow restriction settings. Please update the PR title manually to:

feat(loan): implement dynamic datatable steps for new loan creation

Why this title?
Element Current Improved Reason
Type enhancement feat enhancement is not in the valid type list; feat = new API/behavior for end user
Scope (missing) loan Primary module affected is feature/loan
Subject Implement the Dynamic Data Table Entity for new loan creation implement dynamic datatable steps for new loan creation Must be present tense, lower case per the style guide

@biplab1 biplab1 changed the title (enhancement): Implement the Dynamic Data Table Entity for new loan creation. feat(loan): implement dynamic datatable steps for new loan creation May 26, 2026
Signed-off-by: thekalpeshpawar <pawarkalpesh@proton.me>
- Drop future-only date restriction so schema-driven date columns accept past dates
- Omit empty/unparsable numeric datatable fields from payload instead of sending 0/0.0
- Render empty date field when no value is set rather than a default fallback date
- Build index-aligned id/label pairs for code dropdowns to avoid mismatched selections
- Localize date picker action labels

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt (3)

281-288: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't render today's date before the user picks one.

This field looks prefilled from the start, but nothing is written to the payload until the confirm action calls onFieldChanged(...). DataTableListViewModel.kt only serializes fields present in formValues, so an untouched date can appear filled in the UI and still be omitted from submission.

Suggested fix
-                        var selectedDate by rememberSaveable(name) {
-                            mutableLongStateOf(
-                                Clock.System.now().toEpochMilliseconds(),
-                            )
-                        }
+                        var selectedDate by rememberSaveable(name) { mutableStateOf<Long?>(null) }
                         val datePickerState = rememberDatePickerState(
                             initialSelectedDateMillis = selectedDate,
                         )
@@
                         MifosDatePickerTextField(
-                            value = DateHelper.getDateAsStringFromLong(selectedDate),
+                            value = selectedDate?.let(DateHelper::getDateAsStringFromLong).orEmpty(),
                             label = name,
                             openDatePicker = {
                                 showDatePicker = true
                             },
                         )

Also applies to: 299-304, 321-323

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

In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`
around lines 281 - 288, The UI pre-fills the date by initializing `selectedDate`
to today's millis and passing it to `rememberDatePickerState`, causing the field
to appear filled even if `onFieldChanged(...)` was never called; change
`selectedDate` to a nullable `Long?` (start null via `rememberSaveable`), pass
null (or omit initial date) to `rememberDatePickerState` so the picker shows no
selection initially, and only set `selectedDate` and call `onFieldChanged(...)`
when the user confirms; update the other similar blocks that use `selectedDate`,
`rememberSaveable`, and `rememberDatePickerState` (the occurrences mentioned) to
follow the same nullable/empty-initialization pattern.

231-245: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use KeyboardType.Decimal for DECIMAL/FLOAT columns.
In DataTableListScreen.kt, the numeric branch sets keyboardType = KeyboardType.Number for INTEGER, DECIMAL, and FLOAT, which can block decimal separator input on many keyboards.

Suggested fix
                         MifosOutlinedTextField(
                             value = value,
                             onValueChange = {
                                 value = it
                                 onFieldChanged(name, it)
                             },
                             label = name,
-                            keyboardType = KeyboardType.Number,
+                            keyboardType = when (columnHeader.columnDisplayType) {
+                                DataTableColumnType.INTEGER -> KeyboardType.Number
+                                else -> KeyboardType.Decimal
+                            },
                             modifier = Modifier
                                 .fillMaxWidth()
                                 .padding(vertical = KptTheme.spacing.sm),
                         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`
around lines 231 - 245, The numeric branch handling DataTableColumnType in
DataTableListScreen's column rendering currently sets keyboardType =
KeyboardType.Number for INTEGER, DECIMAL, and FLOAT; change it so INTEGER
continues to use KeyboardType.Number but DECIMAL and FLOAT use
KeyboardType.Decimal by branching on the enum (e.g., when
DataTableColumnType.DECIMAL or FLOAT) and set keyboardType accordingly on the
MifosOutlinedTextField instance (the value/onValueChange/label/keyboardType
props in the DataTableListScreen rendering).

136-138: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix clientCreated(client) being invoked during composition in DataTableListScreen

clientCreated(client) is called directly inside the DataTableListUiState.Success composable branch, so it can re-run on recomposition. Wrap it in a side-effect (e.g., LaunchedEffect) instead.

Suggested fix
                 is DataTableListUiState.Success -> {
                     uiState.client?.let { client ->
-                        clientCreated(client)
+                        LaunchedEffect(client) {
+                            clientCreated(client)
+                        }
                     } ?: run {
                         if (uiState.message != null) {
                             val message = stringResource(uiState.message)
                             LaunchedEffect(key1 = message) {
                                 snackBarHostState.showSnackbar(message)
                                 onBackPressed()
                             }
                         }
                     }
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`
around lines 136 - 138, In DataTableListScreen, avoid invoking
clientCreated(client) directly during composition (inside the
DataTableListUiState.Success branch); instead wrap the call in a Compose
side-effect such as LaunchedEffect keyed by the client (e.g., client.id or the
client object) so the side-effect runs only when the client value changes;
update the branch where DataTableListUiState.Success is handled to call
LaunchedEffect(...) { clientCreated(client) } rather than calling
clientCreated(client) directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`:
- Around line 281-288: The UI pre-fills the date by initializing `selectedDate`
to today's millis and passing it to `rememberDatePickerState`, causing the field
to appear filled even if `onFieldChanged(...)` was never called; change
`selectedDate` to a nullable `Long?` (start null via `rememberSaveable`), pass
null (or omit initial date) to `rememberDatePickerState` so the picker shows no
selection initially, and only set `selectedDate` and call `onFieldChanged(...)`
when the user confirms; update the other similar blocks that use `selectedDate`,
`rememberSaveable`, and `rememberDatePickerState` (the occurrences mentioned) to
follow the same nullable/empty-initialization pattern.
- Around line 231-245: The numeric branch handling DataTableColumnType in
DataTableListScreen's column rendering currently sets keyboardType =
KeyboardType.Number for INTEGER, DECIMAL, and FLOAT; change it so INTEGER
continues to use KeyboardType.Number but DECIMAL and FLOAT use
KeyboardType.Decimal by branching on the enum (e.g., when
DataTableColumnType.DECIMAL or FLOAT) and set keyboardType accordingly on the
MifosOutlinedTextField instance (the value/onValueChange/label/keyboardType
props in the DataTableListScreen rendering).
- Around line 136-138: In DataTableListScreen, avoid invoking
clientCreated(client) directly during composition (inside the
DataTableListUiState.Success branch); instead wrap the call in a Compose
side-effect such as LaunchedEffect keyed by the client (e.g., client.id or the
client object) so the side-effect runs only when the client value changes;
update the branch where DataTableListUiState.Success is handled to call
LaunchedEffect(...) { clientCreated(client) } rather than calling
clientCreated(client) directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ea1b74f9-9f29-4761-b16e-79bdb7e62878

📥 Commits

Reviewing files that changed from the base of the PR and between 50def2a and 3c95c77.

📒 Files selected for processing (4)
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableStepPage.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/pages/DatatableStepPage.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/newLoanAccount/NewLoanAccountViewModel.kt
📜 Review details
⏰ 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 context used
📓 Path-based instructions (2)
**/*Screen.kt

⚙️ CodeRabbit configuration file

**/*Screen.kt: Screen architecture rules:

Each screen must follow a 2-layer structure:

  1. Layer 1 (Entry/Route Composable):

    • Function Name: Typically *ScreenRoute or the entry-point composable.
    • Responsibilities: Inject ViewModel (e.g., koinViewModel), take navigation lambdas, collect the StateFlow, and handle ViewModel events.
    • Logic: Should only handle ViewModel interaction, state collection, and triggering navigation in response to ViewModel events.
  2. Layer 2 (Stateful/Content Composable — *Screen or *ScreenContent):

    • Parameters: MUST only take state (the UI state object) and a single onAction lambda (e.g., onAction: (FeatureAction) -> Unit).
    • Responsibilities: Render the UI based strictly on the provided state.
    • Rule: MUST NOT pass multiple separate lambda functions for different UI interactions; consolidate them into the single onAction.
    • Constraint: Must NOT contain any business logic or ViewModel/Navigation references.

Internal/private helper composables (e.g., dialogs, sections, sub-components):

  • These are NOT subject to the single onAction rule.
  • They may accept specific, focused lambdas (e.g., onRetry: () -> Unit) or a narrowed onAction as appropriate.
  • However, they must NOT be passed the ViewModel or navigation controllers directly.

UI consistency:

  • Avoid hardcoded values (dp, sp, padding, fontSize, colors)
  • Use DesignToken, KptTheme, AppColors, MifosTypography for spacing, typography, and colors

Code quality:

  • Keep Composables small and readable
  • Avoid deeply nested UI

Flag:

  • Missing Layer 2 (*Screen/*ScreenContent) separation from the entry-point composable
  • Layer 2 (*Screen/*ScreenContent) receiving multiple separate lambdas instead of a single onAction
  • UI logic inside the entry-point composable
  • Business logic inside any Composable
  • Hardcoded strings instead of using string resources
  • Hardcoded dp/sp values
  • Direct styling instead of using DesignToken or KptTheme

Files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: Additional Code Review Guidelines:

  1. Null Safety & Stability
  • Avoid using !! operator
  • Handle null cases explicitly using safe calls or proper state handling
  • Do not assume values are always non-null without guarantees
  1. Architecture Boundaries
  • ViewModel must not depend on specific network/library implementations
  • Ensure proper separation between data, domain, and presentation layers
  • Do not format data (currency, dates, calculations) inside the UI layer
  • All formatting must be handled in the ViewModel and exposed via state (e.g., StateFlow)
  1. Performance Considerations
  • Avoid unnecessary recompositions in Compose
  • Do not attach heavy logic to frequently changing states (e.g., scrollState)
  • Prefer lifting state up instead of recomputing in child composables
  1. Compose & Navigation Best Practices
  • NEVER trigger navigation functions or side-effects directly during composition
  • Always wrap navigation calls inside LaunchedEffect or EventsEffect to avoid repeated execution on recomposition
  • Avoid triggering intensive side-effects during recomposition
  • Navigation routes must be type-safe.
  • Ensure all route classes or objects used for navigation are annotated with @Serializable.
  1. UI Structure
  • Dialogs must be separated into their own composables
  • Do not embed dialogs inline within complex main screens
  1. Localization Consistency
  • Ensure all supported languages are updated consistently across modules
  • Verify translations exist for newly added UI strings
  1. Code Cleanliness
  • Avoid unnecessary inline comments unless critical
  • Remove leftover debug or commented code
  1. Focus on correctness, readability, and maintainability over cosmetic nitpicks.
  • Avoid reviewing README, config, or asset files.
  • Prioritize identifying bugs, performance issues, and architectural concerns.
  1. Naming & Intent Rules:

Files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
🧠 Learnings (2)
📚 Learning: 2026-02-06T13:15:16.968Z
Learnt from: sahilshivekar
Repo: openMF/android-client PR: 2603
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt:43-106
Timestamp: 2026-02-06T13:15:16.968Z
Learning: Guideline: When a Kotlin function parameter is nullable (e.g., balance: Double?, currencyCode: String?, maximumFractionDigits: Int?) and downstream calls require a non-null value, add null-safety handling in all implementations. Specifically, avoid calling Currency.getInstance(currencyCode) with a possibly null currencyCode; provide a safe default (e.g., currencyCode ?: "$") or validate before use. Ensure all platform targets (Android/Desktop/Native) follow consistent null handling, and consider centralizing currencyCode normalization in the common layer if feasible. Add tests covering null currencyCode to prevent NPEs.

Applied to files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
📚 Learning: 2026-04-01T05:03:14.323Z
Learnt from: kartikey004
Repo: openMF/mifos-x-field-officer-app PR: 2659
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanDisbursement/LoanAccountDisbursementScreen.kt:190-195
Timestamp: 2026-04-01T05:03:14.323Z
Learning: In this repo, existing `SelectableDates` / `SelectableDates.isSelectableDate(utcTimeMillis: Long)` implementations use `Clock.System.now().toEpochMilliseconds()` (UTC epoch millis) for date-boundary checks. During PR reviews, do not flag these checks for not using a timezone-aware `LocalDate` start-of-day approach; treat it as an established project-wide pattern. If a change is desired, handle it as a coordinated project-wide improvement rather than as a per-PR review issue.

Applied to files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
🔇 Additional comments (1)
feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt (1)

251-272: LGTM!

@biplab1 biplab1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please address the following suggestions.

@biplab1

biplab1 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Please attach the screenshot of web-app screen showing the data table associated with the loan account.

Fold the separate ui-state, data-table-list, form-values and user-status flows
into a single DataTableListState exposed via stateFlow, move navigation and
client-created results to one-shot events, and drive field updates and save
through handleAction. DataTableListScreen now consumes stateFlow/eventFlow and
dispatches via trySendAction. Removes the standalone DataTableListUiState.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt`:
- Around line 114-119: DataTableListState now owns formValues but the UI only
receives dataTableList, causing controls (e.g., in TableColumnHeader) to use
local defaults; update DataTableListContent to accept and forward
state.formValues (add a formValues parameter) and propagate those values to
TableColumnHeader so each control reads its value from formValues rather than
inventing defaults; adjust the call sites that create DataTableListContent
(including the other occurrence at the 131-135 region) and update
TableColumnHeader to derive its control values from the passed formValues map
for each table/column.
- Around line 77-85: The event collector captures a stale state.userStatus and
some UI inputs (TableColumnHeader) use local rememberSaveable defaults instead
of the reducer-owned DataTableListState.formValues; fix by using
rememberUpdatedState(state.userStatus) (or snapshotFlow) inside
DataTableListScreen and call clientCreated with that updated reference when
handling DataTableListEvent.ClientCreated, and change TableColumnHeader and
other editable controls to render their value directly from
DataTableListState.formValues (remove “now”/local rememberSaveable
initializers), accept value/onValueChange from state and dispatch updates to the
reducer so the submitted payload always matches the displayed UI.

In
`@feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt`:
- Around line 83-89: The collectors in DataTableListViewModel (e.g., the
prefManager.userInfo.map { ... }.collect block, the two repository collect { }
blocks, and createClient callbacks) must not call mutableStateFlow.update or
sendEvent directly; instead define the suggested sealed DataTableListAction
variants (UserStatusLoaded, LoanCreateStateChanged, GroupLoanCreateStateChanged,
ClientCreateSucceeded, ClientCreateFailed) and convert collected results into
those action instances (e.g., prefManager.userInfo.map {
DataTableListAction.UserStatusLoaded(it.userStatus) } ) and call
sendAction/collect(::sendAction) from the collectors; then move all state
mutations and event sends into handleAction(...) so it becomes the single
reducer for updating mutableStateFlow and emitting events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8c6e8d2d-f480-4a1f-9294-81eb9a3492bd

📥 Commits

Reviewing files that changed from the base of the PR and between 9ccb162 and 12600c9.

📒 Files selected for processing (3)
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListUiState.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
💤 Files with no reviewable changes (1)
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListUiState.kt
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*ViewModel.kt

⚙️ CodeRabbit configuration file

**/*ViewModel.kt: MVI architecture rules:

  • All new features must follow MVI
  • ViewModel must extend BaseViewModel
  • The ViewModel MUST maintain a single UI state (e.g., a single StateFlow)
    instead of multiple separate state variables.

Required:

  • Use *State, *Event, *Action
  • Naming must be consistent (FeatureViewModel, FeatureState, FeatureEvent, FeatureAction)
  • Follow unidirectional flow:
    Action → ViewModel → State → UI

Internal reducer/action architecture rules:

  • Async operations MUST NOT directly mutate UI state repeatedly inside
    Flow collectors, suspend callbacks, or repository result handlers.

  • Repository/network/database results MUST be converted into internal
    actions using sendAction(...).

  • handleAction(...) must act as the primary reducer responsible for:

    • state mutation
    • reducer-style state transitions
    • triggering follow-up actions
  • Large async methods must be split into:

    1. async collection layer
    2. internal action dispatching
    3. reducer/state handling
  • Avoid directly calling another business/data-loading method from
    repository collectors or async callbacks.
    Prefer dispatching follow-up internal actions instead.

Preferred pattern:
repository result
-> sendAction(...)
-> handleAction(...)
-> mutableStateFlow.update { ... }

Anti-pattern examples:
mutableStateFlow.update { ... } inside collect { }
fetchX() -> directly calls fetchY() inside async collector
large methods mixing:
- collection
- state mutation
- navigation
- business chaining

Flag:

  • Multiple mutableStateFlow.update {} calls inside collect { }
  • Direct state mutation inside async repository callbacks
  • Async methods performing both collection and reducer logic
  • Direct business-flow chaining from async collectors
  • Missing internal reducer actions for async results
  • Missing *State / *Event / *Action
  • ViewModel not...

Files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: Additional Code Review Guidelines:

  1. Null Safety & Stability
  • Avoid using !! operator
  • Handle null cases explicitly using safe calls or proper state handling
  • Do not assume values are always non-null without guarantees
  1. Architecture Boundaries
  • ViewModel must not depend on specific network/library implementations
  • Ensure proper separation between data, domain, and presentation layers
  • Do not format data (currency, dates, calculations) inside the UI layer
  • All formatting must be handled in the ViewModel and exposed via state (e.g., StateFlow)
  1. Performance Considerations
  • Avoid unnecessary recompositions in Compose
  • Do not attach heavy logic to frequently changing states (e.g., scrollState)
  • Prefer lifting state up instead of recomputing in child composables
  1. Compose & Navigation Best Practices
  • NEVER trigger navigation functions or side-effects directly during composition
  • Always wrap navigation calls inside LaunchedEffect or EventsEffect to avoid repeated execution on recomposition
  • Avoid triggering intensive side-effects during recomposition
  • Navigation routes must be type-safe.
  • Ensure all route classes or objects used for navigation are annotated with @Serializable.
  1. UI Structure
  • Dialogs must be separated into their own composables
  • Do not embed dialogs inline within complex main screens
  1. Localization Consistency
  • Ensure all supported languages are updated consistently across modules
  • Verify translations exist for newly added UI strings
  1. Code Cleanliness
  • Avoid unnecessary inline comments unless critical
  • Remove leftover debug or commented code
  1. Focus on correctness, readability, and maintainability over cosmetic nitpicks.
  • Avoid reviewing README, config, or asset files.
  • Prioritize identifying bugs, performance issues, and architectural concerns.
  1. Naming & Intent Rules:

Files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
**/*Screen.kt

⚙️ CodeRabbit configuration file

**/*Screen.kt: Screen architecture rules:

Each screen must follow a 2-layer structure:

  1. Layer 1 (Entry/Route Composable):

    • Function Name: Typically *ScreenRoute or the entry-point composable.
    • Responsibilities: Inject ViewModel (e.g., koinViewModel), take navigation lambdas, collect the StateFlow, and handle ViewModel events.
    • Logic: Should only handle ViewModel interaction, state collection, and triggering navigation in response to ViewModel events.
  2. Layer 2 (Stateful/Content Composable — *Screen or *ScreenContent):

    • Parameters: MUST only take state (the UI state object) and a single onAction lambda (e.g., onAction: (FeatureAction) -> Unit).
    • Responsibilities: Render the UI based strictly on the provided state.
    • Rule: MUST NOT pass multiple separate lambda functions for different UI interactions; consolidate them into the single onAction.
    • Constraint: Must NOT contain any business logic or ViewModel/Navigation references.

Internal/private helper composables (e.g., dialogs, sections, sub-components):

  • These are NOT subject to the single onAction rule.
  • They may accept specific, focused lambdas (e.g., onRetry: () -> Unit) or a narrowed onAction as appropriate.
  • However, they must NOT be passed the ViewModel or navigation controllers directly.

UI consistency:

  • Avoid hardcoded values (dp, sp, padding, fontSize, colors)
  • Use DesignToken, KptTheme, AppColors, MifosTypography for spacing, typography, and colors

Code quality:

  • Keep Composables small and readable
  • Avoid deeply nested UI

Flag:

  • Missing Layer 2 (*Screen/*ScreenContent) separation from the entry-point composable
  • Layer 2 (*Screen/*ScreenContent) receiving multiple separate lambdas instead of a single onAction
  • UI logic inside the entry-point composable
  • Business logic inside any Composable
  • Hardcoded strings instead of using string resources
  • Hardcoded dp/sp values
  • Direct styling instead of using DesignToken or KptTheme

Files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
🧠 Learnings (2)
📚 Learning: 2026-02-06T13:15:16.968Z
Learnt from: sahilshivekar
Repo: openMF/android-client PR: 2603
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt:43-106
Timestamp: 2026-02-06T13:15:16.968Z
Learning: Guideline: When a Kotlin function parameter is nullable (e.g., balance: Double?, currencyCode: String?, maximumFractionDigits: Int?) and downstream calls require a non-null value, add null-safety handling in all implementations. Specifically, avoid calling Currency.getInstance(currencyCode) with a possibly null currencyCode; provide a safe default (e.g., currencyCode ?: "$") or validate before use. Ensure all platform targets (Android/Desktop/Native) follow consistent null handling, and consider centralizing currencyCode normalization in the common layer if feasible. Add tests covering null currencyCode to prevent NPEs.

Applied to files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt
📚 Learning: 2026-04-01T05:03:14.323Z
Learnt from: kartikey004
Repo: openMF/mifos-x-field-officer-app PR: 2659
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanDisbursement/LoanAccountDisbursementScreen.kt:190-195
Timestamp: 2026-04-01T05:03:14.323Z
Learning: In this repo, existing `SelectableDates` / `SelectableDates.isSelectableDate(utcTimeMillis: Long)` implementations use `Clock.System.now().toEpochMilliseconds()` (UTC epoch millis) for date-boundary checks. During PR reviews, do not flag these checks for not using a timezone-aware `LocalDate` start-of-day approach; treat it as an established project-wide pattern. If a change is desired, handle it as a coordinated project-wide improvement rather than as a per-PR review issue.

Applied to files:

  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListViewModel.kt
  • feature/data-table/src/commonMain/kotlin/com/mifos/feature/dataTable/dataTableList/DataTableListScreen.kt

@TheKalpeshPawar TheKalpeshPawar requested a review from biplab1 June 10, 2026 08:37
@TheKalpeshPawar

Copy link
Copy Markdown
Contributor Author

@biplab1 I am not able to login into the web app.
If you can, then can you please cross-check the datatables against the screenshots I have uploaded for review?

@IOhacker

Copy link
Copy Markdown
Contributor

@TheKalpeshPawar @biplab1 we are testing it

@IOhacker

Copy link
Copy Markdown
Contributor

@TheKalpeshPawar @biplab1 we got the following error (we are testing it using the mifos-bank-1)

WhatsApp.Video.2026-06-11.at.9.11.57.PM.mp4

@IOhacker

Copy link
Copy Markdown
Contributor

@TheKalpeshPawar @biplab1 we identified the issue, it was a missing grant on the back end. It is working now.

This PR can be merged!

Thank you!

// TODO()
dataTable = { dataTables, payload ->
navController.navigateDataTableList(
dataTableList = dataTables,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Don't pass huge data via navigation arguments instead pass the ID'S and fetch the required data on specific screen

biplab1

This comment was marked as outdated.

Signed-off-by: thekalpeshpawar <pawarkalpesh@proton.me>
@TheKalpeshPawar TheKalpeshPawar force-pushed the feat/loan-datatable-dynamic-steps branch from 150d241 to a888cd2 Compare June 18, 2026 09:38

@biplab1 biplab1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please address the following minor changes.

Comment thread cmp-shared/cmp_shared.podspec
Comment thread version.txt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please revert this change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@TheKalpeshPawar This file is still appearing.

Signed-off-by: thekalpeshpawar <pawarkalpesh@proton.me>
biplab1
biplab1 previously approved these changes Jun 18, 2026

@biplab1 biplab1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM.

@biplab1

biplab1 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

@niyajali Please review and merge this if everything looks good.

}

private val navArgJson = Json {
serializersModule = SerializersModule {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Still you're passing these data, can you tell why we need such data on the specific screen, can't we fetch the datatable details using ID itself?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Browse the API docs and find a better solution/approach

Signed-off-by: thekalpeshpawar <pawarkalpesh@proton.me>
@TheKalpeshPawar TheKalpeshPawar force-pushed the feat/loan-datatable-dynamic-steps branch from b37ff46 to 6d02513 Compare June 23, 2026 13:43
@sonarqubecloud

Copy link
Copy Markdown

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.

4 participants