From fb969f6280c11d70fa01e4866d244a9ead976542 Mon Sep 17 00:00:00 2001 From: Aditya Gupta <94394661+Aditya-gupta99@users.noreply.github.com> Date: Sat, 6 Jul 2024 23:32:39 +0530 Subject: [PATCH] refactor: refactor add loan account fragment to compose (#2126) --- .../java/com/mifos/core/data/di/DataModule.kt | 11 +- .../data/repository}/LoanAccountRepository.kt | 10 +- .../LoanAccountRepositoryImp.kt | 12 +- .../component/MifosEditTextField.kt | 62 +- .../component/MifosTextFieldDropdown.kt | 8 +- .../use_cases/CreateLoanAccountUseCase.kt | 41 + .../domain/use_cases/GetAllLoanUseCase.kt | 38 + .../GetLoansAccountTemplateUseCase.kt | 41 + feature/loan/.gitignore | 1 + feature/loan/build.gradle.kts | 23 + feature/loan/consumer-rules.pro | 0 feature/loan/proguard-rules.pro | 21 + .../feature/loan/ExampleInstrumentedTest.kt | 24 + feature/loan/src/main/AndroidManifest.xml | 4 + .../loan/loan_account/LoanAccountScreen.kt | 644 +++++++++++++++ .../loan/loan_account/LoanAccountUiState.kt | 19 + .../loan/loan_account/LoanAccountViewModel.kt | 75 ++ feature/loan/src/main/res/values/strings.xml | 37 + .../com/mifos/feature/loan/ExampleUnitTest.kt | 17 + mifosng-android/build.gradle.kts | 1 + .../injection/module/RepositoryModule.kt | 7 - .../online/loanaccount/LoanAccountFragment.kt | 748 ++++-------------- .../online/loanaccount/LoanAccountUiState.kt | 23 - .../loanaccount/LoanAccountViewModel.kt | 93 --- .../src/main/res/navigation/nav_graph.xml | 8 +- settings.gradle.kts | 1 + 26 files changed, 1237 insertions(+), 732 deletions(-) rename {mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount => core/data/src/main/java/com/mifos/core/data/repository}/LoanAccountRepository.kt (53%) rename {mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount => core/data/src/main/java/com/mifos/core/data/repository_imp}/LoanAccountRepositoryImp.kt (63%) create mode 100644 core/domain/src/main/java/com/mifos/core/domain/use_cases/CreateLoanAccountUseCase.kt create mode 100644 core/domain/src/main/java/com/mifos/core/domain/use_cases/GetAllLoanUseCase.kt create mode 100644 core/domain/src/main/java/com/mifos/core/domain/use_cases/GetLoansAccountTemplateUseCase.kt create mode 100644 feature/loan/.gitignore create mode 100644 feature/loan/build.gradle.kts create mode 100644 feature/loan/consumer-rules.pro create mode 100644 feature/loan/proguard-rules.pro create mode 100644 feature/loan/src/androidTest/java/com/mifos/feature/loan/ExampleInstrumentedTest.kt create mode 100644 feature/loan/src/main/AndroidManifest.xml create mode 100644 feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountScreen.kt create mode 100644 feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountUiState.kt create mode 100644 feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountViewModel.kt create mode 100644 feature/loan/src/main/res/values/strings.xml create mode 100644 feature/loan/src/test/java/com/mifos/feature/loan/ExampleUnitTest.kt delete mode 100644 mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountUiState.kt delete mode 100644 mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountViewModel.kt diff --git a/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt b/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt index b35813f0eb5..120dca3e71a 100644 --- a/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt +++ b/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt @@ -5,11 +5,12 @@ import com.mifos.core.data.repository.CenterDetailsRepository import com.mifos.core.data.repository.CenterListRepository import com.mifos.core.data.repository.CheckerInboxRepository import com.mifos.core.data.repository.CheckerInboxTasksRepository -import com.mifos.core.data.repository.ClientIdentifiersRepository import com.mifos.core.data.repository.ClientChargeRepository +import com.mifos.core.data.repository.ClientIdentifiersRepository import com.mifos.core.data.repository.CreateNewCenterRepository import com.mifos.core.data.repository.GroupDetailsRepository import com.mifos.core.data.repository.GroupsListRepository +import com.mifos.core.data.repository.LoanAccountRepository import com.mifos.core.data.repository.NewIndividualCollectionSheetRepository import com.mifos.core.data.repository.PathTrackingRepository import com.mifos.core.data.repository.ReportCategoryRepository @@ -17,11 +18,12 @@ import com.mifos.core.data.repository_imp.CenterDetailsRepositoryImp import com.mifos.core.data.repository_imp.CenterListRepositoryImp import com.mifos.core.data.repository_imp.CheckerInboxRepositoryImp import com.mifos.core.data.repository_imp.CheckerInboxTasksRepositoryImp -import com.mifos.core.data.repository_imp.ClientIdentifiersRepositoryImp import com.mifos.core.data.repository_imp.ClientChargeRepositoryImp +import com.mifos.core.data.repository_imp.ClientIdentifiersRepositoryImp import com.mifos.core.data.repository_imp.CreateNewCenterRepositoryImp import com.mifos.core.data.repository_imp.GroupDetailsRepositoryImp import com.mifos.core.data.repository_imp.GroupsListRepositoryImpl +import com.mifos.core.data.repository_imp.LoanAccountRepositoryImp import com.mifos.core.data.repository_imp.NewIndividualCollectionSheetRepositoryImp import com.mifos.core.data.repository_imp.PathTrackingRepositoryImp import com.mifos.core.data.repository_imp.ReportCategoryRepositoryImp @@ -65,10 +67,13 @@ abstract class DataModule { @Binds internal abstract fun bindClientChargeRepository(impl: ClientChargeRepositoryImp): ClientChargeRepository - + @Binds internal abstract fun bindCreateNewCenterRepository(impl: CreateNewCenterRepositoryImp): CreateNewCenterRepository @Binds internal abstract fun bindClientIdentifiersRepository(impl: ClientIdentifiersRepositoryImp): ClientIdentifiersRepository + + @Binds + internal abstract fun bindLoanAccountRepository(impl: LoanAccountRepositoryImp): LoanAccountRepository } \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountRepository.kt b/core/data/src/main/java/com/mifos/core/data/repository/LoanAccountRepository.kt similarity index 53% rename from mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountRepository.kt rename to core/data/src/main/java/com/mifos/core/data/repository/LoanAccountRepository.kt index 8c8c2e30af8..c1ecf66bb27 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountRepository.kt +++ b/core/data/src/main/java/com/mifos/core/data/repository/LoanAccountRepository.kt @@ -1,4 +1,4 @@ -package com.mifos.mifosxdroid.online.loanaccount +package com.mifos.core.data.repository import com.mifos.core.data.LoansPayload import com.mifos.core.objects.accounts.loan.Loans @@ -11,11 +11,9 @@ import rx.Observable */ interface LoanAccountRepository { - fun allLoans(): Observable> - - fun getLoansAccountTemplate(clientId: Int, productId: Int): Observable - - fun createLoansAccount(loansPayload: LoansPayload?): Observable + suspend fun allLoans(): Observable> + suspend fun getLoansAccountTemplate(clientId: Int, productId: Int): Observable + suspend fun createLoansAccount(loansPayload: LoansPayload): Observable } \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountRepositoryImp.kt b/core/data/src/main/java/com/mifos/core/data/repository_imp/LoanAccountRepositoryImp.kt similarity index 63% rename from mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountRepositoryImp.kt rename to core/data/src/main/java/com/mifos/core/data/repository_imp/LoanAccountRepositoryImp.kt index 511316df17b..224e42b844b 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountRepositoryImp.kt +++ b/core/data/src/main/java/com/mifos/core/data/repository_imp/LoanAccountRepositoryImp.kt @@ -1,6 +1,7 @@ -package com.mifos.mifosxdroid.online.loanaccount +package com.mifos.core.data.repository_imp import com.mifos.core.data.LoansPayload +import com.mifos.core.data.repository.LoanAccountRepository import com.mifos.core.network.datamanager.DataManagerLoan import com.mifos.core.objects.accounts.loan.Loans import com.mifos.core.objects.organisation.LoanProducts @@ -14,15 +15,18 @@ import javax.inject.Inject class LoanAccountRepositoryImp @Inject constructor(private val dataManagerLoan: DataManagerLoan) : LoanAccountRepository { - override fun allLoans(): Observable> { + override suspend fun allLoans(): Observable> { return dataManagerLoan.allLoans } - override fun getLoansAccountTemplate(clientId: Int, productId: Int): Observable { + override suspend fun getLoansAccountTemplate( + clientId: Int, + productId: Int + ): Observable { return dataManagerLoan.getLoansAccountTemplate(clientId, productId) } - override fun createLoansAccount(loansPayload: LoansPayload?): Observable { + override suspend fun createLoansAccount(loansPayload: LoansPayload): Observable { return dataManagerLoan.createLoansAccount(loansPayload) } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt index b1002141ede..2a8ce4be99a 100644 --- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt +++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt @@ -163,7 +163,7 @@ fun MifosOutlinedTextField( modifier: Modifier = Modifier, value: String, label: String, - leadingIcon: ImageVector, + leadingIcon: ImageVector? = null, maxLines: Int = 1, isError: Boolean = false, errorText: String? = null, @@ -205,7 +205,7 @@ fun MifosOutlinedTextField( ) }, leadingIcon = { - Icon(imageVector = leadingIcon, contentDescription = "leadingIcon") + leadingIcon?.let { Icon(imageVector = it, contentDescription = "leadingIcon") } }, trailingIcon = @Composable { if (isPasswordToggleDisplayed) { @@ -261,6 +261,62 @@ fun MifosOutlinedTextField( ) } +@Composable +fun MifosOutlinedTextField( + modifier: Modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp), + value: String, + onValueChange: (String) -> Unit, + maxLines: Int = 1, + singleLine: Boolean = true, + icon: ImageVector? = null, + label: String, + visualTransformation: VisualTransformation = VisualTransformation.None, + trailingIcon: @Composable (() -> Unit)? = null, + keyboardType: KeyboardType = KeyboardType.Text, + error: Int? +) { + + OutlinedTextField( + value = value, + onValueChange = onValueChange, + label = { Text(label) }, + modifier = modifier, + leadingIcon = if (icon != null) { + { + Icon( + imageVector = icon, + contentDescription = null, + tint = if (isSystemInDarkTheme()) White else DarkGray + ) + } + } else null, + trailingIcon = trailingIcon, + maxLines = maxLines, + singleLine = singleLine, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = if (isSystemInDarkTheme()) BluePrimaryDark else BluePrimary, + focusedLabelColor = if (isSystemInDarkTheme()) BluePrimaryDark else BluePrimary + ), + textStyle = LocalDensity.current.run { + TextStyle(fontSize = 18.sp) + }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next,keyboardType = keyboardType), + visualTransformation = visualTransformation, + isError = error != null, + supportingText = if (error != null) { + { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = error), + color = MaterialTheme.colorScheme.error + ) + } + } else { + null + } + ) +} + @Composable private fun PasswordToggleIcon( modifier: Modifier = Modifier, @@ -309,7 +365,7 @@ private fun ClearIconButton( ) } } - + } @Composable diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosTextFieldDropdown.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosTextFieldDropdown.kt index d439db15fc0..80cb75f0788 100644 --- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosTextFieldDropdown.kt +++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosTextFieldDropdown.kt @@ -30,6 +30,9 @@ import com.mifos.core.designsystem.theme.BluePrimaryDark @Composable fun MifosTextFieldDropdown( + modifier: Modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp), value: String, onValueChanged: (String) -> Unit, onOptionSelected: (Int, String) -> Unit, @@ -47,10 +50,7 @@ fun MifosTextFieldDropdown( value = value, onValueChange = { onValueChanged(it) }, label = { Text(text = stringResource(id = label)) }, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp) - .menuAnchor(), + modifier = modifier.menuAnchor(), maxLines = 1, colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = if (isSystemInDarkTheme()) BluePrimaryDark else BluePrimary, diff --git a/core/domain/src/main/java/com/mifos/core/domain/use_cases/CreateLoanAccountUseCase.kt b/core/domain/src/main/java/com/mifos/core/domain/use_cases/CreateLoanAccountUseCase.kt new file mode 100644 index 00000000000..a1bffdc2659 --- /dev/null +++ b/core/domain/src/main/java/com/mifos/core/domain/use_cases/CreateLoanAccountUseCase.kt @@ -0,0 +1,41 @@ +package com.mifos.core.domain.use_cases + +import com.mifos.core.common.utils.Resource +import com.mifos.core.data.LoansPayload +import com.mifos.core.data.repository.LoanAccountRepository +import com.mifos.core.objects.accounts.loan.Loans +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import rx.Subscriber +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import javax.inject.Inject + +class CreateLoanAccountUseCase @Inject constructor(private val loanAccountRepository: LoanAccountRepository) { + + suspend operator fun invoke(loansPayload: LoansPayload): Flow> = callbackFlow { + try { + trySend(Resource.Loading()) + loanAccountRepository.createLoansAccount(loansPayload) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe(object : Subscriber() { + override fun onCompleted() {} + + override fun onError(exception: Throwable) { + trySend(Resource.Error(exception.message.toString())) + } + + override fun onNext(loans: Loans) { + trySend(Resource.Success(loans)) + } + }) + + awaitClose { channel.close() } + + } catch (exception: Exception) { + trySend(Resource.Error(exception.message.toString())) + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetAllLoanUseCase.kt b/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetAllLoanUseCase.kt new file mode 100644 index 00000000000..48c12688927 --- /dev/null +++ b/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetAllLoanUseCase.kt @@ -0,0 +1,38 @@ +package com.mifos.core.domain.use_cases + +import com.mifos.core.common.utils.Resource +import com.mifos.core.data.repository.LoanAccountRepository +import com.mifos.core.objects.organisation.LoanProducts +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import rx.Subscriber +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import javax.inject.Inject + +class GetAllLoanUseCase @Inject constructor(private val loanAccountRepository: LoanAccountRepository) { + + suspend operator fun invoke(): Flow>> = callbackFlow { + try { + trySend(Resource.Loading()) + loanAccountRepository.allLoans() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe(object : Subscriber>() { + override fun onCompleted() {} + + override fun onError(exception: Throwable) { + trySend(Resource.Error(exception.message.toString())) + } + + override fun onNext(products: List) { + trySend(Resource.Success(products)) + } + }) + awaitClose { channel.close() } + } catch (exception: Exception) { + trySend(Resource.Error(exception.message.toString())) + } + } +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetLoansAccountTemplateUseCase.kt b/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetLoansAccountTemplateUseCase.kt new file mode 100644 index 00000000000..69e8916bbd9 --- /dev/null +++ b/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetLoansAccountTemplateUseCase.kt @@ -0,0 +1,41 @@ +package com.mifos.core.domain.use_cases + +import com.mifos.core.common.utils.Resource +import com.mifos.core.data.repository.LoanAccountRepository +import com.mifos.core.objects.templates.loans.LoanTemplate +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import rx.Subscriber +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import javax.inject.Inject + +class GetLoansAccountTemplateUseCase @Inject constructor(private val loanAccountRepository: LoanAccountRepository) { + + suspend operator fun invoke(clientId: Int, productId: Int): Flow> = + callbackFlow { + try { + trySend(Resource.Loading()) + loanAccountRepository.getLoansAccountTemplate(clientId, productId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe(object : Subscriber() { + override fun onCompleted() {} + + override fun onError(exception: Throwable) { + trySend(Resource.Error(exception.message.toString())) + } + + override fun onNext(loanTemplate: LoanTemplate?) { + trySend(Resource.Success(loanTemplate)) + } + }) + + awaitClose { channel.close() } + + } catch (exception: Exception) { + trySend(Resource.Error(exception.message.toString())) + } + } +} \ No newline at end of file diff --git a/feature/loan/.gitignore b/feature/loan/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/feature/loan/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/loan/build.gradle.kts b/feature/loan/build.gradle.kts new file mode 100644 index 00000000000..d4ce95f19f3 --- /dev/null +++ b/feature/loan/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + alias(libs.plugins.mifos.android.feature) + alias(libs.plugins.mifos.android.library.compose) + alias(libs.plugins.mifos.android.library.jacoco) +} + +android { + namespace = "com.mifos.feature.loan" +} + +dependencies { + + implementation(projects.core.domain) + + //DBFlow dependencies + kapt(libs.dbflow.processor) + implementation(libs.dbflow) + kapt(libs.github.dbflow.processor) + testImplementation(libs.hilt.android.testing) + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) +} \ No newline at end of file diff --git a/feature/loan/consumer-rules.pro b/feature/loan/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d diff --git a/feature/loan/proguard-rules.pro b/feature/loan/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/feature/loan/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/loan/src/androidTest/java/com/mifos/feature/loan/ExampleInstrumentedTest.kt b/feature/loan/src/androidTest/java/com/mifos/feature/loan/ExampleInstrumentedTest.kt new file mode 100644 index 00000000000..80841bc46e4 --- /dev/null +++ b/feature/loan/src/androidTest/java/com/mifos/feature/loan/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.mifos.feature.loan + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.mifos.feature.loan.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/loan/src/main/AndroidManifest.xml b/feature/loan/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..a5918e68abc --- /dev/null +++ b/feature/loan/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountScreen.kt b/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountScreen.kt new file mode 100644 index 00000000000..d8e05848d6f --- /dev/null +++ b/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountScreen.kt @@ -0,0 +1,644 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package com.mifos.feature.loan.loan_account + +import android.widget.Toast +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SelectableDates +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.mifos.core.data.LoansPayload +import com.mifos.core.designsystem.component.MifosCircularProgress +import com.mifos.core.designsystem.component.MifosDatePickerTextField +import com.mifos.core.designsystem.component.MifosOutlinedTextField +import com.mifos.core.designsystem.component.MifosScaffold +import com.mifos.core.designsystem.component.MifosSweetError +import com.mifos.core.designsystem.component.MifosTextFieldDropdown +import com.mifos.core.designsystem.icon.MifosIcons +import com.mifos.core.designsystem.theme.BluePrimary +import com.mifos.core.designsystem.theme.BluePrimaryDark +import com.mifos.core.objects.noncore.DataTable +import com.mifos.core.objects.organisation.LoanProducts +import com.mifos.core.objects.templates.loans.LoanTemplate +import com.mifos.feature.loan.R +import java.text.SimpleDateFormat +import java.util.Locale + +@Composable +fun LoanAccountScreen( + clientId: Int, + onBackPressed: () -> Unit, + dataTable: (List, LoansPayload) -> Unit +) { + val viewModel: LoanAccountViewModel = hiltViewModel() + val state by viewModel.loanAccountUiState.collectAsStateWithLifecycle() + val loanAccountTemplateState by viewModel.loanAccountTemplateUiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.loadAllLoans() + } + + LoanAccountScreen( + clientId = clientId, + state = state, + loanAccountTemplateState = loanAccountTemplateState, + onBackPressed = onBackPressed, + onRetry = { + viewModel.loadAllLoans() + }, + onLoanProductSelected = { productId -> + viewModel.loadLoanAccountTemplate(clientId, productId) + }, + createLoanAccount = { loansPayload -> + viewModel.createLoansAccount(loansPayload) + }, + dataTable = dataTable, + fetchTemplate = { productId -> + viewModel.loadLoanAccountTemplate(clientId, productId) + } + ) +} + +@Composable +fun LoanAccountScreen( + clientId: Int, + state: LoanAccountUiState, + loanAccountTemplateState: LoanTemplate, + onBackPressed: () -> Unit, + onRetry: () -> Unit, + onLoanProductSelected: (Int) -> Unit, + createLoanAccount: (LoansPayload) -> Unit, + dataTable: (List, LoansPayload) -> Unit, + fetchTemplate: (Int) -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + + MifosScaffold( + icon = MifosIcons.arrowBack, + title = stringResource(id = R.string.feature_loan_application), + onBackPressed = onBackPressed, + snackbarHostState = snackbarHostState + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + ) { + when (state) { + is LoanAccountUiState.AllLoan -> { + LoanAccountContent( + clientsId = clientId, + productLoans = state.productLoans, + loanTemplate = loanAccountTemplateState, + onLoanProductSelected = onLoanProductSelected, + createLoanAccount = createLoanAccount, + dataTable = dataTable + ) + state.productLoans[0].id?.let { fetchTemplate(it) } + } + + is LoanAccountUiState.Error -> MifosSweetError(message = stringResource(id = state.message)) { + onRetry() + } + + is LoanAccountUiState.Loading -> { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center + ) { + MifosCircularProgress() + } + } + + is LoanAccountUiState.LoanAccountCreatedSuccessfully -> { + Toast.makeText( + LocalContext.current, + stringResource(id = R.string.feature_loan_account_created_successfully), + Toast.LENGTH_SHORT + ).show() + onBackPressed() + } + } + } + } +} + +@Composable +fun LoanAccountContent( + clientsId: Int, + productLoans: List, + loanTemplate: LoanTemplate, + onLoanProductSelected: (Int) -> Unit, + createLoanAccount: (LoansPayload) -> Unit, + dataTable: (List, LoansPayload) -> Unit +) { + + var selectedLoanProduct by rememberSaveable { mutableStateOf("") } + var selectedLoanProductId by rememberSaveable { mutableIntStateOf(0) } + var selectedLoanPurpose by rememberSaveable { mutableStateOf("") } + var selectedLoanPurposeId by rememberSaveable { mutableIntStateOf(0) } + var selectedLoanOfficer by rememberSaveable { mutableStateOf("") } + var selectedLoanOfficerId by rememberSaveable { mutableIntStateOf(0) } + var selectedFund by rememberSaveable { mutableStateOf("") } + var selectedFundId by rememberSaveable { mutableIntStateOf(0) } + + var showSubmissionDatePicker by rememberSaveable { mutableStateOf(false) } + var submissionDate by rememberSaveable { mutableLongStateOf(System.currentTimeMillis()) } + val submissionDatePickerState = rememberDatePickerState( + initialSelectedDateMillis = submissionDate, + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean { + return utcTimeMillis >= System.currentTimeMillis() + } + } + ) + + var showDisbursementDatePicker by rememberSaveable { mutableStateOf(false) } + var disbursementDate by rememberSaveable { mutableLongStateOf(System.currentTimeMillis()) } + val disbursementDatePickerState = rememberDatePickerState( + initialSelectedDateMillis = disbursementDate, + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean { + return utcTimeMillis >= System.currentTimeMillis() + } + } + ) + + var externalId by rememberSaveable { mutableStateOf("") } + var principalAmount by rememberSaveable { mutableStateOf("10000.0") } + var numberOfRepayment by rememberSaveable { mutableStateOf("10") } + var nominal by rememberSaveable { mutableStateOf("5.0") } + var repaidEvery by rememberSaveable { mutableStateOf("2") } + var repaidEveryType by rememberSaveable { mutableStateOf("") } + var repaidEveryTypeFrequency by rememberSaveable { mutableIntStateOf(0) } + var loanTerms by rememberSaveable { mutableStateOf("20") } + var loanTermsType by rememberSaveable { mutableStateOf("") } + var loanTermsTypeFrequency by rememberSaveable { mutableIntStateOf(0) } + + var selectedLinkSavings by rememberSaveable { mutableStateOf("") } + var selectedLinkSavingsId by rememberSaveable { mutableIntStateOf(0) } + var selectedAmortization by rememberSaveable { mutableStateOf("") } + var selectedAmortizationId by rememberSaveable { mutableIntStateOf(0) } + var selectedInterestCalculationPeriod by rememberSaveable { mutableStateOf("") } + var selectedInterestCalculationPeriodId by rememberSaveable { mutableIntStateOf(0) } + var selectedRepaymentStrategy by rememberSaveable { mutableStateOf("") } + var selectedRepaymentStrategyId by rememberSaveable { mutableIntStateOf(0) } + var selectedInterestTypeMethod by rememberSaveable { mutableStateOf("") } + var selectedInterestTypeMethodId by rememberSaveable { mutableIntStateOf(0) } + var selectedCalculateExactDaysIn by rememberSaveable { mutableStateOf(false) } + + if (showSubmissionDatePicker) { + DatePickerDialog( + onDismissRequest = { + showSubmissionDatePicker = false + }, + confirmButton = { + TextButton( + onClick = { + showSubmissionDatePicker = false + submissionDatePickerState.selectedDateMillis?.let { + submissionDate = it + } + } + ) { Text(stringResource(id = R.string.feature_loan_select)) } + }, + dismissButton = { + TextButton( + onClick = { + showSubmissionDatePicker = false + } + ) { Text(stringResource(id = R.string.feature_loan_cancel)) } + } + ) + { + DatePicker(state = submissionDatePickerState) + } + } + + if (showDisbursementDatePicker) { + DatePickerDialog( + onDismissRequest = { + showDisbursementDatePicker = false + }, + confirmButton = { + TextButton( + onClick = { + showDisbursementDatePicker = false + disbursementDatePickerState.selectedDateMillis?.let { + disbursementDate = it + } + } + ) { Text(stringResource(id = R.string.feature_loan_select)) } + }, + dismissButton = { + TextButton( + onClick = { + showSubmissionDatePicker = false + } + ) { Text(stringResource(id = R.string.feature_loan_cancel)) } + } + ) + { + DatePicker(state = disbursementDatePickerState) + } + } + + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + + MifosTextFieldDropdown( + value = selectedLoanProduct, + onValueChanged = { selectedLoanProduct = it }, + onOptionSelected = { index, value -> + selectedLoanProduct = value + productLoans[index].id?.let { + selectedLoanProductId = it + onLoanProductSelected(it) + } + }, + label = R.string.feature_loan_product, + options = productLoans.map { it.name.toString() }, + readOnly = true + ) + + MifosTextFieldDropdown( + value = selectedLoanPurpose, + onValueChanged = { selectedLoanPurpose = it }, + onOptionSelected = { index, value -> + selectedLoanPurpose = value + loanTemplate.loanPurposeOptions[index].id?.let { + selectedLoanPurposeId = it + } + }, + label = R.string.feature_loan_purpose, + options = loanTemplate.loanPurposeOptions.map { it.name.toString() }, + readOnly = true + ) + + MifosTextFieldDropdown( + value = selectedLoanOfficer, + onValueChanged = { selectedLoanOfficer = it }, + onOptionSelected = { index, value -> + selectedLoanOfficer = value + loanTemplate.loanOfficerOptions[index].id?.let { + selectedLoanOfficerId = it + } + }, + label = R.string.feature_loan_officer, + options = loanTemplate.loanOfficerOptions.map { it.displayName.toString() }, + readOnly = true + ) + + MifosTextFieldDropdown( + value = selectedFund, + onValueChanged = { selectedFund = it }, + onOptionSelected = { index, value -> + selectedFund = value + loanTemplate.fundOptions[index].id?.let { + selectedFundId = it + } + }, + label = R.string.feature_loan_fund, + options = loanTemplate.fundOptions.map { it.name.toString() }, + readOnly = true + ) + + MifosDatePickerTextField( + value = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format( + submissionDate + ), + label = R.string.feature_loan_submission_date, + openDatePicker = { + showSubmissionDatePicker = true + } + ) + + MifosDatePickerTextField( + value = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format( + disbursementDate + ), + label = R.string.feature_loan_disbursed_date, + openDatePicker = { + showDisbursementDatePicker = true + } + ) + + MifosOutlinedTextField( + value = externalId, + label = stringResource(id = R.string.feature_loan_external_id), + onValueChange = { + externalId = it + }, + error = null, + keyboardType = KeyboardType.Text + ) + + MifosTextFieldDropdown( + value = selectedLinkSavings, + onValueChanged = { selectedLinkSavings = it }, + onOptionSelected = { index, value -> + selectedLinkSavings = value + loanTemplate.accountLinkingOptions[index].id?.let { + selectedLinkSavingsId = it + } + }, + label = R.string.feature_loan_link_savings, + options = loanTemplate.accountLinkingOptions.map { it.productName.toString() }, + readOnly = true + ) + + MifosOutlinedTextField( + value = principalAmount, + label = stringResource(id = R.string.feature_loan_principal), + onValueChange = { + principalAmount = it + }, + error = null, + keyboardType = KeyboardType.Number + ) + + MifosOutlinedTextField( + value = numberOfRepayment, + label = stringResource(id = R.string.feature_loan_number_of_repayments), + onValueChange = { + numberOfRepayment = it + }, + error = null, + keyboardType = KeyboardType.Number + ) + + Row(verticalAlignment = Alignment.CenterVertically) { + MifosOutlinedTextField( + modifier = Modifier + .weight(1f) + .padding(start = 16.dp), + value = nominal, + label = stringResource(id = R.string.feature_loan_nominal), + onValueChange = { + nominal = it + }, + error = null, + keyboardType = KeyboardType.Number + ) + Text( + modifier = Modifier.padding(16.dp), + text = stringResource(id = R.string.feature_loan_per_month) + ) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + MifosOutlinedTextField( + modifier = Modifier + .weight(2f) + .padding(start = 16.dp), + value = repaidEvery, + label = stringResource(id = R.string.feature_loan_repaid_every), + onValueChange = { + repaidEvery = it + }, + error = null, + keyboardType = KeyboardType.Number + ) + MifosTextFieldDropdown( + modifier = Modifier + .width(164.dp) + .padding(start = 8.dp, end = 16.dp), + value = repaidEveryType, + onValueChanged = { repaidEveryType = it }, + onOptionSelected = { index, value -> + repaidEveryType = value + loanTemplate.repaymentFrequencyDaysOfWeekTypeOptions[index].id?.let { + repaidEveryTypeFrequency = it + } + }, + label = R.string.feature_loan_term, + options = loanTemplate.repaymentFrequencyDaysOfWeekTypeOptions.map { it.value.toString() }, + readOnly = true + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + MifosOutlinedTextField( + modifier = Modifier + .weight(2f) + .padding(start = 16.dp), + value = loanTerms, + label = stringResource(id = R.string.feature_loan_loan_terms), + onValueChange = { + loanTerms = it + }, + error = null, + keyboardType = KeyboardType.Number + ) + MifosTextFieldDropdown( + modifier = Modifier + .width(164.dp) + .padding(start = 8.dp, end = 16.dp), + value = loanTermsType, + onValueChanged = { loanTermsType = it }, + onOptionSelected = { index, value -> + loanTermsType = value + loanTemplate.termFrequencyTypeOptions[index].id?.let { + loanTermsTypeFrequency = it + } + }, + label = R.string.feature_loan_term, + options = loanTemplate.termFrequencyTypeOptions.map { it.value.toString() }, + readOnly = true + ) + } + + MifosTextFieldDropdown( + value = selectedAmortization, + onValueChanged = { selectedAmortization = it }, + onOptionSelected = { index, value -> + selectedAmortization = value + loanTemplate.amortizationTypeOptions[index].id?.let { + selectedAmortizationId = it + } + }, + label = R.string.feature_loan_amortization, + options = loanTemplate.amortizationTypeOptions.map { it.value.toString() }, + readOnly = true + ) + + MifosTextFieldDropdown( + value = selectedInterestCalculationPeriod, + onValueChanged = { selectedInterestCalculationPeriod = it }, + onOptionSelected = { index, value -> + selectedInterestCalculationPeriod = value + loanTemplate.interestCalculationPeriodTypeOptions[index].id?.let { + selectedInterestCalculationPeriodId = it + } + }, + label = R.string.feature_loan_interest_calculation_period, + options = loanTemplate.interestCalculationPeriodTypeOptions.map { it.value.toString() }, + readOnly = true + ) + + MifosTextFieldDropdown( + value = selectedRepaymentStrategy, + onValueChanged = { selectedRepaymentStrategy = it }, + onOptionSelected = { index, value -> + selectedRepaymentStrategy = value + loanTemplate.transactionProcessingStrategyOptions[index].id?.let { + selectedRepaymentStrategyId = it + } + }, + label = R.string.feature_loan_repayment_strategy, + options = loanTemplate.transactionProcessingStrategyOptions.map { it.name.toString() }, + readOnly = true + ) + + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + checked = selectedCalculateExactDaysIn, + onCheckedChange = { + selectedCalculateExactDaysIn = selectedCalculateExactDaysIn.not() + }) + Text(text = stringResource(id = R.string.feature_loan_calculate_interest_for_exact_days_in)) + } + + MifosTextFieldDropdown( + value = selectedInterestTypeMethod, + onValueChanged = { selectedInterestTypeMethod = it }, + onOptionSelected = { index, value -> + selectedInterestTypeMethod = value + loanTemplate.interestTypeOptions[index].id?.let { + selectedInterestTypeMethodId = it + } + }, + label = R.string.feature_loan_interest_type_method, + options = loanTemplate.interestTypeOptions.map { it.value.toString() }, + readOnly = true + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { + val loadPayload = LoansPayload().apply { + allowPartialPeriodInterestCalcualtion = selectedCalculateExactDaysIn + amortizationType = selectedAmortizationId + clientId = clientsId + dateFormat = "dd MMMM yyyy" + expectedDisbursementDate = + SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format( + disbursementDate + ) + interestCalculationPeriodType = selectedInterestCalculationPeriodId + loanType = "individual" + locale = "en" + numberOfRepayments = numberOfRepayment.toInt() + principal = principalAmount.toDouble() + productId = selectedLoanProductId + repaymentEvery = repaidEvery.toInt() + submittedOnDate = + SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format(submissionDate) + loanPurposeId = selectedLoanPurposeId + loanTermFrequency = loanTerms.toInt() + loanTermFrequencyType = loanTermsTypeFrequency + repaymentFrequencyType = loanTermsTypeFrequency + repaymentFrequencyDayOfWeekType = repaidEveryTypeFrequency + repaymentFrequencyNthDayType = repaidEveryTypeFrequency + transactionProcessingStrategyId = selectedRepaymentStrategyId + fundId = selectedFundId + interestType = selectedInterestTypeMethodId + loanOfficerId = selectedLoanOfficerId + linkAccountId = selectedLinkSavingsId + interestRatePerPeriod = nominal.toDouble() + } + if (loanTemplate.dataTables.size > 0) { + dataTable(loanTemplate.dataTables, loadPayload) + } else { + createLoanAccount(loadPayload) + } + }, + modifier = Modifier + .fillMaxWidth() + .heightIn(44.dp) + .padding(start = 16.dp, end = 16.dp), + contentPadding = PaddingValues(), + colors = ButtonDefaults.buttonColors( + containerColor = if (isSystemInDarkTheme()) BluePrimaryDark else BluePrimary + ) + ) { + Text(text = stringResource(id = R.string.feature_loan_submit), fontSize = 16.sp) + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +class LoanAccountUiStateProvider : PreviewParameterProvider { + + override val values: Sequence + get() = sequenceOf( + LoanAccountUiState.AllLoan(sampleLoanList), + LoanAccountUiState.Error(R.string.feature_loan_application), + LoanAccountUiState.Loading, + LoanAccountUiState.LoanAccountCreatedSuccessfully + ) + +} + +@Preview(showBackground = true) +@Composable +private fun LoanAccountScreenPreview( + @PreviewParameter(LoanAccountUiStateProvider::class) state: LoanAccountUiState +) { + LoanAccountScreen( + clientId = 1, + state = state, + loanAccountTemplateState = LoanTemplate(), + onBackPressed = {}, + onRetry = {}, + onLoanProductSelected = {}, + createLoanAccount = {}, + dataTable = { _, _ -> }, + fetchTemplate = {} + ) +} + +val sampleLoanList = List(10) { + LoanProducts(name = "Loan $it", id = it) +} \ No newline at end of file diff --git a/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountUiState.kt b/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountUiState.kt new file mode 100644 index 00000000000..6437dc42206 --- /dev/null +++ b/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountUiState.kt @@ -0,0 +1,19 @@ +package com.mifos.feature.loan.loan_account + +import com.mifos.core.objects.accounts.loan.Loans +import com.mifos.core.objects.organisation.LoanProducts +import com.mifos.core.objects.templates.loans.LoanTemplate + +/** + * Created by Aditya Gupta on 08/08/23. + */ +sealed class LoanAccountUiState { + + data object Loading : LoanAccountUiState() + + data class AllLoan(val productLoans: List) : LoanAccountUiState() + + data class Error(val message: Int) : LoanAccountUiState() + + data object LoanAccountCreatedSuccessfully : LoanAccountUiState() +} \ No newline at end of file diff --git a/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountViewModel.kt b/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountViewModel.kt new file mode 100644 index 00000000000..2fd54f9bb2b --- /dev/null +++ b/feature/loan/src/main/java/com/mifos/feature/loan/loan_account/LoanAccountViewModel.kt @@ -0,0 +1,75 @@ +package com.mifos.feature.loan.loan_account + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mifos.core.common.utils.Resource +import com.mifos.core.data.LoansPayload +import com.mifos.core.domain.use_cases.CreateLoanAccountUseCase +import com.mifos.core.domain.use_cases.GetAllLoanUseCase +import com.mifos.core.domain.use_cases.GetLoansAccountTemplateUseCase +import com.mifos.core.objects.templates.loans.LoanTemplate +import com.mifos.feature.loan.R +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LoanAccountViewModel @Inject constructor( + private val getAllLoanUseCase: GetAllLoanUseCase, + private val getLoansAccountTemplateUseCase: GetLoansAccountTemplateUseCase, + private val createLoanAccountUseCase: CreateLoanAccountUseCase +) : ViewModel() { + + private val _loanAccountUiState = + MutableStateFlow(LoanAccountUiState.Loading) + val loanAccountUiState = _loanAccountUiState.asStateFlow() + + private val _loanAccountTemplateUiState = MutableStateFlow(LoanTemplate()) + val loanAccountTemplateUiState = _loanAccountTemplateUiState.asStateFlow() + + fun loadAllLoans() = viewModelScope.launch(Dispatchers.IO) { + getAllLoanUseCase().collect { result -> + when (result) { + is Resource.Error -> _loanAccountUiState.value = + LoanAccountUiState.Error(R.string.feature_loan_failed_to_load_loan) + + is Resource.Loading -> _loanAccountUiState.value = LoanAccountUiState.Loading + + is Resource.Success -> _loanAccountUiState.value = + LoanAccountUiState.AllLoan(result.data ?: emptyList()) + } + } + } + + fun loadLoanAccountTemplate(clientId: Int, productId: Int) = + viewModelScope.launch(Dispatchers.IO) { + getLoansAccountTemplateUseCase(clientId, productId).collect { result -> + when (result) { + is Resource.Error -> _loanAccountUiState.value = + LoanAccountUiState.Error(R.string.feature_loan_failed_to_load_template) + + is Resource.Loading -> Unit + + is Resource.Success -> _loanAccountTemplateUiState.value = + result.data ?: LoanTemplate() + } + } + } + + fun createLoansAccount(loansPayload: LoansPayload) = viewModelScope.launch(Dispatchers.IO) { + createLoanAccountUseCase(loansPayload).collect { result -> + when (result) { + is Resource.Error -> _loanAccountUiState.value = + LoanAccountUiState.Error(R.string.feature_loan_failed_to_create_loan_account) + + is Resource.Loading -> _loanAccountUiState.value = LoanAccountUiState.Loading + + is Resource.Success -> _loanAccountUiState.value = + LoanAccountUiState.LoanAccountCreatedSuccessfully + } + } + } +} \ No newline at end of file diff --git a/feature/loan/src/main/res/values/strings.xml b/feature/loan/src/main/res/values/strings.xml new file mode 100644 index 00000000000..1203121d9ea --- /dev/null +++ b/feature/loan/src/main/res/values/strings.xml @@ -0,0 +1,37 @@ + + + Loan Application + Loan account created successfully + + Loan Product + Loan Purpose + Loan Officer + Fund + Submission Date + Disbursed Date + Select + Cancel + + External ID + Principal + Number of Repayments + Nominal + Repaid Every + Loan Terms + + Link Savings + Per month + Term + Amortization + Interest Calculation Period + Repayment Strategy + Interest Type Method + Calculate Interest for exact days in + Submit + + + + Failed to load loan + Failed to load loan template + Failed to create loan account + \ No newline at end of file diff --git a/feature/loan/src/test/java/com/mifos/feature/loan/ExampleUnitTest.kt b/feature/loan/src/test/java/com/mifos/feature/loan/ExampleUnitTest.kt new file mode 100644 index 00000000000..c189c45bb94 --- /dev/null +++ b/feature/loan/src/test/java/com/mifos/feature/loan/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.mifos.feature.loan + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/mifosng-android/build.gradle.kts b/mifosng-android/build.gradle.kts index f782405949a..090da89d10a 100644 --- a/mifosng-android/build.gradle.kts +++ b/mifosng-android/build.gradle.kts @@ -138,6 +138,7 @@ dependencies { implementation(projects.feature.about) implementation(projects.feature.report) implementation(projects.feature.pathTracking) + implementation(projects.feature.loan) implementation(projects.core.common) implementation(projects.core.ui) diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt index c5f830c14c5..22ecfd89406 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt @@ -89,8 +89,6 @@ import com.mifos.mifosxdroid.online.grouploanaccount.GroupLoanAccountRepository import com.mifos.mifosxdroid.online.grouploanaccount.GroupLoanAccountRepositoryImp import com.mifos.mifosxdroid.online.groupslist.GroupsListRepository import com.mifos.mifosxdroid.online.groupslist.GroupsListRepositoryImp -import com.mifos.mifosxdroid.online.loanaccount.LoanAccountRepository -import com.mifos.mifosxdroid.online.loanaccount.LoanAccountRepositoryImp import com.mifos.mifosxdroid.online.loanaccountapproval.LoanAccountApprovalRepository import com.mifos.mifosxdroid.online.loanaccountapproval.LoanAccountApprovalRepositoryImp import com.mifos.mifosxdroid.online.loanaccountdisbursement.LoanAccountDisbursementRepository @@ -227,11 +225,6 @@ class RepositoryModule { return SavingsAccountRepositoryImp(dataManagerSavings) } - @Provides - fun providesLoanAccountRepository(dataManagerLoan: DataManagerLoan): LoanAccountRepository { - return LoanAccountRepositoryImp(dataManagerLoan) - } - @Provides fun providesSignatureRepository(dataManagerDocument: DataManagerDocument): SignatureRepository { return SignatureRepositoryImp(dataManagerDocument) diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountFragment.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountFragment.kt index b44682fd6cc..82963924ffa 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountFragment.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountFragment.kt @@ -8,28 +8,17 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.AdapterView -import android.widget.AdapterView.OnItemSelectedListener -import android.widget.ArrayAdapter -import android.widget.Toast -import androidx.fragment.app.DialogFragment -import androidx.lifecycle.ViewModelProvider +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import com.mifos.core.common.utils.Constants import com.mifos.core.data.LoansPayload -import com.mifos.core.objects.accounts.loan.Loans -import com.mifos.core.objects.organisation.LoanProducts -import com.mifos.core.objects.templates.loans.LoanTemplate -import com.mifos.core.objects.templates.loans.RepaymentFrequencyDaysOfWeekTypeOptions -import com.mifos.core.objects.templates.loans.RepaymentFrequencyNthDayTypeOptions +import com.mifos.core.objects.noncore.DataTable +import com.mifos.feature.loan.loan_account.LoanAccountScreen import com.mifos.mifosxdroid.R -import com.mifos.mifosxdroid.core.ProgressableDialogFragment -import com.mifos.mifosxdroid.core.util.Toaster -import com.mifos.mifosxdroid.databinding.FragmentAddLoanBinding import com.mifos.mifosxdroid.online.datatablelistfragment.DataTableListFragment -import com.mifos.mifosxdroid.uihelpers.MFDatePicker -import com.mifos.mifosxdroid.uihelpers.MFDatePicker.OnDatePickListener -import com.mifos.utils.DateHelper import com.mifos.utils.FragmentConstants import dagger.hilt.android.AndroidEntryPoint @@ -40,69 +29,11 @@ import dagger.hilt.android.AndroidEntryPoint * Use this Fragment to Create and/or Update loan */ @AndroidEntryPoint -class LoanAccountFragment : ProgressableDialogFragment(), OnDatePickListener, - OnItemSelectedListener { +class LoanAccountFragment : Fragment() { - private lateinit var binding: FragmentAddLoanBinding private val arg: LoanAccountFragmentArgs by navArgs() - - private lateinit var viewModel: LoanAccountViewModel - - private var submissionDate: String? = null - private var disbursementDate: String? = null - private var hasDataTables = false - private var mfDatePicker: DialogFragment? = null - private var productId: Int? = 0 private var clientId = 0 - private var loanPurposeId: Int? = null - private var loanTermFrequency: Int? = null - private val loanTermFrequencyType = 0 - private var termFrequency: Int? = null - private var repaymentEvery: Int? = null - private var transactionProcessingStrategyId: Int? = null - private var amortizationTypeId: Int? = null - private var interestCalculationPeriodTypeId: Int? = null - private var fundId: Int? = null - private var loanOfficerId: Int? = null - private var interestTypeId: Int? = null - private var repaymentFrequencyNthDayType: Int? = null - private var repaymentFrequencyDayOfWeek: Int? = null - private var interestRatePerPeriod: Double? = null - private var linkAccountId: Int? = null - private var isDisbursebemntDate = false - private var isSubmissionDate = false - private var mLoanProducts: List = ArrayList() - private var mRepaymentFrequencyNthDayTypeOptions: List = - ArrayList() - private var mRepaymentFrequencyDaysOfWeekTypeOptions: List = - ArrayList() - private var mLoanTemplate = LoanTemplate() - private var mListLoanProducts: MutableList = ArrayList() - private var mListLoanPurposeOptions: MutableList = ArrayList() - private var mListAccountLinkingOptions: MutableList = ArrayList() - private var mListAmortizationTypeOptions: MutableList = ArrayList() - private var mListInterestCalculationPeriodTypeOptions: MutableList = ArrayList() - private var mListTransactionProcessingStrategyOptions: MutableList = ArrayList() - private var mListTermFrequencyTypeOptions: MutableList = ArrayList() - private var mListLoanTermFrequencyTypeOptions: MutableList = ArrayList() - private var mListRepaymentFrequencyNthDayTypeOptions: MutableList = ArrayList() - private var mListRepaymentFrequencyDayOfWeekTypeOptions: MutableList = ArrayList() - private var mListLoanFundOptions: MutableList = ArrayList() - private var mListLoanOfficerOptions: MutableList = ArrayList() - private var mListInterestTypeOptions: MutableList = ArrayList() - private var mLoanProductAdapter: ArrayAdapter? = null - private var mLoanPurposeOptionsAdapter: ArrayAdapter? = null - private var mAccountLinkingOptionsAdapter: ArrayAdapter? = null - private var mAmortizationTypeOptionsAdapter: ArrayAdapter? = null - private var mInterestCalculationPeriodTypeOptionsAdapter: ArrayAdapter? = null - private var mTransactionProcessingStrategyOptionsAdapter: ArrayAdapter? = null - private var mTermFrequencyTypeOptionsAdapter: ArrayAdapter? = null - private var mLoanTermFrequencyTypeAdapter: ArrayAdapter? = null - private var mRepaymentFrequencyNthDayTypeOptionsAdapter: ArrayAdapter? = null - private var mRepaymentFrequencyDayOfWeekTypeOptionsAdapter: ArrayAdapter? = null - private var mLoanFundOptionsAdapter: ArrayAdapter? = null - private var mLoanOfficerOptionsAdapter: ArrayAdapter? = null - private var mInterestTypeOptionsAdapter: ArrayAdapter? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) clientId = arg.clientId @@ -113,517 +44,164 @@ class LoanAccountFragment : ProgressableDialogFragment(), OnDatePickListener, container: ViewGroup?, savedInstanceState: Bundle? ): View { - activity?.actionBar?.setDisplayHomeAsUpEnabled(true) - binding = FragmentAddLoanBinding.inflate(inflater, container, false) - viewModel = ViewModelProvider(this)[LoanAccountViewModel::class.java] - inflateSubmissionDate() - inflateDisbursementDate() - inflateLoansProductSpinner() - disbursementDate = binding.tvDisbursementonDate.text.toString() - submissionDate = binding.tvDisbursementonDate.text.toString() - submissionDate = DateHelper.getDateAsStringUsedForCollectionSheetPayload(submissionDate) - .replace("-", " ") - disbursementDate = DateHelper.getDateAsStringUsedForCollectionSheetPayload(disbursementDate) - .replace("-", " ") - inflateSpinners() - - viewModel.loanAccountUiState.observe(viewLifecycleOwner) { - when (it) { - is LoanAccountUiState.ShowAllLoan -> { - showProgressbar(false) - showAllLoan(it.productLoans) - } - - is LoanAccountUiState.ShowFetchingError -> { - showProgressbar(false) - showFetchingError(it.message) - } - - is LoanAccountUiState.ShowLoanAccountCreatedSuccessfully -> { - showProgressbar(false) - showLoanAccountCreatedSuccessfully(it.loans) - } - - is LoanAccountUiState.ShowLoanAccountTemplate -> { - showProgressbar(false) - showLoanAccountTemplate(it.loanTemplate) - } - - is LoanAccountUiState.ShowMessage -> { - showProgressbar(false) - showMessage(it.message) - } - - is LoanAccountUiState.ShowProgressbar -> showProgressbar(true) - } - } - - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.btnLoanSubmit.setOnClickListener { - submit() - } - - binding.tvSubmittedonDate.setOnClickListener { - setTvSubmittedOnDate() - } - - binding.tvDisbursementonDate.setOnClickListener { - setTvDisbursementOnDate() - } - } - - - private fun submit() { - val loansPayload = LoansPayload() - loansPayload.allowPartialPeriodInterestCalcualtion = binding.cbCalculateinterest - .isChecked - loansPayload.amortizationType = amortizationTypeId - loansPayload.clientId = clientId - loansPayload.dateFormat = "dd MMMM yyyy" - loansPayload.expectedDisbursementDate = disbursementDate - loansPayload.interestCalculationPeriodType = interestCalculationPeriodTypeId - loansPayload.loanType = "individual" - loansPayload.locale = "en" - loansPayload.numberOfRepayments = - binding.etNumberofrepayments.editableText.toString().toInt() - loansPayload.principal = binding.etPrincipal.editableText.toString().toDouble() - loansPayload.productId = productId - loansPayload.repaymentEvery = binding.etRepaidevery.editableText.toString().toInt() - loansPayload.submittedOnDate = submissionDate - loansPayload.loanPurposeId = loanPurposeId - loansPayload.loanTermFrequency = binding.etLoanterm.editableText.toString().toInt() - loansPayload.loanTermFrequencyType = loanTermFrequency - - //loanTermFrequencyType and repaymentFrequencyType should be the same. - loansPayload.repaymentFrequencyType = loanTermFrequency - loansPayload.repaymentFrequencyDayOfWeekType = - if (repaymentFrequencyDayOfWeek != null) repaymentFrequencyDayOfWeek else null - loansPayload.repaymentFrequencyNthDayType = - if (repaymentFrequencyNthDayType != null) repaymentFrequencyNthDayType else null - loansPayload.transactionProcessingStrategyId = transactionProcessingStrategyId - loansPayload.fundId = fundId - loansPayload.interestType = interestTypeId - loansPayload.loanOfficerId = loanOfficerId - loansPayload.linkAccountId = linkAccountId - interestRatePerPeriod = - binding.etNominalInterestRate.editableText.toString().toDouble() - loansPayload.interestRatePerPeriod = interestRatePerPeriod - if (hasDataTables) { - val fragment = DataTableListFragment.newInstance( - mLoanTemplate.dataTables, - loansPayload, Constants.CLIENT_LOAN - ) - val fragmentTransaction = requireActivity().supportFragmentManager - .beginTransaction() - fragmentTransaction.addToBackStack(FragmentConstants.DATA_TABLE_LIST) - fragmentTransaction.replace(R.id.container, fragment).commit() - } else { - initiateLoanCreation(loansPayload) - } - } - - override fun onDatePicked(date: String?) { - if (isSubmissionDate) { - binding.tvSubmittedonDate.text = date - submissionDate = date - isSubmissionDate = false - } - if (isDisbursebemntDate) { - binding.tvDisbursementonDate.text = date - disbursementDate = date - isDisbursebemntDate = false - } - } - - private fun inflateSpinners() { - - //Inflating the LoanProducts Spinner - mLoanProductAdapter = ArrayAdapter( - requireActivity(), android.R.layout.simple_spinner_item, - mListLoanProducts - ) - mLoanProductAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spLproduct.adapter = mLoanProductAdapter - binding.spLproduct.onItemSelectedListener = this - - //Inflating the LoanPurposeOptions - mLoanPurposeOptionsAdapter = ArrayAdapter( - requireActivity(), android.R.layout.simple_spinner_item, - mListLoanPurposeOptions - ) - mLoanPurposeOptionsAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spLoanPurpose.adapter = mLoanPurposeOptionsAdapter - binding.spLoanPurpose.onItemSelectedListener = this - - //Inflating Linking Options - mAccountLinkingOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListAccountLinkingOptions - ) - mAccountLinkingOptionsAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spLinkingOptions.adapter = mAccountLinkingOptionsAdapter - binding.spLinkingOptions.onItemSelectedListener = this - - //Inflating AmortizationTypeOptions Spinner - mAmortizationTypeOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListAmortizationTypeOptions - ) - mAmortizationTypeOptionsAdapter?.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item - ) - binding.spAmortization.adapter = mAmortizationTypeOptionsAdapter - binding.spAmortization.onItemSelectedListener = this - - //Inflating InterestCalculationPeriodTypeOptions Spinner - mInterestCalculationPeriodTypeOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListInterestCalculationPeriodTypeOptions - ) - mInterestCalculationPeriodTypeOptionsAdapter?.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item - ) - binding.spInterestcalculationperiod.adapter = mInterestCalculationPeriodTypeOptionsAdapter - binding.spInterestcalculationperiod.onItemSelectedListener = this - - //Inflate TransactionProcessingStrategyOptions Spinner - mTransactionProcessingStrategyOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListTransactionProcessingStrategyOptions - ) - mTransactionProcessingStrategyOptionsAdapter?.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item - ) - binding.spRepaymentstrategy.adapter = mTransactionProcessingStrategyOptionsAdapter - binding.spRepaymentstrategy.onItemSelectedListener = this - - //Inflate TermFrequencyTypeOptionsAdapter Spinner - mTermFrequencyTypeOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListTermFrequencyTypeOptions - ) - mTermFrequencyTypeOptionsAdapter?.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item - ) - binding.spPaymentPeriods.adapter = mTermFrequencyTypeOptionsAdapter - binding.spPaymentPeriods.onItemSelectedListener = this - - //Inflate LoanTerm Frequency Type adapter - mLoanTermFrequencyTypeAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListLoanTermFrequencyTypeOptions - ) - mLoanTermFrequencyTypeAdapter?.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item - ) - binding.spLoanTermPeriods.adapter = mLoanTermFrequencyTypeAdapter - binding.spLoanTermPeriods.onItemSelectedListener = this - - //Inflate FondOptions Spinner - mLoanFundOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListLoanFundOptions - ) - mLoanFundOptionsAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spFund.adapter = mLoanFundOptionsAdapter - binding.spFund.onItemSelectedListener = this - - //Inflating LoanOfficerOptions Spinner - mLoanOfficerOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListLoanOfficerOptions - ) - mLoanOfficerOptionsAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spLoanOfficer.adapter = mLoanOfficerOptionsAdapter - binding.spLoanOfficer.onItemSelectedListener = this - - //Inflating InterestTypeOptions Spinner - mInterestTypeOptionsAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_item, mListInterestTypeOptions - ) - mInterestTypeOptionsAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spInterestType.adapter = mInterestTypeOptionsAdapter - binding.spInterestType.onItemSelectedListener = this - } - - private fun inflateRepaidMonthSpinners() { - mRepaymentFrequencyNthDayTypeOptionsAdapter = ArrayAdapter( - requireActivity(), android.R.layout.simple_spinner_item, - mListRepaymentFrequencyNthDayTypeOptions - ) - mRepaymentFrequencyNthDayTypeOptionsAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spRepaymentFreqNthDay.adapter = mRepaymentFrequencyNthDayTypeOptionsAdapter - binding.spRepaymentFreqNthDay.onItemSelectedListener = this - mRepaymentFrequencyDayOfWeekTypeOptionsAdapter = ArrayAdapter( - requireActivity(), android.R.layout.simple_spinner_item, - mListRepaymentFrequencyDayOfWeekTypeOptions - ) - mRepaymentFrequencyDayOfWeekTypeOptionsAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.spRepaymentFreqDayOfWeek.adapter = mRepaymentFrequencyDayOfWeekTypeOptionsAdapter - binding.spRepaymentFreqDayOfWeek.onItemSelectedListener = this - binding.spRepaymentFreqNthDay.setSelection(mListRepaymentFrequencyNthDayTypeOptions.size - 1) - binding.spRepaymentFreqDayOfWeek.setSelection( - mListRepaymentFrequencyDayOfWeekTypeOptions.size - 1 - ) - } - - private fun inflateLoansProductSpinner() { - viewModel.loadAllLoans() - } - - private fun inflateLoanPurposeSpinner() { - productId?.let { viewModel.loadLoanAccountTemplate(clientId, it) } - } - - private fun initiateLoanCreation(loansPayload: LoansPayload) { - viewModel.createLoansAccount(loansPayload) - } - - private fun inflateSubmissionDate() { - mfDatePicker = MFDatePicker.newInsance(this) - binding.tvSubmittedonDate.text = MFDatePicker.datePickedAsString - } - - private fun setTvSubmittedOnDate() { - isSubmissionDate = true - mfDatePicker?.show( - requireActivity().supportFragmentManager, - FragmentConstants.DFRAG_DATE_PICKER - ) - } - - private fun inflateDisbursementDate() { - mfDatePicker = MFDatePicker.newInsance(this) - binding.tvDisbursementonDate.text = MFDatePicker.datePickedAsString - } - - private fun setTvDisbursementOnDate() { - isDisbursebemntDate = true - mfDatePicker?.show( - requireActivity().supportFragmentManager, - FragmentConstants.DFRAG_DATE_PICKER - ) - } - - private fun showAllLoan(loans: List) { - mLoanProducts = loans - mListLoanProducts.clear() - for (loanProducts in mLoanProducts) { - loanProducts.name?.let { mListLoanProducts.add(it) } - } - mLoanProductAdapter?.notifyDataSetChanged() - } - - private fun showLoanAccountTemplate(loanTemplate: LoanTemplate) { - mLoanTemplate = loanTemplate - hasDataTables = mLoanTemplate.dataTables.size > 0 - mListRepaymentFrequencyNthDayTypeOptions.clear() - mRepaymentFrequencyNthDayTypeOptions = mLoanTemplate - .repaymentFrequencyNthDayTypeOptions - for (options in mRepaymentFrequencyNthDayTypeOptions) { - options.value?.let { mListRepaymentFrequencyNthDayTypeOptions.add(it) } - } - mListRepaymentFrequencyNthDayTypeOptions.add( - resources.getString(R.string.select_week_hint) - ) - mListRepaymentFrequencyDayOfWeekTypeOptions.clear() - mRepaymentFrequencyDaysOfWeekTypeOptions = mLoanTemplate - .repaymentFrequencyDaysOfWeekTypeOptions - for (options in mRepaymentFrequencyDaysOfWeekTypeOptions) { - options.value?.let { mListRepaymentFrequencyDayOfWeekTypeOptions.add(it) } - } - mListRepaymentFrequencyDayOfWeekTypeOptions.add( - resources.getString(R.string.select_day_hint) - ) - mListLoanPurposeOptions.clear() - for (loanPurposeOptions in mLoanTemplate.loanPurposeOptions) { - loanPurposeOptions.name?.let { mListLoanPurposeOptions.add(it) } - } - mLoanPurposeOptionsAdapter?.notifyDataSetChanged() - mListAccountLinkingOptions.clear() - for (options in mLoanTemplate.accountLinkingOptions) { - options.productName?.let { mListAccountLinkingOptions.add(it) } - } - mListAccountLinkingOptions.add( - resources.getString(R.string.select_linkage_account_hint) - ) - mAccountLinkingOptionsAdapter?.notifyDataSetChanged() - mListAmortizationTypeOptions.clear() - for (amortizationTypeOptions in mLoanTemplate.amortizationTypeOptions) { - amortizationTypeOptions.value?.let { mListAmortizationTypeOptions.add(it) } - } - mAmortizationTypeOptionsAdapter?.notifyDataSetChanged() - mListInterestCalculationPeriodTypeOptions.clear() - for (interestCalculationPeriodType in mLoanTemplate - .interestCalculationPeriodTypeOptions) { - interestCalculationPeriodType.value?.let { - mListInterestCalculationPeriodTypeOptions.add( - it + return ComposeView(requireActivity()).apply { + setContent { + LoanAccountScreen( + clientId = clientId, + onBackPressed = { + findNavController().popBackStack() + }, dataTable = { dataTables, loansPayload -> + dataTables(dataTables, loansPayload) + } ) } } - mInterestCalculationPeriodTypeOptionsAdapter?.notifyDataSetChanged() - mListTransactionProcessingStrategyOptions.clear() - for (transactionProcessingStrategyOptions in mLoanTemplate.transactionProcessingStrategyOptions) { - transactionProcessingStrategyOptions - .name?.let { - mListTransactionProcessingStrategyOptions.add( - it - ) - } - } - mTransactionProcessingStrategyOptionsAdapter?.notifyDataSetChanged() - mListTermFrequencyTypeOptions.clear() - for (termFrequencyTypeOptions in mLoanTemplate.termFrequencyTypeOptions) { - termFrequencyTypeOptions.value?.let { mListTermFrequencyTypeOptions.add(it) } - } - mTermFrequencyTypeOptionsAdapter?.notifyDataSetChanged() - mListLoanTermFrequencyTypeOptions.clear() - for (termFrequencyTypeOptions in mLoanTemplate.termFrequencyTypeOptions) { - termFrequencyTypeOptions.value?.let { mListLoanTermFrequencyTypeOptions.add(it) } - } - mLoanTermFrequencyTypeAdapter?.notifyDataSetChanged() - mListLoanFundOptions.clear() - for (fundOptions in mLoanTemplate.fundOptions) { - fundOptions.name?.let { mListLoanFundOptions.add(it) } - } - mLoanFundOptionsAdapter?.notifyDataSetChanged() - mListLoanOfficerOptions.clear() - for (loanOfficerOptions in mLoanTemplate.loanOfficerOptions) { - loanOfficerOptions.displayName?.let { mListLoanOfficerOptions.add(it) } - } - mLoanOfficerOptionsAdapter?.notifyDataSetChanged() - mListInterestTypeOptions.clear() - for (interestTypeOptions in mLoanTemplate.interestTypeOptions) { - interestTypeOptions.value?.let { mListInterestTypeOptions.add(it) } - } - mInterestTypeOptionsAdapter?.notifyDataSetChanged() - showDefaultValues() - } - - private fun showLoanAccountCreatedSuccessfully(loans: Loans?) { - Toast.makeText(activity, R.string.loan_creation_success, Toast.LENGTH_LONG).show() - requireActivity().supportFragmentManager.popBackStackImmediate() - } - - private fun showMessage(messageId: Int) { - Toaster.show(binding.root, messageId) - } - - private fun showFetchingError(s: String?) { - Toaster.show(binding.root, s) - } - - private fun showProgressbar(show: Boolean) { - showProgress(show) } - override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { - when (parent.id) { - R.id.sp_lproduct -> { - productId = mLoanProducts[position].id - inflateLoanPurposeSpinner() - } - - R.id.sp_loan_purpose -> loanPurposeId = mLoanTemplate.loanPurposeOptions[position].id - R.id.sp_amortization -> amortizationTypeId = - mLoanTemplate.amortizationTypeOptions[position].id - - R.id.sp_interestcalculationperiod -> interestCalculationPeriodTypeId = mLoanTemplate - .interestCalculationPeriodTypeOptions[position].id - - R.id.sp_repaymentstrategy -> transactionProcessingStrategyId = mLoanTemplate - .transactionProcessingStrategyOptions[position].id - - R.id.sp_payment_periods -> { - loanTermFrequency = mLoanTemplate.termFrequencyTypeOptions[position] - .id - loanTermFrequency?.let { binding.spLoanTermPeriods.setSelection(it) } - if (loanTermFrequency == 2) { - // Show and inflate Nth day and week spinners - showHideRepaidMonthSpinners(View.VISIBLE) - inflateRepaidMonthSpinners() - } else { - showHideRepaidMonthSpinners(View.GONE) - } - } - - R.id.sp_loan_term_periods -> { - loanTermFrequency = mLoanTemplate.termFrequencyTypeOptions[position] - .id - loanTermFrequency?.let { binding.spPaymentPeriods.setSelection(it) } - if (loanTermFrequency == 2) { - // Show and inflate Nth day and week spinners - showHideRepaidMonthSpinners(View.VISIBLE) - inflateRepaidMonthSpinners() - } else { - showHideRepaidMonthSpinners(View.GONE) - } - } - - R.id.sp_repayment_freq_nth_day -> repaymentFrequencyNthDayType = - if (mListRepaymentFrequencyNthDayTypeOptions[position] - == resources.getString(R.string.select_week_hint) - ) { - null - } else { - mLoanTemplate - .repaymentFrequencyNthDayTypeOptions[position].id - } - - R.id.sp_repayment_freq_day_of_week -> repaymentFrequencyDayOfWeek = - if (mListRepaymentFrequencyDayOfWeekTypeOptions[position] - == resources.getString(R.string.select_day_hint) - ) { - null - } else { - mLoanTemplate - .repaymentFrequencyDaysOfWeekTypeOptions[position].id - } - - R.id.sp_fund -> fundId = mLoanTemplate.fundOptions[position].id - R.id.sp_loan_officer -> loanOfficerId = mLoanTemplate.loanOfficerOptions[position].id - R.id.sp_interest_type -> interestTypeId = mLoanTemplate.interestTypeOptions[position].id - R.id.sp_linking_options -> linkAccountId = if (mListAccountLinkingOptions[position] - == resources.getString(R.string.select_linkage_account_hint) - ) { - null - } else { - mLoanTemplate.accountLinkingOptions[position].id - } - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) {} - private fun showHideRepaidMonthSpinners(visibility: Int) { - binding.spRepaymentFreqNthDay.visibility = visibility - binding.spRepaymentFreqDayOfWeek.visibility = visibility - binding.tvRepaidNthfreqLabelOn.visibility = visibility - } - - private fun showDefaultValues() { - interestRatePerPeriod = mLoanTemplate.interestRatePerPeriod - loanTermFrequency = mLoanTemplate.termPeriodFrequencyType?.id - termFrequency = mLoanTemplate.termFrequency - binding.etPrincipal.setText(mLoanTemplate.principal.toString()) - binding.etNumberofrepayments.setText(mLoanTemplate.numberOfRepayments.toString()) - binding.tvNominalRateYearMonth.text = mLoanTemplate.interestRateFrequencyType?.value - binding.etNominalInterestRate.setText(mLoanTemplate.interestRatePerPeriod.toString()) - binding.etLoanterm.setText(termFrequency.toString()) - if (mLoanTemplate.repaymentEvery != null) { - repaymentEvery = mLoanTemplate.repaymentEvery - binding.etRepaidevery.setText(repaymentEvery.toString()) - } - if (mLoanTemplate.fundId != null) { - fundId = mLoanTemplate.fundId - binding.spFund.setSelection(mLoanTemplate.fundId!!) - } - binding.spLinkingOptions.setSelection(mListAccountLinkingOptions.size) - } + override fun onResume() { + super.onResume() + (requireActivity() as AppCompatActivity).supportActionBar?.hide() + } + + override fun onStop() { + super.onStop() + (requireActivity() as AppCompatActivity).supportActionBar?.show() + } + + private fun dataTables(dataTables: List, loansPayload: LoansPayload) { + val fragment = DataTableListFragment.newInstance( + dataTables, + loansPayload, Constants.CLIENT_LOAN + ) + val fragmentTransaction = requireActivity().supportFragmentManager + .beginTransaction() + fragmentTransaction.addToBackStack(FragmentConstants.DATA_TABLE_LIST) + fragmentTransaction.replace(R.id.container, fragment).commit() + } + +// private fun submit() { +// val loansPayload = LoansPayload() +// loansPayload.allowPartialPeriodInterestCalcualtion = binding.cbCalculateinterest +// .isChecked +// loansPayload.amortizationType = amortizationTypeId +// loansPayload.clientId = clientId +// loansPayload.dateFormat = "dd MMMM yyyy" +// loansPayload.expectedDisbursementDate = disbursementDate +// loansPayload.interestCalculationPeriodType = interestCalculationPeriodTypeId +// loansPayload.loanType = "individual" +// loansPayload.locale = "en" +// loansPayload.numberOfRepayments = +// binding.etNumberofrepayments.editableText.toString().toInt() +// loansPayload.principal = binding.etPrincipal.editableText.toString().toDouble() +// loansPayload.productId = productId +// loansPayload.repaymentEvery = binding.etRepaidevery.editableText.toString().toInt() +// loansPayload.submittedOnDate = submissionDate +// loansPayload.loanPurposeId = loanPurposeId +// loansPayload.loanTermFrequency = binding.etLoanterm.editableText.toString().toInt() +// loansPayload.loanTermFrequencyType = loanTermFrequency +// +// //loanTermFrequencyType and repaymentFrequencyType should be the same. +// loansPayload.repaymentFrequencyType = loanTermFrequency +// loansPayload.repaymentFrequencyDayOfWeekType = +// if (repaymentFrequencyDayOfWeek != null) repaymentFrequencyDayOfWeek else null +// loansPayload.repaymentFrequencyNthDayType = +// if (repaymentFrequencyNthDayType != null) repaymentFrequencyNthDayType else null +// loansPayload.transactionProcessingStrategyId = transactionProcessingStrategyId +// loansPayload.fundId = fundId +// loansPayload.interestType = interestTypeId +// loansPayload.loanOfficerId = loanOfficerId +// loansPayload.linkAccountId = linkAccountId +// interestRatePerPeriod = +// binding.etNominalInterestRate.editableText.toString().toDouble() +// loansPayload.interestRatePerPeriod = interestRatePerPeriod +// if (hasDataTables) { +// val fragment = DataTableListFragment.newInstance( +// mLoanTemplate.dataTables, +// loansPayload, Constants.CLIENT_LOAN +// ) +// val fragmentTransaction = requireActivity().supportFragmentManager +// .beginTransaction() +// fragmentTransaction.addToBackStack(FragmentConstants.DATA_TABLE_LIST) +// fragmentTransaction.replace(R.id.container, fragment).commit() +// } else { +// initiateLoanCreation(loansPayload) +// } +// } + + +// override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { +// when (parent.id) { +// R.id.sp_lproduct -> { +// productId = mLoanProducts[position].id +// inflateLoanPurposeSpinner() +// } +// +// R.id.sp_loan_purpose -> loanPurposeId = mLoanTemplate.loanPurposeOptions[position].id +// R.id.sp_amortization -> amortizationTypeId = +// mLoanTemplate.amortizationTypeOptions[position].id +// +// R.id.sp_interestcalculationperiod -> interestCalculationPeriodTypeId = mLoanTemplate +// .interestCalculationPeriodTypeOptions[position].id +// +// R.id.sp_repaymentstrategy -> transactionProcessingStrategyId = mLoanTemplate +// .transactionProcessingStrategyOptions[position].id +// +// R.id.sp_payment_periods -> { +// loanTermFrequency = mLoanTemplate.termFrequencyTypeOptions[position] +// .id +// loanTermFrequency?.let { binding.spLoanTermPeriods.setSelection(it) } +// if (loanTermFrequency == 2) { +// // Show and inflate Nth day and week spinners +// showHideRepaidMonthSpinners(View.VISIBLE) +// inflateRepaidMonthSpinners() +// } else { +// showHideRepaidMonthSpinners(View.GONE) +// } +// } +// +// R.id.sp_loan_term_periods -> { +// loanTermFrequency = mLoanTemplate.termFrequencyTypeOptions[position] +// .id +// loanTermFrequency?.let { binding.spPaymentPeriods.setSelection(it) } +// if (loanTermFrequency == 2) { +// // Show and inflate Nth day and week spinners +// showHideRepaidMonthSpinners(View.VISIBLE) +// inflateRepaidMonthSpinners() +// } else { +// showHideRepaidMonthSpinners(View.GONE) +// } +// } +// +// R.id.sp_repayment_freq_nth_day -> repaymentFrequencyNthDayType = +// if (mListRepaymentFrequencyNthDayTypeOptions[position] +// == resources.getString(R.string.select_week_hint) +// ) { +// null +// } else { +// mLoanTemplate +// .repaymentFrequencyNthDayTypeOptions[position].id +// } +// +// R.id.sp_repayment_freq_day_of_week -> repaymentFrequencyDayOfWeek = +// if (mListRepaymentFrequencyDayOfWeekTypeOptions[position] +// == resources.getString(R.string.select_day_hint) +// ) { +// null +// } else { +// mLoanTemplate +// .repaymentFrequencyDaysOfWeekTypeOptions[position].id +// } +// +// R.id.sp_fund -> fundId = mLoanTemplate.fundOptions[position].id +// R.id.sp_loan_officer -> loanOfficerId = mLoanTemplate.loanOfficerOptions[position].id +// R.id.sp_interest_type -> interestTypeId = mLoanTemplate.interestTypeOptions[position].id +// R.id.sp_linking_options -> linkAccountId = if (mListAccountLinkingOptions[position] +// == resources.getString(R.string.select_linkage_account_hint) +// ) { +// null +// } else { +// mLoanTemplate.accountLinkingOptions[position].id +// } +// } +// } } \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountUiState.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountUiState.kt deleted file mode 100644 index 884a5eb09d9..00000000000 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountUiState.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.mifos.mifosxdroid.online.loanaccount - -import com.mifos.core.objects.accounts.loan.Loans -import com.mifos.core.objects.organisation.LoanProducts -import com.mifos.core.objects.templates.loans.LoanTemplate - -/** - * Created by Aditya Gupta on 08/08/23. - */ -sealed class LoanAccountUiState { - - data object ShowProgressbar : LoanAccountUiState() - - data class ShowMessage(val message: Int) : LoanAccountUiState() - - data class ShowAllLoan(val productLoans: List) : LoanAccountUiState() - - data class ShowLoanAccountTemplate(val loanTemplate: LoanTemplate) : LoanAccountUiState() - - data class ShowFetchingError(val message: String?) : LoanAccountUiState() - - data class ShowLoanAccountCreatedSuccessfully(val loans: Loans?) : LoanAccountUiState() -} \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountViewModel.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountViewModel.kt deleted file mode 100644 index d34d3d83345..00000000000 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanaccount/LoanAccountViewModel.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.mifos.mifosxdroid.online.loanaccount - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.mifos.core.data.LoansPayload -import com.mifos.core.objects.accounts.loan.Loans -import com.mifos.core.objects.organisation.LoanProducts -import com.mifos.core.objects.templates.loans.LoanTemplate -import com.mifos.mifosxdroid.R -import dagger.hilt.android.lifecycle.HiltViewModel -import rx.Subscriber -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import javax.inject.Inject - -/** - * Created by Aditya Gupta on 08/08/23. - */ -@HiltViewModel -class LoanAccountViewModel @Inject constructor(private val repository: LoanAccountRepository) : - ViewModel() { - - private val _loanAccountUiState = MutableLiveData() - - val loanAccountUiState: LiveData - get() = _loanAccountUiState - - fun loadAllLoans() { - _loanAccountUiState.value = LoanAccountUiState.ShowProgressbar - repository.allLoans() - .observeOn(AndroidSchedulers.mainThread()) - .subscribeOn(Schedulers.io()) - .subscribe(object : Subscriber>() { - override fun onCompleted() { - } - - override fun onError(e: Throwable) { - _loanAccountUiState.value = - LoanAccountUiState.ShowMessage(R.string.failed_to_fetch_loan_products) - } - - override fun onNext(productLoanses: List) { - _loanAccountUiState.value = LoanAccountUiState.ShowAllLoan(productLoanses) - } - }) - } - - fun loadLoanAccountTemplate(clientId: Int, productId: Int) { - _loanAccountUiState.value = LoanAccountUiState.ShowProgressbar - repository.getLoansAccountTemplate(clientId, productId) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeOn(Schedulers.io()) - .subscribe(object : Subscriber() { - override fun onCompleted() { - } - - override fun onError(e: Throwable) { - _loanAccountUiState.value = - LoanAccountUiState.ShowMessage(R.string.failed_to_fetch_loan_template) - } - - override fun onNext(loanTemplate: LoanTemplate?) { - if (loanTemplate != null) { - _loanAccountUiState.value = - LoanAccountUiState.ShowLoanAccountTemplate(loanTemplate) - } - } - }) - } - - fun createLoansAccount(loansPayload: LoansPayload?) { - _loanAccountUiState.value = LoanAccountUiState.ShowProgressbar - repository.createLoansAccount(loansPayload) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeOn(Schedulers.io()) - .subscribe(object : Subscriber() { - override fun onCompleted() { - } - - override fun onError(e: Throwable) { - _loanAccountUiState.value = - LoanAccountUiState.ShowFetchingError(e.message.toString()) - } - - override fun onNext(loans: Loans?) { - _loanAccountUiState.value = - LoanAccountUiState.ShowLoanAccountCreatedSuccessfully(loans) - } - }) - } - -} \ No newline at end of file diff --git a/mifosng-android/src/main/res/navigation/nav_graph.xml b/mifosng-android/src/main/res/navigation/nav_graph.xml index 0947c638673..edc3d5bca44 100644 --- a/mifosng-android/src/main/res/navigation/nav_graph.xml +++ b/mifosng-android/src/main/res/navigation/nav_graph.xml @@ -207,14 +207,14 @@ - - + - - +