Skip to content

fix(common): correct date format in formatDate() for API compatibility - #2616

Merged
therajanmaurya merged 6 commits into
openMF:developmentfrom
therajanmaurya:fix/MIFOSAC-704-wrong-date-format-client-activation
Feb 20, 2026
Merged

fix(common): correct date format in formatDate() for API compatibility#2616
therajanmaurya merged 6 commits into
openMF:developmentfrom
therajanmaurya:fix/MIFOSAC-704-wrong-date-format-client-activation

Conversation

@therajanmaurya

@therajanmaurya therajanmaurya commented Feb 20, 2026

Copy link
Copy Markdown
Member

Summary

This PR introduces a centralized date formatting solution for the Fineract API to ensure consistent date handling across the entire codebase, fixing the issue where date format mismatches caused API validation errors.

Problem

  • The formatDate() function was returning dates in dd/MM/yyyy format (e.g., "18/02/2026")
  • But the API requests specified dateFormat as dd MMMM yyyy (expecting "18 February 2026")
  • This mismatch caused validation errors when creating/activating clients
  • Additionally, some payload classes had YYYY instead of yyyy (wrong year pattern)

Solution

Created a centralized date formatting system:

  1. DateConstants in core/model - Single source of truth for date format strings
  2. ApiDateFormatter in core/common - Utility for formatting dates to API-compatible strings
  3. DateFormatPattern enum in core/common - All supported date format patterns

Architecture

core/model (base module)
└── DateConstants.kt          # DATE_FORMAT = "dd MMMM yyyy", LOCALE = "en"

core/common (depends on core/model)
├── ApiDateFormatter.kt       # formatForApi(millis), references DateConstants
├── DateFormatPattern.kt      # Enum: FULL_MONTH, SHORT_MONTH, etc.
└── FormatDate.kt             # formatDate() function, delegates to ApiDateFormatter

Changes

New Files

File Module Description
DateConstants.kt core/model Constants: DATE_FORMAT, LOCALE, DATE_FORMAT_WITH_TIME
ApiDateFormatter.kt core/common Centralized date formatter with formatForApi() methods
DateFormatPattern.kt core/common Enum with all date format patterns

Updated Files (28 files)

Core Model (6 files)

  • CollectionSheetRequestPayload.kt - Use DateConstants
  • LoanApproval.kt - Use DateConstants
  • LoanApprovalRequest.kt - Fixed dd MM yyyydd MMMM yyyy, use DateConstants
  • LoanDisbursement.kt - Use DateConstants
  • SavingsApproval.kt - Use DateConstants
  • UserLocation.kt - Use DateConstants.DATE_FORMAT_WITH_TIME

Core Network (4 files)

  • Payload.kt - Fixed YYYYyyyy, use ApiDateFormatter
  • DefaultPayload.kt - Fixed YYYYyyyy, use ApiDateFormatter
  • IndividualCollectionSheetPayload.kt - Fixed YYYYyyyy, use ApiDateFormatter
  • DataManagerClient.kt - Use ApiDateFormatter

Core Database (2 files)

  • CollectionSheetPayload.kt - Use ApiDateFormatter
  • ProductiveCollectionSheetPayload.kt - Use ApiDateFormatter

Feature Modules (14 files)

Module Files Updated
client CreateNewClientScreen.kt, ClientEditDetailsScreen.kt
activate ActivateScreen.kt
center CreateNewCenterScreen.kt
groups CreateNewGroupScreen.kt
loan GroupLoanAccountScreen.kt, LoanChargeDialogScreen.kt
savings SavingsAccountScreen.kt, SavingsAccountActivateScreen.kt, SavingsAccountViewModel.kt
collectionSheet NewIndividualCollectionSheetScreen.kt
offline SyncGroupPayloadsUiState.kt
data-table DataTableListViewModel.kt

Usage Example

// Before (inconsistent)
val payload = ClientPayload(
    activationDate = "18/02/2026",      // Wrong format
    dateFormat = "dd MMMM yyyy",         // Expected format
    locale = "en"
)

// After (consistent)
val payload = ClientPayload(
    activationDate = ApiDateFormatter.formatForApi(dateMillis),  // "18 February 2026"
    dateFormat = ApiDateFormatter.DATE_FORMAT,                    // "dd MMMM yyyy"
    locale = ApiDateFormatter.LOCALE                              // "en"
)

// Or using DateConstants directly in core/model
val payload = LoanApproval(
    dateFormat = DateConstants.DATE_FORMAT,
    locale = DateConstants.LOCALE
)

Test Plan

  • Create a new client with activation date - verify API accepts the request
  • Activate a pending client - verify no date format validation errors
  • Edit client details with date changes - verify API accepts the request
  • Create a new group with activation date
  • Create a new center with activation date
  • Create a loan with disbursement date
  • Create a savings account with submission date
  • Verify collection sheet date handling

Related Issue

Fixes: MIFOSAC-704 - Wrong date format while creating a new client and activating it

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Centralized date formatting utilities offering multiple standard patterns (full/short month, numeric dash/slash, ISO, with-time) and APIs to format dates from epoch, LocalDate, numeric components, or lists.
  • Refactor

    • Unified default date format and locale across payloads and screens to use the centralized constants, ensuring consistent API-ready date values.

The formatDate() function was returning dates in "dd/MM/yyyy" format
(e.g., "18/02/2026") but the API requests specify dateFormat as
"dd MMMM yyyy". This mismatch caused validation errors when creating
or activating clients.

Changed the function to return dates in "dd MMMM yyyy" format
(e.g., "18 February 2026") to match the dateFormat parameter sent
to the API.

Fixes: MIFOSAC-704

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a centralized date formatting utility (ApiDateFormatter) and DateFormatPattern, introduces DateConstants, and replaces hard-coded date format and locale literals with the new constants across core, model, network, database, and feature modules.

Changes

Cohort / File(s) Summary
Date Formatting Utilities
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt, core/common/src/commonMain/kotlin/com/mifos/core/common/utils/DateFormatPattern.kt
New ApiDateFormatter object exposing DATE_FORMAT, LOCALE, multiple formatForApi overloads and helpers; new DateFormatPattern enum with six patterns.
Core formatting API
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/FormatDate.kt
formatDate(millis) now delegates to ApiDateFormatter; added overload formatDate(millis, DateFormatPattern).
Model constants
core/model/src/commonMain/kotlin/com/mifos/core/model/utils/DateConstants.kt
New DateConstants object with DATE_FORMAT, LOCALE, and DATE_FORMAT_WITH_TIME.
Model classes (defaults & small API change)
core/model/src/commonMain/kotlin/.../LoanApproval.kt, .../LoanApprovalRequest.kt, .../LoanDisbursement.kt, .../SavingsApproval.kt, .../CollectionSheetRequestPayload.kt, .../UserLocation.kt
Replaced hard-coded dateFormat/locale defaults with DateConstants.*; added note: String? field to SavingsApproval.
Database entities (defaults)
core/database/src/commonMain/kotlin/.../CollectionSheetPayload.kt, core/database/.../ProductiveCollectionSheetPayload.kt
Default dateFormat and locale updated to ApiDateFormatter constants.
Network models & data manager
core/network/src/commonMain/kotlin/.../DefaultPayload.kt, .../IndividualCollectionSheetPayload.kt, .../Payload.kt, .../DataManagerClient.kt
Replaced literal date/locale defaults with ApiDateFormatter constants; payload constructions updated to use centralized values.
Feature modules (payloads / screens / viewmodels)
feature/activate/.../ActivateScreen.kt, feature/center/.../CreateNewCenterScreen.kt, feature/client/.../ClientEditDetailsScreen.kt, feature/client/.../CreateNewClientScreen.kt, feature/collectionSheet/.../NewIndividualCollectionSheetScreen.kt, feature/data-table/.../DataTableListViewModel.kt, feature/groups/.../CreateNewGroupScreen.kt, feature/loan/.../GroupLoanAccountScreen.kt, feature/loan/.../LoanChargeDialogScreen.kt, feature/offline/.../SyncGroupPayloadsUiState.kt, feature/savings/.../SavingsAccountScreen.kt, feature/savings/.../SavingsAccountActivateScreen.kt, feature/savings/.../SavingsAccountViewModel.kt
Replaced literal date format and locale strings with ApiDateFormatter.DATE_FORMAT and ApiDateFormatter.LOCALE; LoanChargeDialogScreen uses ApiDateFormatter.formatForApi for due dates; removed local literals.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • biplab1
  • niyajali
  • TheKalpeshPawar

Poem

🐰 I hopped through code with joyful cheer,
Swapped scattered strings to one held dear.
Dates aligned in tidy rows,
One formatter where the river flows.
🥕📅

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: centralizing date formatting for API compatibility by correcting the formatDate() function.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

@therajanmaurya
therajanmaurya changed the base branch from master to development February 20, 2026 14:09
therajanmaurya and others added 2 commits February 20, 2026 19:53
…ndling

This commit introduces a centralized date formatting solution for the
Fineract API to ensure consistent date format handling across the entire
codebase.

Changes:
- Add DateFormatPattern enum with standard API date patterns
- Add ApiDateFormatter object for centralized date formatting
- Update FormatDate.kt to delegate to ApiDateFormatter
- Fix YYYY -> yyyy typo in Payload, DefaultPayload, IndividualCollectionSheetPayload
- Update all payload classes to use ApiDateFormatter constants
- Update feature modules (client, activate, center, groups, loan, savings,
  collectionSheet, offline, data-table) to use centralized formatter
- Update DataManagerClient to use ApiDateFormatter

This ensures that the date format string sent to the API always matches
the actual date format being used, preventing validation errors.

Fixes: MIFOSAC-704

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@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

Caution

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

⚠️ Outside diff range comments (4)
feature/offline/src/commonMain/kotlin/com/mifos/feature/offline/syncGroupPayloads/SyncGroupPayloadsUiState.kt (1)

44-80: ⚠️ Potential issue | 🟡 Minor

Dummy date strings don't match the declared dateFormat.

dateFormat = ApiDateFormatter.DATE_FORMAT declares the format as "dd MMMM yyyy", but activationDate/submittedOnDate are set to "2024-01-01" (ISO/numeric). While this is preview-only data and won't reach the API, it's misleading for anyone using the sample entries as a reference. Update the date strings to match the declared format.

🛠️ Proposed fix
-        activationDate = "2024-01-01",
-        submittedOnDate = "2024-01-01",
+        activationDate = "01 January 2024",
+        submittedOnDate = "01 January 2024",

(Apply the same pattern to all three GroupPayloadEntity instances.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/offline/src/commonMain/kotlin/com/mifos/feature/offline/syncGroupPayloads/SyncGroupPayloadsUiState.kt`
around lines 44 - 80, The dummy date strings in dummyGroupPayloads do not match
the declared ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"); update
activationDate and submittedOnDate on each GroupPayloadEntity (the three
instances in dummyGroupPayloads) to use the "dd MMMM yyyy" pattern (e.g., "01
January 2024", "01 February 2024", "01 March 2024") so the sample data aligns
with ApiDateFormatter.DATE_FORMAT and serves as an accurate reference.
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/groupLoanAccount/GroupLoanAccountScreen.kt (1)

586-613: ⚠️ Potential issue | 🟠 Major

Payload date strings use DateHelper.getDateAsStringFromLong() which returns "dd-MM-yyyy", but dateFormat is "dd MMMM yyyy" — format mismatch will cause API validation failure.

The payload correctly sets dateFormat = ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"), but the actual date strings sent for expectedDisbursementDate and submittedOnDate come from DateHelper.getDateAsStringFromLong(), which uses the "dd-MM-yyyy" format. This format mismatch will cause the Fineract API to reject the request with a date validation error—the exact problem this PR is designed to fix. Other feature screens in this PR (e.g. CreateNewGroupScreen.kt, CreateNewCenterScreen.kt) were corrected to use formatDate() which returns the matching "dd MMMM yyyy" format.

Replace with:

+import com.mifos.core.common.utils.formatDate
 ...
-                    expectedDisbursementDate =
-                        DateHelper.getDateAsStringFromLong(
-                            disbursementDate,
-                        )
+                    expectedDisbursementDate = formatDate(disbursementDate)
 ...
-                    submittedOnDate =
-                        DateHelper.getDateAsStringFromLong(submissionDate)
+                    submittedOnDate = formatDate(submissionDate)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/groupLoanAccount/GroupLoanAccountScreen.kt`
around lines 586 - 613, The date strings in GroupLoanPayload are using
DateHelper.getDateAsStringFromLong() (which yields "dd-MM-yyyy") while
dateFormat is set to ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"); update the
assignments for expectedDisbursementDate and submittedOnDate to use the
project's formatDate() helper (or DateHelper.formatDate/formatDateLong
equivalent that returns "dd MMMM yyyy") so the string format matches
ApiDateFormatter.DATE_FORMAT; adjust GroupLoanPayload.expectedDisbursementDate
and GroupLoanPayload.submittedOnDate assignments accordingly in
GroupLoanAccountScreen (replace getDateAsStringFromLong(...) with
formatDate(...) for those two fields).
feature/collectionSheet/src/commonMain/kotlin/com/mifos/feature/individualCollectionSheet/newIndividualCollectionSheet/NewIndividualCollectionSheetScreen.kt (1)

93-103: ⚠️ Potential issue | 🟠 Major

transactionDate format does not match declared dateFormat — API calls will fail with validation errors.

transactionDate is set to DateHelper.getDateAsStringFromLong(repaymentDate) (line 282), which produces dates in "dd-MM-yyyy" format (e.g., "14-04-2016"). However, dateFormat is declared as ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"). The Fineract API parses the date string using the declared dateFormat, so these must match exactly.

The correct fix is to use formatDate(repaymentDate) instead, which delegates to ApiDateFormatter.formatForApi() and produces the correct "dd MMMM yyyy" format:

Suggested fix
 generateCollection = { id, idStaff, date ->
     viewModel.getIndividualCollectionSheet(
         RequestCollectionSheetPayload().apply {
             officeId = id
-            transactionDate = date
+            transactionDate = formatDate(repaymentDate)
             staffId = idStaff
             locale = ApiDateFormatter.LOCALE
             dateFormat = ApiDateFormatter.DATE_FORMAT
         },
     )
 },

repaymentDate must be captured by the outer lambda, or formatDate can be called at the call site (line 282) before passing to generateCollection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/collectionSheet/src/commonMain/kotlin/com/mifos/feature/individualCollectionSheet/newIndividualCollectionSheet/NewIndividualCollectionSheetScreen.kt`
around lines 93 - 103, The transactionDate being passed to
RequestCollectionSheetPayload in the generateCollection lambda uses a dd-MM-yyyy
string (from DateHelper.getDateAsStringFromLong) which doesn't match
ApiDateFormatter.DATE_FORMAT (dd MMMM yyyy); update the code so transactionDate
is produced via formatDate(repaymentDate) (which calls
ApiDateFormatter.formatForApi()) or ensure repaymentDate is captured by the
outer lambda and formatted before calling
viewModel.getIndividualCollectionSheet; specifically change the transactionDate
assignment inside generateCollection (used when calling
viewModel.getIndividualCollectionSheet and populating
RequestCollectionSheetPayload) to use formatDate(...) instead of
DateHelper.getDateAsStringFromLong(...).
feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt (1)

149-154: ⚠️ Potential issue | 🟠 Major

submittedOnDate format mismatch: declared as "dd MMMM yyyy" but populated as "dd-MM-yyyy".

The code declares dateFormat = ApiDateFormatter.DATE_FORMAT (which is "dd MMMM yyyy", e.g., "18 February 2026"), but state.submissionDate is initialized via DateHelper.getDateAsStringFromLong() which produces "dd-MM-yyyy" format (e.g., "18-02-2026"). The v1 screen (SavingsAccountScreen.kt line 481) explicitly uses ApiDateFormatter.formatForApi() to fix this exact mismatch. The v2 ViewModel must apply the same fix to avoid API validation errors.

🐛 Proposed fix to align with v1 screen pattern
 val savingsPayload = SavingsPayload().apply {
     locale = ApiDateFormatter.LOCALE
     dateFormat = ApiDateFormatter.DATE_FORMAT
     productId = state.savingProductOptions.getOrNull(state.savingsProductSelected)?.id
     clientId = state.clientId
     fieldOfficerId = state.fieldOfficerOptions.getOrNull(state.fieldOfficerIndex)?.id
-    submittedOnDate = state.submissionDate
+    submittedOnDate = state.submissionDateMillis?.let { ApiDateFormatter.formatForApi(it) }
+        ?: state.submissionDate

Alternatively, store submissionDate as a Long in SavingsAccountState (parallel to v1) and format it at the call site via ApiDateFormatter.formatForApi.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt`
around lines 149 - 154, The submittedOnDate value in SavingsAccountViewModel is
using state.submissionDate which is formatted as "dd-MM-yyyy" (from
DateHelper.getDateAsStringFromLong) while dateFormat expects
ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"); update the ViewModel to format
the date for API by calling ApiDateFormatter.formatForApi(state.submissionDate)
(or, alternatively, change SavingsAccountState to store submissionDate as a Long
and call ApiDateFormatter.formatForApi on that Long) so submittedOnDate matches
the API format; specifically modify the assignment of submittedOnDate in
SavingsAccountViewModel to use ApiDateFormatter.formatForApi(...) instead of
state.submissionDate.
🧹 Nitpick comments (5)
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt (3)

147-149: getDateFormatString is redundant — DateFormatPattern.pattern is already public.

All this method does is return pattern.pattern, which callers can access directly. It adds surface area without value.

♻️ Proposed cleanup
-    /**
-     * Get the date format pattern string for a given pattern enum.
-     *
-     * `@param` pattern The DateFormatPattern enum value
-     * `@return` The pattern string to send as dateFormat parameter
-     */
-    fun getDateFormatString(pattern: DateFormatPattern): String {
-        return pattern.pattern
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 147 - 149, The getDateFormatString function is redundant because
DateFormatPattern.pattern is public; remove the getDateFormatString(pattern:
DateFormatPattern) function from ApiDateFormatter and update any callers
(references to getDateFormatString) to read the pattern property directly (e.g.,
replace getDateFormatString(x) with x.pattern); ensure imports and usages still
compile and run, and run tests to confirm no behavioral changes.

49-54: Redundant property aliases for constants.

dateFormat and locale are instance-property aliases for DATE_FORMAT and LOCALE constants and serve no additional purpose. Consumers calling ApiDateFormatter.DATE_FORMAT directly is both more idiomatic and avoids the misleading impression that these are mutable or computed.

♻️ Proposed cleanup
-    /**
-     * Alias for DATE_FORMAT - the dateFormat parameter value to send to API
-     */
-    val dateFormat: String get() = DATE_FORMAT
-
-    /**
-     * Alias for LOCALE - the locale parameter value to send to API
-     */
-    val locale: String get() = LOCALE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 49 - 54, ApiDateFormatter exposes redundant instance properties
dateFormat and locale that simply alias the constants DATE_FORMAT and LOCALE;
remove these instance-property getters (dateFormat and locale) from
ApiDateFormatter so callers use the existing constants DATE_FORMAT and LOCALE
directly, and update any internal/consumer references to stop using
ApiDateFormatter.dateFormat or ApiDateFormatter.locale in favor of
ApiDateFormatter.DATE_FORMAT and ApiDateFormatter.LOCALE.

98-168: Duplicate when expressions in formatForApi(LocalDate, …) and formatDateTime(LocalDateTime, …).

Both methods implement the same six-branch pattern for mapping DateFormatPattern to a formatted string, differing only in the input type. Extract a shared private helper that accepts the pre-computed primitive parts (day, month, year, hour, minute) to eliminate the duplication.

♻️ Proposed refactor sketch
+    private fun buildDateString(
+        day: String,
+        month: kotlinx.datetime.Month,
+        year: Int,
+        hour: String = "00",
+        minute: String = "00",
+        pattern: DateFormatPattern,
+    ): String = when (pattern) {
+        DateFormatPattern.FULL_MONTH -> "$day ${month.toFullName()} $year"
+        DateFormatPattern.SHORT_MONTH -> "$day ${month.toShortName()} $year"
+        DateFormatPattern.NUMERIC_DASH -> "$day-${month.number.toString().padStart(2, '0')}-$year"
+        DateFormatPattern.NUMERIC_SLASH -> "$day/${month.number.toString().padStart(2, '0')}/$year"
+        DateFormatPattern.ISO -> "$year-${month.number.toString().padStart(2, '0')}-$day"
+        DateFormatPattern.FULL_MONTH_WITH_TIME -> "$day ${month.toFullName()} $year $hour:$minute"
+    }

Then delegate from both formatForApi(date: LocalDate, …) and formatDateTime(dateTime: LocalDateTime, …) to buildDateString(…).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 98 - 168, The two duplicated when-expressions in formatForApi(date:
LocalDate, pattern: DateFormatPattern) and formatDateTime(dateTime:
LocalDateTime, pattern: DateFormatPattern) should be collapsed into a single
private helper (e.g., buildDateString) that takes the precomputed primitives
(day string, month or month number/name, year and optional hour/minute or
nullable hour/minute) and returns the formatted string for the given
DateFormatPattern; then have formatForApi(LocalDate, …),
formatDateTime(LocalDateTime, …) (and optionally
formatForApi(day:Int,month:Int,year:Int)) compute their primitive parts and
delegate to buildDateString, removing the duplicated when blocks in formatForApi
and formatDateTime.
core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerClient.kt (1)

545-549: Missed locale = "en" in createCollateral — not updated to ApiDateFormatter.LOCALE.

All other payload constructions in this file were centralized in this PR, but createCollateral still hardcodes "en".

♻️ Proposed fix
         payload = CollateralPayload(
             collateralId = collateralId,
             quantity = quantity,
-            locale = "en",
+            locale = ApiDateFormatter.LOCALE,
         ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerClient.kt`
around lines 545 - 549, The createCollateral payload still hardcodes locale =
"en"; update the payload construction inside the createCollateral function to
use ApiDateFormatter.LOCALE instead of the literal "en" so it matches the
centralized locale handling used by other payloads (replace the locale value in
the collateralId/quantity payload to ApiDateFormatter.LOCALE).
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanChargeDialog/LoanChargeDialogScreen.kt (1)

122-122: locale display value is disconnected from ApiDateFormatter.LOCALE

The locale state variable (used to display locale in the UI at line 265) is still hardcoded to "en". The payload at line 280 now correctly uses ApiDateFormatter.LOCALE. If ApiDateFormatter.LOCALE ever changes, the displayed value will silently diverge from what is actually sent to the API.

♻️ Proposed fix
-val locale by rememberSaveable { mutableStateOf("en") }
+val locale by rememberSaveable { mutableStateOf(ApiDateFormatter.LOCALE) }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanChargeDialog/LoanChargeDialogScreen.kt`
at line 122, The UI's locale state in LoanChargeDialogScreen is hardcoded to
"en" and can diverge from ApiDateFormatter.LOCALE; change the initialization of
the locale state so it derives from ApiDateFormatter.LOCALE (e.g., replace
mutableStateOf("en") with mutableStateOf(ApiDateFormatter.LOCALE) or bind the
displayed locale directly to ApiDateFormatter.LOCALE) so the displayed value and
the payload use the same source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`:
- Around line 73-79: The formatForApi function is converting the input UTC
millis to a LocalDateTime using TimeZone.currentSystemDefault(), which shifts
UTC-midnight dates for negative-offset users; change the timezone usage in
formatForApi to TimeZone.UTC so
Instant.fromEpochMilliseconds(millis).toLocalDateTime(TimeZone.UTC) is used (the
rest of the function should continue to call formatDateTime(dateTime, pattern)),
ensuring selectedDateMillis stays on the intended UTC date when formatted for
the API.

In
`@core/model/src/commonMain/kotlin/com/mifos/core/model/objects/users/UserLocation.kt`:
- Line 38: The dateFormat default in UserLocation currently uses
DateFormatPattern.FULL_MONTH_WITH_TIME.pattern; change it to
DateFormatPattern.FULL_MONTH.pattern so the UserLocation.dateFormat only
contains the date portion (since startTime and stopTime hold times). Update the
declaration of dateFormat in the UserLocation class to use
DateFormatPattern.FULL_MONTH and ensure any usages/serializations of
UserLocation continue to expect "dd MMMM yyyy" format.

In
`@feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountActivate/SavingsAccountActivateScreen.kt`:
- Around line 235-240: The activatedOnDate value is currently set to
approvalDate.toString(), which yields epoch millis and mismatches the declared
ApiDateFormatter.DATE_FORMAT; update the payload assembly in
SavingsAccountActivateScreen (the block that builds hashMap and calls
activateSavings.invoke) to set hashMap["activatedOnDate"] =
ApiDateFormatter.formatForApi(approvalDate) so the date string matches
ApiDateFormatter.DATE_FORMAT before calling activateSavings.invoke(hashMap).

---

Outside diff comments:
In
`@feature/collectionSheet/src/commonMain/kotlin/com/mifos/feature/individualCollectionSheet/newIndividualCollectionSheet/NewIndividualCollectionSheetScreen.kt`:
- Around line 93-103: The transactionDate being passed to
RequestCollectionSheetPayload in the generateCollection lambda uses a dd-MM-yyyy
string (from DateHelper.getDateAsStringFromLong) which doesn't match
ApiDateFormatter.DATE_FORMAT (dd MMMM yyyy); update the code so transactionDate
is produced via formatDate(repaymentDate) (which calls
ApiDateFormatter.formatForApi()) or ensure repaymentDate is captured by the
outer lambda and formatted before calling
viewModel.getIndividualCollectionSheet; specifically change the transactionDate
assignment inside generateCollection (used when calling
viewModel.getIndividualCollectionSheet and populating
RequestCollectionSheetPayload) to use formatDate(...) instead of
DateHelper.getDateAsStringFromLong(...).

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/groupLoanAccount/GroupLoanAccountScreen.kt`:
- Around line 586-613: The date strings in GroupLoanPayload are using
DateHelper.getDateAsStringFromLong() (which yields "dd-MM-yyyy") while
dateFormat is set to ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"); update the
assignments for expectedDisbursementDate and submittedOnDate to use the
project's formatDate() helper (or DateHelper.formatDate/formatDateLong
equivalent that returns "dd MMMM yyyy") so the string format matches
ApiDateFormatter.DATE_FORMAT; adjust GroupLoanPayload.expectedDisbursementDate
and GroupLoanPayload.submittedOnDate assignments accordingly in
GroupLoanAccountScreen (replace getDateAsStringFromLong(...) with
formatDate(...) for those two fields).

In
`@feature/offline/src/commonMain/kotlin/com/mifos/feature/offline/syncGroupPayloads/SyncGroupPayloadsUiState.kt`:
- Around line 44-80: The dummy date strings in dummyGroupPayloads do not match
the declared ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"); update
activationDate and submittedOnDate on each GroupPayloadEntity (the three
instances in dummyGroupPayloads) to use the "dd MMMM yyyy" pattern (e.g., "01
January 2024", "01 February 2024", "01 March 2024") so the sample data aligns
with ApiDateFormatter.DATE_FORMAT and serves as an accurate reference.

In
`@feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt`:
- Around line 149-154: The submittedOnDate value in SavingsAccountViewModel is
using state.submissionDate which is formatted as "dd-MM-yyyy" (from
DateHelper.getDateAsStringFromLong) while dateFormat expects
ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"); update the ViewModel to format
the date for API by calling ApiDateFormatter.formatForApi(state.submissionDate)
(or, alternatively, change SavingsAccountState to store submissionDate as a Long
and call ApiDateFormatter.formatForApi on that Long) so submittedOnDate matches
the API format; specifically modify the assignment of submittedOnDate in
SavingsAccountViewModel to use ApiDateFormatter.formatForApi(...) instead of
state.submissionDate.

---

Nitpick comments:
In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`:
- Around line 147-149: The getDateFormatString function is redundant because
DateFormatPattern.pattern is public; remove the getDateFormatString(pattern:
DateFormatPattern) function from ApiDateFormatter and update any callers
(references to getDateFormatString) to read the pattern property directly (e.g.,
replace getDateFormatString(x) with x.pattern); ensure imports and usages still
compile and run, and run tests to confirm no behavioral changes.
- Around line 49-54: ApiDateFormatter exposes redundant instance properties
dateFormat and locale that simply alias the constants DATE_FORMAT and LOCALE;
remove these instance-property getters (dateFormat and locale) from
ApiDateFormatter so callers use the existing constants DATE_FORMAT and LOCALE
directly, and update any internal/consumer references to stop using
ApiDateFormatter.dateFormat or ApiDateFormatter.locale in favor of
ApiDateFormatter.DATE_FORMAT and ApiDateFormatter.LOCALE.
- Around line 98-168: The two duplicated when-expressions in formatForApi(date:
LocalDate, pattern: DateFormatPattern) and formatDateTime(dateTime:
LocalDateTime, pattern: DateFormatPattern) should be collapsed into a single
private helper (e.g., buildDateString) that takes the precomputed primitives
(day string, month or month number/name, year and optional hour/minute or
nullable hour/minute) and returns the formatted string for the given
DateFormatPattern; then have formatForApi(LocalDate, …),
formatDateTime(LocalDateTime, …) (and optionally
formatForApi(day:Int,month:Int,year:Int)) compute their primitive parts and
delegate to buildDateString, removing the duplicated when blocks in formatForApi
and formatDateTime.

In
`@core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerClient.kt`:
- Around line 545-549: The createCollateral payload still hardcodes locale =
"en"; update the payload construction inside the createCollateral function to
use ApiDateFormatter.LOCALE instead of the literal "en" so it matches the
centralized locale handling used by other payloads (replace the locale value in
the collateralId/quantity payload to ApiDateFormatter.LOCALE).

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanChargeDialog/LoanChargeDialogScreen.kt`:
- Line 122: The UI's locale state in LoanChargeDialogScreen is hardcoded to "en"
and can diverge from ApiDateFormatter.LOCALE; change the initialization of the
locale state so it derives from ApiDateFormatter.LOCALE (e.g., replace
mutableStateOf("en") with mutableStateOf(ApiDateFormatter.LOCALE) or bind the
displayed locale directly to ApiDateFormatter.LOCALE) so the displayed value and
the payload use the same source of truth.

- Created DateConstants in core/model with DATE_FORMAT, LOCALE, and
  DATE_FORMAT_WITH_TIME constants
- Updated ApiDateFormatter in core/common to reference DateConstants
- Updated all payload classes in core/model to use DateConstants
- This avoids circular dependency since core/common depends on core/model

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

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

Caution

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

⚠️ Outside diff range comments (3)
feature/offline/src/commonMain/kotlin/com/mifos/feature/offline/syncGroupPayloads/SyncGroupPayloadsUiState.kt (1)

44-80: ⚠️ Potential issue | 🟡 Minor

Dummy payload date strings are in ISO format (yyyy-MM-dd) but dateFormat claims "dd MMMM yyyy" — inconsistent.

activationDate = "2024-01-01" and submittedOnDate = "2024-01-01" are in yyyy-MM-dd format across all three entries, while dateFormat = ApiDateFormatter.DATE_FORMAT asserts "dd MMMM yyyy". If these payloads are ever used in integration tests or as templates, the mismatch will produce API validation errors.

🐛 Proposed fix — align dummy dates with the declared format
 GroupPayloadEntity(
     id = 1,
     ...
-    activationDate = "2024-01-01",
-    submittedOnDate = "2024-01-01",
+    activationDate = "01 January 2024",
+    submittedOnDate = "01 January 2024",
     ...
     dateFormat = ApiDateFormatter.DATE_FORMAT,
 ),
 GroupPayloadEntity(
     id = 2,
     ...
-    activationDate = "2024-02-01",
-    submittedOnDate = "2024-02-01",
+    activationDate = "01 February 2024",
+    submittedOnDate = "01 February 2024",
     ...
     dateFormat = ApiDateFormatter.DATE_FORMAT,
 ),
 GroupPayloadEntity(
     id = 3,
     ...
-    activationDate = "2024-03-01",
-    submittedOnDate = "2024-03-01",
+    activationDate = "01 March 2024",
+    submittedOnDate = "01 March 2024",
     ...
     dateFormat = ApiDateFormatter.DATE_FORMAT,
 ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/offline/src/commonMain/kotlin/com/mifos/feature/offline/syncGroupPayloads/SyncGroupPayloadsUiState.kt`
around lines 44 - 80, The dummyGroupPayloads list contains GroupPayloadEntity
entries whose activationDate and submittedOnDate strings use ISO format
("yyyy-MM-dd") while dateFormat is set to ApiDateFormatter.DATE_FORMAT ("dd MMMM
yyyy"); update the three GroupPayloadEntity instances so their activationDate
and submittedOnDate values match ApiDateFormatter.DATE_FORMAT (e.g., "01 January
2024", "01 February 2024", "01 March 2024") to keep the data consistent with the
declared dateFormat.
core/model/src/commonMain/kotlin/com/mifos/core/model/objects/collectionsheets/CollectionSheetRequestPayload.kt (1)

12-25: ⚠️ Potential issue | 🔴 Critical

Build-breaking: ApiDateFormatter is unresolvable in core/model — cannot add core/common dependency due to circular dependency

The pipeline confirms three compile errors at lines 12, 23, and 25 because core/model does not declare a dependency on core/common. However, adding core/common as a dependency to core/model is not viable: core/common already depends on core/model (via api(projects.core.model) in its build.gradle.kts), which would create a circular dependency and break the build.

Options to resolve:

  1. Move ApiDateFormatter to core/model itself (simplest for a utility constant).
  2. Create a new foundational shared module (e.g., core/constants) that both modules can safely depend on without cycles.
  3. Revert to inline string constants "dd MMMM yyyy" and "en" in this file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/model/src/commonMain/kotlin/com/mifos/core/model/objects/collectionsheets/CollectionSheetRequestPayload.kt`
around lines 12 - 25, CollectionSheetRequestPayload references ApiDateFormatter
(used for dateFormat and locale) but core/model cannot depend on core/common,
causing build errors; fix by removing the cross-module dependency: either move
ApiDateFormatter into core/model, create a new shared module (e.g.,
core/constants) that both core/model and core/common can depend on, or replace
the references in CollectionSheetRequestPayload with the literal defaults ("dd
MMMM yyyy" and "en"); update the references to ApiDateFormatter.DATE_FORMAT and
ApiDateFormatter.LOCALE in the CollectionSheetRequestPayload data class
accordingly (or import the new constants) so the file compiles without adding
core/common as a dependency.
feature/collectionSheet/src/commonMain/kotlin/com/mifos/feature/individualCollectionSheet/newIndividualCollectionSheet/NewIndividualCollectionSheetScreen.kt (1)

93-103: ⚠️ Potential issue | 🟠 Major

transactionDate format mismatches the declared dateFormat

The date being sent (transactionDate) is formatted by DateHelper.getDateAsStringFromLong(), which returns "dd-MM-yyyy", but the payload declares dateFormat = ApiDateFormatter.DATE_FORMAT ("dd MMMM yyyy"). This mismatch will cause the API to reject the request with a date format validation error.

Use ApiDateFormatter.formatForApi(repaymentDate) instead, which produces the correct "dd MMMM yyyy" format to match the declared dateFormat. This pattern is already correctly applied in SavingsAccountScreen.kt line 481.

The same issue exists in GroupLoanAccountScreen.kt lines 591–603.

Proposed fix at line 282
 generateCollection(
     officeId,
     staffId,
-    DateHelper.getDateAsStringFromLong(repaymentDate),
+    ApiDateFormatter.formatForApi(repaymentDate),
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/collectionSheet/src/commonMain/kotlin/com/mifos/feature/individualCollectionSheet/newIndividualCollectionSheet/NewIndividualCollectionSheetScreen.kt`
around lines 93 - 103, The payload sends transactionDate using the wrong
formatter; in NewIndividualCollectionSheetScreen's generateCollection lambda
(where you call viewModel.getIndividualCollectionSheet with
RequestCollectionSheetPayload setting transactionDate and dateFormat =
ApiDateFormatter.DATE_FORMAT), replace the current
DateHelper.getDateAsStringFromLong(...) usage with
ApiDateFormatter.formatForApi(repaymentDate) (same pattern used in
SavingsAccountScreen.kt) so the string matches the declared "dd MMMM yyyy"
format; apply the same change in GroupLoanAccountScreen.kt for its
transactionDate construction to prevent API validation errors.
🧹 Nitpick comments (4)
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt (3)

147-149: getDateFormatString is a trivial one-liner that adds no value over pattern.pattern.

Every caller can access DateFormatPattern.FULL_MONTH.pattern directly. Consider removing this function.

♻️ Proposed removal
-    /**
-     * Get the date format pattern string for a given pattern enum.
-     *
-     * `@param` pattern The DateFormatPattern enum value
-     * `@return` The pattern string to send as dateFormat parameter
-     */
-    fun getDateFormatString(pattern: DateFormatPattern): String {
-        return pattern.pattern
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 147 - 149, The getDateFormatString function is a trivial one-liner
that merely returns pattern.pattern and adds no value over accessing
DateFormatPattern.pattern directly; remove the getDateFormatString function from
ApiDateFormatter (delete its declaration) and update any callers to use
DateFormatPattern.<name>.pattern (or the pattern variable directly) instead,
ensuring references to getDateFormatString are replaced with direct access to
the DateFormatPattern.pattern property.

98-111: Duplicate when block logic between formatForApi(LocalDate, DateFormatPattern) and formatDateTime.

Both functions contain an identical six-branch when block; the only difference is that formatForApi(LocalDate, ...) hardcodes "00:00" for the time component while formatDateTime uses actual hour/minute values. The LocalDate overload can delegate directly to formatDateTime by constructing a midnight LocalDateTime:

♻️ Proposed refactor
+import kotlinx.datetime.LocalTime
+import kotlinx.datetime.atTime

 fun formatForApi(date: LocalDate, pattern: DateFormatPattern): String {
-    val day = date.dayOfMonth.toString().padStart(2, '0')
-    val month = date.month
-    val year = date.year
-
-    return when (pattern) {
-        DateFormatPattern.FULL_MONTH -> "$day ${month.toFullName()} $year"
-        DateFormatPattern.SHORT_MONTH -> "$day ${month.toShortName()} $year"
-        DateFormatPattern.NUMERIC_DASH -> "$day-${month.number.toString().padStart(2, '0')}-$year"
-        DateFormatPattern.NUMERIC_SLASH -> "$day/${month.number.toString().padStart(2, '0')}/$year"
-        DateFormatPattern.ISO -> "$year-${month.number.toString().padStart(2, '0')}-$day"
-        DateFormatPattern.FULL_MONTH_WITH_TIME -> "$day ${month.toFullName()} $year 00:00"
-    }
+    return formatDateTime(date.atTime(0, 0), pattern)
 }

Also applies to: 151-169

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 98 - 111, The formatForApi(date: LocalDate, pattern:
DateFormatPattern) contains a duplicated when block also present in
formatDateTime(LocalDateTime, DateFormatPattern); instead of duplicating logic,
delegate: construct a midnight LocalDateTime from the LocalDate (e.g.,
date.atStartOfDay()) and call formatDateTime(...) with that LocalDateTime and
the same pattern so the shared when logic lives only in formatDateTime; update
formatForApi(LocalDate, ...) to return the result of that call.

49-54: Redundant computed-property aliases for DATE_FORMAT and LOCALE.

dateFormat and locale simply delegate to DATE_FORMAT and LOCALE, adding indirection without benefit. Callers can reference the constants directly.

♻️ Proposed simplification
-    /**
-     * Alias for DATE_FORMAT - the dateFormat parameter value to send to API
-     */
-    val dateFormat: String get() = DATE_FORMAT
-
-    /**
-     * Alias for LOCALE - the locale parameter value to send to API
-     */
-    val locale: String get() = LOCALE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 49 - 54, ApiDateFormatter exposes redundant computed properties
dateFormat and locale that just delegate to the constants DATE_FORMAT and
LOCALE; remove the computed properties (dateFormat and locale) from
ApiDateFormatter and update all call sites to reference the constants
DATE_FORMAT and LOCALE directly (search for ApiDateFormatter.dateFormat /
.locale usages and replace with ApiDateFormatter.DATE_FORMAT / .LOCALE or the
appropriate import). Ensure no behavioral change and run tests/compile to catch
any missed references.
core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerClient.kt (1)

544-549: Remaining hard-coded "en" locale in createCollateral

CollateralPayload at line 547 still uses the literal "en" instead of ApiDateFormatter.LOCALE, making it the only call site in this file not updated by the refactor. While ApiDateFormatter.LOCALE likely resolves to "en" today, this inconsistency will silently diverge if the constant is ever changed.

♻️ Suggested fix
-            locale = "en",
+            locale = ApiDateFormatter.LOCALE,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerClient.kt`
around lines 544 - 549, In DataManagerClient.kt within the createCollateral
call, CollateralPayload is still using the hard-coded locale string "en";
replace that literal with ApiDateFormatter.LOCALE so the payload uses the
centralized locale constant (update the payload construction in the
createCollateral/CollateralPayload site to use ApiDateFormatter.LOCALE).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/FormatDate.kt`:
- Around line 22-24: The display in CreateNewCenterScreen uses
formatDate(millis: Long) which now returns "dd MMMM yyyy"; update the
display-only usage (the value passed into MifosDatePickerTextField in
CreateNewCenterScreen) to call the two-argument overload formatDate(millis,
"dd/MM/yyyy") (or otherwise parse and reformat the single-arg output back to
"dd/MM/yyyy") so the UI shows the original "18/02/2026" style; look for the
MifosDatePickerTextField value assignment in CreateNewCenterScreen and replace
the single-arg formatDate call with the two-arg version.

---

Outside diff comments:
In
`@core/model/src/commonMain/kotlin/com/mifos/core/model/objects/collectionsheets/CollectionSheetRequestPayload.kt`:
- Around line 12-25: CollectionSheetRequestPayload references ApiDateFormatter
(used for dateFormat and locale) but core/model cannot depend on core/common,
causing build errors; fix by removing the cross-module dependency: either move
ApiDateFormatter into core/model, create a new shared module (e.g.,
core/constants) that both core/model and core/common can depend on, or replace
the references in CollectionSheetRequestPayload with the literal defaults ("dd
MMMM yyyy" and "en"); update the references to ApiDateFormatter.DATE_FORMAT and
ApiDateFormatter.LOCALE in the CollectionSheetRequestPayload data class
accordingly (or import the new constants) so the file compiles without adding
core/common as a dependency.

In
`@feature/collectionSheet/src/commonMain/kotlin/com/mifos/feature/individualCollectionSheet/newIndividualCollectionSheet/NewIndividualCollectionSheetScreen.kt`:
- Around line 93-103: The payload sends transactionDate using the wrong
formatter; in NewIndividualCollectionSheetScreen's generateCollection lambda
(where you call viewModel.getIndividualCollectionSheet with
RequestCollectionSheetPayload setting transactionDate and dateFormat =
ApiDateFormatter.DATE_FORMAT), replace the current
DateHelper.getDateAsStringFromLong(...) usage with
ApiDateFormatter.formatForApi(repaymentDate) (same pattern used in
SavingsAccountScreen.kt) so the string matches the declared "dd MMMM yyyy"
format; apply the same change in GroupLoanAccountScreen.kt for its
transactionDate construction to prevent API validation errors.

In
`@feature/offline/src/commonMain/kotlin/com/mifos/feature/offline/syncGroupPayloads/SyncGroupPayloadsUiState.kt`:
- Around line 44-80: The dummyGroupPayloads list contains GroupPayloadEntity
entries whose activationDate and submittedOnDate strings use ISO format
("yyyy-MM-dd") while dateFormat is set to ApiDateFormatter.DATE_FORMAT ("dd MMMM
yyyy"); update the three GroupPayloadEntity instances so their activationDate
and submittedOnDate values match ApiDateFormatter.DATE_FORMAT (e.g., "01 January
2024", "01 February 2024", "01 March 2024") to keep the data consistent with the
declared dateFormat.

---

Duplicate comments:
In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`:
- Around line 73-79: formatForApi currently converts millis to a LocalDateTime
using TimeZone.currentSystemDefault(), which shifts UTC-midnight dates (e.g.,
DatePickerState.selectedDateMillis) backward for UTC−1..−12 users; change the
timezone used in formatForApi (the Instant.toLocalDateTime call) from
TimeZone.currentSystemDefault() to TimeZone.UTC so the input midnight UTC millis
stays on the intended date when formatted by formatDateTime.

In
`@core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/LoanDisbursement.kt`:
- Line 12: The build fails because ApiDateFormatter used in LoanDisbursement.kt
(and similarly in LoanApproval.kt) comes from the core:common module which is
not declared as a dependency of core/model; add the missing dependency for
':core:common' to the core/model module's build configuration so
ApiDateFormatter is available to the LoanDisbursement file (and the other files
referenced: LoanApproval.kt and lines 26-28) and then rebuild.

In
`@core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/SavingsApproval.kt`:
- Line 12: The import com.mifos.core.common.utils.ApiDateFormatter in
SavingsApproval.kt fails because core/model is missing the core:common
dependency; add implementation(projects.core.common) to
core/model/build.gradle.kts (same fix applied for LoanApproval.kt) so the
ApiDateFormatter and other types from core.common resolve and the file compiles.

In
`@core/model/src/commonMain/kotlin/com/mifos/core/model/objects/users/UserLocation.kt`:
- Line 38: The default for the UserLocation.dateFormat field is using
DateFormatPattern.FULL_MONTH_WITH_TIME.pattern (a datetime format) which is
wrong because UserLocation has separate startTime/stopTime; change the default
to DateFormatPattern.FULL_MONTH.pattern so the API receives a date-only format;
locate the dateFormat property in the UserLocation class and replace the
FULL_MONTH_WITH_TIME reference with FULL_MONTH.
- Around line 12-13: The build is failing because UserLocation.kt (and related
imports at lines 38-40) use types from the core:common module but core/model's
Gradle settings don't declare that dependency; add a project dependency on the
core:common module in core/model's commonMain implementation (or api)
dependencies so classes referenced in UserLocation.kt (e.g., ApiDateFormatter,
DateFormatPattern and any other symbols imported around lines 38-40) are
available at compile time.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/groupLoanAccount/GroupLoanAccountScreen.kt`:
- Around line 586-613: The expectedDisbursementDate and submittedOnDate
assignments on the GroupLoanPayload currently call
DateHelper.getDateAsStringFromLong(...) which may not match the declared
dateFormat (ApiDateFormatter.DATE_FORMAT); replace those two calls so both
expectedDisbursementDate and submittedOnDate use
ApiDateFormatter.formatForApi(disbursementDate) and
ApiDateFormatter.formatForApi(submissionDate) respectively to guarantee the "dd
MMMM yyyy" format and keep format logic consistent with dateFormat on
GroupLoanPayload.

In
`@feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountActivate/SavingsAccountActivateScreen.kt`:
- Around line 234-241: The activation request is sending approvalDate.toString()
(epoch millis) which doesn't match ApiDateFormatter.DATE_FORMAT; update the
onClick handler in SavingsAccountActivateScreen (where
activateSavings.invoke(...) is called) to convert approvalDate (Long) into a
formatted date string using ApiDateFormatter.DATE_FORMAT and
ApiDateFormatter.LOCALE (same approach used in SavingsAccountScreen.kt), then
put that formatted string into the "activatedOnDate" entry of the hashMap before
calling activateSavings.invoke.

In
`@feature/savings/src/commonMain/kotlin/com/mifos/feature/savings/savingsAccountv2/SavingsAccountViewModel.kt`:
- Around line 147-155: The submittedOnDate assigned in submitSavingsApplication
may not match ApiDateFormatter.DATE_FORMAT because state.submissionDate comes
from DateHelper.getDateAsStringFromLong; update submitSavingsApplication to
explicitly format the timestamp/string into the expected format before setting
SavingsPayload.submittedOnDate (use a formatting helper that produces
ApiDateFormatter.DATE_FORMAT or add a formatter call around
state.submissionDate), ensuring the value assigned to submittedOnDate matches
ApiDateFormatter.DATE_FORMAT; reference the submitSavingsApplication function,
SavingsPayload.submittedOnDate, ApiDateFormatter.DATE_FORMAT, and
DateHelper.getDateAsStringFromLong when making the change.

---

Nitpick comments:
In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`:
- Around line 147-149: The getDateFormatString function is a trivial one-liner
that merely returns pattern.pattern and adds no value over accessing
DateFormatPattern.pattern directly; remove the getDateFormatString function from
ApiDateFormatter (delete its declaration) and update any callers to use
DateFormatPattern.<name>.pattern (or the pattern variable directly) instead,
ensuring references to getDateFormatString are replaced with direct access to
the DateFormatPattern.pattern property.
- Around line 98-111: The formatForApi(date: LocalDate, pattern:
DateFormatPattern) contains a duplicated when block also present in
formatDateTime(LocalDateTime, DateFormatPattern); instead of duplicating logic,
delegate: construct a midnight LocalDateTime from the LocalDate (e.g.,
date.atStartOfDay()) and call formatDateTime(...) with that LocalDateTime and
the same pattern so the shared when logic lives only in formatDateTime; update
formatForApi(LocalDate, ...) to return the result of that call.
- Around line 49-54: ApiDateFormatter exposes redundant computed properties
dateFormat and locale that just delegate to the constants DATE_FORMAT and
LOCALE; remove the computed properties (dateFormat and locale) from
ApiDateFormatter and update all call sites to reference the constants
DATE_FORMAT and LOCALE directly (search for ApiDateFormatter.dateFormat /
.locale usages and replace with ApiDateFormatter.DATE_FORMAT / .LOCALE or the
appropriate import). Ensure no behavioral change and run tests/compile to catch
any missed references.

In
`@core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerClient.kt`:
- Around line 544-549: In DataManagerClient.kt within the createCollateral call,
CollateralPayload is still using the hard-coded locale string "en"; replace that
literal with ApiDateFormatter.LOCALE so the payload uses the centralized locale
constant (update the payload construction in the
createCollateral/CollateralPayload site to use ApiDateFormatter.LOCALE).

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@core/model/src/commonMain/kotlin/com/mifos/core/model/objects/users/UserLocation.kt`:
- Around line 37-39: The dateFormat property in the UserLocation data class is
set to DateConstants.DATE_FORMAT_WITH_TIME but UserLocation already has
startTime/stopTime fields, so change the default to DateConstants.DATE_FORMAT
(the date-only format) to avoid mismatches; update the dateFormat declaration in
UserLocation to use DateConstants.DATE_FORMAT and ensure any
serialization/deserialization or tests referencing UserLocation.dateFormat are
adjusted accordingly.

therajanmaurya and others added 2 commits February 20, 2026 20:22
…patibility

The kotlinx.datetime.Month enum doesn't have a 'number' property.
Use LocalDate.monthNumber and LocalDateTime.monthNumber instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added missing import for Constants class used for ACTIVATE_CLIENT,
ACTIVATE_CENTER, and ACTIVATE_GROUP constants.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

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

🧹 Nitpick comments (2)
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt (2)

143-162: LocalDateTime used fully-qualified; missing import.

kotlinx.datetime.LocalDateTime is written in-line on line 144 because it is absent from the import block (lines 12–16). Add the import to keep the signature readable and consistent with the rest of the file.

♻️ Proposed fix
 import kotlinx.datetime.LocalDate
+import kotlinx.datetime.LocalDateTime
 import kotlinx.datetime.TimeZone
 import kotlinx.datetime.toLocalDateTime
-    private fun formatDateTime(
-        dateTime: kotlinx.datetime.LocalDateTime,
+    private fun formatDateTime(
+        dateTime: LocalDateTime,
         pattern: DateFormatPattern,
     ): String {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 143 - 162, The function signature for formatDateTime uses a
fully-qualified kotlinx.datetime.LocalDateTime; add an import for
kotlinx.datetime.LocalDateTime at the top of the file and update the signature
parameter type to LocalDateTime to match the file's style (affects the
formatDateTime function and keeps consistency with other usages and
DateFormatPattern handling such as FULL_MONTH, ISO, and FULL_MONTH_WITH_TIME).

164-188: Duplicate month-name resolution — DRY violation between toFullName() and getMonthName().

toFullName() / toShortName() (lines 164–170) derive month names from kotlinx.datetime.Month.name, while getMonthName(Int) (lines 172–188) repeats the same 12-name mapping as a hardcoded when expression. A Month can be acquired from LocalDate.month or constructed using the Month factory function that accepts the month number. This means getMonthName can simply delegate to the existing extension, removing the second source of truth and future divergence risk.

♻️ Proposed refactor

Add the Month import:

 import kotlinx.datetime.LocalDate
+import kotlinx.datetime.Month
 import kotlinx.datetime.TimeZone

Replace the 14-line when expression with a single delegation:

 private fun getMonthName(month: Int): String {
-    return when (month) {
-        1 -> "January"
-        2 -> "February"
-        3 -> "March"
-        4 -> "April"
-        5 -> "May"
-        6 -> "June"
-        7 -> "July"
-        8 -> "August"
-        9 -> "September"
-        10 -> "October"
-        11 -> "November"
-        12 -> "December"
-        else -> throw IllegalArgumentException("Invalid month: $month")
-    }
+    require(month in 1..12) { "Invalid month: $month" }
+    return Month(month).toFullName()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`
around lines 164 - 188, Replace the hardcoded 12-branch when in
getMonthName(month: Int) by converting the Int to a kotlinx.datetime.Month and
delegating to the existing extension toFullName() (and use toShortName() where
appropriate), so getMonthName simply returns that extension result; update
imports to reference kotlinx.datetime.Month/Month factory as needed and remove
the duplicate name strings to keep a single source of truth (methods:
toFullName, toShortName, getMonthName; use LocalDate.month or Month factory to
obtain Month from the Int).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`:
- Around line 113-117: formatForApi and formatFromList lack validation for the
day parameter and can produce invalid dates (e.g., day=0 or day=32); update both
functions to validate that day is within 1..maxDaysForMonth where
maxDaysForMonth is derived from month (use getMonthName for month validation)
and year (handle February leap years correctly). If the day is out of range,
throw IllegalArgumentException with a clear message including the invalid
day/month/year and the allowed range; ensure the validation runs before
constructing the formatted string so only valid dates are returned.

---

Duplicate comments:
In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`:
- Around line 64-70: The formatForApi function currently converts millis to a
LocalDateTime using TimeZone.currentSystemDefault(), which causes an
off-by-one-day for DatePickerState.selectedDateMillis (UTC-midnight) for users
in UTC−1..−12; change the timezone used in formatForApi — specifically replace
TimeZone.currentSystemDefault() with TimeZone.UTC in the
Instant.fromEpochMilliseconds(millis).toLocalDateTime(...) call so dates remain
anchored to UTC when formatting for the API.

---

Nitpick comments:
In
`@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt`:
- Around line 143-162: The function signature for formatDateTime uses a
fully-qualified kotlinx.datetime.LocalDateTime; add an import for
kotlinx.datetime.LocalDateTime at the top of the file and update the signature
parameter type to LocalDateTime to match the file's style (affects the
formatDateTime function and keeps consistency with other usages and
DateFormatPattern handling such as FULL_MONTH, ISO, and FULL_MONTH_WITH_TIME).
- Around line 164-188: Replace the hardcoded 12-branch when in
getMonthName(month: Int) by converting the Int to a kotlinx.datetime.Month and
delegating to the existing extension toFullName() (and use toShortName() where
appropriate), so getMonthName simply returns that extension result; update
imports to reference kotlinx.datetime.Month/Month factory as needed and remove
the duplicate name strings to keep a single source of truth (methods:
toFullName, toShortName, getMonthName; use LocalDate.month or Month factory to
obtain Month from the Int).

@therajanmaurya
therajanmaurya merged commit a2ab067 into openMF:development Feb 20, 2026
5 checks passed
Arinyadav1 pushed a commit to Arinyadav1/mifos-x-field-officer-app that referenced this pull request Feb 24, 2026
openMF#2616)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
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