fix(common): correct date format in formatDate() for API compatibility - #2616
fix(common): correct date format in formatDate() for API compatibility#2616therajanmaurya merged 6 commits into
Conversation
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>
📝 WalkthroughWalkthroughAdds a centralized date formatting utility ( Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
…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>
There was a problem hiding this comment.
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 | 🟡 MinorDummy date strings don't match the declared
dateFormat.
dateFormat = ApiDateFormatter.DATE_FORMATdeclares the format as"dd MMMM yyyy", butactivationDate/submittedOnDateare 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
GroupPayloadEntityinstances.)🤖 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 | 🟠 MajorPayload date strings use
DateHelper.getDateAsStringFromLong()which returns"dd-MM-yyyy", butdateFormatis"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 forexpectedDisbursementDateandsubmittedOnDatecome fromDateHelper.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 useformatDate()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
transactionDateformat does not match declareddateFormat— API calls will fail with validation errors.
transactionDateis set toDateHelper.getDateAsStringFromLong(repaymentDate)(line 282), which produces dates in"dd-MM-yyyy"format (e.g.,"14-04-2016"). However,dateFormatis declared asApiDateFormatter.DATE_FORMAT("dd MMMM yyyy"). The Fineract API parses the date string using the declareddateFormat, so these must match exactly.The correct fix is to use
formatDate(repaymentDate)instead, which delegates toApiDateFormatter.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 }, ) },
repaymentDatemust be captured by the outer lambda, orformatDatecan be called at the call site (line 282) before passing togenerateCollection.🤖 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
submittedOnDateformat 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"), butstate.submissionDateis initialized viaDateHelper.getDateAsStringFromLong()which produces"dd-MM-yyyy"format (e.g., "18-02-2026"). The v1 screen (SavingsAccountScreen.ktline 481) explicitly usesApiDateFormatter.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.submissionDateAlternatively, store
submissionDateas aLonginSavingsAccountState(parallel to v1) and format it at the call site viaApiDateFormatter.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:getDateFormatStringis redundant —DateFormatPattern.patternis 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.
dateFormatandlocaleare instance-property aliases forDATE_FORMATandLOCALEconstants and serve no additional purpose. Consumers callingApiDateFormatter.DATE_FORMATdirectly 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: Duplicatewhenexpressions informatForApi(LocalDate, …)andformatDateTime(LocalDateTime, …).Both methods implement the same six-branch pattern for mapping
DateFormatPatternto 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, …)andformatDateTime(dateTime: LocalDateTime, …)tobuildDateString(…).🤖 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: Missedlocale = "en"increateCollateral— not updated toApiDateFormatter.LOCALE.All other payload constructions in this file were centralized in this PR, but
createCollateralstill 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:localedisplay value is disconnected fromApiDateFormatter.LOCALEThe
localestate variable (used to display locale in the UI at line 265) is still hardcoded to"en". The payload at line 280 now correctly usesApiDateFormatter.LOCALE. IfApiDateFormatter.LOCALEever 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>
There was a problem hiding this comment.
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 | 🟡 MinorDummy payload date strings are in ISO format (
yyyy-MM-dd) butdateFormatclaims"dd MMMM yyyy"— inconsistent.
activationDate = "2024-01-01"andsubmittedOnDate = "2024-01-01"are inyyyy-MM-ddformat across all three entries, whiledateFormat = ApiDateFormatter.DATE_FORMATasserts"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 | 🔴 CriticalBuild-breaking:
ApiDateFormatteris unresolvable incore/model— cannot addcore/commondependency due to circular dependencyThe pipeline confirms three compile errors at lines 12, 23, and 25 because
core/modeldoes not declare a dependency oncore/common. However, addingcore/commonas a dependency tocore/modelis not viable:core/commonalready depends oncore/model(viaapi(projects.core.model)in its build.gradle.kts), which would create a circular dependency and break the build.Options to resolve:
- Move
ApiDateFormattertocore/modelitself (simplest for a utility constant).- Create a new foundational shared module (e.g.,
core/constants) that both modules can safely depend on without cycles.- 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
transactionDateformat mismatches the declareddateFormatThe date being sent (
transactionDate) is formatted byDateHelper.getDateAsStringFromLong(), which returns"dd-MM-yyyy", but the payload declaresdateFormat = 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 declareddateFormat. This pattern is already correctly applied inSavingsAccountScreen.ktline 481.The same issue exists in
GroupLoanAccountScreen.ktlines 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:getDateFormatStringis a trivial one-liner that adds no value overpattern.pattern.Every caller can access
DateFormatPattern.FULL_MONTH.patterndirectly. 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: Duplicatewhenblock logic betweenformatForApi(LocalDate, DateFormatPattern)andformatDateTime.Both functions contain an identical six-branch
whenblock; the only difference is thatformatForApi(LocalDate, ...)hardcodes"00:00"for the time component whileformatDateTimeuses actualhour/minutevalues. TheLocalDateoverload can delegate directly toformatDateTimeby constructing a midnightLocalDateTime:♻️ 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 forDATE_FORMATandLOCALE.
dateFormatandlocalesimply delegate toDATE_FORMATandLOCALE, 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 increateCollateral
CollateralPayloadat line 547 still uses the literal"en"instead ofApiDateFormatter.LOCALE, making it the only call site in this file not updated by the refactor. WhileApiDateFormatter.LOCALElikely 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).
There was a problem hiding this comment.
🤖 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.
…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>
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/ApiDateFormatter.kt (2)
143-162:LocalDateTimeused fully-qualified; missing import.
kotlinx.datetime.LocalDateTimeis 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 betweentoFullName()andgetMonthName().
toFullName()/toShortName()(lines 164–170) derive month names fromkotlinx.datetime.Month.name, whilegetMonthName(Int)(lines 172–188) repeats the same 12-name mapping as a hardcodedwhenexpression. AMonthcan be acquired fromLocalDate.monthor constructed using theMonthfactory function that accepts the month number. This meansgetMonthNamecan simply delegate to the existing extension, removing the second source of truth and future divergence risk.♻️ Proposed refactor
Add the
Monthimport:import kotlinx.datetime.LocalDate +import kotlinx.datetime.Month import kotlinx.datetime.TimeZoneReplace the 14-line
whenexpression 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).
openMF#2616) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>



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
formatDate()function was returning dates indd/MM/yyyyformat (e.g., "18/02/2026")dateFormatasdd MMMM yyyy(expecting "18 February 2026")YYYYinstead ofyyyy(wrong year pattern)Solution
Created a centralized date formatting system:
DateConstantsincore/model- Single source of truth for date format stringsApiDateFormatterincore/common- Utility for formatting dates to API-compatible stringsDateFormatPatternenum incore/common- All supported date format patternsArchitecture
Changes
New Files
DateConstants.ktDATE_FORMAT,LOCALE,DATE_FORMAT_WITH_TIMEApiDateFormatter.ktformatForApi()methodsDateFormatPattern.ktUpdated Files (28 files)
Core Model (6 files)
CollectionSheetRequestPayload.kt- UseDateConstantsLoanApproval.kt- UseDateConstantsLoanApprovalRequest.kt- Fixeddd MM yyyy→dd MMMM yyyy, useDateConstantsLoanDisbursement.kt- UseDateConstantsSavingsApproval.kt- UseDateConstantsUserLocation.kt- UseDateConstants.DATE_FORMAT_WITH_TIMECore Network (4 files)
Payload.kt- FixedYYYY→yyyy, useApiDateFormatterDefaultPayload.kt- FixedYYYY→yyyy, useApiDateFormatterIndividualCollectionSheetPayload.kt- FixedYYYY→yyyy, useApiDateFormatterDataManagerClient.kt- UseApiDateFormatterCore Database (2 files)
CollectionSheetPayload.kt- UseApiDateFormatterProductiveCollectionSheetPayload.kt- UseApiDateFormatterFeature Modules (14 files)
CreateNewClientScreen.kt,ClientEditDetailsScreen.ktActivateScreen.ktCreateNewCenterScreen.ktCreateNewGroupScreen.ktGroupLoanAccountScreen.kt,LoanChargeDialogScreen.ktSavingsAccountScreen.kt,SavingsAccountActivateScreen.kt,SavingsAccountViewModel.ktNewIndividualCollectionSheetScreen.ktSyncGroupPayloadsUiState.ktDataTableListViewModel.ktUsage Example
Test Plan
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
Refactor