diff --git a/cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt b/cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt index 3b9126979..270f276a0 100644 --- a/cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt +++ b/cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt @@ -3475,6 +3475,86 @@ | | +--- org.jetbrains.compose.components:components-resources:1.9.3 -> 1.10.0 (*) | | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.9.3 (*) | | \--- project :core:common (*) +| +--- project :libs:mifos-loans +| | +--- androidx.lifecycle:lifecycle-runtime-compose:2.9.4 (*) +| | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.9.4 (*) +| | +--- androidx.tracing:tracing-ktx:1.3.0 (*) +| | +--- io.insert-koin:koin-bom:4.1.1 (*) +| | +--- io.insert-koin:koin-android:4.1.1 (*) +| | +--- io.insert-koin:koin-androidx-compose:4.1.1 (*) +| | +--- io.insert-koin:koin-androidx-navigation:4.1.1 (*) +| | +--- io.insert-koin:koin-core-viewmodel:4.1.1 (*) +| | +--- io.github.kevinnzou:compose-webview-multiplatform:2.0.3 +| | | \--- io.github.kevinnzou:compose-webview-multiplatform-android:2.0.3 +| | | +--- androidx.activity:activity-compose:1.10.1 -> 1.12.2 (*) +| | | +--- androidx.webkit:webkit:1.13.0 +| | | | +--- androidx.annotation:annotation:1.8.1 -> 1.9.1 (*) +| | | | +--- androidx.annotation:annotation-experimental:1.4.1 -> 1.5.1 (*) +| | | | +--- androidx.core:core:1.1.0 -> 1.17.0 (*) +| | | | \--- org.jspecify:jspecify:1.0.0 +| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (*) +| | | +--- org.jetbrains.compose.runtime:runtime:1.8.0 -> 1.10.1 (*) +| | | +--- org.jetbrains.compose.foundation:foundation:1.8.0 -> 1.10.0 (*) +| | | +--- org.jetbrains.compose.components:components-resources:1.8.0 -> 1.10.0 (*) +| | | +--- co.touchlab:kermit:2.0.3 -> 2.0.8 (*) +| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2 (*) +| | | \--- org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1 -> 1.9.0 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.2.21 (*) +| | +--- io.insert-koin:koin-core:4.1.1 (*) +| | +--- io.insert-koin:koin-annotations:2.1.0 (*) +| | +--- project :core:ui (*) +| | +--- project :core:designsystem (*) +| | +--- project :core:data (*) +| | +--- io.insert-koin:koin-compose:4.1.1 (*) +| | +--- io.insert-koin:koin-compose-viewmodel:4.1.1 (*) +| | +--- org.jetbrains.compose.runtime:runtime:1.9.3 -> 1.10.1 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.9.6 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.9.6 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.9.6 (*) +| | +--- org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.9.6 (*) +| | +--- org.jetbrains.androidx.savedstate:savedstate:1.4.0 (*) +| | +--- org.jetbrains.androidx.core:core-bundle:1.0.1 (*) +| | +--- org.jetbrains.androidx.navigation:navigation-compose:2.9.1 (*) +| | +--- org.jetbrains.androidx.navigationevent:navigationevent-compose:1.0.1 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0 (*) +| | +--- io.github.openmf:mifos-authenticator-passcode:2.3.0-beta (*) +| | +--- io.github.openmf:mifos-authenticator-biometrics:2.3.0-beta (*) +| | +--- org.jetbrains.compose.ui:ui:1.9.3 -> 1.10.1 (*) +| | +--- org.jetbrains.compose.foundation:foundation:1.9.3 -> 1.10.0 (*) +| | +--- org.jetbrains.compose.material3:material3:1.9.0-beta03 -> 1.9.0 (*) +| | +--- org.jetbrains.compose.material:material-icons-extended:1.7.3 (*) +| | +--- org.jetbrains.compose.components:components-resources:1.9.3 -> 1.10.0 (*) +| | +--- org.jetbrains.compose.components:components-ui-tooling-preview:1.9.3 (*) +| | +--- io.github.vinceglb:filekit-core:0.12.0 (*) +| | +--- io.github.vinceglb:filekit-dialogs:0.12.0 (*) +| | +--- io.github.vinceglb:filekit-dialogs-compose:0.12.0 (*) +| | +--- io.github.niyajali:compose-signature:1.0.1 +| | | \--- io.github.niyajali:compose-signature-android:1.0.1 +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.21 -> 2.2.21 (*) +| | | +--- org.jetbrains.compose.runtime:runtime:1.8.1 -> 1.10.1 (*) +| | | +--- org.jetbrains.compose.ui:ui:1.8.1 -> 1.10.1 (*) +| | | +--- org.jetbrains.compose.foundation:foundation:1.8.1 -> 1.10.0 (*) +| | | +--- org.jetbrains.compose.material3:material3:1.8.1 -> 1.9.0 (*) +| | | +--- org.jetbrains.compose.components:components-resources:1.8.1 -> 1.10.0 (*) +| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2 (*) +| | | \--- org.jetbrains.kotlinx:kotlinx-datetime:0.6.2 -> 0.7.1 (*) +| | +--- project :core-base:ui (*) +| | +--- project :core-base:common +| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2 (*) +| | | +--- co.touchlab:kermit:2.0.8 (*) +| | | +--- com.squareup.okio:okio:3.16.4 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.2.21 (*) +| | | +--- org.jetbrains.kotlinx:kotlinx-datetime:0.7.1 (*) +| | | +--- io.insert-koin:koin-bom:4.1.1 (*) +| | | +--- io.insert-koin:koin-core:4.1.1 (*) +| | | +--- io.insert-koin:koin-annotations:2.1.0 (*) +| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2 (*) +| | | \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.2.21 (*) +| | +--- project :core-base:network (*) +| | +--- androidx.appcompat:appcompat:1.7.1 (*) +| | \--- androidx.core:core-ktx:1.17.0 (*) | +--- project :feature:pocket | | +--- androidx.lifecycle:lifecycle-runtime-compose:2.9.4 (*) | | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.9.4 (*) diff --git a/cmp-android/dependencies/prodReleaseRuntimeClasspath.txt b/cmp-android/dependencies/prodReleaseRuntimeClasspath.txt index 50f80cd23..bade1072d 100644 --- a/cmp-android/dependencies/prodReleaseRuntimeClasspath.txt +++ b/cmp-android/dependencies/prodReleaseRuntimeClasspath.txt @@ -1,4 +1,5 @@ :cmp-shared +:core-base:common :core-base:datastore :core-base:designsystem :core-base:network @@ -41,6 +42,7 @@ :feature:transfer-interbank :feature:transfer-intrabank :feature:upi-setup +:libs:mifos-loans androidx.activity:activity-compose:1.12.2 androidx.activity:activity-ktx:1.12.2 androidx.activity:activity:1.12.2 @@ -213,6 +215,7 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 +androidx.webkit:webkit:1.13.0 androidx.window:window-core-android:1.5.0 androidx.window:window-core:1.5.0 androidx.window:window:1.5.0 @@ -345,6 +348,10 @@ io.github.jan-tennert.supabase:postgrest-kt-android:3.1.1 io.github.jan-tennert.supabase:postgrest-kt:3.1.1 io.github.jan-tennert.supabase:supabase-kt-android:3.1.1 io.github.jan-tennert.supabase:supabase-kt:3.1.1 +io.github.kevinnzou:compose-webview-multiplatform-android:2.0.3 +io.github.kevinnzou:compose-webview-multiplatform:2.0.3 +io.github.niyajali:compose-signature-android:1.0.1 +io.github.niyajali:compose-signature:1.0.1 io.github.niyajali:fluentui-system-icons-android:1.0.0 io.github.niyajali:fluentui-system-icons:1.0.0 io.github.openmf:mifos-authenticator-biometrics-android:2.3.0-beta diff --git a/cmp-shared/build.gradle.kts b/cmp-shared/build.gradle.kts index d57693125..9637793dd 100644 --- a/cmp-shared/build.gradle.kts +++ b/cmp-shared/build.gradle.kts @@ -60,6 +60,7 @@ kotlin { implementation(projects.feature.passcode) implementation(projects.feature.autopay) implementation(projects.feature.sendMoney) + implementation(projects.libs.mifosLoans) implementation(projects.feature.pocket) } diff --git a/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt b/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt index 9784c34b6..8dafc4ddd 100644 --- a/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt +++ b/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt @@ -16,6 +16,7 @@ import org.koin.dsl.koinApplication import org.koin.dsl.module import org.mifos.authenticator.passcode.PasscodeManager import org.mifos.feature.passcode.MifosAuthenticatorModule +import org.mifos.lib.loan.di.LoanApplicationModule import org.mifospay.core.common.di.DispatchersModule import org.mifospay.core.common.di.stringProviderModule import org.mifospay.core.data.di.RepositoryModule @@ -119,6 +120,7 @@ object KoinModules { MifosAuthenticatorModule, AutoPayModule, SendMoneyModule, + LoanApplicationModule, PocketModule, ) } diff --git a/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt b/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt index 5c76521cd..5043258fc 100644 --- a/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt +++ b/cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt @@ -23,6 +23,8 @@ import org.jetbrains.compose.resources.stringResource import org.koin.compose.koinInject import org.mifos.feature.passcode.internalMifosPasscodeScreen import org.mifos.feature.passcode.navigateToInternalMifosPasscodeScreen +import org.mifos.lib.loan.navigation.loanApplicationGraph +import org.mifos.lib.loan.navigation.navigateToLoanApplicationGraph import org.mifospay.core.data.repository.UserVerificationRepository import org.mifospay.core.ui.utility.TabContent import org.mifospay.feature.accounts.AccountsScreen @@ -271,6 +273,9 @@ internal fun MifosNavHost( onAddEditSavingsAccount = navController::navigateToSavingAccountAddEdit, onViewSavingAccountDetails = navController::navigateToSavingAccountDetails, onAddOrEditBeneficiary = navController::navigateToBeneficiaryAddEdit, + onApplyForLoanClick = { clientId -> + navController.navigateToLoanApplicationGraph(clientId) + }, ) }, @@ -869,6 +874,22 @@ internal fun MifosNavHost( }, ) + loanApplicationGraph( + navController = navController, + navigateBack = navController::navigateUp, + navigateForPasscodeVerification = { verificationKey -> + navController.navigateToInternalMifosPasscodeScreen(verificationKey) + }, + onSubmitSuccess = { + navController.navigate(FINANCE_ROUTE) { + popUpTo(FINANCE_ROUTE) { + inclusive = false + } + launchSingleTop = true + } + }, + ) + transferSuccessScreen( navigateBack = { returnDestination -> when (returnDestination) { diff --git a/docs/LOAN_APPLICATION_ARCHITECTURE.md b/docs/LOAN_APPLICATION_ARCHITECTURE.md new file mode 100644 index 000000000..0ec47a6a4 --- /dev/null +++ b/docs/LOAN_APPLICATION_ARCHITECTURE.md @@ -0,0 +1,196 @@ +# Loan Application Feature — Architecture & Provider Selection + +## Overview + +`libs/mifos-loans` is a self-contained Kotlin Multiplatform module implementing a 6-step +loan-application wizard, ported from mifos-mobile's `feature/loan-application` and re-architected +so the app can plug in different lender backends behind a single UI flow. + +``` +Select Loan Provider → Select Loan Type → Loan Product Details → Loan Apply → Upload Docs → Confirm Details +``` + +The module owns its full vertical slice: domain models, its own Ktorfit network client, mappers, +provider adapters, a repository facade, DI, and all 6 screens. The host app (`cmp-shared`) only +knows about one entry point (`navigateToLoanApplicationGraph`) and one Koin module +(`LoanApplicationModule`). + +--- + +## Why a provider-selection step exists + +The product requirement was: tapping "Apply for Loan" should first show a choice of lenders +(HDFC Bank, SBI, ICICI Bank, Axis Bank today — illustrative names, not live integrations), *then* +proceed into the application flow. The rest of this document explains how that choice is threaded +through the wizard and turned into an actual backend selection, even though today every provider +still resolves to the same mock data. + +--- + +## Package layout + +``` +libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ +├── core/ LoanProvider interface + all domain models (LoanTemplate, LoansPayload, ...) +├── network/ Ktorfit LoansService + DTOs + the library's OWN Ktorfit engine (di/LoansNetworkModule.kt) +├── mapper/ DTO → domain model conversions +├── providers/ Concrete LoanProvider adapters + the registry that resolves between them +│ ├── FineractLoanProvider.kt real Fineract-backed adapter (not active yet) +│ ├── DummyLoanProvider.kt mock adapter (currently backs every provider id) +│ └── LoanProviderRegistry.kt resolves a LoanProvider by providerId +├── repository/ +│ ├── LoansRepository.kt facade interface — this is what ViewModels inject +│ └── LoansRepositoryImpl.kt delegates every call to LoanProviderRegistry.resolve(providerId) +├── di/LoanApplicationModule.kt Koin bindings for all of the above + the 6 screen ViewModels +├── ui/ the 6 screens of the wizard +│ ├── selectLoanProvider/ Screen 1 +│ ├── selectLoanType/ Screen 2 +│ ├── loanProductDetails/ Screen 3 +│ ├── loanApply/ Screen 4 +│ ├── uploadDocs/ Screen 5 +│ └── confirmDetails/ Screen 6 +├── navigation/LoanApplicationNavGraph.kt wires all 6 screens together +└── component/ shared Compose UI pieces (LoanCard, etc.) +``` + +--- + +## How `providerId` flows through the wizard + +**The core design decision:** the selected provider is carried as a plain field on every screen's +navigation route (`providerId: String`) — the exact same mechanism already used for `clientId`. +It is **not** a shared mutable singleton/holder. + +Why that matters: Android can kill the app process mid-flow (backgrounding, memory pressure). The +nav backstack and each screen's arguments survive this via `SavedStateHandle` — a singleton held +in a Koin container does not. Since this flow ends in a non-reversible submit, silently losing +"which provider you were applying through" after a process restart would be a real correctness bug. +Threading it through routes costs one extra field per screen and gets this for free. + +Concretely, every route in the chain looks like this (fields accumulate as the wizard progresses): + +```kotlin +SelectLoanProviderRoute(clientId) +SelectLoanTypeRoute(clientId, providerId) +LoanProductDetailsRoute(clientId, productId, providerId) +LoanApplyRoute(clientId, productId, providerId) +UploadDocsRoute(clientId, productId, providerId, applicantName, loanProductName, loanPurpose, disbursementDate, principalAmount) +ConfirmDetailsRoute(clientId, productId, providerId, applicantName, loanProductName, loanPurpose, disbursementDate, principalAmount) +``` + +Each screen's ViewModel reads `providerId` off its own route via `savedStateHandle.toRoute()`, +keeps it in its UI state, and passes it forward when it navigates to the next screen — mirroring +exactly how `clientId` and `productId` are already threaded. + +--- + +## The resolution mechanism: registry + facade + +### 1. `LoanProvider` (interface, in `core/`) — unchanged, the seam every adapter implements + +```kotlin +interface LoanProvider { + fun getLoanTemplate(clientId: Long): Flow> + fun getLoanTemplateByProduct(clientId: Long, productId: Long): Flow> + suspend fun submitLoanApplication(loanState: LoanState, payload: LoansPayload, loanId: Long): DataState +} +``` + +### 2. `providers/` — concrete adapters + +- **`DummyLoanProvider`** — no network calls, returns fabricated `LoanTemplate`/success data. Currently backs every provider id. +- **`FineractLoanProvider`** — the real adapter, calls the library's own `LoansService` (Ktorfit) against the live Fineract self-service API. Built and wired, but not reachable from the UI today (see "Current state" below). + +### 3. `LoanProviderRegistry` — resolves an id to an adapter + +```kotlin +class LoanProviderRegistry( + private val providersById: Map, + private val defaultProvider: LoanProvider, +) { + fun resolve(providerId: String): LoanProvider = providersById[providerId] ?: defaultProvider +} +``` + +Bound in `LoanApplicationModule.kt` today as: + +```kotlin +providersById = mapOf( + "hdfc" to dummy, + "sbi" to dummy, + "icici" to dummy, + "axis" to dummy, + "fineract" to get(), +), +defaultProvider = dummy, +``` + +All four UI-facing ids point at the **same** `DummyLoanProvider` instance — this is the "current +state" honesty: selecting HDFC vs SBI vs ICICI vs Axis produces identical data today. `"fineract"` +is registered and ready, just not selectable from the current 4-item provider list. + +### 4. `LoansRepository` — the facade ViewModels actually inject + +None of the 4 data-consuming ViewModels (`SelectLoanTypeViewModel`, `LoanProductDetailsViewModel`, +`LoanApplyViewModel`, `ConfirmDetailsViewModel`) inject `LoanProvider` directly anymore. They inject +`LoansRepository`, which takes `providerId` as a parameter on every call and resolves through the +registry: + +```kotlin +interface LoansRepository { + fun getLoanTemplate(providerId: String, clientId: Long): Flow> + fun getLoanTemplateByProduct(providerId: String, clientId: Long, productId: Long): Flow> + suspend fun submitLoanApplication(providerId: String, loanState: LoanState, payload: LoansPayload, loanId: Long): DataState +} +``` + +`LoansRepositoryImpl` is a one-line delegation: `registry.resolve(providerId).getLoanTemplate(clientId)`, etc. + +--- + +## Walkthrough: user taps "SBI" + +1. **Select Loan Provider screen** — `SelectLoanProviderViewModel` holds a static list (`defaultLoanProviders`, ids `"hdfc"`/`"sbi"`/`"icici"`/`"axis"`). Tapping SBI dispatches `ProviderClicked("sbi")`, which now forwards `providerId = "sbi"` into the next navigation call (previously this was discarded — that was the bug fixed). +2. **Select Loan Type screen** — receives `SelectLoanTypeRoute(clientId, providerId = "sbi")`. Its ViewModel calls `loansRepository.getLoanTemplate(providerId = "sbi", clientId)`. `LoansRepositoryImpl` calls `registry.resolve("sbi")` → returns the shared `DummyLoanProvider` → mock `LoanTemplate` comes back, screen renders the same product list it always does. +3. **Loan Product Details / Loan Apply / Upload Docs** — each screen reads `providerId` off its own route and forwards it to the next, same as `clientId`. +4. **Confirm Details screen** — on submit, calls `loansRepository.submitLoanApplication(providerId = "sbi", ...)` → resolves to `DummyLoanProvider` → returns a mock success `DataState.Success(...)`. + +If `FineractLoanProvider` were registered under `"sbi"` instead of the shared dummy, step 2 and 4 +above would transparently start hitting the real Fineract API — **no changes needed in any +screen or ViewModel**, only in the one map entry inside `LoanApplicationModule.kt`. + +--- + +## The library's own network engine + +`libs/mifos-loans` does not depend on the host app's `core/network` module for HTTP anymore. +`network/di/LoansNetworkModule.kt` builds its own `Ktorfit` instance (qualifier `"LoansClient"`), +using the same generic building blocks the app's own client uses: + +- `httpClient()` / `setupDefaultHttpClient()` / `DynamicBaseUrlPlugin` / `DynamicLoggableHosts` — generic Ktor plumbing from `core-base:network` +- `KtorInterceptor` reading `UserPreferencesRepository.authToken` — so it still authenticates against the same logged-in session, just via its own client instance +- `UrlType.SELF_SERVICE` — same Fineract self-service base URL the rest of the app uses + +`core/network` no longer contains any loan-specific code (`LoansService`, loan DTOs, +`ApiEndPoints.LOANS` were all removed from it). + +--- + +## Current state — what's real vs. placeholder + +| Piece | Status | +|---|---| +| 6-screen nav chain, `providerId` threading | Real, verified on-device | +| `LoanProviderRegistry` / `LoansRepository` resolution mechanism | Real | +| `DummyLoanProvider` bound for all 4 UI provider ids | Active (intentional — "no APIs for now") | +| `FineractLoanProvider` + library's own Ktorfit engine | Built, compiles, registered under `"fineract"` — **not** wired to any UI-selectable provider | +| Passcode/biometric gate before submit | Disabled (commented out in `ConfirmDetailsViewModel`, easy to restore) | +| Fineract endpoint shapes (`loans/template?templateType=individual`, `POST loans`) | Ported as-is from mifos-mobile — **do not yet match** the simpler ticket-specified shape (`GET /self/loanproducts`, `POST /self/loans`, `GET /self/loans/{id}`, `POST /self/loans/{id}/transactions`) — deferred by request | + +## To activate a real provider later + +1. Build a concrete `LoanProvider` implementation (can reuse `FineractLoanProvider` if the ticket's + simpler endpoint shape is implemented on `LoansService`, or write a new adapter class in `providers/`). +2. Bind it in `LoanApplicationModule.kt` and swap the relevant entry in `LoanProviderRegistry`'s + `providersById` map (e.g. `"sbi" to get()` instead of `"sbi" to dummy`). +3. Nothing else changes — no screen, ViewModel, or route touches a concrete provider type directly. diff --git a/feature/accounts/src/commonMain/composeResources/values-es/strings.xml b/feature/accounts/src/commonMain/composeResources/values-es/strings.xml index f8c746f30..be2b10e6c 100644 --- a/feature/accounts/src/commonMain/composeResources/values-es/strings.xml +++ b/feature/accounts/src/commonMain/composeResources/values-es/strings.xml @@ -58,6 +58,7 @@ Predeterminada Editar beneficiario Eliminar beneficiario + Solicitar préstamo Pendiente de aprobación Aprobada diff --git a/feature/accounts/src/commonMain/composeResources/values/strings.xml b/feature/accounts/src/commonMain/composeResources/values/strings.xml index 7ce246f3a..1a46455a6 100644 --- a/feature/accounts/src/commonMain/composeResources/values/strings.xml +++ b/feature/accounts/src/commonMain/composeResources/values/strings.xml @@ -59,6 +59,7 @@ Default Edit Beneficiary Delete Beneficiary + Apply for Loan Pending Approval diff --git a/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt b/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt index a17059226..fb627164f 100644 --- a/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt +++ b/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountViewModel.kt @@ -137,6 +137,12 @@ class AccountViewModel( is AccountAction.SetDefaultAccount -> handleSetDefaultAccount(action) + is AccountAction.ApplyForLoan -> { + state.clientId?.let { clientId -> + sendEvent(AccountEvent.OnApplyForLoan(clientId)) + } + } + is DeleteBeneficiary -> handleDeleteBeneficiary(action) is BeneficiaryDeleteResultReceived -> handleBeneficiaryDeleteResult(action) @@ -238,6 +244,7 @@ sealed interface AccountEvent { data class OnAddEditSavingsAccount(val type: SavingsAddEditType) : AccountEvent data class OnNavigateToAccountDetail(val accountId: Long) : AccountEvent data class OnAddOrEditTPTBeneficiary(val type: BeneficiaryAddEditType) : AccountEvent + data class OnApplyForLoan(val clientId: Long) : AccountEvent data class ShowToast(val message: StringResource) : AccountEvent } @@ -251,6 +258,7 @@ sealed interface AccountAction { data class EditSavingsAccount(val accountId: Long) : AccountAction data class ViewAccountDetails(val accountId: Long) : AccountAction data class SetDefaultAccount(val accountId: Long, val accountNo: String) : AccountAction + data object ApplyForLoan : AccountAction data object DismissDialog : AccountAction diff --git a/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountsScreen.kt b/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountsScreen.kt index 30182783a..32072fe38 100644 --- a/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountsScreen.kt +++ b/feature/accounts/src/commonMain/kotlin/org/mifospay/feature/accounts/AccountsScreen.kt @@ -50,6 +50,7 @@ import mobile_wallet.feature.accounts.generated.resources.Res import mobile_wallet.feature.accounts.generated.resources.baseline_check import mobile_wallet.feature.accounts.generated.resources.baseline_unchecked import mobile_wallet.feature.accounts.generated.resources.feature_accounts_add +import mobile_wallet.feature.accounts.generated.resources.feature_accounts_apply_for_loan import mobile_wallet.feature.accounts.generated.resources.feature_accounts_check import mobile_wallet.feature.accounts.generated.resources.feature_accounts_default import mobile_wallet.feature.accounts.generated.resources.feature_accounts_edit @@ -97,6 +98,7 @@ fun AccountsScreen( onViewSavingAccountDetails: (Long) -> Unit, onAddEditSavingsAccount: (SavingsAddEditType) -> Unit, onAddOrEditBeneficiary: (BeneficiaryAddEditType) -> Unit, + onApplyForLoanClick: (Long) -> Unit, modifier: Modifier = Modifier, viewModel: AccountViewModel = koinViewModel(), ) { @@ -120,6 +122,10 @@ fun AccountsScreen( onAddOrEditBeneficiary(event.type) } + is AccountEvent.OnApplyForLoan -> { + onApplyForLoanClick(event.clientId) + } + is AccountEvent.ShowToast -> { scope.launch { snackbarHostState.showSnackbar(getString(event.message)) @@ -225,6 +231,9 @@ internal fun AccountsScreenContent( onClickViewAccount = { onAction(AccountAction.ViewAccountDetails(it)) }, + onApplyForLoanClick = { + onAction(AccountAction.ApplyForLoan) + }, ) } @@ -235,6 +244,7 @@ private fun AccountsList( onAccountClicked: (Long, String) -> Unit, onClickEditAccount: (Long) -> Unit, onClickViewAccount: (Long) -> Unit, + onApplyForLoanClick: () -> Unit, modifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), ) { @@ -245,6 +255,10 @@ private fun AccountsList( contentPadding = PaddingValues(KptTheme.spacing.md), verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), ) { + item { + ApplyForLoanCard(onClick = onApplyForLoanClick) + } + item { Text( text = stringResource(Res.string.feature_accounts_savings_account), @@ -402,6 +416,35 @@ private fun AccountItem( } } +@Composable +private fun ApplyForLoanCard( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Card( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = KptTheme.colorScheme.secondaryContainer, + ), + ) { + ListItem( + headlineContent = { + Text(text = stringResource(Res.string.feature_accounts_apply_for_loan)) + }, + leadingContent = { + AvatarBox( + icon = MifosIcons.CreditCard, + backgroundColor = KptTheme.colorScheme.tertiaryContainer, + ) + }, + colors = ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + } +} + @Composable private fun AccountDialogs( dialogState: AccountState.DialogState?, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 611f8c8ef..4378d3c24 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -114,6 +114,8 @@ qroseVersion = "1.0.1" okioVersion = "3.16.4" kermit = "2.0.8" fileKit = "0.12.0" +composeSignature = "1.0.1" +webviewMultiplatform = "2.0.3" wire = "5.4.0" uiBackhandler = "1.9.3" @@ -361,6 +363,8 @@ filekit-compose = { module = "io.github.vinceglb:filekit-dialogs-compose", versi filekit-dialogs = { module = "io.github.vinceglb:filekit-dialogs", version.ref = "fileKit" } filekit-dialogs-compose = { module = "io.github.vinceglb:filekit-dialogs-compose", version.ref = "fileKit" } filekit-coil = { module = "io.github.vinceglb:filekit-coil", version.ref = "fileKit" } +compose-webview-multiplatform = { module = "io.github.kevinnzou:compose-webview-multiplatform", version.ref = "webviewMultiplatform" } +compose-signature = { module = "io.github.niyajali:compose-signature", version.ref = "composeSignature" } qrose = { group = "io.github.alexzhirkevich", name="qrose", version.ref = "qroseVersion" } lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottie" } compottie = { module = "io.github.alexzhirkevich:compottie", version.ref = "compottie" } diff --git a/libs/mifos-loans/build.gradle.kts b/libs/mifos-loans/build.gradle.kts index 776bd7afb..8a6363555 100644 --- a/libs/mifos-loans/build.gradle.kts +++ b/libs/mifos-loans/build.gradle.kts @@ -10,6 +10,8 @@ plugins { alias(libs.plugins.cmp.feature.convention) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ktorfit) + alias(libs.plugins.ksp) } @@ -17,10 +19,47 @@ android { namespace = "org.mifos.lib.loan" } +kotlin { + sourceSets { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.filekit.core) + implementation(libs.filekit.dialogs) + implementation(libs.filekit.dialogs.compose) + implementation(libs.compose.signature) + implementation(projects.coreBase.ui) + implementation(projects.coreBase.common) + implementation(projects.coreBase.network) + } + + androidMain.dependencies { + implementation(libs.compose.webview.multiplatform) + } + + nativeMain.dependencies { + implementation(libs.compose.webview.multiplatform) + } + } +} + dependencies { implementation(libs.androidx.appcompat) implementation(libs.androidx.core.ktx) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.espresso.core) + + add("kspCommonMainMetadata", libs.ktorfit.ksp) + add("kspAndroid", libs.ktorfit.ksp) + add("kspJs", libs.ktorfit.ksp) + add("kspWasmJs", libs.ktorfit.ksp) + add("kspDesktop", libs.ktorfit.ksp) + add("kspIosX64", libs.ktorfit.ksp) + add("kspIosArm64", libs.ktorfit.ksp) + add("kspIosSimulatorArm64", libs.ktorfit.ksp) } \ No newline at end of file diff --git a/libs/mifos-loans/src/androidMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.android.kt b/libs/mifos-loans/src/androidMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.android.kt new file mode 100644 index 000000000..41fb0869d --- /dev/null +++ b/libs/mifos-loans/src/androidMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.android.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.multiplatform.webview.web.WebView +import com.multiplatform.webview.web.rememberWebViewState + +@Composable +actual fun PlatformWebView(url: String, modifier: Modifier) { + val state = rememberWebViewState(url) + WebView(state = state, modifier = modifier) +} diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-ar/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-ar/strings.xml new file mode 100644 index 000000000..7a2439a06 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-ar/strings.xml @@ -0,0 +1,157 @@ + + + + + + تقديم طلب قرض جديد + + + طلب القرض + + + اسم مقدم الطلب + منتج القرض + الغرض من القرض + تاريخ الصرف + المبلغ الأصلي + العملة + + + متابعة + موافق + إلغاء + تقديم طلب القرض + التالي + + + اسم مقدم الطلب مطلوب + يجب أن يتكون اسم مقدم الطلب من 4 أحرف على الأقل + لا يمكن أن يتجاوز اسم مقدم الطلب 100 حرف + يحتوي اسم مقدم الطلب على أحرف غير صالحة + + + منتج القرض مطلوب + + + المبلغ الأصلي مطلوب + الرجاء إدخال تنسيق مبلغ صالح + المبلغ الأصلي صغير جداً + المبلغ الأصلي يتجاوز الحد الأقصى + يحتوي المبلغ على عدد كبير جداً من المنازل العشرية للعملة المحددة + المبلغ ليس ضمن المضاعفات الصالحة + + + تاريخ الصرف مطلوب + الرجاء إدخال تنسيق تاريخ صالح (يوم/شهر/سنة) + يجب أن يكون تاريخ الصرف في المستقبل + تاريخ الصرف بعيد جداً في المستقبل (بحد أقصى 10 سنوات) + + + مشكلة في الخادم من جانبنا لا يمكن المتابعة، حاول مرة أخرى بعد قليل + فشل الكثير من المحاولات. الرجاء المحاولة مرة أخرى لاحقاً. + فشل تقديم طلب القرض. الرجاء المحاولة مرة أخرى. + + + تم تقديم طلب القرض بنجاح! + تم حفظ مسودة الطلب بنجاح + + + تغييرات غير محفوظة! + لديك تغييرات غير محفوظة. هل أنت متأكد أنك تريد العودة؟ + + + تجاهل + حفظ المسودة + إعادة المحاولة + إلغاء + متابعة + + + أدخل اسمك الكامل + اختر منتج القرض + أدخل مبلغ القرض + اختر تاريخ الصرف + صف الغرض باختصار + + + تم تغيير العملة. سيتم إعادة التحقق من المبلغ. + الحد الأدنى لمبلغ القرض: %1$s + الحد الأقصى لمبلغ القرض: %1$s + تأكيد التفاصيل + + تم تقديم الطلب بنجاح + تم تقديم طلب القرض الخاص بك. سيتم إعلامك بمجرد مراجعته. + العودة إلى الصفحة الرئيسية + + فشل تقديم الطلب + حدث خطأ ما أثناء تقديم طلب القرض الخاص بك. الرجاء المحاولة مرة أخرى. + حاول مرة أخرى + + + + تقديم طلب قرض جديد + اختر نوع القرض + حالياً منتجات القروض غير متوفرة + تأكد من اتصالك بالإنترنت + قبول الشروط والأحكام + + + تقديم طلب قرض %1$s + الحصول على قرض %1$s + يصل إلى %1$s + %1$s إلى %2$s فائدة + الشروط والأحكام + + الموافقة على القرض وصرفه + سعر الفائدة + متابعة + السداد + الضمان والرهن + التأمين + التعثر + التوثيق + الامتثال القانوني + التعديلات والإنهاء + الاختصاص القضائي + + + سيتم الموافقة على القرض الشخصي بناءً على أهلية مقدم الطلب، واستقرار الدخل، والجدارة الائتمانية، والسياسات الائتمانية الداخلية للمقرض. يحتفظ المقرض بالحق في الموافقة على أي طلب قرض شخصي أو رفضه وفقاً لتقديره الخاص دون إبداء أي سبب. قد يتم صرف مبلغ القرض المعتمد بالكامل أو على أجزاء، اعتماداً على اتفاقية القرض ومتطلبات التوثيق. يخضع الصرف للإكمال الناجح لإجراءات التحقق من الهوية (KYC)، والتحقق من المستندات المقدمة، والالتزام بجميع الشروط الموضحة في خطاب الموافقة. + قد يحمل القرض الشخصي سعر فائدة ثابت أو متغير، كما يختاره المقترض وقت اتفاقية القرض. في حالة القروض ذات سعر الفائدة المتغير، يخضع السعر المطبق لمراجعات دورية بناءً على التغييرات في المؤشر أو السعر المرجعي للمقرض. ستكون هذه التغييرات ملزمة للمقترض وقد تؤدي إلى تغيير في مبلغ القسط الشهري و/أو مدة القرض، حسب ما يراه المقرض ضرورياً. + سيتم سداد القرض الشخصي من خلال أقساط شهرية متساوية (EMIs) تتكون من كل من أصل الدين ومكونات الفائدة. ستبدأ الأقساط الشهرية من التاريخ المحدد في اتفاقية القرض وستستمر على أساس شهري حتى يتم سداد مبلغ القرض بالكامل، جنباً إلى جنب مع الفائدة المطبقة. قد يُسمح بالسداد المبكر أو السداد الجزئي وفقاً للسياسات السائدة للمقرض وقد يترتب عليه رسوم معينة، خاصة بالنسبة للقروض ذات السعر الثابت. + تكون القروض الشخصية عادةً غير مضمونة بطبيعتها ولا تتطلب رهناً. ومع ذلك، يحتفظ المقرض بالحق في طلب ضمانات أو تأمينات إضافية في حالات استثنائية، اعتماداً على الملف الائتماني للمقترض ومبلغ القرض. سيتم ذكر أي من هذه المتطلبات بوضوح في خطاب الموافقة. يتحمل المقترض جميع التكاليف المرتبطة بالتوثيق أو الإجراءات القانونية، إن وجدت. + على الرغم من أن القروض الشخصية لا تتطلب عموماً تأميناً على الأصول، إلا أن المقرض قد يوصي المقترض باختيار بوليصة تأمين على الحياة لحماية مبلغ القرض المستحق في حالة وفاة المقترض. هذا ليس إلزامياً ولكنه مستحسن كإجراء حماية مالية لأسرة المقترض أو المعالين. + سيشكل عدم دفع الأقساط الشهرية أو تأخر سدادها تعثراً. في مثل هذه الحالات، قد يبدأ المقرض إجراءات الاسترداد لتحصيل المستحقات القائمة، والتي قد تشمل إجراءات قانونية. قد يتم تطبيق فائدة جزائية ورسوم أخرى على المبالغ المتأخرة. يمكن أن يؤثر التعثر المستمر أو المتكرر سلباً على درجة الائتمان الخاصة بالمقترض وقدرته على الاقتراض في المستقبل. + يتعين على المقترض تقديم وثائق دقيقة وكاملة، بما في ذلك التوثيق الدقيق والكامل، بما في ذلك على سبيل المثال لا الحصر إثبات الهوية، وإثبات العنوان، وتفاصيل الدخل، وكشوفات الحساب المصرفي، وأي مستندات أخرى يطلبها المقرض. قد تؤدي أي اختلافات أو تحريف أو تزوير في المستندات المقدمة إلى رفض طلب القرض أو إنهاء اتفاقية القرض، جنباً إلى جنب مع الآثار القانونية المحتملة. + يجب على المقترض ضمان الامتثال الكامل لجميع القوانين المحلية والولائية والوطنية المطبقة فيما يتعلق بالتزاماته المالية. ويشمل ذلك دفع الضرائب في الوقت المناسب، والالتزام بالمتطلبات التنظيمية، وتقديم مستندات صالحة. لن يتحمل المقرض المسؤولية عن أي انتهاكات قانونية يرتكبها المقترض. + يجوز للمقرض، من وقت لآخر، مراجعة أو تحديث هذه الشروط والأحكام، مع مراعاة إشعار مسبق مناسب للمقترض. ستعتبر اتفاقية القرض منتهية عند السداد الكامل لمبلغ القرض أو من خلال إجراءات حبس الرهن التي بدأت بسبب التعثر. + تخضع جميع النزاعات الناشئة عن أو المتعلقة باتفاقية القرض الشخصي للاختصاص القضائي الحصري للمحاكم في موقع المكتب المسجل للمقرض، ما لم ينص الاتفاق على خلاف ذلك. + + + + + تحميل كشوف الحساب المصرفي + تحميل مستندات الرهن + تحميل التوقيع + + حدث خطأ ما! تعذر تحميل الملف + الرجاء تحميل كشف حساب مصرفي + الرجاء تحميل مستند ملكية + الرجاء تحميل توقيع + تم تحميل المستندات بنجاح! + + وقع هنا + إعادة تعيين + توقيع + التقاط + إرفاق + تحميل التوقيع + حفظ وإرسال + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-bn/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-bn/strings.xml new file mode 100644 index 000000000..cc34a7a54 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-bn/strings.xml @@ -0,0 +1,157 @@ + + + + + + নতুন ঋণের জন্য আবেদন করুন + + + ঋণের আবেদন + + + আবেদনকারীর নাম + ঋণ পণ্য + ঋণের উদ্দেশ্য + বিতরণের তারিখ + মূল পরিমাণ + মুদ্রা + + + চালিয়ে যান + ঠিক আছে + বাতিল + ঋণের জন্য আবেদন করুন + পরবর্তী + + + আবেদনকারীর নাম প্রয়োজন + আবেদনকারীর নাম অন্তত ৪ অক্ষরের হতে হবে + আবেদনকারীর নাম ১০০ অক্ষরের বেশি হতে পারবে না + আবেদনকারীর নামে অবৈধ অক্ষর রয়েছে + + + ঋণ পণ্য প্রয়োজন + + + মূল পরিমাণ প্রয়োজন + অনুগ্রহ করে একটি বৈধ পরিমাণ বিন্যাস লিখুন + মূল পরিমাণ খুব কম + মূল পরিমাণ সর্বোচ্চ সীমা অতিক্রম করেছে + নির্বাচিত মুদ্রার জন্য পরিমাণে খুব বেশি দশমিক স্থান রয়েছে + পরিমাণটি বৈধ গুণিতকে নেই + + + বিতরণের তারিখ প্রয়োজন + অনুগ্রহ করে একটি বৈধ তারিখ বিন্যাস লিখুন (DD/MM/YYYY) + বিতরণের তারিখ ভবিষ্যতে হতে হবে + বিতরণের তারিখ ভবিষ্যতে খুব বেশি দূরে (সর্বোচ্চ ১০ বছর) + + + আমাদের দিক থেকে সার্ভার সমস্যা, আর এগোনো যাচ্ছে না, কিছুক্ষণ পর আবার চেষ্টা করুন + খুব বেশি ব্যর্থ প্রচেষ্টা। অনুগ্রহ করে পরে আবার চেষ্টা করুন। + ঋণের আবেদন জমা দিতে ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন। + + + ঋণের আবেদন সফলভাবে জমা দেওয়া হয়েছে! + আবেদনের খসড়া সফলভাবে সংরক্ষিত হয়েছে + + + অসংরক্ষিত পরিবর্তন! + আপনার অসংরক্ষিত পরিবর্তন আছে। আপনি কি নিশ্চিত যে আপনি ফিরে যেতে চান? + + + বাতিল করুন + খসড়া সংরক্ষণ করুন + পুনরায় চেষ্টা করুন + বাতিল + চালিয়ে যান + + + আপনার পুরো নাম লিখুন + ঋণ পণ্য নির্বাচন করুন + ঋণের পরিমাণ লিখুন + বিতরণের তারিখ নির্বাচন করুন + উদ্দেশ্য সংক্ষেপে বর্ণনা করুন + + + মুদ্রা পরিবর্তন করা হয়েছে। পরিমাণ পুনরায় যাচাই করা হবে। + ন্যূনতম ঋণের পরিমাণ: %1$s + সর্বোচ্চ ঋণের পরিমাণ: %1$s + বিবরণ নিশ্চিত করুন + + অনুরোধ সফলভাবে জমা দেওয়া হয়েছে + আপনার ঋণের আবেদন জমা দেওয়া হয়েছে। এটি পর্যালোচনা করা হলে আপনাকে জানানো হবে। + হোমে ফিরে যান + + অনুরোধ জমা দিতে ব্যর্থ হয়েছে + আপনার ঋণের আবেদন জমা দেওয়ার সময় কিছু ভুল হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন। + আবার চেষ্টা করুন + + + + নতুন ঋণের জন্য আবেদন করুন + ঋণের ধরণ নির্বাচন করুন + বর্তমানে ঋণ পণ্য উপলব্ধ নেই + আপনি ইন্টারনেটের সাথে সংযুক্ত আছেন কিনা নিশ্চিত করুন + শর্তাবলী মেনে নিন + + + %1$s ঋণের জন্য আবেদন করুন + %1$s ঋণ পান + %1$s পর্যন্ত + %1$s থেকে %2$s সুদ + শর্তাবলী + + ঋণ মঞ্জুরি এবং বিতরণ + সুদের হার + চালিয়ে যান + পরিশোধ + নিরাপত্তা এবং জামানত + বীমা + খেলাপি + নথিপত্র + আইনী সম্মতি + সংশোধন এবং সমাপ্তি + এখতিয়ার + + + আবেদনকারীর যোগ্যতা, আয়ের স্থিতিশীলতা, ঋণযোগ্যতা এবং ঋণদাতার অভ্যন্তরীণ ঋণ নীতির উপর ভিত্তি করে ব্যক্তিগত ঋণ মঞ্জুর করা হবে। ঋণদাতা কোনো কারণ দর্শানো ছাড়াই তার নিজস্ব বিবেচনার ভিত্তিতে যেকোনো ব্যক্তিগত ঋণের আবেদন অনুমোদন বা প্রত্যাখ্যান করার অধিকার সংরক্ষণ করে। মঞ্জুরকৃত ঋণের পরিমাণ সম্পূর্ণ বা আংশিকভাবে বিতরণ করা যেতে পারে, যা ঋণ চুক্তি এবং নথিপত্রের প্রয়োজনীয়তার উপর নির্ভর করে। বিতরণ কেওয়াইসি প্রক্রিয়া সফলভাবে সমাপ্ত, জমা দেওয়া নথিপত্র যাচাই এবং মঞ্জুরি পত্রে নির্দিষ্ট সমস্ত শর্ত মেনে চলার সাপেক্ষে। + ব্যক্তিগত ঋণে স্থায়ী বা পরিবর্তনশীল সুদের হার থাকতে পারে, যা ঋণ চুক্তির সময় ঋণগ্রহীতা নির্বাচন করবেন। পরিবর্তনশীল সুদের হারের ঋণের ক্ষেত্রে, প্রযোজ্য হার ঋণদাতার বেঞ্চমার্ক বা রেফারেন্স হারের পরিবর্তনের উপর ভিত্তি করে পর্যায়ক্রমিক সংশোধনের সাপেক্ষে। এই ধরনের পরিবর্তন ঋণগ্রহীতার জন্য বাধ্যতামূলক হবে এবং ঋণদাতার প্রয়োজন অনুযায়ী ইএমআই পরিমাণ এবং/অথবা ঋণের মেয়াদে পরিবর্তন ঘটাতে পারে। + ব্যক্তিগত ঋণ পরিশোধ সমান মাসিক কিস্তি (ইএমআই) এর মাধ্যমে করা হবে যাতে মূল এবং সুদ উভয়ই অন্তর্ভুক্ত থাকে। ইএমআই ঋণ চুক্তিতে নির্দিষ্ট তারিখ থেকে শুরু হবে এবং সম্পূর্ণ ঋণের পরিমাণ, প্রযোজ্য সুদ সহ পরিশোধ না হওয়া পর্যন্ত মাসিক ভিত্তিতে চলতে থাকবে। ঋণদাতার প্রচলিত নীতি অনুযায়ী প্রিপেমেন্ট বা আংশিক পেমেন্ট অনুমোদিত হতে পারে এবং এতে নির্দিষ্ট চার্জ প্রযোজ্য হতে পারে, বিশেষ করে স্থায়ী হারের ঋণের জন্য। + ব্যক্তিগত ঋণ সাধারণত জামানতবিহীন প্রকৃতির হয় এবং জামানতের প্রয়োজন হয় না। তবে, ঋণগ্রহীতার ক্রেডিট প্রোফাইল এবং ঋণের পরিমাণের উপর ভিত্তি করে ব্যতিক্রমী ক্ষেত্রে অতিরিক্ত গ্যারান্টি বা নিরাপত্তা চাওয়ার অধিকার ঋণদাতা সংরক্ষণ করে। এই ধরনের প্রয়োজনীয়তা মঞ্জুরি পত্রে স্পষ্টভাবে উল্লেখ করা হবে। প্রযোজ্য হলে নথিপত্র বা আইনি আনুষ্ঠানিকতার সাথে সম্পর্কিত সমস্ত খরচ ঋণগ্রহীতাকে বহন করতে হবে। + যদিও ব্যক্তিগত ঋণের জন্য সাধারণত সম্পদ বীমার প্রয়োজন হয় না, তবে ঋণদাতা সুপারিশ করতে পারেন যে ঋণগ্রহীতা মৃত্যুর ক্ষেত্রে বকেয়া ঋণের পরিমাণ সুরক্ষিত করার জন্য একটি জীবন বীমা পলিসি বেছে নিন। এটি বাধ্যতামূলক নয় তবে ঋণগ্রহীতার পরিবার বা নির্ভরশীলদের জন্য আর্থিক সুরক্ষা ব্যবস্থা হিসাবে এটি পরামর্শ দেওয়া হয়। + ইএমআই পরিশোধ না করা বা বিলম্বিত পেমেন্ট খেলাপি হিসেবে গণ্য হবে। এই ধরনের ক্ষেত্রে, ঋণদাতা বকেয়া আদায়ের জন্য পুনরুদ্ধারের প্রক্রিয়া শুরু করতে পারে, যাতে আইনি প্রক্রিয়া অন্তর্ভুক্ত থাকতে পারে। বকেয়া পরিমাণের উপর পেনাল সুদ এবং অন্যান্য চার্জ প্রয়োগ করা যেতে পারে। ধারাবাহিক বা বারবার খেলাপি ঋণগ্রহীতার ক্রেডিট স্কোর এবং ভবিষ্যতে ঋণ নেওয়ার ক্ষমতাকে নেতিবাচকভাবে প্রভাবিত করতে পারে। + ঋণগ্রহীতাকে সঠিক এবং সম্পূর্ণ নথিপত্র জমা দিতে হবে, যার মধ্যে পরিচয় প্রমাণ, ঠিকানার প্রমাণ, আয়ের বিবরণ, ব্যাংক স্টেটমেন্ট এবং ঋণদাতার অনুরোধ করা অন্য কোনো নথিপত্র অন্তর্ভুক্ত। জমা দেওয়া নথিপত্রে কোনো অসঙ্গতি, ভুল উপস্থাপনা বা জালিয়াতি থাকলে ঋণের আবেদন প্রত্যাখ্যান বা ঋণ চুক্তি বাতিল হতে পারে, সাথে সম্ভাব্য আইনি প্রভাবও পড়তে পারে। + ঋণগ্রহীতাকে তাদের আর্থিক বাধ্যবাধকতা সংক্রান্ত সমস্ত প্রযোজ্য স্থানীয়, রাজ্য এবং জাতীয় আইন সম্পূর্ণ মেনে চলতে হবে। এর মধ্যে রয়েছে সময়মতো কর পরিশোধ, নিয়ন্ত্রক প্রয়োজনীয়তা মেনে চলা এবং বৈধ নথিপত্র জমা দেওয়া। ঋণগ্রহীতার দ্বারা সংঘটিত কোনো আইনি লঙ্ঘনের জন্য ঋণদাতা দায়ী থাকবে না। + ঋণদাতা সময়ে সময়ে এই শর্তাবলী সংশোধন বা আপডেট করতে পারে, তবে ঋণগ্রহীতাকে উপযুক্ত পূর্ব নোটিশ দিতে হবে। ঋণের পরিমাণ সম্পূর্ণ পরিশোধ বা খেলাপির কারণে শুরু হওয়া ফোরক্লোজার প্রক্রিয়ার মাধ্যমে ঋণ চুক্তি সমাপ্ত বলে গণ্য হবে। + ব্যক্তিগত ঋণ চুক্তি থেকে উদ্ভূত বা সম্পর্কিত সমস্ত বিরোধ চুক্তিতে অন্যথায় উল্লেখ না থাকলে ঋণদাতার নিবন্ধিত অফিসের অবস্থানের আদালতের একচেটিয়া এখতিয়ারে আসবে। + + + + + ব্যাংক অ্যাকাউন্টের বিবরণী আপলোড করুন + জামানত নথিপত্র আপলোড করুন + স্বাক্ষর আপলোড করুন + + কিছু ভুল হয়েছে! ফাইল আপলোড করা যাচ্ছে না + অনুগ্রহ করে একটি ব্যাংক স্টেটমেন্ট আপলোড করুন + অনুগ্রহ করে একটি সম্পত্তির নথি আপলোড করুন + অনুগ্রহ করে একটি স্বাক্ষর আপলোড করুন + নথিপত্র সফলভাবে আপলোড করা হয়েছে! + + এখানে স্বাক্ষর করুন + রিসেট + স্বাক্ষর + ক্যাপচার + সংযুক্ত করুন + স্বাক্ষর আপলোড করুন + সংরক্ষণ করুন এবং জমা দিন + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-es/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-es/strings.xml new file mode 100644 index 000000000..61565c1c1 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-es/strings.xml @@ -0,0 +1,157 @@ + + + + + + Solicitar nuevo crédito + + + Complete los detalles de la solicitud + + + Nombre del solicitante + Producto de crédito + Propósito del crédito + Fecha de desembolso + Monto principal + Moneda + + + Continuar + Aceptar + Cancelar + Solicitar Crédito + Siguiente + + + Se requiere el nombre del solicitante + El nombre del solicitante debe tener al menos 4 caracteres. + El nombre del solicitante no puede exceder los 100 caracteres + El nombre del solicitante contiene caracteres no válidos + + + Se requiere producto de crédito + + + Se requiere monto del capital + Por favor, introduzca un formato de importe válido + El monto del capital es demasiado pequeño + El monto del capital excede el límite máximo + El monto tiene demasiados decimales para la moneda seleccionada + El importe no está en múltiplos válidos + + + Se requiere fecha de desembolso + Por favor, introduzca un formato de fecha válido (DD/MM/YYYY) + La fecha de desembolso debe ser futura + La fecha de desembolso está demasiado lejana en el futuro (máximo 10 años) + + + Error interno del servidor. Inténtelo de nuevo + Demasiados intentos fallidos. Inténtelo de nuevo. + No se pudo enviar la solicitud de crédito. Inténtelo de nuevo. + + + ¡Solicitud de crédito enviada! + Preregistro de solicitud guardada exitosamente + + + ¡Cambios no guardados! + Tiene cambios sin guardar. ¿Está seguro de que quiere volver atrás? + + + Descartar + Guardar preregistro + Reintentar + Cancelar + Continuar + + + Introduzca su nombre completo + Seleccionar producto de crédito + Ingrese el monto del crédito + Seleccione la fecha de desembolso + Describa brevemente el propósito + + + Moneda cambiada. El importe será revalidado. + Monto mínimo del crédito: %1$s + Monto máximo del crédito: %1$s + Confirmar detalles + + Solicitud enviada + Su solicitud de crédito ha sido enviada. Se le notificará una vez revisada. + Volver al Inicio + + Solicitud no enviada + Se produjo un error al enviar su solicitud de crédito. Inténtelo de nuevo. + Intentar nuevamente + + + + Aplicar nuevo crédito + Elija el tipo de crédito + Actualmente los productos de crédito no están disponibles + Asegúrese de conectarse a Internet. + Aceptar Términos & Condiciones + + + Aplicar %1$s Crédito + Obtener %1$s Crédito + Hasta %1$s + %1$s a %2$s Interés + Términos & Condiciones + + Sanción y desembolso de créditos + Tasa de interés + Continuar + Reembolso + Seguridad y garantía + Seguro + Por defecto + Documentación + Cumplimiento legal + Modificaciones y terminación + Jurisdicción + + + El crédito personal se aprobará en función de la elegibilidad del solicitante, su estabilidad económica, su solvencia y las políticas crediticias internas del prestamista. El prestamista se reserva el derecho de aprobar o rechazar cualquier solicitud de crédito personal a su entera discreción, sin dar explicaciones. El monto del crédito aprobado podrá desembolsarse total o parcialmente, según el contrato de crédito y los requisitos de documentación. El desembolso está sujeto a la finalización satisfactoria de los procedimientos KYC, la verificación de la documentación presentada y el cumplimiento de todas las condiciones especificadas en la carta de aprobación. + El crédito personal puede tener un tipo de interés fijo o variable, según lo elija el prestatario al formalizar el crédito. En el caso de los créditos con tipo de interés variable, el tipo aplicable está sujeto a revisiones periódicas en función de los cambios en el tipo de referencia del prestamista. Dichos cambios serán vinculantes para el prestatario y podrán dar lugar a una variación en el importe de la cuota mensual o en el plazo del crédito, según lo considere necesario el prestamista. + El reembolso del crédito personal se realizará mediante cuotas mensuales equivalentes (EMI), que incluyen capital e intereses. Las cuotas comenzarán en la fecha especificada en el contrato de crédito y se mantendrán mensualmente hasta que se pague el importe total del crédito, junto con los intereses aplicables. Se podrán permitir pagos anticipados o parciales, de acuerdo con las políticas vigentes del prestamista, y estos pagos podrían conllevar ciertas comisiones, especialmente en el caso de los créditos a tipo fijo. + Los créditos personales suelen ser sin garantía y no requieren aval. Sin embargo, el prestamista se reserva el derecho de solicitar garantías o avales adicionales en casos excepcionales, dependiendo del perfil crediticio del prestatario y del monto del crédito. Dichos requisitos se especificarán claramente en la carta de autorización. El prestatario asumirá todos los costos asociados con la documentación o los trámites legales, si corresponde. + Si bien los créditos personales generalmente no requieren un seguro de activos, el prestamista puede recomendar al prestatario que opte por un seguro de vida para proteger el saldo pendiente del crédito en caso de fallecimiento. Esto no es obligatorio, pero es recomendable como medida de protección financiera para la familia o las personas a cargo del prestatario. + La falta de pago o el retraso en el pago de las cuotas mensuales constituirá un incumplimiento. En tales casos, el prestamista podrá iniciar procedimientos de recuperación para cobrar las cuotas pendientes, lo que puede incluir acciones legales. Se podrán aplicar intereses de mora y otros cargos sobre los importes vencidos. El incumplimiento continuo o reiterado puede afectar negativamente la calificación crediticia del prestatario y su futura capacidad de endeudamiento. + El prestatario debe presentar documentación precisa y completa, incluyendo, entre otros, comprobante de identidad, comprobante de domicilio, información de ingresos, extractos bancarios y cualquier otro documento solicitado por el prestamista. Cualquier discrepancia, tergiversación o falsificación en los documentos presentados puede resultar en el rechazo de la solicitud de crédito o la rescisión del contrato, con posibles consecuencias legales. + El prestatario debe garantizar el pleno cumplimiento de todas las leyes locales, estatales y nacionales aplicables en relación con sus obligaciones financieras. Esto incluye el pago puntual de impuestos, el cumplimiento de los requisitos regulatorios y la presentación de documentos válidos. El prestamista no será responsable de ningún incumplimiento legal cometido por el prestatario. + El prestamista podrá, ocasionalmente, revisar o actualizar estos términos y condiciones, previo aviso al prestatario. El contrato de crédito se considerará rescindido tras el reembolso total del crédito o mediante un procedimiento de ejecución hipotecaria iniciado por incumplimiento. + Todas las disputas que surjan de o en relación con el contrato de crédito personal serán competencia exclusiva de los tribunales del lugar del domicilio social del prestamista, a menos que se especifique lo contrario en el contrato. + + + + + Subir estados de cuenta bancarios + Cargar documentos de garantía + Subir firma + + ¡Algo salió mal! No se pudo cargar el archivo. + Por favor, cargue un extracto bancario + Por favor, cargue un documento de propiedad + Por favor, cargue una firma + ¡Documentos cargados! + + Firmar aquí + Reiniciar + Firmar + Capturar + Adjuntar + Subir firma + Guardar & Enviar + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-fa/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-fa/strings.xml new file mode 100644 index 000000000..b5480678d --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-fa/strings.xml @@ -0,0 +1,157 @@ + + + + + + درخواست وام جدید + + + درخواست وام + + + نام متقاضی + محصول وام + هدف وام + تاریخ پرداخت + مبلغ اصلی + ارز + + + ادامه + تایید + لغو + درخواست وام + بعدی + + + نام متقاضی الزامی است + نام متقاضی باید حداقل ۴ کاراکتر باشد + نام متقاضی نمی‌تواند بیش از ۱۰۰ کاراکتر باشد + نام متقاضی حاوی کاراکترهای نامعتبر است + + + محصول وام الزامی است + + + مبلغ اصلی الزامی است + لطفا فرمت مبلغ معتبری وارد کنید + مبلغ اصلی بسیار کم است + مبلغ اصلی بیش از حد مجاز است + مبلغ دارای اعشار زیادی برای ارز انتخاب شده است + مبلغ در ضرایب معتبر نیست + + + تاریخ پرداخت الزامی است + لطفا فرمت تاریخ معتبر وارد کنید (روز/ماه/سال) + تاریخ پرداخت باید در آینده باشد + تاریخ پرداخت خیلی دور در آینده است (حداکثر ۱۰ سال) + + + مشکل سرور از سمت ما، امکان ادامه وجود ندارد، لطفا کمی بعد دوباره تلاش کنید + تلاش های ناموفق بیش از حد. لطفا بعدا دوباره تلاش کنید. + ارسال درخواست وام ناموفق بود. لطفا دوباره تلاش کنید. + + + درخواست وام با موفقیت ارسال شد! + پیش نویس درخواست با موفقیت ذخیره شد + + + تغییرات ذخیره نشده! + شما تغییرات ذخیره نشده دارید. آیا مطمئن هستید که می‌خواهید برگردید؟ + + + دور انداختن + ذخیره پیش نویس + تلاش مجدد + لغو + ادامه + + + نام کامل خود را وارد کنید + محصول وام را انتخاب کنید + مبلغ وام را وارد کنید + تاریخ پرداخت را انتخاب کنید + هدف را مختصر توضیح دهید + + + ارز تغییر کرده است. مبلغ مجددا تایید خواهد شد. + حداقل مبلغ وام: %1$s + حداکثر مبلغ وام: %1$s + تایید جزئیات + + درخواست با موفقیت ارسال شد + درخواست وام شما ارسال شده است. پس از بررسی به شما اطلاع داده خواهد شد. + بازگشت به خانه + + ارسال درخواست ناموفق بود + مشکلی در هنگام ارسال درخواست وام شما پیش آمد. لطفا دوباره تلاش کنید. + تلاش مجدد + + + + درخواست وام جدید + نوع وام را انتخاب کنید + محصولات وام در حال حاضر در دسترس نیستند + اطمینان حاصل کنید که به اینترنت متصل هستید + شرایط و ضوابط را بپذیرید + + + درخواست وام %1$s + دریافت وام %1$s + تا %1$s + %1$s تا %2$s بهره + شرایط و ضوابط + + تصویب و پرداخت وام + نرخ بهره + ادامه + بازپرداخت + امنیت و وثیقه + بیمه + نکول + مستندات + رعایت قانونی + اصلاحات و فسخ + صلاحیت قضایی + + + وام شخصی بر اساس واجد شرایط بودن متقاضی، ثبات درآمد، اعتبار و سیاست‌های اعتباری داخلی وام‌دهنده تصویب می‌شود. وام‌دهنده حق دارد هر درخواست وام شخصی را به صلاحدید خود بدون ارائه دلیل تصویب یا رد کند. مبلغ وام تصویب شده ممکن است بسته به قرارداد وام و الزامات اسناد به صورت کامل یا اقساط پرداخت شود. پرداخت منوط به تکمیل موفقیت‌آمیز فرآیندهای KYC، تأیید اسناد ارسال شده و رعایت کلیه شرایط ذکر شده در نامه تصویب است. + وام شخصی ممکن است دارای نرخ بهره ثابت یا شناور باشد، همانطور که توسط وام‌گیرنده در زمان قرارداد وام انتخاب شده است. در مورد وام‌های با نرخ بهره شناور، نرخ اعمال شده منوط به بازنگری‌های دوره‌ای بر اساس تغییرات در معیار یا نرخ پایه وام‌دهنده است. چنین تغییراتی برای وام‌گیرنده لازم‌الاجرا خواهد بود و ممکن است منجر به تغییر در مبلغ اقساط و/یا دوره تصدی وام شود، همانطور که وام‌دهنده ضروری بداند. + بازپرداخت وام شخصی از طریق اقساط ماهانه برابر (EMI) شامل اجزای اصل و بهره انجام می‌شود. اقساط از تاریخ ذکر شده در قرارداد وام شروع می‌شود و به صورت ماهانه تا زمانی که کل مبلغ وام به همراه سود مربوطه بازپرداخت شود، ادامه می‌یابد. پیش‌پرداخت یا پرداخت جزئی ممکن است طبق سیاست‌های جاری وام‌دهنده مجاز باشد و ممکن است مشمول هزینه‌های خاصی شود، به ویژه برای وام‌های با نرخ ثابت. + وام‌های شخصی معمولا ماهیت بدون وثیقه دارند و نیازی به وثیقه ندارند. با این حال، وام‌دهنده حق دارد در موارد استثنایی بسته به مشخصات اعتباری وام‌گیرنده و مبلغ وام، ضمانت یا امنیت اضافی درخواست کند. هرگونه الزام این چنینی به وضوح در نامه تصویب ذکر خواهد شد. وام‌گیرنده کلیه هزینه‌های مربوط به اسناد یا تشریفات قانونی را در صورت وجود بر عهده خواهد گرفت. + اگرچه معمولا بیمه دارایی برای وام‌های شخصی مورد نیاز نیست، وام‌دهنده ممکن است توصیه کند که وام‌گیرنده یک بیمه نامه عمر برای پوشش مبلغ وام معوق در صورت فوت وام‌گیرنده انتخاب کند. این اجباری نیست اما به عنوان یک اقدام حفاظت مالی برای خانواده یا افراد تحت تکفل وام‌گیرنده توصیه می‌شود. + عدم پرداخت یا تاخیر در پرداخت اقساط به منزله نکول خواهد بود. در چنین مواردی، وام‌دهنده ممکن است اقدامات بازیابی را برای وصول بدهی‌های معوق آغاز کند که ممکن است شامل اقدامات قانونی باشد. سود جریمه و سایر هزینه‌ها ممکن است برای مبالغ معوق اعمال شود. نکول مداوم یا مکرر ممکن است بر امتیاز اعتباری وام‌گیرنده و توانایی وام‌گیری در آینده تأثیر منفی بگذارد. + وام‌گیرنده موظف است اسناد دقیق و کامل را ارسال کند، از جمله اما نه محدود به مدرک هویت، مدرک آدرس، جزئیات درآمد، صورت‌حساب‌های بانکی و هر سند دیگری که توسط وام‌دهنده درخواست شده است. هرگونه مغایرت، ارائه نادرست یا جعل در اسناد ارسال شده می‌تواند منجر به رد درخواست وام یا فسخ قرارداد وام، همراه با عواقب قانونی احتمالی شود. + وام‌گیرنده باید از رعایت کامل کلیه قوانین محلی، ایالتی و ملی مربوط به تعهدات مالی خود اطمینان حاصل کند. این شامل پرداخت به موقع مالیات، رعایت الزامات نظارتی و ارسال اسناد معتبر است. وام‌دهنده مسئولیتی در قبال هرگونه تخلف قانونی توسط وام‌گیرنده نخواهد داشت. + وام‌دهنده ممکن است این شرایط و ضوابط را هر از چند گاهی با اطلاع قبلی مناسب به وام‌گیرنده بازنگری یا به‌روزرسانی کند. قرارداد وام پس از بازپرداخت کامل مبلغ وام یا از طریق اقدامات سلب مالکیت که به دلیل نکول آغاز شده است، فسخ شده تلقی می‌شود. + کلیه اختلافات ناشی از یا مربوط به قرارداد وام شخصی مشمول صلاحیت انحصاری دادگاه‌های محل دفتر ثبت شده وام‌دهنده خواهد بود، مگر اینکه در قرارداد خلاف آن ذکر شده باشد. + + + + + بارگذاری صورت‌حساب‌های بانکی + بارگذاری اسناد وثیقه + بارگذاری امضا + + مشکلی پیش آمد! امکان بارگذاری فایل وجود ندارد + لطفا صورت‌حساب بانکی را بارگذاری کنید + لطفا سند ملک را بارگذاری کنید + لطفا امضا را بارگذاری کنید + اسناد با موفقیت بارگذاری شد! + + اینجا امضا کنید + بازنشانی + امضا + ثبت + پیوست + بارگذاری امضا + ذخیره و ارسال + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-fr/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-fr/strings.xml new file mode 100644 index 000000000..b70cb86d8 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-fr/strings.xml @@ -0,0 +1,157 @@ + + + + + + Demander un prêt + + + Demande de prêt + + + Nom du demandeur + Produit de prêt + Objectif du prêt + Date de versement + Montant principal + Devise + + + Continuer + Ok + Annuler + Appliquer le prêt + Suivant + + + Le nom du demandeur est obligatoire + Le nom du demandeur doit comporter au moins 4 caractères + Le nom du demandeur ne peut pas dépasser 100 caractères + Le nom du demandeur contient des caractères invalides + + + Le produit de prêt est requis + + + Le montant principal est requis + Veuillez saisir un format de montant valide + Le montant principal est trop petit + Le montant principal dépasse la limite maximale + Le montant comporte trop de décimales pour la devise sélectionnée + Le montant n\'est pas dans des multiples valides + + + La date de versement est requise + Veuillez saisir un format de date valide (JJ/MM/AAAA) + La date de versement doit être future + La date de versement est trop lointaine (max 10 ans) + + + Problème de serveur de notre côté, impossible de continuer, réessayez après un moment + Trop de tentatives échouées. Veuillez réessayer plus tard. + Échec de l\'envoi de la demande de prêt. Veuillez réessayer. + + + Demande de prêt envoyée avec succès ! + Brouillon de demande enregistré avec succès + + + Modifications non enregistrées ! + Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir revenir en arrière ? + + + Ignorer + Enregistrer le brouillon + Réessayer + Annuler + Continuer + + + Entrez votre nom complet + Sélectionner un produit de prêt + Saisir le montant du prêt + Sélectionner la date de versement + Décrivez brièvement l\'objectif + + + Devise modifiée. Le montant sera revalidé. + Montant minimum du prêt : %1$s + Montant maximum du prêt : %1$s + Confirmer les détails + + Demande envoyée avec succès + Votre demande de prêt a été envoyée. Vous serez informé une fois qu\'elle sera examinée. + Retour à l\'accueil + + Échec de l\'envoi de la demande + Une erreur s\'est produite lors de l\'envoi de votre demande de prêt. Veuillez réessayer. + Réessayer + + + + Demander un nouveau prêt + Choisir le type de prêt + Les produits de prêt ne sont actuellement pas disponibles + Assurez-vous d\'être connecté à Internet + Accepter les conditions générales + + + Demander un prêt %1$s + Obtenir un prêt %1$s + Jusqu\'à %1$s + %1$s à %2$s Intérêt + Termes et Conditions + + Sanction et décaissement du prêt + Taux d\'intérêt + Continuer + Remboursement + Garantie et sûreté + Assurance + Défaut + Documentation + Conformité légale + Amendements et résiliation + Juridiction + + + Le prêt personnel sera sanctionné en fonction de l\'éligibilité du demandeur, de la stabilité des revenus, de la solvabilité et des politiques de crédit internes du prêteur. Le prêteur se réserve le droit d\'approuver ou de rejeter toute demande de prêt personnel à sa seule discrétion sans donner de raison. Le montant du prêt sanctionné peut être décaissé intégralement ou en partie, selon l\'accord de prêt et les exigences en matière de documentation. Le décaissement est soumis à la réussite des procédures KYC, à la vérification des documents soumis et au respect de toutes les conditions spécifiées dans la lettre de sanction. + Le prêt personnel peut comporter un taux d\'intérêt fixe ou variable, choisi par l\'emprunteur au moment de l\'accord de prêt. Dans le cas de prêts à taux variable, le taux applicable est soumis à des révisions périodiques en fonction des changements du taux de référence ou du taux de référence du prêteur. De tels changements lieront l\'emprunteur et pourront entraîner une variation du montant de l\'EMI et/ou de la durée du prêt, selon ce que le prêteur jugera nécessaire. + Le remboursement du prêt personnel se fera par versements mensuels équivalents (EMI) comprenant à la fois le principal et les intérêts. Les EMI commenceront à la date spécifiée dans l\'accord de prêt et se poursuivront sur une base mensuelle jusqu\'à ce que le montant total du prêt, ainsi que les intérêts applicables, soit remboursé. Le remboursement anticipé ou partiel peut être autorisé conformément aux politiques en vigueur du prêteur et peut entraîner certains frais, en particulier pour les prêts à taux fixe. + Les prêts personnels sont généralement de nature non garantie et ne nécessitent pas de garantie. Cependant, le prêteur se réserve le droit de demander des garanties ou des sûretés supplémentaires dans des cas exceptionnels, en fonction du profil de crédit de l\'emprunteur et du montant du prêt. De telles exigences seront clairement indiquées dans la lettre de sanction. L\'emprunteur supportera tous les frais liés à la documentation ou aux formalités légales, le cas échéant. + Bien que les prêts personnels ne nécessitent généralement pas d\'assurance des biens, le prêteur peut recommander à l\'emprunteur d\'opter pour une police d\'assurance-vie pour garantir le montant du prêt impayé en cas de décès de l\'emprunteur. Cela n\'est pas obligatoire mais est conseillé comme mesure de protection financière pour la famille ou les personnes à charge de l\'emprunteur. + Le non-paiement ou le retard de paiement des EMI constituera un défaut. Dans de telles cas, le prêteur peut engager des procédures de recouvrement pour récupérer les sommes dues, ce qui peut inclure des procédures judiciaires. Des intérêts de retard et d\'autres frais peuvent être appliqués sur les montants en souffrance. Un défaut continu ou répété peut affecter négativement la cote de crédit de l\'emprunteur et sa capacité d\'emprunt future. + L\'emprunteur est tenu de soumettre des documents exacts et complets, y compris, mais sans s\'y limiter, une preuve d\'identité, une preuve d\'adresse, des détails sur les revenus, des relevés bancaires et tout autre document demandé par le prêteur. Toute divergence, fausse déclaration ou falsification dans les documents soumis peut entraîner le rejet de la demande de prêt ou la résiliation de l\'accord de prêt, ainsi que des implications juridiques possibles. + L\'emprunteur doit s\'assurer du respect total de toutes les lois locales, étatiques et nationales applicables concernant ses obligations financières. Cela inclut le paiement en temps opportun des impôts, le respect des exigences réglementaires et la soumission de documents valides. Le prêteur ne sera pas tenu responsable des violations légales commises par l\'emprunteur. + Le prêteur peut, de temps à autre, réviser ou mettre à jour les présentes conditions générales, sous réserve d\'un préavis approprié à l\'emprunteur. L\'accord de prêt sera considéré comme résilié lors du remboursement intégral du montant du prêt ou par des procédures de saisie engagées en raison d\'un défaut. + Tous les litiges découlant de ou liés à l\'accord de prêt personnel relèveront de la compétence exclusive des tribunaux du lieu du siège social du prêteur, sauf indication contraire dans l\'accord. + + + + + Télécharger les relevés bancaires + Télécharger les documents de garantie + Télécharger la signature + + Une erreur s\'est produite ! Impossible de télécharger le fichier + Veuillez télécharger un relevé bancaire + Veuillez télécharger un document de propriété + Veuillez télécharger une signature + Documents téléchargés avec succès ! + + Signer ici + Réinitialiser + Signer + Capturer + Joindre + Télécharger la signature + Enregistrer et envoyer + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-hi/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-hi/strings.xml new file mode 100644 index 000000000..6a2d47827 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-hi/strings.xml @@ -0,0 +1,157 @@ + + + + + + नया ऋण आवेदन करें + + + ऋण आवेदन + + + आवेदक का नाम + ऋण उत्पाद + ऋण का उद्देश्य + वितरण की तिथि + मूल राशि + मुद्रा + + + जारी रखें + ठीक है + रद्द करें + ऋण लागू करें + अगला + + + आवेदक का नाम आवश्यक है + आवेदक का नाम कम से कम 4 अक्षरों का होना चाहिए + आवेदक का नाम 100 अक्षरों से अधिक नहीं हो सकता + आवेदक के नाम में अमान्य वर्ण हैं + + + ऋण उत्पाद आवश्यक है + + + मूल राशि आवश्यक है + कृपया एक मान्य राशि प्रारूप दर्ज करें + मूल राशि बहुत कम है + मूल राशि अधिकतम सीमा से अधिक है + चयनित मुद्रा के लिए राशि में बहुत अधिक दशमलव स्थान हैं + राशि मान्य गुणकों में नहीं है + + + वितरण तिथि आवश्यक है + कृपया एक मान्य तिथि प्रारूप (DD/MM/YYYY) दर्ज करें + वितरण की तिथि भविष्य में होनी चाहिए + वितरण की तिथि भविष्य में बहुत दूर है (अधिकतम 10 वर्ष) + + + हमारी तरफ से सर्वर की समस्या आगे नहीं बढ़ सकती, थोड़ी देर बाद फिर से प्रयास करें + बहुत अधिक असफल प्रयास। कृपया बाद में फिर से कोशिश करें। + ऋण आवेदन जमा करने में विफल। कृपया पुन: प्रयास करें। + + + ऋण आवेदन सफलतापूर्वक जमा किया गया! + आवेदन ड्राफ्ट सफलतापूर्वक सहेजा गया + + + बिना सहेजे गए परिवर्तन! + आपके पास बिना सहेजे गए परिवर्तन हैं। क्या आप हकीकत में वापस जाना चाहते हैं? + + + अस्वीकार करें + ड्राफ्ट सहेजें + पुन: प्रयास करें + रद्द करें + जारी रखें + + + अपना पूरा नाम दर्ज करें + ऋण उत्पाद का चयन करें + ऋण राशि दर्ज करें + वितरण तिथि का चयन करें + संक्षेप में उद्देश्य का वर्णन करें + + + मुद्रा बदल दी गई है। राशि को फिर से मान्य किया जाएगा। + न्यूनतम ऋण राशि: %1$s + अधिकतम ऋण राशि: %1$s + विवरण की पुष्टि करें + + अनुरोध सफलतापूर्वक जमा किया गया + आपका ऋण आवेदन जमा कर दिया गया है। इसकी समीक्षा होने के बाद आपको सूचित किया जाएगा। + होम पर वापस जाएं + + अनुरोध जमा करने में विफल + अपना ऋण आवेदन जमा करते समय कुछ गलत हुआ। कृपया पुन: प्रयास करें। + पुन: प्रयास करें + + + + नया ऋण आवेदन करें + ऋण का प्रकार चुनें + वर्तमान में ऋण उत्पाद उपलब्ध नहीं हैं + सुनिश्चित करें कि आप इंटरनेट से जुड़े हैं + नियम और शर्तें स्वीकार करें + + + %1$s ऋण आवेदन करें + %1$s ऋण प्राप्त करें + %1$s तक + %1$s से %2$s ब्याज + नियम और शर्तें + + ऋण स्वीकृति और वितरण + ब्याज दर + जारी रखें + पुनर्भुगतान + सुरक्षा और संपार्श्विक + बीमा + डिफ़ॉल्ट + दस्तावेज़ीकरण + कानूनी अनुपालन + संशोधन और समाप्ति + अधिकार क्षेत्र + + + व्यक्तिगत ऋण आवेदक की पात्रता, आय स्थिरता, साख और ऋणदाता की आंतरिक ऋण नीतियों के आधार पर स्वीकृत किया जाएगा। ऋणदाता बिना कोई कारण बताए अपने पूर्ण विवेक पर किसी भी व्यक्तिगत ऋण आवेदन को स्वीकृत या अस्वीकार करने का अधिकार सुरक्षित रखता है। स्वीकृत ऋण राशि को ऋण समझौते और दस्तावेज़ीकरण आवश्यकताओं के आधार पर पूर्ण या भागों में वितरित किया जा सकता है। वितरण केवाईसी प्रक्रियाओं के सफल समापन, प्रस्तुत दस्तावेजों के सत्यापन और स्वीकृति पत्र में निर्दिष्ट सभी शर्तों के पालन के अधीन है। + व्यक्तिगत ऋण में या तो निश्चित या फ्लोटिंग ब्याज दर हो सकती है, जैसा कि ऋण समझौते के समय उधारकर्ता द्वारा चुना गया हो। फ्लोटिंग ब्याज दर ऋणों के मामले में, लागू दर ऋणदाता के बेंचमार्क या संदर्भ दर में परिवर्तन के आधार पर समय-समय पर संशोधनों के अधीन है। इस तरह के परिवर्तन उधारकर्ता पर बाध्यकारी होंगे और ईएमआई राशि और/या ऋण अवधि में बदलाव का कारण बन सकते हैं, जैसा कि ऋणदाता द्वारा आवश्यक समझा जाए। + व्यक्तिगत ऋण का पुनर्भुगतान समान मासिक किस्तों (ईएमआई) के माध्यम से किया जाएगा जिसमें मूल और ब्याज दोनों घटक शामिल होंगे। ईएमआई ऋण समझौते में निर्दिष्ट तिथि से शुरू होगी और मासिक आधार पर जारी रहेगी जब तक कि लागू ब्याज के साथ पूरी ऋण राशि का भुगतान नहीं हो जाता। ऋणदाता की प्रचलित नीतियों के अनुसार पूर्व भुगतान या आंशिक भुगतान की अनुमति दी जा सकती है और इस पर कुछ शुल्क लग सकते हैं, विशेष रूप से निश्चित दर वाले ऋणों के लिए। + व्यक्तिगत ऋण आमतौर पर असुरक्षित प्रकृति के होते हैं और इसके लिए संपार्श्विक की आवश्यकता नहीं होती है। हालांकि, ऋणदाता उधारकर्ता के क्रेडिट प्रोफाइल और ऋण राशि के आधार पर असाधारण मामलों में अतिरिक्त गारंटी या सुरक्षा का अनुरोध करने का अधिकार सुरक्षित रखता है। ऐसी किसी भी आवश्यकता को स्वीकृति पत्र में स्पष्ट रूप से बताया जाएगा। यदि लागू हो, तो दस्तावेज़ीकरण या कानूनी औपचारिकताओं से जुड़े सभी लागत उधारकर्ता द्वारा वहन किए जाएंगे। + जबकि व्यक्तिगत ऋणों के लिए आमतौर पर संपत्ति बीमा की आवश्यकता नहीं होती है, ऋणदाता सिफारिश कर सकता है कि उधारकर्ता उधारकर्ता की मृत्यु की स्थिति में बकाया ऋण राशि को सुरक्षित करने के लिए जीवन बीमा पॉलिसी का विकल्प चुनें। यह अनिवार्य नहीं है लेकिन उधारकर्ता के परिवार या आश्रितों के लिए वित्तीय सुरक्षा उपाय के रूप में यह सलाह दी जाती है। + ईएमआई का भुगतान न करना या विलंबित भुगतान डिफ़ॉल्ट माना जाएगा। ऐसे मामलों में, ऋणदाता बकाया राशि एकत्र करने के लिए वसूली प्रक्रिया शुरू कर सकता है, जिसमें कानूनी कार्यवाही शामिल हो सकती है। अतिदेय राशियों पर दंडात्मक ब्याज और अन्य शुल्क लागू किए जा सकते हैं। निरंतर या बार-बार डिफ़ॉल्ट उधारकर्ता के क्रेडिट स्कोर और भविष्य में उधार लेने की क्षमता को नकारात्मक रूप से प्रभावित कर सकता है। + उधारकर्ता को सटीक और पूर्ण दस्तावेज़ जमा करने की आवश्यकता होती है, जिसमें पहचान प्रमाण, पते का प्रमाण, आय विवरण, बैंक विवरण और ऋणदाता द्वारा अनुरोधित कोई अन्य दस्तावेज़ शामिल हैं। प्रस्तुत दस्तावेजों में कोई भी विसंगति, गलत बयानी या मिथ्याकरण ऋण आवेदन की अस्वीकृति या ऋण समझौते की समाप्ति का परिणाम हो सकता है, साथ ही संभावित कानूनी निहितार्थ भी हो सकते हैं। + उधारकर्ता को अपने वित्तीय दायित्वों से संबंधित सभी लागू स्थानीय, राज्य और राष्ट्रीय कानूनों का पूर्ण अनुपालन सुनिश्चित करना होगा। इसमें करों का समय पर भुगतान, विनियामक आवश्यकताओं का पालन और वैध दस्तावेज़ जमा करना शामिल है। ऋणदाता उधारकर्ता द्वारा किए गए किसी भी कानूनी उल्लंघन के लिए उत्तरदायी नहीं होगा। + ऋणदाता समय-समय पर इन नियमों और शर्तों को संशोधित या अद्यतन कर सकता है, जो उधारकर्ता को उचित पूर्व सूचना के अधीन है। ऋण राशि के पूर्ण भुगतान या डिफ़ॉल्ट के कारण शुरू की गई फौजदारी प्रक्रियाओं के माध्यम से ऋण समझौता समाप्त माना जाएगा। + व्यक्तिगत ऋण समझौते से उत्पन्न होने वाले या उससे संबंधित सभी विवाद ऋणदाता के पंजीकृत कार्यालय के स्थान पर अदालतों के अनन्य क्षेत्राधिकार के अंतर्गत आएंगे, जब तक कि समझौते में अन्यथा निर्दिष्ट न किया गया हो। + + + + + बैंक खाता विवरण अपलोड करें + संपार्श्विक दस्तावेज अपलोड करें + हस्ताक्षर अपलोड करें + + कुछ गलत हो गया! फ़ाइल अपलोड करने में असमर्थ + कृपया बैंक विवरण अपलोड करें + कृपया संपत्ति दस्तावेज़ अपलोड करें + कृपया हस्ताक्षर अपलोड करें + दस्तावेज़ सफलतापूर्वक अपलोड किए गए! + + यहाँ हस्ताक्षर करें + रीसेट + हस्ताक्षर + कैप्चर + संलग्न करें + हस्ताक्षर अपलोड करें + सहेजें और जमा करें + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-in/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-in/strings.xml new file mode 100644 index 000000000..f3a0894cb --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-in/strings.xml @@ -0,0 +1,157 @@ + + + + + + Ajukan Pinjaman Baru + + + Aplikasi Pinjaman + + + Nama Pemohon + Produk Pinjaman + Tujuan Pinjaman + Tanggal Pencairan + Jumlah Pokok + Mata Uang + + + Lanjutkan + Oke + Batal + Ajukan Pinjaman + Berikutnya + + + Nama pemohon wajib diisi + Nama pemohon harus minimal 4 karakter + Nama pemohon tidak boleh lebih dari 100 karakter + Nama pemohon mengandung karakter yang tidak valid + + + Produk pinjaman wajib diisi + + + Jumlah pokok wajib diisi + Masukkan format jumlah yang valid + Jumlah pokok terlalu kecil + Jumlah pokok melebihi batas maksimum + Jumlah memiliki terlalu banyak tempat desimal untuk mata uang yang dipilih + Jumlah tidak dalam kelipatan yang valid + + + Tanggal pencairan wajib diisi + Masukkan format tanggal yang valid (DD/MM/YYYY) + Tanggal pencairan harus di masa depan + Tanggal pencairan terlalu jauh di masa depan (maks 10 tahun) + + + Masalah server dari sisi kami, tidak dapat melanjutkan, coba lagi sesaat lagi + Terlalu banyak percobaan gagal. Silakan coba lagi nanti. + Gagal mengirim aplikasi pinjaman. Silakan coba lagi. + + + Aplikasi pinjaman berhasil dikirim! + Draf aplikasi berhasil disimpan + + + Perubahan Belum Disimpan! + Anda memiliki perubahan yang belum disimpan. Apakah Anda yakin ingin kembali? + + + Buang + Simpan Draf + Coba Lagi + Batal + Lanjutkan + + + Masukkan nama lengkap Anda + Pilih produk pinjaman + Masukkan jumlah pinjaman + Pilih tanggal pencairan + Jelaskan tujuan secara singkat + + + Mata uang berubah. Jumlah akan divalidasi ulang. + Jumlah pinjaman minimum: %1$s + Jumlah pinjaman maksimum: %1$s + Konfirmasi Detail + + Permintaan Berhasil Dikirim + Aplikasi pinjaman Anda telah dikirim. Anda akan diberitahu setelah ditinjau. + Kembali Ke Beranda + + Gagal Mengirim Permintaan + Terjadi kesalahan saat mengirim aplikasi pinjaman Anda. Silakan coba lagi. + Coba Lagi + + + + Ajukan Pinjaman Baru + Pilih Jenis Pinjaman + Saat ini Produk Pinjaman tidak tersedia + Pastikan Anda terhubung ke internet + Terima Syarat & Ketentuan + + + Ajukan Pinjaman %1$s + Dapatkan Pinjaman %1$s + Hingga %1$s + %1$s hingga %2$s Bunga + Syarat & Ketentuan + + Sanksi dan Pencairan Pinjaman + Suku Bunga + Lanjutkan + Pelunasan + Keamanan dan Jaminan + Asuransi + Default + Dokumentasi + Kepatuhan Hukum + Amandemen dan Pengakhiran + Yurisdiksi + + + Pinjaman pribadi akan disetujui berdasarkan kelayakan pemohon, stabilitas pendapatan, kelayakan kredit, dan kebijakan kredit internal pemberi pinjaman. Pemberi pinjaman berhak untuk menyetujui atau menolak aplikasi pinjaman pribadi apa pun atas kebijakannya sendiri tanpa memberikan alasan apa pun. Jumlah pinjaman yang disetujui dapat dicairkan secara penuh atau sebagian, tergantung pada perjanjian pinjaman dan persyaratan dokumentasi. Pencairan tunduk pada keberhasilan penyelesaian prosedur KYC, verifikasi dokumen yang diserahkan, dan kepatuhan terhadap semua kondisi yang ditentukan dalam surat persetujuan. + Pinjaman pribadi dapat memiliki suku bunga tetap atau mengambang, seperti yang dipilih oleh peminjam pada saat perjanjian pinjaman. Dalam hal pinjaman suku bunga mengambang, suku bunga yang berlaku tunduk pada revisi berkala berdasarkan perubahan dalam tolok ukur atau suku bunga acuan pemberi pinjaman. Perubahan tersebut akan mengikat peminjam dan dapat menyebabkan variasi dalam jumlah EMI dan/atau tenor pinjaman, sebagaimana dianggap perlu oleh pemberi pinjaman. + Pelunasan pinjaman pribadi akan dilakukan melalui Angsuran Bulanan yang Disamakan (EMI) yang terdiri dari komponen pokok dan bunga. EMI akan dimulai dari tanggal yang ditentukan dalam perjanjian pinjaman dan akan terus berlanjut setiap bulan sampai jumlah pinjaman penuh, bersama dengan bunga yang berlaku, dilunasi. Pembayaran di muka atau pembayaran sebagian dapat diizinkan sesuai dengan kebijakan pemberi pinjaman yang berlaku dan dapat dikenakan biaya tertentu, terutama untuk pinjaman suku bunga tetap. + Pinjaman pribadi biasanya bersifat tanpa jaminan dan tidak memerlukan agunan. Namun, pemberi pinjaman berhak untuk meminta jaminan atau keamanan tambahan dalam kasus luar biasa, tergantung pada profil kredit peminjam dan jumlah pinjaman. Persyaratan semacam itu akan dinyatakan dengan jelas dalam surat persetujuan. Peminjam akan menanggung semua biaya yang terkait dengan dokumentasi atau formalitas hukum, jika berlaku. + Meskipun pinjaman pribadi umumnya tidak memerlukan asuransi aset, pemberi pinjaman dapat merekomendasikan agar peminjam memilih polis asuransi jiwa untuk melindungi jumlah pinjaman yang belum lunas jika terjadi kematian peminjam. Ini tidak wajib tetapi disarankan sebagai tindakan perlindungan finansial bagi keluarga atau tanggungan peminjam. + Tidak membayar atau terlambat membayar EMI akan dianggap sebagai default. Dalam kejadian seperti itu, pemberi pinjaman dapat memulai prosedur pemulihan untuk menagih iuran yang belum lunas, yang dapat mencakup proses hukum. Bunga denda dan biaya lainnya dapat dikenakan pada jumlah yang telah jatuh tempo. Default yang berkelanjutan atau berulang dapat berdampak negatif pada skor kredit peminjam dan kemampuan meminjam di masa depan. + Peminjam diharuskan untuk menyerahkan dokumentasi yang akurat dan lengkap, termasuk namun tidak terbatas pada bukti identitas, bukti alamat, rincian pendapatan, laporan bank, dan dokumen lain yang diminta oleh pemberi pinjaman. Setiap perbedaan, kesalahan penyajian, atau pemalsuan dalam dokumen yang diserahkan dapat mengakibatkan penolakan aplikasi pinjaman atau pengakhiran perjanjian pinjaman, bersama dengan kemungkinan implikasi hukum. + Peminjam harus memastikan kepatuhan penuh terhadap semua hukum lokal, negara bagian, dan nasional yang berlaku mengenai kewajiban keuangan mereka. Ini termasuk pembayaran pajak tepat waktu, kepatuhan terhadap persyaratan peraturan, dan penyerahan dokumen yang valid. Pemberi pinjaman tidak bertanggung jawab atas pelanggaran hukum yang dilakukan oleh peminjam. + Pemberi pinjaman dapat, dari waktu ke waktu, merevisi atau memperbarui syarat dan ketentuan ini, dengan pemberitahuan sebelumnya yang sesuai kepada peminjam. Perjanjian pinjaman akan dianggap berakhir setelah pelunasan penuh jumlah pinjaman atau melalui prosedur penyitaan yang dimulai karena default. + Semua perselisihan yang timbul dari atau sehubungan dengan perjanjian pinjaman pribadi akan berada di bawah yurisdiksi eksklusif pengadilan di lokasi kantor terdaftar pemberi pinjaman, kecuali ditentukan lain dalam perjanjian. + + + + + Unggah Laporan Rekening Bank + Unggah Dokumen Jaminan + Unggah Tanda Tangan + + Ada yang Salah! Tidak dapat mengunggah file + Harap Unggah Laporan Bank + Harap Unggah Dokumen Properti + Harap Unggah Tanda Tangan + Dokumen Berhasil Diunggah! + + Tanda Tangan Di Sini + Atur Ulang + Tanda Tangan + Tangkap + Lampirkan + Unggah Tanda Tangan + Simpan & Kirim + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-km/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-km/strings.xml new file mode 100644 index 000000000..95b237b31 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-km/strings.xml @@ -0,0 +1,157 @@ + + + + + + ដាក់ពាក្យស្នើសុំប្រាក់កម្ចីថ្មី + + + ពាក្យស្នើសុំប្រាក់កម្ចី + + + ឈ្មោះអ្នកស្នើសុំ + ផលិតផលប្រាក់កម្ចី + គោលបំណងនៃប្រាក់កម្ចី + កាលបរិច្ឆេទបញ្ចេញប្រាក់ + ចំនួនប្រាក់ដើម + រូបិយប័ណ្ណ + + + បន្ត + យល់ព្រម + បោះបង់ + អនុវត្តប្រាក់កម្ចី + បន្ទាប់ + + + ឈ្មោះអ្នកស្នើសុំត្រូវបានទាមទារ + ឈ្មោះអ្នកស្នើសុំត្រូវមានយ៉ាងហោចណាស់ ៤ តួអក្សរ + ឈ្មោះអ្នកស្នើសុំមិនអាចលើសពី ១០០ តួអក្សរ + ឈ្មោះអ្នកស្នើសុំមានតួអក្សរមិនត្រឹមត្រូវ + + + ផលិតផលប្រាក់កម្ចីត្រូវបានទាមទារ + + + ចំនួនប្រាក់ដើមត្រូវបានទាមទារ + សូមបញ្ចូលទម្រង់ចំនួនទឹកប្រាក់ត្រឹមត្រូវ + ចំនួនប្រាក់ដើមតូចពេក + ចំនួនប្រាក់ដើមលើសពីដែនកំណត់អតិបរមា + ចំនួនទឹកប្រាក់មានខ្ទង់ទសភាគច្រើនពេកសម្រាប់រូបិយប័ណ្ណដែលបានជ្រើសរើស + ចំនួនទឹកប្រាក់មិនស្ថិតនៅក្នុងពហុគុណត្រឹមត្រូវទេ + + + កាលបរិច្ឆេទបញ្ចេញប្រាក់ត្រូវបានទាមទារ + សូមបញ្ចូលទម្រង់កាលបរិច្ឆេទត្រឹមត្រូវ (DD/MM/YYYY) + កាលបរិច្ឆេទបញ្ចេញប្រាក់ត្រូវតែនៅថ្ងៃអនាគត + កាលបរិច្ឆេទបញ្ចេញប្រាក់ឆ្ងាយពេកនៅថ្ងៃអនាគត (អតិបរមា ១០ ឆ្នាំ) + + + បញ្ហាម៉ាស៊ីនមេពីខាងយើងមិនអាចបន្តទៀតបានទេ សូមព្យាយាមម្តងទៀតបន្ទាប់ពីមួយសន្ទុះ + ការប៉ុនប៉ងបរាជ័យច្រើនពេក។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។ + ការដាក់ពាក្យស្នើសុំប្រាក់កម្ចីបានបរាជ័យ។ សូម​ព្យាយាម​ម្តង​ទៀត។ + + + ពាក្យស្នើសុំប្រាក់កម្ចីត្រូវបានដាក់ជូនដោយជោគជ័យ! + សេចក្តីព្រាងកម្មវិធីបានរក្សាទុកដោយជោគជ័យ + + + ការផ្លាស់ប្តូរមិនបានរក្សាទុក! + អ្នកមានការផ្លាស់ប្តូរដែលមិនបានរក្សាទុក។ តើអ្នកប្រាកដថាចង់ត្រឡប់ក្រោយទេ? + + + បោះចោល + រក្សាទុកសេចក្តីព្រាង + ព្យាយាមម្តងទៀត + បោះបង់ + បន្ត + + + បញ្ចូលឈ្មោះពេញរបស់អ្នក + ជ្រើសរើសផលិតផលប្រាក់កម្ចី + បញ្ចូលចំនួនប្រាក់កម្ចី + ជ្រើសរើសកាលបរិច្ឆេទបញ្ចេញប្រាក់ + ពណ៌នាដោយសង្ខេបអំពីគោលបំណង + + + រូបិយប័ណ្ណត្រូវបានផ្លាស់ប្តូរ។ ចំនួនទឹកប្រាក់នឹងត្រូវបានធ្វើឱ្យមានសុពលភាពឡើងវិញ។ + ចំនួនប្រាក់កម្ចីអប្បបរមា៖ %1$s + ចំនួនប្រាក់កម្ចីអតិបរមា៖ %1$s + បញ្ជាក់ព័ត៌មានលម្អិត + + សំណើត្រូវបានដាក់ជូនដោយជោគជ័យ + ពាក្យស្នើសុំប្រាក់កម្ចីរបស់អ្នកត្រូវបានដាក់ជូន។ អ្នកនឹងត្រូវបានជូនដំណឹងនៅពេលវាត្រូវបានពិនិត្យ។ + ត្រឡប់ទៅផ្ទះវិញ + + បរាជ័យក្នុងការដាក់សំណើ + មានបញ្ហាក្នុងការដាក់ពាក្យស្នើសុំប្រាក់កម្ចីរបស់អ្នក។ សូម​ព្យាយាម​ម្តង​ទៀត។ + ព្យាយាមម្តងទៀត + + + + ដាក់ពាក្យស្នើសុំប្រាក់កម្ចីថ្មី + ជ្រើសរើសប្រភេទប្រាក់កម្ចី + បច្ចុប្បន្នផលិតផលប្រាក់កម្ចីមិនមានទេ + សូមប្រាកដថាអ្នកបានភ្ជាប់អ៊ីនធឺណិត + ទទួលយកលក្ខខណ្ឌ + + + ដាក់ពាក្យស្នើសុំប្រាក់កម្ចី %1$s + ទទួលបានប្រាក់កម្ចី %1$s + រហូតដល់ %1$s + %1$s ទៅ %2$s ការប្រាក់ + លក្ខខណ្ឌ + + ការអនុម័ត និងការបញ្ចេញប្រាក់កម្ចី + អត្រាការប្រាក់ + បន្ត + ការសងត្រលប់ + សន្តិសុខ និងទ្រព្យបញ្ចាំ + ការធានារ៉ាប់រង + ការខកខាន + ឯកសារ + ការអនុលោមតាមច្បាប់ + វិសោធនកម្ម និងការបញ្ចប់ + ដែនសមត្ថកិច្ច + + + ប្រាក់កម្ចីផ្ទាល់ខ្លួននឹងត្រូវបានអនុម័តដោយផ្អែកលើសិទ្ធិរបស់អ្នកដាក់ពាក្យ ស្ថិរភាពប្រាក់ចំណូល ភាពសក្តិសមនៃឥណទាន និងគោលនយោបាយឥណទានផ្ទៃក្នុងរបស់អ្នកផ្តល់ប្រាក់កម្ចី។ អ្នកផ្តល់ប្រាក់កម្ចីរក្សាសិទ្ធិក្នុងការអនុម័ត ឬបដិសេធរាល់ពាក្យស្នើសុំប្រាក់កម្ចីផ្ទាល់ខ្លួនតាមឆន្ទានុសិទ្ធិរបស់ខ្លួន ដោយមិនផ្តល់ហេតុផលណាមួយឡើយ។ ចំនួនប្រាក់កម្ចីដែលត្រូវបានអនុម័តអាចត្រូវបានបញ្ចេញពេញលេញ ឬមួយផ្នែក អាស្រ័យលើកិច្ចព្រមព្រៀងប្រាក់កម្ចី និងតម្រូវការឯកសារ។ ការបញ្ចេញប្រាក់គឺស្ថិតនៅក្រោមការបញ្ចប់នីតិវិធី KYC ដោយជោគជ័យ ការផ្ទៀងផ្ទាត់ឯកសារដែលបានដាក់ជូន និងការប្រកាន់ខ្ជាប់នូវលក្ខខណ្ឌទាំងអស់ដែលបានបញ្ជាក់នៅក្នុងលិខិតអនុម័ត។ + ប្រាក់កម្ចីផ្ទាល់ខ្លួនអាចមានអត្រាការប្រាក់ថេរ ឬអថេរ ដូចដែលបានជ្រើសរើសដោយអ្នកខ្ចីនៅពេលនៃកិច្ចព្រមព្រៀងប្រាក់កម្ចី។ នៅក្នុងករណីនៃប្រាក់កម្ចីអត្រាការប្រាក់អថេរ អត្រាដែលត្រូវអនុវត្តគឺស្ថិតនៅក្រោមការកែសម្រួលតាមកាលកំណត់ដោយផ្អែកលើការផ្លាស់ប្តូរនៅក្នុងស្តង់ដារ ឬអត្រាយោងរបស់អ្នកផ្តល់ប្រាក់កម្ចី។ ការផ្លាស់ប្តូរបែបនេះនឹងមានកាតព្វកិច្ចលើអ្នកខ្ចី ហើយអាចនាំឱ្យមានការប្រែប្រួលនៃចំនួន EMI និង/ឬរយៈពេលនៃប្រាក់កម្ចី ដូចដែលបានចាត់ទុកថាចាំបាច់ដោយអ្នកផ្តល់ប្រាក់កម្ចី។ + ការសងត្រលប់នៃប្រាក់កម្ចីផ្ទាល់ខ្លួននឹងធ្វើឡើងតាមរយៈការបង់រំលស់ប្រចាំខែស្មើៗគ្នា (EMIs) ដែលរួមមានទាំងប្រាក់ដើម និងការប្រាក់។ EMIs នឹងចាប់ផ្តើមពីកាលបរិច្ឆេទដែលបានបញ្ជាក់នៅក្នុងកិច្ចព្រមព្រៀងប្រាក់កម្ចី ហើយនឹងបន្តជារៀងរាល់ខែរហូតដល់ចំនួនប្រាក់កម្ចីពេញលេញ រួមជាមួយនឹងការប្រាក់ដែលត្រូវអនុវត្ត ត្រូវបានសងវិញ។ ការទូទាត់ជាមុន ឬការទូទាត់ដោយផ្នែកអាចត្រូវបានអនុញ្ញាតដោយអនុលោមតាមគោលនយោបាយដែលមានស្រាប់របស់អ្នកផ្តល់ប្រាក់កម្ចី ហើយអាចទាក់ទាញការគិតថ្លៃជាក់លាក់ ជាពិសេសសម្រាប់ប្រាក់កម្ចីអត្រាថេរ។ + ប្រាក់កម្ចីផ្ទាល់ខ្លួនជាធម្មតាគ្មានការធានានៅក្នុងធម្មជាតិ និងមិនទាមទារទ្រព្យបញ្ចាំទេ។ ទោះជាយ៉ាងណាក៏ដោយ អ្នកផ្តល់ប្រាក់កម្ចីរក្សាសិទ្ធិក្នុងការស្នើសុំការធានា ឬសន្តិសុខបន្ថែមក្នុងករណីពិសេស អាស្រ័យលើប្រវត្តិឥណទានរបស់អ្នកខ្ចី និងចំនួនប្រាក់កម្ចី។ តម្រូវការបែបនេះនឹងត្រូវបានបញ្ជាក់យ៉ាងច្បាស់នៅក្នុងលិខិតអនុម័ត។ អ្នកខ្ចីនឹងទទួលរាល់ចំណាយដែលទាក់ទងនឹងឯកសារ ឬបែបបទផ្លូវច្បាប់ ប្រសិនបើសមស្រប។ + ខណៈពេលដែលប្រាក់កម្ចីផ្ទាល់ខ្លួនជាទូទៅមិនទាមទារការធានារ៉ាប់រងលើទ្រព្យសកម្ម អ្នកផ្តល់ប្រាក់កម្ចីអាចណែនាំអោយអ្នកខ្ចីជ្រើសរើសយកគោលនយោបាយធានារ៉ាប់រងអាយុជីវិតដើម្បីការពារចំនួនប្រាក់កម្ចីដែលនៅសល់ក្នុងករណីមានមរណភាពរបស់អ្នកខ្ចី។ នេះមិនមែនជាការចាំបាច់ទេ ប៉ុន្តែត្រូវបានណែនាំជាវិធានការការពារហិរញ្ញវត្ថុសម្រាប់គ្រួសារអ្នកខ្ចី ឬអ្នកនៅក្នុងបន្ទុក។ + ការមិនទូទាត់ ឬការទូទាត់យឺតយ៉ាវនៃ EMIs នឹងបង្កើតជាការខកខាន។ ក្នុងករណីបែបនេះ អ្នកផ្តល់ប្រាក់កម្ចីអាចចាប់ផ្តើមនីតិវិធីស្តារឡើងវិញដើម្បីប្រមូលបំណុលដែលនៅសល់ ដែលអាចរួមបញ្ចូលទាំងចំណាត់ការផ្លូវច្បាប់។ ការប្រាក់ពិន័យ និងការគិតថ្លៃផ្សេងទៀតអាចត្រូវបានអនុវត្តលើចំនួនទឹកប្រាក់ដែលហួសកាលកំណត់។ ការខកខានជាបន្តបន្ទាប់ ឬម្តងហើយម្តងទៀតអាចជះឥទ្ធិពលអវិជ្ជមានដល់ពិន្ទុឥណទានរបស់អ្នកខ្ចី និងសមត្ថភាពខ្ចីនាពេលអនាគត។ + អ្នកខ្ចីតម្រូវឱ្យដាក់ឯកសារត្រឹមត្រូវ និងពេញលេញ រួមទាំងប៉ុន្តែមិនកំណត់ចំពោះភស្តុតាងអត្តសញ្ញាណ ភស្តុតាងអាសយដ្ឋាន ព័ត៌មានលម្អិតអំពីប្រាក់ចំណូល របាយការណ៍ធនាគារ និងឯកសារផ្សេងទៀតដែលស្នើសុំដោយអ្នកផ្តល់ប្រាក់កម្ចី។ រាល់ភាពមិនស្របគ្នា ការបង្ហាញខុស ឬការក្លែងបន្លំនៅក្នុងឯកសារដែលបានដាក់ជូនអាចបណ្តាលឱ្យមានការបដិសេធពាក្យស្នើសុំប្រាក់កម្ចី ឬការបញ្ចប់កិច្ចព្រមព្រៀងប្រាក់កម្ចី ព្រមទាំងផលវិបាកផ្លូវច្បាប់ដែលអាចកើតមាន។ + អ្នកខ្ចីត្រូវធានានូវការអនុលោមតាមច្បាប់ពេញលេញជាមួយនឹងច្បាប់ក្នុងតំបន់ រដ្ឋ និងថ្នាក់ជាតិជាធរមានទាំងអស់ទាក់ទងនឹងកាតព្វកិច្ចហិរញ្ញវត្ថុរបស់ពួកគេ។ នេះរួមបញ្ចូលទាំងការទូទាត់ពន្ធទាន់ពេលវេលា ការប្រកាន់ខ្ជាប់នូវតម្រូវការបទប្បញ្ញត្តិ និងការដាក់ឯកសារត្រឹមត្រូវ។ អ្នកផ្តល់ប្រាក់កម្ចីនឹងមិនទទួលខុសត្រូវចំពោះការរំលោភច្បាប់ណាមួយដែលប្រព្រឹត្តដោយអ្នកខ្ចីឡើយ។ + អ្នកផ្តល់ប្រាក់កម្ចីអាចកែសម្រួល ឬធ្វើបច្ចុប្បន្នភាពលក្ខខណ្ឌទាំងនេះ ដោយផ្តល់ជូនការជូនដំណឹងជាមុនសមរម្យដល់អ្នកខ្ចី។ កិច្ចព្រមព្រៀងប្រាក់កម្ចីនឹងត្រូវបានចាត់ទុកថាបានបញ្ចប់នៅពេលសងប្រាក់កម្ចីពេញលេញ ឬតាមរយៈនីតិវិធីរឹបអូសដែលបានចាប់ផ្តើមដោយសារតែការខកខាន។ + រាល់វិវាទដែលកើតចេញពី ឬទាក់ទងនឹងកិច្ចព្រមព្រៀងប្រាក់កម្ចីផ្ទាល់ខ្លួននឹងស្ថិតនៅក្រោមដែនសមត្ថកិច្ចផ្តាច់មុខរបស់តុលាការនៅទីតាំងការិយាល័យចុះបញ្ជីរបស់អ្នកផ្តល់ប្រាក់កម្ចី លើកលែងតែមានការបញ្ជាក់ផ្សេងពីនេះនៅក្នុងកិច្ចព្រមព្រៀង។ + + + + + បង្ហោះរបាយការណ៍គណនីធនាគារ + បង្ហោះឯកសារទ្រព្យបញ្ចាំ + បង្ហោះហត្ថលេខា + + មានអ្វីខុស! មិនអាចបង្ហោះឯកសារបានទេ + សូមបង្ហោះរបាយការណ៍ធនាគារ + សូមបង្ហោះឯកសារទ្រព្យសម្បត្តិ + សូមបង្ហោះហត្ថលេខា + ឯកសារត្រូវបានបង្ហោះដោយជោគជ័យ! + + ចុះហត្ថលេខានៅទីនេះ + កំណត់ឡើងវិញ + ហត្ថលេខា + ចាប់យក + ភ្ជាប់ + បង្ហោះហត្ថលេខា + រក្សាទុក & ដាក់ជូន + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-kn/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-kn/strings.xml new file mode 100644 index 000000000..d09aff3a7 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-kn/strings.xml @@ -0,0 +1,157 @@ + + + + + + ಹೊಸ ಸಾಲಕ್ಕೆ ಅರ್ಜಿ ಸಲ್ಲಿಸಿ + + + ಸಾಲದ ಅರ್ಜಿ + + + ಅರ್ಜಿದಾರರ ಹೆಸರು + ಸಾಲದ ಉತ್ಪನ್ನ + ಸಾಲದ ಉದ್ದೇಶ + ವಿತರಣಾ ದಿನಾಂಕ + ಅಸಲು ಮೊತ್ತ + ಕರೆನ್ಸಿ + + + ಮುಂದುವರಿಸಿ + ಸರಿ + ರದ್ದುಮಾಡಿ + ಸಾಲಕ್ಕೆ ಅರ್ಜಿ ಸಲ್ಲಿಸಿ + ಮುಂದೆ + + + ಅರ್ಜಿದಾರರ ಹೆಸರು ಅಗತ್ಯವಿದೆ + ಅರ್ಜಿದಾರರ ಹೆಸರು ಕನಿಷ್ಠ 4 ಅಕ್ಷರಗಳಿರಬೇಕು + ಅರ್ಜಿದಾರರ ಹೆಸರು 100 ಅಕ್ಷರಗಳನ್ನು ಮೀರಬಾರದು + ಅರ್ಜಿದಾರರ ಹೆಸರು ಅಮಾನ್ಯ ಅಕ್ಷರಗಳನ್ನು ಒಳಗೊಂಡಿದೆ + + + ಸಾಲದ ಉತ್ಪನ್ನ ಅಗತ್ಯವಿದೆ + + + ಅಸಲು ಮೊತ್ತ ಅತ್ಯಗತ್ಯ + ದಯವಿಟ್ಟು ಮಾನ್ಯ ಮೊತ್ತದ ಸ್ವರೂಪವನ್ನು ನಮೂದಿಸಿ + ಅಸಲು ಮೊತ್ತ ತೀರಾ ಕಡಿಮೆಯಾಗಿದೆ + ಅಸಲು ಮೊತ್ತ ಗರಿಷ್ಠ ಮಿತಿಯನ್ನು ಮೀರುತ್ತದೆ + ಆಯ್ಕೆ ಮಾಡಿದ ಕರೆನ್ಸಿಗೆ ಮೊತ್ತವು ತುಂಬಾ ದಶಮಾಂಶ ಸ್ಥಾನಗಳನ್ನು ಒಳಗೊಂಡಿದೆ + ಮೊತ್ತವು ಮಾನ್ಯ ಗುಣಕಗಳಲ್ಲಿಲ್ಲ + + + ವಿತರಣಾ ದಿನಾಂಕ ಅಗತ್ಯವಿದೆ + ದಯವಿಟ್ಟು ಮಾನ್ಯ ದಿನಾಂಕ ಸ್ವರೂಪವನ್ನು ನಮೂದಿಸಿ (DD/MM/YYYY) + ವಿತರಣಾ ದಿನಾಂಕ ಭವಿಷ್ಯದಲ್ಲಿರಬೇಕು + ವಿತರಣಾ ದಿನಾಂಕ ಭವಿಷ್ಯದ ತುಂಬಾ ದೂರದಲ್ಲಿದೆ (ಗರಿಷ್ಠ 10 ವರ್ಷಗಳು) + + + ನಮ್ಮ ಕಡೆಯಿಂದ ಸರ್ವರ್ ಸಮಸ್ಯೆ, ಮುಂದುವರಿಯಲು ಸಾಧ್ಯವಿಲ್ಲ, ಸ್ವಲ್ಪ ಸಮಯದ ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ + ತುಂಬಾ ವಿಫಲ ಪ್ರಯತ್ನಗಳು. ದಯವಿಟ್ಟು ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. + ಸಾಲದ ಅರ್ಜಿಯನ್ನು ಸಲ್ಲಿಸಲು ವಿಫಲವಾಗಿದೆ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. + + + ಸಾಲದ ಅರ್ಜಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಸಲ್ಲಿಸಲಾಗಿದೆ! + ಅರ್ಜಿ ಕರಡು ಯಶಸ್ವಿಯಾಗಿ ಉಳಿಸಲಾಗಿದೆ + + + ಉಳಿಸದ ಬದಲಾವಣೆಗಳು! + ನೀವು ಉಳಿಸದ ಬದಲಾವಣೆಗಳನ್ನು ಹೊಂದಿದ್ದೀರಿ. ನೀವು ಖಂಡಿತವಾಗಿಯೂ ಹಿಂತಿರುಗಲು ಬಯಸುತ್ತೀರಾ? + + + ತ್ಯಜಿಸಿ + ಕರಡು ಉಳಿಸಿ + ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ + ರದ್ದುಮಾಡಿ + ಮುಂದುವರಿಸಿ + + + ನಿಮ್ಮ ಪೂರ್ಣ ಹೆಸರನ್ನು ನಮೂದಿಸಿ + ಸಾಲದ ಉತ್ಪನ್ನವನ್ನು ಆಯ್ಕೆಮಾಡಿ + ಸಾಲದ ಮೊತ್ತವನ್ನು ನಮೂದಿಸಿ + ವಿತರಣಾ ದಿನಾಂಕವನ್ನು ಆಯ್ಕೆಮಾಡಿ + ಉದ್ದೇಶವನ್ನು ಸಂಕ್ಷಿಪ್ತವಾಗಿ ವಿವರಿಸಿ + + + ಕರೆನ್ಸಿ ಬದಲಾಗಿದೆ. ಮೊತ್ತವನ್ನು ಮರುಪರಿಶೀಲಿಸಲಾಗುತ್ತದೆ. + ಕನಿಷ್ಠ ಸಾಲದ ಮೊತ್ತ: %1$s + ಗರಿಷ್ಠ ಸಾಲದ ಮೊತ್ತ: %1$s + ವಿವರಗಳನ್ನು ಖಚಿತಪಡಿಸಿ + + ವಿನಂತಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಸಲ್ಲಿಸಲಾಗಿದೆ + ನಿಮ್ಮ ಸಾಲದ ಅರ್ಜಿಯನ್ನು ಸಲ್ಲಿಸಲಾಗಿದೆ. ಪರಿಶೀಲನೆಯ ನಂತರ ನಿಮಗೆ ತಿಳಿಸಲಾಗುವುದು. + ಮುಖಪುಟಕ್ಕೆ ಹಿಂತಿರುಗಿ + + ವಿನಂತಿಯನ್ನು ಸಲ್ಲಿಸಲು ವಿಫಲವಾಗಿದೆ + ನಿಮ್ಮ ಸಾಲದ ಅರ್ಜಿಯನ್ನು ಸಲ್ಲಿಸುವಾಗ ಏನೋ ತಪ್ಪಾಗಿದೆ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. + ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ + + + + ಹೊಸ ಸಾಲಕ್ಕೆ ಅರ್ಜಿ ಸಲ್ಲಿಸಿ + ಸಾಲದ ಪ್ರಕಾರವನ್ನು ಆರಿಸಿ + ಪ್ರಸ್ತುತ ಸಾಲದ ಉತ್ಪನ್ನಗಳು ಲಭ್ಯವಿಲ್ಲ + ನೀವು ಇಂಟರ್ನೆಟ್‌ಗೆ ಸಂಪರ್ಕಗೊಂಡಿದ್ದೀರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ + ನಿಯಮಗಳು ಮತ್ತು ಷರತ್ತುಗಳನ್ನು ಸ್ವೀಕರಿಸಿ + + + %1$s ಸಾಲಕ್ಕೆ ಅರ್ಜಿ ಸಲ್ಲಿಸಿ + %1$s ಸಾಲ ಪಡೆಯಿರಿ + %1$s ವರೆಗೆ + %1$s ರಿಂದ %2$s ಬಡ್ಡಿ + ನಿಯಮಗಳು ಮತ್ತು ಷರತ್ತುಗಳು + + ಸಾಲ ಮಂಜೂರಾತಿ ಮತ್ತು ವಿತರಣೆ + ಬಡ್ಡಿ ದರ + ಮುಂದುವರಿಸಿ + ಮರುಪಾವತಿ + ಭದ್ರತೆ ಮತ್ತು ಮೇಲಾಧಾರ + ವಿಮೆ + ಡೀಫಾಲ್ಟ್ + ದಾಖಲಾತಿ + ಕಾನೂನು ಅನುಸರಣೆ + ತಿದ್ದುಪಡಿಗಳು ಮತ್ತು ಹಕ್ಕುತ್ಯಾಗ + ನ್ಯಾಯವ್ಯಾಪ್ತಿ + + + ಅರ್ಜಿದಾರರ ಅರ್ಹತೆ, ಆದಾಯದ ಸ್ಥಿರತೆ, ಸಾಲದ ಅರ್ಹತೆ ಮತ್ತು ಸಾಲದಾತರ ಆಂತರಿಕ ಕ್ರೆಡಿಟ್ ನೀತಿಗಳ ಆಧಾರದ ಮೇಲೆ ವೈಯಕ್ತಿಕ ಸಾಲವನ್ನು ಮಂಜೂರು ಮಾಡಲಾಗುತ್ತದೆ. ಯಾವುದೇ ಕಾರಣವನ್ನು ನೀಡದೆ ತನ್ನ ಸ್ವಂತ ವಿವೇಚನೆಯಿಂದ ಯಾವುದೇ ವೈಯಕ್ತಿಕ ಸಾಲದ ಅರ್ಜಿಯನ್ನು ಅನುಮೋದಿಸಲು ಅಥವಾ ತಿರಸ್ಕರಿಸಲು ಸಾಲದಾತ ಹಕ್ಕನ್ನು ಕಾಯ್ದಿರಿಸಿಕೊಂಡಿದ್ದಾನೆ. ಮಂಜೂರಾದ ಸಾಲದ ಮೊತ್ತವನ್ನು ಸಾಲದ ಒಪ್ಪಂದ ಮತ್ತು ದಾಖಲಾತಿ ಅವಶ್ಯಕತೆಗಳ ಆಧಾರದ ಮೇಲೆ ಪೂರ್ಣವಾಗಿ ಅಥವಾ ಭಾಗಗಳಲ್ಲಿ ವಿತರಿಸಬಹುದು. ವಿತರಣೆಯು ಕೆವೈಸಿ ಪ್ರಕ್ರಿಯೆಗಳ ಯಶಸ್ವಿ ಪೂರ್ಣಗೊಳಿಸುವಿಕೆ, ಸಲ್ಲಿಸಿದ ದಾಖಲೆಗಳ ಪರಿಶೀಲನೆ ಮತ್ತು ಮಂಜೂರಾತಿ ಪತ್ರದಲ್ಲಿ ನಿರ್ದಿಷ್ಟಪಡಿಸಿದ ಎಲ್ಲಾ ಷರತ್ತುಗಳ ಪಾಲನೆಗೆ ಒಳಪಟ್ಟಿರುತ್ತದೆ. + ವೈಯಕ್ತಿಕ ಸಾಲವು ಸಾಲದ ಒಪ್ಪಂದದ ಸಮಯದಲ್ಲಿ ಸಾಲಗಾರನು ಆಯ್ಕೆ ಮಾಡಿದಂತೆ ಸ್ಥಿರ ಅಥವಾ ಫ್ಲೋಟಿಂಗ್ ಬಡ್ಡಿ ದರವನ್ನು ಹೊಂದಿರಬಹುದು. ಫ್ಲೋಟಿಂಗ್ ಬಡ್ಡಿ ದರದ ಸಾಲಗಳ ಸಂದರ್ಭದಲ್ಲಿ, ಅನ್ವಯವಾಗುವ ದರವು ಸಾಲದಾತರ ಬೆಂಚ್‌ಮಾರ್ಕ್ ಅಥವಾ ಉಲ್ಲೇಖ ದರದಲ್ಲಿನ ಬದಲಾವಣೆಗಳ ಆಧಾರದ ಮೇಲೆ ಆವರ್ತಕ ಪರಿಷ್ಕರಣೆಗಳಿಗೆ ಒಳಪಟ್ಟಿರುತ್ತದೆ. ಇಂತಹ ಬದಲಾವಣೆಗಳು ಸಾಲಗಾರನ ಮೇಲೆ ಕಡ್ಡಾಯವಾಗಿರುತ್ತದೆ ಮತ್ತು ಸಾಲದಾತನು ಅಗತ್ಯವೆಂದು ಪರಿಗಣಿಸಿದಂತೆ ಇಎಂಐ ಮೊತ್ತ ಮತ್ತು/ಅಥವಾ ಸಾಲದ ಅವಧಿಯಲ್ಲಿ ವ್ಯತ್ಯಾಸಕ್ಕೆ ಕಾರಣವಾಗಬಹುದು. + ವೈಯಕ್ತಿಕ ಸಾಲದ ಮರುಪಾವತಿಯನ್ನು ಸಮಾನ ಮಾಸಿಕ ಕಂತುಗಳ (ಇಎಂಐ) ಮೂಲಕ ಮಾಡಲಾಗುತ್ತದೆ, ಇದರಲ್ಲಿ ಅಸಲು ಮತ್ತು ಬಡ್ಡಿ ಎರಡೂ ಘಟಕಗಳು ಸೇರಿರುತ್ತವೆ. ಇಎಂಐಗಳು ಸಾಲದ ಒಪ್ಪಂದದಲ್ಲಿ ನಿರ್ದಿಷ್ಟಪಡಿಸಿದ ದಿನಾಂಕದಿಂದ ಪ್ರಾರಂಭವಾಗುತ್ತವೆ ಮತ್ತು ಅನ್ವಯವಾಗುವ ಬಡ್ಡಿಯೊಂದಿಗೆ ಪೂರ್ಣ ಸಾಲದ ಮೊತ್ತವನ್ನು ಮರುಪಾವತಿಸುವವರೆಗೆ ಮಾಸಿಕ ಆಧಾರದ ಮೇಲೆ ಮುಂದುವರಿಯುತ್ತದೆ. ಸಾಲದಾತರ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ನೀತಿಗಳ ಪ್ರಕಾರ ಪೂರ್ವಪಾವತಿ ಅಥವಾ ಭಾಗಶಃ ಪಾವತಿಯನ್ನು ಅನುಮತಿಸಬಹುದು ಮತ್ತು ನಿರ್ದಿಷ್ಟ ಶುಲ್ಕಗಳು ಆಕರ್ಷಿಸಬಹುದು, ವಿಶೇಷವಾಗಿ ಸ್ಥಿರ ದರದ ಸಾಲಗಳಿಗೆ. + ವೈಯಕ್ತಿಕ ಸಾಲಗಳು ಸಾಮಾನ್ಯವಾಗಿ ಅಸುರಕ್ಷಿತ ಸ್ವಭಾವದವು ಮತ್ತು ಮೇಲಾಧಾರದ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ. ಆದಾಗ್ಯೂ, ಸಾಲಗಾರನ ಕ್ರೆಡಿಟ್ ಪ್ರೊಫೈಲ್ ಮತ್ತು ಸಾಲದ ಮೊತ್ತವನ್ನು ಅವಲಂಬಿಸಿ ಅಸಾಧಾರಣ ಸಂದರ್ಭಗಳಲ್ಲಿ ಹೆಚ್ಚುವರಿ ಗ್ಯಾರಂಟಿ ಅಥವಾ ಭದ್ರತೆಯನ್ನು ಕೋರುವ ಹಕ್ಕನ್ನು ಸಾಲದಾತ ಕಾಯ್ದಿರಿಸಿಕೊಂಡಿದ್ದಾನೆ. ಅಂತಹ ಯಾವುದೇ ಅವಶ್ಯಕತೆಯನ್ನು ಮಂಜೂರಾತಿ ಪತ್ರದಲ್ಲಿ ಸ್ಪಷ್ಟವಾಗಿ ಹೇಳಲಾಗುತ್ತದೆ. ಅನ್ವಯವಾಗುವುದಾದರೆ, ದಾಖಲಾತಿ ಅಥವಾ ಕಾನೂನು ಔಪಚಾರಿಕತೆಗಳಿಗೆ ಸಂಬಂಧಿಸಿದ ಎಲ್ಲಾ ವೆಚ್ಚಗಳನ್ನು ಸಾಲಗಾರನು ಭರಿಸಬೇಕಾಗುತ್ತದೆ. + ವೈಯಕ್ತಿಕ ಸಾಲಗಳಿಗೆ ಸಾಮಾನ್ಯವಾಗಿ ಆಸ್ತಿ ವಿಮೆಯ ಅಗತ್ಯವಿಲ್ಲದಿದ್ದರೂ, ಸಾಲಗಾರನ ಮರಣದ ಸಂದರ್ಭದಲ್ಲಿ ಬಾಕಿ ಇರುವ ಸಾಲದ ಮೊತ್ತವನ್ನು ರಕ್ಷಿಸಲು ಸಾಲಗಾರನು ಜೀವ ವಿಮಾ ಪಾಲಿಸಿಯನ್ನು ಆರಿಸಿಕೊಳ್ಳುವಂತೆ ಸಾಲದಾತ ಶಿಫಾರಸು ಮಾಡಬಹುದು. ಇದು ಕಡ್ಡಾಯವಲ್ಲ ಆದರೆ ಸಾಲಗಾರನ ಕುಟುಂಬ ಅಥವಾ ಅವಲಂಬಿತರಿಗೆ ಆರ್ಥಿಕ ರಕ್ಷಣಾ ಕ್ರಮವಾಗಿ ಸಲಹೆ ನೀಡಲಾಗುತ್ತದೆ. + ಇಎಂಐಗಳ ಪಾವತಿ ಮಾಡದಿರುವುದು ಅಥವಾ ವಿಳಂಬವಾಗಿ ಪಾವತಿಸುವುದು ಡೀಫಾಲ್ಟ್ ಅನ್ನು ರೂಪಿಸುತ್ತದೆ. ಅಂತಹ ಸಂದರ್ಭಗಳಲ್ಲಿ, ಬಾಕಿ ಇರುವ ಹಣವನ್ನು ವಸೂಲಿ ಮಾಡಲು ಸಾಲದಾತನು ಮರುಪಡೆಯುವಿಕೆ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಪ್ರಾರಂಭಿಸಬಹುದು, ಇದರಲ್ಲಿ ಕಾನೂನು ಕ್ರಮವೂ ಸೇರಿರಬಹುದು. ಮಿತಿಮೀರಿದ ಮೊತ್ತಗಳಿಗೆ ದಂಡದ ಬಡ್ಡಿ ಮತ್ತು ಇತರೆ ಶುಲ್ಕಗಳನ್ನು ಅನ್ವಯಿಸಬಹುದು. ನಿರಂತರ ಅಥವಾ ಪುನರಾವರ್ತಿತ ಡೀಫಾಲ್ಟ್ ಸಾಲಗಾರನ ಕ್ರೆಡಿಟ್ ಸ್ಕೋರ್ ಮತ್ತು ಭವಿಷ್ಯದಲ್ಲಿ ಸಾಲ ಪಡೆಯುವ ಸಾಮರ್ಥ್ಯದ ಮೇಲೆ ಪ್ರತಿಕೂಲ ಪರಿಣಾಮ ಬೀರಬಹುದು. + ಗುರುತಿನ ಪುರಾವೆ, ವಿಳಾಸದ ಪುರಾವೆ, ಆದಾಯದ ವಿವರಗಳು, ಬ್ಯಾಂಕ್ ಹೇಳಿಕೆಗಳು ಮತ್ತು ಸಾಲದಾತ ವಿನಂತಿಸಿದ ಯಾವುದೇ ಇತರ ದಾಖಲೆಗಳು ಸೇರಿದಂತೆ ಆದರೆ ಸೀಮಿತವಾಗಿಲ್ಲದ ನಿಖರವಾದ ಮತ್ತು ಸಂಪೂರ್ಣ ದಾಖಲಾತಿಯನ್ನು ಸಾಲಗಾರನು ಸಲ್ಲಿಸಬೇಕು. ಸಲ್ಲಿಸಿದ ದಾಖಲೆಗಳಲ್ಲಿ ಯಾವುದೇ ವ್ಯತ್ಯಾಸ, ತಪ್ಪು ನಿರೂಪಣೆ ಅಥವಾ ಸುಳ್ಳುತನವು ಸಾಲದ ಅರ್ಜಿಯನ್ನು ತಿರಸ್ಕರಿಸಲು ಅಥವಾ ಸಾಲದ ಒಪ್ಪಂದವನ್ನು ರದ್ದುಗೊಳಿಸಲು ಕಾರಣವಾಗಬಹುದು, ಜೊತೆಗೆ ಸಂಭಾವ್ಯ ಕಾನೂನು ಪರಿಣಾಮಗಳನ್ನು ಎದುರಿಸಬೇಕಾಗುತ್ತದೆ. + ತಮ್ಮ ಹಣಕಾಸಿನ ಕಟ್ಟುಪಾಡುಗಳಿಗೆ ಸಂಬಂಧಿಸಿದಂತೆ ಅನ್ವಯವಾಗುವ ಎಲ್ಲಾ ಸ್ಥಳೀಯ, ರಾಜ್ಯ ಮತ್ತು ರಾಷ್ಟ್ರೀಯ ಕಾನೂನುಗಳ ಸಂಪೂರ್ಣ ಅನುಸರಣೆಯನ್ನು ಸಾಲಗಾರನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಬೇಕು. ಇದರಲ್ಲಿ ಸಮಯೋಚಿತ ತೆರಿಗೆ ಪಾವತಿ, ನಿಯಂತ್ರಕ ಅವಶ್ಯಕತೆಗಳ ಪಾಲನೆ ಮತ್ತು ಮಾನ್ಯ ದಾಖಲೆಗಳ ಸಲ್ಲಿಕೆ ಸೇರಿವೆ. ಸಾಲಗಾರನು ಮಾಡಿದ ಯಾವುದೇ ಕಾನೂನು ಉಲ್ಲಂಘನೆಗಳಿಗೆ ಸಾಲದಾತ ಜವಾಬ್ದಾರನಾಗಿರುವುದಿಲ್ಲ. + ಸಾಲದಾತನು ಕಾಲಕಾಲಕ್ಕೆ ಈ ನಿಯಮಗಳು ಮತ್ತು ಷರತ್ತುಗಳನ್ನು ಪರಿಷ್ಕರಿಸಬಹುದು ಅಥವಾ ನವೀಕರಿಸಬಹುದು, ಸಾಲಗಾರನಿಗೆ ಸೂಕ್ತ ಪೂರ್ವ ಸೂಚನೆಗೆ ಒಳಪಟ್ಟಿರುತ್ತದೆ. ಸಾಲದ ಮೊತ್ತವನ್ನು ಪೂರ್ಣವಾಗಿ ಮರುಪಾವತಿಸಿದ ನಂತರ ಅಥವಾ ಡೀಫಾಲ್ಟ್ ಕಾರಣದಿಂದ ಪ್ರಾರಂಭಿಸಲಾದ ಫೋರ್‌ಕ್ಲೋಸರ್ ಪ್ರಕ್ರಿಯೆಗಳ ಮೂಲಕ ಸಾಲದ ಒಪ್ಪಂದವು ಮುಕ್ತಾಯಗೊಂಡಿದೆ ಎಂದು ಪರಿಗಣಿಸಲಾಗುತ್ತದೆ. + ವೈಯಕ್ತಿಕ ಸಾಲ ಒಪ್ಪಂದದಿಂದ ಉಂಟಾಗುವ ಅಥವಾ ಸಂಬಂಧಿಸಿದ ಎಲ್ಲಾ ವಿವಾದಗಳು ಒಪ್ಪಂದದಲ್ಲಿ ಸ್ಪಷ್ಟವಾಗಿ ಸೂಚಿಸದಿದ್ದಲ್ಲಿ ಸಾಲದಾತರ ನೋಂದಾಯಿತ ಕಚೇರಿಯ ಸ್ಥಳದಲ್ಲಿರುವ ನ್ಯಾಯಾಲಯಗಳ ವಿಶೇಷ ನ್ಯಾಯವ್ಯಾಪ್ತಿಗೆ ಒಳಪಟ್ಟಿರುತ್ತವೆ. + + + + + ಬ್ಯಾಂಕ್ ಖಾತೆ ಸ್ಟೇಟ್‌ಮೆಂಟ್‌ಗಳನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ + ಮೇಲಾಧಾರ ದಾಖಲೆಗಳನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ + ಹೊಸ ಸಹಿ ಅಪ್‌ಲೋಡ್ ಮಾಡಿ + + ಏನೋ ತಪ್ಪಾಗಿದೆ! ಫೈಲ್ ಅಪ್‌ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ + ದಯವಿಟ್ಟು ಬ್ಯಾಂಕ್ ಸ್ಟೇಟ್‌ಮೆಂಟ್ ಅಪ್‌ಲೋಡ್ ಮಾಡಿ + ದಯವಿಟ್ಟು ಆಸ್ತಿ ದಾಖಲೆಯನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ + ದಯವಿಟ್ಟು ಸಹಿಯನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ + ದಾಖಲೆಗಳನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಅಪ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ! + + ಇಲ್ಲಿ ಸಹಿ ಮಾಡಿ + ಮರುಹೊಂದಿಸಿ + ಸಹಿ + ಸೆರೆಹಿಡಿಯಿರಿ + ಲಗತ್ತಿಸಿ + ಸಹಿ ಅಪ್‌ಲೋಡ್ ಮಾಡಿ + ಉಳಿಸಿ ಮತ್ತು ಸಲ್ಲಿಸಿ + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-ml/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-ml/strings.xml new file mode 100644 index 000000000..22eb18ef4 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-ml/strings.xml @@ -0,0 +1,157 @@ + + + + + + പുതിയ വായ്പയ്ക്ക് അപേക്ഷിക്കുക + + + വായ്പ അപേക്ഷ + + + അപേക്ഷകന്റെ പേര് + വായ്പ ഉൽപ്പന്നം + വായ്പയുടെ ഉദ്ദേശ്യം + വിതരണ തീയതി + മുതൽ തുക + കറൻസി + + + തുടരുക + ശരി + റദ്ദാക്കുക + വായ്പയ്ക്ക് അപേക്ഷിക്കുക + അടുത്തത് + + + അപേക്ഷകന്റെ പേര് ആവശ്യമാണ് + അപേക്ഷകന്റെ പേരിൽ കുറഞ്ഞത് 4 അക്ഷരങ്ങളെങ്കിലും ഉണ്ടായിരിക്കണം + അപേക്ഷകന്റെ പേര് 100 അക്ഷരങ്ങളിൽ കൂടാൻ പാടില്ല + അപേക്ഷകന്റെ പേരിൽ അസാധുവായ അക്ഷരങ്ങൾ അടങ്ങിയിരിക്കുന്നു + + + വായ്പ ഉൽപ്പന്നം ആവശ്യമാണ് + + + മുതൽ തുക ആവശ്യമാണ് + ദയവായി ശരിയായ തുക ഫോർമാറ്റ് നൽകുക + മുതൽ തുക വളരെ ചെറുതാണ് + മുതൽ തുക പരമാവധി പരിധി കവിയുന്നു + തിരഞ്ഞെടുത്ത കറൻസിക്ക് തുകയിൽ ധാരാളം ദശാംശ സ്ഥാനങ്ങളുണ്ട് + തുക സാധുവായ ഗുണിതങ്ങളിലല്ല + + + വിതരണ തീയതി ആവശ്യമാണ് + ദയവായി സാധുവായ ഒരു തീയതി ഫോർമാറ്റ് നൽകുക (DD/MM/YYYY) + വിതരണ തീയതി ഭാവിയിലായിരിക്കണം + വിതരണ തീയതി വളരെ ദൂരെയുള്ള ഭാവിയിലാണ് (പരമാവധി 10 വർഷം) + + + സെർവർ തകരാർ കാരണം മുന്നോട്ട് പോകാനാവില്ല, കുറച്ചു കഴിഞ്ഞ് വീണ്ടും ശ്രമിക്കുക + ഒരുപാട് പരാജയപ്പെട്ട ശ്രമങ്ങൾ. ദയവായി പിന്നീട് വീണ്ടും ശ്രമിക്കുക. + വായ്പ അപേക്ഷ സമർപ്പിക്കുന്നതിൽ പരാജയപ്പെട്ടു. ദയവായി വീണ്ടും ശ്രമിക്കുക. + + + വായ്പ അപേക്ഷ വിജയകരമായി സമർപ്പിച്ചു! + അപേക്ഷയുടെ ഡ്രാഫ്റ്റ് വിജയകരമായി സേവ് ചെയ്തു + + + സേവ് ചെയ്യാത്ത മാറ്റങ്ങൾ! + നിങ്ങൾക്ക് സേവ് ചെയ്യാത്ത മാറ്റങ്ങളുണ്ട്. നിങ്ങൾക്ക് തിരികെ പോകണമെന്ന് ഉറപ്പാണോ? + + + നിരസിക്കുക + ഡ്രാഫ്റ്റ് സേവ് ചെയ്യുക + വീണ്ടും ശ്രമിക്കുക + റദ്ദാക്കുക + തുടരുക + + + നിങ്ങളുടെ മുഴുവൻ പേര് നൽകുക + വായ്പ ഉൽപ്പന്നം തിരഞ്ഞെടുക്കുക + വായ്പ തുക നൽകുക + വിതരണ തീയതി തിരഞ്ഞെടുക്കുക + ഉദ്ദേശ്യം ചുരുക്കത്തിൽ വിവരിക്കുക + + + കറൻസി മാറി. തുക വീണ്ടും സാധൂകരിക്കും. + കുറഞ്ഞ വായ്പ തുക: %1$s + കൂടിയ വായ്പ തുക: %1$s + വിശദാംശങ്ങൾ സ്ഥിരീകരിക്കുക + + അപേക്ഷ വിജയകരമായി സമർപ്പിച്ചു + നിങ്ങളുടെ വായ്പ അപേക്ഷ സമർപ്പിച്ചു. ഇത് പരിശോധിച്ചുകഴിഞ്ഞാൽ നിങ്ങളെ അറിയിക്കും. + തിരികെ ഹോമിലേക്ക് + + അപേക്ഷ സമർപ്പിക്കുന്നതിൽ പരാജയപ്പെട്ടു + നിങ്ങളുടെ വായ്പ അപേക്ഷ സമർപ്പിക്കുന്നതിനിടെ എന്തോ തകരാറുണ്ടായി. ദയവായി വീണ്ടും ശ്രമിക്കുക. + വീണ്ടും ശ്രമിക്കുക + + + + പുതിയ വായ്പയ്ക്ക് അപേക്ഷിക്കുക + വായ്പ തരം തിരഞ്ഞെടുക്കുക + നിലവിൽ വായ്പ ഉൽപ്പന്നങ്ങൾ ലഭ്യമല്ല + നിങ്ങൾ ഇന്റർനെറ്റുമായി ബന്ധിപ്പിച്ചിട്ടുണ്ടെന്ന് ഉറപ്പുവരുത്തുക + നിബന്ധനകളും വ്യവസ്ഥകളും അംഗീകരിക്കുക + + + %1$s വായ്പയ്ക്ക് അപേക്ഷിക്കുക + %1$s വായ്പ നേടുക + %1$s വരെ + %1$s മുതൽ %2$s വരെ പലിശ + നിബന്ധനകളും വ്യവസ്ഥകളും + + വായ്പ അനുവദിക്കലും വിതരണവും + പലിശ നിരക്ക് + തുടരുക + തിരിച്ചടവ് + സെക്യൂരിറ്റിയും കൊളാറ്ററലും + ഇൻഷുറൻസ് + വീഴ്ച വരുത്തൽ + ഡോക്യുമെന്റേഷൻ + നിയമപരമായ അനുസരണം + ഭേദഗതികളും അവസാനിപ്പിക്കലും + നിയമപരിധി + + + അപേക്ഷകന്റെ യോഗ്യത, വരുമാന സ്ഥിരത, ക്രെഡിറ്റ് യോഗ്യത, വായ്പ നൽകുന്നയാളുടെ ആന്തരിക ക്രെഡിറ്റ് നയങ്ങൾ എന്നിവ അടിസ്ഥാനമാക്കിയാണ് വ്യക്തിഗത വായ്പ അനുവദിക്കുന്നത്. യാതൊരു കാരണവും കൂടാതെ ഏത് വ്യക്തിഗത വായ്പ അപേക്ഷയും അംഗീകരിക്കാനോ നിരസിക്കാനോ ഉള്ള അധികാരം വായ്പ നൽകുന്നയാളിനുണ്ട്. അനുവദിച്ച വായ്പ തുക വായ്പ കരാറും ഡോക്യുമെന്റേഷൻ ആവശ്യകതകളും അനുസരിച്ച് പൂർണ്ണമായോ ഭാഗികമായോ വിതരണം ചെയ്യാം. കെവൈസി നടപടിക്രമങ്ങൾ വിജയകരമായി പൂർത്തിയാക്കൽ, സമർപ്പിച്ച രേഖകളുടെ പരിശോധന, അനുമതി കത്തിൽ വ്യക്തമാക്കിയിട്ടുള്ള എല്ലാ നിബന്ധനകളും പാലിക്കൽ എന്നിവയ്ക്ക് വിധേയമാണ് വിതരണം. + വായ്പ കരാർ സമയത്ത് വായ്പയെടുക്കുന്നയാൾ തിരഞ്ഞെടുക്കുന്നതനുസരിച്ച് വ്യക്തിഗത വായ്പയ്ക്ക് സ്ഥിരമായതോ മാറ്റം വരാവുന്നതോ ആയ പലിശ നിരക്ക് ഉണ്ടായിരിക്കാം. ഫ്ലോട്ടിംഗ് പലിശ നിരക്ക് വായ്പകളുടെ കാര്യത്തിൽ, വായ്പ നൽകുന്നയാളുടെ ബെഞ്ച്മാർക്ക് അല്ലെങ്കിൽ റഫറൻസ് നിരക്കിലെ മാറ്റങ്ങൾക്കനുസരിച്ച് ബാധകമായ നിരക്ക് കാലാകാലങ്ങളിൽ പരിഷ്കരിക്കുന്നതാണ്. അത്തരം മാറ്റങ്ങൾ വായ്പയെടുക്കുന്നയാളിന് ബാധകമായിരിക്കും, കൂടാതെ വായ്പ നൽകുന്നയാൾക്ക് ആവശ്യമെന്ന് തോന്നുന്ന പക്ഷം ഇഎംഐ തുകയിലും/അല്ലെങ്കിൽ വായ്പ കാലാവധിയിലും മാറ്റം വരുത്താവുന്നതാണ്. + വ്യക്തിഗത വായ്പയുടെ തിരിച്ചടവ് മുതലും പലിശയും അടങ്ങുന്ന ഇക്വേറ്റഡ് മന്ത്ലി ഇൻസ്റ്റാൾമെന്റുകൾ (ഇഎംഐ) വഴിയായിരിക്കും. വായ്പ കരാറിൽ വ്യക്തമാക്കിയിട്ടുള്ള തീയതി മുതൽ ഇഎംഐകൾ ആരംഭിക്കുകയും ബാധകമായ പലിശയോടൊപ്പം മുഴുവൻ വായ്പ തുകയും തിരിച്ചടയ്ക്കുന്നതുവരെ പ്രതിമാസ അടിസ്ഥാനത്തിൽ തുടരുകയും ചെയ്യും. വായ്പ നൽകുന്നയാളുടെ നിലവിലുള്ള നയങ്ങൾക്കനുസൃതമായി മുൻകൂട്ടി അടയ്ക്കലോ ഭാഗികമായി അടയ്ക്കലോ അനുവദിക്കാവുന്നതാണ്, പ്രത്യേകിച്ച് സ്ഥിര നിരക്ക് വായ്പകൾക്ക് ചില ചാർജുകൾ ഈടാക്കാം. + വ്യക്തിഗത വായ്പകൾക്ക് സാധാരണയായി ഈട് (collateral) ആവശ്യമില്ല. എന്നിരുന്നാലും, അപേക്ഷകന്റെ ക്രെഡിറ്റ് പ്രൊഫൈലും വായ്പ തുകയും അനുസരിച്ച്, അപൂർവ്വ സാഹചര്യങ്ങളിൽ അധിക ഗ്യാരണ്ടികളോ സെക്യൂരിറ്റിയോ ആവശ്യപ്പെടാനുള്ള അധികാരം വായ്പ നൽകുന്നയാളിനുണ്ട്. അത്തരം ആവശ്യകതകൾ എന്തെങ്കിലും ഉണ്ടെങ്കിൽ അത് അനുമതി കത്തിൽ വ്യക്തമായി രേഖപ്പെടുത്തിയിരിക്കും. ബാധകമാണെങ്കിൽ ഡോക്യുമെന്റേഷൻ അല്ലെങ്കിൽ നിയമപരമായ നടപടിക്രമങ്ങൾ എന്നിവയുമായി ബന്ധപ്പെട്ട എല്ലാ ചിലവുകളും വായ്പയെടുക്കുന്നയാൾ വഹിക്കേണ്ടതാണ്. + വ്യക്തിഗത വായ്പകൾക്ക് സാധാരണയായി അസറ്റ് ഇൻഷുറൻസ് ആവശ്യമില്ലെങ്കിലും, വായ്പയെടുക്കുന്നയാളുടെ മരണമുണ്ടായാൽ കുടിശ്ശികയുള്ള വായ്പ തുക സംരക്ഷിക്കുന്നതിനായി ഒരു ലൈഫ് ഇൻഷുറൻസ് പോളിസി തിരഞ്ഞെടുക്കാൻ വായ്പ നൽകുന്നയാൾ ശുപാർശ ചെയ്തേക്കാം. ഇത് നിർബന്ധമല്ലെങ്കിലും വായ്പയെടുക്കുന്നയാളുടെ കുടുംബത്തിനോ ആശ്രിതർക്കോ ഒരു സാമ്പത്തിക സംരക്ഷണ നടപടിയായി ഇത് അഭികാമ്യമാണ്. + ഇഎംഐകൾ അടയ്ക്കാതിരിക്കുകയോ വൈകിപ്പിക്കുകയോ ചെയ്യുന്നത് വീഴ്ചയായി കണക്കാക്കും. അത്തരം സന്ദർഭങ്ങളിൽ, കുടിശ്ശിക പിരിച്ചെടുക്കുന്നതിന് നിയമനടപടികൾ ഉൾപ്പെടെയുള്ള വീണ്ടെടുക്കൽ നടപടികൾ വായ്പ നൽകുന്നയാൾ ആരംഭിച്ചേക്കാം. കുടിശ്ശികയുള്ള തുകയ്ക്ക് പിഴ പലിശയും മറ്റ് ചാർജുകളും ഈടാക്കിയേക്കാം. തുടർച്ചയായ അല്ലെങ്കിൽ ആവർത്തിച്ചുള്ള വീഴ്ച വായ്പയെടുക്കുന്നയാളുടെ ക്രെഡിറ്റ് സ്കോറിനെയും ഭാവിയിൽ വായ്പ എടുക്കാനുള്ള കഴിവിനെയും ദോഷകരമായി ബാധിക്കും. + തിരിച്ചറിയൽ രേഖ, വിലാസ രേഖ, വരുമാന വിവരങ്ങൾ, ബാങ്ക് സ്റ്റേറ്റ്‌മെന്റുകൾ, വായ്പ നൽകുന്നയാൾ ആവശ്യപ്പെടുന്ന മറ്റ് രേഖകൾ എന്നിവയുൾപ്പെടെ കൃത്യവും പൂർണ്ണവുമായ രേഖകൾ വായ്പയെടുക്കുന്നയാൾ സമർപ്പിക്കേണ്ടതാണ്. സമർപ്പിച്ച രേഖകളിലെ എന്തെങ്കിലും പൊരുത്തക്കേടുകൾ, തെറ്റായ വിവരങ്ങൾ അല്ലെങ്കിൽ തിരിമറികൾ എന്നിവ വായ്പ അപേക്ഷ നിരസിക്കുന്നതിനോ വായ്പ കരാർ അവസാനിപ്പിക്കുന്നതിനോ കാരണമായേക്കാം, ഒപ്പം നിയമനടപടികൾക്കും സാധ്യതയുണ്ട്. + വായ്പയെടുക്കുന്നയാൾ അവരുടെ സാമ്പത്തിക ബാധ്യതകളുമായി ബന്ധപ്പെട്ട ബാധകമായ എല്ലാ പ്രാദേശിക, സംസ്ഥാന, ദേശീയ നിയമങ്ങളും പൂർണ്ണമായി പാലിക്കുന്നുണ്ടെന്ന് ഉറപ്പാക്കണം. നികുതികൾ കൃത്യസമയത്ത് അടയ്ക്കൽ, റെഗുലേറ്ററി ആവശ്യകതകൾ പാലിക്കൽ, സാധുവായ രേഖകൾ സമർപ്പിക്കൽ എന്നിവ ഇതിൽ ഉൾപ്പെടുന്നു. വായ്പയെടുക്കുന്നയാൾ നടത്തുന്ന നിയമലംഘനങ്ങൾക്ക് വായ്പ നൽകുന്നയാൾ ഉത്തരവാദിയായിരിക്കില്ല. + വായ്പയെടുക്കുന്നയാളിന് മതിയായ മുൻകൂൂർ അറിയിപ്പ് നൽകിക്കൊണ്ട്, ഈ നിബന്ധനകളും വ്യവസ്ഥകളും വായ്പ നൽകുന്നയാൾക്ക് കാലാകാലങ്ങളിൽ പരിഷ്കരിക്കാവുന്നതാണ്. വായ്പ തുക പൂർണ്ണമായി തിരിച്ചടയ്ക്കുമ്പോഴോ വീഴ്ച കാരണം ആരംഭിച്ച ജപ്തി നടപടികളിലൂടെയോ വായ്പ കരാർ അവസാനിച്ചതായി കണക്കാക്കും. + വ്യക്തിഗത വായ്പ കരാറുമായി ബന്ധപ്പെട്ട് ഉണ്ടാകുന്ന എല്ലാ തർക്കങ്ങളും, കരാറിൽ മറ്റെന്തെങ്കിലും വ്യക്തമാക്കിയിട്ടില്ലെങ്കിൽ, വായ്പ നൽകുന്നയാളുടെ രജിസ്റ്റർ ചെയ്ത ഓഫീസ് സ്ഥിതിചെയ്യുന്ന സ്ഥലത്തെ കോടതികളുടെ പ്രത്യേക അധികാരപരിധിയിൽ വരുന്നതാണ്. + + + + + ബാങ്ക് അക്കൗണ്ട് സ്റ്റേറ്റ്‌മെന്റുകൾ അപ്‌ലോഡ് ചെയ്യുക + ഈട് രേഖകൾ അപ്‌ലോഡ് ചെയ്യുക + ഒപ്പ് അപ്‌ലോഡ് ചെയ്യുക + + എന്തോ കുഴപ്പം സംഭവിച്ചു! ഫയൽ അപ്‌ലോഡ് ചെയ്യാൻ കഴിയുന്നില്ല + ദയവായി ഒരു ബാങ്ക് സ്റ്റേറ്റ്‌മെന്റ് അപ്‌ലോഡ് ചെയ്യുക + ദയവായി ഒരു പ്രോപ്പർട്ടി ഡോക്യുമെന്റ് അപ്‌ലോഡ് ചെയ്യുക + ദയവായി ഒരു ഒപ്പ് അപ്‌ലോഡ് ചെയ്യുക + രേഖകൾ വിജയകരമായി അപ്‌ലോഡ് ചെയ്തു! + + ഇവിടെ ഒപ്പിടുക + റീസെറ്റ് + ഒപ്പിടുക + ക്യാപ്‌ചർ + അറ്റാച്ചുചെയ്യുക + ഒപ്പ് അപ്‌ലോഡ് ചെയ്യുക + സേവ് & സബ്മിറ്റ് + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-my/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-my/strings.xml new file mode 100644 index 000000000..35e4244f9 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-my/strings.xml @@ -0,0 +1,157 @@ + + + + + + ချေးငွေအသစ်လျှောက်ထားရန် + + + ချေးငွေလျှောက်လွှာ + + + လျှောက်ထားသူအမည် + ချေးငွေအမျိုးအစား + ချေးငွေရယူရသည့်ရည်ရွယ်ချက် + ထုတ်ပေးမည့်ရက်စွဲ + ချေးငွေပမာဏ + ငွေကြေး + + + ဆက်လက်လုပ်ဆောင်ရန် + အိုကေ + မလုပ်တော့ပါ + ချေးငွေလျှောက်ထားရန် + နောက်တစ်ဆင့် + + + လျှောက်ထားသူအမည် လိုအပ်ပါသည် + လျှောက်ထားသူအမည်သည် အနည်းဆုံး ၄ လုံး ရှိရပါမည် + လျှောက်ထားသူအမည်သည် စာလုံးရေ ၁၀၀ ထက်မပိုရပါ + လျှောက်ထားသူအမည်တွင် မမှန်ကန်သော စာလုံးများ ပါဝင်နေပါသည် + + + ချေးငွေအမျိုးအစား လိုအပ်ပါသည် + + + ချေးငွေပမာဏ လိုအပ်ပါသည် + ကျေးဇူးပြု၍ မှန်ကန်သော ပမာဏပုံစံကို ရိုက်ထည့်ပါ + ချေးငွေပမာဏ အလွန်နည်းနေပါသည် + ချေးငွေပမာဏသည် သတ်မှတ်ထားသော အမြင့်ဆုံးပမာဏထက် ကျော်လွန်နေပါသည် + ရွေးချယ်ထားသော ငွေကြေးအတွက် ဒသမကိန်းများလွန်းနေပါသည် + ပမာဏသည် မှန်ကန်သော ဆခွဲကိန်းများတွင် မရှိပါ + + + ထုတ်ပေးမည့်ရက်စွဲ လိုအပ်ပါသည် + ကျေးဇူးပြု၍ မှန်ကန်သော ရက်စွဲပုံစံကို ရိုက်ထည့်ပါ (DD/MM/YYYY) + ထုတ်ပေးမည့်ရက်စွဲသည် အနာဂတ်တွင် ဖြစ်ရပါမည် + ထုတ်ပေးမည့်ရက်စွဲသည် အနာဂတ်တွင် အလွန်ဝေးကွာနေပါသည် (အများဆုံး ၁၀ နှစ်) + + + ကျွန်ုပ်တို့ဘက်မှ ဆာဗာပြဿနာကြောင့် ဆက်လက်လုပ်ဆောင်၍မရပါ၊ ခဏကြာပြီးနောက် ထပ်မံကြိုးစားကြည့်ပါ + မအောင်မြင်သော ကြိုးပမ်းမှုများလွန်းနေပါသည်။ ကျေးဇူးပြု၍ နောက်မှ ထပ်မံကြိုးစားပါ။ + ချေးငွေလျှောက်လွှာ တင်သွင်းမှု မအောင်မြင်ပါ။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။ + + + ချေးငွေလျှောက်လွှာကို အောင်မြင်စွာ တင်သွင်းပြီးပါပြီ! + လျှောက်လွှာမူကြမ်းကို အောင်မြင်စွာ သိမ်းဆည်းပြီးပါပြီ + + + မသိမ်းဆည်းရသေးသော ပြောင်းလဲမှုများ! + သင့်တွင် မသိမ်းဆည်းရသေးသော ပြောင်းလဲမှုများ ရှိနေပါသည်။ ပြန်ထွက်ရန် သေချာပါသလား? + + + ပယ်ဖျက်ရန် + မူကြမ်းသိမ်းရန် + ထပ်မံကြိုးစားရန် + မလုပ်တော့ပါ + ဆက်လက်လုပ်ဆောင်ရန် + + + သင့်အမည်အပြည့်အစုံကို ရိုက်ထည့်ပါ + ချေးငွေအမျိုးအစားကို ရွေးချယ်ပါ + ချေးငွေပမာဏကို ရိုက်ထည့်ပါ + ထုတ်ပေးမည့်ရက်စွဲကို ရွေးချယ်ပါ + ရည်ရွယ်ချက်ကို အတိုချုပ်ဖော်ပြပါ + + + ငွေကြေးပြောင်းလဲသွားပါပြီ။ ပမာဏကို ပြန်လည်စစ်ဆေးပါမည်။ + အနည်းဆုံး ချေးငွေပမာဏ: %1$s + အများဆုံး ချေးငွေပမာဏ: %1$s + အသေးစိတ်အချက်အလက်များကို အတည်ပြုပါ + + တောင်းဆိုမှုကို အောင်မြင်စွာ တင်သွင်းပြီးပါပြီ + သင့်ချေးငွေလျှောက်လွှာကို တင်သွင်းပြီးပါပြီ။ စစ်ဆေးပြီးပါက သင့်အား အကြောင်းကြားပါမည်။ + ပင်မစာမျက်နှာသို့ ပြန်သွားရန် + + တောင်းဆိုမှု တင်သွင်းရန် မအောင်မြင်ပါ + သင့်ချေးငွေလျှောက်လွှာကို တင်သွင်းရာတွင် တစ်စုံတစ်ခု မှားယွင်းသွားပါသည်။ ကျေးဇူးပြု၍ ထပ်မံကြိုးစားပါ။ + ထပ်မံကြိုးစားရန် + + + + ချေးငွေအသစ်လျှောက်ထားရန် + ချေးငွေအမျိုးအစားကို ရွေးချယ်ပါ + လောလောဆယ် ချေးငွေအမျိုးအစားများ မရရှိနိုင်ပါ + အင်တာနက် ချိတ်ဆက်ထားကြောင်း သေချာပါစေ + စည်းကမ်းနှင့် သတ်မှတ်ချက်များကို လက်ခံပါ + + + %1$s ချေးငွေလျှောက်ထားရန် + %1$s ချေးငွေရယူရန် + %1$s အထိ + %1$s မှ %2$s အတိုး + စည်းကမ်းနှင့် သတ်မှတ်ချက်များ + + ချေးငွေခွင့်ပြုခြင်းနှင့် ထုတ်ပေးခြင်း + အတိုးနှုန်း + ဆက်လက်လုပ်ဆောင်ရန် + ပြန်လည်ပေးဆပ်ခြင်း + လုံခြုံရေးနှင့် အာမခံပစ္စည်း + အာမခံ + ပျက်ကွက်ခြင်း + စာရွက်စာတမ်းများ + ဥပဒေလိုက်နာမှု + ပြင်ဆင်ချက်များနှင့် ရပ်စဲခြင်း + တရားစီရင်ပိုင်ခွင့် + + + လျှောက်ထားသူ၏ အရည်အချင်းပြည့်မီမှု၊ ဝင်ငွေတည်ငြိမ်မှု၊ အကြွေးပေးဆပ်နိုင်မှုနှင့် ချေးငွေပေးသူ၏ အတွင်းပိုင်း အကြွေးမူဝါဒများအပေါ် အခြေခံ၍ ကိုယ်ပိုင်ချေးငွေကို ခွင့်ပြုပေးမည်ဖြစ်ပါသည်။ ချေးငွေပေးသူသည် မည်သည့်အကြောင်းပြချက်မျှ မပေးဘဲ မိမိ၏သဘောဆန္ဒအရ ကိုယ်ပိုင်ချေးငွေလျှောက်လွှာကို အတည်ပြုရန် သို့မဟုတ် ပယ်ချရန် အခွင့်အရေးရှိသည်။ ခွင့်ပြုထားသော ချေးငွေပမာဏကို ချေးငွေသဘောတူညီချက်နှင့် စာရွက်စာတမ်းလိုအပ်ချက်များအပေါ် မူတည်၍ တစ်လုံးတစ်ခဲတည်း သို့မဟုတ် အပိုင်းလိုက် ထုတ်ပေးနိုင်သည်။ ထုတ်ပေးမှုသည် KYC လုပ်ငန်းစဉ်များ အောင်မြင်စွာ ပြီးဆုံးခြင်း၊ တင်သွင်းထားသော စာရွက်စာတမ်းများကို စစ်ဆေးခြင်းနှင့် ခွင့်ပြုမိန့်စာတွင် ဖော်ပြထားသော အခြေအနေများအားလုံးကို လိုက်နာခြင်းတို့အပေါ် မူတည်ပါသည်။ + ချေးငွေသဘောတူညီချက်ရယူချိန်တွင် ငွေချေးသူမှ ရွေးချယ်ထားသော အတိုးနှုန်းမှာ သေချာသော သို့မဟုတ် ပြောင်းလဲနိုင်သော အတိုးနှုန်း ဖြစ်နိုင်သည်။ ပြောင်းလဲနိုင်သော အတိုးနှုန်းဖြင့် ချေးငွေများအတွက်၊ သက်ဆိုင်ရာနှုန်းထားသည် ချေးငွေပေးသူ၏ စံနှုန်း သို့မဟုတ် ရည်ညွှန်းနှုန်းထား အပြောင်းအလဲများအပေါ် အခြေခံ၍ အခါအားလျော်စွာ ပြန်လည်ပြင်ဆင်မှုများအပေါ် မူတည်ပါသည်။ ထိုသို့သော ပြောင်းလဲမှုများသည် ငွေချေးသူအပေါ် တာဝန်ရှိမည်ဖြစ်ပြီး ချေးငွေပေးသူမှ လိုအပ်သည်ဟု ယူဆပါက EMI ပမာဏနှင့်/သို့မဟုတ် ချေးငွေသက်တမ်း အပြောင်းအလဲများကို ဖြစ်ပေါ်စေနိုင်သည်။ + ကိုယ်ပိုင်ချေးငွေ ပြန်လည်ပေးဆပ်ခြင်းကို အရင်းနှင့် အတိုး နှစ်မျိုးလုံး ပါဝင်သော လစဉ်အရစ်ကျငွေပေးချေမှု (EMI) များမှတစ်ဆင့် ပြုလုပ်ရမည်။ EMI များသည် ချေးငွေသဘောတူညီချက်တွင် သတ်မှတ်ထားသော ရက်စွဲမှ စတင်မည်ဖြစ်ပြီး ချေးငွေပမာဏတစ်ခုလုံးနှင့် သက်ဆိုင်ရာအတိုးများကို ပြန်လည်ပေးဆပ်ပြီးသည်အထိ လစဉ် ဆက်လက်ပေးဆောင်ရမည်ဖြစ်သည်။ ကြိုတင်ပေးချေမှု သို့မဟုတ် တစ်စိတ်တစ်ပိုင်းပေးချေမှုကို ချေးငွေပေးသူ၏ လက်ရှိမူဝါဒများအရ ခွင့်ပြုနိုင်ပြီး အထူးသဖြင့် သေချာသော အတိုးနှုန်းဖြင့် ချေးငွေများအတွက် အခကြေးငွေများ ပေးဆောင်ရနိုင်သည်။ + ကိုယ်ပိုင်ချေးငွေများသည် ပုံမှန်အားဖြင့် အာမခံမဲ့ အမျိုးအစားဖြစ်ပြီး အာမခံပစ္စည်း မလိုအပ်ပါ။ သို့သော်၊ ငွေချေးသူ၏ အကြွေးရာဇဝင်နှင့် ချေးငွေပမာဏအပေါ် မူတည်၍ ထူးခြားသောကိစ္စရပ်များတွင် အပိုဆောင်းအာမခံ သို့မဟုတ် လုံခြုံရေး တောင်းဆိုရန် အခွင့်အရေးကို ချေးငွေပေးသူမှ ရယူထားပါသည်။ ထိုသို့သော လိုအပ်ချက်များကို ခွင့်ပြုမိန့်စာတွင် ရှင်းလင်းစွာ ဖော်ပြထားမည်ဖြစ်ပါသည်။ စာရွက်စာတမ်းများ သို့မဟုတ် ဥပဒေရေးရာ လုပ်ထုံးလုပ်နည်းများနှင့် သက်ဆိုင်သော ကုန်ကျစရိတ်များအားလုံးကို သက်ဆိုင်ပါက ငွေချေးသူမှ ကျခံရမည်ဖြစ်သည်။ + ကိုယ်ပိုင်ချေးငွေများအတွက် ပိုင်ဆိုင်မှုအာမခံ လိုအပ်လေ့မရှိသော်လည်း၊ ငွေချေးသူ သေဆုံးသည်ရှိသော် လက်ကျန်ချေးငွေပမာဏကို ကာကွယ်ရန်အတွက် အသက်အာမခံ ပေါ်လစီတစ်ခု ရယူရန် ချေးငွေပေးသူမှ အကြံပြုနိုင်သည်။ ၎င်းသည် မဖြစ်မနေ လိုအပ်သည်မဟုတ်သော်လည်း ငွေချေးသူ၏ မိသားစု သို့မဟုတ် မှီခိုသူများအတွက် ငွေကြေးကာကွယ်မှု အစီအမံအဖြစ် အကြံပြုပါသည်။ + EMI များကို မပေးချေခြင်း သို့မဟုတ် နောက်ကျမှပေးချေခြင်းသည် ပျက်ကွက်ခြင်း ဖြစ်ပါသည်။ ထိုသို့သော ကိစ္စရပ်များတွင်၊ ချေးငွေပေးသူသည် ဥပဒေအရ အရေးယူခြင်း အပါအဝင် လက်ကျန်ငွေများကို ကောက်ခံရန် ပြန်လည်ရယူခြင်း လုပ်ငန်းစဉ်ကို စတင်နိုင်သည်။ ရက်လွန်ငွေပမာဏများအပေါ် ဒဏ်ကြေးအတိုးနှင့် အခြားအခကြေးငွေများ ကောက်ခံနိုင်သည်။ ဆက်တိုက် သို့မဟုတ် ထပ်ခါတလဲလဲ ပျက်ကွက်ခြင်းသည် ငွေချေးသူ၏ အကြွေးအမှတ်နှင့် အနာဂတ်တွင် ချေးငွေရယူနိုင်စွမ်းကို ဆိုးရွားစွာ ထိခိုက်စေနိုင်သည်။ + ငွေချေးသူသည် မှတ်ပုံတင်၊ နေရပ်လိပ်စာ၊ ဝင်ငွေအထောက်အထား၊ ဘဏ်စာရင်းရှင်းတမ်းများနှင့် ချေးငွေပေးသူမှ တောင်းဆိုသော အခြားစာရွက်စာတမ်းများ အပါအဝင် တိကျပြီး ပြည့်စုံသော စာရွက်စာတမ်းများကို တင်သွင်းရန် လိုအပ်ပါသည်။ တင်သွင်းထားသော စာရွက်စာတမ်းများတွင် ကွဲလွဲမှု၊ လွဲမှားစွာ ဖော်ပြမှု သို့မဟုတ် အတုအပ ပြုလုပ်မှုများသည် ချေးငွေလျှောက်လွှာကို ပယ်ချခြင်း သို့မဟုတ် ချေးငွေသဘောတူညီချက်ကို ရပ်စဲခြင်းနှင့်အတူ ဖြစ်နိုင်ဖွယ်ရာ ဥပဒေရေးရာ အကျိုးဆက်များကို ဖြစ်ပေါ်စေနိုင်သည်။ + ငွေချေးသူသည် ၎င်းတို့၏ ငွေကြေးဆိုင်ရာ တာဝန်ဝတ္တရားများနှင့် စပ်လျဉ်း၍ သက်ဆိုင်ရာ ဒေသဆိုင်ရာ၊ ပြည်နယ်နှင့် နိုင်ငံတော် ဥပဒေများအားလုံးကို အပြည့်အဝ လိုက်နာရန် သေချာစေရမည်။ ယင်းတွင် အခွန်များကို အချိန်မီ ပေးဆောင်ခြင်း၊ စည်းမျဉ်းစည်းကမ်း လိုအပ်ချက်များကို လိုက်နာခြင်းနှင့် တရားဝင် စာရွက်စာတမ်းများ တင်သွင်းခြင်းတို့ ပါဝင်သည်။ ငွေချေးသူ ကျူးလွန်သော မည်သည့် ဥပဒေချိုးဖောက်မှုများအတွက်မဆို ချေးငွေပေးသူတွင် တာဝန်မရှိပါ။ + ချေးငွေပေးသူသည် ငွေချေးသူအား ကြိုတင်အကြောင်းကြားစာ ပေးပို့ပြီး ဤစည်းကမ်းနှင့် သတ်မှတ်ချက်များကို အခါအားလျော်စွာ ပြန်လည်ပြင်ဆင်ခြင်း သို့မဟုတ် မွမ်းမံခြင်း ပြုလုပ်နိုင်သည်။ ချေးငွေပမာဏ အပြည့်အဝ ပြန်လည်ပေးဆပ်ပြီးချိန် သို့မဟုတ် ပျက်ကွက်မှုကြောင့် သိမ်းဆည်းခြင်း လုပ်ငန်းစဉ်များ စတင်ချိန်တွင် ချေးငွေသဘောတူညီချက်ကို ရပ်စဲသည်ဟု မှတ်ယူရမည်။ + ကိုယ်ပိုင်ချေးငွေ သဘောတူညီချက်မှ ပေါ်ပေါက်လာသော သို့မဟုတ် ဆက်စပ်နေသော အငြင်းပွားမှုများအားလုံးသည် သဘောတူညီချက်တွင် အခြားနည်းဖြင့် သတ်မှတ်ထားခြင်း မရှိပါက ချေးငွေပေးသူ၏ မှတ်ပုံတင်ရုံး တည်ရှိရာ တရားရုံးများ၏ သီးသန့်တရားစီရင်ပိုင်ခွင့်အောက်တွင် ရှိရမည်။ + + + + + ဘဏ်အကောင့်ရှင်းတမ်းများကို တင်ရန် + အာမခံစာရွက်စာတမ်းများကို တင်ရန် + လက်မှတ်တင်ရန် + + တစ်ခုခုမှားယွင်းနေပါသည်! ဖိုင်တင်၍မရပါ + ကျေးဇူးပြု၍ ဘဏ်ရှင်းတမ်းကို တင်ပါ + ကျေးဇူးပြု၍ ပိုင်ဆိုင်မှုစာရွက်စာတမ်းကို တင်ပါ + ကျေးဇူးပြု၍ လက်မှတ်ကို တင်ပါ + စာရွက်စာတမ်းများ အောင်မြင်စွာ တင်ပြီးပါပြီ! + + ဤနေရာတွင် လက်မှတ်ထိုးပါ + ပြန်လည်သတ်မှတ်ရန် + လက်မှတ် + ဖမ်းယူရန် + ပူးတွဲရန် + လက်မှတ်တင်ရန် + သိမ်းဆည်းပြီး တင်သွင်းရန် + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-pl/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-pl/strings.xml new file mode 100644 index 000000000..d956f7d32 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-pl/strings.xml @@ -0,0 +1,157 @@ + + + + + + Złóż wniosek o nową pożyczkę + + + Wniosek o pożyczkę + + + Imię i nazwisko wnioskodawcy + Produkt pożyczkowy + Cel pożyczki + Data wypłaty + Kwota główna + Waluta + + + Kontynuuj + OK + Anuluj + Złóż wniosek + Dalej + + + Imię i nazwisko wnioskodawcy jest wymagane + Imię i nazwisko wnioskodawcy musi mieć co najmniej 4 znaki + Imię i nazwisko wnioskodawcy nie może przekraczać 100 znaków + Imię i nazwisko wnioskodawcy zawiera nieprawidłowe znaki + + + Produkt pożyczkowy jest wymagany + + + Kwota główna jest wymagana + Wprowadź prawidłowy format kwoty + Kwota główna jest zbyt mała + Kwota główna przekracza maksymalny limit + Kwota ma zbyt wiele miejsc po przecinku dla wybranej waluty + Kwota nie jest w prawidłowych wielokrotnościach + + + Data wypłaty jest wymagana + Wprowadź prawidłowy format daty (DD/MM/RRRR) + Data wypłaty musi być w przyszłości + Data wypłaty jest zbyt odległa w przyszłości (maks. 10 lat) + + + Problem z serwerem po naszej stronie, nie można kontynuować, spróbuj ponownie za chwilę + Zbyt wiele nieudanych prób. Spróbuj ponownie później. + Nie udało się złożyć wniosku o pożyczkę. Spróbuj ponownie. + + + Wniosek o pożyczkę został pomyślnie złożony! + Wersja robocza wniosku została pomyślnie zapisana + + + Niezapisane zmiany! + Masz niezapisane zmiany. Czy na pewno chcesz wrócić? + + + Odrzuć + Zapisz wersję roboczą + Spróbuj ponownie + Anuluj + Kontynuuj + + + Wpisz swoje pełne imię i nazwisko + Wybierz produkt pożyczkowy + Wpisz kwotę pożyczki + Wybierz datę wypłaty + Opisz krótko cel + + + Waluta uległa zmianie. Kwota zostanie ponownie zweryfikowana. + Minimalna kwota pożyczki: %1$s + Maksymalna kwota pożyczki: %1$s + Potwierdź szczegóły + + Żądanie zostało pomyślnie złożone + Twój wniosek o pożyczkę został złożony. Powiadomimy Cię po jego rozpatrzeniu. + Wróć do strony głównej + + Nie udało się złożyć żądania + Coś poszło nie tak podczas składania wniosku o pożyczkę. Spróbuj ponownie. + Spróbuj ponownie + + + + Złóż wniosek o nową pożyczkę + Wybierz typ pożyczki + Produkty pożyczkowe są obecnie niedostępne + Upewnij się, że masz połączenie z Internetem + Zaakceptuj Regulamin + + + Złóż wniosek o pożyczkę %1$s + Uzyskaj pożyczkę %1$s + Do %1$s + Odsetki %1$s do %2$s + Regulamin + + Zatwierdzenie i wypłata pożyczki + Stopa procentowa + Kontynuuj + Spłata + Zabezpieczenie i hipoteka + Ubezpieczenie + Niewywiązanie się + Dokumentacja + Zgodność z prawem + Zmiany i rozwiązanie + Jurysdykcja + + + Pożyczka osobista zostanie zatwierdzona na podstawie kwalifikowalności Wnioskodawcy, stabilności dochodów, zdolności kredytowej i wewnętrznych polityk kredytowych Pożyczkodawcy. Pożyczkodawca zastrzega sobie prawo do zatwierdzenia lub odrzucenia każdego wniosku o pożyczkę osobistą według własnego uznania bez podawania przyczyny. Zatwierdzona kwota pożyczki może zostać wypłacona w całości lub w częściach, w zależności od Umowy pożyczki i wymogów dokumentacyjnych. Wypłata jest uzależniona od pomyślnego zakończenia procesów KYC, weryfikacji złożonych dokumentów i spełnienia wszystkich warunków określonych w Liście zatwierdzającym. + Pożyczka osobista może mieć stałą lub zmienną stopę procentową, zgodnie z wyborem Pożyczkobiorcy w momencie zawierania Umowy pożyczki. W przypadku pożyczek o zmiennej stopie procentowej, obowiązująca stopa podlega okresowym rewizjom w oparciu o zmiany stopy referencyjnej lub wskaźnika Pożyczkodawcy. Takie zmiany będą wiążące dla Pożyczkobiorcy i mogą skutkować zmianą kwoty raty EMI i/lub okresu kredytowania, jeśli Pożyczkodawca uzna to za konieczne. + Spłata pożyczki osobistej będzie dokonywana w równych miesięcznych ratach (EMI) obejmujących zarówno kapitał, jak i odsetki. Raty EMI rozpoczną się od daty określonej w Umowie pożyczki i będą kontynuowane co miesiąc aż do spłaty całej kwoty pożyczki wraz z należnymi odsetkami. Wcześniejsza spłata lub częściowa spłata może być dozwolona zgodnie z obowiązującymi zasadami Pożyczkodawcy i może wiązać się z pewnymi opłatami, zwłaszcza w przypadku pożyczek o stałej stopie. + Pożyczki osobiste są zazwyczaj niezabezpieczone i nie wymagają zabezpieczenia. Jednakże Pożyczkodawca zastrzega sobie prawo do żądania dodatkowej gwarancji lub zabezpieczenia w wyjątkowych przypadkach, w zależności od profilu kredytowego Pożyczkobiorcy i kwoty pożyczki. Wszelkie takie wymagania zostaną wyraźnie określone w Liście zatwierdzającym. Pożyczkobiorca poniesie wszelkie koszty związane z dokumentacją lub formalnościami prawnymi, jeśli dotyczy. + Chociaż ubezpieczenie mienia nie jest zazwyczaj wymagane w przypadku pożyczek osobistych, Pożyczkodawca może zalecić Pożyczkobiorcy wykupienie polisy ubezpieczenia na życie w celu zabezpieczenia niespłaconej kwoty pożyczki w przypadku śmierci Pożyczkobiorcy. Nie jest to obowiązkowe, ale zalecane jako środek ochrony finansowej dla rodziny lub osób na utrzymaniu Pożyczkobiorcy. + Brak płatności lub opóźniona płatność rat EMI będzie stanowić niewywiązanie się z umowy. W takich przypadkach Pożyczkodawca może wszcząć postępowanie windykacyjne w celu odzyskania należności, co może obejmować kroki prawne. Odsetki karne i inne opłaty mogą zostać naliczone od przeterminowanych kwot. Uporczywe lub powtarzające się niewywiązanie się z płatności może negatywnie wpłynąć na ocenę kredytową Pożyczkobiorcy i przyszłą zdolność kredytową. + Pożyczkobiorca jest zobowiązany do złożenia dokładnej i kompletnej dokumentacji, w tym między innymi dowodu tożsamości, dowodu adresu, szczegółów dochodów, wyciągów bankowych i wszelkich innych dokumentów wymaganych przez Pożyczkodawcę. Wszelkie rozbieżności, fałszywe oświadczenia lub fałszerstwa w złożonych dokumentach mogą skutkować odrzuceniem wniosku o pożyczkę lub rozwiązaniem Umowy pożyczki, wraz z możliwymi konsekwencjami prawnymi. + Pożyczkobiorca musi zapewnić pełną zgodność ze wszystkimi obowiązującymi przepisami lokalnymi, stanowymi i krajowymi w odniesieniu do swoich zobowiązań finansowych. Obejmuje to terminową zapłatę podatków, przestrzeganie wymogów regulacyjnych i składanie ważnych dokumentów. Pożyczkodawca nie ponosi odpowiedzialności za jakiekolwiek naruszenia prawa popełnione przez Pożyczkobiorcę. + Pożyczkodawca może od czasu do czasu zmieniać lub aktualizować niniejszy Regulamin, z zastrzeżeniem odpowiedniego uprzedniego powiadomienia Pożyczkobiorcy. Umowę pożyczki uznaje się za rozwiązaną po całkowitej spłacie kwoty pożyczki lub w drodze postępowania egzekucyjnego wszczętego z powodu niewywiązania się z umowy. + Wszelkie spory wynikające z Umowy pożyczki osobistej lub z nią związane podlegają wyłącznej jurysdykcji sądów właściwych dla siedziby Pożyczkodawcy, chyba że Umowa stanowi inaczej. + + + + + Prześlij wyciągi bankowe + Prześlij dokumenty zabezpieczenia + Prześlij podpis + + Coś poszło nie tak! Nie można przesłać pliku + Prześlij wyciąg bankowy + Prześlij dokument dotyczący nieruchomości + Prześlij podpis + Dokumenty przesłane pomyślnie! + + Podpisz tutaj + Zresetuj + Podpis + Przechwyć + Załącz + Prześlij podpis + Zapisz i prześlij + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-pt/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-pt/strings.xml new file mode 100644 index 000000000..fd21ddb30 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-pt/strings.xml @@ -0,0 +1,157 @@ + + + + + + Solicitar Novo Empréstimo + + + Pedido de Empréstimo + + + Nome do Requerente + Produto de Empréstimo + Objetivo do Empréstimo + Data de Desembolso + Montante Principal + Moeda + + + Continuar + OK + Cancelar + Aplicar Empréstimo + Seguinte + + + Nome do requerente é obrigatório + O nome do requerente deve ter pelo menos 4 caracteres + O nome do requerente não pode exceder 100 caracteres + O nome do requerente contém caracteres inválidos + + + Produto de empréstimo é obrigatório + + + Montante principal é obrigatório + Introduza um formato de montante válido + O montante principal é demasiado pequeno + O montante principal excede o limite máximo + O montante tem demasiadas casas decimais para a moeda selecionada + O montante não está em múltiplos válidos + + + Data de desembolso é obrigatória + Introduza um formato de data válido (DD/MM/AAAA) + A data de desembolso deve ser no futuro + A data de desembolso é demasiado distante no futuro (máximo 10 anos) + + + Problema no servidor do nosso lado, não é possível prosseguir, tente novamente mais tarde + Demasiadas tentativas falhadas. Por favor, tente novamente mais tarde. + Falha ao submeter o pedido de empréstimo. Por favor, tente novamente. + + + Pedido de empréstimo submetido com sucesso! + Rascunho do pedido guardado com sucesso + + + Alterações Não Guardadas! + Tem alterações não guardadas. Tem a certeza de que pretende voltar? + + + Descartar + Guardar Rascunho + Tentar Novamente + Cancelar + Continuar + + + Introduza o seu nome completo + Selecione o produto de empréstimo + Introduza o montante do empréstimo + Selecione a data de desembolso + Descreva brevemente o objetivo + + + A moeda mudou. O montante será revalidado. + Montante mínimo de empréstimo: %1$s + Montante máximo de empréstimo: %1$s + Confirmar Detalhes + + Pedido submetido com sucesso + O seu pedido de empréstimo foi submetido. Será notificado assim que for analisado. + Voltar ao Início + + Falha ao submeter o pedido + Algo correu mal ao submeter o seu pedido de empréstimo. Por favor, tente novamente. + Tentar novamente + + + + Solicitar Novo Empréstimo + Escolha o Tipo de Empréstimo + Produtos de empréstimo indisponíveis no momento + Certifique-se de que está ligado à internet + Aceitar Termos e Condições + + + Aplicar Empréstimo %1$s + Obter Empréstimo %1$s + Até %1$s + %1$s a %2$s Juros + Termos e Condições + + Sanção e Desembolso de Empréstimo + Taxa de Juro + Continuar + Reembolso + Segurança e Garantia + Seguro + Incumprimento + Documentação + Conformidade Legal + Alterações e Rescisão + Jurisdição + + + O Empréstimo Pessoal será aprovado com base na elegibilidade do requerente, estabilidade de rendimento, capacidade de crédito e políticas de crédito internas do Mutuante. O Mutuante reserva-se o direito de aprovar ou rejeitar qualquer pedido de Empréstimo Pessoal a seu exclusivo critério, sem apresentar qualquer motivo. O montante do empréstimo aprovado pode ser desembolsado na totalidade ou em partes, dependendo do Contrato de Empréstimo e dos requisitos de documentação. O desembolso está sujeito à conclusão bem-sucedida dos processos KYC, verificação dos documentos submetidos e cumprimento de todas as condições especificadas na Carta de Sanção. + O Empréstimo Pessoal pode ter uma taxa de juro fixa ou flutuante, conforme escolhido pelo Mutuário no momento do Contrato de Empréstimo. No caso de empréstimos com taxa de juro flutuante, a taxa aplicável está sujeita a revisões periódicas com base em alterações na taxa de referência ou de referência do Mutuante. Tais alterações serão vinculativas para o Mutuário e podem resultar na alteração do montante do EMI e/ou do prazo do empréstimo, conforme considerado necessário pelo Mutuante. + O reembolso do Empréstimo Pessoal será feito através de Prestações Mensais Equacionadas (EMIs) compreendendo componentes de capital e juros. Os EMIs começarão a partir da data especificada no Contrato de Empréstimo e continuarão mensalmente até que a totalidade do montante do empréstimo, juntamente com os juros aplicáveis, seja reembolsada. O pré-pagamento ou pagamento parcial pode ser permitido de acordo com as políticas vigentes do Mutuante e pode atrair certos encargos, especialmente para empréstimos de taxa fixa. + Os Empréstimos Pessoais são normalmente de natureza não garantida e não requerem garantia. No entanto, o Mutuante reserva-se o direito de solicitar garantias ou segurança adicionais em casos excecionais, dependendo do perfil de crédito do Mutuário e do montante do empréstimo. Qualquer requisito desse tipo será claramente indicado na Carta de Sanção. O Mutuário suportará todas as despesas relacionadas com a documentação ou formalidades legais, se aplicável. + Embora o seguro de ativos geralmente não seja necessário para Empréstimos Pessoais, o Mutuante pode recomendar que o Mutuário opte por uma apólice de seguro de vida para proteger o montante do empréstimo pendente em caso de falecimento do Mutuário. Isto não é obrigatório, mas é aconselhado como medida de proteção financeira para a família ou dependentes do Mutuário. + O não pagamento ou atraso no pagamento de EMIs constituirá incumprimento. Nesses casos, o Mutuante poderá iniciar processos de recuperação para cobrar as dívidas pendentes, o que pode incluir ações legais. Juros de mora e outros encargos podem ser aplicados a montantes em atraso. O incumprimento persistente ou repetido pode afetar negativamente a pontuação de crédito do Mutuário e a capacidade de empréstimo futura. + O Mutuário é obrigado a submeter documentação precisa e completa, incluindo, entre outros, prova de identidade, prova de morada, detalhes de rendimento, extratos bancários e quaisquer outros documentos solicitados pelo Mutuante. Qualquer discrepância, deturpação ou falsificação nos documentos submetidos pode resultar na rejeição do pedido de empréstimo ou na rescisão do Contrato de Empréstimo, juntamente com possíveis consequências legais. + O Mutuário deve garantir o cumprimento integral de todas as leis locais, estaduais e nacionais aplicáveis no que diz respeito às suas obrigações financeiras. Isto inclui o pagamento atempado de impostos, a adesão a requisitos regulamentares e a submissão de documentos válidos. O Mutuante não será responsável por quaisquer violações legais cometidas pelo Mutuário. + O Mutuante pode rever ou atualizar estes Termos e Condições de tempos a tempos, sujeito a um aviso prévio adequado ao Mutuário. O Contrato de Empréstimo será considerado rescindido após o reembolso total do montante do empréstimo ou através de processos de execução hipotecária iniciados devido a incumprimento. + Todos os litígios decorrentes ou relacionados com o Contrato de Empréstimo Pessoal estarão sujeitos à jurisdição exclusiva dos tribunais no local da sede social do Mutuante, salvo indicação em contrário no Contrato. + + + + + Carregar Extratos Bancários + Carregar Documentos de garantia + Carregar Assinatura + + Algo correu mal! Não foi possível carregar o ficheiro + Por favor, carregue o extrato bancário + Por favor, carregue o documento de propriedade + Por favor, carregue a assinatura + Documentos carregados com sucesso! + + Assine Aqui + Redefinir + Assinar + Capturar + Anexar + Carregar Assinatura + Guardar e Submeter + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-ru/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-ru/strings.xml new file mode 100644 index 000000000..046b70668 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-ru/strings.xml @@ -0,0 +1,157 @@ + + + + + + Подать заявку на новый кредит + + + Заявка на кредит + + + Имя заявителя + Кредитный продукт + Цель кредита + Дата выдачи + Основная сумма + Валюта + + + Продолжить + ОК + Отмена + Подать заявку + Далее + + + Требуется имя заявителя + Имя заявителя должно содержать не менее 4 символов + Имя заявителя не должно превышать 100 символов + Имя заявителя содержит недопустимые символы + + + Требуется кредитный продукт + + + Требуется основная сумма + Пожалуйста, введите правильный формат суммы + Основная сумма слишком мала + Основная сумма превышает максимальный лимит + Сумма имеет слишком много знаков после запятой для выбранной валюты + Сумма не кратна допустимым значениям + + + Требуется дата выдачи + Пожалуйста, введите правильный формат даты (ДД/ММ/ГГГГ) + Дата выдачи должна быть в будущем + Дата выдачи слишком далека в будущем (макс. 10 лет) + + + Проблема с сервером на нашей стороне, невозможно продолжить, попробуйте позже + Слишком много неудачных попыток. Пожалуйста, попробуйте позже. + Не удалось отправить заявку на кредит. Пожалуйста, попробуйте снова. + + + Заявка на кредит успешно отправлена! + Черновик заявки успешно сохранен + + + Несохраненные изменения! + У вас есть несохраненные изменения. Вы уверены, что хотите вернуться? + + + Сбросить + Сохранить черновик + Повторить + Отмена + Продолжить + + + Введите ваше полное имя + Выберите кредитный продукт + Введите сумму кредита + Выберите дату выдачи + Кратко опишите цель + + + Валюта изменилась. Сумма будет перепроверена. + Минимальная сумма кредита: %1$s + Максимальная сумма кредита: %1$s + Подтвердить данные + + Запрос успешно отправлен + Ваша заявка на кредит отправлена. Вы будете уведомлены после ее рассмотрения. + Вернуться на главную + + Не удалось отправить запрос + Что-то пошло не так при отправке вашей заявки на кредит. Пожалуйста, попробуйте снова. + Повторить + + + + Подать заявку на новый кредит + Выберите тип кредита + Кредитные продукты в данный момент недоступны + Убедитесь, что вы подключены к интернету + Принять Условия и Положения + + + Подать заявку на кредит %1$s + Получить кредит %1$s + До %1$s + %1$s до %2$s Процентов + Условия и Положения + + Одобрение и выдача кредита + Процентная ставка + Продолжить + Погашение + Безопасность и залог + Страхование + Дефолт + Документация + Правовое соответствие + Поправки и прекращение + Юрисдикция + + + Персональный кредит будет одобрен на основании соответствия требованиям Заявителя, стабильности дохода, кредитоспособности и внутренней кредитной политики Кредитора. Кредитор оставляет за собой право одобрить или отклонить любую заявку на Персональный кредит по своему усмотрению без объяснения причин. Одобренная сумма кредита может быть выдана полностью или частями в зависимости от Кредитного договора и требований к документации. Выдача зависит от успешного завершения процессов KYC, проверки представленных документов и соблюдения всех условий, указанных в Письме об одобрении. + Персональный кредит может иметь фиксированную или плавающую процентную ставку, по выбору Заемщика на момент заключения Кредитного договора. В случае кредитов с плавающей процентной ставкой, применимая ставка подлежит периодическому пересмотру в зависимости от изменений Бенчмарка или Справочной ставки Кредитора. Такие изменения будут обязательными для Заемщика и могут привести к изменению суммы EMI и/или срока кредита, если Кредитор сочтет это необходимым. + Погашение Персонального кредита будет осуществляться равными ежемесячными платежами (EMI), включающими как основной долг, так и проценты. EMI начнутся с даты, указанной в Кредитном договоре, и будут продолжаться ежемесячно до полного погашения всей суммы кредита вместе с применимыми процентами. Досрочное погашение или частичная оплата могут быть разрешены в соответствии с действующей политикой Кредитора и могут повлечь за собой определенные сборы, особенно для кредитов с фиксированной ставкой. + Персональные кредиты, как правило, являются необеспеченными и не требуют залога. Однако Кредитор оставляет за собой право запросить дополнительную гарантию или обеспечение в исключительных случаях в зависимости от кредитного профиля Заемщика и суммы кредита. Любое такое требование будет четко указано в Письме об одобрении. Заемщик несет все расходы, связанные с документацией или юридическими формальностями, если таковые имеются. + Хотя страхование активов обычно не требуется для Персональных кредитов, Кредитор может рекомендовать Заемщику выбрать полис страхования жизни для защиты непогашенной суммы кредита в случае смерти Заемщика. Это не является обязательным, но рекомендуется в качестве меры финансовой защиты для семьи или иждивенцев Заемщика. + Неуплата или задержка оплаты EMI будет считаться дефолтом. В таких случаях Кредитор может инициировать процедуры взыскания для сбора непогашенной задолженности, что может включать юридические действия. На просроченные суммы могут начисляться штрафные проценты и другие сборы. Постоянный или повторный дефолт может негативно повлиять на кредитный рейтинг Заемщика и будущую возможность получения кредитов. + Заемщик обязан предоставить точную и полную документацию, включая, помимо прочего, подтверждение личности, подтверждение адреса, сведения о доходах, банковские выписки и любые другие документы, запрошенные Кредитором. Любое несоответствие, искажение фактов или подделка в представленных документах может привести к отклонению заявки на кредит или расторжению Кредитного договора, а также к возможным юридическим последствиям. + Заемщик должен обеспечить полное соблюдение всех применимых местных, государственных и национальных законов в отношении своих финансовых обязательств. Это включает своевременную уплату налогов, соблюдение нормативных требований и подачу действительных документов. Кредитор не несет ответственности за любые правонарушения, совершенные Заемщиком. + Кредитор может время от времени пересматривать или обновлять настоящие Условия и Положения при условии надлежащего предварительного уведомления Заемщика. Кредитный договор считается расторгнутым после полного погашения суммы кредита или посредством процедур взыскания, инициированных в связи с дефолтом. + Все споры, возникающие из или в связи с Договором персонального кредита, подлежат исключительной юрисдикции судов по месту нахождения зарегистрированного офиса Кредитора, если иное не указано в Договоре. + + + + + Загрузить банковские выписки + Загрузить документы по залогу + Загрузить подпись + + Что-то пошло не так! Не удалось загрузить файл + Пожалуйста, загрузите банковскую выписку + Пожалуйста, загрузите документ на собственность + Пожалуйста, загрузите подпись + Документы успешно загружены! + + Подпишите здесь + Сбросить + Подпись + Снять + Прикрепить + Загрузить подпись + Сохранить и отправить + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-sw/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-sw/strings.xml new file mode 100644 index 000000000..d6b897b42 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-sw/strings.xml @@ -0,0 +1,157 @@ + + + + + + Omba Mkopo Mpya + + + Maombi ya Mkopo + + + Jina la Mwombaji + Bidhaa ya Mkopo + Kusudi la Mkopo + Tarehe ya Utoaji + Kiasi Kikuu + Sarafu + + + Endelea + Sawa + Ghairi + Omba Mkopo + Ijayo + + + Jina la mwombaji linahitajika + Jina la mwombaji lazima liwe na angalau herufi 4 + Jina la mwombaji lisizidi herufi 100 + Jina la mwombaji lina herufi batili + + + Bidhaa ya mkopo inahitajika + + + Kiasi kikuu kinahitajika + Tafadhali weka muundo sahihi wa kiasi + Kiasi kikuu ni kidogo sana + Kiasi kikuu kinazidi kikomo cha juu + Kiasi kina nafasi nyingi za desimali kwa sarafu iliyochaguliwa + Kiasi hakiko katika vizidisho sahihi + + + Tarehe ya utoaji inahitajika + Tafadhali weka muundo sahihi wa tarehe (SIKU/MWEZI/MWAKA) + Tarehe ya utoaji lazima iwe ya baadaye + Tarehe ya utoaji iko mbali sana baadaye (kiwango cha juu miaka 10) + + + Tatizo la seva upande wetu, haiwezekani kuendelea, jaribu tena baada ya muda mfupi + Majaribio mengi yaliyoshindwa. Tafadhali jaribu tena baadaye. + Imeshindwa kuwasilisha maombi ya mkopo. Tafadhali jaribu tena. + + + Maombi ya mkopo yamewasilishwa kikamilifu! + Rasimu ya maombi imehifadhiwa kikamilifu + + + Mabadiliko Hayajahifadhiwa! + Una mabadiliko ambayo hayajahifadhiwa. Je, una uhakika unataka kurudi? + + + Tupa + Hifadhi Rasimu + Jaribu Tena + Ghairi + Endelea + + + Weka jina lako kamili + Chagua bidhaa ya mkopo + Weka kiasi cha mkopo + Chagua tarehe ya utoaji + Eleza kusudi kwa ufupi + + + Sarafu imebadilika. Kiasi kitathibitishwa tena. + Kiasi cha chini cha mkopo: %1$s + Kiasi cha juu cha mkopo: %1$s + Thibitisha Maelezo + + Ombi limewasilishwa kikamilifu + Maombi yako ya mkopo yamewasilishwa. Utaarifiwa mara tu yatakapopitiwa. + Rudi Nyumbani + + Imeshindwa kuwasilisha ombi + Hitilafu imetokea wakati wa kuwasilisha maombi yako ya mkopo. Tafadhali jaribu tena. + Jaribu tena + + + + Omba Mkopo Mpya + Chagua Aina ya Mkopo + Bidhaa za mkopo hazipatikani kwa sasa + Hakikisha umeunganishwa kwenye mtandao + Kubali Sheria na Masharti + + + Omba Mkopo wa %1$s + Pata Mkopo wa %1$s + Hadi %1$s + %1$s hadi %2$s Riba + Sheria na Masharti + + Uidhinishaji na Utoaji wa Mkopo + Kiwango cha Riba + Endelea + Ulipaji + Usalama na Dhamana + Bima + Kutolipa + Nyaraka + Uzingatiaji wa Kisheria + Marekebisho na Ukomeshaji + Mamlaka + + + Mkopo wa Binafsi utaidhinishwa kulingana na ustahiki wa mwombaji, uthabiti wa mapato, uwezo wa kukopesheka, na sera za ndani za mkopo za Mkopesha. Mkopesha anahifadhi haki ya kuidhinisha au kukataa maombi yoyote ya Mkopo wa Binafsi kwa hiari yake pekee bila kutoa sababu yoyote. Kiasi cha mkopo kilichoidhinishwa kinaweza kutolewa kikamilifu au kwa sehemu kulingana na Mkataba wa Mkopo na mahitaji ya nyaraka. Utoaji unategemea kukamilika kwa mafanikio kwa michakato ya KYC, uthibitishaji wa nyaraka zilizowasilishwa, na kufuata masharti yote yaliyotajwa katika Barua ya Uidhinishaji. + Mkopo wa Binafsi unaweza kuwa na kiwango cha riba kisichobadilika au kinachobadilika, kama ilivyochaguliwa na Mkopaji wakati wa Mkataba wa Mkopo. Kwa upande wa mikopo ya kiwango cha riba kinachobadilika, kiwango kinachotumika kinategemea marekebisho ya mara kwa mara kulingana na mabadiliko katika Kigezo au Kiwango cha Rejeleo cha Mkopesha. Mabadiliko kama hayo yatakuwa ya lazima kwa Mkopaji na yanaweza kusababisha mabadiliko katika kiasi cha EMI na/au muda wa mkopo kama itakavyoonekana inafaa na Mkopesha. + Ulipaji wa Mkopo wa Binafsi utafanywa kupitia Malipo ya Kila Mwezi Yaliyosawazishwa (EMIs) yanayojumuisha vipengele vya mkuu na riba. EMI zitaanza kutoka tarehe iliyotajwa katika Mkataba wa Mkopo na zitaendelea kila mwezi hadi kiasi chote cha mkopo pamoja na riba inayotumika kilipwe kikamilifu. Malipo ya mapema au malipo ya sehemu yanaweza kuruhusiwa kulingana na sera zilizopo za Mkopesha na yanaweza kuvutia tozo fulani, haswa kwa mikopo ya kiwango kisichobadilika. + Mikopo ya Binafsi kwa kawaida haina dhamana na haihitaji dhamana ya mali. Hata hivyo, Mkopesha anahifadhi haki ya kuomba dhamana au usalama wa ziada katika kesi za kipekee kulingana na wasifu wa mkopo wa Mkopaji na kiasi cha mkopo. Mahitaji yoyote kama hayo yataelezwa wazi katika Barua ya Uidhinishaji. Mkopaji atagharamia gharama zote zinazohusiana na nyaraka au taratibu za kisheria, ikiwa inafaa. + Ingawa bima ya mali haihitajiki kwa kawaida kwa Mikopo ya Binafsi, Mkopesha anaweza kupendekeza kwamba Mkopaji achague sera ya bima ya maisha ili kulinda kiasi cha mkopo kinachodaiwa ikitokea kifo cha Mkopaji. Hii sio lazima lakini inashauriwa kama hatua ya ulinzi wa kifedha kwa familia au wategemezi wa Mkopaji. + Kutolipa au kuchelewa kulipa EMI kutajumuisha kutolipa. Katika kesi kama hizo, Mkopesha anaweza kuanzisha kesi za kurejesha ili kukusanya madeni yaliyosalia, ambayo yanaweza kujumuisha hatua za kisheria. Riba ya adhabu na tozo zingine zinaweza kutumika kwa kiasi kilichopitwa na wakati. Kutolipa mara kwa mara au kwa kurudia kunaweza kuathiri vibaya alama ya mkopo ya Mkopaji na uwezo wa kukopa wa baadaye. + Mkopaji anatakiwa kuwasilisha nyaraka sahihi na kamili, ikiwa ni pamoja na lakini sio tu uthibitisho wa utambulisho, uthibitisho wa anwani, maelezo ya mapato, taarifa za benki, na nyaraka zingine zozote zinazoombwa na Mkopesha. Tofauti yoyote, kutoa taarifa za uongo, au kughushi katika nyaraka zilizowasilishwa kunaweza kusababisha kukataliwa kwa maombi ya mkopo au kusitishwa kwa Mkataba wa Mkopo, pamoja na uwezekano wa matokeo ya kisheria. + Mkopaji lazima ahakikishe anafuata kikamilifu sheria zote za mitaa, jimbo, na kitaifa zinazotumika kuhusiana na majukumu yao ya kifedha. Hii inajumuisha malipo ya kodi kwa wakati, kufuata mahitaji ya udhibiti, na uwasilishaji wa nyaraka halali. Mkopesha hatawajibika kwa ukiukaji wowote wa shisheria unaofanywa na Mkopaji. + Mkopesha anaweza kurekebisha au kusasisha Sheria na Masharti haya mara kwa mara, kulingana na taarifa ya mapema inayofaa kwa Mkopaji. Mkataba wa Mkopo utachukuliwa kuwa umekoma baada ya kulipa kikamilifu kiasi cha mkopo au kupitia kesi za utaifishaji zilizoanzishwa kutokana na kutolipa. + Migogoro yote inayotokana na au inayohusiana na Mkataba wa Mkopo wa Binafsi itakuwa chini ya mamlaka ya kipekee ya mahakama katika eneo la ofisi iliyosajiliwa ya Mkopesha, isipokuwa imeelezwa vinginevyo katika Mkataba. + + + + + Pakia Taarifa za Akaunti ya Benki + Pakia Nyaraka za Dhamana + Pakia Sahihi + + Kuna hitilafu! Imeshindwa kupakia faili + Tafadhali pakia taarifa ya benki + Tafadhali pakia hati ya mali + Tafadhali pakia sahihi + Nyaraka zimepakiwa kikamilifu! + + Saini Hapa + Weka upya + Sahihi + Nasa + Ambatisha + Pakia Sahihi + Hifadhi na Wasilisha + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-te/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-te/strings.xml new file mode 100644 index 000000000..66f12b543 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-te/strings.xml @@ -0,0 +1,157 @@ + + + + + + కొత్త రుణానికి దరఖాస్తు చేయండి + + + రుణ దరఖాస్తు + + + దరఖాస్తుదారుని పేరు + రుణ ఉత్పత్తి + రుణ ఉద్దేశ్యం + పంపిణీ తేదీ + ప్రధాన మొత్తం + కరెన్సీ + + + కొనసాగించు + సరే + రద్దు చేయండి + రుణానికి దరఖాస్తు చేయండి + తరువాత + + + దరఖాస్తుదారుని పేరు అవసరం + దరఖాస్తుదారుని పేరు కనీసం 4 అక్షరాలు ఉండాలి + దరఖాస్తుదారుని పేరు 100 అక్షరాలకు మించకూడదు + దరఖాస్తుదారుని పేరు చెల్లని అక్షరాలను కలిగి ఉంది + + + రుణ ఉత్పత్తి అవసరం + + + ప్రధాన మొత్తం అవసరం + దయచేసి సరైన మొత్తం ఆకృతిని నమోదు చేయండి + ప్రధాన మొత్తం చాలా చిన్నగా ఉంది + ప్రధాన మొత్తం గరిష్ట పరిమితిని మించిపోయింది + ఎంచుకున్న కరెన్సీకి మొత్తంలో ఎక్కువ దశాంశ స్థానాలు ఉన్నాయి + మొత్తం సరైన గుణకాల్లో లేదు + + + పంపిణీ తేదీ అవసరం + దయచేసి చెల్లుబాటు అయ్యే తేదీ ఆకృతిని నమోదు చేయండి (DD/MM/YYYY) + పంపిణీ తేదీ భవిష్యత్తులో ఉండాలి + పంపిణీ తేదీ చాలా దూరంలో ఉంది (గరిష్టంగా 10 సంవత్సరాలు) + + + మా వైపు నుండి సర్వర్ సమస్య, ముందుకు సాగలేము, కాసేపటి తర్వాత మళ్లీ ప్రయత్నించండి + చాలా విఫల ప్రయత్నాలు. దయచేసి తర్వాత మళ్లీ ప్రయత్నించండి. + రుణ దరఖాస్తును సమర్పించడంలో విఫలమైంది. దయచేసి మళ్లీ ప్రయత్నించండి. + + + రుణ దరఖాస్తు విజయవంతంగా సమర్పించబడింది! + దరఖాస్తు డ్రాఫ్ట్ విజయవంతంగా సేవ్ చేయబడింది + + + సేవ్ చేయని మార్పులు! + మీకు సేవ్ చేయని మార్పులు ఉన్నాయి. మీరు ఖచ్చితంగా వెనుకకు వెళ్లాలనుకుంటున్నారా? + + + విస్మరించు + డ్రాఫ్ట్ సేవ్ చేయండి + మళ్లీ ప్రయత్నించండి + రద్దు చేయండి + కొనసాగించు + + + మీ పూర్తి పేరు నమోదు చేయండి + రుణ ఉత్పత్తిని ఎంచుకోండి + రుణ మొత్తాన్ని నమోదు చేయండి + పంపిణీ తేదీని ఎంచుకోండి + ఉద్దేశ్యాన్ని క్లుప్తంగా వివరించండి + + + కరెన్సీ మార్చబడింది. మొత్తం మళ్లీ ధృవీకరించబడుతుంది. + కనీస రుణ మొత్తం: %1$s + గరిష్ట రుణ మొత్తం: %1$s + వివరాలను నిర్ధారించండి + + అభ్యర్థన విజయవంతంగా సమర్పించబడింది + మీ రుణ దరఖాస్తు సమర్పించబడింది. అది సమీక్షించబడిన తర్వాత మీకు తెలియజేయబడుతుంది. + హోమ్‌కి తిరిగి వెళ్లు + + అభ్యర్థన సమర్పించడంలో విఫలమైంది + మీ రుణ దరఖాస్తును సమర్పించేటప్పుడు ఏదో తప్పు జరిగింది. దయచేసి మళ్లీ ప్రయత్నించండి. + మళ్లీ ప్రయత్నించండి + + + + కొత్త రుణానికి దరఖాస్తు చేయండి + రుణ రకాన్ని ఎంచుకోండి + ప్రస్తుతం రుణ ఉత్పత్తులు అందుబాటులో లేవు + మీరు ఇంటర్నెట్‌కు కనెక్ట్ అయ్యారని నిర్ధారించుకోండి + నిబంధనలు మరియు షరతులను అంగీకరించండి + + + %1$s రుణానికి దరఖాస్తు చేయండి + %1$s రుణం పొందండి + %1$s వరకు + %1$s నుండి %2$s వడ్డీ + నిబంధనలు మరియు షరతులు + + రుణ మంజూరు మరియు పంపిణీ + వడ్డీ రేటు + కొనసాగించు + తిరిగి చెల్లింపు + సెక్యూరిటీ మరియు కొలేటరల్ + భీమా + డిఫాల్ట్ + డాక్యుమెంటేషన్ + చట్టపరమైన సమ్మతి + సవరణలు మరియు రద్దు + అధికార పరిధి + + + దరఖాస్తుదారు అర్హత, ఆదాయ స్థిరత్వం, క్రెడిట్ అర్హత మరియు రుణదాత యొక్క అంతర్గత క్రెడిట్ విధానాల ఆధారంగా వ్యక్తిగత రుణం మంజూరు చేయబడుతుంది. ఎటువంటి కారణం చెప్పకుండా ఏ వ్యక్తిగత రుణ దరఖాస్తునైనా ఆమోదించడానికి లేదా తిరస్కరించడానికి రుణదాతకు పూర్తి అధికారం ఉంటుంది. మంజూరైన రుణ మొత్తం రుణ ఒప్పందం మరియు డాక్యుమెంటేషన్ అవసరాలను బట్టి పూర్తిగా లేదా పాక్షికంగా పంపిణీ చేయబడవచ్చు. పంపిణీ కేవైసీ విధానాల విజయవంతమైన పూర్తికి, సమర్పించిన పత్రాల ధృవీకరణకు మరియు మంజూరు లేఖలో పేర్కొన్న అన్ని షరతులకు కట్టుబడి ఉండటానికి లోబడి ఉంటుంది. + వ్యక్తిగత రుణం కోసం రుణ ఒప్పందం సమయంలో రుణగ్రహీత ఎంచుకున్నదాని ప్రకారం, స్థిరమైన లేదా ఫ్లోటింగ్ వడ్డీ రేటు ఉండవచ్చు. ఫ్లోటింగ్ వడ్డీ రేటు రుణాల విషయంలో, వర్తించే రేటు రుణదాత యొక్క బెంచ్‌మార్క్ లేదా రిఫరెన్స్ రేటులో మార్పుల ఆధారంగా ఎప్పటికప్పుడు సవరించబడుతుంది. అటువంటి మార్పులు రుణగ్రహీతకు కట్టుబడి ఉంటాయి మరియు రుణదాత అవసరమని భావించినట్లయితే ఈఎంఐ మొత్తం మరియు/లేదా రుణ కాలపరిమితిలో మార్పుకు దారితీయవచ్చు. + వ్యక్తిగత రుణాన్ని అసలు మరియు వడ్డీ రెండూ కలిపి సమానమైన నెలవారీ వాయిదాల (ఈఎంఐలు) ద్వారా తిరిగి చెల్లించాలి. ఈఎంఐలు రుణ ఒప్పందంలో పేర్కొన్న తేదీ నుండి ప్రారంభమవుతాయి మరియు వర్తించే వడ్డీతో పాటు పూర్తి రుణ మొత్తం తిరిగి చెల్లించే వరకు నెలవారీగా కొనసాగుతాయి. రుణదాత యొక్క ప్రస్తుత విధానాలకు అనుగుణంగా ముందస్తు చెల్లింపు లేదా పాక్షిక చెల్లింపు అనుమతించబడవచ్చు మరియు స్థిర వడ్డీ రేటు రుణాలకు కొన్ని ఛార్జీలు వర్తించవచ్చు. + వ్యక్తిగత రుణాలు సాధారణంగా అసురక్షితమైనవి మరియు వాటికి కొలేటరల్ అవసరం లేదు. అయితే, రుణగ్రహీత యొక్క క్రెడిట్ ప్రొఫైల్ మరియు రుణ మొత్తం ఆధారంగా అసాధారణ సందర్భాల్లో అదనపు గ్యారెంటీలు లేదా సెక్యూరిటీని కోరే హక్కు రుణదాతకు ఉంటుంది. అటువంటి అవసరాలు ఏవైనా ఉంటే మంజూరు లేఖలో స్పష్టంగా పేర్కొనబడతాయి. వర్తిస్తే డాక్యుమెంటేషన్ లేదా చట్టపరమైన ఫార్మాలిటీలకు సంబంధించిన అన్ని ఖర్చులను రుణగ్రహీత భరించాలి. + వ్యక్తిగత రుణాలకు సాధారణంగా ఆస్తి భీమా అవసరం లేనప్పటికీ, రుణగ్రహీత మరణం సంభవిస్తే బకాయి ఉన్న రుణ మొత్తాన్ని సురక్షితం చేయడానికి రుణగ్రహీత జీవిత బీమా పాలసీని ఎంచుకోవాలని రుణదాత సిఫార్సు చేయవచ్చు. ఇది తప్పనిసరి కాదు కానీ రుణగ్రహీత కుటుంబానికి లేదా ఆధారపడిన వారికి ఆర్థిక రక్షణ చర్యగా ఇది మంచిది. + ఈఎంఐలు చెల్లించకపోవడమో లేదా ఆలస్యంగా చెల్లించడమో డిఫాల్ట్‌గా పరిగణించబడుతుంది. అటువంటి సందర్భాల్లో, బకాయిలను వసూలు చేయడానికి రుణదాత రికవరీ చర్యలను ప్రారంభించవచ్చు, ఇందులో చట్టపరమైన చర్యలు కూడా ఉండవచ్చు. మీరిన మొత్తాలపై పెనల్ వడ్డీ మరియు ఇతర ఛార్జీలు వర్తించవచ్చు. నిరంతర లేదా పదేపదే డిఫాల్ట్ రుణగ్రహీత యొక్క క్రెడిట్ స్కోర్‌ను మరియు భవిష్యత్తులో రుణం తీసుకునే సామర్థ్యాన్ని ప్రతికూలంగా ప్రభావితం చేస్తుంది. + రుణగ్రహీత ఖచ్చితమైన మరియు పూర్తి పత్రాలను సమర్పించాల్సి ఉంటుంది, ఇందులో గుర్తింపు రుజువు, చిరునామా రుజువు, ఆదాయ వివరాలు, బ్యాంక్ స్టేట్‌మెంట్‌లు మరియు రుణదాత కోరిన ఇతర పత్రాలు ఉంటాయి. సమర్పించిన పత్రాలలో ఏవైనా వ్యత్యాసాలు, తప్పుడు సమాచారం లేదా తారుమారు ఉంటే రుణ దరఖాస్తు తిరస్కరణకు లేదా రుణ ఒప్పందం రద్దుకు దారితీయవచ్చు, అలాగే చట్టపరమైన చిక్కులు కూడా ఉండవచ్చు. + రుణగ్రహీత వారి ఆర్థిక బాధ్యతలకు సంబంధించిన అన్ని వర్తించే స్థానిక, రాష్ట్ర మరియు జాతీయ చట్టాలకు పూర్తిగా కట్టుబడి ఉన్నారని నిర్ధారించుకోవాలి. ఇందులో పన్నుల సకాలంలో చెల్లింపు, నిబంధనలకు కట్టుబడి ఉండటం మరియు చెల్లుబాటు అయ్యే పత్రాల సమర్పణ ఉంటాయి. రుణగ్రహీత చేసే చట్టపరమైన ఉల్లంఘనలకు రుణదాత బాధ్యత వహించరు. + రుణదాత ఎప్పటికప్పుడు ఈ నిబంధనలు మరియు షరతులను సవరించవచ్చు లేదా నవీకరించవచ్చు, దీనికి రుణగ్రహీతకు తగిన ముందస్తు నోటీసు ఇవ్వబడుతుంది. రుణ మొత్తం పూర్తిగా తిరిగి చెల్లించినప్పుడు లేదా డిఫాల్ట్ కారణంగా ప్రారంభించిన జప్తు చర్యల ద్వారా రుణ ఒప్పందం ముగిసినట్లు పరిగణించబడుతుంది. + వ్యక్తిగత రుణ ఒప్పందం నుండి లేదా దానికి సంబంధించి తలెత్తే అన్ని వివాదాలు ఒప్పందంలో వేరే విధంగా పేర్కొనకపోతే, రుణదాత యొక్క రిజిస్టర్డ్ ఆఫీస్ ఉన్న ప్రదేశంలోని న్యాయస్థానాల ప్రత్యేక అధికార పరిధిలోకి వస్తాయి. + + + + + బ్యాంక్ ఖాతా స్టేట్‌మెంట్‌లను అప్‌లోడ్ చేయండి + కొలేటరల్ పత్రాలను అప్‌లోడ్ చేయండి + సంతకాన్ని అప్‌లోడ్ చేయండి + + ఏదో తప్పు జరిగింది! ఫైల్‌ను అప్‌లోడ్ చేయలేకపోతోంది + దయచేసి బ్యాంక్ స్టేట్‌మెంట్‌ను అప్‌లోడ్ చేయండి + దయచేసి ఆస్తి పత్రాన్ని అప్‌లోడ్ చేయండి + దయచేసి సంతకాన్ని అప్‌లోడ్ చేయండి + పత్రాలు విజయవంతంగా అప్‌లోడ్ చేయబడ్డాయి! + + ఇక్కడ సంతకం చేయండి + రీసెట్ + సంతకం + క్యాప్చర్ + జత చేయండి + సంతకాన్ని అప్‌లోడ్ చేయండి + సేవ్ చేసి సమర్పించండి + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values-ur/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values-ur/strings.xml new file mode 100644 index 000000000..a4e39bb82 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values-ur/strings.xml @@ -0,0 +1,157 @@ + + + + + + نیا قرض لاگو کریں + + + قرض کی درخواست + + + درخواست دہندہ کا نام + قرض کی مصنوعات + قرض کا مقصد + ادائیگی کی تاریخ + اصل رقم + کرنسی + + + جاری رکھیں + ٹھیک ہے + منسوخ کریں + قرض کے لیے درخواست دیں + اگلا + + + درخواست دہندہ کا نام درکار ہے + درخواست دہندہ کا نام کم از کم 4 حروف پر مشتمل ہونا چاہیے + درخواست دہندہ کا نام 100 حروف سے زیادہ نہیں ہو سکتا + درخواست دہندہ کے نام میں غلط حروف شامل ہیں + + + قرض کی مصنوعات درکار ہے + + + اصل رقم درکار ہے + براہ کرم ایک درست رقم کا فارمیٹ درج کریں + اصل رقم بہت کم ہے + اصل رقم زیادہ سے زیادہ حد سے تجاوز کر گئی ہے + منتخب کردہ کرنسی کے لیے رقم میں بہت سارے اعشاریہ ہیں + رقم درست ضربوں میں نہیں ہے + + + ادائیگی کی تاریخ درکار ہے + براہ کرم ایک درست تاریخ کا فارمیٹ درج کریں (DD/MM/YYYY) + ادائیگی کی تاریخ مستقبل میں ہونی چاہیے + ادائیگی کی تاریخ مستقبل میں بہت دور ہے (زیادہ سے زیادہ 10 سال) + + + ہماری طرف سے سرور کا مسئلہ مزید جاری نہیں رہ سکتا، تھوڑی دیر بعد دوبارہ کوشش کریں + بہت سی ناکام کوششیں۔ براہ کرم بعد میں دوبارہ کوشش کریں۔ + قرض کی درخواست جمع کرانے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔ + + + قرض کی درخواست کامیابی کے ساتھ جمع کرائی گئی! + درخواست کا مسودہ کامیابی کے ساتھ محفوظ ہو گیا + + + غیر محفوظ شدہ تبدیلیاں! + آپ کے پاس غیر محفوظ شدہ تبدیلیاں ہیں۔ کیا آپ واقعی واپس جانا چاہتے ہیں؟ + + + رد کریں + مسودہ محفوظ کریں + دوبارہ کوشش کریں + منسوخ کریں + جاری رکھیں + + + اپنا پورا نام درج کریں + قرض کی مصنوعات منتخب کریں + قرض کی رقم درج کریں + ادائیگی کی تاریخ منتخب کریں + مقصد کو مختصر بیان کریں + + + کرنسی تبدیل کر دی گئی ہے۔ رقم کی دوبارہ توثیق کی جائے گی۔ + قرض کی کم از کم رقم: %1$s + زیادہ سے زیادہ قرض کی رقم: %1$s + تفصیلات کی تصدیق کریں + + درخواست کامیابی کے ساتھ جمع کرائی گئی + آپ کی قرض کی درخواست جمع کر دی گئی ہے۔ اس کا جائزہ لینے کے بعد آپ کو مطلع کیا جائے گا۔ + ہوم پر واپس جائیں + + درخواست جمع کرانے میں ناکام + آپ کی قرض کی درخواست جمع کراتے وقت کچھ غلط ہو گیا۔ براہ کرم دوبارہ کوشش کریں۔ + دوبارہ کوشش کریں + + + + نیا قرض لاگو کریں + قرض کی قسم منتخب کریں + فی الحال قرض کی مصنوعات دستیاب نہیں ہیں + یقینی بنائیں کہ آپ انٹرنیٹ سے جڑے ہوئے ہیں + شرائط و ضوابط قبول کریں + + + %1$s قرض کے لیے درخواست دیں + %1$s قرض حاصل کریں + %1$s تک + %1$s سے %2$s سود + شرائط و ضوابط + + قرض کی منظوری اور ادائیگی + شرح سود + جاری رکھیں + واپسی + سیکورٹی اور ضمانت + انشورنس + ڈیفالٹ + دستاویزی کارروائی + قانونی تعمیل + ترمیمات اور خاتمہ + دائرہ اختیار + + + ذاتی قرض کی منظوری درخواست دہندہ کی اہلیت، آمدنی کے استحکام، ساکھ اور قرض دہندہ کی اندرونی کریڈٹ پالیسیوں کی بنیاد پر دی جائے گی۔ قرض دہندہ کسی بھی ذاتی قرض کی درخواست کو اپنی صوابدید پر اور بغیر کسی وجہ کے منظور یا مسترد کرنے کا حق محفوظ رکھتا ہے۔ منظور شدہ قرض کی رقم مکمل یا جزوی طور پر تقسیم کی جا سکتی ہے، جو قرض کے معاہدے اور دستاویزات کی ضروریات پر منحصر ہے۔ ادائیگی KYC طریقہ کار کی کامیاب تکمیل، جمع کرائے گئے دستاویزات کی تصدیق اور منظوری کے خط میں بیان کردہ تمام شرائط کی تعمیل سے مشروط ہے۔ + ذاتی قرض میں یا تو مقررہ یا تیرتی سود کی شرح ہو سکتی ہے، جیسا کہ قرض کے معاہدے کے وقت قرض لینے والے نے منتخب کیا ہو۔ تیرتی سود کی شرح والے قرضوں کی صورت میں، لاگو ہونے والی شرح قرض دہندہ کے بینچ مارک یا حوالہ کی شرح میں تبدیلیوں کی بنیاد پر وقتاً فوقتاً نظر ثانی سے مشروط ہے۔ ایسی تبدیلیاں قرض لینے والے پر لازم ہوں گی اور EMI کی رقم اور/یا قرض کی مدت میں تبدیلی کا باعث بن سکتی ہیں، جیسا کہ قرض دہندہ ضروری سمجھے۔ + ذاتی قرض کی واپسی مساوی ماہانہ اقساط (EMIs) کے ذریعے کی جائے گی جس میں اصل اور سود دونوں شامل ہیں۔ EMIs قرض کے معاہدے میں بیان کردہ تاریخ سے شروع ہوں گی اور ماہانہ بنیاد پر جاری رہیں گی جب تک کہ سود کے ساتھ قرض کی پوری رقم واپس نہ کر دی جائے۔ قرض دہندہ کی مروجہ پالیسیوں کے مطابق قبل از وقت ادائیگی یا جزوی ادائیگی کی اجازت دی جا سکتی ہے اور اس پر کچھ چارجز لاگو ہو سکتے ہیں، خاص طور پر مقررہ شرح والے قرضوں کے لیے۔ + ذاتی قرضے عام طور پر غیر محفوظ نوعیت کے ہوتے ہیں اور انہیں ضمانت کی ضرورت نہیں ہوتی۔ تاہم، قرض دہندہ درخواست دہندہ کے کریڈٹ پروفائل اور قرض کی رقم کی بنیاد پر غیر معمولی معاملات میں اضافی گارنٹی یا سیکیورٹی کی درخواست کرنے کا حق محفوظ رکھتا ہے۔ ایسی کسی بھی ضرورت کو منظوری کے خط میں واضح طور پر بیان کیا جائے گا۔ اگر قابل اطلاق ہو تو، دستاویزات یا قانونی کارروائیوں سے وابستہ تمام اخراجات قرض لینے والے برداشت کریں گے۔ + اگرچہ ذاتی قرضوں کے لیے عام طور پر اثاثہ انشورنس کی ضرورت نہیں ہوتی، قرض دہندہ تجویز کر سکتا ہے کہ قرض لینے والا درخواست دہندہ کی موت کی صورت میں بقایا قرض کی رقم کو محفوظ کرنے کے لیے لائف انشورنس پالیسی کا انتخاب کرے۔ یہ لازمی نہیں ہے لیکن قرض لینے والے کے خاندان یا انحصار کرنے والوں کے لیے مالی تحفظ کے اقدام کے طور پر مشورہ دیا جاتا ہے۔ + EMIs کی عدم ادائیگی یا تاخیر سے ادائیگی ڈیفالٹ تصور کی جائے گی۔ ایسی صورتوں میں، قرض دہندہ بقایا جات وصول کرنے کے لیے بازیابی کی کارروائی شروع کر سکتا ہے، جس میں قانونی کارروائی بھی شامل ہو سکتی ہے۔ التواء شدہ رقوم پر تعزیری سود اور دیگر چارجز لاگو ہو سکتے ہیں۔ مسلسل یا بار بار ڈیفالٹ قرض لینے والے کے کریڈٹ سکور اور مستقبل میں قرض لینے کی صلاحیت کو منفی طور پر متاثر کر سکتا ہے۔ + قرض لینے والے کو درست اور مکمل دستاویزات جمع کروانے کی ضرورت ہوتی ہے، بشمول شناخت کا ثبوت، پتہ کا ثبوت، آمدنی کی تفصیلات، بینک اسٹیٹمنٹ، اور قرض دہندہ کی درخواست کردہ دیگر دستاویزات۔ جمع کرائے گئے دستاویزات میں کوئی تضاد، غلط بیانی یا جعل سازی قرض کی درخواست مسترد ہونے یا قرض کا معاہدہ ختم کرنے کا سبب بن سکتی ہے، ساتھ ہی قانونی نتائج بھی بھگتنا پڑ سکتے ہیں۔ + قرض لینے والے کو اپنی مالی ذمہ داریوں سے متعلق تمام قابل اطلاق مقامی، ریاستی اور قومی قوانین کی مکمل تعمیل کو یقینی بنانا چاہیے۔ اس میں ٹیکسوں کی بروقت ادائیگی، ریگولیٹری تقاضوں کی پاسداری اور درست دستاویزات جمع کرانا شامل ہے۔ قرض دہندہ کو قرض لینے والے کی طرف سے کی گئی کسی بھی قانونی خلاف ورزی کے لیے ذمہ دار نہیں ٹھہرایا جائے گا۔ + قرض دہندہ وقتاً فوقتاً ان شرائط و ضوابط پر نظر ثانی یا اپ ڈیٹ کر سکتا ہے، بشرطیکہ قرض لینے والے کو پیشگی نوٹس دیا جائے۔ قرض کا معاہدہ قرض کی رقم کی مکمل واپسی یا ڈیفالٹ کی وجہ سے شروع کی گئی ضبطی کی کارروائی کے ذریعے ختم تصور کیا جائے گا۔ + ذاتی قرض کے معاہدے سے پیدا ہونے والے یا اس سے متعلق تمام تنازعات قرض دہندہ کے رجسٹرڈ آفس کے مقام پر عدالتوں کے خصوصی دائرہ اختیار میں آئیں گے، جب تک کہ معاہدے میں دوسری صورت میں وضاحت نہ کی گئی ہو۔ + + + + + بینک اکاؤنٹ کے بیانات اپ لوڈ کریں + ضمانتی دستاویزات اپ لوڈ کریں + دستخط اپ لوڈ کریں + + کچھ غلط ہو گیا! فائل اپ لوڈ کرنے سے قاصر + براہ کرم بینک اسٹیٹمنٹ اپ لوڈ کریں + براہ کرم پراپرٹی کا دستاویز اپ لوڈ کریں + براہ کرم دستخط اپ لوڈ کریں + دستاویزات کامیابی کے ساتھ اپ لوڈ ہو گئیں! + + یہاں دستخط کریں + دوبارہ ترتیب دیں + دستخط + کیپچر کریں + منسلک کریں + دستخط اپ لوڈ کریں + محفوظ کریں اور جمع کرائیں + diff --git a/libs/mifos-loans/src/commonMain/composeResources/values/strings.xml b/libs/mifos-loans/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 000000000..e79f53720 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,162 @@ + + + + + + Apply New Loan + + + Loan Application + + + Applicant Name + Loan Product + Purpose of Loan + Disbursement Date + Principal Amount + Currency + + + Continue + Ok + Cancel + Apply Loan + Next + + + Applicant name is required + Applicant name must be at least 4 characters + Applicant name cannot exceed 100 characters + Applicant name contains invalid characters + + + Loan product is required + + + Principal amount is required + Please enter a valid amount format + Principal amount is too small + Principal amount exceeds maximum limit + Amount has too many decimal places for selected currency + Amount is not in valid multiples + + + Disbursement date is required + Please enter a valid date format (DD/MM/YYYY) + Disbursement date must be in the future + Disbursement date is too far in the future (max 10 years) + + + Server issue from our side cannot proceed further, Try again after a moment + Too many failed attempts. Please try again later. + Failed to submit loan application. Please try again. + + + Loan application submitted successfully! + Application draft saved successfully + + + Unsaved Changes! + You have unsaved changes. Are you sure you want to go back? + + + Discard + Save Draft + Retry + Cancel + Continue + + + Enter your full name + Select loan product + Enter loan amount + Select disbursement date + Briefly describe the purpose + + + Currency changed. Amount will be revalidated. + Minimum loan amount: %1$s + Maximum loan amount: %1$s + Confirm Details + + Request Submitted Successfully + Your loan application has been submitted. You will be notified once it's reviewed. + Back To Home + + Request Failed To Submit + Something went wrong while submitting your loan application. Please try again. + Try Again + + User verification failed. Please try again. + + + + Select Loan Provider + Choose a Loan Provider + Loan Provider + Apply New Loan + Choose Type of Loan + Currently Loan Products are not available + Make sure you connect to internet + Accept Terms & Conditions + + + Apply %1$s Loan + Get %1$s Loan + Upto %1$s + %1$s to %2$s Interest + Terms & Conditions + + Loan Sanction and Disbursement + Interest Rate + Continue + Repayment + Security and Collateral + Insurance + Default + Documentation + Legal Compliance + Amendments and Termination + Jurisdiction + + + The personal loan will be sanctioned based on the applicant’s eligibility, income stability, creditworthiness, and the lender’s internal credit policies. The lender reserves the right to approve or reject any personal loan application at its sole discretion without assigning any reason. The sanctioned loan amount may be disbursed in full or in parts, depending on the loan agreement and documentation requirements. Disbursement is subject to successful completion of KYC procedures, verification of submitted documents, and adherence to all conditions specified in the sanction letter. + The personal loan may carry either a fixed or floating interest rate, as chosen by the borrower at the time of loan agreement. In the case of floating interest rate loans, the applicable rate is subject to periodic revisions based on changes in the lender’s benchmark or reference rate. Such changes shall be binding on the borrower and may lead to a variation in the EMI amount and/or the loan tenure, as deemed necessary by the lender. + Repayment of the personal loan will be made through Equated Monthly Installments (EMIs) comprising both principal and interest components. EMIs will begin from the date specified in the loan agreement and will continue on a monthly basis until the full loan amount, along with applicable interest, is repaid. Prepayment or part payment may be allowed in accordance with the lender’s prevailing policies and may attract certain charges, particularly for fixed-rate loans. + Personal loans are typically unsecured in nature and do not require collateral. However, the lender reserves the right to request additional guarantees or security in exceptional cases, depending on the borrower’s credit profile and loan amount. Any such requirements will be clearly stated in the sanction letter. The borrower will bear all costs associated with documentation or legal formalities, if applicable. + While personal loans do not generally require asset insurance, the lender may recommend that the borrower opt for a life insurance policy to safeguard the outstanding loan amount in the event of the borrower’s death. This is not mandatory but is advisable as a financial protection measure for the borrower’s family or dependents. + Non-payment or delayed payment of EMIs will constitute a default. In such instances, the lender may initiate recovery procedures to collect the outstanding dues, which may include legal proceedings. Penal interest and other charges may be applied on overdue amounts. Continued or repeated default can negatively affect the borrower’s credit score and future borrowing ability. + The borrower is required to submit accurate and complete documentation, including but not limited to identity proof, address proof, income details, bank statements, and any other documents requested by the lender. Any discrepancies, misrepresentation, or falsification in the submitted documents may result in the rejection of the loan application or termination of the loan agreement, along with possible legal implications. + The borrower must ensure full compliance with all applicable local, state, and national laws concerning their financial obligations. This includes timely payment of taxes, adherence to regulatory requirements, and submission of valid documents. The lender shall not be held liable for any legal breaches committed by the borrower. + The lender may, from time to time, revise or update these terms and conditions, subject to appropriate prior notice to the borrower. The loan agreement shall be considered terminated upon full repayment of the loan amount or through foreclosure procedures initiated due to default. + All disputes arising out of or in relation to the personal loan agreement shall fall under the exclusive jurisdiction of the courts at the location of the lender’s registered office, unless otherwise specified in the agreement. + + + + + Upload Bank Account Statements + Upload Collateral Documents + Upload Signature + + Something Went Wrong! Unable to upload file + Please Upload a Bank Statement + Please Upload a Property Document + Please Upload a Signature + Documents Uploaded Successfully! + + Sign Here + Reset + Sign + Capture + Attach + Upload Signature + Save & Submit + diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/ApplyLoanBottomBar.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/ApplyLoanBottomBar.kt new file mode 100644 index 000000000..1bc5ef03b --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/ApplyLoanBottomBar.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_title +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_product_details_terms +import org.jetbrains.compose.resources.stringResource +import org.mifospay.core.designsystem.component.MifosButton +import org.mifospay.core.designsystem.utils.onClick +import template.core.base.designsystem.theme.KptTheme + +/** + * A bottom bar for the loan application screen, featuring a terms agreement + * checkbox and a submission button. + * + * @param checked The current selection state of the terms agreement checkbox. + * @param isEnabled Controls whether the 'Apply' button is clickable. + * @param onCheckedChange Callback invoked when the terms checkbox is toggled. + * @param onApplyClick Action to perform when the 'Apply' button is clicked. + */ +@Composable +fun ApplyLoanBottomBar( + checked: Boolean, + isEnabled: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + onApplyClick: () -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(KptTheme.spacing.md), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .onClick { onCheckedChange(!checked) }, + verticalAlignment = Alignment.CenterVertically, + ) { + Checkbox( + modifier = Modifier.size(18.dp), + checked = checked, + onCheckedChange = onCheckedChange, + colors = CheckboxDefaults.colors( + checkedColor = KptTheme.colorScheme.primary, + ), + ) + Text( + text = stringResource(Res.string.feature_loan_product_details_terms), + style = KptTheme.typography.labelMedium, + ) + } + + MifosButton( + modifier = Modifier.fillMaxWidth().height(48.dp), + onClick = onApplyClick, + enabled = isEnabled, + shape = KptTheme.shapes.medium, + ) { + Text( + text = stringResource(Res.string.feature_apply_loan_title), + style = KptTheme.typography.titleMedium, + ) + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/ConfirmDetailsCard.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/ConfirmDetailsCard.kt new file mode 100644 index 000000000..6612af7b2 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/ConfirmDetailsCard.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.component + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.stringResource +import org.mifospay.core.designsystem.component.MifosCard +import template.core.base.designsystem.theme.KptTheme + +/** + * Displays a summary card containing a list of labeled details, typically used for confirmation screens. + * + * @param keyValuePairs A map where the key is the label resource and the value is the detail text to display. + */ +@Composable +fun ConfirmDetailsCard( + keyValuePairs: Map, + modifier: Modifier = Modifier, +) { + MifosCard( + modifier = modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = KptTheme.colorScheme.outlineVariant, + shape = KptTheme.shapes.medium, + ), + shape = KptTheme.shapes.medium, + colors = CardDefaults.cardColors(), + ) { + Column(modifier = Modifier.padding(KptTheme.spacing.md)) { + keyValuePairs.forEach { (key, value) -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = KptTheme.spacing.sm), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = "${stringResource(key)} :", + style = KptTheme.typography.labelMedium, + ) + Text( + text = value, + style = KptTheme.typography.labelMedium, + textAlign = TextAlign.Right, + ) + } + } + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/DocumentType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/DocumentType.kt new file mode 100644 index 000000000..59c548c56 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/DocumentType.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.component + +/** +* Represents the specific categories of documents required for a loan application. +*/ +enum class DocumentType { + BANK_STATEMENT, + PROPERTY_DOCUMENT, + SIGNATURE, +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/LoanCard.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/LoanCard.kt new file mode 100644 index 000000000..319239273 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/LoanCard.kt @@ -0,0 +1,176 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.painterResource +import org.mifospay.core.designsystem.component.MifosCard +import template.core.base.designsystem.theme.KptTheme + +/** + * A compact loan summary card showing title/amount/interest as plain text on a solid + * background — no background image, since a generic illustration stretched to fill the + * card looked like a stray diagonal shimmer artifact. + * + * @param title The loan product title (e.g., "Home Loan"). + * @param amount The formatted loan amount string. + * @param interestRate The interest rate string to display. + * @param onClick Optional callback invoked when the card is clicked. + */ +@Composable +fun LoanCard( + title: String, + amount: String, + interestRate: String, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, +) { + MifosCard( + modifier = modifier + .fillMaxWidth() + .then( + if (onClick != null) { + Modifier.clickable { onClick() } + } else { + Modifier + }, + ), + shape = KptTheme.shapes.medium, + colors = CardDefaults.cardColors( + containerColor = KptTheme.colorScheme.primaryContainer, + ), + elevation = KptTheme.elevation.level2, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(KptTheme.spacing.lg), + ) { + Text( + text = title, + style = KptTheme.typography.titleMedium, + color = KptTheme.colorScheme.onPrimaryContainer, + ) + + Spacer(modifier = Modifier.height(KptTheme.spacing.sm)) + + Text( + text = amount, + style = KptTheme.typography.headlineSmall, + color = KptTheme.colorScheme.onPrimaryContainer, + ) + + Spacer(modifier = Modifier.height(KptTheme.spacing.xs)) + + Text( + text = interestRate, + style = KptTheme.typography.bodySmall, + color = KptTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f), + ) + } + } +} + +/** + * An expanded loan card featuring a top-section image followed by a colored content area. + * + * @param cardImage The hero image displayed at the top of the card. + * @param title The loan product title. + * @param amount The formatted loan amount string. + * @param interestRate The interest rate string. + * @param backgroundColor The background color for the text content area. + * @param contentColor The color used for text elements. + * @param onClick Optional callback invoked when the card is clicked. + */ +@Composable +fun LoanCardCustom( + cardImage: DrawableResource, + title: String, + amount: String, + interestRate: String, + modifier: Modifier = Modifier, + backgroundColor: Color = KptTheme.colorScheme.primary, + contentColor: Color = Color.White, + onClick: (() -> Unit)? = null, +) { + MifosCard( + modifier = modifier + .fillMaxWidth() + .then( + if (onClick != null) { + Modifier.clickable { onClick() } + } else { + Modifier + }, + ), + shape = KptTheme.shapes.medium, + colors = CardDefaults.cardColors(containerColor = backgroundColor), + elevation = KptTheme.elevation.level3, + ) { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + // Image section + Image( + modifier = Modifier + .fillMaxWidth() + .height(140.dp) + .clip(KptTheme.shapes.medium), + painter = painterResource(cardImage), + contentDescription = null, + contentScale = ContentScale.Crop, + ) + + // Content section + Column( + modifier = Modifier + .fillMaxWidth() + .padding(KptTheme.spacing.lg), + ) { + Text( + text = title, + style = KptTheme.typography.bodySmall, + color = contentColor, + ) + + Spacer(modifier = Modifier.height(KptTheme.spacing.sm)) + + Text( + text = amount, + style = KptTheme.typography.titleMedium, + color = contentColor, + ) + + Spacer(modifier = Modifier.height(KptTheme.spacing.md)) + + Text( + text = interestRate, + style = KptTheme.typography.bodySmall, + color = contentColor.copy(alpha = 0.8f), + ) + } + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/SignatureUploadType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/SignatureUploadType.kt new file mode 100644 index 000000000..10e560dd5 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/SignatureUploadType.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.component + +/** + * Defines the available methods for providing a user signature during the loan application. + */ +enum class SignatureUploadType { + SIGN, + CAPTURE, + GALLERY, +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/TermsAndConditionItem.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/TermsAndConditionItem.kt new file mode 100644 index 000000000..34c0dc732 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/TermsAndConditionItem.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.stringResource +import template.core.base.designsystem.theme.KptTheme + +/** + * Displays a single terms and conditions entry with a highlighted title and body text. + * + * @param title The resource ID for the term's heading. + * @param description The resource ID for the detailed explanation of the term. + */ +@Composable +fun TermsAndConditionItem( + title: StringResource, + description: StringResource, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.sm), + ) { + Text( + text = stringResource(title), + style = KptTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + ) + + Text( + text = stringResource(description), + style = KptTheme.typography.bodySmall, + ) + Spacer(modifier = Modifier.height(KptTheme.spacing.sm)) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/UploadDocsSection.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/UploadDocsSection.kt new file mode 100644 index 000000000..7014eb304 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/component/UploadDocsSection.kt @@ -0,0 +1,202 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Image +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_upload_docs_bank_account +import mobile_wallet.libs.mifos_loans.generated.resources.feature_upload_docs_collateral +import mobile_wallet.libs.mifos_loans.generated.resources.feature_upload_docs_signature +import org.jetbrains.compose.resources.stringResource +import org.mifos.lib.loan.ui.uploadDocs.UploadDocsAction +import org.mifos.lib.loan.ui.uploadDocs.UploadDocsState +import org.mifospay.core.designsystem.component.MifosCard +import org.mifospay.core.designsystem.icon.MifosIcons +import template.core.base.designsystem.theme.KptTheme + +/** + * Renders the document upload section, switching between upload prompts and file summaries based on the current state. + * + * @param state The current state containing file data for bank statements, property documents, and signatures. + * @param onAction Callback to handle user interactions like uploading, removing, or re-selecting documents. + */ +@Composable +internal fun UploadDocumentsSection( + state: UploadDocsState, + onAction: (UploadDocsAction) -> Unit, +) { + Column( + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + if (state.bankStatementFile != null) { + UploadedFileCard( + icon = MifosIcons.Receipt, + label = stringResource(Res.string.feature_upload_docs_bank_account), + fileName = state.bankStatementFileName ?: "", + fileSize = state.bankStatementSize ?: "", + onRemoveClick = { onAction(UploadDocsAction.RemoveDocument(DocumentType.BANK_STATEMENT)) }, + onSelectNewClick = { onAction(UploadDocsAction.SelectNewDocument(DocumentType.BANK_STATEMENT)) }, + ) + } else { + UploadPromptCard( + icon = Icons.Filled.Image, + text = stringResource(Res.string.feature_upload_docs_bank_account), + onClick = { + onAction(UploadDocsAction.UploadDocument(DocumentType.BANK_STATEMENT)) + }, + ) + } + + if (state.propertyDocumentsFile != null) { + UploadedFileCard( + icon = MifosIcons.DataInfo, + label = stringResource(Res.string.feature_upload_docs_collateral), + fileName = state.propertyDocumentFileName ?: "", + fileSize = state.propertyDocumentsSize ?: "", + onRemoveClick = { onAction(UploadDocsAction.RemoveDocument(DocumentType.PROPERTY_DOCUMENT)) }, + onSelectNewClick = { onAction(UploadDocsAction.SelectNewDocument(DocumentType.PROPERTY_DOCUMENT)) }, + ) + } else { + UploadPromptCard( + icon = Icons.Filled.Image, + text = stringResource(Res.string.feature_upload_docs_collateral), + onClick = { + onAction(UploadDocsAction.UploadDocument(DocumentType.PROPERTY_DOCUMENT)) + }, + ) + } + + if (state.signatureDocumentFile != null) { + UploadedFileCard( + icon = MifosIcons.Edit, + label = stringResource(Res.string.feature_upload_docs_signature), + fileName = state.signatureFileName ?: "", + fileSize = state.signatureSize ?: "", + onRemoveClick = { onAction(UploadDocsAction.RemoveDocument(DocumentType.SIGNATURE)) }, + onSelectNewClick = { onAction(UploadDocsAction.ShowSignatureUploadSheet) }, + ) + } else { + UploadPromptCard( + icon = Icons.Filled.Image, + text = stringResource(Res.string.feature_upload_docs_signature), + onClick = { + onAction(UploadDocsAction.ShowSignatureUploadSheet) + }, + ) + } + } +} + +/** + * A tappable card prompting the user to upload a document, shown when no file has been selected yet. + */ +@Composable +private fun UploadPromptCard( + icon: ImageVector, + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + MifosCard( + modifier = modifier.fillMaxWidth(), + shape = KptTheme.shapes.medium, + onClick = onClick, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(KptTheme.spacing.md), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(KptTheme.spacing.sm), + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = KptTheme.colorScheme.primary, + ) + Text( + text = text, + style = KptTheme.typography.bodyMedium, + ) + } + } +} + +/** + * A summary card for a document that has already been selected, showing its name, size, and + * actions to remove or replace it. + */ +@Composable +private fun UploadedFileCard( + icon: ImageVector, + label: String, + fileName: String, + fileSize: String, + onRemoveClick: () -> Unit, + onSelectNewClick: () -> Unit, + modifier: Modifier = Modifier, +) { + MifosCard( + modifier = modifier.fillMaxWidth(), + shape = KptTheme.shapes.medium, + onClick = onSelectNewClick, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(KptTheme.spacing.md), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(KptTheme.spacing.sm), + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = KptTheme.colorScheme.primary, + ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = label, + style = KptTheme.typography.labelMedium, + ) + Text( + text = fileName, + style = KptTheme.typography.bodySmall, + color = KptTheme.colorScheme.onSurfaceVariant, + ) + if (fileSize.isNotBlank()) { + Text( + text = fileSize, + style = KptTheme.typography.bodySmall, + color = KptTheme.colorScheme.onSurfaceVariant, + ) + } + } + IconButton(onClick = onRemoveClick) { + Icon( + imageVector = MifosIcons.Delete, + contentDescription = null, + ) + } + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/LoanProvider.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/LoanProvider.kt index 90b7be3ee..f094ddf3d 100644 --- a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/LoanProvider.kt +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/LoanProvider.kt @@ -9,25 +9,20 @@ */ package org.mifos.lib.loan.core -import org.mifos.lib.loan.core.model.Loan -import org.mifos.lib.loan.core.model.LoanPayment -import org.mifos.lib.loan.core.model.LoanProduct -import org.mifos.lib.loan.core.model.LoanSchedule +import kotlinx.coroutines.flow.Flow +import org.mifos.lib.loan.core.model.LoanState +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.LoansPayload import org.mifospay.core.common.DataState interface LoanProvider { + fun getLoanTemplate(clientId: Long): Flow> - suspend fun getProducts(): DataState> + fun getLoanTemplateByProduct(clientId: Long, productId: Long): Flow> - suspend fun getLoanDetails( + suspend fun submitLoanApplication( + loanState: LoanState, + payload: LoansPayload, loanId: Long, - ): DataState - - suspend fun applyForLoan( - request: Loan, - ): DataState - - suspend fun makePayment( - payment: LoanPayment, - ): DataState + ): DataState } diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AccountLinkingOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AccountLinkingOptions.kt new file mode 100644 index 000000000..237c08a29 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AccountLinkingOptions.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class AccountLinkingOptions( + + val accountNo: String? = null, + + val clientId: Int? = null, + + val clientName: String? = null, + + val currency: Currency? = null, + + val fieldOfficerId: Int? = null, + + val id: Int? = null, + + val productId: Int? = null, + + val productName: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanSchedule.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AccountingRule.kt similarity index 76% rename from libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanSchedule.kt rename to libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AccountingRule.kt index d9e608ce8..4636c84f4 100644 --- a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanSchedule.kt +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AccountingRule.kt @@ -12,9 +12,11 @@ package org.mifos.lib.loan.core.model import kotlinx.serialization.Serializable @Serializable -data class LoanSchedule( - val loanId: Long, - val totalDue: Double, - val totalPaid: Double, - val nextDueDate: String?, +data class AccountingRule( + + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AllowAttributeOverrides.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AllowAttributeOverrides.kt new file mode 100644 index 000000000..554513dc3 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AllowAttributeOverrides.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class AllowAttributeOverrides( + + val amortizationType: Boolean? = null, + + val interestType: Boolean? = null, + + val transactionProcessingStrategyId: Boolean? = null, + + val interestCalculationPeriodType: Boolean? = null, + + val inArrearsTolerance: Boolean? = null, + + val repaymentEvery: Boolean? = null, + + val graceOnPrincipalAndInterestPayment: Boolean? = null, + + val graceOnArrearsAgeing: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Loan.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AmortizationType.kt similarity index 69% rename from libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Loan.kt rename to libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AmortizationType.kt index 64784df19..ff4715bd2 100644 --- a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Loan.kt +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AmortizationType.kt @@ -12,11 +12,10 @@ package org.mifos.lib.loan.core.model import kotlinx.serialization.Serializable @Serializable -data class Loan( - val id: Long, - val productId: Long, - val principal: Double, - val interestRate: Double, - val status: String, - val schedule: LoanSchedule? = null, +data class AmortizationType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AmortizationTypeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AmortizationTypeOptions.kt new file mode 100644 index 000000000..7678a3607 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/AmortizationTypeOptions.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class AmortizationTypeOptions( + + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/CalendarData.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/CalendarData.kt new file mode 100644 index 000000000..d5cf33d50 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/CalendarData.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class CalendarData( + val id: Int? = null, + + val calendarInstanceId: Int? = null, + + val entityId: Int? = null, + + val entityType: EntityType, + + val title: String? = null, + + val startDate: List = emptyList(), + + val endDate: List = emptyList(), + + val duration: Double? = null, + + val type: Type, + + val repeating: Boolean? = null, + + val recurrence: String? = null, + + val frequency: Frequency, + + val interval: Double? = null, + + val repeatsOnNthDayOfMonth: RepeatsOnNthDayOfMonth, + + val firstReminder: Int? = null, + + val secondReminder: Int? = null, + + val humanReadable: String? = null, + + val createdDate: String? = null, + + val lastUpdatedDate: String? = null, + + val createdByUserId: Int? = null, + + val createdByUsername: String? = null, + + val lastUpdatedByUserId: Int? = null, + + val lastUpdatedByUsername: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeAppliesTo.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeAppliesTo.kt new file mode 100644 index 000000000..0b38a43ba --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeAppliesTo.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChargeAppliesTo( + + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeCalculationType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeCalculationType.kt new file mode 100644 index 000000000..db0339ff9 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeCalculationType.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChargeCalculationType( + val id: Int = 0, + val code: String? = null, + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeOptions.kt new file mode 100644 index 000000000..20e609e1b --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeOptions.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChargeOptions( + + val id: Int? = null, + + val name: String? = null, + + val active: Boolean? = null, + + val penalty: Boolean? = null, + + val currency: Currency? = null, + + val amount: Double? = null, + + val chargeTimeType: ChargeTimeType? = null, + + val chargeAppliesTo: ChargeAppliesTo? = null, + + val chargeCalculationType: ChargeCalculationType? = null, + + val chargePaymentMode: ChargePaymentMode? = null, + + val taxGroup: TaxGroup? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargePaymentMode.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargePaymentMode.kt new file mode 100644 index 000000000..6e78af8fd --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargePaymentMode.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChargePaymentMode( + + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeTimeType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeTimeType.kt new file mode 100644 index 000000000..955358ba5 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ChargeTimeType.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChargeTimeType( + val id: Int = 0, + val code: String? = null, + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Currency.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Currency.kt new file mode 100644 index 000000000..0e55cc308 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Currency.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Currency( + + val code: String? = null, + + val name: String? = null, + + val decimalPlaces: Double? = null, + + val inMultiplesOf: Int? = null, + + val displaySymbol: String? = null, + + val nameCode: String? = null, + + val displayLabel: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/DaysInMonthType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/DaysInMonthType.kt new file mode 100644 index 000000000..7624b395e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/DaysInMonthType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class DaysInMonthType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/DaysInYearType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/DaysInYearType.kt new file mode 100644 index 000000000..a8a8c02fe --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/DaysInYearType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class DaysInYearType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/EntityType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/EntityType.kt new file mode 100644 index 000000000..1833da915 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/EntityType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class EntityType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/EventType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/EventType.kt new file mode 100644 index 000000000..fda9bb72f --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/EventType.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class EventType { + @SerialName("success") + SUCCESS, + + @SerialName("failure") + FAILURE, + + @SerialName("server") + SERVER_EXCEPTION, +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Frequency.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Frequency.kt new file mode 100644 index 000000000..1c51f91c8 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Frequency.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Frequency( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanPayment.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/FundOptions.kt similarity index 80% rename from libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanPayment.kt rename to libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/FundOptions.kt index d6dcab8b6..d165e2061 100644 --- a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanPayment.kt +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/FundOptions.kt @@ -12,8 +12,9 @@ package org.mifos.lib.loan.core.model import kotlinx.serialization.Serializable @Serializable -data class LoanPayment( - val loanId: Long, - val amount: Double, - val paymentDate: String, +data class FundOptions( + + val id: Int? = null, + + val name: String? = null, ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestCalculationPeriodType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestCalculationPeriodType.kt new file mode 100644 index 000000000..0c3266bbd --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestCalculationPeriodType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class InterestCalculationPeriodType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRateFrequencyType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRateFrequencyType.kt new file mode 100644 index 000000000..867763870 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRateFrequencyType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class InterestRateFrequencyType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRateFrequencyTypeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRateFrequencyTypeOptions.kt new file mode 100644 index 000000000..ba9b8c1ed --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRateFrequencyTypeOptions.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class InterestRateFrequencyTypeOptions( + + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRecalculationCompoundingType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRecalculationCompoundingType.kt new file mode 100644 index 000000000..af1bf96ab --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRecalculationCompoundingType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class InterestRecalculationCompoundingType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRecalculationData.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRecalculationData.kt new file mode 100644 index 000000000..0391d0726 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestRecalculationData.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class InterestRecalculationData( + val id: Int? = null, + + val loanId: Int? = null, + + val interestRecalculationCompoundingType: InterestRecalculationCompoundingType? = null, + + val rescheduleStrategyType: RescheduleStrategyType? = null, + + val calendarData: CalendarData, + + val recalculationRestFrequencyType: RecalculationRestFrequencyType? = null, + + val recalculationRestFrequencyInterval: Double? = null, + + val recalculationCompoundingFrequencyType: RecalculationCompoundingFrequencyType? = null, + + @SerialName("isCompoundingToBePostedAsTransaction") + val compoundingToBePostedAsTransaction: Boolean? = null, + + val allowCompoundingOnEod: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestType.kt new file mode 100644 index 000000000..8d6b283d9 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class InterestType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestTypeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestTypeOptions.kt new file mode 100644 index 000000000..25b684a11 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/InterestTypeOptions.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class InterestTypeOptions( + + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanProduct.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanCollateralOptions.kt similarity index 70% rename from libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanProduct.kt rename to libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanCollateralOptions.kt index fed8d6508..0dea46b29 100644 --- a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanProduct.kt +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanCollateralOptions.kt @@ -12,12 +12,15 @@ package org.mifos.lib.loan.core.model import kotlinx.serialization.Serializable @Serializable -data class LoanProduct( - val id: Long, - val name: String, - val shortName: String, +data class LoanCollateralOptions( + + val id: Int? = null, + + val name: String? = null, + + val position: Int? = null, + val description: String? = null, - val minimumPrincipal: Double, - val maximumPrincipal: Double, - val interestRate: Double, + + val active: Boolean? = null, ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanOfficerOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanOfficerOptions.kt new file mode 100644 index 000000000..632319897 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanOfficerOptions.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanOfficerOptions( + + val id: Int? = null, + + val firstname: String? = null, + + val lastname: String? = null, + + val displayName: String? = null, + + val mobileNo: String? = null, + + val officeId: Int? = null, + + val officeName: String? = null, + + @SerialName("isLoanOfficer") + val loanOfficer: Boolean? = null, + + @SerialName("isActive") + val active: Boolean? = null, + + val joiningDate: List? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanPurposeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanPurposeOptions.kt new file mode 100644 index 000000000..3a17f72c1 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanPurposeOptions.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanPurposeOptions( + + val id: Int? = null, + + val name: String? = null, + + val position: Int? = null, + + val description: String? = null, + + @SerialName("isActive") + val active: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanState.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanState.kt new file mode 100644 index 000000000..c7806ff25 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanState.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +/** + * Created by dilpreet on 3/6/17. + */ +enum class LoanState { + + CREATE, + + UPDATE, +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanTemplate.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanTemplate.kt new file mode 100644 index 000000000..d546f2618 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoanTemplate.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanTemplate( + + val clientId: Int? = null, + + val clientAccountNo: String? = null, + + val clientName: String? = null, + + val clientOfficeId: Int? = null, + + val loanProductName: String? = null, + + @SerialName("isLoanProductLinkedToFloatingRate") + val loanProductLinkedToFloatingRate: Boolean? = null, + + val fundId: Int? = null, + + val fundName: String? = null, + + val currency: Currency? = null, + + val principal: Double? = null, + + val approvedPrincipal: Double? = null, + + val proposedPrincipal: Double? = null, + + val termFrequency: Int? = null, + + val termPeriodFrequencyType: TermPeriodFrequencyType? = null, + + val numberOfRepayments: Int? = null, + + val repaymentEvery: Int? = null, + + val repaymentFrequencyType: RepaymentFrequencyType? = null, + + val interestRatePerPeriod: Double? = null, + + val interestRateFrequencyType: InterestRateFrequencyType? = null, + + val annualInterestRate: Double? = null, + + @SerialName("isFloatingInterestRate") + val floatingInterestRate: Boolean? = null, + + val amortizationType: AmortizationType? = null, + + val interestType: InterestType? = null, + + val interestCalculationPeriodType: InterestCalculationPeriodType? = null, + + val allowPartialPeriodInterestCalculation: Boolean? = null, + + val transactionProcessingStrategyId: Int? = null, + + val transactionProcessingStrategyCode: String? = null, + + val graceOnArrearsAgeing: Int? = null, + + val timeline: Timeline? = null, + + val productOptions: List = emptyList(), + + val loanOfficerOptions: List = emptyList(), + + val loanPurposeOptions: List = emptyList(), + + val fundOptions: List = emptyList(), + + val termFrequencyTypeOptions: List = emptyList(), + + val repaymentFrequencyTypeOptions: List = emptyList(), + + val repaymentFrequencyNthDayTypeOptions: List = emptyList(), + + val repaymentFrequencyDaysOfWeekTypeOptions: List = emptyList(), + + val interestRateFrequencyTypeOptions: List = emptyList(), + + val amortizationTypeOptions: List = emptyList(), + + val interestTypeOptions: List = emptyList(), + + val interestCalculationPeriodTypeOptions: List = emptyList(), + + val transactionProcessingStrategyOptions: List = emptyList(), + + val chargeOptions: List = emptyList(), + + val loanCollateralOptions: List = emptyList(), + + val multiDisburseLoan: Boolean? = null, + + val canDefineInstallmentAmount: Boolean? = null, + + val canDisburse: Boolean? = null, + + val product: Product? = null, + + val daysInMonthType: DaysInMonthType? = null, + + val daysInYearType: DaysInYearType? = null, + + @SerialName("isInterestRecalculationEnabled") + val interestRecalculationEnabled: Boolean? = null, + + @SerialName("isvaliableInstallmentsAllowed") + val valiableInstallmentsAllowed: Boolean? = null, + + val minimumGap: Int? = null, + + val maximumGap: Int? = null, + + val accountLinkingOptions: List = emptyList(), + +) { + /** + * Required to set default value to the Fund spinner + * + * @param fundId The value received from the Template for that particular loanProduct + * @return Returns the index of the fundOption list where the specified fundId is located + */ + fun getFundNameFromId(fundId: Int): Int { + for (i in fundOptions.indices) { + if (fundOptions[i].id == fundId) { + return i + } + } + return 0 + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoansPayload.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoansPayload.kt new file mode 100644 index 000000000..3f6aff2cf --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/LoansPayload.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class LoansPayload( + + val clientId: Int? = null, + + val productId: Int? = null, + + val productName: String? = null, + + val principal: Double? = null, + + val loanTermFrequency: Int? = null, + + val loanTermFrequencyType: Int? = null, + + val loanType: String? = null, + + val numberOfRepayments: Int? = null, + + val repaymentEvery: Int? = null, + + val repaymentFrequencyType: Int? = null, + + val interestRatePerPeriod: Double? = null, + + val amortizationType: Int? = null, + + val interestType: Int? = null, + + val interestCalculationPeriodType: Int? = null, + + val transactionProcessingStrategyId: Int? = null, + + val transactionProcessingStrategyCode: String? = null, + + val expectedDisbursementDate: String? = null, + + val submittedOnDate: String? = null, + + val linkAccountId: Int? = null, + + val loanPurposeId: Int? = null, + + val loanPurpose: String? = null, + + val maxOutstandingLoanBalance: Double? = null, + + val currency: String? = null, + + val dateFormat: String? = null, + + val locale: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Product.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Product.kt new file mode 100644 index 000000000..dd9ff7b86 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Product.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Product( + + val id: Int? = null, + + val name: String? = null, + + val shortName: String? = null, + + val fundId: Int? = null, + + val fundName: String? = null, + + val includeInBorrowerCycle: Boolean? = null, + + val useBorrowerCycle: Boolean? = null, + + val startDate: List? = null, + + val status: String? = null, + + val currency: Currency? = null, + + val principal: Double? = null, + + val minPrincipal: Double? = null, + + val maxPrincipal: Double? = null, + + val numberOfRepayments: Int? = null, + + val minNumberOfRepayments: Int? = null, + + val maxNumberOfRepayments: Int? = null, + + val repaymentEvery: Int? = null, + + val repaymentFrequencyType: RepaymentFrequencyType? = null, + + val interestRatePerPeriod: Double? = null, + + val minInterestRatePerPeriod: Double? = null, + + val maxInterestRatePerPeriod: Double? = null, + + val interestRateFrequencyType: InterestRateFrequencyType? = null, + + val annualInterestRate: Double? = null, + + @SerialName("isLinkedToFloatingInterestRates") + val linkedToFloatingInterestRates: Boolean? = null, + + @SerialName("isFloatingInterestRateCalculationAllowed") + val floatingInterestRateCalculationAllowed: Boolean? = null, + + val allowvaliableInstallments: Boolean? = null, + + val minimumGap: Double? = null, + + val maximumGap: Double? = null, + + val amortizationType: AmortizationType, + + val interestType: InterestType, + + val interestCalculationPeriodType: InterestCalculationPeriodType? = null, + + val allowPartialPeriodInterestCalculation: Boolean? = null, + + val transactionProcessingStrategyId: Int? = null, + + val transactionProcessingStrategyName: String? = null, + + val graceOnArrearsAgeing: Int? = null, + + val overdueDaysForNPA: Int? = null, + + val daysInMonthType: DaysInMonthType? = null, + + val daysInYearType: DaysInYearType, + + @SerialName("isInterestRecalculationEnabled") + val interestRecalculationEnabled: Boolean? = null, + + val interestRecalculationData: InterestRecalculationData? = null, + + val canDefineInstallmentAmount: Boolean? = null, + + val accountingRule: AccountingRule? = null, + + val multiDisburseLoan: Boolean? = null, + + val maxTrancheCount: Int? = null, + + val principalThresholdForLastInstallment: Double? = null, + + val holdGuaranteeFunds: Boolean? = null, + + val accountMovesOutOfNPAOnlyOnArrearsCompletion: Boolean? = null, + + val allowAttributeOverrides: AllowAttributeOverrides? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ProductOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ProductOptions.kt new file mode 100644 index 000000000..351aeee97 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/ProductOptions.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ProductOptions( + + val id: Int? = null, + + val name: String? = null, + + val includeInBorrowerCycle: Boolean? = null, + + val useBorrowerCycle: Boolean? = null, + + @SerialName("isLinkedToFloatingInterestRates") + val linkedToFloatingInterestRates: Boolean? = null, + + @SerialName("isFloatingInterestRateCalculationAllowed") + val floatingInterestRateCalculationAllowed: Boolean? = null, + + val allowvaliableInstallments: Boolean? = null, + + @SerialName("isInterestRecalculationEnabled") + val interestRecalculationEnabled: Boolean? = null, + + val canDefineInstallmentAmount: Boolean? = null, + + val holdGuaranteeFunds: Boolean? = null, + + val accountMovesOutOfNPAOnlyOnArrearsCompletion: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RecalculationCompoundingFrequencyType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RecalculationCompoundingFrequencyType.kt new file mode 100644 index 000000000..3b0ec8d22 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RecalculationCompoundingFrequencyType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RecalculationCompoundingFrequencyType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RecalculationRestFrequencyType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RecalculationRestFrequencyType.kt new file mode 100644 index 000000000..a74186ced --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RecalculationRestFrequencyType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RecalculationRestFrequencyType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyDaysOfWeekTypeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyDaysOfWeekTypeOptions.kt new file mode 100644 index 000000000..d6365e95c --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyDaysOfWeekTypeOptions.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RepaymentFrequencyDaysOfWeekTypeOptions( + + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyNthDayTypeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyNthDayTypeOptions.kt new file mode 100644 index 000000000..d63194ac8 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyNthDayTypeOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RepaymentFrequencyNthDayTypeOptions( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyType.kt new file mode 100644 index 000000000..09d29a3b4 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RepaymentFrequencyType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyTypeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyTypeOptions.kt new file mode 100644 index 000000000..fd4e571e7 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepaymentFrequencyTypeOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RepaymentFrequencyTypeOptions( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepeatsOnNthDayOfMonth.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepeatsOnNthDayOfMonth.kt new file mode 100644 index 000000000..c4c6023a6 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RepeatsOnNthDayOfMonth.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RepeatsOnNthDayOfMonth( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RescheduleStrategyType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RescheduleStrategyType.kt new file mode 100644 index 000000000..b756d4555 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/RescheduleStrategyType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RescheduleStrategyType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/StatusNavigationDestination.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/StatusNavigationDestination.kt new file mode 100644 index 000000000..ece216a97 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/StatusNavigationDestination.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +enum class StatusNavigationDestination { + HOME, + SAVINGS_APPLICATION, + LOAN_APPLICATION, + SHARE_APPLICATION, + PREVIOUS_SCREEN, + LOGIN, + + LOAN_ACCOUNT, + SAVINGS_ACCOUNT, + THIRD_PARTY_TRANSFER, + + SAVINGS_UPDATE, + SAVINGS_WITHDRAW, + + BENEFICIARY, +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TaxGroup.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TaxGroup.kt new file mode 100644 index 000000000..16b9c1936 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TaxGroup.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class TaxGroup( + val id: Int? = null, + + val name: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TermFrequencyTypeOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TermFrequencyTypeOptions.kt new file mode 100644 index 000000000..9f4376253 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TermFrequencyTypeOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class TermFrequencyTypeOptions( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TermPeriodFrequencyType.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TermPeriodFrequencyType.kt new file mode 100644 index 000000000..9efa72e3e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TermPeriodFrequencyType.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class TermPeriodFrequencyType( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Timeline.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Timeline.kt new file mode 100644 index 000000000..0b6f9b59f --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Timeline.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Timeline( + val expectedDisbursementDate: List, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TransactionProcessingStrategyOptions.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TransactionProcessingStrategyOptions.kt new file mode 100644 index 000000000..f55fcd107 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/TransactionProcessingStrategyOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class TransactionProcessingStrategyOptions( + val id: Int? = null, + + val code: String? = null, + + val name: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Type.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Type.kt new file mode 100644 index 000000000..324674300 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/core/model/Type.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Type( + val id: Int? = null, + + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/di/LoanApplicationModule.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/di/LoanApplicationModule.kt new file mode 100644 index 000000000..5d1410b42 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/di/LoanApplicationModule.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.di + +import org.koin.core.module.dsl.viewModelOf +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.mifos.lib.loan.network.di.LoansNetworkModule +import org.mifos.lib.loan.providers.DummyLoanProvider +import org.mifos.lib.loan.providers.FineractLoanProvider +import org.mifos.lib.loan.providers.LoanProviderRegistry +import org.mifos.lib.loan.repository.LoansRepository +import org.mifos.lib.loan.repository.LoansRepositoryImpl +import org.mifos.lib.loan.ui.confirmDetails.ConfirmDetailsViewModel +import org.mifos.lib.loan.ui.loanApply.LoanApplyViewModel +import org.mifos.lib.loan.ui.loanProductDetails.LoanProductDetailsViewModel +import org.mifos.lib.loan.ui.providerWebView.LoanProviderWebViewViewModel +import org.mifos.lib.loan.ui.selectLoanProvider.SelectLoanProviderViewModel +import org.mifos.lib.loan.ui.selectLoanType.SelectLoanTypeViewModel +import org.mifos.lib.loan.ui.uploadDocs.UploadDocsViewModel +import org.mifospay.core.common.MifosDispatchers + +/** + * Koin module for the `libs/mifos-loans` loan-application wizard. + */ +val LoanApplicationModule = module { + includes(LoansNetworkModule) + + // The loan-application flow has no real per-provider backend yet — the product owner asked + // for a dummy provider-selection step ahead of the (also-dummied-for-now) product flow. All + // four ids offered by the Select Loan Provider screen ("hdfc", "sbi", "icici", "axis") resolve + // to the SAME shared [DummyLoanProvider] instance below, keeping today's "all providers behave + // identically" behaviour while making the resolution mechanism (LoanProviderRegistry) real. + // The real Fineract-backed [FineractLoanProvider] is registered under "fineract" for future + // activation — swap any of the four entries below for a distinct adapter once one exists. + single { DummyLoanProvider() } + single { + FineractLoanProvider(get(), get(named(MifosDispatchers.IO.name))) + } + single { + val dummy = get() + LoanProviderRegistry( + providersById = mapOf( + "hdfc" to dummy, + "sbi" to dummy, + "icici" to dummy, + "axis" to dummy, + "fineract" to get(), + ), + defaultProvider = dummy, + ) + } + single { LoansRepositoryImpl(get()) } + + viewModelOf(::SelectLoanProviderViewModel) + viewModelOf(::LoanProviderWebViewViewModel) + viewModelOf(::SelectLoanTypeViewModel) + viewModelOf(::LoanProductDetailsViewModel) + viewModelOf(::LoanApplyViewModel) + viewModelOf(::UploadDocsViewModel) + viewModelOf(::ConfirmDetailsViewModel) +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/ChargeMapper.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/ChargeMapper.kt new file mode 100644 index 000000000..ffd4d906e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/ChargeMapper.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.mapper + +import org.mifos.lib.loan.core.model.ChargeOptions +import org.mifos.lib.loan.core.model.TaxGroup +import org.mifos.lib.loan.network.model.ChargeOptionsResponseDto +import org.mifos.lib.loan.network.model.TaxGroupResponseDto + +fun ChargeOptionsResponseDto.toModel(): ChargeOptions = + ChargeOptions( + id = id, + name = name, + active = active, + penalty = penalty, + currency = currency?.toModel(), + amount = amount, + chargeTimeType = chargeTimeType?.toChargeTimeType(), + chargeAppliesTo = chargeAppliesTo?.toChargeAppliesTo(), + chargeCalculationType = chargeCalculationType?.toChargeCalculationType(), + chargePaymentMode = chargePaymentMode?.toChargePaymentMode(), + taxGroup = taxGroup?.toModel(), + ) + +fun TaxGroupResponseDto.toModel(): TaxGroup = + TaxGroup( + id = id, + name = name, + ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/CurrencyMapper.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/CurrencyMapper.kt new file mode 100644 index 000000000..96cae6d7f --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/CurrencyMapper.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.mapper + +import org.mifos.lib.loan.core.model.Currency +import org.mifos.lib.loan.network.model.CurrencyResponseDto + +/** + * Note: [CurrencyResponseDto.decimalPlaces] (Int) and [CurrencyResponseDto.inMultiplesOf] + * (Double) are intentionally swapped in type compared to the domain [Currency] model + * (decimalPlaces: Double?, inMultiplesOf: Int?), hence the explicit conversions below. + */ +fun CurrencyResponseDto.toModel(): Currency = + Currency( + code = code, + name = name, + decimalPlaces = decimalPlaces.toDouble(), + inMultiplesOf = inMultiplesOf.toInt(), + displaySymbol = displaySymbol, + nameCode = nameCode, + displayLabel = displayLabel, + ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/InterestRecalculationMapper.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/InterestRecalculationMapper.kt new file mode 100644 index 000000000..5df3dc91e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/InterestRecalculationMapper.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.mapper + +import org.mifos.lib.loan.core.model.CalendarData +import org.mifos.lib.loan.core.model.InterestRecalculationData +import org.mifos.lib.loan.network.model.CalendarDataResponseDto +import org.mifos.lib.loan.network.model.InterestRecalculationDataResponseDto + +fun InterestRecalculationDataResponseDto.toModel(): InterestRecalculationData = + InterestRecalculationData( + id = id, + loanId = loanId, + interestRecalculationCompoundingType = + interestRecalculationCompoundingType?.toInterestRecalculationCompoundingType(), + rescheduleStrategyType = rescheduleStrategyType?.toRescheduleStrategyType(), + calendarData = calendarData.toModel(), + recalculationRestFrequencyType = + recalculationRestFrequencyType?.toRecalculationRestFrequencyType(), + recalculationRestFrequencyInterval = recalculationRestFrequencyInterval, + recalculationCompoundingFrequencyType = + recalculationCompoundingFrequencyType?.toRecalculationCompoundingFrequencyType(), + compoundingToBePostedAsTransaction = compoundingToBePostedAsTransaction, + allowCompoundingOnEod = allowCompoundingOnEod, + ) + +fun CalendarDataResponseDto.toModel(): CalendarData = + CalendarData( + id = id, + calendarInstanceId = calendarInstanceId, + entityId = entityId, + entityType = entityType.toEntityType(), + title = title, + startDate = startDate, + endDate = endDate, + duration = duration, + type = type.toType(), + repeating = repeating, + recurrence = recurrence, + frequency = frequency.toFrequency(), + interval = interval, + repeatsOnNthDayOfMonth = repeatsOnNthDayOfMonth.toRepeatsOnNthDayOfMonth(), + firstReminder = firstReminder, + secondReminder = secondReminder, + humanReadable = humanReadable, + createdDate = createdDate, + lastUpdatedDate = lastUpdatedDate, + createdByUserId = createdByUserId, + createdByUsername = createdByUsername, + lastUpdatedByUserId = lastUpdatedByUserId, + lastUpdatedByUsername = lastUpdatedByUsername, + ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanAccountApplicationMapper.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanAccountApplicationMapper.kt new file mode 100644 index 000000000..5256e3034 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanAccountApplicationMapper.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.mapper + +import org.mifos.lib.loan.core.model.LoansPayload +import org.mifos.lib.loan.network.model.LoanAccountApplicationPayloadDto + +fun LoansPayload.toDto(): LoanAccountApplicationPayloadDto = + LoanAccountApplicationPayloadDto( + clientId = clientId, + productId = productId, + productName = productName, + principal = principal, + loanTermFrequency = loanTermFrequency, + loanTermFrequencyType = loanTermFrequencyType, + loanType = loanType, + numberOfRepayments = numberOfRepayments, + repaymentEvery = repaymentEvery, + repaymentFrequencyType = repaymentFrequencyType, + interestRatePerPeriod = interestRatePerPeriod, + amortizationType = amortizationType, + interestType = interestType, + interestCalculationPeriodType = interestCalculationPeriodType, + transactionProcessingStrategyId = transactionProcessingStrategyId, + transactionProcessingStrategyCode = transactionProcessingStrategyCode, + expectedDisbursementDate = expectedDisbursementDate, + submittedOnDate = submittedOnDate, + linkAccountId = linkAccountId, + loanPurposeId = loanPurposeId, + loanPurpose = loanPurpose, + maxOutstandingLoanBalance = maxOutstandingLoanBalance, + currency = currency, + dateFormat = dateFormat, + locale = locale, + ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanTemplateMapper.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanTemplateMapper.kt new file mode 100644 index 000000000..9db61bc81 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanTemplateMapper.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +@file:Suppress("TooManyFunctions") + +package org.mifos.lib.loan.mapper + +import org.mifos.lib.loan.core.model.AccountLinkingOptions +import org.mifos.lib.loan.core.model.AllowAttributeOverrides +import org.mifos.lib.loan.core.model.FundOptions +import org.mifos.lib.loan.core.model.LoanCollateralOptions +import org.mifos.lib.loan.core.model.LoanOfficerOptions +import org.mifos.lib.loan.core.model.LoanPurposeOptions +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.Product +import org.mifos.lib.loan.core.model.ProductOptions +import org.mifos.lib.loan.core.model.Timeline +import org.mifos.lib.loan.network.model.AccountLinkingOptionsResponseDto +import org.mifos.lib.loan.network.model.AllowAttributesOverridesResponseDto +import org.mifos.lib.loan.network.model.FundOptionsResponseDto +import org.mifos.lib.loan.network.model.LoanCollateralOptionsResponseDto +import org.mifos.lib.loan.network.model.LoanOfficerOptionsResponseDto +import org.mifos.lib.loan.network.model.LoanProductOptionsResponseDto +import org.mifos.lib.loan.network.model.LoanProductResponseDto +import org.mifos.lib.loan.network.model.LoanPurposeOptionsResponseDto +import org.mifos.lib.loan.network.model.LoanTemplateResponseDto +import org.mifos.lib.loan.network.model.LoanTimelineResponseDto + +fun LoanTemplateResponseDto.toModel(): LoanTemplate = + LoanTemplate( + clientId = clientId, + clientAccountNo = clientAccountNo, + clientName = clientName, + clientOfficeId = clientOfficeId, + loanProductName = loanProductName, + loanProductLinkedToFloatingRate = loanProductLinkedToFloatingRate, + fundId = fundId, + fundName = fundName, + currency = currency?.toModel(), + principal = principal, + approvedPrincipal = approvedPrincipal, + proposedPrincipal = proposedPrincipal, + termFrequency = termFrequency, + termPeriodFrequencyType = termPeriodFrequencyType?.toTermPeriodFrequencyType(), + numberOfRepayments = numberOfRepayments, + repaymentEvery = repaymentEvery, + repaymentFrequencyType = repaymentFrequencyType?.toRepaymentFrequencyType(), + interestRatePerPeriod = interestRatePerPeriod, + interestRateFrequencyType = interestRateFrequencyType?.toInterestRateFrequencyType(), + annualInterestRate = annualInterestRate, + floatingInterestRate = floatingInterestRate, + amortizationType = amortizationType?.toAmortizationType(), + interestType = interestType?.toInterestType(), + interestCalculationPeriodType = + interestCalculationPeriodType?.toInterestCalculationPeriodType(), + allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation, + transactionProcessingStrategyId = transactionProcessingStrategyId, + transactionProcessingStrategyCode = transactionProcessingStrategyCode, + graceOnArrearsAgeing = graceOnArrearsAgeing, + timeline = timeline?.toModel(), + + productOptions = productOptions.map { it.toModel() }, + loanOfficerOptions = loanOfficerOptions.map { it.toModel() }, + loanPurposeOptions = loanPurposeOptions.map { it.toModel() }, + fundOptions = fundOptions.map { it.toModel() }, + + termFrequencyTypeOptions = termFrequencyTypeOptions.map { it.toTermFrequencyTypeOptions() }, + + repaymentFrequencyTypeOptions = + repaymentFrequencyTypeOptions.map { it.toRepaymentFrequencyTypeOptions() }, + + repaymentFrequencyNthDayTypeOptions = + repaymentFrequencyNthDayTypeOptions.map { + it.toRepaymentFrequencyNthDayTypeOptions() + }, + + repaymentFrequencyDaysOfWeekTypeOptions = + repaymentFrequencyDaysOfWeekTypeOptions.map { + it.toRepaymentFrequencyDaysOfWeekTypeOptions() + }, + + interestRateFrequencyTypeOptions = + interestRateFrequencyTypeOptions.map { it.toInterestRateFrequencyTypeOptions() }, + + amortizationTypeOptions = amortizationTypeOptions.map { it.toAmortizationTypeOptions() }, + + interestTypeOptions = interestTypeOptions.map { it.toInterestTypeOptions() }, + + interestCalculationPeriodTypeOptions = + interestCalculationPeriodTypeOptions.map { it.toInterestCalculationPeriodType() }, + + transactionProcessingStrategyOptions = + transactionProcessingStrategyOptions.map { it.toTransactionProcessingStrategyOptions() }, + + chargeOptions = chargeOptions.map { it.toModel() }, + loanCollateralOptions = loanCollateralOptions.map { it.toModel() }, + + multiDisburseLoan = multiDisburseLoan, + canDefineInstallmentAmount = canDefineInstallmentAmount, + canDisburse = canDisburse, + product = product?.toModel(), + + daysInMonthType = daysInMonthType?.toDaysInMonthType(), + daysInYearType = daysInYearType?.toDaysInYearType(), + + interestRecalculationEnabled = interestRecalculationEnabled, + valiableInstallmentsAllowed = valiableInstallmentsAllowed, + minimumGap = minimumGap, + maximumGap = maximumGap, + + accountLinkingOptions = accountLinkingOptions.map { it.toModel() }, + ) + +fun LoanTimelineResponseDto.toModel(): Timeline = + Timeline( + expectedDisbursementDate = expectedDisbursementDate, + ) + +fun LoanProductOptionsResponseDto.toModel(): ProductOptions = + ProductOptions( + id = id, + name = name, + includeInBorrowerCycle = includeInBorrowerCycle, + useBorrowerCycle = useBorrowerCycle, + linkedToFloatingInterestRates = linkedToFloatingInterestRates, + floatingInterestRateCalculationAllowed = floatingInterestRateCalculationAllowed, + allowvaliableInstallments = allowvaliableInstallments, + interestRecalculationEnabled = interestRecalculationEnabled, + canDefineInstallmentAmount = canDefineInstallmentAmount, + holdGuaranteeFunds = holdGuaranteeFunds, + accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion, + ) + +fun LoanOfficerOptionsResponseDto.toModel(): LoanOfficerOptions = + LoanOfficerOptions( + id = id, + firstname = firstname, + lastname = lastname, + displayName = displayName, + mobileNo = mobileNo, + officeId = officeId, + officeName = officeName, + loanOfficer = loanOfficer, + active = active, + joiningDate = joiningDate, + ) + +fun LoanPurposeOptionsResponseDto.toModel(): LoanPurposeOptions = + LoanPurposeOptions( + id = id, + name = name, + position = position, + description = description, + active = active, + ) + +fun FundOptionsResponseDto.toModel(): FundOptions = + FundOptions( + id = id, + name = name, + ) + +fun LoanCollateralOptionsResponseDto.toModel(): LoanCollateralOptions = + LoanCollateralOptions( + id = id, + name = name, + position = position, + description = description, + active = active, + ) + +fun LoanProductResponseDto.toModel(): Product = + Product( + id = id, + name = name, + shortName = shortName, + fundId = fundId, + fundName = fundName, + includeInBorrowerCycle = includeInBorrowerCycle, + useBorrowerCycle = useBorrowerCycle, + startDate = startDate, + status = status, + currency = currency?.toModel(), + principal = principal, + minPrincipal = minPrincipal, + maxPrincipal = maxPrincipal, + numberOfRepayments = numberOfRepayments, + minNumberOfRepayments = minNumberOfRepayments, + maxNumberOfRepayments = maxNumberOfRepayments, + repaymentEvery = repaymentEvery, + repaymentFrequencyType = repaymentFrequencyType?.toRepaymentFrequencyType(), + interestRatePerPeriod = interestRatePerPeriod, + minInterestRatePerPeriod = minInterestRatePerPeriod, + maxInterestRatePerPeriod = maxInterestRatePerPeriod, + interestRateFrequencyType = interestRateFrequencyType?.toInterestRateFrequencyType(), + annualInterestRate = annualInterestRate, + linkedToFloatingInterestRates = linkedToFloatingInterestRates, + floatingInterestRateCalculationAllowed = floatingInterestRateCalculationAllowed, + allowvaliableInstallments = allowvaliableInstallments, + minimumGap = minimumGap, + maximumGap = maximumGap, + amortizationType = amortizationType.toAmortizationType(), + interestType = interestType.toInterestType(), + interestCalculationPeriodType = + interestCalculationPeriodType?.toInterestCalculationPeriodType(), + allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation, + transactionProcessingStrategyId = transactionProcessingStrategyId, + transactionProcessingStrategyName = transactionProcessingStrategyName, + graceOnArrearsAgeing = graceOnArrearsAgeing, + overdueDaysForNPA = overdueDaysForNPA, + daysInMonthType = daysInMonthType?.toDaysInMonthType(), + daysInYearType = daysInYearType.toDaysInYearType(), + interestRecalculationEnabled = interestRecalculationEnabled, + interestRecalculationData = interestRecalculationData?.toModel(), + canDefineInstallmentAmount = canDefineInstallmentAmount, + accountingRule = accountingRule?.toAccountingRule(), + multiDisburseLoan = multiDisburseLoan, + maxTrancheCount = maxTrancheCount, + principalThresholdForLastInstallment = principalThresholdForLastInstallment, + holdGuaranteeFunds = holdGuaranteeFunds, + accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion, + allowAttributeOverrides = allowAttributeOverrides?.toModel(), + ) + +fun AccountLinkingOptionsResponseDto.toModel(): AccountLinkingOptions = + AccountLinkingOptions( + accountNo = accountNo, + clientId = clientId, + clientName = clientName, + currency = currency?.toModel(), + fieldOfficerId = fieldOfficerId, + id = id, + productId = productId, + productName = productName, + ) + +fun AllowAttributesOverridesResponseDto.toModel(): AllowAttributeOverrides = + AllowAttributeOverrides( + amortizationType = amortizationType, + interestType = interestType, + transactionProcessingStrategyId = transactionProcessingStrategyId, + interestCalculationPeriodType = interestCalculationPeriodType, + inArrearsTolerance = inArrearsTolerance, + repaymentEvery = repaymentEvery, + graceOnPrincipalAndInterestPayment = graceOnPrincipalAndInterestPayment, + graceOnArrearsAgeing = graceOnArrearsAgeing, + ) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanTypeMapper.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanTypeMapper.kt new file mode 100644 index 000000000..08281f86c --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/mapper/LoanTypeMapper.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +@file:Suppress("TooManyFunctions") + +package org.mifos.lib.loan.mapper + +import org.mifos.lib.loan.core.model.AccountingRule +import org.mifos.lib.loan.core.model.AmortizationType +import org.mifos.lib.loan.core.model.AmortizationTypeOptions +import org.mifos.lib.loan.core.model.ChargeAppliesTo +import org.mifos.lib.loan.core.model.ChargeCalculationType +import org.mifos.lib.loan.core.model.ChargePaymentMode +import org.mifos.lib.loan.core.model.ChargeTimeType +import org.mifos.lib.loan.core.model.DaysInMonthType +import org.mifos.lib.loan.core.model.DaysInYearType +import org.mifos.lib.loan.core.model.EntityType +import org.mifos.lib.loan.core.model.Frequency +import org.mifos.lib.loan.core.model.InterestCalculationPeriodType +import org.mifos.lib.loan.core.model.InterestRateFrequencyType +import org.mifos.lib.loan.core.model.InterestRateFrequencyTypeOptions +import org.mifos.lib.loan.core.model.InterestRecalculationCompoundingType +import org.mifos.lib.loan.core.model.InterestType +import org.mifos.lib.loan.core.model.InterestTypeOptions +import org.mifos.lib.loan.core.model.RecalculationCompoundingFrequencyType +import org.mifos.lib.loan.core.model.RecalculationRestFrequencyType +import org.mifos.lib.loan.core.model.RepaymentFrequencyDaysOfWeekTypeOptions +import org.mifos.lib.loan.core.model.RepaymentFrequencyNthDayTypeOptions +import org.mifos.lib.loan.core.model.RepaymentFrequencyType +import org.mifos.lib.loan.core.model.RepaymentFrequencyTypeOptions +import org.mifos.lib.loan.core.model.RepeatsOnNthDayOfMonth +import org.mifos.lib.loan.core.model.RescheduleStrategyType +import org.mifos.lib.loan.core.model.TermFrequencyTypeOptions +import org.mifos.lib.loan.core.model.TermPeriodFrequencyType +import org.mifos.lib.loan.core.model.TransactionProcessingStrategyOptions +import org.mifos.lib.loan.core.model.Type +import org.mifos.lib.loan.network.model.TypeResponseDto + +fun TypeResponseDto.toTermPeriodFrequencyType() = TermPeriodFrequencyType(id, code, value) + +fun TypeResponseDto.toRepaymentFrequencyType() = RepaymentFrequencyType(id, code, value) + +fun TypeResponseDto.toInterestRateFrequencyType() = InterestRateFrequencyType(id, code, value) + +fun TypeResponseDto.toAmortizationType() = AmortizationType(id, code, value) + +fun TypeResponseDto.toInterestType() = InterestType(id, code, value) + +fun TypeResponseDto.toInterestCalculationPeriodType() = + InterestCalculationPeriodType(id, code, value) + +fun TypeResponseDto.toDaysInMonthType() = DaysInMonthType(id, code, value) + +fun TypeResponseDto.toDaysInYearType() = DaysInYearType(id, code, value) + +fun TypeResponseDto.toEntityType() = EntityType(id, code, value) + +fun TypeResponseDto.toType() = Type(id, code, value) + +fun TypeResponseDto.toFrequency() = Frequency(id, code, value) + +fun TypeResponseDto.toRepeatsOnNthDayOfMonth() = RepeatsOnNthDayOfMonth(id, code, value) + +fun TypeResponseDto.toInterestRecalculationCompoundingType() = + InterestRecalculationCompoundingType(id, code, value) + +fun TypeResponseDto.toRescheduleStrategyType() = RescheduleStrategyType(id, code, value) + +fun TypeResponseDto.toRecalculationRestFrequencyType() = + RecalculationRestFrequencyType(id, code, value) + +fun TypeResponseDto.toRecalculationCompoundingFrequencyType() = + RecalculationCompoundingFrequencyType(id, code, value) + +fun TypeResponseDto.toAccountingRule() = AccountingRule(id, code, value) + +fun TypeResponseDto.toChargeAppliesTo() = ChargeAppliesTo(id, code, value) + +fun TypeResponseDto.toChargePaymentMode() = ChargePaymentMode(id, code, value) + +fun TypeResponseDto.toChargeTimeType() = ChargeTimeType(id = id ?: 0, code = code, value = value) + +fun TypeResponseDto.toChargeCalculationType() = + ChargeCalculationType(id = id ?: 0, code = code, value = value) + +fun TypeResponseDto.toTermFrequencyTypeOptions() = + TermFrequencyTypeOptions(id, code, value) + +fun TypeResponseDto.toRepaymentFrequencyTypeOptions() = + RepaymentFrequencyTypeOptions(id, code, value) + +fun TypeResponseDto.toRepaymentFrequencyNthDayTypeOptions() = + RepaymentFrequencyNthDayTypeOptions(id, code, value) + +fun TypeResponseDto.toRepaymentFrequencyDaysOfWeekTypeOptions() = + RepaymentFrequencyDaysOfWeekTypeOptions(id, code, value) + +fun TypeResponseDto.toInterestRateFrequencyTypeOptions() = + InterestRateFrequencyTypeOptions(id, code, value) + +fun TypeResponseDto.toAmortizationTypeOptions() = + AmortizationTypeOptions(id, code, value) + +fun TypeResponseDto.toInterestTypeOptions() = + InterestTypeOptions(id, code, value) + +fun TypeResponseDto.toTransactionProcessingStrategyOptions() = + TransactionProcessingStrategyOptions(id = id, code = code, name = value) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/navigation/LoanApplicationNavGraph.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/navigation/LoanApplicationNavGraph.kt new file mode 100644 index 000000000..ae32aa11b --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/navigation/LoanApplicationNavGraph.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import org.mifos.lib.loan.ui.confirmDetails.confirmDetailsScreen +import org.mifos.lib.loan.ui.confirmDetails.navigateToConfirmDetails +import org.mifos.lib.loan.ui.loanApply.loanApplyScreen +import org.mifos.lib.loan.ui.loanApply.navigateToLoanApply +import org.mifos.lib.loan.ui.loanProductDetails.loanProductDetailsScreen +import org.mifos.lib.loan.ui.loanProductDetails.navigateToLoanProductDetails +import org.mifos.lib.loan.ui.providerWebView.loanProviderWebViewScreen +import org.mifos.lib.loan.ui.providerWebView.navigateToLoanProviderWebView +import org.mifos.lib.loan.ui.providerWebView.urlForLoanProvider +import org.mifos.lib.loan.ui.selectLoanProvider.navigateToSelectLoanProvider +import org.mifos.lib.loan.ui.selectLoanProvider.selectLoanProviderScreen +import org.mifos.lib.loan.ui.selectLoanType.selectLoanTypeScreen +import org.mifos.lib.loan.ui.uploadDocs.navigateToUploadDocs +import org.mifos.lib.loan.ui.uploadDocs.uploadDocsScreen + +/** + * Entry point into the loan-application wizard — this app's single flat `NavHost` convention + * (see e.g. `feature/transfer-intrabank` and `feature/send-money`, which have no nested + * `navigation{ ... }` wrapper) has no notion of a nested sub-graph, so this simply navigates + * to the first step, [org.mifos.lib.loan.ui.selectLoanProvider.SelectLoanProviderRoute] — the new + * "pick a loan provider (dummy for now)" step the wizard starts with, ahead of + * [org.mifos.lib.loan.ui.selectLoanType.SelectLoanTypeRoute]. + * + * @param clientId The id of the client applying for a loan. + */ +fun NavController.navigateToLoanApplicationGraph(clientId: Long, navOptions: NavOptions? = null) { + navigateToSelectLoanProvider(clientId, navOptions) +} + +/** + * Registers the loan-application wizard's destinations — Select Loan Provider, Loan Provider + * Website, Select Loan Type, Loan Product Details, Loan Apply, Upload Docs, and Confirm Details — + * and wires their forward/back navigation together internally, mirroring how this app registers + * destinations flatly inside a single shared `NavHost` (no nested `navigation{ ... }` + * sub-graph is used anywhere in this codebase). + * + * Only the wizard's boundary callbacks are exposed to the caller: + * - [navigateBack] exits the wizard entirely from its first screen (Select Loan Provider). + * - [navigateForPasscodeVerification] and [onSubmitSuccess] are Confirm Details' own boundary + * callbacks — see [org.mifos.lib.loan.ui.confirmDetails.confirmDetailsScreen] for their exact + * contract. [navigateForPasscodeVerification] is expected to eventually be bound to + * `navController.navigateToInternalMifosPasscodeScreen(verificationKey)` by the caller. + * + * Picking a provider on Select Loan Provider currently opens Loan Provider Website (rendering + * that provider's, for now identical, test URL — see + * [org.mifos.lib.loan.ui.providerWebView.urlForLoanProvider]) instead of continuing into Select + * Loan Type onwards; the rest of the wizard (Select Loan Type -> Loan Product Details -> Loan + * Apply -> Upload Docs -> Confirm Details, and each screen's own "back") stays registered and + * wired exactly as before, via each screen's own `navigateToX` extension, using [navController]. + * + * @param navController The app's shared nav controller, used to drive the internal forward/back + * transitions between this wizard's own screens. + * @param navigateBack Callback to leave the loan-application wizard entirely from its first step. + * @param navigateForPasscodeVerification `(verificationKey) -> Unit` forwarded, unchanged, to + * [org.mifos.lib.loan.ui.confirmDetails.confirmDetailsScreen]. + * @param onSubmitSuccess Callback forwarded, unchanged, to + * [org.mifos.lib.loan.ui.confirmDetails.confirmDetailsScreen], invoked once the loan application has + * been submitted successfully and the user has acknowledged the success dialog. + */ +fun NavGraphBuilder.loanApplicationGraph( + navController: NavController, + navigateBack: () -> Unit, + navigateForPasscodeVerification: (String) -> Unit, + onSubmitSuccess: () -> Unit, +) { + selectLoanProviderScreen( + navigateBack = navigateBack, + navigateToLoanProviderWebView = { clientId, providerId, providerUrl -> + navController.navigateToLoanProviderWebView( + clientId = clientId, + providerId = providerId, + url = providerUrl, + ) + }, + ) + + loanProviderWebViewScreen( + navigateBack = navController::popBackStack, + ) + + selectLoanTypeScreen( + navigateBack = navController::popBackStack, + navigateToLoanProductDetails = { clientId, productId, providerId -> + navController.navigateToLoanProductDetails(clientId, productId, providerId) + }, + ) + + loanProductDetailsScreen( + navigateBack = navController::popBackStack, + navigateToLoanApply = { clientId, productId, providerId -> + navController.navigateToLoanApply(clientId, productId, providerId) + }, + ) + + loanApplyScreen( + navigateBack = navController::popBackStack, + navigateToUploadDocs = { + clientId, + productId, + applicantName, + loanProductName, + loanPurpose, + disbursementDate, + principalAmount, + providerId, + -> + navController.navigateToUploadDocs( + clientId = clientId, + productId = productId, + applicantName = applicantName, + loanProductName = loanProductName, + loanPurpose = loanPurpose, + disbursementDate = disbursementDate, + principalAmount = principalAmount, + providerId = providerId, + ) + }, + ) + + uploadDocsScreen( + navigateBack = navController::popBackStack, + navigateToConfirmDetails = { + clientId, + productId, + applicantName, + loanProductName, + loanPurpose, + disbursementDate, + principalAmount, + providerId, + -> + navController.navigateToConfirmDetails( + clientId = clientId, + productId = productId, + applicantName = applicantName, + loanProductName = loanProductName, + loanPurpose = loanPurpose, + disbursementDate = disbursementDate, + principalAmount = principalAmount, + providerId = providerId, + ) + }, + ) + + confirmDetailsScreen( + navigateBack = navController::popBackStack, + navigateForPasscodeVerification = navigateForPasscodeVerification, + onSubmitSuccess = onSubmitSuccess, + ) +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/ApiEndPoints.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/ApiEndPoints.kt new file mode 100644 index 000000000..ce7ddd01f --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/ApiEndPoints.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network + +internal object ApiEndPoints { + const val LOANS = "loans" +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/LoansService.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/LoansService.kt new file mode 100644 index 000000000..be196bfa7 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/LoansService.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network + +import de.jensklingenberg.ktorfit.http.Body +import de.jensklingenberg.ktorfit.http.GET +import de.jensklingenberg.ktorfit.http.POST +import de.jensklingenberg.ktorfit.http.Query +import kotlinx.coroutines.flow.Flow +import org.mifos.lib.loan.network.model.LoanAccountApplicationPayloadDto +import org.mifos.lib.loan.network.model.LoanTemplateResponseDto + +interface LoansService { + @GET(ApiEndPoints.LOANS + "/template?templateType=individual") + fun getLoanTemplate(@Query("clientId") clientId: Long?): Flow + + @GET(ApiEndPoints.LOANS + "/template?templateType=individual") + fun getLoanTemplateByProduct( + @Query("clientId") clientId: Long?, + @Query("productId") productId: Long?, + ): Flow + + @POST(ApiEndPoints.LOANS) + suspend fun createLoansAccount(@Body payload: LoanAccountApplicationPayloadDto) +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/di/LoansNetworkModule.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/di/LoansNetworkModule.kt new file mode 100644 index 000000000..7dd5b297a --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/di/LoansNetworkModule.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.di + +import de.jensklingenberg.ktorfit.Ktorfit +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.mifos.corebase.network.DynamicBaseUrlPlugin +import org.mifos.corebase.network.DynamicLoggableHosts +import org.mifos.corebase.network.MultiUrlConfigProvider +import org.mifos.corebase.network.httpClient +import org.mifos.corebase.network.setupDefaultHttpClient +import org.mifos.lib.loan.network.LoansService +import org.mifos.lib.loan.network.createLoansService +import org.mifospay.core.datastore.UserPreferencesRepository +import org.mifospay.core.network.config.InstanceConfigManager +import org.mifospay.core.network.utils.FlowConverterFactory +import org.mifospay.core.network.utils.KtorInterceptor + +/** + * Koin module providing the `libs/mifos-loans` module's own, self-contained Ktorfit/HttpClient + * engine and [LoansService]. This mirrors the pattern used for the app's SelfClient in + * `core/network`'s `NetworkModule`, but is owned entirely by this library so it can remain + * independently publishable. + */ +val LoansClient = named("LoansClient") + +val LoansNetworkModule = module { + single(qualifier = LoansClient) { + val preferencesRepository = get() + val configManager = get() + Ktorfit.Builder() + .httpClient( + client = httpClient( + config = setupDefaultHttpClient( + // Use placeholder - actual URL is set dynamically by DynamicBaseUrlPlugin + baseUrl = "https://placeholder.local/", + // Dynamic loggable hosts - reads current endpoints from configManager + loggableHosts = DynamicLoggableHosts(configManager), + ), + ).config { + install(DynamicBaseUrlPlugin) { + multiConfigProvider = configManager + urlType = MultiUrlConfigProvider.UrlType.SELF_SERVICE + } + install(KtorInterceptor) { + getToken = { preferencesRepository.authToken } + this.configManager = configManager + } + }, + ) + .converterFactories(FlowConverterFactory()) + .build() + } + + single { get(LoansClient).createLoansService() } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/AccountLinkingOptionsResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/AccountLinkingOptionsResponseDto.kt new file mode 100644 index 000000000..d525f0898 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/AccountLinkingOptionsResponseDto.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class AccountLinkingOptionsResponseDto( + + val accountNo: String? = null, + + val clientId: Int? = null, + + val clientName: String? = null, + + val currency: CurrencyResponseDto? = null, + + val fieldOfficerId: Int? = null, + + val id: Int? = null, + + val productId: Int? = null, + + val productName: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/AllowAttributesOverridesResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/AllowAttributesOverridesResponseDto.kt new file mode 100644 index 000000000..4407f18ef --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/AllowAttributesOverridesResponseDto.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class AllowAttributesOverridesResponseDto( + + val amortizationType: Boolean? = null, + + val interestType: Boolean? = null, + + val transactionProcessingStrategyId: Boolean? = null, + + val interestCalculationPeriodType: Boolean? = null, + + val inArrearsTolerance: Boolean? = null, + + val repaymentEvery: Boolean? = null, + + val graceOnPrincipalAndInterestPayment: Boolean? = null, + + val graceOnArrearsAgeing: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/CalendarDataResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/CalendarDataResponseDto.kt new file mode 100644 index 000000000..94fc9375c --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/CalendarDataResponseDto.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class CalendarDataResponseDto( + val id: Int? = null, + + val calendarInstanceId: Int? = null, + + val entityId: Int? = null, + + val entityType: TypeResponseDto, + + val title: String? = null, + + val startDate: List = emptyList(), + + val endDate: List = emptyList(), + + val duration: Double? = null, + + val type: TypeResponseDto, + + val repeating: Boolean? = null, + + val recurrence: String? = null, + + val frequency: TypeResponseDto, + + val interval: Double? = null, + + val repeatsOnNthDayOfMonth: TypeResponseDto, + + val firstReminder: Int? = null, + + val secondReminder: Int? = null, + + val humanReadable: String? = null, + + val createdDate: String? = null, + + val lastUpdatedDate: String? = null, + + val createdByUserId: Int? = null, + + val createdByUsername: String? = null, + + val lastUpdatedByUserId: Int? = null, + + val lastUpdatedByUsername: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/ChargeOptionsResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/ChargeOptionsResponseDto.kt new file mode 100644 index 000000000..dcb920659 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/ChargeOptionsResponseDto.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class ChargeOptionsResponseDto( + + val id: Int? = null, + + val name: String? = null, + + val active: Boolean? = null, + + val penalty: Boolean? = null, + + val currency: CurrencyResponseDto? = null, + + val amount: Double? = null, + + val chargeTimeType: TypeResponseDto? = null, + + val chargeAppliesTo: TypeResponseDto? = null, + + val chargeCalculationType: TypeResponseDto? = null, + + val chargePaymentMode: TypeResponseDto? = null, + + val taxGroup: TaxGroupResponseDto? = null, + +) + +@Serializable +data class TaxGroupResponseDto( + val id: Int? = null, + + val name: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/CurrencyResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/CurrencyResponseDto.kt new file mode 100644 index 000000000..ebcefab17 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/CurrencyResponseDto.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class CurrencyResponseDto( + val code: String? = null, + val name: String? = null, + val decimalPlaces: Int = 0, + val inMultiplesOf: Double = 0.0, + val displaySymbol: String? = null, + val nameCode: String? = null, + val displayLabel: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/FundOptionsResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/FundOptionsResponseDto.kt new file mode 100644 index 000000000..b7df321db --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/FundOptionsResponseDto.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class FundOptionsResponseDto( + + val id: Int? = null, + + val name: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/InterestRecalculationDataResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/InterestRecalculationDataResponseDto.kt new file mode 100644 index 000000000..f451091a6 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/InterestRecalculationDataResponseDto.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class InterestRecalculationDataResponseDto( + val id: Int? = null, + + val loanId: Int? = null, + + val interestRecalculationCompoundingType: TypeResponseDto? = null, + + val rescheduleStrategyType: TypeResponseDto? = null, + + val calendarData: CalendarDataResponseDto, + + val recalculationRestFrequencyType: TypeResponseDto? = null, + + val recalculationRestFrequencyInterval: Double? = null, + + val recalculationCompoundingFrequencyType: TypeResponseDto? = null, + + @SerialName("isCompoundingToBePostedAsTransaction") + val compoundingToBePostedAsTransaction: Boolean? = null, + + val allowCompoundingOnEod: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanAccountApplicationPayloadDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanAccountApplicationPayloadDto.kt new file mode 100644 index 000000000..ab1bee251 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanAccountApplicationPayloadDto.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class LoanAccountApplicationPayloadDto( + + val clientId: Int? = null, + + val productId: Int? = null, + + val productName: String? = null, + + val principal: Double? = null, + + val loanTermFrequency: Int? = null, + + val loanTermFrequencyType: Int? = null, + + val loanType: String? = null, + + val numberOfRepayments: Int? = null, + + val repaymentEvery: Int? = null, + + val repaymentFrequencyType: Int? = null, + + val interestRatePerPeriod: Double? = null, + + val amortizationType: Int? = null, + + val interestType: Int? = null, + + val interestCalculationPeriodType: Int? = null, + + val transactionProcessingStrategyId: Int? = null, + + val transactionProcessingStrategyCode: String? = null, + + val expectedDisbursementDate: String? = null, + + val submittedOnDate: String? = null, + + val linkAccountId: Int? = null, + + val loanPurposeId: Int? = null, + + val loanPurpose: String? = null, + + val maxOutstandingLoanBalance: Double? = null, + + val currency: String? = null, + + val dateFormat: String? = null, + + val locale: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanCollateralOptionsResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanCollateralOptionsResponseDto.kt new file mode 100644 index 000000000..1aeb7756f --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanCollateralOptionsResponseDto.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class LoanCollateralOptionsResponseDto( + + val id: Int? = null, + + val name: String? = null, + + val position: Int? = null, + + val description: String? = null, + + val active: Boolean? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanOfficerOptionsResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanOfficerOptionsResponseDto.kt new file mode 100644 index 000000000..4a49ad1b6 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanOfficerOptionsResponseDto.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanOfficerOptionsResponseDto( + + val id: Int? = null, + + val firstname: String? = null, + + val lastname: String? = null, + + val displayName: String? = null, + + val mobileNo: String? = null, + + val officeId: Int? = null, + + val officeName: String? = null, + + @SerialName("isLoanOfficer") + val loanOfficer: Boolean? = null, + + @SerialName("isActive") + val active: Boolean? = null, + + val joiningDate: List? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanProductOptionsResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanProductOptionsResponseDto.kt new file mode 100644 index 000000000..19d13d792 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanProductOptionsResponseDto.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanProductOptionsResponseDto( + + val id: Int? = null, + + val name: String? = null, + + val includeInBorrowerCycle: Boolean? = null, + + val useBorrowerCycle: Boolean? = null, + + @SerialName("isLinkedToFloatingInterestRates") + val linkedToFloatingInterestRates: Boolean? = null, + + @SerialName("isFloatingInterestRateCalculationAllowed") + val floatingInterestRateCalculationAllowed: Boolean? = null, + + val allowvaliableInstallments: Boolean? = null, + + @SerialName("isInterestRecalculationEnabled") + val interestRecalculationEnabled: Boolean? = null, + + val canDefineInstallmentAmount: Boolean? = null, + + val holdGuaranteeFunds: Boolean? = null, + + val accountMovesOutOfNPAOnlyOnArrearsCompletion: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanProductResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanProductResponseDto.kt new file mode 100644 index 000000000..be9b6d2b9 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanProductResponseDto.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanProductResponseDto( + + val id: Int? = null, + + val name: String? = null, + + val shortName: String? = null, + + val fundId: Int? = null, + + val fundName: String? = null, + + val includeInBorrowerCycle: Boolean? = null, + + val useBorrowerCycle: Boolean? = null, + + val startDate: List? = null, + + val status: String? = null, + + val currency: CurrencyResponseDto? = null, + + val principal: Double? = null, + + val minPrincipal: Double? = null, + + val maxPrincipal: Double? = null, + + val numberOfRepayments: Int? = null, + + val minNumberOfRepayments: Int? = null, + + val maxNumberOfRepayments: Int? = null, + + val repaymentEvery: Int? = null, + + val repaymentFrequencyType: TypeResponseDto? = null, + + val interestRatePerPeriod: Double? = null, + + val minInterestRatePerPeriod: Double? = null, + + val maxInterestRatePerPeriod: Double? = null, + + val interestRateFrequencyType: TypeResponseDto? = null, + + val annualInterestRate: Double? = null, + + @SerialName("isLinkedToFloatingInterestRates") + val linkedToFloatingInterestRates: Boolean? = null, + + @SerialName("isFloatingInterestRateCalculationAllowed") + val floatingInterestRateCalculationAllowed: Boolean? = null, + + val allowvaliableInstallments: Boolean? = null, + + val minimumGap: Double? = null, + + val maximumGap: Double? = null, + + val amortizationType: TypeResponseDto, + + val interestType: TypeResponseDto, + + val interestCalculationPeriodType: TypeResponseDto? = null, + + val allowPartialPeriodInterestCalculation: Boolean? = null, + + val transactionProcessingStrategyId: Int? = null, + + val transactionProcessingStrategyName: String? = null, + + val graceOnArrearsAgeing: Int? = null, + + val overdueDaysForNPA: Int? = null, + + val daysInMonthType: TypeResponseDto? = null, + + val daysInYearType: TypeResponseDto, + + @SerialName("isInterestRecalculationEnabled") + val interestRecalculationEnabled: Boolean? = null, + + val interestRecalculationData: InterestRecalculationDataResponseDto? = null, + + val canDefineInstallmentAmount: Boolean? = null, + + val accountingRule: TypeResponseDto? = null, + + val multiDisburseLoan: Boolean? = null, + + val maxTrancheCount: Int? = null, + + val principalThresholdForLastInstallment: Double? = null, + + val holdGuaranteeFunds: Boolean? = null, + + val accountMovesOutOfNPAOnlyOnArrearsCompletion: Boolean? = null, + + val allowAttributeOverrides: AllowAttributesOverridesResponseDto? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanPurposeOptionsResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanPurposeOptionsResponseDto.kt new file mode 100644 index 000000000..50cd925c4 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanPurposeOptionsResponseDto.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanPurposeOptionsResponseDto( + + val id: Int? = null, + + val name: String? = null, + + val position: Int? = null, + + val description: String? = null, + + @SerialName("isActive") + val active: Boolean? = null, + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanTemplateResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanTemplateResponseDto.kt new file mode 100644 index 000000000..2750b2d6c --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanTemplateResponseDto.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoanTemplateResponseDto( + + val clientId: Int? = null, + + val clientAccountNo: String? = null, + + val clientName: String? = null, + + val clientOfficeId: Int? = null, + + val loanProductName: String? = null, + + @SerialName("isLoanProductLinkedToFloatingRate") + val loanProductLinkedToFloatingRate: Boolean? = null, + + val fundId: Int? = null, + + val fundName: String? = null, + + val currency: CurrencyResponseDto? = null, + + val principal: Double? = null, + + val approvedPrincipal: Double? = null, + + val proposedPrincipal: Double? = null, + + val termFrequency: Int? = null, + + val termPeriodFrequencyType: TypeResponseDto? = null, + + val numberOfRepayments: Int? = null, + + val repaymentEvery: Int? = null, + + val repaymentFrequencyType: TypeResponseDto? = null, + + val interestRatePerPeriod: Double? = null, + + val interestRateFrequencyType: TypeResponseDto? = null, + + val annualInterestRate: Double? = null, + + @SerialName("isFloatingInterestRate") + val floatingInterestRate: Boolean? = null, + + val amortizationType: TypeResponseDto? = null, + + val interestType: TypeResponseDto? = null, + + val interestCalculationPeriodType: TypeResponseDto? = null, + + val allowPartialPeriodInterestCalculation: Boolean? = null, + + val transactionProcessingStrategyId: Int? = null, + + val transactionProcessingStrategyCode: String? = null, + + val graceOnArrearsAgeing: Int? = null, + + val timeline: LoanTimelineResponseDto? = null, + + val productOptions: List = emptyList(), + + val loanOfficerOptions: List = emptyList(), + + val loanPurposeOptions: List = emptyList(), + + val fundOptions: List = emptyList(), + + val termFrequencyTypeOptions: List = emptyList(), + + val repaymentFrequencyTypeOptions: List = emptyList(), + + val repaymentFrequencyNthDayTypeOptions: List = emptyList(), + + val repaymentFrequencyDaysOfWeekTypeOptions: List = emptyList(), + + val interestRateFrequencyTypeOptions: List = emptyList(), + + val amortizationTypeOptions: List = emptyList(), + + val interestTypeOptions: List = emptyList(), + + val interestCalculationPeriodTypeOptions: List = emptyList(), + + val transactionProcessingStrategyOptions: List = emptyList(), + + val chargeOptions: List = emptyList(), + + val loanCollateralOptions: List = emptyList(), + + val multiDisburseLoan: Boolean? = null, + + val canDefineInstallmentAmount: Boolean? = null, + + val canDisburse: Boolean? = null, + + val product: LoanProductResponseDto? = null, + + val daysInMonthType: TypeResponseDto? = null, + + val daysInYearType: TypeResponseDto? = null, + + @SerialName("isInterestRecalculationEnabled") + val interestRecalculationEnabled: Boolean? = null, + + @SerialName("isvaliableInstallmentsAllowed") + val valiableInstallmentsAllowed: Boolean? = null, + + val minimumGap: Int? = null, + + val maximumGap: Int? = null, + + val accountLinkingOptions: List = emptyList(), + +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanTimelineResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanTimelineResponseDto.kt new file mode 100644 index 000000000..f42bb5df9 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/LoanTimelineResponseDto.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class LoanTimelineResponseDto( + val expectedDisbursementDate: List, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/TypeResponseDto.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/TypeResponseDto.kt new file mode 100644 index 000000000..dc17d85d6 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/network/model/TypeResponseDto.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2024 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class TypeResponseDto( + val id: Int? = null, + val code: String? = null, + + val value: String? = null, +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/DummyLoanProvider.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/DummyLoanProvider.kt new file mode 100644 index 000000000..4ae05509a --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/DummyLoanProvider.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.providers + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import org.mifos.lib.loan.core.LoanProvider +import org.mifos.lib.loan.core.model.AmortizationType +import org.mifos.lib.loan.core.model.Currency +import org.mifos.lib.loan.core.model.DaysInYearType +import org.mifos.lib.loan.core.model.InterestCalculationPeriodType +import org.mifos.lib.loan.core.model.InterestType +import org.mifos.lib.loan.core.model.LoanPurposeOptions +import org.mifos.lib.loan.core.model.LoanState +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.LoansPayload +import org.mifos.lib.loan.core.model.Product +import org.mifos.lib.loan.core.model.ProductOptions +import org.mifospay.core.common.DataState + +/** + * Dummy, no-network implementation of [LoanProvider]. + * + * The product owner's requirement is to stand up a new "Select Loan Provider" step ahead of the + * existing (Fineract-backed) wizard, without wiring any real per-provider backend yet ("this does + * not have any apis for now let it be dummy for now"). Rather than let the rest of the wizard + * (Select Loan Type, Loan Product Details, Loan Apply, Confirm Details) fail against a real + * server, this class fabricates the same [LoanTemplate] shape the real + * [org.mifos.lib.loan.providers.FineractLoanProvider] would have returned, so the remaining + * screens keep working end-to-end with static data. + * + * A single shared instance of this class is currently bound in + * [org.mifos.lib.loan.providers.LoanProviderRegistry] under all four of `"hdfc"`, `"sbi"`, + * `"icici"`, `"axis"` — there is no real per-bank backend distinction yet. Once one exists, swap + * the corresponding registry entry for a dedicated adapter without touching the others. + */ +class DummyLoanProvider : LoanProvider { + + override fun getLoanTemplate(clientId: Long): Flow> = flow { + delay(DUMMY_DELAY_MS) + emit(DataState.Success(mockLoanTemplate)) + } + + override fun getLoanTemplateByProduct( + clientId: Long, + productId: Long, + ): Flow> = flow { + delay(DUMMY_DELAY_MS) + emit(DataState.Success(mockLoanTemplate)) + } + + override suspend fun submitLoanApplication( + loanState: LoanState, + payload: LoansPayload, + loanId: Long, + ): DataState { + delay(DUMMY_SUBMIT_DELAY_MS) + return DataState.Success("Loan application submitted successfully (dummy)") + } + + private companion object { + const val DUMMY_DELAY_MS = 300L + const val DUMMY_SUBMIT_DELAY_MS = 500L + } +} + +/** + * A shared fabricated currency used by [mockLoanTemplate] and [mockProduct]. + */ +private val mockCurrency = Currency( + code = "USD", + name = "US Dollar", + decimalPlaces = 2.0, + inMultiplesOf = 1, + displaySymbol = "$", + nameCode = "currency.USD", + displayLabel = "US Dollar ($)", +) + +/** + * A shared fabricated product used by [mockLoanTemplate], populated with the fields read by + * [org.mifos.lib.loan.ui.loanApply.LoanApplyViewModel] and + * [org.mifos.lib.loan.ui.loanProductDetails.LoanProductDetailsViewModel] (principal bounds, interest + * rate bounds, currency, name). + */ +private val mockProduct = Product( + id = 1, + name = "Personal Loan", + shortName = "PL", + currency = mockCurrency, + principal = 10_000.0, + minPrincipal = 1_000.0, + maxPrincipal = 50_000.0, + interestRatePerPeriod = 12.0, + minInterestRatePerPeriod = 8.0, + maxInterestRatePerPeriod = 24.0, + annualInterestRate = 12.0, + amortizationType = AmortizationType(id = 1, code = "equal.installments", value = "Equal installments"), + interestType = InterestType(id = 0, code = "declining.balance", value = "Declining Balance"), + interestCalculationPeriodType = InterestCalculationPeriodType( + id = 1, + code = "daily", + value = "Daily", + ), + daysInYearType = DaysInYearType(id = 1, code = "actual", value = "Actual"), +) + +/** + * The single fabricated [LoanTemplate] returned by both [DummyLoanProvider.getLoanTemplate] + * and [DummyLoanProvider.getLoanTemplateByProduct], populated with the fields actually read + * by the wizard's three consuming ViewModels: + * - [org.mifos.lib.loan.ui.selectLoanType.SelectLoanTypeViewModel] reads [productOptions]. + * - [org.mifos.lib.loan.ui.loanProductDetails.LoanProductDetailsViewModel] reads [product], + * [currency], and [loanProductName]. + * - [org.mifos.lib.loan.ui.loanApply.LoanApplyViewModel] reads [product], [currency], + * [loanPurposeOptions], and [loanProductName]. + * + * Everything else is left at its default (`null`/`emptyList()`). + */ +private val mockLoanTemplate = LoanTemplate( + loanProductName = "Personal Loan", + currency = mockCurrency, + principal = 10_000.0, + product = mockProduct, + annualInterestRate = 12.0, + productOptions = listOf( + ProductOptions(id = 1, name = "Personal Loan"), + ProductOptions(id = 2, name = "Business Loan"), + ProductOptions(id = 3, name = "Home Loan"), + ProductOptions(id = 4, name = "Education Loan"), + ), + loanPurposeOptions = listOf( + LoanPurposeOptions(id = 1, name = "Agriculture"), + LoanPurposeOptions(id = 2, name = "Business"), + LoanPurposeOptions(id = 3, name = "Education"), + LoanPurposeOptions(id = 4, name = "Housing"), + ), +) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/FineractLoanProvider.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/FineractLoanProvider.kt new file mode 100644 index 000000000..063bad452 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/FineractLoanProvider.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.providers + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import org.mifos.lib.loan.core.LoanProvider +import org.mifos.lib.loan.core.model.LoanState +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.LoansPayload +import org.mifos.lib.loan.mapper.toDto +import org.mifos.lib.loan.mapper.toModel +import org.mifos.lib.loan.network.LoansService +import org.mifospay.core.common.DataState +import org.mifospay.core.common.asDataStateFlow + +/** + * The real, Fineract-backed [LoanProvider] adapter. Registered in [org.mifos.lib.loan.providers.LoanProviderRegistry] + * under the `"fineract"` key for future activation once a real per-provider backend exists — it is + * not reachable from today's static 4-item Select Loan Provider list. + */ +class FineractLoanProvider( + private val loansService: LoansService, + private val ioDispatcher: CoroutineDispatcher, +) : LoanProvider { + + override fun getLoanTemplate(clientId: Long): Flow> { + return loansService.getLoanTemplate(clientId) + .map { it.toModel() } + .asDataStateFlow() + .flowOn(ioDispatcher) + } + + override fun getLoanTemplateByProduct( + clientId: Long, + productId: Long, + ): Flow> { + return loansService.getLoanTemplateByProduct(clientId, productId) + .map { it.toModel() } + .asDataStateFlow() + .flowOn(ioDispatcher) + } + + // Note: [loanState] and [loanId] are kept for interface symmetry with mifos-mobile's + // CREATE/UPDATE distinction, but this port only ever submits new loan applications, so + // an UPDATE path (updateLoanAccount) is intentionally not wired up here. + override suspend fun submitLoanApplication( + loanState: LoanState, + payload: LoansPayload, + loanId: Long, + ): DataState { + return try { + withContext(ioDispatcher) { + loansService.createLoansAccount(payload.toDto()) + } + DataState.Success("Loan application submitted successfully") + } catch (e: Exception) { + DataState.Error(e) + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/LoanProviderRegistry.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/LoanProviderRegistry.kt new file mode 100644 index 000000000..ce67cd10e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/providers/LoanProviderRegistry.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.providers + +import org.mifos.lib.loan.core.LoanProvider + +/** + * Resolves a [LoanProvider] adapter (Fineract or a third-party bank integration) by the + * `providerId` chosen on the Select Loan Provider screen (e.g. `"hdfc"`, `"sbi"`, `"icici"`, + * `"axis"`). + * + * There is no real per-bank backend distinction yet, so [org.mifos.lib.loan.di.LoanApplicationModule] + * currently maps all four ids to the same [DummyLoanProvider] instance in [providersById] — this + * class only owns the *resolution* mechanism, not the decision of which concrete adapter backs + * which id. Any entry in [providersById] can be swapped for a distinct adapter later without + * touching call sites that go through [resolve]. + * + * @param providersById Concrete [LoanProvider] adapters, keyed by provider id. + * @param defaultProvider Fallback used when [resolve] is called with an id absent from + * [providersById] (defensive — the Select Loan Provider screen only ever offers ids that are + * present in the map). + */ +class LoanProviderRegistry( + private val providersById: Map, + private val defaultProvider: LoanProvider, +) { + fun resolve(providerId: String): LoanProvider = providersById[providerId] ?: defaultProvider +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/repository/LoansRepository.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/repository/LoansRepository.kt new file mode 100644 index 000000000..e02f75fd9 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/repository/LoansRepository.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.repository + +import kotlinx.coroutines.flow.Flow +import org.mifos.lib.loan.core.model.LoanState +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.LoansPayload +import org.mifospay.core.common.DataState + +/** + * Facade injected by the wizard's data-consuming ViewModels ([org.mifos.lib.loan.ui.selectLoanType.SelectLoanTypeViewModel], + * [org.mifos.lib.loan.ui.loanProductDetails.LoanProductDetailsViewModel], + * [org.mifos.lib.loan.ui.loanApply.LoanApplyViewModel], [org.mifos.lib.loan.ui.confirmDetails.ConfirmDetailsViewModel]) + * instead of [org.mifos.lib.loan.core.LoanProvider] directly. + * + * Each call takes the `providerId` chosen on the Select Loan Provider screen and resolves the + * concrete [org.mifos.lib.loan.core.LoanProvider] adapter to delegate to via + * [org.mifos.lib.loan.providers.LoanProviderRegistry], so the same wizard behaves differently per + * provider once distinct adapters exist for each. + */ +interface LoansRepository { + fun getLoanTemplate(providerId: String, clientId: Long): Flow> + + fun getLoanTemplateByProduct( + providerId: String, + clientId: Long, + productId: Long, + ): Flow> + + suspend fun submitLoanApplication( + providerId: String, + loanState: LoanState, + payload: LoansPayload, + loanId: Long, + ): DataState +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/repository/LoansRepositoryImpl.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/repository/LoansRepositoryImpl.kt new file mode 100644 index 000000000..6d61700ae --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/repository/LoansRepositoryImpl.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.repository + +import kotlinx.coroutines.flow.Flow +import org.mifos.lib.loan.core.model.LoanState +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.LoansPayload +import org.mifos.lib.loan.providers.LoanProviderRegistry +import org.mifospay.core.common.DataState + +/** + * Default [LoansRepository] implementation — resolves the concrete [org.mifos.lib.loan.core.LoanProvider] + * for the given `providerId` via [registry] and delegates every call to it. + */ +class LoansRepositoryImpl( + private val registry: LoanProviderRegistry, +) : LoansRepository { + + override fun getLoanTemplate(providerId: String, clientId: Long): Flow> = + registry.resolve(providerId).getLoanTemplate(clientId) + + override fun getLoanTemplateByProduct( + providerId: String, + clientId: Long, + productId: Long, + ): Flow> = + registry.resolve(providerId).getLoanTemplateByProduct(clientId, productId) + + override suspend fun submitLoanApplication( + providerId: String, + loanState: LoanState, + payload: LoansPayload, + loanId: Long, + ): DataState = + registry.resolve(providerId).submitLoanApplication(loanState, payload, loanId) +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsRoute.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsRoute.kt new file mode 100644 index 000000000..f95f1bbc8 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsRoute.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.confirmDetails + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import kotlinx.serialization.Serializable +import org.mifospay.core.ui.composableWithSlideTransitions +import template.core.base.ui.composableWithSlideTransitions + +/** + * Type-safe navigation route for the final "Confirm Details" step of the loan application + * wizard. Carries the same application form data forwarded, unchanged, all the way from + * [org.mifos.lib.loan.ui.loanApply] through [org.mifos.lib.loan.ui.uploadDocs]. + * + * @param clientId The id of the client applying for a loan. + * @param productId The id of the loan product being applied for. + * @param applicantName The applicant's display name. + * @param loanProductName The selected loan product's display name. + * @param loanPurpose The stated purpose for the loan. + * @param disbursementDate The scheduled date for funds disbursement. + * @param principalAmount The requested loan amount. + * @param providerId The id of the loan provider chosen on the Select Loan Provider screen. + */ +@Serializable +data class ConfirmDetailsRoute( + val clientId: Long, + val productId: Long, + val applicantName: String, + val loanProductName: String, + val loanPurpose: String, + val disbursementDate: String, + val principalAmount: String, + val providerId: String, +) + +/** + * Navigates to the Confirm Details step with the specified application data. + */ +fun NavController.navigateToConfirmDetails( + clientId: Long, + productId: Long, + applicantName: String, + loanProductName: String, + loanPurpose: String, + disbursementDate: String, + principalAmount: String, + providerId: String, + navOptions: NavOptions? = null, +) { + navigate( + ConfirmDetailsRoute( + clientId = clientId, + productId = productId, + applicantName = applicantName, + loanProductName = loanProductName, + loanPurpose = loanPurpose, + disbursementDate = disbursementDate, + principalAmount = principalAmount, + providerId = providerId, + ), + navOptions, + ) +} + +/** + * Registers the Confirm Details screen in the navigation graph. + * + * Hoists the destination's own `SavedStateHandle` into [ConfirmDetailsScreen] so the + * [LOAN_APPLICATION_VERIFICATION_KEY] passcode-gate round trip works, mirroring + * `transferConfirmScreen` in `:feature:transfer-intrabank`. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateForPasscodeVerification `(verificationKey) -> Unit` — bind to + * `navController::navigateToInternalMifosPasscodeScreen`. The screen forwards + * [LOAN_APPLICATION_VERIFICATION_KEY] so the passcode-screen success/failure callbacks write the + * boolean result back to this destination's saved-state-handle. + * @param onSubmitSuccess Callback invoked once the loan application has been submitted + * successfully and the user has acknowledged the success dialog. Callers are expected to + * navigate away from the loan-application flow entirely (e.g. back to the Accounts screen). + */ +fun NavGraphBuilder.confirmDetailsScreen( + navigateBack: () -> Unit, + navigateForPasscodeVerification: (String) -> Unit, + onSubmitSuccess: () -> Unit, +) { + composableWithSlideTransitions { backStackEntry -> + ConfirmDetailsScreen( + navigateBack = navigateBack, + navigateForPasscodeVerification = navigateForPasscodeVerification, + onSubmitSuccess = onSubmitSuccess, + entryStateHandle = backStackEntry.savedStateHandle, + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsScreen.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsScreen.kt new file mode 100644 index 000000000..b7b437f14 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsScreen.kt @@ -0,0 +1,207 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.confirmDetails + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.repeatOnLifecycle +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_info_confirm_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_status_success +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_title +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifos.lib.loan.component.ConfirmDetailsCard +import org.mifospay.core.designsystem.component.BasicDialogState +import org.mifospay.core.designsystem.component.MifosBasicDialog +import org.mifospay.core.designsystem.component.MifosButton +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.designsystem.component.MifosTopBar +import org.mifospay.core.ui.MifosProgressIndicatorOverlay +import org.mifospay.core.ui.utils.EventsEffect +import template.core.base.designsystem.theme.KptTheme + +/** + * Entry point for the Confirm Details screen — the final step of the loan-application wizard. + * + * Owns the **passcode/biometric auth-gate plumbing**, mirroring `TransferConfirmScreen`: + * - Observes [entryStateHandle] for [LOAN_APPLICATION_VERIFICATION_KEY] (written by + * `internalMifosPasscodeScreen` on success/failure) and re-dispatches it as + * [ConfirmDetailsAction.UpdateUserVerificationResult] so the suspended + * `handleUserVerification` in [ConfirmDetailsViewModel] resumes. + * - Cancellation guard: if the user backs out of the passcode screen without verifying, the + * `repeatOnLifecycle` block dispatches `UpdateUserVerificationResult(false)` on RESUMED so + * the VM doesn't hang forever. + * - Routes [ConfirmDetailsEvent.NavigateForPasscodeVerification] to + * [navigateForPasscodeVerification] with [LOAN_APPLICATION_VERIFICATION_KEY]. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateForPasscodeVerification `(verificationKey) -> Unit` — caller binds to + * `navController::navigateToInternalMifosPasscodeScreen`. + * @param onSubmitSuccess Callback invoked once the loan application has been submitted + * successfully and the user has acknowledged the success dialog. + * @param entryStateHandle This destination's own `SavedStateHandle`, hoisted from the nav-graph + * builder so the round-trip channel works. + * @param viewModel The state holder managing the submission flow. + */ +@Composable +internal fun ConfirmDetailsScreen( + navigateBack: () -> Unit, + navigateForPasscodeVerification: (String) -> Unit, + onSubmitSuccess: () -> Unit, + entryStateHandle: SavedStateHandle, + modifier: Modifier = Modifier, + viewModel: ConfirmDetailsViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + val authResult by entryStateHandle + .getStateFlow(LOAN_APPLICATION_VERIFICATION_KEY, null) + .collectAsStateWithLifecycle() + + LaunchedEffect(authResult) { + authResult?.let { result -> + entryStateHandle.remove(LOAN_APPLICATION_VERIFICATION_KEY) + viewModel.trySendAction(ConfirmDetailsAction.UpdateUserVerificationResult(result)) + } + } + + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(lifecycleOwner) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + if (authResult == null && state.isAwaitingPasscodeVerification) { + viewModel.trySendAction( + ConfirmDetailsAction.UpdateUserVerificationResult(false), + ) + } + } + } + + EventsEffect(viewModel) { event -> + when (event) { + ConfirmDetailsEvent.NavigateBack -> navigateBack() + + ConfirmDetailsEvent.NavigateForPasscodeVerification -> { + navigateForPasscodeVerification(LOAN_APPLICATION_VERIFICATION_KEY) + } + + ConfirmDetailsEvent.SubmitSucceeded -> onSubmitSuccess() + } + } + + ConfirmDetailsDialogs( + dialogState = state.dialogState, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) + + ConfirmDetailsScreenContent( + state = state, + modifier = modifier, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) +} + +/** + * Displays modal dialogs for the submission result: a success acknowledgement, or an error with + * a retry option, mirroring `TransferConfirmScreen`'s dialog pattern. + */ +@Composable +internal fun ConfirmDetailsDialogs( + dialogState: ConfirmDetailsState.DialogState?, + onAction: (ConfirmDetailsAction) -> Unit, +) { + when (dialogState) { + is ConfirmDetailsState.DialogState.Success -> { + MifosBasicDialog( + visibilityState = BasicDialogState.Shown( + title = stringResource(Res.string.feature_apply_loan_status_success), + message = dialogState.message, + ), + onDismissRequest = { onAction(ConfirmDetailsAction.AcknowledgeSuccess) }, + ) + } + + is ConfirmDetailsState.DialogState.Error -> { + MifosBasicDialog( + visibilityState = BasicDialogState.Shown(message = dialogState.message), + onConfirm = { onAction(ConfirmDetailsAction.RetrySubmit) }, + onDismissRequest = { onAction(ConfirmDetailsAction.DismissDialog) }, + ) + } + + null -> Unit + } +} + +/** + * Renders the confirmation summary card and the final submit button. + * + * @param state The current UI state containing the application summary and submission status. + * @param onAction Callback to handle user intent (e.g. clicking "Apply Loan" or navigating back). + */ +@Composable +internal fun ConfirmDetailsScreenContent( + state: ConfirmDetailsState, + onAction: (ConfirmDetailsAction) -> Unit, + modifier: Modifier = Modifier, +) { + MifosScaffold( + modifier = modifier, + topBar = { + MifosTopBar( + topBarTitle = stringResource(Res.string.feature_apply_loan_info_confirm_details), + backPress = { onAction(ConfirmDetailsAction.OnNavigateBack) }, + ) + }, + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .padding(KptTheme.spacing.md) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.lg), + ) { + ConfirmDetailsCard(keyValuePairs = state.details) + + MifosButton( + modifier = Modifier.fillMaxWidth().height(48.dp), + onClick = { onAction(ConfirmDetailsAction.SubmitClicked) }, + enabled = !state.isSubmitting, + shape = KptTheme.shapes.medium, + ) { + Text(text = stringResource(Res.string.feature_apply_loan_title)) + } + } + + if (state.isSubmitting) { + MifosProgressIndicatorOverlay() + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsViewModel.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsViewModel.kt new file mode 100644 index 000000000..a869b7f6f --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/confirmDetails/ConfirmDetailsViewModel.kt @@ -0,0 +1,370 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.confirmDetails + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_error_server +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_applicant_name +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_disbursement_date +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_loan_product +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_principal_amount +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_purpose +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_status_success_tip +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.getString +import org.mifos.lib.loan.core.model.LoanState +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.LoansPayload +import org.mifos.lib.loan.repository.LoansRepository +import org.mifospay.core.common.DataState +import org.mifospay.core.data.repository.UserVerificationRepository +import org.mifospay.core.ui.utils.BaseViewModel + +/** + * `SavedStateHandle` key written by `internalMifosPasscodeScreen` and read by + * [ConfirmDetailsScreen] for the loan-application submission auth-gate round trip. Mirrors + * [org.mifospay.feature.transfer.intrabank.confirm.INTRA_BANK_TRANSFER_VERIFICATION_KEY], but + * with its own key so the two flows don't cross-fire. + */ +const val LOAN_APPLICATION_VERIFICATION_KEY = "loan_application_verification_key" + +/** + * `ViewModel` for the Confirm Details screen — the final step of the loan-application wizard. + * + * Owns the **passcode/biometric auth gate** required before submitting the application, mirroring + * `TransferConfirmViewModel`: + * 1. On [ConfirmDetailsAction.SubmitClicked], emits [ConfirmDetailsEvent.NavigateForPasscodeVerification]. + * The screen responds by navigating to `internalMifosPasscodeScreen` with + * [LOAN_APPLICATION_VERIFICATION_KEY]. + * 2. The screen observes its own saved-state-handle for the round-trip boolean and re-dispatches + * it as [ConfirmDetailsAction.UpdateUserVerificationResult]. This VM resumes on the result. + * 3. On `success = true`, the VM calls [UserVerificationRepository.consumeVerification]. If the + * 30s token is still valid, the loan application is fetched (for term/interest data not + * carried by the wizard's route chain) and submitted; if expired, submission is cancelled + * with a "user verification failed" dialog. + * + * On successful submission, an in-place success dialog is shown (no dedicated status screen + * exists in this app); once acknowledged, [ConfirmDetailsEvent.SubmitSucceeded] tells the caller + * to leave the loan-application flow entirely. On failure, an in-place error dialog with a retry + * option is shown instead. + * + * @param loansRepository Facade resolving the loan provider chosen for this application, used for + * fetching the loan template (term/interest data) and for submitting the application itself. + * @param userVerificationRepository The passcode/biometric verification token contract. + * @param savedStateHandle Handle used to read the [ConfirmDetailsRoute] navigation arguments. + */ +internal class ConfirmDetailsViewModel( + private val loansRepository: LoansRepository, + private val userVerificationRepository: UserVerificationRepository, + savedStateHandle: SavedStateHandle, +) : BaseViewModel( + initialState = run { + val route = savedStateHandle.toRoute() + ConfirmDetailsState( + clientId = route.clientId, + productId = route.productId, + applicantName = route.applicantName, + loanProductName = route.loanProductName, + loanPurpose = route.loanPurpose, + disbursementDate = route.disbursementDate, + principalAmount = route.principalAmount, + providerId = route.providerId, + ) + }, +) { + + override fun handleAction(action: ConfirmDetailsAction) { + when (action) { + ConfirmDetailsAction.OnNavigateBack -> sendEvent(ConfirmDetailsEvent.NavigateBack) + + ConfirmDetailsAction.SubmitClicked -> onSubmitClicked() + + ConfirmDetailsAction.RetrySubmit -> onSubmitClicked() + + ConfirmDetailsAction.DismissDialog -> { + mutableStateFlow.update { it.copy(dialogState = null) } + } + + ConfirmDetailsAction.AcknowledgeSuccess -> { + mutableStateFlow.update { it.copy(dialogState = null) } + sendEvent(ConfirmDetailsEvent.SubmitSucceeded) + } + + is ConfirmDetailsAction.UpdateUserVerificationResult -> { + mutableStateFlow.update { + it.copy( + userVerificationResult = action.result, + isAwaitingPasscodeVerification = false, + ) + } + } + } + } + + private fun onSubmitClicked() { + if (state.isSubmitting) return + mutableStateFlow.update { it.copy(isSubmitting = true, dialogState = null) } + + // Passcode/biometric verification gate is temporarily disabled for loan submission. + // To re-enable, restore the handleUserVerification(...) wrapping below (see git history). + viewModelScope.launch { + submitLoanApplication() + } + } + + /** + * Suspends until the passcode/biometric gate round trip completes. + * + * Emits [ConfirmDetailsEvent.NavigateForPasscodeVerification], then waits for + * [ConfirmDetailsState.userVerificationResult] to flip from `null`. The screen is responsible + * for calling [ConfirmDetailsAction.UpdateUserVerificationResult] with the boolean written + * under [LOAN_APPLICATION_VERIFICATION_KEY] in the saved-state-handle. + */ + private suspend fun handleUserVerification( + onSuccess: suspend () -> Unit, + onFailed: suspend () -> Unit, + ) { + mutableStateFlow.update { + it.copy(userVerificationResult = null, isAwaitingPasscodeVerification = true) + } + + sendEvent(ConfirmDetailsEvent.NavigateForPasscodeVerification) + + val result = stateFlow + .map { it.userVerificationResult } + .filter { it != null } + .first() + + when (result) { + true -> onSuccess() + false -> onFailed() + null -> Unit + } + } + + /** + * Fetches the full loan template for the selected product — needed for term/interest fields + * that the wizard's route chain doesn't carry — then submits the loan application. + */ + private suspend fun submitLoanApplication() { + val templateResult = loansRepository + .getLoanTemplateByProduct(state.providerId, state.clientId, state.productId) + .first { it !is DataState.Loading } + + when (templateResult) { + is DataState.Success -> { + val payload = buildLoanPayload(templateResult.data) + val result = loansRepository.submitLoanApplication( + providerId = state.providerId, + loanState = LoanState.CREATE, + payload = payload, + loanId = -1, + ) + handleSubmitResult(result) + } + + is DataState.Error -> { + mutableStateFlow.update { + it.copy( + isSubmitting = false, + dialogState = ConfirmDetailsState.DialogState.Error( + getString(Res.string.feature_apply_loan_error_server), + ), + ) + } + } + + DataState.Loading -> Unit + } + } + + private suspend fun handleSubmitResult(result: DataState) { + when (result) { + is DataState.Success -> { + mutableStateFlow.update { + it.copy( + isSubmitting = false, + dialogState = ConfirmDetailsState.DialogState.Success( + getString(Res.string.feature_apply_loan_status_success_tip), + ), + ) + } + } + + is DataState.Error -> { + mutableStateFlow.update { + it.copy( + isSubmitting = false, + dialogState = ConfirmDetailsState.DialogState.Error(result.message), + ) + } + } + + DataState.Loading -> Unit + } + } + + /** + * Builds the payload for the loan application submission using the data carried by the + * wizard's route chain, plus term/interest fields from the fetched [LoanTemplate]. + */ + private fun buildLoanPayload(template: LoanTemplate) = LoansPayload( + locale = "en", + // Must match the actual format of state.disbursementDate, produced by + // DateHelper.getDateAsStringFromLong() in LoanApplyViewModel (e.g. "19-07-2026") — + // Fineract validates the date string against this declared format and rejects any + // mismatch. + dateFormat = "dd-MM-yyyy", + clientId = state.clientId.toInt(), + productId = state.productId.toInt(), + principal = state.principalAmount.toDoubleOrNull(), + loanType = "individual", + // Fineract's self/loans endpoint rejects the raw "loanPurpose" string parameter + // outright ("The parameter loanPurpose is not supported") — it only recognizes + // loanPurposeId, a numeric FK into the product's loanPurposeOptions. The purpose + // dropdown here isn't wired to those option ids yet, so omit the field entirely + // rather than send a value the server always rejects. + loanPurpose = null, + loanTermFrequency = template.termFrequency, + loanTermFrequencyType = template.interestRateFrequencyType?.id, + numberOfRepayments = template.numberOfRepayments, + repaymentEvery = template.repaymentEvery, + repaymentFrequencyType = template.interestRateFrequencyType?.id, + interestRatePerPeriod = template.interestRatePerPeriod, + interestType = template.interestType?.id, + interestCalculationPeriodType = template.interestCalculationPeriodType?.id, + amortizationType = template.amortizationType?.id, + transactionProcessingStrategyCode = template.transactionProcessingStrategyCode, + expectedDisbursementDate = state.disbursementDate, + submittedOnDate = state.disbursementDate, + ) +} + +/** + * UI state for the Confirm Details screen. + * + * @property clientId The id of the client applying for a loan. + * @property productId The id of the loan product being applied for. + * @property applicantName The applicant's display name. + * @property loanProductName The selected loan product's display name. + * @property loanPurpose The stated purpose for the loan. + * @property disbursementDate The scheduled disbursement date. + * @property principalAmount The requested loan amount. + * @property providerId The id of the loan provider chosen on the Select Loan Provider screen. + * @property isSubmitting Whether a submission (auth gate and/or network call) is in flight. + * @property isAwaitingPasscodeVerification `true` between [ConfirmDetailsEvent.NavigateForPasscodeVerification] + * being emitted and [ConfirmDetailsAction.UpdateUserVerificationResult] being received. + * @property userVerificationResult Round-trip result from the passcode/biometric gate: `null` — + * not yet returned; `true` — verified; `false` — cancelled/rejected. Cleared to `null` on each + * new attempt. + * @property dialogState The state of any dialog to be shown on the screen. + */ +internal data class ConfirmDetailsState( + val clientId: Long, + val productId: Long, + val applicantName: String, + val loanProductName: String, + val loanPurpose: String, + val disbursementDate: String, + val principalAmount: String, + val providerId: String, + val isSubmitting: Boolean = false, + val isAwaitingPasscodeVerification: Boolean = false, + val userVerificationResult: Boolean? = null, + val dialogState: DialogState? = null, +) { + /** + * Key/value pairs rendered by `ConfirmDetailsCard`. + */ + val details: Map + get() = mapOf( + Res.string.feature_apply_loan_label_applicant_name to applicantName, + Res.string.feature_apply_loan_label_loan_product to loanProductName, + Res.string.feature_apply_loan_label_purpose to loanPurpose, + Res.string.feature_apply_loan_label_disbursement_date to disbursementDate, + Res.string.feature_apply_loan_label_principal_amount to principalAmount, + ) + + /** + * A sealed interface representing the different types of dialogs that can be shown on the + * Confirm Details screen. + */ + sealed interface DialogState { + /** + * The application was submitted successfully. + * @property message A user-facing confirmation message. + */ + data class Success(val message: String) : DialogState + + /** + * The application failed to submit (or the auth gate expired). + * @property message A user-facing error message. + */ + data class Error(val message: String) : DialogState + } +} + +/** + * One-shot navigation events emitted by [ConfirmDetailsViewModel]. + */ +internal sealed interface ConfirmDetailsEvent { + /** Navigates back from the current screen. */ + data object NavigateBack : ConfirmDetailsEvent + + /** + * Tells the screen to push `internalMifosPasscodeScreen` with + * [LOAN_APPLICATION_VERIFICATION_KEY]. The screen is responsible for observing its own + * saved-state-handle for the round-trip boolean and dispatching it back as + * [ConfirmDetailsAction.UpdateUserVerificationResult]. + */ + data object NavigateForPasscodeVerification : ConfirmDetailsEvent + + /** + * The loan application was submitted successfully and the user has acknowledged the success + * dialog. The screen should leave the loan-application flow entirely. + */ + data object SubmitSucceeded : ConfirmDetailsEvent +} + +/** + * User actions and internal events handled by [ConfirmDetailsViewModel]. + */ +internal sealed interface ConfirmDetailsAction { + /** User action to navigate back. */ + data object OnNavigateBack : ConfirmDetailsAction + + /** User action to submit the loan application, triggering the passcode auth gate. */ + data object SubmitClicked : ConfirmDetailsAction + + /** User action to retry submission after a failure. */ + data object RetrySubmit : ConfirmDetailsAction + + /** User action to dismiss the current dialog (e.g. an error). */ + data object DismissDialog : ConfirmDetailsAction + + /** User action acknowledging the success dialog, closing out the flow. */ + data object AcknowledgeSuccess : ConfirmDetailsAction + + /** + * Round-trip result from the passcode/biometric gate, dispatched by [ConfirmDetailsScreen] + * after observing [LOAN_APPLICATION_VERIFICATION_KEY] flip on its saved-state-handle. + * `true` lets the submission proceed (subject to a still-valid + * [UserVerificationRepository] token); `false` aborts. + */ + data class UpdateUserVerificationResult(val result: Boolean) : ConfirmDetailsAction +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyRoute.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyRoute.kt new file mode 100644 index 000000000..52d450210 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyRoute.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.loanApply + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import kotlinx.serialization.Serializable +import template.core.base.ui.composableWithSlideTransitions + +/** + * Type-safe navigation route for the loan application form screen. + * + * @param clientId The id of the client applying for a loan. + * @param productId The id of the loan product being applied for. + * @param providerId The id of the loan provider chosen on the Select Loan Provider screen. + */ +@Serializable +data class LoanApplyRoute( + val clientId: Long, + val productId: Long, + val providerId: String, +) + +/** + * Navigates to the loan application form for the given [clientId]/[productId]/[providerId]. + */ +fun NavController.navigateToLoanApply( + clientId: Long, + productId: Long, + providerId: String, + navOptions: NavOptions? = null, +) { + navigate(LoanApplyRoute(clientId, productId, providerId), navOptions) +} + +/** + * Registers the loan application form screen in the navigation graph. + * + * The next step in the wizard — document upload — is not implemented yet, so + * [navigateToUploadDocs] is left as a plain callback here. Once `org.mifos.lib.loan.ui.uploadDocs` + * exists, the caller wiring this destination should bind it to that package's + * `NavController.navigateToUploadDocsScreen(...)` extension. The expected shape of that + * destination's route, based on the fields threaded through here, is: + * + * ``` + * @Serializable + * data class UploadDocsRoute( + * val clientId: Long, + * val productId: Long, + * val applicantName: String, + * val loanProductName: String, + * val loanPurpose: String, + * val disbursementDate: String, + * val principalAmount: String, + * val providerId: String, + * ) + * ``` + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToUploadDocs Callback to proceed to the document upload step with the + * fully-populated application form data. + */ +fun NavGraphBuilder.loanApplyScreen( + navigateBack: () -> Unit, + navigateToUploadDocs: ( + clientId: Long, + productId: Long, + applicantName: String, + loanProductName: String, + loanPurpose: String, + disbursementDate: String, + principalAmount: String, + providerId: String, + ) -> Unit, +) { + composableWithSlideTransitions { + LoanApplyScreen( + navigateBack = navigateBack, + navigateToUploadDocs = navigateToUploadDocs, + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyScreen.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyScreen.kt new file mode 100644 index 000000000..c5b02dc15 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyScreen.kt @@ -0,0 +1,244 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.loanApply + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_button_continue +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_hint_applicant_name +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_hint_disbursement_date +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_hint_loan_product +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_hint_principal_amount +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_hint_purpose +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_applicant_name +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_disbursement_date +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_loan_product +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_principal_amount +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_label_purpose +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_section_fill_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_select_loan_type_network_issue +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.component.MifosButton +import org.mifospay.core.designsystem.component.MifosOutlinedTextField +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.ui.DropdownBoxItem +import org.mifospay.core.ui.ErrorScreenContent +import org.mifospay.core.ui.ExposedDropdownBox +import org.mifospay.core.ui.MifosProgressIndicator +import org.mifospay.core.ui.utils.EventsEffect +import template.core.base.designsystem.theme.KptTheme + +/** + * Entry point for the Loan Application form. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToUploadDocs Callback to proceed to the document upload step with the + * populated application form data. + * @param viewModel The state holder managing form validation and business logic. + */ +@Composable +internal fun LoanApplyScreen( + navigateBack: () -> Unit, + navigateToUploadDocs: ( + clientId: Long, + productId: Long, + applicantName: String, + loanProductName: String, + loanPurpose: String, + disbursementDate: String, + principalAmount: String, + providerId: String, + ) -> Unit, + modifier: Modifier = Modifier, + viewModel: LoanApplyViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + LoanApplyEvent.NavigateBack -> navigateBack() + + is LoanApplyEvent.NavigateToUploadDocs -> { + navigateToUploadDocs( + event.clientId, + event.productId, + event.applicantName, + event.loanProductName, + event.loanPurpose, + event.disbursementDate, + event.principalAmount, + event.providerId, + ) + } + } + } + + LoanApplyContent( + state = state, + modifier = modifier, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) +} + +@Composable +private fun LoanApplyContent( + state: LoanApplyState, + onAction: (LoanApplyAction) -> Unit, + modifier: Modifier = Modifier, +) { + MifosScaffold( + modifier = modifier, + backPress = { onAction(LoanApplyAction.NavigateBack) }, + topBarTitle = stringResource(Res.string.feature_apply_loan_section_fill_details), + ) { paddingValues -> + when (val viewState = state.viewState) { + LoanApplyState.ViewState.Loading -> MifosProgressIndicator() + + is LoanApplyState.ViewState.Error -> { + ErrorScreenContent( + subTitle = viewState.message, + onClickRetry = { onAction(LoanApplyAction.Retry) }, + ) + } + + LoanApplyState.ViewState.NetworkError -> { + ErrorScreenContent( + subTitle = stringResource(Res.string.feature_select_loan_type_network_issue), + onClickRetry = { onAction(LoanApplyAction.Retry) }, + ) + } + + LoanApplyState.ViewState.Content -> { + Column( + modifier = Modifier + .padding(paddingValues) + .padding(KptTheme.spacing.md) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + MifosOutlinedTextField( + value = state.applicantName, + label = stringResource(Res.string.feature_apply_loan_label_applicant_name), + onValueChange = {}, + readOnly = true, + showClearIcon = false, + placeholder = { + Text( + text = stringResource(Res.string.feature_apply_loan_hint_applicant_name), + ) + }, + ) + + MifosOutlinedTextField( + value = state.disbursementDate, + label = stringResource(Res.string.feature_apply_loan_label_disbursement_date), + onValueChange = {}, + readOnly = true, + showClearIcon = false, + placeholder = { + Text( + text = stringResource(Res.string.feature_apply_loan_hint_disbursement_date), + ) + }, + ) + + MifosOutlinedTextField( + value = state.loanProductName, + label = stringResource(Res.string.feature_apply_loan_label_loan_product), + onValueChange = {}, + readOnly = true, + showClearIcon = false, + placeholder = { + Text( + text = stringResource(Res.string.feature_apply_loan_hint_loan_product), + ) + }, + ) + + LoanPurposeDropdown(state = state, onAction = onAction) + + MifosOutlinedTextField( + value = state.principalAmount, + label = stringResource(Res.string.feature_apply_loan_label_principal_amount), + onValueChange = { onAction(LoanApplyAction.PrincipalAmountChanged(it)) }, + isError = state.principalAmountError != null, + errorMessage = state.principalAmountError?.let { stringResource(it) }, + placeholder = { + Text( + text = stringResource(Res.string.feature_apply_loan_hint_principal_amount), + ) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done, + ), + ) + + MifosButton( + modifier = Modifier.fillMaxWidth(), + enabled = state.isSubmitEnabled, + onClick = { onAction(LoanApplyAction.SubmitClicked) }, + ) { + Text( + text = stringResource(Res.string.feature_apply_loan_button_continue), + ) + } + } + } + } + } +} + +@Composable +private fun LoanPurposeDropdown( + state: LoanApplyState, + onAction: (LoanApplyAction) -> Unit, +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownBox( + expanded = expanded, + label = stringResource(Res.string.feature_apply_loan_label_purpose), + value = state.selectedLoanPurpose.ifEmpty { + stringResource(Res.string.feature_apply_loan_hint_purpose) + }, + onExpandChange = { expanded = it }, + modifier = Modifier.fillMaxWidth(), + ) { + state.loanPurposeOptions.forEach { (_, name) -> + DropdownBoxItem( + text = name, + onClick = { + onAction(LoanApplyAction.LoanPurposeSelected(name)) + expanded = false + }, + ) + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyViewModel.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyViewModel.kt new file mode 100644 index 000000000..0e8bed3af --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanApply/LoanApplyViewModel.kt @@ -0,0 +1,408 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.loanApply + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_error_amount_empty +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_error_amount_invalid_decimal +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_error_amount_invalid_format +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_error_amount_multiple +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_error_amount_too_large +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_error_amount_too_small +import org.jetbrains.compose.resources.StringResource +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.repository.LoansRepository +import org.mifospay.core.common.DataState +import org.mifospay.core.common.DateHelper +import org.mifospay.core.data.repository.SelfServiceRepository +import org.mifospay.core.data.util.NetworkMonitor +import org.mifospay.core.datastore.UserPreferencesRepository +import org.mifospay.core.model.client.Client +import org.mifospay.core.ui.utils.BaseViewModel +import kotlin.time.Clock +import kotlin.time.ExperimentalTime + +/** + * `ViewModel` for the Loan Application form screen. + * + * Combines the applicant's client record, the client-wide loan template (for currency), and the + * product-specific loan template (for principal bounds and loan-purpose options) to populate the + * application form, then validates and forwards the entered data to the document-upload step. + * + * @param savedStateHandle Handle used to read the [LoanApplyRoute] navigation arguments. + * @param loansRepository Facade resolving the loan provider chosen for this application. + * @param selfServiceRepository Used to fetch the applicant's client record (name, activation date). + * @param userPreferencesRepository Fallback source for the current client id if the navigation + * argument was not supplied. + * @param networkMonitor Monitors network connectivity so the form can be reloaded on reconnect. + */ +@OptIn(ExperimentalTime::class) +internal class LoanApplyViewModel( + savedStateHandle: SavedStateHandle, + private val loansRepository: LoansRepository, + private val selfServiceRepository: SelfServiceRepository, + private val userPreferencesRepository: UserPreferencesRepository, + private val networkMonitor: NetworkMonitor, +) : BaseViewModel( + initialState = run { + val route = savedStateHandle.toRoute() + val resolvedClientId = route.clientId.takeIf { it > 0 } + ?: requireNotNull(userPreferencesRepository.clientId.value) + LoanApplyState( + clientId = resolvedClientId, + productId = route.productId, + providerId = route.providerId, + ) + }, +) { + + init { + observeNetworkStatus() + } + + private fun observeNetworkStatus() { + viewModelScope.launch { + networkMonitor.isOnline.distinctUntilChanged().collect { isOnline -> + sendAction(LoanApplyAction.ReceiveNetworkStatus(isOnline)) + } + } + } + + override fun handleAction(action: LoanApplyAction) { + when (action) { + LoanApplyAction.NavigateBack -> sendEvent(LoanApplyEvent.NavigateBack) + + LoanApplyAction.Retry -> loadData() + + is LoanApplyAction.PrincipalAmountChanged -> onPrincipalAmountChanged(action.value) + + is LoanApplyAction.LoanPurposeSelected -> { + mutableStateFlow.update { it.copy(selectedLoanPurpose = action.purpose) } + } + + LoanApplyAction.SubmitClicked -> onSubmitClicked() + + is LoanApplyAction.ReceiveNetworkStatus -> handleNetworkStatus(action.isOnline) + + is LoanApplyAction.Internal.DataLoaded -> { + handleDataLoaded(action.client, action.template, action.productTemplate) + } + } + } + + private fun handleNetworkStatus(isOnline: Boolean) { + mutableStateFlow.update { it.copy(networkStatus = isOnline) } + if (!isOnline) { + mutableStateFlow.update { current -> + if (current.viewState !is LoanApplyState.ViewState.Content) { + current.copy(viewState = LoanApplyState.ViewState.NetworkError) + } else { + current + } + } + } else { + loadData() + } + } + + private fun loadData() { + mutableStateFlow.update { it.copy(viewState = LoanApplyState.ViewState.Loading) } + viewModelScope.launch { + combine( + selfServiceRepository.getSelfClientDetails(state.clientId), + loansRepository.getLoanTemplate(state.providerId, state.clientId), + loansRepository.getLoanTemplateByProduct( + state.providerId, + state.clientId, + state.productId, + ), + ) { client, template, productTemplate -> + Triple(client, template, productTemplate) + }.collect { (client, template, productTemplate) -> + sendAction(LoanApplyAction.Internal.DataLoaded(client, template, productTemplate)) + } + } + } + + private fun handleDataLoaded( + client: DataState, + template: DataState, + productTemplate: DataState, + ) { + when { + listOf(client, template, productTemplate).any { it is DataState.Loading } -> { + mutableStateFlow.update { it.copy(viewState = LoanApplyState.ViewState.Loading) } + } + + client is DataState.Success && + template is DataState.Success && + productTemplate is DataState.Success -> { + populateForm(client.data, template.data, productTemplate.data) + } + + else -> { + val message = listOf(client, template, productTemplate) + .filterIsInstance>() + .firstOrNull() + ?.message + ?: "Something went wrong. Please try again." + mutableStateFlow.update { + it.copy(viewState = LoanApplyState.ViewState.Error(message)) + } + } + } + } + + private fun populateForm(client: Client, template: LoanTemplate, productTemplate: LoanTemplate) { + val product = productTemplate.product + val currency = template.currency + + val minPrincipal = product?.minPrincipal ?: productTemplate.principal ?: 0.0 + val maxPrincipal = product?.maxPrincipal ?: productTemplate.principal ?: Double.MAX_VALUE + + val purposeOptions = productTemplate.loanPurposeOptions + .mapNotNull { option -> + val id = option.id?.toLong() + val name = option.name + if (id != null && name != null) id to name else null + } + .toMap() + + val initialPrincipal = if (minPrincipal % 1.0 == 0.0) { + minPrincipal.toLong().toString() + } else { + minPrincipal.toString() + } + + mutableStateFlow.update { + it.copy( + applicantName = client.displayName, + loanProductName = productTemplate.loanProductName ?: product?.name.orEmpty(), + activationDate = client.activationDate, + loanPurposeOptions = purposeOptions, + minPrincipal = minPrincipal, + maxPrincipal = maxPrincipal, + principalAmount = initialPrincipal, + principalAmountError = null, + disbursementDate = computeDefaultDisbursementDate(client.activationDate), + currencyDisplaySymbol = currency?.displaySymbol.orEmpty(), + currencyDecimalPlaces = currency?.decimalPlaces?.toInt() ?: DEFAULT_DECIMAL_PLACES, + currencyInMultiplesOf = currency?.inMultiplesOf?.toDouble() ?: 0.0, + viewState = LoanApplyState.ViewState.Content, + ) + } + } + + /** + * The disbursement date defaults to the later of today or the client's activation date, + * mirroring the source app's behaviour of never allowing disbursement before a client is + * active. + */ + private fun computeDefaultDisbursementDate(activationDate: List): String { + val todayMillis = Clock.System.now().toEpochMilliseconds() + val activationMillis = runCatching { + if (activationDate.size >= 3) { + LocalDate( + year = activationDate[0].toInt(), + monthNumber = activationDate[1].toInt(), + dayOfMonth = activationDate[2].toInt(), + ).atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds() + } else { + null + } + }.getOrNull() + + val effectiveMillis = maxOf(todayMillis, activationMillis ?: todayMillis) + return DateHelper.getDateAsStringFromLong(effectiveMillis) + } + + private fun onPrincipalAmountChanged(newValue: String) { + mutableStateFlow.update { + it.copy( + principalAmount = newValue, + principalAmountError = validatePrincipal(newValue), + ) + } + } + + private fun onSubmitClicked() { + val error = validatePrincipal(state.principalAmount) + mutableStateFlow.update { it.copy(principalAmountError = error) } + + if (error == null && state.isSubmitEnabled) { + sendEvent( + LoanApplyEvent.NavigateToUploadDocs( + clientId = state.clientId, + productId = state.productId, + applicantName = state.applicantName, + loanProductName = state.loanProductName, + loanPurpose = state.selectedLoanPurpose, + disbursementDate = state.disbursementDate, + principalAmount = state.principalAmount, + providerId = state.providerId, + ), + ) + } + } + + /** + * Validates the principal amount against the selected product's bounds and the loan + * currency's decimal/step configuration. There is no shared `ValidationHelper` in this app, + * so the rules are implemented directly here. + */ + private fun validatePrincipal(amount: String): StringResource? { + if (amount.isBlank()) return Res.string.feature_apply_loan_error_amount_empty + + val cleaned = amount.trim().replace(",", "") + if (!AMOUNT_FORMAT_REGEX.matches(cleaned)) { + return Res.string.feature_apply_loan_error_amount_invalid_format + } + + val value = cleaned.toDoubleOrNull() + ?: return Res.string.feature_apply_loan_error_amount_invalid_format + + val dotIndex = cleaned.indexOf('.') + if (dotIndex != -1 && cleaned.length - dotIndex - 1 > state.currencyDecimalPlaces) { + return Res.string.feature_apply_loan_error_amount_invalid_decimal + } + + if (value < state.minPrincipal) return Res.string.feature_apply_loan_error_amount_too_small + if (value > state.maxPrincipal) return Res.string.feature_apply_loan_error_amount_too_large + + val multiplesOf = state.currencyInMultiplesOf + if (multiplesOf > 0) { + val remainder = value % multiplesOf + val isMultiple = remainder < AMOUNT_EPSILON || (multiplesOf - remainder) < AMOUNT_EPSILON + if (!isMultiple) return Res.string.feature_apply_loan_error_amount_multiple + } + + return null + } + + private companion object { + const val DEFAULT_DECIMAL_PLACES = 2 + const val AMOUNT_EPSILON = 0.0001 + val AMOUNT_FORMAT_REGEX = Regex("^\\d+(\\.\\d+)?$") + } +} + +/** + * UI state for the Loan Application form screen. + * + * @property clientId The id of the client applying for a loan. + * @property productId The id of the loan product being applied for. + * @property providerId The id of the loan provider chosen on the Select Loan Provider screen. + * @property applicantName The applicant's display name (read-only, sourced from the client record). + * @property loanProductName The selected loan product's name (read-only). + * @property activationDate The client's activation date, as `[year, month, day]`. + * @property loanPurposeOptions Map of loan-purpose id to display name, sourced from the product template. + * @property selectedLoanPurpose The currently selected loan purpose name. + * @property principalAmount The requested principal amount, as entered by the user. + * @property disbursementDate The computed disbursement date (`dd-MM-yyyy`), read-only. + * @property currencyDisplaySymbol The currency symbol to prefix amounts with. + * @property currencyDecimalPlaces Allowed decimal places for the currency. + * @property currencyInMultiplesOf The step the principal amount must be a multiple of, if any. + * @property minPrincipal The minimum principal allowed for the selected product. + * @property maxPrincipal The maximum principal allowed for the selected product. + * @property principalAmountError Validation error for the principal amount field, if any. + * @property networkStatus Whether the device currently has network connectivity. + * @property viewState The current content/loading/error state of the screen. + */ +internal data class LoanApplyState( + val clientId: Long, + val productId: Long, + val providerId: String, + val applicantName: String = "", + val loanProductName: String = "", + val activationDate: List = emptyList(), + val loanPurposeOptions: Map = emptyMap(), + val selectedLoanPurpose: String = "", + val principalAmount: String = "", + val disbursementDate: String = "", + val currencyDisplaySymbol: String = "", + val currencyDecimalPlaces: Int = 2, + val currencyInMultiplesOf: Double = 0.0, + val minPrincipal: Double = 0.0, + val maxPrincipal: Double = Double.MAX_VALUE, + val principalAmountError: StringResource? = null, + val networkStatus: Boolean = true, + val viewState: ViewState = ViewState.Loading, +) { + val isSubmitEnabled: Boolean + get() = viewState is ViewState.Content && + principalAmountError == null && + principalAmount.isNotBlank() && + selectedLoanPurpose.isNotBlank() && + disbursementDate.isNotBlank() + + sealed interface ViewState { + data object Loading : ViewState + data object Content : ViewState + data class Error(val message: String) : ViewState + data object NetworkError : ViewState + } +} + +/** + * One-shot navigation events emitted by [LoanApplyViewModel]. + */ +internal sealed interface LoanApplyEvent { + data object NavigateBack : LoanApplyEvent + + /** + * Requests navigation to the document-upload step with the fully-populated application + * form data. See [loanApplyScreen] for the expected shape of the destination this feeds. + */ + data class NavigateToUploadDocs( + val clientId: Long, + val productId: Long, + val applicantName: String, + val loanProductName: String, + val loanPurpose: String, + val disbursementDate: String, + val principalAmount: String, + val providerId: String, + ) : LoanApplyEvent +} + +/** + * User actions and internal events handled by [LoanApplyViewModel]. + */ +internal sealed interface LoanApplyAction { + data object NavigateBack : LoanApplyAction + + data object Retry : LoanApplyAction + + data class PrincipalAmountChanged(val value: String) : LoanApplyAction + + data class LoanPurposeSelected(val purpose: String) : LoanApplyAction + + data object SubmitClicked : LoanApplyAction + + data class ReceiveNetworkStatus(val isOnline: Boolean) : LoanApplyAction + + sealed interface Internal : LoanApplyAction { + data class DataLoaded( + val client: DataState, + val template: DataState, + val productTemplate: DataState, + ) : Internal + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsRoute.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsRoute.kt new file mode 100644 index 000000000..40701286e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsRoute.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.loanProductDetails + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import kotlinx.serialization.Serializable +import template.core.base.ui.composableWithSlideTransitions + +/** + * Type-safe navigation route for the loan product details screen. + * + * @param clientId The id of the client applying for a loan. + * @param productId The id of the loan product the client selected. + * @param providerId The id of the loan provider chosen on the Select Loan Provider screen. + */ +@Serializable +data class LoanProductDetailsRoute( + val clientId: Long, + val productId: Long, + val providerId: String, +) + +/** + * Navigates to the loan product details screen for the given [clientId]/[productId]/[providerId]. + */ +fun NavController.navigateToLoanProductDetails( + clientId: Long, + productId: Long, + providerId: String, + navOptions: NavOptions? = null, +) { + navigate(LoanProductDetailsRoute(clientId, productId, providerId), navOptions) +} + +/** + * Registers the loan product details screen in the navigation graph. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToLoanApply Callback to proceed to the application form for this product. + */ +fun NavGraphBuilder.loanProductDetailsScreen( + navigateBack: () -> Unit, + navigateToLoanApply: (clientId: Long, productId: Long, providerId: String) -> Unit, +) { + composableWithSlideTransitions { + LoanProductDetailsScreen( + navigateBack = navigateBack, + navigateToLoanApply = navigateToLoanApply, + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsScreen.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsScreen.kt new file mode 100644 index 000000000..ca0720a8b --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsScreen.kt @@ -0,0 +1,244 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.loanProductDetails + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_title +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_amendments_and_termination +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_apply_loan +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_continue_legal_compliance +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_default +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_documentation +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_get_loan +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_insurance +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_interest_rate +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_interest_rate_in_numbers +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_jurisdiction +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_repayment +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_sanction_and_disbursement +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_security_and_collateral +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_terms_and_conditions +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_up_to +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_amendments_and_termination_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_continue_legal_compliance_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_default_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_documentation_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_insurance_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_interest_rate_description +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_jurisdiction_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_repayment_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_sanction_and_disbursement_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_personal_loan_security_and_collateral_details +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifos.lib.loan.component.ApplyLoanBottomBar +import org.mifos.lib.loan.component.LoanCard +import org.mifos.lib.loan.component.TermsAndConditionItem +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.designsystem.component.MifosTopBar +import org.mifospay.core.ui.ErrorScreenContent +import org.mifospay.core.ui.MifosProgressIndicator +import org.mifospay.core.ui.utils.EventsEffect +import template.core.base.designsystem.theme.KptTheme + +/** + * Entry point for the Loan Product Details screen. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToLoanApply Callback to proceed to the loan application form with the selected product. + * @param viewModel The state holder managing the product data and UI state. + */ +@Composable +internal fun LoanProductDetailsScreen( + navigateBack: () -> Unit, + navigateToLoanApply: (clientId: Long, productId: Long, providerId: String) -> Unit, + modifier: Modifier = Modifier, + viewModel: LoanProductDetailsViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + LoanProductDetailsEvent.NavigateBack -> navigateBack() + + is LoanProductDetailsEvent.NavigateToApply -> { + navigateToLoanApply(event.clientId, event.productId, event.providerId) + } + } + } + + LoanProductDetailsContent( + state = state, + modifier = modifier, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) +} + +@Composable +private fun LoanProductDetailsContent( + state: LoanProductDetailsState, + onAction: (LoanProductDetailsAction) -> Unit, + modifier: Modifier = Modifier, +) { + val viewState = state.viewState + val topBarTitle = if (viewState is LoanProductDetailsState.ViewState.Content) { + stringResource(Res.string.feature_loan_apply_loan, viewState.productName.lowercase()) + } else { + stringResource(Res.string.feature_apply_loan_title) + } + + MifosScaffold( + modifier = modifier, + topBar = { + MifosTopBar( + topBarTitle = topBarTitle, + backPress = { onAction(LoanProductDetailsAction.NavigateBack) }, + ) + }, + bottomBar = { + if (viewState is LoanProductDetailsState.ViewState.Content) { + ApplyLoanBottomBar( + checked = state.agreedToTerms, + isEnabled = state.isApplyEnabled, + onCheckedChange = { onAction(LoanProductDetailsAction.TermsCheckedChanged(it)) }, + onApplyClick = { onAction(LoanProductDetailsAction.ApplyClicked) }, + ) + } + }, + ) { paddingValues -> + when (viewState) { + LoanProductDetailsState.ViewState.Loading -> MifosProgressIndicator() + + is LoanProductDetailsState.ViewState.Error -> { + ErrorScreenContent( + subTitle = viewState.message, + onClickRetry = { onAction(LoanProductDetailsAction.Retry) }, + ) + } + + is LoanProductDetailsState.ViewState.Content -> { + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(KptTheme.spacing.md), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + item { + LoanCard( + title = stringResource(Res.string.feature_loan_get_loan, viewState.productName), + amount = stringResource( + Res.string.feature_loan_up_to, + "${viewState.currencySymbol}${formatAmount(viewState.maxPrincipal)}", + ), + interestRate = stringResource( + Res.string.feature_loan_interest_rate_in_numbers, + formatRate(viewState.minInterest), + formatRate(viewState.maxInterest), + ), + ) + } + + item { + Text( + text = stringResource(Res.string.feature_loan_terms_and_conditions), + style = KptTheme.typography.titleSmall, + ) + Spacer(modifier = Modifier.height(KptTheme.spacing.md)) + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.xs), + ) { + TermsAndConditionItem( + title = Res.string.feature_loan_sanction_and_disbursement, + description = Res.string + .feature_personal_loan_sanction_and_disbursement_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_interest_rate, + description = Res.string.feature_personal_loan_interest_rate_description, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_repayment, + description = Res.string.feature_personal_loan_repayment_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_security_and_collateral, + description = Res.string.feature_personal_loan_security_and_collateral_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_insurance, + description = Res.string.feature_personal_loan_insurance_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_default, + description = Res.string.feature_personal_loan_default_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_documentation, + description = Res.string.feature_personal_loan_documentation_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_continue_legal_compliance, + description = Res.string + .feature_personal_loan_continue_legal_compliance_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_amendments_and_termination, + description = Res.string + .feature_personal_loan_amendments_and_termination_details, + ) + + TermsAndConditionItem( + title = Res.string.feature_loan_jurisdiction, + description = Res.string.feature_personal_loan_jurisdiction_details, + ) + } + } + } + } + } + } +} + +/** + * Formats a principal amount, dropping the decimal point when the value is a whole number. + */ +private fun formatAmount(amount: Double): String = + if (amount % 1.0 == 0.0) amount.toLong().toString() else amount.toString() + +/** + * Formats an interest rate percentage for display, e.g. "12%". + */ +private fun formatRate(rate: Double): String = "${formatAmount(rate)}%" diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsViewModel.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsViewModel.kt new file mode 100644 index 000000000..2c0217358 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/loanProductDetails/LoanProductDetailsViewModel.kt @@ -0,0 +1,188 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.loanProductDetails + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.repository.LoansRepository +import org.mifospay.core.common.DataState +import org.mifospay.core.ui.utils.BaseViewModel + +/** + * `ViewModel` for the Loan Product Details screen. + * + * Fetches the loan template scoped to a specific product so the screen can show the product's + * terms (principal range, interest rate, currency) and gate the "Apply" action behind a + * terms-and-conditions acknowledgement. + * + * @param savedStateHandle Handle used to read the [LoanProductDetailsRoute] navigation arguments. + * @param loansRepository Facade resolving the loan provider chosen for this application. + */ +internal class LoanProductDetailsViewModel( + savedStateHandle: SavedStateHandle, + private val loansRepository: LoansRepository, +) : BaseViewModel( + initialState = run { + val route = savedStateHandle.toRoute() + LoanProductDetailsState( + clientId = route.clientId, + productId = route.productId, + providerId = route.providerId, + ) + }, +) { + + init { + loadTemplate() + } + + override fun handleAction(action: LoanProductDetailsAction) { + when (action) { + LoanProductDetailsAction.NavigateBack -> sendEvent(LoanProductDetailsEvent.NavigateBack) + + is LoanProductDetailsAction.TermsCheckedChanged -> { + mutableStateFlow.update { it.copy(agreedToTerms = action.checked) } + } + + LoanProductDetailsAction.ApplyClicked -> { + if (state.agreedToTerms) { + sendEvent( + LoanProductDetailsEvent.NavigateToApply( + clientId = state.clientId, + productId = state.productId, + providerId = state.providerId, + ), + ) + } + } + + LoanProductDetailsAction.Retry -> loadTemplate() + + is LoanProductDetailsAction.Internal.TemplateLoaded -> handleTemplateLoaded(action.result) + } + } + + private fun loadTemplate() { + mutableStateFlow.update { it.copy(viewState = LoanProductDetailsState.ViewState.Loading) } + viewModelScope.launch { + loansRepository + .getLoanTemplateByProduct(state.providerId, state.clientId, state.productId) + .collect { result -> + sendAction(LoanProductDetailsAction.Internal.TemplateLoaded(result)) + } + } + } + + private fun handleTemplateLoaded(result: DataState) { + when (result) { + is DataState.Loading -> { + mutableStateFlow.update { it.copy(viewState = LoanProductDetailsState.ViewState.Loading) } + } + + is DataState.Success -> { + val template = result.data + val product = template.product + + val minPrincipal = product?.minPrincipal ?: template.principal ?: 0.0 + val maxPrincipal = product?.maxPrincipal ?: template.principal ?: 0.0 + val minInterest = product?.minInterestRatePerPeriod ?: template.annualInterestRate ?: 0.0 + val maxInterest = product?.maxInterestRatePerPeriod ?: template.annualInterestRate ?: 0.0 + + mutableStateFlow.update { + it.copy( + viewState = LoanProductDetailsState.ViewState.Content( + productName = template.loanProductName ?: product?.name.orEmpty(), + currencySymbol = template.currency?.displaySymbol.orEmpty(), + minPrincipal = minPrincipal, + maxPrincipal = maxPrincipal, + minInterest = minInterest, + maxInterest = maxInterest, + ), + ) + } + } + + is DataState.Error -> { + mutableStateFlow.update { + it.copy(viewState = LoanProductDetailsState.ViewState.Error(result.message)) + } + } + } + } +} + +/** + * UI state for the Loan Product Details screen. + * + * @property clientId The id of the client applying for a loan. + * @property productId The id of the loan product being viewed. + * @property providerId The id of the loan provider chosen on the Select Loan Provider screen. + * @property agreedToTerms Whether the user has checked the terms-and-conditions checkbox. + * @property viewState The current content/loading/error state of the screen. + */ +internal data class LoanProductDetailsState( + val clientId: Long, + val productId: Long, + val providerId: String, + val agreedToTerms: Boolean = false, + val viewState: ViewState = ViewState.Loading, +) { + val isApplyEnabled: Boolean + get() = agreedToTerms && viewState is ViewState.Content + + sealed interface ViewState { + data object Loading : ViewState + + data class Content( + val productName: String, + val currencySymbol: String, + val minPrincipal: Double, + val maxPrincipal: Double, + val minInterest: Double, + val maxInterest: Double, + ) : ViewState + + data class Error(val message: String) : ViewState + } +} + +/** + * One-shot navigation events emitted by [LoanProductDetailsViewModel]. + */ +internal sealed interface LoanProductDetailsEvent { + data object NavigateBack : LoanProductDetailsEvent + + data class NavigateToApply( + val clientId: Long, + val productId: Long, + val providerId: String, + ) : LoanProductDetailsEvent +} + +/** + * User actions and internal events handled by [LoanProductDetailsViewModel]. + */ +internal sealed interface LoanProductDetailsAction { + data object NavigateBack : LoanProductDetailsAction + + data object Retry : LoanProductDetailsAction + + data class TermsCheckedChanged(val checked: Boolean) : LoanProductDetailsAction + + data object ApplyClicked : LoanProductDetailsAction + + sealed interface Internal : LoanProductDetailsAction { + data class TemplateLoaded(val result: DataState) : Internal + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderUrls.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderUrls.kt new file mode 100644 index 000000000..18c8a94d1 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderUrls.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +/** + * Maps a loan provider id (see `org.mifos.lib.loan.ui.selectLoanProvider.LoanProviderOption`) to + * the website rendered on [LoanProviderWebViewScreen]. + * + * There's no per-provider backend yet, so every provider currently points at the same test URL; + * this is the single place to swap in the real per-provider URLs once they exist. + */ +internal fun urlForLoanProvider(providerId: String): String = "https://www.google.com" diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewRoute.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewRoute.kt new file mode 100644 index 000000000..87a06c13a --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewRoute.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import kotlinx.serialization.Serializable +import template.core.base.ui.composableWithSlideTransitions + +/** + * Type-safe navigation route for the loan provider website screen, shown right after a provider + * is picked on `org.mifos.lib.loan.ui.selectLoanProvider.SelectLoanProviderRoute`. + * + * @param clientId The id of the client applying for a loan. + * @param providerId The id of the loan provider chosen on the Select Loan Provider screen. + * @param url The provider's website to render (see [urlForLoanProvider]). + */ +@Serializable +data class LoanProviderWebViewRoute(val clientId: Long, val providerId: String, val url: String) + +/** + * Navigates to the loan provider website screen for the given [clientId]/[providerId]/[url]. + */ +fun NavController.navigateToLoanProviderWebView( + clientId: Long, + providerId: String, + url: String, + navOptions: NavOptions? = null, +) { + navigate(LoanProviderWebViewRoute(clientId, providerId, url), navOptions) +} + +/** + * Registers the loan provider website screen in the navigation graph. + * + * @param navigateBack Callback to return to the previous screen (Select Loan Provider). + */ +fun NavGraphBuilder.loanProviderWebViewScreen( + navigateBack: () -> Unit, +) { + composableWithSlideTransitions { + LoanProviderWebViewScreen( + navigateBack = navigateBack, + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewScreen.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewScreen.kt new file mode 100644 index 000000000..84131b44e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewScreen.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_loan_provider_web_view_top_bar_title +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.ui.utils.EventsEffect + +/** + * Entry point for the Loan Provider Website screen — shown right after a provider is picked on + * the Select Loan Provider screen, rendering that provider's website in an embedded web view. + * + * @param navigateBack Callback to return to the Select Loan Provider screen. + * @param viewModel The state holder responsible for resolving which url to render. + */ +@Composable +internal fun LoanProviderWebViewScreen( + navigateBack: () -> Unit, + modifier: Modifier = Modifier, + viewModel: LoanProviderWebViewViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + LoanProviderWebViewEvent.NavigateBack -> navigateBack() + } + } + + LoanProviderWebViewContent( + state = state, + modifier = modifier, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) +} + +@Composable +private fun LoanProviderWebViewContent( + state: LoanProviderWebViewState, + onAction: (LoanProviderWebViewAction) -> Unit, + modifier: Modifier = Modifier, +) { + MifosScaffold( + modifier = modifier, + backPress = { onAction(LoanProviderWebViewAction.NavigateBack) }, + topBarTitle = stringResource(Res.string.feature_loan_provider_web_view_top_bar_title), + ) { paddingValues -> + PlatformWebView( + url = state.url, + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewViewModel.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewViewModel.kt new file mode 100644 index 000000000..4818ace16 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/LoanProviderWebViewViewModel.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.lifecycle.SavedStateHandle +import androidx.navigation.toRoute +import org.mifospay.core.ui.utils.BaseViewModel + +/** + * `ViewModel` for the Loan Provider Website screen — renders the chosen provider's website + * (see [urlForLoanProvider]) in an embedded web view. + * + * @param savedStateHandle Handle used to read the [LoanProviderWebViewRoute] navigation arguments. + */ +internal class LoanProviderWebViewViewModel( + savedStateHandle: SavedStateHandle, +) : BaseViewModel( + initialState = run { + val route = savedStateHandle.toRoute() + LoanProviderWebViewState( + clientId = route.clientId, + providerId = route.providerId, + url = route.url, + ) + }, +) { + + override fun handleAction(action: LoanProviderWebViewAction) { + when (action) { + LoanProviderWebViewAction.NavigateBack -> { + sendEvent(LoanProviderWebViewEvent.NavigateBack) + } + } + } +} + +/** + * UI state for the Loan Provider Website screen. + * + * @property clientId The id of the client applying for a loan. + * @property providerId The id of the chosen loan provider. + * @property url The provider's website to render. + */ +internal data class LoanProviderWebViewState( + val clientId: Long, + val providerId: String, + val url: String, +) + +/** + * One-shot navigation events emitted by [LoanProviderWebViewViewModel]. + */ +internal sealed interface LoanProviderWebViewEvent { + data object NavigateBack : LoanProviderWebViewEvent +} + +/** + * User actions handled by [LoanProviderWebViewViewModel]. + */ +internal sealed interface LoanProviderWebViewAction { + data object NavigateBack : LoanProviderWebViewAction +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.kt new file mode 100644 index 000000000..573442ef9 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** + * Renders [url] in an embedded web view. + * + * Backed by `compose-webview-multiplatform` on Android and iOS. That library doesn't publish + * js/wasmJs artifacts and its desktop support needs a separate KCEF runtime download, so those + * platforms fall back to a plain placeholder instead of an embedded browser (see the respective + * `actual` implementations). + */ +@Composable +expect fun PlatformWebView(url: String, modifier: Modifier = Modifier) diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderRoute.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderRoute.kt new file mode 100644 index 000000000..7161d8244 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderRoute.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.selectLoanProvider + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import kotlinx.serialization.Serializable +import template.core.base.ui.composableWithSlideTransitions + +/** + * Type-safe navigation route for the loan provider selection screen — the new entry point of the + * loan application wizard, shown before [org.mifos.lib.loan.ui.selectLoanType.SelectLoanTypeRoute]. + * + * @param clientId The id of the client applying for a loan. + */ +@Serializable +data class SelectLoanProviderRoute(val clientId: Long) + +/** + * Navigates to the loan provider selection screen for the given [clientId]. + */ +fun NavController.navigateToSelectLoanProvider(clientId: Long, navOptions: NavOptions? = null) { + navigate(SelectLoanProviderRoute(clientId), navOptions) +} + +/** + * Registers the loan provider selection screen in the navigation graph. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToSelectLoanType Callback to proceed into the existing loan-type selection step + * once a provider has been chosen. + */ +fun NavGraphBuilder.selectLoanProviderScreen( + navigateBack: () -> Unit, + navigateToLoanProviderWebView: ( + clientId: Long, + providerId: String, + url: String, + ) -> Unit, +) { + composableWithSlideTransitions { + SelectLoanProviderScreen( + navigateBack = navigateBack, + navigateToLoanProviderWebView = navigateToLoanProviderWebView, + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderScreen.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderScreen.kt new file mode 100644 index 000000000..d77b8ae18 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderScreen.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.selectLoanProvider + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_select_loan_provider_choose_provider +import mobile_wallet.libs.mifos_loans.generated.resources.feature_select_loan_provider_top_bar_title +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifospay.core.designsystem.component.MifosCard +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.ui.utils.EventsEffect +import template.core.base.designsystem.theme.KptTheme + +/** + * Entry point for the Select Loan Provider screen — the new first step of the loan-application + * wizard, shown when the user taps "Apply for Loan". + * + * Shows a short list of (currently dummy/placeholder) loan providers; tapping one proceeds into + * the existing wizard starting at Select Loan Type. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToLoanProviderWebView Callback to proceed into the existing loan-type selection step. + * @param viewModel The state holder responsible for the (static) provider list. + */ +@Composable +internal fun SelectLoanProviderScreen( + navigateBack: () -> Unit, + navigateToLoanProviderWebView: ( + clientId: Long, + providerId: String, + url: String, + ) -> Unit, + modifier: Modifier = Modifier, + viewModel: SelectLoanProviderViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + SelectLoanProviderEvent.NavigateBack -> navigateBack() + + is SelectLoanProviderEvent.NavigateToLoanProvider -> { + navigateToLoanProviderWebView( + event.clientId, + event.providerId, + event.url, + ) + } + } + } + + SelectLoanProviderContent( + state = state, + modifier = modifier, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) +} + +@Composable +private fun SelectLoanProviderContent( + state: SelectLoanProviderState, + onAction: (SelectLoanProviderAction) -> Unit, + modifier: Modifier = Modifier, +) { + MifosScaffold( + modifier = modifier, + backPress = { onAction(SelectLoanProviderAction.NavigateBack) }, + topBarTitle = stringResource(Res.string.feature_select_loan_provider_top_bar_title), + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(top = KptTheme.spacing.md), + ) { + Text( + text = stringResource(Res.string.feature_select_loan_provider_choose_provider), + style = KptTheme.typography.titleSmall, + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) + + LazyColumn( + contentPadding = PaddingValues(KptTheme.spacing.md), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + items(state.providers, key = { it.id }) { provider -> + LoanProviderCard( + provider = provider, + onClick = { + onAction( + SelectLoanProviderAction.ProviderClicked( + providerId = provider.id, + url = provider.url, + ), + ) + }, + ) + } + } + } + } +} + +@Composable +private fun LoanProviderCard( + provider: LoanProviderOption, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + MifosCard( + modifier = modifier, + shape = KptTheme.shapes.medium, + elevation = KptTheme.elevation.level2, + onClick = onClick, + ) { + Text( + text = provider.name, + style = KptTheme.typography.titleMedium, + modifier = Modifier.padding(KptTheme.spacing.lg), + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderViewModel.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderViewModel.kt new file mode 100644 index 000000000..e0b7c2bff --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanProvider/SelectLoanProviderViewModel.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.selectLoanProvider + +import androidx.lifecycle.SavedStateHandle +import androidx.navigation.toRoute +import org.mifospay.core.ui.utils.BaseViewModel + +/** + * `ViewModel` for the Select Loan Provider screen — the new first step of the loan-application + * wizard. + * + * The list of providers is static placeholder data for now (no backend to source it from — see + * [LoanProviderOption] kdoc). Selecting a provider simply forwards the client into the existing + * wizard (Select Loan Type onwards); the chosen provider doesn't yet affect any downstream data. + * + * @param savedStateHandle Handle used to read the [SelectLoanProviderRoute] navigation arguments. + */ +internal class SelectLoanProviderViewModel( + savedStateHandle: SavedStateHandle, +) : BaseViewModel( + initialState = run { + val route = savedStateHandle.toRoute() + SelectLoanProviderState( + clientId = route.clientId, + providers = defaultLoanProviders, + ) + }, +) { + + override fun handleAction(action: SelectLoanProviderAction) { + when (action) { + SelectLoanProviderAction.NavigateBack -> { + sendEvent(SelectLoanProviderEvent.NavigateBack) + } + + is SelectLoanProviderAction.ProviderClicked -> { + sendEvent( + SelectLoanProviderEvent.NavigateToLoanProvider( + clientId = state.clientId, + providerId = action.providerId, + url = action.url, + ), + ) + } + } + } +} + +/** + * A dummy/placeholder loan provider (e.g. "HDFC Bank", "SBI") shown on the Select Loan Provider + * screen. + * + * There is no backend for loan providers yet — this is illustrative UI data only, per the product + * requirement to expose a `loanProvider` selection step ahead of the (currently dummy) product + * flow. Once a real per-provider backend exists, this should be sourced from a repository instead + * of hardcoded here. + * + * @property id A stable identifier for the provider. + * @property name The provider's display name. + * @property url + */ +data class LoanProviderOption( + val id: String, + val name: String, + val url: String, +) + +/** + * The static placeholder list of loan providers. See [LoanProviderOption] for why this is + * hardcoded rather than fetched. + */ +internal val defaultLoanProviders = listOf( + LoanProviderOption(id = "hdfc", name = "HDFC Bank", url = "https://applyonline.hdfc.bank.in/personal-loans"), + LoanProviderOption(id = "sbi", name = "SBI", url = "https://sbi.bank.in/web/personal-banking/loans/personal-loans"), + LoanProviderOption(id = "icici", name = "ICICI Bank", url = "https://www.icici.bank.in/personal-banking/loans"), + LoanProviderOption(id = "axis", name = "Axis Bank", url = "https://www.axis.bank.in/loans/personal-loan"), +) + +/** + * UI state for the Select Loan Provider screen. + * + * @property clientId The id of the client applying for a loan. + * @property providers The list of selectable (currently dummy) loan providers. + */ +internal data class SelectLoanProviderState( + val clientId: Long, + val providers: List = defaultLoanProviders, +) + +/** + * One-shot navigation events emitted by [SelectLoanProviderViewModel]. + */ +sealed interface SelectLoanProviderEvent { + + data object NavigateBack : SelectLoanProviderEvent + + data class NavigateToLoanProvider( + val clientId: Long, + val providerId: String, + val url: String, + ) : SelectLoanProviderEvent +} + +/** + * User actions handled by [SelectLoanProviderViewModel]. + */ +internal sealed interface SelectLoanProviderAction { + data object NavigateBack : SelectLoanProviderAction + + data class ProviderClicked( + val providerId: String, + val url: String, + ) : SelectLoanProviderAction +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeRoute.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeRoute.kt new file mode 100644 index 000000000..2d85436ea --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeRoute.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.selectLoanType + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import kotlinx.serialization.Serializable +import template.core.base.ui.composableWithSlideTransitions + +/** + * Type-safe navigation route for the loan type selection screen — the entry point of the + * loan application wizard. + * + * @param clientId The id of the client applying for a loan. + * @param providerId The id of the loan provider chosen on the Select Loan Provider screen. + */ +@Serializable +data class SelectLoanTypeRoute(val clientId: Long, val providerId: String) + +/** + * Navigates to the loan type selection screen for the given [clientId]/[providerId]. + */ +fun NavController.navigateToSelectLoanType( + clientId: Long, + providerId: String, + navOptions: NavOptions? = null, +) { + navigate(SelectLoanTypeRoute(clientId, providerId), navOptions) +} + +/** + * Registers the loan type selection screen in the navigation graph. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToLoanProductDetails Callback to proceed to the details of a selected product. + */ +fun NavGraphBuilder.selectLoanTypeScreen( + navigateBack: () -> Unit, + navigateToLoanProductDetails: (clientId: Long, productId: Long, providerId: String) -> Unit, +) { + composableWithSlideTransitions { + SelectLoanTypeScreen( + navigateBack = navigateBack, + navigateToLoanProductDetails = navigateToLoanProductDetails, + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeScreen.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeScreen.kt new file mode 100644 index 000000000..b3ebce367 --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeScreen.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.selectLoanType + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_select_loan_type_choose_loan +import mobile_wallet.libs.mifos_loans.generated.resources.feature_select_loan_type_empty +import mobile_wallet.libs.mifos_loans.generated.resources.feature_select_loan_type_top_bar_title +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifos.lib.loan.component.LoanCard +import org.mifos.lib.loan.core.model.ProductOptions +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.ui.EmptyContentScreen +import org.mifospay.core.ui.ErrorScreenContent +import org.mifospay.core.ui.MifosProgressIndicator +import org.mifospay.core.ui.utils.EventsEffect +import template.core.base.designsystem.theme.KptTheme + +/** + * Entry point for the Loan Type Selection screen. + * Fetches available loan products via the ViewModel and handles navigation to the product details. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToLoanProductDetails Callback to navigate to the details of a selected product. + * @param viewModel The state holder responsible for fetching and mapping loan products. + */ +@Composable +internal fun SelectLoanTypeScreen( + navigateBack: () -> Unit, + navigateToLoanProductDetails: (clientId: Long, productId: Long, providerId: String) -> Unit, + modifier: Modifier = Modifier, + viewModel: SelectLoanTypeViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + SelectLoanTypeEvent.NavigateBack -> navigateBack() + + is SelectLoanTypeEvent.NavigateToProductDetails -> { + navigateToLoanProductDetails(event.clientId, event.productId, event.providerId) + } + } + } + + SelectLoanTypeContent( + state = state, + modifier = modifier, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) +} + +@Composable +private fun SelectLoanTypeContent( + state: SelectLoanTypeState, + onAction: (SelectLoanTypeAction) -> Unit, + modifier: Modifier = Modifier, +) { + MifosScaffold( + modifier = modifier, + backPress = { onAction(SelectLoanTypeAction.NavigateBack) }, + topBarTitle = stringResource(Res.string.feature_select_loan_type_top_bar_title), + ) { paddingValues -> + when (val viewState = state.viewState) { + SelectLoanTypeState.ViewState.Loading -> MifosProgressIndicator() + + is SelectLoanTypeState.ViewState.Error -> { + ErrorScreenContent( + subTitle = viewState.message, + onClickRetry = { onAction(SelectLoanTypeAction.Retry) }, + ) + } + + is SelectLoanTypeState.ViewState.Content -> { + if (viewState.productOptions.isEmpty()) { + EmptyContentScreen( + title = stringResource(Res.string.feature_select_loan_type_top_bar_title), + subTitle = stringResource(Res.string.feature_select_loan_type_empty), + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + ) + } else { + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(top = KptTheme.spacing.md), + ) { + Text( + text = stringResource(Res.string.feature_select_loan_type_choose_loan), + style = KptTheme.typography.titleSmall, + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) + + LazyColumn( + contentPadding = PaddingValues(KptTheme.spacing.md), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + items(viewState.productOptions, key = { it.id ?: -1 }) { product -> + LoanTypeCard( + product = product, + onClick = { + product.id?.let { id -> + onAction(SelectLoanTypeAction.ProductClicked(id)) + } + }, + ) + } + } + } + } + } + } + } +} + +@Composable +private fun LoanTypeCard( + product: ProductOptions, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + LoanCard( + modifier = modifier, + title = product.name.orEmpty(), + amount = "", + interestRate = "", + onClick = onClick, + ) +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeViewModel.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeViewModel.kt new file mode 100644 index 000000000..882ab82ed --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/selectLoanType/SelectLoanTypeViewModel.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.selectLoanType + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.mifos.lib.loan.core.model.LoanTemplate +import org.mifos.lib.loan.core.model.ProductOptions +import org.mifos.lib.loan.repository.LoansRepository +import org.mifospay.core.common.DataState +import org.mifospay.core.ui.utils.BaseViewModel + +/** + * `ViewModel` for the Select Loan Type screen. + * + * Fetches the loan template for the current client and exposes its [ProductOptions] so the + * screen can render a selectable list of loan products. + * + * @param savedStateHandle Handle used to read the [SelectLoanTypeRoute] navigation arguments. + * @param loansRepository Facade resolving the loan provider chosen for this application. + */ +internal class SelectLoanTypeViewModel( + savedStateHandle: SavedStateHandle, + private val loansRepository: LoansRepository, +) : BaseViewModel( + initialState = run { + val route = savedStateHandle.toRoute() + SelectLoanTypeState(clientId = route.clientId, providerId = route.providerId) + }, +) { + + init { + loadLoanTemplate() + } + + override fun handleAction(action: SelectLoanTypeAction) { + when (action) { + SelectLoanTypeAction.NavigateBack -> sendEvent(SelectLoanTypeEvent.NavigateBack) + + is SelectLoanTypeAction.ProductClicked -> { + sendEvent( + SelectLoanTypeEvent.NavigateToProductDetails( + clientId = state.clientId, + productId = action.productId.toLong(), + providerId = state.providerId, + ), + ) + } + + SelectLoanTypeAction.Retry -> loadLoanTemplate() + + is SelectLoanTypeAction.Internal.TemplateLoaded -> handleTemplateLoaded(action.result) + } + } + + private fun loadLoanTemplate() { + mutableStateFlow.update { it.copy(viewState = SelectLoanTypeState.ViewState.Loading) } + viewModelScope.launch { + loansRepository.getLoanTemplate(state.providerId, state.clientId).collect { result -> + sendAction(SelectLoanTypeAction.Internal.TemplateLoaded(result)) + } + } + } + + private fun handleTemplateLoaded(result: DataState) { + when (result) { + is DataState.Loading -> { + mutableStateFlow.update { it.copy(viewState = SelectLoanTypeState.ViewState.Loading) } + } + + is DataState.Success -> { + mutableStateFlow.update { + it.copy( + viewState = SelectLoanTypeState.ViewState.Content( + productOptions = result.data.productOptions, + ), + ) + } + } + + is DataState.Error -> { + mutableStateFlow.update { + it.copy(viewState = SelectLoanTypeState.ViewState.Error(result.message)) + } + } + } + } +} + +/** + * UI state for the Select Loan Type screen. + * + * @property clientId The id of the client applying for a loan. + * @property providerId The id of the loan provider chosen on the Select Loan Provider screen. + * @property viewState The current content/loading/error state of the screen. + */ +internal data class SelectLoanTypeState( + val clientId: Long, + val providerId: String, + val viewState: ViewState = ViewState.Loading, +) { + sealed interface ViewState { + data object Loading : ViewState + data class Content(val productOptions: List) : ViewState + data class Error(val message: String) : ViewState + } +} + +/** + * One-shot navigation events emitted by [SelectLoanTypeViewModel]. + */ +internal sealed interface SelectLoanTypeEvent { + data object NavigateBack : SelectLoanTypeEvent + + data class NavigateToProductDetails( + val clientId: Long, + val productId: Long, + val providerId: String, + ) : SelectLoanTypeEvent +} + +/** + * User actions and internal events handled by [SelectLoanTypeViewModel]. + */ +internal sealed interface SelectLoanTypeAction { + data object NavigateBack : SelectLoanTypeAction + + data object Retry : SelectLoanTypeAction + + data class ProductClicked(val productId: Int) : SelectLoanTypeAction + + sealed interface Internal : SelectLoanTypeAction { + data class TemplateLoaded(val result: DataState) : Internal + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsRoute.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsRoute.kt new file mode 100644 index 000000000..f1ecb9c5e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsRoute.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.uploadDocs + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import kotlinx.serialization.Serializable +import template.core.base.ui.composableWithSlideTransitions + +/** + * Type-safe navigation route for the document upload step of the loan application wizard. + * + * Carries the fully-populated application form data forward from [org.mifos.lib.loan.ui.loanApply], + * so it can be threaded through to the Confirm Details step without re-fetching it. + * + * @param clientId The id of the client applying for a loan. + * @param productId The id of the loan product being applied for. + * @param applicantName The applicant's display name. + * @param loanProductName The selected loan product's display name. + * @param loanPurpose The stated purpose for the loan. + * @param disbursementDate The scheduled date for funds disbursement. + * @param principalAmount The requested loan amount. + * @param providerId The id of the loan provider chosen on the Select Loan Provider screen. + */ +@Serializable +data class UploadDocsRoute( + val clientId: Long, + val productId: Long, + val applicantName: String, + val loanProductName: String, + val loanPurpose: String, + val disbursementDate: String, + val principalAmount: String, + val providerId: String, +) + +/** + * Navigates to the document upload step with the fully-populated application form data. + */ +fun NavController.navigateToUploadDocs( + clientId: Long, + productId: Long, + applicantName: String, + loanProductName: String, + loanPurpose: String, + disbursementDate: String, + principalAmount: String, + providerId: String, + navOptions: NavOptions? = null, +) { + navigate( + UploadDocsRoute( + clientId = clientId, + productId = productId, + applicantName = applicantName, + loanProductName = loanProductName, + loanPurpose = loanPurpose, + disbursementDate = disbursementDate, + principalAmount = principalAmount, + providerId = providerId, + ), + navOptions, + ) +} + +/** + * Registers the document upload screen in the navigation graph. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToConfirmDetails Callback to proceed to the Confirm Details step once all + * required documents have been uploaded. Receives the same application form data this + * destination was entered with, so it can be threaded through unchanged. + */ +fun NavGraphBuilder.uploadDocsScreen( + navigateBack: () -> Unit, + navigateToConfirmDetails: ( + clientId: Long, + productId: Long, + applicantName: String, + loanProductName: String, + loanPurpose: String, + disbursementDate: String, + principalAmount: String, + providerId: String, + ) -> Unit, +) { + composableWithSlideTransitions { + UploadDocsScreen( + navigateBack = navigateBack, + navigateToConfirmDetails = navigateToConfirmDetails, + ) + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsScreen.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsScreen.kt new file mode 100644 index 000000000..40839373a --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsScreen.kt @@ -0,0 +1,238 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.uploadDocs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.BottomSheetDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_apply_loan_section_fill_details +import mobile_wallet.libs.mifos_loans.generated.resources.feature_button_next +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel +import org.mifos.lib.loan.component.UploadDocumentsSection +import org.mifos.lib.loan.ui.uploadDocs.component.BottomSheetContent +import org.mifospay.core.designsystem.component.BasicDialogState +import org.mifospay.core.designsystem.component.MifosBasicDialog +import org.mifospay.core.designsystem.component.MifosButton +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.designsystem.component.MifosTopBar +import org.mifospay.core.ui.utils.EventsEffect +import template.core.base.designsystem.theme.KptTheme + +/** + * Entry point for the Document Upload screen. + * + * Orchestrates the UI state, handles navigation side effects, and manages the display of + * dialogs and bottom sheets for uploading the bank statement, property document, and + * signature required by the loan application. + * + * @param navigateBack Callback to return to the previous screen. + * @param navigateToConfirmDetails Callback to proceed to the Confirm Details step once all + * required documents have been uploaded. + * @param viewModel The state holder managing document selection and upload logic. + */ +@Composable +internal fun UploadDocsScreen( + navigateBack: () -> Unit, + navigateToConfirmDetails: ( + clientId: Long, + productId: Long, + applicantName: String, + loanProductName: String, + loanPurpose: String, + disbursementDate: String, + principalAmount: String, + providerId: String, + ) -> Unit, + modifier: Modifier = Modifier, + viewModel: UploadDocsViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + UploadDocsEvent.NavigateBack -> navigateBack() + + is UploadDocsEvent.NavigateToConfirmDetails -> { + navigateToConfirmDetails( + event.clientId, + event.productId, + event.applicantName, + event.loanProductName, + event.loanPurpose, + event.disbursementDate, + event.principalAmount, + event.providerId, + ) + } + } + } + + UploadDocsDialogs( + dialogState = state.dialogState, + onAction = remember(viewModel) { + { viewModel.trySendAction(it) } + }, + ) + + UploadDocsScreenContent( + modifier = modifier, + state = state, + onAction = { + viewModel.trySendAction(it) + }, + ) +} + +/** + * Renders overlay UIs such as error alerts or the signature capture bottom sheet based on + * the active dialog state. + * + * @param dialogState The current state determining which dialog or sheet to show. + * @param onAction Callback to handle dialog interactions (dismissal, mode switching). + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun UploadDocsDialogs( + dialogState: UploadDocumentDialog?, + onAction: (UploadDocsAction) -> Unit, +) { + when (dialogState) { + is UploadDocumentDialog.Error -> { + MifosBasicDialog( + visibilityState = BasicDialogState.Shown( + message = stringResource(dialogState.error), + ), + onDismissRequest = { + onAction(UploadDocsAction.DismissDialog) + }, + ) + } + + is UploadDocumentDialog.ShowSignaturePicker -> { + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = dialogState.isSignatureMode, + ) + + // Animate the sheet expansion when transitioning to signature mode + LaunchedEffect(dialogState.isSignatureMode) { + if (dialogState.isSignatureMode) { + sheetState.expand() + } + } + + ModalBottomSheet( + onDismissRequest = { + onAction(UploadDocsAction.DismissDialog) + }, + sheetState = sheetState, + containerColor = Color.White, + contentWindowInsets = { + if (dialogState.isSignatureMode) { + WindowInsets(0) + } else { + BottomSheetDefaults.windowInsets + } + }, + modifier = if (dialogState.isSignatureMode) { + Modifier.fillMaxSize() + } else { + Modifier.wrapContentHeight() + }, + dragHandle = { + if (dialogState.isSignatureMode) { + null + } else { + BottomSheetDefaults.DragHandle() + } + }, + ) { + BottomSheetContent( + onAction = onAction, + isSignatureMode = dialogState.isSignatureMode, + ) + } + } + + null -> Unit + } +} + +/** + * Displays the main layout containing the document list and the "Next" button. + * + * @param state The current UI state containing the list of required and uploaded documents. + * @param onAction Callback to handle user interactions like clicking upload or continue. + */ +@Composable +internal fun UploadDocsScreenContent( + state: UploadDocsState, + modifier: Modifier = Modifier, + onAction: (UploadDocsAction) -> Unit, +) { + MifosScaffold( + modifier = modifier, + topBar = { + MifosTopBar( + topBarTitle = stringResource(Res.string.feature_apply_loan_section_fill_details), + backPress = { onAction(UploadDocsAction.OnNavigateBack) }, + ) + }, + ) { paddingValues -> + Column( + Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + .padding(KptTheme.spacing.md), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + UploadDocumentsSection( + state = state, + onAction = onAction, + ) + + MifosButton( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + onClick = { + onAction(UploadDocsAction.NavigateToNextScreen) + }, + enabled = state.isSubmitEnabled, + ) { + Text( + text = stringResource(Res.string.feature_button_next), + ) + } + } + } +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsViewModel.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsViewModel.kt new file mode 100644 index 000000000..172283c2e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/UploadDocsViewModel.kt @@ -0,0 +1,436 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.uploadDocs + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import io.github.vinceglb.filekit.FileKit +import io.github.vinceglb.filekit.dialogs.FileKitType +import io.github.vinceglb.filekit.dialogs.openFilePicker +import io.github.vinceglb.filekit.name +import io.github.vinceglb.filekit.readBytes +import io.github.vinceglb.filekit.size +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.feature_upload_file_error +import org.jetbrains.compose.resources.StringResource +import org.mifos.lib.loan.component.DocumentType +import org.mifos.lib.loan.component.SignatureUploadType +import org.mifospay.core.ui.utils.BaseViewModel +import template.core.base.common.toBase64DataUri +import kotlin.io.encoding.ExperimentalEncodingApi + +/** + * `ViewModel` for the Upload Documents screen. + * + * Handles local file selection for the bank statement, property document, and signature + * required by the loan application, driven purely on-device via FileKit — there is no backend + * call here (mirrors the source app, where document submission is not yet implemented). + * Once all three are present, "Next" forwards the application form data carried in + * [UploadDocsRoute] on to the Confirm Details step. + * + * @param savedStateHandle Handle used to read the [UploadDocsRoute] navigation arguments. + */ +@Suppress("CyclomaticComplexMethod") +@OptIn(ExperimentalEncodingApi::class) +internal class UploadDocsViewModel( + savedStateHandle: SavedStateHandle, +) : BaseViewModel( + initialState = run { + val route = savedStateHandle.toRoute() + UploadDocsState( + clientId = route.clientId, + productId = route.productId, + applicantName = route.applicantName, + loanProductName = route.loanProductName, + loanPurpose = route.loanPurpose, + disbursementDate = route.disbursementDate, + principalAmount = route.principalAmount, + providerId = route.providerId, + ) + }, +) { + override fun handleAction(action: UploadDocsAction) { + when (action) { + is UploadDocsAction.OnNavigateBack -> { + sendEvent(UploadDocsEvent.NavigateBack) + } + + is UploadDocsAction.RemoveDocument -> { + removeFile(action.type) + } + + is UploadDocsAction.SelectNewDocument -> handleDocumentUpload(action.type) + + is UploadDocsAction.UploadDocument -> handleDocumentUpload(action.type) + + is UploadDocsAction.ShowSignatureUploadSheet -> { + mutableStateFlow.update { + it.copy( + dialogState = UploadDocumentDialog.ShowSignaturePicker(isSignatureMode = false), + ) + } + } + + is UploadDocsAction.DismissDialog -> { + mutableStateFlow.update { + it.copy(dialogState = null) + } + } + + is UploadDocsAction.UploadSignature -> handleUploadSignature(action) + + is UploadDocsAction.UploadSign -> { + mutableStateFlow.update { + it.copy( + dialogState = null, + signatureDocumentFile = action.image, + signatureFileName = "signature.png", + signatureSize = null, + ) + } + } + + is UploadDocsAction.NavigateToNextScreen -> { + mutableStateFlow.update { + it.copy(dialogState = null) + } + sendEvent( + UploadDocsEvent.NavigateToConfirmDetails( + clientId = state.clientId, + productId = state.productId, + applicantName = state.applicantName, + loanProductName = state.loanProductName, + loanPurpose = state.loanPurpose, + disbursementDate = state.disbursementDate, + principalAmount = state.principalAmount, + providerId = state.providerId, + ), + ) + } + } + } + + /** + * Handles a user's request to upload a document by delegating to a specific + * upload function based on the document type. + * + * @param type The [DocumentType] to be uploaded. + */ + private fun handleDocumentUpload(type: DocumentType) { + when (type) { + DocumentType.BANK_STATEMENT -> uploadBankStatements() + DocumentType.PROPERTY_DOCUMENT -> uploadPropertyDocument() + DocumentType.SIGNATURE -> Unit + } + } + + /** + * Removes a document of a specific type from the state. + * + * @param type The [DocumentType] of the document to be removed. + */ + private fun removeFile(type: DocumentType) { + when (type) { + DocumentType.BANK_STATEMENT -> { + mutableStateFlow.update { + it.copy( + bankStatementFile = null, + bankStatementFileName = null, + bankStatementSize = null, + ) + } + } + + DocumentType.PROPERTY_DOCUMENT -> { + mutableStateFlow.update { + it.copy( + propertyDocumentsFile = null, + propertyDocumentFileName = null, + propertyDocumentsSize = null, + ) + } + } + + DocumentType.SIGNATURE -> { + mutableStateFlow.update { + it.copy( + signatureDocumentFile = null, + signatureFileName = null, + signatureSize = null, + ) + } + } + } + } + + /** + * Launches a file picker to select a property document and updates the state + * with the selected file's data and metadata. + */ + private fun uploadPropertyDocument() { + viewModelScope.launch { + try { + val image = FileKit.openFilePicker(type = FileKitType.Image) + image?.let { file -> + mutableStateFlow.update { + it.copy( + propertyDocumentsFile = file.readBytes().toBase64DataUri(), + propertyDocumentFileName = file.name, + propertyDocumentsSize = formatFileSize(file.size()), + ) + } + } + } catch (e: Exception) { + e.printStackTrace() + handleErrorMessage(Res.string.feature_upload_file_error) + } + } + } + + /** + * Launches a file picker to select a bank statement and updates the state + * with the selected file's data and metadata. + */ + private fun uploadBankStatements() { + viewModelScope.launch { + try { + val image = FileKit.openFilePicker(type = FileKitType.Image) + image?.let { file -> + mutableStateFlow.update { + it.copy( + bankStatementFile = file.readBytes().toBase64DataUri(), + bankStatementFileName = file.name, + bankStatementSize = formatFileSize(file.size()), + ) + } + } + } catch (e: Exception) { + e.printStackTrace() + handleErrorMessage(Res.string.feature_upload_file_error) + } + } + } + + /** + * Handles the user's request to upload a signature by delegating to the appropriate + * function based on the signature upload type. + * + * @param action The [UploadDocsAction.UploadSignature] containing the type of upload. + */ + private fun handleUploadSignature(action: UploadDocsAction.UploadSignature) { + when (action.type) { + SignatureUploadType.SIGN -> handleShowSignatureUploadSheet() + SignatureUploadType.CAPTURE -> uploadSignature() + SignatureUploadType.GALLERY -> uploadSignature() + } + } + + /** + * Launches a file picker to select an image for the signature and updates the + * state with the selected file's data. + */ + private fun uploadSignature() { + viewModelScope.launch { + try { + val image = FileKit.openFilePicker(type = FileKitType.Image) + image?.let { file -> + mutableStateFlow.update { + it.copy( + dialogState = null, + signatureDocumentFile = file.readBytes().toBase64DataUri(), + signatureFileName = file.name, + signatureSize = formatFileSize(file.size()), + ) + } + } + } catch (e: Exception) { + e.printStackTrace() + handleErrorMessage(Res.string.feature_upload_file_error) + } + } + } + + /** + * Updates the dialog state to show the signature drawing sheet. + */ + private fun handleShowSignatureUploadSheet() { + mutableStateFlow.update { + it.copy( + dialogState = UploadDocumentDialog.ShowSignaturePicker(isSignatureMode = true), + ) + } + } + + /** + * Updates the state to show an error dialog. + * + * @param message The [StringResource] for the error message. + */ + private fun handleErrorMessage(message: StringResource) { + mutableStateFlow.update { + it.copy( + dialogState = UploadDocumentDialog.Error(message), + ) + } + } + + private fun formatFileSize(sizeInBytes: Long): String { + val sizeInKB = sizeInBytes / BYTES_PER_KB + val sizeInMb = sizeInKB / BYTES_PER_KB + return if (sizeInMb >= 1) "$sizeInMb MB" else "$sizeInKB KB" + } + + private companion object { + const val BYTES_PER_KB = 1024 + } +} + +/** + * Represents the UI state for the Upload Documents screen. + * + * @property clientId The id of the client applying for a loan, forwarded to Confirm Details. + * @property productId The id of the loan product being applied for, forwarded to Confirm Details. + * @property applicantName The applicant's display name, forwarded to Confirm Details. + * @property loanProductName The selected loan product's display name, forwarded to Confirm Details. + * @property loanPurpose The stated purpose for the loan, forwarded to Confirm Details. + * @property disbursementDate The scheduled disbursement date, forwarded to Confirm Details. + * @property principalAmount The requested loan amount, forwarded to Confirm Details. + * @property providerId The id of the loan provider chosen on the Select Loan Provider screen, + * forwarded to Confirm Details. + * @property bankStatementFile The base64-encoded string of the bank statement file, or `null`. + * @property propertyDocumentsFile The base64-encoded string of the property document file, or `null`. + * @property signatureDocumentFile The base64-encoded string of the signature file, or `null`. + * @property bankStatementSize The size of the bank statement file as a formatted string, or `null`. + * @property propertyDocumentsSize The size of the property document file as a formatted string, or `null`. + * @property signatureSize The size of the signature file as a formatted string, or `null`. + * @property bankStatementFileName The name of the bank statement file, or `null`. + * @property propertyDocumentFileName The name of the property document file, or `null`. + * @property signatureFileName The name of the signature file, or `null`. + * @property dialogState The state of any dialog to be shown on the screen. + */ +internal data class UploadDocsState( + val clientId: Long, + val productId: Long, + val applicantName: String, + val loanProductName: String, + val loanPurpose: String, + val disbursementDate: String, + val principalAmount: String, + val providerId: String, + val bankStatementFile: String? = null, + val propertyDocumentsFile: String? = null, + val signatureDocumentFile: String? = null, + val bankStatementSize: String? = null, + val propertyDocumentsSize: String? = null, + val signatureSize: String? = null, + val bankStatementFileName: String? = null, + val propertyDocumentFileName: String? = null, + val signatureFileName: String? = null, + val dialogState: UploadDocumentDialog? = null, +) { + /** + * A boolean indicating if the "Next" button should be enabled. + */ + val isSubmitEnabled: Boolean + get() = bankStatementFile != null && propertyDocumentsFile != null && signatureDocumentFile != null +} + +/** + * A sealed interface representing the different types of dialogs that can be + * shown on the Upload Documents screen. + */ +internal sealed interface UploadDocumentDialog { + /** + * Represents a modal bottom sheet for signature upload options. + * @property isSignatureMode A boolean to control the content shown inside the sheet. + */ + data class ShowSignaturePicker(val isSignatureMode: Boolean) : UploadDocumentDialog + + /** + * Represents a generic error dialog with a message. + * @property error The [StringResource] for the error message. + */ + data class Error(val error: StringResource) : UploadDocumentDialog +} + +/** + * A sealed interface representing one-time events that trigger UI side effects, + * such as navigation. + */ +internal sealed interface UploadDocsEvent { + /** Event to navigate back from the screen. */ + data object NavigateBack : UploadDocsEvent + + /** + * Event to navigate to the Confirm Details step once all required documents have been + * uploaded, carrying forward the application form data unchanged. + */ + data class NavigateToConfirmDetails( + val clientId: Long, + val productId: Long, + val applicantName: String, + val loanProductName: String, + val loanPurpose: String, + val disbursementDate: String, + val principalAmount: String, + val providerId: String, + ) : UploadDocsEvent +} + +/** + * A sealed interface representing user actions that the ViewModel needs to handle for the + * Upload Documents screen. + */ +internal sealed interface UploadDocsAction { + /** User action to navigate back. */ + data object OnNavigateBack : UploadDocsAction + + /** Action to navigate to the Confirm Details step. */ + data object NavigateToNextScreen : UploadDocsAction + + /** User action to dismiss a dialog. */ + data object DismissDialog : UploadDocsAction + + /** User action to show the signature upload modal bottom sheet. */ + data object ShowSignatureUploadSheet : UploadDocsAction + + /** + * User action to select a signature based on a specific method. + * @property type The [SignatureUploadType] for the chosen method. + */ + data class UploadSignature(val type: SignatureUploadType) : UploadDocsAction + + /** + * User action to upload a signed image. + * @property image The base64-encoded string of the signature image. + */ + data class UploadSign(val image: String) : UploadDocsAction + + /** + * User action to initiate the upload of a document. + * @property type The [DocumentType] to be uploaded. + */ + data class UploadDocument(val type: DocumentType) : UploadDocsAction + + /** + * User action to remove an uploaded document. + * @property type The [DocumentType] to be removed. + */ + data class RemoveDocument(val type: DocumentType) : UploadDocsAction + + /** + * User action to select a new document for a given type. + * @property type The [DocumentType] for which a new document is being selected. + */ + data class SelectNewDocument(val type: DocumentType) : UploadDocsAction +} diff --git a/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/component/BottomSheetContent.kt b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/component/BottomSheetContent.kt new file mode 100644 index 000000000..5cdb5831e --- /dev/null +++ b/libs/mifos-loans/src/commonMain/kotlin/org/mifos/lib/loan/ui/uploadDocs/component/BottomSheetContent.kt @@ -0,0 +1,301 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.uploadDocs.component + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.toSize +import com.niyajali.compose.sign.ComposeSign +import com.niyajali.compose.sign.exportSignature +import com.niyajali.compose.sign.rememberSignatureState +import io.github.vinceglb.filekit.dialogs.compose.util.encodeToByteArray +import kotlinx.coroutines.launch +import mobile_wallet.libs.mifos_loans.generated.resources.Res +import mobile_wallet.libs.mifos_loans.generated.resources.attach +import mobile_wallet.libs.mifos_loans.generated.resources.capture +import mobile_wallet.libs.mifos_loans.generated.resources.reset +import mobile_wallet.libs.mifos_loans.generated.resources.save_and_submit +import mobile_wallet.libs.mifos_loans.generated.resources.sign +import mobile_wallet.libs.mifos_loans.generated.resources.sign_here +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.stringResource +import org.mifos.lib.loan.component.SignatureUploadType +import org.mifos.lib.loan.ui.uploadDocs.UploadDocsAction +import org.mifospay.core.designsystem.component.MifosButton +import org.mifospay.core.designsystem.component.MifosCard +import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.designsystem.component.MifosTopBar +import org.mifospay.core.designsystem.icon.MifosIcons +import template.core.base.common.toBase64DataUri +import template.core.base.designsystem.theme.KptTheme + +/** + * Orchestrates the bottom sheet UI, transitioning between the document upload method selection + * and the digital signature drawing canvas based on the current mode. + * + * @param onAction Callback to handle upload selection or signature submission events. + * @param isSignatureMode Determines whether to display the drawing canvas (true) or selection grid (false). + */ +@Composable +internal fun BottomSheetContent( + onAction: (UploadDocsAction) -> Unit, + modifier: Modifier = Modifier, + isSignatureMode: Boolean = false, +) { + AnimatedContent( + targetState = isSignatureMode, + transitionSpec = { + slideInVertically( + initialOffsetY = { it }, + animationSpec = tween(durationMillis = 400, easing = FastOutSlowInEasing), + ) + fadeIn( + animationSpec = tween(durationMillis = 300, delayMillis = 100), + ) togetherWith slideOutVertically( + targetOffsetY = { -it }, + animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing), + ) + fadeOut( + animationSpec = tween(durationMillis = 200), + ) + }, + modifier = modifier, + ) { isSignature -> + if (isSignature) { + SignatureContent( + onAction = onAction, + ) + } else { + Column( + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) { + Row { + BottomSheetIconContainer( + text = Res.string.sign, + icon = MifosIcons.Edit, + onClick = { + onAction(UploadDocsAction.UploadSignature(SignatureUploadType.SIGN)) + }, + ) + + BottomSheetIconContainer( + text = Res.string.capture, + icon = MifosIcons.Camera, + onClick = { + onAction(UploadDocsAction.UploadSignature(SignatureUploadType.CAPTURE)) + }, + ) + + BottomSheetIconContainer( + text = Res.string.attach, + icon = MifosIcons.PhotoLibrary, + onClick = { + onAction(UploadDocsAction.UploadSignature(SignatureUploadType.GALLERY)) + }, + ) + } + } + } + } +} + +/** + * Renders a single action item (Icon + Label) within the upload method selection grid. + * + * @param text The label resource to display below the icon. + * @param icon The icon vector to display. + * @param onClick Action to perform when this item is selected. + */ +@Composable +internal fun BottomSheetIconContainer( + text: StringResource, + icon: ImageVector, + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier.padding(horizontal = KptTheme.spacing.md), + ) { + Box( + modifier = Modifier.size(56.dp) + .border( + 1.dp, + KptTheme.colorScheme.secondaryContainer, + KptTheme.shapes.medium, + ) + .clickable { onClick() }, + contentAlignment = Alignment.Center, + ) { + Image( + modifier = Modifier.size(24.dp), + imageVector = icon, + contentDescription = null, + ) + } + + Spacer(modifier = Modifier.height(KptTheme.spacing.sm)) + + Text( + text = stringResource(text), + style = KptTheme.typography.bodySmall, + color = KptTheme.colorScheme.onBackground, + ) + } +} + +/** + * Provides an interactive canvas for capturing the user's digital signature. + * Handles the logic for clearing the canvas and exporting the drawing to a Base64 string. + * + * @param onAction Callback to send the generated signature data or dismiss the screen. + */ +@Composable +private fun SignatureContent( + modifier: Modifier = Modifier, + onAction: (UploadDocsAction) -> Unit, +) { + MifosScaffold( + modifier = modifier, + topBar = { + MifosTopBar( + topBarTitle = stringResource(Res.string.sign_here), + backPress = { + onAction(UploadDocsAction.DismissDialog) + }, + ) + }, + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(KptTheme.spacing.md), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + val signatureState = rememberSignatureState() + var size = remember { Size.Zero } + + AnimatedVisibility( + visible = true, + enter = scaleIn( + animationSpec = tween(durationMillis = 500, delayMillis = 200), + initialScale = 0.8f, + ) + fadeIn( + animationSpec = tween(durationMillis = 400, delayMillis = 200), + ), + modifier = Modifier.weight(1f), + ) { + MifosCard( + modifier = Modifier + .fillMaxWidth() + .border( + 1.dp, + KptTheme.colorScheme.secondaryContainer, + KptTheme.shapes.medium, + ), + shape = KptTheme.shapes.medium, + ) { + ComposeSign( + modifier = Modifier + .fillMaxSize() + .onGloballyPositioned { + size = it.size.toSize() + }, + state = signatureState, + backgroundColor = Color.Transparent, + showGrid = true, + onSignatureUpdate = {}, + ) + } + } + + AnimatedVisibility( + visible = true, + enter = slideInVertically( + initialOffsetY = { it }, + animationSpec = tween(durationMillis = 400, delayMillis = 300), + ) + fadeIn( + animationSpec = tween(durationMillis = 300, delayMillis = 300), + ), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + MifosButton( + modifier = Modifier.weight(0.4f), + shape = KptTheme.shapes.medium, + onClick = signatureState::clear, + ) { + Text( + text = stringResource(Res.string.reset), + style = KptTheme.typography.titleMedium, + ) + } + + val scope = rememberCoroutineScope() + MifosButton( + modifier = Modifier.weight(0.4f), + shape = KptTheme.shapes.medium, + onClick = { + scope.launch { + val data = signatureState.exportSignature( + width = size.width.toInt(), + height = size.height.toInt(), + backgroundColor = Color.White, + ) + if (data != null) { + val encoded = data.encodeToByteArray().toBase64DataUri() + onAction(UploadDocsAction.UploadSign(encoded)) + } + } + }, + ) { + Text( + text = stringResource(Res.string.save_and_submit), + style = KptTheme.typography.titleMedium, + ) + } + } + } + } + } +} diff --git a/libs/mifos-loans/src/desktopMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.desktop.kt b/libs/mifos-loans/src/desktopMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.desktop.kt new file mode 100644 index 000000000..fe2747844 --- /dev/null +++ b/libs/mifos-loans/src/desktopMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.desktop.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +/** + * `compose-webview-multiplatform`'s desktop target needs a separate KCEF runtime download, which + * is out of scope here, so this just surfaces the target URL instead of embedding a real browser. + */ +@Composable +actual fun PlatformWebView(url: String, modifier: Modifier) { + Box(modifier = modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) { + Text(text = url) + } +} diff --git a/libs/mifos-loans/src/jsMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.js.kt b/libs/mifos-loans/src/jsMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.js.kt new file mode 100644 index 000000000..41b657221 --- /dev/null +++ b/libs/mifos-loans/src/jsMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.js.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +/** + * `compose-webview-multiplatform` doesn't publish a js target, so this just surfaces the target + * URL instead of embedding a real browser. + */ +@Composable +actual fun PlatformWebView(url: String, modifier: Modifier) { + Box(modifier = modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) { + Text(text = url) + } +} diff --git a/libs/mifos-loans/src/nativeMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.native.kt b/libs/mifos-loans/src/nativeMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.native.kt new file mode 100644 index 000000000..41fb0869d --- /dev/null +++ b/libs/mifos-loans/src/nativeMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.native.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.multiplatform.webview.web.WebView +import com.multiplatform.webview.web.rememberWebViewState + +@Composable +actual fun PlatformWebView(url: String, modifier: Modifier) { + val state = rememberWebViewState(url) + WebView(state = state, modifier = modifier) +} diff --git a/libs/mifos-loans/src/wasmJsMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.wasmJs.kt b/libs/mifos-loans/src/wasmJsMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.wasmJs.kt new file mode 100644 index 000000000..276f67907 --- /dev/null +++ b/libs/mifos-loans/src/wasmJsMain/kotlin/org/mifos/lib/loan/ui/providerWebView/PlatformWebView.wasmJs.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + */ +package org.mifos.lib.loan.ui.providerWebView + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +/** + * `compose-webview-multiplatform` doesn't publish a wasmJs target, so this just surfaces the + * target URL instead of embedding a real browser. + */ +@Composable +actual fun PlatformWebView(url: String, modifier: Modifier) { + Box(modifier = modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) { + Text(text = url) + } +}