-
Notifications
You must be signed in to change notification settings - Fork 682
feat(loan): implement loan repayment form parity with Web App (MIFOSA… #2597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gurnoorpannu
wants to merge
42
commits into
openMF:development
Choose a base branch
from
gurnoorpannu:feature/loan-repayment-form-parity-clean
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+753
−424
Open
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
a8c29cb
feat(loan): implement loan repayment form parity with Web App (MIFOSA…
gurnoorpannu c430781
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu 094be7e
implemented requested changes
gurnoorpannu 6be78cf
Merge remote-tracking branch 'origin/feature/loan-repayment-form-pari…
gurnoorpannu 1a504bc
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu 242c5dd
made requested changes
gurnoorpannu 35e73c8
Merge remote-tracking branch 'origin/feature/loan-repayment-form-pari…
gurnoorpannu 838a8a3
Made changes requested
gurnoorpannu 34d3d40
Fixing changes requested
gurnoorpannu 736575c
feat(loan): implement loan repayment form parity with Web App (MIFOSA…
gurnoorpannu 3ccf92d
implemented requested changes
gurnoorpannu 276cccb
made requested changes
gurnoorpannu 9cfa094
Made changes requested
gurnoorpannu 0b356ad
Fixing changes requested
gurnoorpannu 12a57fa
Fixing conflitcts
gurnoorpannu 5dca051
Merge remote-tracking branch 'origin/feature/loan-repayment-form-pari…
gurnoorpannu da0da1e
implemented requested changes
gurnoorpannu 35a47c4
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu bfa45cb
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu fe61b9f
implemented coderabbit suggestions
gurnoorpannu 518dcb7
Merge remote-tracking branch 'origin/feature/loan-repayment-form-pari…
gurnoorpannu 0ebdc13
Added the externalId field
gurnoorpannu 3782f02
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu 70dd3fd
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu 1c8f53a
implemented Requested code changes
gurnoorpannu c8c1f90
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu aaeb0d4
implemented Requested code changes
gurnoorpannu 0c89f51
Merge remote-tracking branch 'origin/feature/loan-repayment-form-pari…
gurnoorpannu 7d0902b
requested changes
gurnoorpannu d9675ce
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu 5d7c3f0
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu ccca472
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu 9ea3a7d
Made requested changes!
gurnoorpannu f03ab6c
Merge remote-tracking branch 'origin/feature/loan-repayment-form-pari…
gurnoorpannu 95c69d7
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu 2a87ebc
Fixed Merge Conflicts
gurnoorpannu ba2a0f7
Merge remote-tracking branch 'origin/feature/loan-repayment-form-pari…
gurnoorpannu 00b8389
Fixed Merge Conflicts
gurnoorpannu 5c0a8fa
Merge branch 'development' into feature/loan-repayment-form-parity-clean
gurnoorpannu f21ca5c
Solved Merge Conflicts
gurnoorpannu efea4a2
Solved Merge Conflicts
gurnoorpannu 015ed1a
Merge branch 'development' into feature/loan-repayment-form-parity-clean
niyajali File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
840 changes: 531 additions & 309 deletions
840
...re/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreen.kt
Large diffs are not rendered by default.
Oops, something went wrong.
31 changes: 0 additions & 31 deletions
31
...e/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentUiState.kt
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,124 +13,231 @@ import androidclient.feature.loan.generated.resources.Res | |
| import androidclient.feature.loan.generated.resources.feature_loan_failed_to_load_loan_repayment | ||
| import androidclient.feature.loan.generated.resources.feature_loan_payment_failed | ||
| import androidx.lifecycle.SavedStateHandle | ||
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.viewModelScope | ||
| import androidx.navigation.toRoute | ||
| import com.mifos.core.common.utils.CurrencyFormatter | ||
| import com.mifos.core.common.utils.DataState | ||
| import com.mifos.core.data.repository.LoanRepaymentRepository | ||
| import com.mifos.core.ui.util.BaseViewModel | ||
| import com.mifos.room.entities.accounts.loans.LoanRepaymentRequestEntity | ||
| import com.mifos.room.entities.accounts.loans.LoanRepaymentResponseEntity | ||
| import com.mifos.room.entities.templates.loans.LoanRepaymentTemplateEntity | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.StateFlow | ||
| import kotlinx.coroutines.launch | ||
| import kotlin.math.round | ||
| import org.jetbrains.compose.resources.StringResource | ||
| import kotlin.time.Clock | ||
| import kotlin.time.ExperimentalTime | ||
|
|
||
| class LoanRepaymentViewModel( | ||
| savedStateHandle: SavedStateHandle, | ||
| private val repository: LoanRepaymentRepository, | ||
| ) : ViewModel() { | ||
| ) : BaseViewModel<LoanRepaymentUiState, LoanRepaymentEvent, LoanRepaymentAction>( | ||
| initialState = LoanRepaymentUiState(), | ||
| ) { | ||
|
|
||
| val arg = savedStateHandle.toRoute<LoanRepaymentScreenRoute>() | ||
|
|
||
| private val _loanRepaymentUiState = | ||
| MutableStateFlow<LoanRepaymentUiState>(LoanRepaymentUiState.ShowProgressbar) | ||
| val loanRepaymentUiState: StateFlow<LoanRepaymentUiState> get() = _loanRepaymentUiState | ||
| init { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| repaymentDate = currentEpochMillis(), | ||
| ) | ||
| trySendAction(LoanRepaymentAction.CheckDatabaseLoanRepayment) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why we're doing like this, make it flowable and collect it and send the appropriate action |
||
| } | ||
|
|
||
| fun loanLoanRepaymentTemplate() { | ||
| @OptIn(ExperimentalTime::class) | ||
| private fun currentEpochMillis(): Long = Clock.System.now().toEpochMilliseconds() | ||
|
|
||
| override fun handleAction(action: LoanRepaymentAction) { | ||
| when (action) { | ||
| is LoanRepaymentAction.LoadLoanRepaymentTemplate -> { | ||
| loadLoanRepaymentTemplate() | ||
| } | ||
| is LoanRepaymentAction.CheckDatabaseLoanRepayment -> { | ||
| checkDatabaseLoanRepaymentByLoanId() | ||
| } | ||
| is LoanRepaymentAction.SubmitPayment -> { | ||
| submitPayment(action.request) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun loadLoanRepaymentTemplate() { | ||
| viewModelScope.launch { | ||
| repository.getLoanRepayTemplate(arg.loanId).collect { state -> | ||
| when (state) { | ||
| is DataState.Error -> | ||
| _loanRepaymentUiState.value = | ||
| LoanRepaymentUiState.ShowError( | ||
| Res.string | ||
| .feature_loan_failed_to_load_loan_repayment, | ||
| ) | ||
| DataState.Loading -> | ||
| _loanRepaymentUiState.value = LoanRepaymentUiState.ShowProgressbar | ||
| is DataState.Success -> | ||
| _loanRepaymentUiState.value = | ||
| LoanRepaymentUiState.ShowLoanRepayTemplate( | ||
| state.data ?: LoanRepaymentTemplateEntity(), | ||
| ) | ||
| is DataState.Error -> { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| isLoading = false, | ||
| error = Res.string.feature_loan_failed_to_load_loan_repayment, | ||
| ) | ||
| } | ||
| DataState.Loading -> { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(isLoading = true, error = null) | ||
| } | ||
| is DataState.Success -> { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| isLoading = false, | ||
| error = null, | ||
| loanRepaymentTemplate = state.data ?: LoanRepaymentTemplateEntity(), | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun submitPayment(request: LoanRepaymentRequestEntity) { | ||
| private fun submitPayment(request: LoanRepaymentRequestEntity) { | ||
| viewModelScope.launch { | ||
| _loanRepaymentUiState.value = LoanRepaymentUiState.ShowProgressbar | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(isLoading = true, error = null) | ||
|
|
||
| try { | ||
| val loanRepaymentResponse = repository.submitPayment(arg.loanId, request) | ||
| _loanRepaymentUiState.value = | ||
| LoanRepaymentUiState.ShowPaymentSubmittedSuccessfully( | ||
| loanRepaymentResponse, | ||
| ) | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(isLoading = false) | ||
| sendEvent(LoanRepaymentEvent.PaymentSubmittedSuccessfully(loanRepaymentResponse)) | ||
| } catch (e: Exception) { | ||
| _loanRepaymentUiState.value = | ||
| LoanRepaymentUiState.ShowError(Res.string.feature_loan_payment_failed) | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| isLoading = false, | ||
| error = Res.string.feature_loan_payment_failed, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun checkDatabaseLoanRepaymentByLoanId() { | ||
| private fun checkDatabaseLoanRepaymentByLoanId() { | ||
| viewModelScope.launch { | ||
| repository.getDatabaseLoanRepaymentByLoanId(arg.loanId).collect { state -> | ||
| when (state) { | ||
| is DataState.Error -> | ||
| _loanRepaymentUiState.value = | ||
| LoanRepaymentUiState.ShowError( | ||
| Res.string | ||
| .feature_loan_failed_to_load_loan_repayment, | ||
| ) | ||
| DataState.Loading -> | ||
| _loanRepaymentUiState.value = LoanRepaymentUiState.ShowProgressbar | ||
| is DataState.Error -> { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| isLoading = false, | ||
| hasCheckedDatabase = true, | ||
| error = Res.string.feature_loan_failed_to_load_loan_repayment, | ||
| ) | ||
| } | ||
| DataState.Loading -> { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(isLoading = true, error = null) | ||
| } | ||
| is DataState.Success -> { | ||
| if (state.data != null) { | ||
| _loanRepaymentUiState.value = | ||
| LoanRepaymentUiState.ShowLoanRepaymentExistInDatabase | ||
| } else { | ||
| _loanRepaymentUiState.value = | ||
| LoanRepaymentUiState.ShowLoanRepaymentDoesNotExistInDatabase | ||
| val existsInDatabase = state.data != null | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| isLoading = false, | ||
| error = null, | ||
| hasCheckedDatabase = true, | ||
| loanRepaymentExistsInDatabase = existsInDatabase, | ||
| ) | ||
| if (!existsInDatabase) { | ||
| trySendAction(LoanRepaymentAction.LoadLoanRepaymentTemplate) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun calculateTotal(fees: String, amount: String, additionalPayment: String): Double { | ||
| fun setValue(value: String): Double { | ||
| if (value.isEmpty()) return 0.0 | ||
| return try { | ||
| value.toDouble() | ||
| } catch (e: NumberFormatException) { | ||
| 0.0 | ||
| } | ||
| } | ||
| val total = setValue(fees) + setValue(amount) + setValue(additionalPayment) | ||
| return round(total * 100) / 100.0 | ||
| fun updatePaymentType(paymentType: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(paymentType = paymentType) | ||
| } | ||
|
|
||
| fun updatePaymentTypeWithId(paymentType: String, paymentTypeId: Int) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| paymentType = paymentType, | ||
| paymentTypeId = paymentTypeId, | ||
| paymentTypeError = null, | ||
| ) | ||
| } | ||
|
|
||
| fun formatCurrency(amount: Double?, code: String?, decimalPlaces: Int?): String { | ||
| return CurrencyFormatter.format( | ||
| balance = amount, | ||
| currencyCode = code, | ||
| maximumFractionDigits = decimalPlaces ?: 2, | ||
| fun updateAmount(amount: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| amount = amount, | ||
| amountError = null, | ||
| ) | ||
| } | ||
|
|
||
| fun isAllFieldsValid( | ||
| amount: String, | ||
| additionalPayment: String, | ||
| fees: String, | ||
| paymentType: String, | ||
| ): Boolean { | ||
| return listOf(amount, additionalPayment, fees).all { | ||
| it.trim().toDoubleOrNull()?.let { n -> n >= 0 } == true | ||
| } && paymentType.isNotBlank() | ||
| fun updateAdditionalPayment(additionalPayment: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(additionalPayment = additionalPayment) | ||
| } | ||
|
|
||
| fun updateFees(fees: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(fees = fees) | ||
| } | ||
|
|
||
| fun updateRepaymentDate(date: Long) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(repaymentDate = date) | ||
| } | ||
|
|
||
| fun updateShowPaymentDetails(show: Boolean) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(showPaymentDetails = show) | ||
| } | ||
|
|
||
| fun updateAccountNumber(accountNumber: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(accountNumber = accountNumber) | ||
| } | ||
|
|
||
| fun updateExternalId(externalId: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(externalId = externalId) | ||
| } | ||
|
|
||
| fun updateChequeNumber(chequeNumber: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(chequeNumber = chequeNumber) | ||
| } | ||
|
|
||
| fun updateRoutingCode(routingCode: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(routingCode = routingCode) | ||
| } | ||
|
|
||
| fun updateReceiptNumber(receiptNumber: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(receiptNumber = receiptNumber) | ||
| } | ||
|
|
||
| fun updateBankNumber(bankNumber: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(bankNumber = bankNumber) | ||
| } | ||
|
|
||
| fun updateNote(note: String) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(note = note) | ||
| } | ||
|
|
||
| fun updateWaivePenalties(waive: Boolean) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy(waivePenalties = waive) | ||
| } | ||
|
|
||
| fun setValidationErrors(amountError: String?, paymentTypeError: String?) { | ||
| mutableStateFlow.value = mutableStateFlow.value.copy( | ||
| amountError = amountError, | ||
| paymentTypeError = paymentTypeError, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| data class LoanRepaymentUiState( | ||
| val isLoading: Boolean = false, | ||
| val error: StringResource? = null, | ||
| val loanRepaymentTemplate: LoanRepaymentTemplateEntity? = null, | ||
| val loanRepaymentExistsInDatabase: Boolean = false, | ||
| val hasCheckedDatabase: Boolean = false, | ||
| val showPaymentDetails: Boolean = false, | ||
| val paymentType: String = "", | ||
| val amount: String = "", | ||
| val additionalPayment: String = "", | ||
| val fees: String = "", | ||
| val paymentTypeId: Int = 0, | ||
| val repaymentDate: Long = 0L, | ||
| val accountNumber: String = "", | ||
| val externalId: String = "", | ||
| val chequeNumber: String = "", | ||
| val routingCode: String = "", | ||
| val receiptNumber: String = "", | ||
| val bankNumber: String = "", | ||
| val note: String = "", | ||
| val waivePenalties: Boolean = false, | ||
| val amountError: String? = null, | ||
| val paymentTypeError: String? = null, | ||
| ) | ||
|
|
||
| sealed interface LoanRepaymentEvent { | ||
| data class PaymentSubmittedSuccessfully(val response: LoanRepaymentResponseEntity?) : LoanRepaymentEvent | ||
| } | ||
|
|
||
| sealed interface LoanRepaymentAction { | ||
| data object LoadLoanRepaymentTemplate : LoanRepaymentAction | ||
| data object CheckDatabaseLoanRepayment : LoanRepaymentAction | ||
| data class SubmitPayment(val request: LoanRepaymentRequestEntity) : LoanRepaymentAction | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Directly move this on initial state