Skip to content

Commit

Permalink
fix: loan charge display (openMF#2787)
Browse files Browse the repository at this point in the history
Co-authored-by: Rajan Maurya <[email protected]>
  • Loading branch information
Nagarjuna0033 and therajanmaurya committed Mar 3, 2025
1 parent 64ad103 commit 8a10193
Show file tree
Hide file tree
Showing 21 changed files with 182 additions and 244 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ fun handleHomeNavigation(
}

HomeDestinations.RECENT_TRANSACTIONS -> navController.navigateToRecentTransaction()
HomeDestinations.CHARGES -> navController.navigateToClientChargeScreen(ChargeType.CLIENT)
HomeDestinations.CHARGES -> navController.navigateToClientChargeScreen(ChargeType.CLIENT, -1L)
HomeDestinations.THIRD_PARTY_TRANSFER -> navController.navigateToThirdPartyTransfer()
HomeDestinations.SETTINGS -> navController.navigateToSettings()
HomeDestinations.ABOUT_US -> navController.navigateToAboutUsScreen()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ object Constants {
const val PERMISSIONS_READ_PHONE_STATE_STATUS = "read_phone_status"
const val INTIAL_LOGIN = "initial_login"
const val CHARGE_TYPE = "charge_type"
const val CHARGE_TYPE_ID = "charge_type_id"
const val QR_DATA = "qrcode_data"
const val TEMPLATE = "template"
const val RECENT_TRANSACTIONS = "recent_transactions"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,9 @@ package org.mifos.mobile.core.data.repository

import kotlinx.coroutines.flow.Flow
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
import org.mifos.mobile.core.model.enums.ChargeType

interface ClientChargeRepository {
fun getClientCharges(clientId: Long): Flow<Page<Charge>>

fun getLoanCharges(loanId: Long): Flow<List<Charge>>

fun getSavingsCharges(savingsId: Long): Flow<List<Charge>>

fun clientLocalCharges(): Flow<Page<Charge>>

suspend fun syncCharges(charges: Page<Charge>?): Page<Charge>?
fun getCharges(chargeType: ChargeType, chargeTypeId: Long): Flow<List<Charge>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,23 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.mifos.mobile.core.common.network.Dispatcher
import org.mifos.mobile.core.common.network.MifosDispatchers
import org.mifos.mobile.core.data.model.toCharge
import org.mifos.mobile.core.data.model.toChargeEntity
import org.mifos.mobile.core.data.repository.ClientChargeRepository
import org.mifos.mobile.core.database.dao.ChargeDao
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
import org.mifos.mobile.core.model.enums.ChargeType
import org.mifos.mobile.core.network.DataManager
import javax.inject.Inject

class ClientChargeRepositoryImp @Inject constructor(
private val dataManager: DataManager,
private val chargeDao: ChargeDao,
@Dispatcher(MifosDispatchers.IO)
private val ioDispatcher: CoroutineDispatcher,
) : ClientChargeRepository {

override fun getClientCharges(clientId: Long): Flow<Page<Charge>> {
override fun getCharges(chargeType: ChargeType, chargeTypeId: Long): Flow<List<Charge>> {
return flow {
emit(dataManager.getClientCharges(clientId))
}
}

override fun getLoanCharges(loanId: Long): Flow<List<Charge>> {
return flow {
emit(dataManager.getLoanCharges(loanId))
}
}

override fun getSavingsCharges(savingsId: Long): Flow<List<Charge>> {
return flow {
emit(dataManager.getSavingsCharges(savingsId))
}
}

override fun clientLocalCharges(): Flow<Page<Charge>> {
return chargeDao.getAllLocalCharges().map { chargeList ->
Page(chargeList.size, chargeList.map { it.toCharge() })
emit(dataManager.getCharges(chargeType, chargeTypeId))
}.flowOn(ioDispatcher)
}

override suspend fun syncCharges(charges: Page<Charge>?): Page<Charge>? {
return withContext(ioDispatcher) {
charges?.pageItems?.let {
chargeDao.syncCharges(it.map { it.toChargeEntity() })
}

charges?.copy(pageItems = charges.pageItems)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,17 @@ package org.mifos.mobile.core.data.repositories
import app.cash.turbine.test
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mifos.mobile.core.data.model.toCharge
import org.mifos.mobile.core.data.repositoryImpl.ClientChargeRepositoryImp
import org.mifos.mobile.core.database.dao.ChargeDao
import org.mifos.mobile.core.database.entity.ChargeEntity
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
import org.mifos.mobile.core.model.enums.ChargeType
import org.mifos.mobile.core.network.DataManager
import org.mifos.mobile.core.testing.util.MainDispatcherRule
import org.mockito.Mock
Expand Down Expand Up @@ -65,7 +63,7 @@ class ClientChargeRepositoryImpTest {
val success = Page<Charge>(5, chargeList)
`when`(dataManager.getClientCharges(123L))
.thenReturn(success)
val resultFlow = clientChargeRepositoryImp.getClientCharges(123L)
val resultFlow = clientChargeRepositoryImp.getCharges(ChargeType.CLIENT, 123L)
resultFlow.test {
assertEquals(success, awaitItem())
cancelAndIgnoreRemainingEvents()
Expand All @@ -76,7 +74,7 @@ class ClientChargeRepositoryImpTest {
fun testGetClientCharges_Unsuccessful() = runTest {
`when`(dataManager.getClientCharges(123L))
.thenThrow(Exception("Error occurred"))
val result = clientChargeRepositoryImp.getClientCharges(123L)
val result = clientChargeRepositoryImp.getCharges(ChargeType.CLIENT, 123L)
result.test {
assert(Throwable("Error occurred") == awaitError())
}
Expand All @@ -87,7 +85,7 @@ class ClientChargeRepositoryImpTest {
val loanChargeMock = mock(Charge::class.java)
val success = List(5) { loanChargeMock }.toList()
`when`(dataManager.getLoanCharges(123L)).thenReturn(success)
val resultFlow = clientChargeRepositoryImp.getLoanCharges(123L)
val resultFlow = clientChargeRepositoryImp.getCharges(ChargeType.LOAN, 123L)
resultFlow.test {
assertEquals(success, awaitItem())
cancelAndIgnoreRemainingEvents()
Expand All @@ -98,7 +96,7 @@ class ClientChargeRepositoryImpTest {
fun testGetLoanCharges_Unsuccessful() = runTest {
`when`(dataManager.getLoanCharges(123L))
.thenThrow(Exception("Error occurred"))
val result = clientChargeRepositoryImp.getLoanCharges(123L)
val result = clientChargeRepositoryImp.getCharges(ChargeType.LOAN, 123L)
result.test {
assert(Throwable("Error occurred") == awaitError())
}
Expand All @@ -109,7 +107,7 @@ class ClientChargeRepositoryImpTest {
val savingChargeMock = mock(Charge::class.java)
val success = List(5) { savingChargeMock }.toList()
`when`(dataManager.getSavingsCharges(123L)).thenReturn(success)
val resultFlow = clientChargeRepositoryImp.getSavingsCharges(123L)
val resultFlow = clientChargeRepositoryImp.getCharges(ChargeType.SAVINGS, 123L)
resultFlow.test {
assertEquals(success, awaitItem())
cancelAndIgnoreRemainingEvents()
Expand All @@ -120,32 +118,7 @@ class ClientChargeRepositoryImpTest {
fun testGetSavingsCharges_Unsuccessful() = runTest {
`when`(dataManager.getSavingsCharges(123L))
.thenThrow(Exception("Error occurred"))
val result = clientChargeRepositoryImp.getSavingsCharges(123L)
result.test {
assert(Throwable("Error occurred") == awaitError())
}
}

@Test
fun testClientLocalCharges_Successful() = runTest {
val clientLocalChargeMock = List(5) { mock(ChargeEntity::class.java) }
val success = Page<Charge?>(
clientLocalChargeMock.size,
clientLocalChargeMock.map { it.toCharge() },
)
`when`(chargeDao.getAllLocalCharges()).thenReturn(flowOf(clientLocalChargeMock))
val resultFlow = clientChargeRepositoryImp.clientLocalCharges()
resultFlow.test {
assertEquals(success, awaitItem())
cancelAndIgnoreRemainingEvents()
}
}

@Test(expected = Exception::class)
fun testClientLocalCharges_Unsuccessful() = runTest {
`when`(clientChargeRepositoryImp.clientLocalCharges())
.thenThrow(Exception("Error occurred"))
val result = clientChargeRepositoryImp.clientLocalCharges()
val result = clientChargeRepositoryImp.getCharges(ChargeType.SAVINGS, 123L)
result.test {
assert(Throwable("Error occurred") == awaitError())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,8 @@
*/
package org.mifos.mobile.core.model.enums

/**
* Created by dilpreet on 19/7/17.
*/

enum class ChargeType {

CLIENT,

SAVINGS,

LOAN,
enum class ChargeType(val type: String) {
CLIENT("clients"),
SAVINGS("savingsaccounts"),
LOAN("loans"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import org.mifos.mobile.core.model.entity.templates.account.AccountOptionsTempla
import org.mifos.mobile.core.model.entity.templates.beneficiary.BeneficiaryTemplate
import org.mifos.mobile.core.model.entity.templates.loans.LoanTemplate
import org.mifos.mobile.core.model.entity.templates.savings.SavingsAccountTemplate
import org.mifos.mobile.core.model.enums.ChargeType
import javax.inject.Inject
import javax.inject.Singleton

Expand Down Expand Up @@ -84,16 +85,19 @@ class DataManager @Inject constructor(
)
}

suspend fun getClientCharges(clientId: Long): Page<Charge> {
return baseApiManager.clientChargeApi.getClientChargeList(clientId)
}

suspend fun getLoanCharges(loanId: Long): List<Charge> {
return baseApiManager.clientChargeApi.getLoanAccountChargeList(loanId)
}
suspend fun getCharges(
chargeType: ChargeType,
chargeTypeId: Long,
): List<Charge> {
return when (chargeType) {
ChargeType.CLIENT -> {
baseApiManager.clientChargeApi.getClientChargeList(chargeTypeId).pageItems
}

suspend fun getSavingsCharges(savingsId: Long): List<Charge> {
return baseApiManager.clientChargeApi.getSavingsAccountChargeList(savingsId)
ChargeType.LOAN, ChargeType.SAVINGS -> {
baseApiManager.clientChargeApi.getChargeList(chargeType.type, chargeTypeId)
}
}
}

suspend fun getSavingsWithAssociations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,22 @@ import retrofit2.http.Path

interface ClientChargeService {

/**
* This service allows to fetch loans and savings charges
* 2. Saving Charges // https://DomainName/api/v1/self/savingsaccounts/{savingsId}/charges
* 2. Loan Charges // https://DomainName/api/v1/self/loans/{loanId}/charges
* @param chargeType is savingsaccounts, loans
* @param chargeTypeId is savingsId, loanId
*/
@GET("/{chargeType}/{chargeTypeId}/charges")
suspend fun getChargeList(
@Path("chargeType") chargeType: String,
@Path("chargeTypeId") chargeTypeId: Long?,
): List<Charge>

/**
* This service allows to fetch client charges
*/
@GET(ApiEndPoints.CLIENTS + "/{clientId}/charges")
suspend fun getClientChargeList(@Path("clientId") clientId: Long?): Page<Charge>

@GET(ApiEndPoints.LOANS + "/{loanId}/charges")
suspend fun getLoanAccountChargeList(@Path("loanId") loanId: Long?): List<Charge>

@GET(ApiEndPoints.SAVINGS_ACCOUNTS + "/{savingsId}/charges")
suspend fun getSavingsAccountChargeList(@Path("savingsId") savingsId: Long?): List<Charge>
}
24 changes: 13 additions & 11 deletions core/ui/src/main/java/org/mifos/mobile/core/ui/utils/ImageUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object ImageUtil {
decodedBytes: ByteArray,
maxWidth: Float = DEFAULT_MAX_WIDTH,
maxHeight: Float = DEFAULT_MAX_HEIGHT,
): Bitmap {
): Bitmap? {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
Expand All @@ -43,18 +43,20 @@ object ImageUtil {
BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size, options)
} catch (e: OutOfMemoryError) {
Log.e(this::class.java.simpleName, "OutOfMemoryError while decoding bitmap", e)
return Bitmap.createBitmap(
1,
1,
Bitmap.Config.ARGB_8888,
) // Return a 1x1 bitmap as fallback
null
}

return try {
createScaledBitmap(bmp, actualWidth, actualHeight, options)
} catch (e: OutOfMemoryError) {
Log.e(this::class.java.simpleName, "OutOfMemoryError while scaling bitmap", e)
bmp // Return the original bitmap if scaling fails
if (bmp == null) {
Log.e(this::class.java.simpleName, "Bitmap decoding failed")
}

return bmp?.let {
try {
createScaledBitmap(it, actualWidth, actualHeight, options)
} catch (e: OutOfMemoryError) {
Log.e(this::class.java.simpleName, "OutOfMemoryError while scaling bitmap", e)
it
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.charge.navigation

import org.mifos.mobile.core.common.Constants.CHARGE_TYPE
import org.mifos.mobile.core.common.Constants.CHARGE_TYPE_ID
import org.mifos.mobile.core.model.enums.ChargeType

// Constants for Routes
const val CHARGE_ROUTE_BASE = "charge_route_base"
const val CHARGE_ROUTE_SCREEN = "charge_route_screen"

// Sealed class for Navigation Routes
sealed class ChargeNavigation(var route: String) {

data object ChargeRouteBase : ChargeNavigation(
route = "$CHARGE_ROUTE_BASE/{$CHARGE_TYPE}/${CHARGE_TYPE_ID}",
)

data object ChargeRouteScreen : ChargeNavigation(
route = "$CHARGE_ROUTE_SCREEN/{$CHARGE_TYPE}/{$CHARGE_TYPE_ID}",
) {
fun passArguments(chargeType: ChargeType, chargeTypeId: Long?): String {
return "$CHARGE_ROUTE_SCREEN/${chargeType.name}/$chargeTypeId"
}
}
}
Loading

0 comments on commit 8a10193

Please sign in to comment.