Skip to content

fix(feature:client): fix FOREIGN KEY constraint failure when saving ChargesEntity to DB #2439

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

Closed
Closed
Show file tree
Hide file tree
Changes from all 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 @@ -37,31 +37,31 @@ import kotlinx.serialization.Serializable
ForeignKey(
entity = ChargeTimeTypeEntity::class,
parentColumns = ["id"],
childColumns = ["chargeTimeType"],
childColumns = ["chargeTimeTypeId"],
onDelete = ForeignKeyAction.CASCADE,
onUpdate = ForeignKeyAction.NO_ACTION,
deferred = false,
),
ForeignKey(
entity = ClientDateEntity::class,
parentColumns = ["clientId"],
childColumns = ["chargeDueDate"],
childColumns = ["chargeDueDateClientId"],
onDelete = ForeignKeyAction.CASCADE,
onUpdate = ForeignKeyAction.NO_ACTION,
deferred = false,
),
ForeignKey(
entity = ChargeCalculationTypeEntity::class,
parentColumns = ["id"],
childColumns = ["id"],
childColumns = ["chargeCalculationTypeId"],
onDelete = ForeignKeyAction.CASCADE,
onUpdate = ForeignKeyAction.NO_ACTION,
deferred = false,
),
ForeignKey(
entity = ClientChargeCurrencyEntity::class,
parentColumns = ["id"],
childColumns = ["id"],
childColumns = ["currencyId"],
onDelete = ForeignKeyAction.CASCADE,
onUpdate = ForeignKeyAction.NO_ACTION,
deferred = false,
Expand All @@ -82,18 +82,18 @@ data class ChargesEntity(
val name: String? = null,

@ColumnInfo(index = true, name = INHERIT_FIELD_NAME, typeAffinity = UNDEFINED, collate = UNSPECIFIED, defaultValue = VALUE_UNSPECIFIED)
val chargeTimeType: ChargeTimeTypeEntity? = null,
val chargeTimeTypeId: Int? = null,

@ColumnInfo(index = true, name = INHERIT_FIELD_NAME, typeAffinity = UNDEFINED, collate = UNSPECIFIED, defaultValue = VALUE_UNSPECIFIED)
val chargeDueDate: ClientDateEntity? = null,
val chargeDueDateClientId: Int? = null,

val dueDate: List<Int>? = null,

@ColumnInfo(index = true, name = INHERIT_FIELD_NAME, typeAffinity = UNDEFINED, collate = UNSPECIFIED, defaultValue = VALUE_UNSPECIFIED)
val chargeCalculationType: ChargeCalculationTypeEntity? = null,
val chargeCalculationTypeId: Int? = null,

@ColumnInfo(index = true, name = INHERIT_FIELD_NAME, typeAffinity = UNDEFINED, collate = UNSPECIFIED, defaultValue = VALUE_UNSPECIFIED)
val currency: ClientChargeCurrencyEntity? = null,
val currencyId: Int? = null,

val amount: Double? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import com.mifos.room.entities.client.ChargesEntity
import com.mifos.room.helper.ChargeDaoHelper
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onEach

/**
* This DataManager is for Managing Charge API, In which Request is going to Server
Expand Down Expand Up @@ -50,19 +51,74 @@ class DataManagerCharge(
offset: Int,
limit: Int,
): Flow<Page<ChargesEntity>> {
println("[titan] getClientCharges called with clientId=$clientId, offset=$offset, limit=$limit")

return prefManager.userInfo.flatMapLatest { userData ->
when (userData.userStatus) {
false -> mBaseApiManager.chargeApi.getListOfCharges(clientId, offset, limit)
.onEach { chargeDatabase.saveClientCharges(it, clientId) }
println("[titan] userStatus: ${userData.userStatus}")

try {
when (userData.userStatus) {
false -> {
println("[titan] Fetching from API and saving to DB")
flow {
try {
val apiResponse = mBaseApiManager.chargeApi.getListOfCharges(clientId, offset, limit)
.first()
println("[titan] API response: $apiResponse")
try {
chargeDatabase.saveClientCharges(apiResponse, clientId)
println("[titan] Saved charges to DB")
} catch (e: Exception) {
println("[titan] Exception saving to DB: ${e.message}")
}
} catch (e: Exception) {
println("[titan] Exception fetching from API or saving to DB: ${e.message}")
}

true -> {
if (offset == 0) {
chargeDatabase.readClientCharges(clientId)
} else {
flowOf(Page())
// val page = chargeDatabase.readClientCharges(clientId).first()
// emit(page)
}
}
true -> {
if (offset == 0) {
println("[titan] Fetching from local DB")
try {
chargeDatabase.readClientCharges(clientId)
} catch (e: Exception) {
println("[titan] Exception fetching from DB: ${e.message}")
flowOf(Page())
}
} else {
println("[titan] Returning empty page from DB (offset != 0)")
flowOf(Page())
}
}
}
} catch (e: Exception) {
println("[titan] Exception in getClientCharges: ${e.message}")
flowOf(Page())
}
}
}
// original code
// @OptIn(ExperimentalCoroutinesApi::class)
//
// fun getClientCharges(
// clientId: Int,
// offset: Int,
// limit: Int,
// ): Flow<Page<ChargesEntity>> {
// return prefManager.userInfo.flatMapLatest { userData ->
// when (userData.userStatus) {
// false -> mBaseApiManager.chargeApi.getListOfCharges(clientId, offset, limit)
// .onEach { chargeDatabase.saveClientCharges(it, clientId) }
// true -> {
// if (offset == 0) {
// chargeDatabase.readClientCharges(clientId)
// } else {
// flowOf(Page())
// }
// }
// }
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2025 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/android-client/blob/master/LICENSE.md
*/
package com.mifos.core.network.mappers.clients

import com.mifos.core.network.model.ChargesResponse
import com.mifos.room.entities.client.ChargesEntity

class ChargesMapper {
fun ChargesResponse.toEntity(): ChargesEntity {
return ChargesEntity(
id = 0,
clientId = this.clientId,
loanId = this.loanId,
chargeId = this.chargeId,
name = this.name,
chargeTimeTypeId = this.chargeTimeType?.id,
chargeDueDateClientId = this.chargeDueDate?.clientId,
chargeCalculationTypeId = this.chargeCalculationType?.id,
currencyId = this.currency?.id,
dueDate = this.dueDate,
amount = this.amount,
amountPaid = this.amountPaid,
amountWaived = this.amountWaived,
amountWrittenOff = this.amountWrittenOff,
amountOutstanding = this.amountOutstanding,
penalty = this.penalty,
active = this.active,
paid = this.paid,
waived = this.waived,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2025 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/android-client/blob/master/LICENSE.md
*/
package com.mifos.core.network.model

import com.mifos.core.model.utils.Parcelable
import com.mifos.core.model.utils.Parcelize
import com.mifos.room.entities.client.ChargeCalculationTypeEntity
import com.mifos.room.entities.client.ChargeTimeTypeEntity
import com.mifos.room.entities.client.ClientChargeCurrencyEntity
import com.mifos.room.entities.client.ClientDateEntity
import kotlinx.serialization.Serializable

@Serializable
@Parcelize
data class ChargesResponse(
val id: Int = 0,

val clientId: Int? = null,

val loanId: Int? = null,

val chargeId: Int? = null,

val name: String? = null,

val chargeTimeType: ChargeTimeTypeEntity? = null,

val chargeDueDate: ClientDateEntity? = null,

val dueDate: List<Int>? = null,

val chargeCalculationType: ChargeCalculationTypeEntity? = null,

val currency: ClientChargeCurrencyEntity? = null,

val amount: Double? = null,

val amountPaid: Double? = null,

val amountWaived: Double? = null,

val amountWrittenOff: Double? = null,

val amountOutstanding: Double? = null,

val penalty: Boolean? = null,

val active: Boolean? = null,

val paid: Boolean? = null,

val waived: Boolean? = null,
) : Parcelable {

val formattedDueDate: String
get() = if (dueDate?.size == 3) {
"${dueDate[0]}-${dueDate[1]}-${dueDate[2]}"
} else {
"No Due Date"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import com.mifos.core.model.objects.clients.ChargeCreationResponse
import com.mifos.core.model.objects.clients.Page
import com.mifos.core.model.objects.payloads.ChargesPayload
import com.mifos.core.model.objects.template.client.ChargeTemplate
import com.mifos.core.network.model.ChargesResponse
import com.mifos.room.basemodel.APIEndPoint
import com.mifos.room.entities.client.ChargesEntity
import de.jensklingenberg.ktorfit.http.Body
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.POST
Expand All @@ -41,7 +41,7 @@ interface ChargeService {
@Path("clientId") clientId: Int,
@Query("offset") offset: Int,
@Query("limit") limit: Int,
): Flow<Page<ChargesEntity>>
): Flow<Page<ChargesResponse>>

@POST(APIEndPoint.CLIENTS + "/{clientId}/charges")
suspend fun createCharges(
Expand All @@ -50,7 +50,7 @@ interface ChargeService {
): ChargeCreationResponse

@GET(APIEndPoint.LOANS + "/{loanId}/charges")
fun getListOfLoanCharges(@Path("loanId") loanId: Int): Flow<Page<ChargesEntity>>
fun getListOfLoanCharges(@Path("loanId") loanId: Int): Flow<Page<ChargesResponse>>

@POST(APIEndPoint.LOANS + "/{loanId}/charges")
suspend fun createLoanCharges(
Expand Down
Loading