Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ interface LoanAccountDisbursementRepository {
command: String?,
): Flow<DataState<LoanTransactionTemplate>>

fun disburseLoan(
suspend fun disburseLoan(
loanId: Int,
loanDisbursement: LoanDisbursement?,
): Flow<DataState<GenericResponse>>
): DataState<GenericResponse>
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This follows the approach suggested by @niyajali, but the current combine usage doesn’t actually prevent upstream calls when offline and may lead to incorrect error mapping.

I would suggest that we should align on the exact expected behavior/implementation details on Slack before proceeding, to avoid unintended side effects.

Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,65 @@ package com.mifos.core.data.repositoryImp
import com.mifos.core.common.utils.DataState
import com.mifos.core.common.utils.asDataStateFlow
import com.mifos.core.data.repository.LoanAccountDisbursementRepository
import com.mifos.core.data.util.NetworkMonitor
import com.mifos.core.model.objects.account.loan.LoanDisbursement
import com.mifos.core.network.GenericResponse
import com.mifos.core.network.datamanager.DataManagerLoan
import com.mifos.room.entities.templates.loans.LoanTransactionTemplate
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn

/**
* Created by Aditya Gupta on 10/08/23.
*/
class LoanAccountDisbursementRepositoryImp(
private val dataManagerLoan: DataManagerLoan,
private val ioDispatcher: CoroutineDispatcher,
private val networkMonitor: NetworkMonitor,
) : LoanAccountDisbursementRepository {

override fun getLoanTransactionTemplate(
loanId: Int,
command: String?,
): Flow<DataState<LoanTransactionTemplate>> {
return dataManagerLoan.getLoanTransactionTemplate(loanId, command)
.asDataStateFlow()
return combine(
networkMonitor.isOnline,
dataManagerLoan.getLoanTransactionTemplate(loanId, command)
.asDataStateFlow(),
) { isOnline, dataState ->
if (!isOnline && dataState !is DataState.Success) {
DataState.Error(NetworkUnavailableException())
} else {
dataState
}
}.flowOn(ioDispatcher)
}

override fun disburseLoan(
override suspend fun disburseLoan(
loanId: Int,
loanDisbursement: LoanDisbursement?,
): Flow<DataState<GenericResponse>> {
return dataManagerLoan.disburseLoan(loanId, loanDisbursement)
.asDataStateFlow()
): DataState<GenericResponse> {
return try {
if (!networkMonitor.isOnline.first()) {
return DataState.Error(NetworkUnavailableException())
}

val response = dataManagerLoan.disburseLoan(loanId, loanDisbursement)
.asDataStateFlow()
.first { it !is DataState.Loading }

Comment on lines +61 to +64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're converting a one-shot API call into a Flow.
Then converting it back into a single value.

If API is one-shot -> keep it simple:

return try {
    val response = dataManagerLoan.disburseLoan(...)
    DataState.Success(response)
} catch (e: Exception) {
    DataState.Error(e)
}

Or expose suspend -> not Flow

if (response is DataState.Success) {
DataState.Success(response.data)
} else {
DataState.Error((response as DataState.Error).exception)
}
} catch (e: Exception) {
DataState.Error(e)
}
}
}

class NetworkUnavailableException : IllegalStateException()
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@
package com.mifos.room.entities.templates.loans

import com.mifos.core.model.objects.template.loan.Type
import com.mifos.core.model.utils.IgnoredOnParcel
import com.mifos.core.model.utils.Parcelable
import com.mifos.core.model.utils.Parcelize
import com.mifos.room.entities.PaymentTypeOptionEntity
import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity
import kotlinx.serialization.Serializable

/**
* Created by Rajan Maurya on 14/02/17.
*/
@Serializable
@Parcelize
data class LoanTransactionTemplate(
@IgnoredOnParcel
val type: Type? = null,

val date: List<Int> = emptyList(),

val amount: Double? = null,

val availableDisbursementAmountWithOverApplied: Double? = null,

val currency: SavingAccountCurrencyEntity? = null,

val manuallyReversed: Boolean? = null,

val possibleNextRepaymentDate: List<Int> = emptyList(),

val paymentTypeOptions: List<PaymentTypeOptionEntity> = emptyList(),
) : Parcelable
)
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ object AppColors {
val loanIndicatorOther = Color.Black

val loanActiveStatus = Color(0xFF5CB85C)
val loanApprovedStatus = Color(0xFF4B46E5)
val loanPendingStatus = Color(0xFFFFA500)
val loanOverpaidStatus = Color(0xFF800080)
val loanUnknownStatus = Color(0xFF9E9E9E)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,22 @@
*/
package com.mifos.core.model.objects.account.loan

import com.mifos.core.model.utils.DateConstants
import com.mifos.core.model.utils.Parcelable
import com.mifos.core.model.utils.Parcelize
import kotlinx.serialization.Serializable

@Parcelize
@Serializable
data class LoanDisbursement(
var actualDisbursementDate: String? = null,
val actualDisbursementDate: String? = null,
val transactionAmount: Double? = null,
val paymentTypeId: Int? = null,
val externalId: String? = null,
val note: String? = null,

var note: String? = null,
val accountNumber: String? = null,
val checkNumber: String? = null,
val routingCode: String? = null,
val receiptNumber: String? = null,
val bankNumber: String? = null,

var transactionAmount: Double? = null,

var paymentId: Int? = null,

var locale: String? = DateConstants.LOCALE,

var dateFormat: String? = DateConstants.DATE_FORMAT,
) : Parcelable
var locale: String? = null,
val dateFormat: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ interface LoanService {

// Mandatory Fields
// String actualDisbursementDate
@POST(APIEndPoint.LOANS + "/{loanId}/?command=disburse")
@POST(APIEndPoint.LOANS + "/{loanId}?command=disburse")
fun disburseLoan(
@Path("loanId") loanId: Int,
@Body loanDisbursement: LoanDisbursement?,
Expand Down
19 changes: 15 additions & 4 deletions feature/loan/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,21 @@

<string name="feature_loan_disburse_loan">Disburse Loan</string>
<string name="feature_loan_loan_disburse_successfully">Loan disburse successfully</string>
<string name="feature_loan_approval_disbursement_date">Approval Disbursement Date</string>
<string name="feature_loan_loan_amount_disbursed">Loan Amount Disbursed</string>
<string name="feature_loan_approval_disbursement_date">Disbursed On*</string>
<string name="feature_loan_loan_amount_disbursed">Transaction Amount*</string>
<string name="feature_loan_submission_failed">Failed to disburse loan. Please try again.</string>
<string name="feature_loan_payment_type">Payment Type</string>
<string name="feature_loan_disbursement_note">Disbursement Note</string>
<string name="feature_loan_disbursement_note">Note</string>
<string name="feature_loan_error_network_not_available">Network not available</string>
<string name="feature_loan_error_amount_can_not_be_empty">Amount can not be empty</string>
<string name="feature_loan_error_amount_can_not_be_empty">Amount cannot be empty</string>
<string name="feature_loan_available_disbursement_amount">Available Disbursement Amount (with Over Applied)</string>
<string name="feature_loan_show_payment_details">Show Payment Details</string>
<string name="feature_loan_show_account_number">Account Number</string>
<string name="feature_loan_cheque_number">Cheque Number</string>
<string name="feature_loan_routing_code">Routing Code</string>
<string name="feature_loan_receipt_number">Receipt Number</string>
<string name="feature_loan_bank_number">Bank Number</string>
<string name="feature_loan_invalid_amount_error">Please enter a valid amount</string>

<string name="feature_loan_loan_account_summary">Loan Account Summary</string>
<string name="feature_loan_transactions">Transactions</string>
Expand Down Expand Up @@ -166,11 +175,13 @@
<string name="feature_loan_profile_item_standing_instructions_subtitle">Automated payment settings</string>
<string name="feature_loan_profile_status_active">Active</string>
<string name="feature_loan_profile_status_pending">Pending</string>
<string name="feature_loan_profile_status_approved">Approved</string>
<string name="feature_loan_profile_status_overpaid">Overpaid</string>
<string name="feature_loan_profile_status_unknown">Unknown</string>
<string name="feature_loan_profile_action_approve">Approve Account</string>
<string name="feature_loan_profile_action_repayment">Make Repayment</string>
<string name="feature_loan_profile_action_transfer">Transfer Funds</string>
<string name="feature_loan_profile_action_disburse">Disburse Loan</string>
<string name="feature_loan_profile_action_view">View Action</string>
<string name="feature_loan_profile_section_actions_details">ACTIONS &amp; DETAILS</string>
<string name="feature_loan_profile_section_account_overview">ACCOUNT OVERVIEW</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fun NavGraphBuilder.loanProfileAccountDestination(
navigateToReschedules: (Int) -> Unit,
navigateToNotes: (Int) -> Unit,
approveLoan: (Int, LoanWithAssociationsEntity) -> Unit,
disburseLoan: (Int) -> Unit,
onRepaymentClick: (LoanWithAssociationsEntity) -> Unit,
navigateToTransferScreen: (loanId: Int, accountNumber: String, clientId: Int, currencyCode: String, officeId: Int) -> Unit,
) {
Expand All @@ -44,6 +45,7 @@ fun NavGraphBuilder.loanProfileAccountDestination(
navigateToReschedules = navigateToReschedules,
navigateToNotes = navigateToNotes,
approveLoan = approveLoan,
disburseLoan = disburseLoan,
onRepaymentClick = onRepaymentClick,
navigateToTransferScreen = navigateToTransferScreen,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import template.core.base.designsystem.theme.KptTheme
internal fun LoanAccountProfileScreen(
onNavigateBack: () -> Unit,
approveLoan: (Int, LoanWithAssociationsEntity) -> Unit,
disburseLoan: (Int) -> Unit,
onRepaymentClick: (LoanWithAssociationsEntity) -> Unit,
navigateToRepaymentSchedule: (Int) -> Unit,
navigateToTransactions: (Int) -> Unit,
Expand All @@ -100,6 +101,7 @@ internal fun LoanAccountProfileScreen(

when (event.action) {
LoanProfileAction.Approve -> approveLoan(account.id, account)
LoanProfileAction.Disburse -> disburseLoan(account.id)
LoanProfileAction.Repayment -> onRepaymentClick(account)
LoanProfileAction.Transfer -> {
val account = state.loanAccount ?: return@EventsEffect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ package com.mifos.feature.loan.loanAccountProfile

import androidclient.feature.loan.generated.resources.Res
import androidclient.feature.loan.generated.resources.feature_loan_profile_action_approve
import androidclient.feature.loan.generated.resources.feature_loan_profile_action_disburse
import androidclient.feature.loan.generated.resources.feature_loan_profile_action_repayment
import androidclient.feature.loan.generated.resources.feature_loan_profile_action_transfer
import androidclient.feature.loan.generated.resources.feature_loan_profile_action_view
import androidclient.feature.loan.generated.resources.feature_loan_profile_error_details_not_found
import androidclient.feature.loan.generated.resources.feature_loan_profile_error_network_not_available
import androidclient.feature.loan.generated.resources.feature_loan_profile_failed_to_load_loan
import androidclient.feature.loan.generated.resources.feature_loan_profile_status_active
import androidclient.feature.loan.generated.resources.feature_loan_profile_status_approved
import androidclient.feature.loan.generated.resources.feature_loan_profile_status_overpaid
import androidclient.feature.loan.generated.resources.feature_loan_profile_status_pending
import androidclient.feature.loan.generated.resources.feature_loan_profile_status_unknown
Expand Down Expand Up @@ -114,6 +116,7 @@ internal class LoanAccountProfileViewModel(
private fun calculateNextActionResource(status: LoanProfileStatus): StringResource {
return when (status) {
LoanProfileStatus.PENDING -> Res.string.feature_loan_profile_action_approve
LoanProfileStatus.APPROVED -> Res.string.feature_loan_profile_action_disburse
LoanProfileStatus.OVERPAID -> Res.string.feature_loan_profile_action_transfer
LoanProfileStatus.ACTIVE -> Res.string.feature_loan_profile_action_repayment
LoanProfileStatus.UNKNOWN -> Res.string.feature_loan_profile_action_view
Expand All @@ -123,6 +126,7 @@ internal class LoanAccountProfileViewModel(
private fun calculateStatusUi(status: LoanProfileStatus): LoanStatusUiModel {
return when (status) {
LoanProfileStatus.ACTIVE -> LoanStatusUiModel(Res.string.feature_loan_profile_status_active, AppColors.loanActiveStatus)
LoanProfileStatus.APPROVED -> LoanStatusUiModel(Res.string.feature_loan_profile_status_approved, AppColors.loanApprovedStatus)
LoanProfileStatus.PENDING -> LoanStatusUiModel(Res.string.feature_loan_profile_status_pending, AppColors.loanPendingStatus)
LoanProfileStatus.OVERPAID -> LoanStatusUiModel(Res.string.feature_loan_profile_status_overpaid, AppColors.loanOverpaidStatus)
LoanProfileStatus.UNKNOWN -> LoanStatusUiModel(Res.string.feature_loan_profile_status_unknown, AppColors.loanUnknownStatus)
Expand Down Expand Up @@ -150,6 +154,7 @@ internal class LoanAccountProfileViewModel(

when (account.status.toProfileStatus()) {
LoanProfileStatus.PENDING -> sendEvent(LoanAccountEvent.NavigateToAction(LoanProfileAction.Approve))
LoanProfileStatus.APPROVED -> sendEvent(LoanAccountEvent.NavigateToAction(LoanProfileAction.Disburse))
LoanProfileStatus.OVERPAID -> sendEvent(LoanAccountEvent.NavigateToAction(LoanProfileAction.Transfer))
LoanProfileStatus.ACTIVE -> sendEvent(LoanAccountEvent.NavigateToAction(LoanProfileAction.Repayment))
LoanProfileStatus.UNKNOWN -> sendEvent(LoanAccountEvent.NavigateToAccountDetails)
Expand All @@ -160,6 +165,7 @@ internal class LoanAccountProfileViewModel(
if (this == null) return LoanProfileStatus.UNKNOWN
return when {
this.pendingApproval == true -> LoanProfileStatus.PENDING
this.waitingForDisbursal == true -> LoanProfileStatus.APPROVED
this.overpaid == true -> LoanProfileStatus.OVERPAID
this.active == true -> LoanProfileStatus.ACTIVE
else -> LoanProfileStatus.UNKNOWN
Expand All @@ -170,6 +176,7 @@ internal class LoanAccountProfileViewModel(
enum class LoanProfileStatus {
ACTIVE,
PENDING,
APPROVED,
OVERPAID,
UNKNOWN,
}
Expand All @@ -194,6 +201,7 @@ data class LoanStatusUiModel(

sealed interface LoanProfileAction {
data object Approve : LoanProfileAction
data object Disburse : LoanProfileAction
data object Repayment : LoanProfileAction
data object Transfer : LoanProfileAction
}
Expand Down
Loading
Loading