diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/di/RepositoryModule.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/di/RepositoryModule.kt index 50ea6ef3ebf..10df702e14b 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/di/RepositoryModule.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/di/RepositoryModule.kt @@ -42,6 +42,7 @@ import com.mifos.core.data.repository.GroupsListRepository import com.mifos.core.data.repository.IndividualCollectionSheetDetailsRepository import com.mifos.core.data.repository.LoanAccountApprovalRepository import com.mifos.core.data.repository.LoanAccountDisbursementRepository +import com.mifos.core.data.repository.LoanAccountGeneralRepository import com.mifos.core.data.repository.LoanAccountRepository import com.mifos.core.data.repository.LoanAccountSummaryRepository import com.mifos.core.data.repository.LoanChargeFormRepository @@ -111,6 +112,7 @@ import com.mifos.core.data.repositoryImp.GroupsListRepositoryImpl import com.mifos.core.data.repositoryImp.IndividualCollectionSheetDetailsRepositoryImp import com.mifos.core.data.repositoryImp.LoanAccountApprovalRepositoryImp import com.mifos.core.data.repositoryImp.LoanAccountDisbursementRepositoryImp +import com.mifos.core.data.repositoryImp.LoanAccountGeneralRepositoryImp import com.mifos.core.data.repositoryImp.LoanAccountRepositoryImp import com.mifos.core.data.repositoryImp.LoanAccountSummaryRepositoryImp import com.mifos.core.data.repositoryImp.LoanChargeFormRepositoryImp @@ -190,6 +192,7 @@ val RepositoryModule = module { singleOf(::LoanAccountRepositoryImp) bind LoanAccountRepository::class singleOf(::LoanAccountApprovalRepositoryImp) bind LoanAccountApprovalRepository::class singleOf(::LoanAccountDisbursementRepositoryImp) bind LoanAccountDisbursementRepository::class + singleOf(::LoanAccountGeneralRepositoryImp) bind LoanAccountGeneralRepository::class singleOf(::LoanAccountSummaryRepositoryImp) bind LoanAccountSummaryRepository::class singleOf(::LoanChargeFormRepositoryImp) bind LoanChargeFormRepository::class singleOf(::LoanChargeRepositoryImp) bind LoanChargeRepository::class diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanAccountGeneralRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanAccountGeneralRepository.kt new file mode 100644 index 00000000000..3bcd7fb366c --- /dev/null +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanAccountGeneralRepository.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.data.repository + +import com.mifos.core.common.utils.DataState +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations +import kotlinx.coroutines.flow.Flow + +interface LoanAccountGeneralRepository { + + fun getLoanById(loanId: Int): Flow> +} diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanAccountSummaryRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanAccountSummaryRepository.kt index 7810c56a8a8..1d704aaf2fb 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanAccountSummaryRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanAccountSummaryRepository.kt @@ -10,7 +10,7 @@ package com.mifos.core.data.repository import com.mifos.core.common.utils.DataState -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import kotlinx.coroutines.flow.Flow /** @@ -18,5 +18,5 @@ import kotlinx.coroutines.flow.Flow */ interface LoanAccountSummaryRepository { - fun getLoanById(loanId: Int): Flow> + fun getLoanById(loanId: Int): Flow> } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanRepaymentScheduleRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanRepaymentScheduleRepository.kt index ca417ab9be7..140a2568cf6 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanRepaymentScheduleRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanRepaymentScheduleRepository.kt @@ -10,7 +10,7 @@ package com.mifos.core.data.repository import com.mifos.core.common.utils.DataState -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import kotlinx.coroutines.flow.Flow /** @@ -18,5 +18,5 @@ import kotlinx.coroutines.flow.Flow */ interface LoanRepaymentScheduleRepository { - fun getLoanRepaySchedule(loanId: Int): Flow> + fun getLoanRepaySchedule(loanId: Int): Flow> } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanTransactionsRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanTransactionsRepository.kt index 7a5e967b8f3..47c8e5cab3a 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanTransactionsRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/LoanTransactionsRepository.kt @@ -10,7 +10,7 @@ package com.mifos.core.data.repository import com.mifos.core.common.utils.DataState -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import kotlinx.coroutines.flow.Flow /** @@ -18,5 +18,5 @@ import kotlinx.coroutines.flow.Flow */ interface LoanTransactionsRepository { - fun getLoanTransactions(loan: Int): Flow> + fun getLoanTransactions(loan: Int): Flow> } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncCentersDialogRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncCentersDialogRepository.kt index 44f4d7b0073..535f77e9b48 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncCentersDialogRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncCentersDialogRepository.kt @@ -10,10 +10,10 @@ package com.mifos.core.data.repository import com.mifos.core.common.utils.DataState +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.room.entities.accounts.CenterAccounts import com.mifos.room.entities.accounts.ClientAccounts import com.mifos.room.entities.accounts.GroupAccounts -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.accounts.savings.SavingsAccountWithAssociationsEntity import com.mifos.room.entities.client.ClientEntity import com.mifos.room.entities.group.CenterEntity @@ -31,7 +31,7 @@ interface SyncCentersDialogRepository { fun syncCenterAccounts(centerId: Int): Flow> - fun syncLoanById(loanId: Int): Flow> + fun syncLoanById(loanId: Int): Flow> fun syncLoanRepaymentTemplate(loanId: Int): Flow> diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncClientsDialogRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncClientsDialogRepository.kt index b8fec20e7cd..8c1dd10b291 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncClientsDialogRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncClientsDialogRepository.kt @@ -10,8 +10,8 @@ package com.mifos.core.data.repository import com.mifos.core.common.utils.DataState +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.room.entities.accounts.ClientAccounts -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.accounts.savings.SavingsAccountWithAssociationsEntity import com.mifos.room.entities.client.ClientEntity import com.mifos.room.entities.templates.loans.LoanRepaymentTemplateEntity @@ -25,7 +25,7 @@ interface SyncClientsDialogRepository { suspend fun syncClientAccounts(clientId: Int): ClientAccounts - fun syncLoanById(loanId: Int): Flow> + fun syncLoanById(loanId: Int): Flow> fun syncLoanRepaymentTemplate(loanId: Int): Flow> diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncGroupsDialogRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncGroupsDialogRepository.kt index e60b048bb7d..6ae3ad3c44d 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncGroupsDialogRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/SyncGroupsDialogRepository.kt @@ -10,9 +10,9 @@ package com.mifos.core.data.repository import com.mifos.core.common.utils.DataState +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.room.entities.accounts.ClientAccounts import com.mifos.room.entities.accounts.GroupAccounts -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.accounts.savings.SavingsAccountWithAssociationsEntity import com.mifos.room.entities.client.ClientEntity import com.mifos.room.entities.group.GroupEntity @@ -28,7 +28,7 @@ interface SyncGroupsDialogRepository { fun syncGroupAccounts(groupId: Int): Flow> - fun syncLoanById(loanId: Int): Flow> + fun syncLoanById(loanId: Int): Flow> fun syncLoanRepaymentTemplate(loanId: Int): Flow> diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanAccountGeneralRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanAccountGeneralRepositoryImp.kt new file mode 100644 index 00000000000..ff6eac899a4 --- /dev/null +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanAccountGeneralRepositoryImp.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +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.LoanAccountGeneralRepository +import com.mifos.core.data.util.NetworkMonitor +import com.mifos.core.data.util.withNetworkCheck +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations +import com.mifos.core.network.datamanager.DataManagerLoan +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +class LoanAccountGeneralRepositoryImp( + private val dataManagerLoan: DataManagerLoan, + private val networkMonitor: NetworkMonitor, + private val ioDispatcher: CoroutineDispatcher, +) : LoanAccountGeneralRepository { + + override fun getLoanById(loanId: Int): Flow> { + return networkMonitor.withNetworkCheck( + dataManagerLoan.getLoanById(loanId) + .asDataStateFlow(), + ).flowOn(ioDispatcher) + } +} diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanAccountSummaryRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanAccountSummaryRepositoryImp.kt index e0be8e01032..be2415003d9 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanAccountSummaryRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanAccountSummaryRepositoryImp.kt @@ -12,8 +12,8 @@ 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.LoanAccountSummaryRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.network.datamanager.DataManagerLoan -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import kotlinx.coroutines.flow.Flow /** @@ -23,7 +23,7 @@ class LoanAccountSummaryRepositoryImp( private val dataManagerLoan: DataManagerLoan, ) : LoanAccountSummaryRepository { - override fun getLoanById(loanId: Int): Flow> { + override fun getLoanById(loanId: Int): Flow> { return dataManagerLoan.getLoanById(loanId) .asDataStateFlow() } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanRepaymentScheduleRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanRepaymentScheduleRepositoryImp.kt index 951f20ea02a..dffbc65d705 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanRepaymentScheduleRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanRepaymentScheduleRepositoryImp.kt @@ -12,9 +12,11 @@ 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.LoanRepaymentScheduleRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.network.DataManager -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.network.mappers.loan.toDomain import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map /** * Created by Aditya Gupta on 12/08/23. @@ -23,7 +25,8 @@ class LoanRepaymentScheduleRepositoryImp( private val dataManager: DataManager, ) : LoanRepaymentScheduleRepository { - override fun getLoanRepaySchedule(loanId: Int): Flow> { - return dataManager.getLoanRepaySchedule(loanId).asDataStateFlow() + override fun getLoanRepaySchedule(loanId: Int): Flow> { + return dataManager.getLoanRepaySchedule(loanId).map { it.toDomain() } + .asDataStateFlow() } } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanTransactionsRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanTransactionsRepositoryImp.kt index 2124e67bf65..635265a71f3 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanTransactionsRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/LoanTransactionsRepositoryImp.kt @@ -12,9 +12,11 @@ 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.LoanTransactionsRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.network.DataManager -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.network.mappers.loan.toDomain import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map /** * Created by Aditya Gupta on 12/08/23. @@ -23,7 +25,8 @@ class LoanTransactionsRepositoryImp( private val dataManager: DataManager, ) : LoanTransactionsRepository { - override fun getLoanTransactions(loan: Int): Flow> { - return dataManager.getLoanTransactions(loan).asDataStateFlow() + override fun getLoanTransactions(loan: Int): Flow> { + return dataManager.getLoanTransactions(loan).map { it.toDomain() } + .asDataStateFlow() } } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncCentersDialogRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncCentersDialogRepositoryImp.kt index bde97d15af1..b7dd70011d2 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncCentersDialogRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncCentersDialogRepositoryImp.kt @@ -12,6 +12,7 @@ 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.SyncCentersDialogRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.network.datamanager.DataManagerCenter import com.mifos.core.network.datamanager.DataManagerClient import com.mifos.core.network.datamanager.DataManagerGroups @@ -20,7 +21,6 @@ import com.mifos.core.network.datamanager.DataManagerSavings import com.mifos.room.entities.accounts.CenterAccounts import com.mifos.room.entities.accounts.ClientAccounts import com.mifos.room.entities.accounts.GroupAccounts -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.accounts.savings.SavingsAccountWithAssociationsEntity import com.mifos.room.entities.client.ClientEntity import com.mifos.room.entities.group.CenterEntity @@ -47,7 +47,7 @@ class SyncCentersDialogRepositoryImp( .asDataStateFlow() } - override fun syncLoanById(loanId: Int): Flow> { + override fun syncLoanById(loanId: Int): Flow> { return dataManagerLoan.syncLoanById(loanId).asDataStateFlow() } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncClientsDialogRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncClientsDialogRepositoryImp.kt index a868920901f..d421e828c6a 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncClientsDialogRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncClientsDialogRepositoryImp.kt @@ -12,11 +12,11 @@ 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.SyncClientsDialogRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.network.datamanager.DataManagerClient import com.mifos.core.network.datamanager.DataManagerLoan import com.mifos.core.network.datamanager.DataManagerSavings import com.mifos.room.entities.accounts.ClientAccounts -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.accounts.savings.SavingsAccountWithAssociationsEntity import com.mifos.room.entities.client.ClientEntity import com.mifos.room.entities.templates.loans.LoanRepaymentTemplateEntity @@ -36,7 +36,7 @@ class SyncClientsDialogRepositoryImp( return dataManagerClient.getClientAccounts(clientId) } - override fun syncLoanById(loanId: Int): Flow> { + override fun syncLoanById(loanId: Int): Flow> { return dataManagerLoan.syncLoanById(loanId).asDataStateFlow() } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncGroupsDialogRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncGroupsDialogRepositoryImp.kt index 67d96fa8577..54ff0d703f0 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncGroupsDialogRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/SyncGroupsDialogRepositoryImp.kt @@ -12,13 +12,13 @@ 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.SyncGroupsDialogRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.network.datamanager.DataManagerClient import com.mifos.core.network.datamanager.DataManagerGroups import com.mifos.core.network.datamanager.DataManagerLoan import com.mifos.core.network.datamanager.DataManagerSavings import com.mifos.room.entities.accounts.ClientAccounts import com.mifos.room.entities.accounts.GroupAccounts -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.accounts.savings.SavingsAccountWithAssociationsEntity import com.mifos.room.entities.client.ClientEntity import com.mifos.room.entities.group.GroupEntity @@ -42,7 +42,7 @@ class SyncGroupsDialogRepositoryImp( .asDataStateFlow() } - override fun syncLoanById(loanId: Int): Flow> { + override fun syncLoanById(loanId: Int): Flow> { return dataManagerLoan.syncLoanById(loanId) .asDataStateFlow() } diff --git a/core/database/src/androidMain/kotlin/com/mifos/room/MifosDatabase.kt b/core/database/src/androidMain/kotlin/com/mifos/room/MifosDatabase.kt index 7ce7b9cb729..176aa98e185 100644 --- a/core/database/src/androidMain/kotlin/com/mifos/room/MifosDatabase.kt +++ b/core/database/src/androidMain/kotlin/com/mifos/room/MifosDatabase.kt @@ -25,13 +25,13 @@ import com.mifos.room.dao.SurveyDao import com.mifos.room.entities.PaymentTypeOptionEntity import com.mifos.room.entities.accounts.loans.ActualDisbursementDateEntity import com.mifos.room.entities.accounts.loans.LoanAccountEntity +import com.mifos.room.entities.accounts.loans.LoanAccountSummaryEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentRequestEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentResponseEntity import com.mifos.room.entities.accounts.loans.LoanStatusEntity import com.mifos.room.entities.accounts.loans.LoanTimelineEntity import com.mifos.room.entities.accounts.loans.LoanTypeEntity import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity -import com.mifos.room.entities.accounts.loans.LoansAccountSummaryEntity import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity import com.mifos.room.entities.accounts.savings.SavingAccountDepositTypeEntity import com.mifos.room.entities.accounts.savings.SavingsAccountEntity @@ -90,7 +90,7 @@ import com.mifos.room.typeconverters.CustomTypeConverters LoanStatusEntity::class, LoanTypeEntity::class, LoanWithAssociationsEntity::class, - LoansAccountSummaryEntity::class, + LoanAccountSummaryEntity::class, LoanTimelineEntity::class, // savings package SavingAccountDepositTypeEntity::class, diff --git a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/Loan.kt b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/Loan.kt index 38e7e34e97a..b8bbff9b0d9 100644 --- a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/Loan.kt +++ b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/Loan.kt @@ -83,7 +83,7 @@ data class Loan( val timeline: LoanTimelineEntity? = null, - val summary: LoansAccountSummaryEntity? = null, + val summary: LoanAccountSummaryEntity? = null, val feeChargesAtDisbursementCharged: Double? = null, diff --git a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoansAccountSummaryEntity.kt b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanAccountSummaryEntity.kt similarity index 97% rename from core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoansAccountSummaryEntity.kt rename to core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanAccountSummaryEntity.kt index d9765ed5e43..786b3c104b3 100644 --- a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoansAccountSummaryEntity.kt +++ b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanAccountSummaryEntity.kt @@ -27,7 +27,7 @@ import template.core.base.database.PrimaryKey ) @Serializable @Parcelize -data class LoansAccountSummaryEntity( +data class LoanAccountSummaryEntity( @PrimaryKey(autoGenerate = true) val loanId: Int? = null, @@ -38,6 +38,8 @@ data class LoansAccountSummaryEntity( val principalPaid: Double? = null, + val principalWaived: Double? = null, + val principalWrittenOff: Double? = null, val principalOutstanding: Double? = null, diff --git a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanApprovalData.kt b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanApprovalData.kt index 612cab66af2..19efa40ea58 100644 --- a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanApprovalData.kt +++ b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanApprovalData.kt @@ -9,13 +9,9 @@ */ package com.mifos.room.entities.accounts.loans -import com.mifos.core.model.utils.Parcelable -import com.mifos.core.model.utils.Parcelize -import kotlinx.serialization.Serializable +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations -@Parcelize -@Serializable data class LoanApprovalData( val loanID: Int, - val loanWithAssociations: LoanWithAssociationsEntity, -) : Parcelable + val loanWithAssociations: LoanWithAssociations, +) diff --git a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanWithAssociationsEntity.kt b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanWithAssociationsEntity.kt index e9bb4b61172..6d34e07736e 100644 --- a/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanWithAssociationsEntity.kt +++ b/core/database/src/commonMain/kotlin/com/mifos/room/entities/accounts/loans/LoanWithAssociationsEntity.kt @@ -71,7 +71,7 @@ import template.core.base.database.PrimaryKey deferred = false, ), ForeignKey( - entity = LoansAccountSummaryEntity::class, + entity = LoanAccountSummaryEntity::class, parentColumns = ["loanId"], childColumns = ["summary"], onDelete = ForeignKeyAction.CASCADE, @@ -123,6 +123,8 @@ data class LoanWithAssociationsEntity( val approvedPrincipal: Double = 0.0, + val proposedPrincipal: Double = 0.0, + val termFrequency: Int = 0, @IgnoredOnParcel @@ -161,7 +163,7 @@ data class LoanWithAssociationsEntity( val timeline: LoanTimelineEntity = LoanTimelineEntity(), @ColumnInfo(index = true, name = INHERIT_FIELD_NAME, typeAffinity = UNDEFINED, collate = UNSPECIFIED, defaultValue = VALUE_UNSPECIFIED) - val summary: LoansAccountSummaryEntity = LoansAccountSummaryEntity(), + val summary: LoanAccountSummaryEntity = LoanAccountSummaryEntity(), @IgnoredOnParcel val repaymentSchedule: RepaymentSchedule = RepaymentSchedule(), diff --git a/core/database/src/commonMain/kotlin/com/mifos/room/entities/zipmodels/LoanAndLoanRepayment.kt b/core/database/src/commonMain/kotlin/com/mifos/room/entities/zipmodels/LoanAndLoanRepayment.kt index 9ab2813be30..1796d373983 100644 --- a/core/database/src/commonMain/kotlin/com/mifos/room/entities/zipmodels/LoanAndLoanRepayment.kt +++ b/core/database/src/commonMain/kotlin/com/mifos/room/entities/zipmodels/LoanAndLoanRepayment.kt @@ -9,7 +9,7 @@ */ package com.mifos.room.entities.zipmodels -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.room.entities.templates.loans.LoanRepaymentTemplateEntity /** @@ -17,12 +17,12 @@ import com.mifos.room.entities.templates.loans.LoanRepaymentTemplateEntity * Created by Rajan Maurya on 08/08/16. */ class LoanAndLoanRepayment { - var loanWithAssociations: LoanWithAssociationsEntity? = null + var loanWithAssociations: LoanWithAssociations? = null var loanRepaymentTemplate: LoanRepaymentTemplateEntity? = null constructor() constructor( - loanWithAssociations: LoanWithAssociationsEntity?, + loanWithAssociations: LoanWithAssociations?, loanRepaymentTemplate: LoanRepaymentTemplateEntity?, ) { this.loanWithAssociations = loanWithAssociations diff --git a/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/CustomTypeConverters.kt b/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/CustomTypeConverters.kt index 4190f881522..46315b47027 100644 --- a/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/CustomTypeConverters.kt +++ b/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/CustomTypeConverters.kt @@ -29,10 +29,10 @@ import com.mifos.core.model.objects.clients.Address import com.mifos.room.entities.PaymentTypeOptionEntity import com.mifos.room.entities.Timeline import com.mifos.room.entities.accounts.loans.ActualDisbursementDateEntity +import com.mifos.room.entities.accounts.loans.LoanAccountSummaryEntity import com.mifos.room.entities.accounts.loans.LoanStatusEntity import com.mifos.room.entities.accounts.loans.LoanTimelineEntity import com.mifos.room.entities.accounts.loans.LoanTypeEntity -import com.mifos.room.entities.accounts.loans.LoansAccountSummaryEntity import com.mifos.room.entities.accounts.savings.Charge import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity import com.mifos.room.entities.accounts.savings.SavingAccountDepositTypeEntity @@ -68,7 +68,6 @@ import com.mifos.room.entities.templates.clients.OptionsEntity import com.mifos.room.entities.templates.clients.SavingProductOptionsEntity import com.mifos.room.entities.templates.clients.StaffOptionsEntity import com.mifos.room.entities.templates.loans.LoanType -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import template.core.base.database.TypeConverter @@ -470,12 +469,12 @@ class CustomTypeConverters { } @TypeConverter - fun fromSummary(summary: LoansAccountSummaryEntity?): String? { + fun fromSummary(summary: LoanAccountSummaryEntity?): String? { return summary?.let { Json.encodeToString(it) } } @TypeConverter - fun toSummary(json: String?): LoansAccountSummaryEntity? { + fun toSummary(json: String?): LoanAccountSummaryEntity? { return json?.let { Json.decodeFromString(it) } } diff --git a/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/LoanTypeConverters.kt b/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/LoanTypeConverters.kt index 2e734323a31..07dc1ff34d5 100644 --- a/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/LoanTypeConverters.kt +++ b/core/database/src/commonMain/kotlin/com/mifos/room/typeconverters/LoanTypeConverters.kt @@ -22,12 +22,11 @@ import com.mifos.core.model.objects.account.loan.Transaction import com.mifos.core.model.objects.account.loan.Type import com.mifos.room.entities.PaymentTypeOptionEntity import com.mifos.room.entities.accounts.loans.ActualDisbursementDateEntity +import com.mifos.room.entities.accounts.loans.LoanAccountSummaryEntity import com.mifos.room.entities.accounts.loans.LoanStatusEntity import com.mifos.room.entities.accounts.loans.LoanTimelineEntity import com.mifos.room.entities.accounts.loans.LoanTypeEntity -import com.mifos.room.entities.accounts.loans.LoansAccountSummaryEntity import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import template.core.base.database.TypeConverter @@ -98,12 +97,12 @@ class LoanTypeConverters { } @TypeConverter - fun fromSummary(summary: LoansAccountSummaryEntity?): String? { + fun fromSummary(summary: LoanAccountSummaryEntity?): String? { return summary?.let { Json.encodeToString(it) } } @TypeConverter - fun toSummary(json: String?): LoansAccountSummaryEntity? { + fun toSummary(json: String?): LoanAccountSummaryEntity? { return json?.let { Json.decodeFromString(it) } } diff --git a/core/database/src/desktopMain/kotlin/com/mifos/room/MifosDatabase.kt b/core/database/src/desktopMain/kotlin/com/mifos/room/MifosDatabase.kt index 7ce7b9cb729..176aa98e185 100644 --- a/core/database/src/desktopMain/kotlin/com/mifos/room/MifosDatabase.kt +++ b/core/database/src/desktopMain/kotlin/com/mifos/room/MifosDatabase.kt @@ -25,13 +25,13 @@ import com.mifos.room.dao.SurveyDao import com.mifos.room.entities.PaymentTypeOptionEntity import com.mifos.room.entities.accounts.loans.ActualDisbursementDateEntity import com.mifos.room.entities.accounts.loans.LoanAccountEntity +import com.mifos.room.entities.accounts.loans.LoanAccountSummaryEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentRequestEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentResponseEntity import com.mifos.room.entities.accounts.loans.LoanStatusEntity import com.mifos.room.entities.accounts.loans.LoanTimelineEntity import com.mifos.room.entities.accounts.loans.LoanTypeEntity import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity -import com.mifos.room.entities.accounts.loans.LoansAccountSummaryEntity import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity import com.mifos.room.entities.accounts.savings.SavingAccountDepositTypeEntity import com.mifos.room.entities.accounts.savings.SavingsAccountEntity @@ -90,7 +90,7 @@ import com.mifos.room.typeconverters.CustomTypeConverters LoanStatusEntity::class, LoanTypeEntity::class, LoanWithAssociationsEntity::class, - LoansAccountSummaryEntity::class, + LoanAccountSummaryEntity::class, LoanTimelineEntity::class, // savings package SavingAccountDepositTypeEntity::class, diff --git a/core/database/src/nativeMain/kotlin/com/mifos/room/MifosDatabase.kt b/core/database/src/nativeMain/kotlin/com/mifos/room/MifosDatabase.kt index dbe98af9d82..610bae5b063 100644 --- a/core/database/src/nativeMain/kotlin/com/mifos/room/MifosDatabase.kt +++ b/core/database/src/nativeMain/kotlin/com/mifos/room/MifosDatabase.kt @@ -27,13 +27,13 @@ import com.mifos.room.dao.SurveyDao import com.mifos.room.entities.PaymentTypeOptionEntity import com.mifos.room.entities.accounts.loans.ActualDisbursementDateEntity import com.mifos.room.entities.accounts.loans.LoanAccountEntity +import com.mifos.room.entities.accounts.loans.LoanAccountSummaryEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentRequestEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentResponseEntity import com.mifos.room.entities.accounts.loans.LoanStatusEntity import com.mifos.room.entities.accounts.loans.LoanTimelineEntity import com.mifos.room.entities.accounts.loans.LoanTypeEntity import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity -import com.mifos.room.entities.accounts.loans.LoansAccountSummaryEntity import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity import com.mifos.room.entities.accounts.savings.SavingAccountDepositTypeEntity import com.mifos.room.entities.accounts.savings.SavingsAccountEntity @@ -92,7 +92,7 @@ import com.mifos.room.typeconverters.CustomTypeConverters LoanStatusEntity::class, LoanTypeEntity::class, LoanWithAssociationsEntity::class, - LoansAccountSummaryEntity::class, + LoanAccountSummaryEntity::class, LoanTimelineEntity::class, // savings package SavingAccountDepositTypeEntity::class, diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/ActualDisbursementDate.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/ActualDisbursementDate.kt new file mode 100644 index 00000000000..4f60a322b97 --- /dev/null +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/ActualDisbursementDate.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.model.objects.account.loan.loanWithAssociations + +data class ActualDisbursementDate( + val loanId: Int? = null, + val year: Int? = null, + val month: Int? = null, + val date: Int? = null, +) diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanAccountSummary.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanAccountSummary.kt new file mode 100644 index 00000000000..d309063c8ee --- /dev/null +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanAccountSummary.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.model.objects.account.loan.loanWithAssociations + +data class LoanAccountSummary( + val loanId: Int? = null, + val currency: SavingAccountCurrency? = null, + val principalDisbursed: Double? = null, + val principalPaid: Double? = null, + val principalWaived: Double? = null, + val principalWrittenOff: Double? = null, + val principalOutstanding: Double? = null, + val principalOverdue: Double? = null, + val interestCharged: Double? = null, + val interestPaid: Double? = null, + val interestWaived: Double? = null, + val interestWrittenOff: Double? = null, + val interestOutstanding: Double? = null, + val interestOverdue: Double? = null, + val feeChargesCharged: Double? = null, + val feeChargesDueAtDisbursementCharged: Double? = null, + val feeChargesPaid: Double? = null, + val feeChargesWaived: Double? = null, + val feeChargesWrittenOff: Double? = null, + val feeChargesOutstanding: Double? = null, + val feeChargesOverdue: Double? = null, + val penaltyChargesCharged: Double? = null, + val penaltyChargesPaid: Double? = null, + val penaltyChargesWaived: Double? = null, + val penaltyChargesWrittenOff: Double? = null, + val penaltyChargesOutstanding: Double? = null, + val penaltyChargesOverdue: Double? = null, + val totalExpectedRepayment: Double? = null, + val totalRepayment: Double? = null, + val totalExpectedCostOfLoan: Double? = null, + val totalCostOfLoan: Double? = null, + val totalWaived: Double? = null, + val totalWrittenOff: Double? = null, + val totalOutstanding: Double? = null, + val totalOverdue: Double? = null, + val overdueSinceDate: List? = null, +) diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanStatus.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanStatus.kt new file mode 100644 index 00000000000..8aa8d2b8e3c --- /dev/null +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanStatus.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.model.objects.account.loan.loanWithAssociations + +data class LoanStatus( + val id: Int? = null, + val code: String? = null, + val value: String? = null, + val pendingApproval: Boolean? = null, + val waitingForDisbursal: Boolean? = null, + val active: Boolean? = null, + val closedObligationsMet: Boolean? = null, + val closedWrittenOff: Boolean? = null, + val closedRescheduled: Boolean? = null, + val closed: Boolean? = null, + val overpaid: Boolean? = null, +) diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanTimeline.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanTimeline.kt new file mode 100644 index 00000000000..f9813e23e11 --- /dev/null +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanTimeline.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.model.objects.account.loan.loanWithAssociations + +data class LoanTimeline( + val loanId: Int? = null, + val submittedOnDate: List? = null, + val submittedByUsername: String? = null, + val submittedByFirstname: String? = null, + val submittedByLastname: String? = null, + val approvedOnDate: List? = null, + val approvedByUsername: String? = null, + val approvedByFirstname: String? = null, + val approvedByLastname: String? = null, + val expectedDisbursementDate: List? = null, + val actualDisburseDate: ActualDisbursementDate? = null, + val actualDisbursementDate: List? = null, + val disbursedByUsername: String? = null, + val disbursedByFirstname: String? = null, + val disbursedByLastname: String? = null, + val closedOnDate: List? = null, + val expectedMaturityDate: List? = null, +) diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanType.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanType.kt new file mode 100644 index 00000000000..eefe85755cc --- /dev/null +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanType.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.model.objects.account.loan.loanWithAssociations + +data class LoanType( + val id: Int? = null, + val code: String? = null, + val value: String? = null, +) diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanWithAssociations.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanWithAssociations.kt new file mode 100644 index 00000000000..8289cb05ebd --- /dev/null +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/LoanWithAssociations.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.model.objects.account.loan.loanWithAssociations + +import com.mifos.core.model.objects.account.loan.AmortizationType +import com.mifos.core.model.objects.account.loan.InterestCalculationPeriodType +import com.mifos.core.model.objects.account.loan.InterestRateFrequencyType +import com.mifos.core.model.objects.account.loan.InterestType +import com.mifos.core.model.objects.account.loan.RepaymentFrequencyType +import com.mifos.core.model.objects.account.loan.RepaymentSchedule +import com.mifos.core.model.objects.account.loan.TermPeriodFrequencyType +import com.mifos.core.model.objects.account.loan.Transaction + +data class LoanWithAssociations( + val id: Int? = null, + val accountNo: String? = null, + val status: LoanStatus? = null, + val clientId: Int? = null, + val clientName: String? = null, + val clientOfficeId: Int? = null, + val loanProductId: Int? = null, + val loanProductName: String? = null, + val loanProductDescription: String? = null, + val fundId: Int? = null, + val fundName: String? = null, + val loanPurposeId: Int? = null, + val loanPurposeName: String? = null, + val loanOfficerId: Int? = null, + val loanOfficerName: String? = null, + val loanType: LoanType? = null, + val currency: SavingAccountCurrency? = null, + val principal: Double? = null, + val approvedPrincipal: Double? = null, + val proposedPrincipal: Double? = null, + val termFrequency: Int? = null, + val termPeriodFrequencyType: TermPeriodFrequencyType? = null, + val numberOfRepayments: Int? = null, + val repaymentEvery: Int? = null, + val repaymentFrequencyType: RepaymentFrequencyType? = null, + val interestRatePerPeriod: Double? = null, + val interestRateFrequencyType: InterestRateFrequencyType? = null, + val annualInterestRate: Double? = null, + val amortizationType: AmortizationType? = null, + val interestType: InterestType? = null, + val interestCalculationPeriodType: InterestCalculationPeriodType? = null, + val transactionProcessingStrategyId: Int? = null, + val transactionProcessingStrategyName: String? = null, + val syncDisbursementWithMeeting: Boolean? = null, + val timeline: LoanTimeline? = null, + val summary: LoanAccountSummary? = null, + val repaymentSchedule: RepaymentSchedule? = null, + val transactions: List? = null, + val feeChargesAtDisbursementCharged: Double? = null, + val totalOverpaid: Double? = null, + val loanCounter: Int? = null, + val loanProductCounter: Int? = null, + val multiDisburseLoan: Boolean? = null, + val canDisburse: Boolean? = null, + val inArrears: Boolean? = null, + val isNPA: Boolean? = null, +) diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/SavingAccountCurrency.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/SavingAccountCurrency.kt new file mode 100644 index 00000000000..307b92e86b1 --- /dev/null +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/account/loan/loanWithAssociations/SavingAccountCurrency.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.model.objects.account.loan.loanWithAssociations + +data class SavingAccountCurrency( + val id: Int = 0, + val code: String? = null, + val name: String? = null, + val decimalPlaces: Int? = null, + val inMultiplesOf: Int? = null, + val displaySymbol: String? = null, + val nameCode: String? = null, + val displayLabel: String? = null, +) diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/DataManager.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/DataManager.kt index 434b046f075..0c7a97aab18 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/DataManager.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/DataManager.kt @@ -15,10 +15,10 @@ import com.mifos.core.model.objects.databaseobjects.OfflineCenter import com.mifos.core.model.objects.payloads.GroupLoanPayload import com.mifos.core.model.objects.responses.SaveResponse import com.mifos.core.model.objects.template.loan.GroupLoanTemplate +import com.mifos.core.network.dto.loan.LoanWithAssociationsDto import com.mifos.core.network.model.CollectionSheetPayload import com.mifos.core.network.model.Payload import com.mifos.room.entities.accounts.loans.Loan -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.client.ChargesEntity import com.mifos.room.entities.group.CenterEntity import com.mifos.room.entities.group.CenterWithAssociations @@ -119,7 +119,7 @@ class DataManager : KoinComponent { /** * Loans API */ - fun getLoanTransactions(loan: Int): Flow { + fun getLoanTransactions(loan: Int): Flow { return mBaseApiManager.loanService.getLoanWithTransactions(loan) } @@ -134,7 +134,7 @@ class DataManager : KoinComponent { return mBaseApiManager.loanService.createGroupLoansAccount(loansPayload) } - fun getLoanRepaySchedule(loanId: Int): Flow { + fun getLoanRepaySchedule(loanId: Int): Flow { return mBaseApiManager.loanService.getLoanRepaymentSchedule(loanId) } diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerLoan.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerLoan.kt index c75798c2adf..524dd6f3860 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerLoan.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerLoan.kt @@ -13,6 +13,7 @@ import com.mifos.core.common.utils.extractErrorMessage import com.mifos.core.datastore.UserPreferencesRepository import com.mifos.core.model.objects.account.loan.LoanDisbursement import com.mifos.core.model.objects.account.loan.RepaymentSchedule +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.model.objects.account.loan.reschedules.LoanRescheduleApprovalRequest import com.mifos.core.model.objects.account.loan.reschedules.LoanRescheduleRejectionRequest import com.mifos.core.model.objects.account.loan.reschedules.LoanRescheduleRequest @@ -23,11 +24,12 @@ import com.mifos.core.model.objects.account.loan.transfer.AccountTransferRespons import com.mifos.core.model.objects.account.loan.transfer.AccountTransferTemplate import com.mifos.core.network.BaseApiManager import com.mifos.core.network.GenericResponse +import com.mifos.core.network.mappers.loan.LoanAccountMapper +import com.mifos.core.network.mappers.loan.toDomain import com.mifos.core.network.model.LoansPayload import com.mifos.room.entities.PaymentTypeOptionEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentRequestEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentResponseEntity -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.templates.loans.LoanRepaymentTemplateEntity import com.mifos.room.entities.templates.loans.LoanTemplate import com.mifos.room.entities.templates.loans.LoanTransactionTemplate @@ -37,6 +39,7 @@ import io.ktor.client.statement.bodyAsText import io.ktor.http.isSuccess import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow @@ -67,14 +70,14 @@ class DataManagerLoan( * @return LoanWithAssociation */ @OptIn(ExperimentalCoroutinesApi::class) - fun getLoanById(loanId: Int): Flow { + fun getLoanById(loanId: Int): Flow { return prefManager.userInfo.flatMapLatest { userData -> when (userData.userStatus) { false -> flow { emit( mBaseApiManager.loanService.getLoanByIdWithAllAssociations( loanId, - ), + ).toDomain(), ) } @@ -82,7 +85,7 @@ class DataManagerLoan( /** * offline Mode, Return LoanWithAssociation from LoanDaoHelper. */ - loanDaoHelper.getLoanById(loanId) + loanDaoHelper.getLoanById(loanId).map { entity -> entity?.let(LoanAccountMapper::mapFromEntity) } } } } @@ -96,11 +99,15 @@ class DataManagerLoan( * @param loanId Loan Id * @return LoanWithAssociations */ - fun syncLoanById(loanId: Int): Flow { + fun syncLoanById(loanId: Int): Flow { return flow { val loanWithAssociations = - mBaseApiManager.loanService.getLoanByIdWithAllAssociations(loanId) - loanDaoHelper.saveLoanById(loanWithAssociations) + mBaseApiManager.loanService.getLoanByIdWithAllAssociations(loanId).toDomain() + + val loanWithAssociationsEntity = LoanAccountMapper.mapToEntity(loanWithAssociations) + + loanDaoHelper.saveLoanById(loanWithAssociationsEntity).collect() + emit(loanWithAssociations) } } diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/dto/loan/LoanWithAsoociationsDto.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/dto/loan/LoanWithAsoociationsDto.kt new file mode 100644 index 00000000000..4511ba2948b --- /dev/null +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/dto/loan/LoanWithAsoociationsDto.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.network.dto.loan + +import kotlinx.serialization.Serializable + +@Serializable +data class LoanWithAssociationsDto( + val id: Long, + val accountNo: String? = null, + val status: LoanStatusDto? = null, + val clientId: Long, + val clientAccountNo: String? = null, + val clientName: String? = null, + val loanProductId: Long, + val loanProductName: String? = null, + val loanProductDescription: String? = null, + val loanOfficerId: Long? = null, + val loanOfficerName: String? = null, + val loanType: LoanTypeDto? = null, + val currency: LoanCurrencyDto? = null, + val principal: Double, + val approvedPrincipal: Double, + val netDisbursalAmount: Double, + val termFrequency: Int, + val numberOfRepayments: Int, + val annualInterestRate: Double, + val timeline: LoanTimelineDto? = null, + val summary: LoanSummaryDto? = null, + val repaymentSchedule: LoanRepaymentScheduleDto? = null, + val transactions: List? = emptyList(), + val charges: List? = emptyList(), + val notes: List? = emptyList(), + val delinquent: LoanDelinquentDto? = null, +) + +@Serializable +data class LoanStatusDto(val id: Int, val code: String? = null, val value: String? = null, val active: Boolean = false, val closed: Boolean = false, val overpaid: Boolean = false) + +@Serializable +data class LoanTypeDto(val id: Int, val code: String? = null, val value: String? = null) + +@Serializable +data class LoanCurrencyDto(val code: String? = null, val name: String? = null, val decimalPlaces: Int, val displaySymbol: String? = null, val displayLabel: String? = null) + +@Serializable +data class LoanTimelineDto(val submittedOnDate: List? = null, val approvedOnDate: List? = null, val expectedDisbursementDate: List? = null, val actualDisbursementDate: List? = null, val expectedMaturityDate: List? = null) + +@Serializable +data class LoanSummaryDto(val totalPrincipal: Double, val principalPaid: Double, val principalOutstanding: Double, val totalExpectedRepayment: Double, val totalRepayment: Double, val totalOutstanding: Double) + +@Serializable +data class LoanRepaymentScheduleDto(val loanTermInDays: Int, val totalPrincipalDisbursed: Double, val totalOutstanding: Double, val periods: List? = emptyList()) + +@Serializable +data class LoanPeriodDto(val period: Int? = null, val dueDate: List? = null, val principalDue: Double? = null, val principalPaid: Double? = null, val principalOutstanding: Double? = null, val totalDueForPeriod: Double? = null, val totalPaidForPeriod: Double? = null, val totalOutstandingForPeriod: Double? = null, val complete: Boolean? = null) + +@Serializable +data class LoanTransactionDto(val id: Long, val type: LoanTransactionTypeDto? = null, val date: List? = null, val amount: Double, val outstandingLoanBalance: Double? = null, val manuallyReversed: Boolean = false) + +@Serializable +data class LoanTransactionTypeDto(val id: Int, val code: String? = null, val value: String? = null, val disbursement: Boolean = false, val repayment: Boolean = false) + +@Serializable +data class LoanChargeDto(val id: Long, val name: String? = null, val amount: Double, val amountPaid: Double, val amountOutstanding: Double, val paid: Boolean = false, val waived: Boolean = false) + +@Serializable +data class LoanNoteDto(val id: Long, val note: String? = null, val createdOn: String? = null) + +@Serializable +data class LoanDelinquentDto(val pastDueDays: Int, val delinquentAmount: Double, val nextPaymentDueDate: List? = null) +data class NoteDto( + val id: Long, + val note: String?, + val createdOn: String?, +) + +data class DelinquentDto( + val pastDueDays: Int, + val delinquentAmount: Double, + val nextPaymentDueDate: List?, +) diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/loan/LoanAccountMapper.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/loan/LoanAccountMapper.kt new file mode 100644 index 00000000000..24f22803d2b --- /dev/null +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/loan/LoanAccountMapper.kt @@ -0,0 +1,355 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.network.mappers.loan + +import com.mifos.core.model.objects.account.loan.AmortizationType +import com.mifos.core.model.objects.account.loan.InterestCalculationPeriodType +import com.mifos.core.model.objects.account.loan.InterestRateFrequencyType +import com.mifos.core.model.objects.account.loan.InterestType +import com.mifos.core.model.objects.account.loan.RepaymentFrequencyType +import com.mifos.core.model.objects.account.loan.RepaymentSchedule +import com.mifos.core.model.objects.account.loan.TermPeriodFrequencyType +import com.mifos.core.model.objects.account.loan.loanWithAssociations.ActualDisbursementDate +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanAccountSummary +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanStatus +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanTimeline +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanType +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations +import com.mifos.core.model.objects.account.loan.loanWithAssociations.SavingAccountCurrency +import com.mifos.core.network.data.AbstractMapper +import com.mifos.room.entities.accounts.loans.ActualDisbursementDateEntity +import com.mifos.room.entities.accounts.loans.LoanAccountSummaryEntity +import com.mifos.room.entities.accounts.loans.LoanStatusEntity +import com.mifos.room.entities.accounts.loans.LoanTimelineEntity +import com.mifos.room.entities.accounts.loans.LoanTypeEntity +import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity + +object LoanAccountMapper : AbstractMapper() { + + override fun mapFromEntity(entity: LoanWithAssociationsEntity): LoanWithAssociations { + return LoanWithAssociations( + id = entity.id, + accountNo = entity.accountNo, + status = entity.status.toDomain(), + clientId = entity.clientId, + clientName = entity.clientName, + clientOfficeId = entity.clientOfficeId, + loanProductId = entity.loanProductId, + loanProductName = entity.loanProductName, + loanProductDescription = entity.loanProductDescription, + fundId = entity.fundId, + fundName = entity.fundName, + loanPurposeId = entity.loanPurposeId, + loanPurposeName = entity.loanPurposeName, + loanOfficerId = entity.loanOfficerId, + loanOfficerName = entity.loanOfficerName, + loanType = entity.loanType.toDomain(), + currency = entity.currency.toDomain(), + principal = entity.principal, + approvedPrincipal = entity.approvedPrincipal, + proposedPrincipal = entity.proposedPrincipal, + termFrequency = entity.termFrequency, + termPeriodFrequencyType = entity.termPeriodFrequencyType, + numberOfRepayments = entity.numberOfRepayments, + repaymentEvery = entity.repaymentEvery, + repaymentFrequencyType = entity.repaymentFrequencyType, + interestRatePerPeriod = entity.interestRatePerPeriod, + interestRateFrequencyType = entity.interestRateFrequencyType, + annualInterestRate = entity.annualInterestRate, + amortizationType = entity.amortizationType, + interestType = entity.interestType, + interestCalculationPeriodType = entity.interestCalculationPeriodType, + transactionProcessingStrategyId = entity.transactionProcessingStrategyId, + transactionProcessingStrategyName = entity.transactionProcessingStrategyName, + syncDisbursementWithMeeting = entity.syncDisbursementWithMeeting, + timeline = entity.timeline.toDomain(), + summary = entity.summary.toDomain(), + repaymentSchedule = entity.repaymentSchedule, + transactions = entity.transactions, + feeChargesAtDisbursementCharged = entity.feeChargesAtDisbursementCharged, + totalOverpaid = entity.totalOverpaid, + loanCounter = entity.loanCounter, + loanProductCounter = entity.loanProductCounter, + multiDisburseLoan = entity.multiDisburseLoan, + canDisburse = entity.canDisburse, + inArrears = entity.inArrears, + isNPA = entity.isNPA, + ) + } + + override fun mapToEntity(domainModel: LoanWithAssociations): LoanWithAssociationsEntity { + return LoanWithAssociationsEntity( + id = domainModel.id ?: 0, + accountNo = domainModel.accountNo ?: "", + status = domainModel.status?.toEntity() ?: LoanStatusEntity(), + clientId = domainModel.clientId ?: 0, + clientName = domainModel.clientName ?: "", + clientOfficeId = domainModel.clientOfficeId ?: 0, + loanProductId = domainModel.loanProductId ?: 0, + loanProductName = domainModel.loanProductName ?: "", + loanProductDescription = domainModel.loanProductDescription ?: "", + fundId = domainModel.fundId ?: 0, + fundName = domainModel.fundName ?: "", + loanPurposeId = domainModel.loanPurposeId ?: 0, + loanPurposeName = domainModel.loanPurposeName ?: "", + loanOfficerId = domainModel.loanOfficerId ?: 0, + loanOfficerName = domainModel.loanOfficerName ?: "", + loanType = domainModel.loanType?.toEntity() ?: LoanTypeEntity(), + currency = domainModel.currency?.toEntity() ?: SavingAccountCurrencyEntity(), + principal = domainModel.principal ?: 0.0, + approvedPrincipal = domainModel.approvedPrincipal ?: 0.0, + proposedPrincipal = domainModel.proposedPrincipal ?: 0.0, + termFrequency = domainModel.termFrequency ?: 0, + termPeriodFrequencyType = domainModel.termPeriodFrequencyType ?: TermPeriodFrequencyType(), + numberOfRepayments = domainModel.numberOfRepayments ?: 0, + repaymentEvery = domainModel.repaymentEvery ?: 0, + repaymentFrequencyType = domainModel.repaymentFrequencyType ?: RepaymentFrequencyType(), + interestRatePerPeriod = domainModel.interestRatePerPeriod ?: 0.0, + interestRateFrequencyType = domainModel.interestRateFrequencyType ?: InterestRateFrequencyType(), + annualInterestRate = domainModel.annualInterestRate ?: 0.0, + amortizationType = domainModel.amortizationType ?: AmortizationType(), + interestType = domainModel.interestType ?: InterestType(), + interestCalculationPeriodType = domainModel.interestCalculationPeriodType ?: InterestCalculationPeriodType(), + transactionProcessingStrategyId = domainModel.transactionProcessingStrategyId ?: 0, + transactionProcessingStrategyName = domainModel.transactionProcessingStrategyName ?: "", + syncDisbursementWithMeeting = domainModel.syncDisbursementWithMeeting ?: false, + timeline = domainModel.timeline?.toEntity() ?: LoanTimelineEntity(), + summary = domainModel.summary?.toEntity() ?: LoanAccountSummaryEntity(), + repaymentSchedule = domainModel.repaymentSchedule ?: RepaymentSchedule(), + transactions = domainModel.transactions ?: emptyList(), + feeChargesAtDisbursementCharged = domainModel.feeChargesAtDisbursementCharged ?: 0.0, + totalOverpaid = domainModel.totalOverpaid ?: 0.0, + loanCounter = domainModel.loanCounter ?: 0, + loanProductCounter = domainModel.loanProductCounter ?: 0, + multiDisburseLoan = domainModel.multiDisburseLoan ?: false, + canDisburse = domainModel.canDisburse ?: false, + inArrears = domainModel.inArrears ?: false, + isNPA = domainModel.isNPA ?: false, + ) + } +} + +private fun LoanStatusEntity.toDomain(): LoanStatus { + return LoanStatus( + id = id, + code = code, + value = value, + pendingApproval = pendingApproval, + waitingForDisbursal = waitingForDisbursal, + active = active, + closedObligationsMet = closedObligationsMet, + closedWrittenOff = closedWrittenOff, + closedRescheduled = closedRescheduled, + closed = closed, + overpaid = overpaid, + ) +} + +private fun LoanTypeEntity.toDomain(): LoanType { + return LoanType( + id = id, + code = code, + value = value, + ) +} + +private fun SavingAccountCurrencyEntity.toDomain(): SavingAccountCurrency { + return SavingAccountCurrency( + id = id, + code = code, + name = name, + decimalPlaces = decimalPlaces, + inMultiplesOf = inMultiplesOf, + displaySymbol = displaySymbol, + nameCode = nameCode, + displayLabel = displayLabel, + ) +} + +private fun LoanTimelineEntity.toDomain(): LoanTimeline { + return LoanTimeline( + loanId = loanId, + submittedOnDate = submittedOnDate, + submittedByUsername = submittedByUsername, + submittedByFirstname = submittedByFirstname, + submittedByLastname = submittedByLastname, + approvedOnDate = approvedOnDate, + approvedByUsername = approvedByUsername, + approvedByFirstname = approvedByFirstname, + approvedByLastname = approvedByLastname, + expectedDisbursementDate = expectedDisbursementDate, + actualDisburseDate = actualDisburseDate?.toDomain(), + actualDisbursementDate = actualDisbursementDate, + disbursedByUsername = disbursedByUsername, + disbursedByFirstname = disbursedByFirstname, + disbursedByLastname = disbursedByLastname, + closedOnDate = closedOnDate, + expectedMaturityDate = expectedMaturityDate, + ) +} + +private fun ActualDisbursementDateEntity.toDomain(): ActualDisbursementDate { + return ActualDisbursementDate( + loanId = loanId, + year = year, + month = month, + date = date, + ) +} + +private fun LoanAccountSummaryEntity.toDomain(): LoanAccountSummary { + return LoanAccountSummary( + loanId = loanId, + currency = currency?.toDomain(), + principalDisbursed = principalDisbursed, + principalPaid = principalPaid, + principalWaived = principalWaived, + principalWrittenOff = principalWrittenOff, + principalOutstanding = principalOutstanding, + principalOverdue = principalOverdue, + interestCharged = interestCharged, + interestPaid = interestPaid, + interestWaived = interestWaived, + interestWrittenOff = interestWrittenOff, + interestOutstanding = interestOutstanding, + interestOverdue = interestOverdue, + feeChargesCharged = feeChargesCharged, + feeChargesDueAtDisbursementCharged = feeChargesDueAtDisbursementCharged, + feeChargesPaid = feeChargesPaid, + feeChargesWaived = feeChargesWaived, + feeChargesWrittenOff = feeChargesWrittenOff, + feeChargesOutstanding = feeChargesOutstanding, + feeChargesOverdue = feeChargesOverdue, + penaltyChargesCharged = penaltyChargesCharged, + penaltyChargesPaid = penaltyChargesPaid, + penaltyChargesWaived = penaltyChargesWaived, + penaltyChargesWrittenOff = penaltyChargesWrittenOff, + penaltyChargesOutstanding = penaltyChargesOutstanding, + penaltyChargesOverdue = penaltyChargesOverdue, + totalExpectedRepayment = totalExpectedRepayment, + totalRepayment = totalRepayment, + totalExpectedCostOfLoan = totalExpectedCostOfLoan, + totalCostOfLoan = totalCostOfLoan, + totalWaived = totalWaived, + totalWrittenOff = totalWrittenOff, + totalOutstanding = totalOutstanding, + totalOverdue = totalOverdue, + overdueSinceDate = overdueSinceDate, + ) +} +private fun LoanStatus.toEntity(): LoanStatusEntity { + return LoanStatusEntity( + id = id ?: 0, + code = code, + value = value, + pendingApproval = pendingApproval ?: false, + waitingForDisbursal = waitingForDisbursal ?: false, + active = active ?: false, + closedObligationsMet = closedObligationsMet ?: false, + closedWrittenOff = closedWrittenOff ?: false, + closedRescheduled = closedRescheduled ?: false, + closed = closed ?: false, + overpaid = overpaid ?: false, + ) +} + +private fun LoanType.toEntity(): LoanTypeEntity { + return LoanTypeEntity( + id = id ?: 0, + code = code, + value = value, + ) +} + +private fun SavingAccountCurrency.toEntity(): SavingAccountCurrencyEntity { + return SavingAccountCurrencyEntity( + id = id, + code = code, + name = name, + decimalPlaces = decimalPlaces, + inMultiplesOf = inMultiplesOf, + displaySymbol = displaySymbol, + nameCode = nameCode, + displayLabel = displayLabel, + ) +} + +private fun LoanTimeline.toEntity(): LoanTimelineEntity { + return LoanTimelineEntity( + loanId = loanId ?: 0, + submittedOnDate = submittedOnDate, + submittedByUsername = submittedByUsername, + submittedByFirstname = submittedByFirstname, + submittedByLastname = submittedByLastname, + approvedOnDate = approvedOnDate, + approvedByUsername = approvedByUsername, + approvedByFirstname = approvedByFirstname, + approvedByLastname = approvedByLastname, + expectedDisbursementDate = expectedDisbursementDate, + actualDisburseDate = actualDisburseDate?.toEntity(), + actualDisbursementDate = actualDisbursementDate, + disbursedByUsername = disbursedByUsername, + disbursedByFirstname = disbursedByFirstname, + disbursedByLastname = disbursedByLastname, + closedOnDate = closedOnDate, + expectedMaturityDate = expectedMaturityDate, + ) +} + +private fun ActualDisbursementDate.toEntity(): ActualDisbursementDateEntity { + return ActualDisbursementDateEntity( + loanId = loanId ?: 0, + year = year, + month = month, + date = date, + ) +} + +private fun LoanAccountSummary.toEntity(): LoanAccountSummaryEntity { + return LoanAccountSummaryEntity( + loanId = loanId ?: 0, + currency = currency?.toEntity(), + principalDisbursed = principalDisbursed, + principalPaid = principalPaid, + principalWaived = principalWaived, + principalWrittenOff = principalWrittenOff, + principalOutstanding = principalOutstanding, + principalOverdue = principalOverdue, + interestCharged = interestCharged, + interestPaid = interestPaid, + interestWaived = interestWaived, + interestWrittenOff = interestWrittenOff, + interestOutstanding = interestOutstanding, + interestOverdue = interestOverdue, + feeChargesCharged = feeChargesCharged, + feeChargesDueAtDisbursementCharged = feeChargesDueAtDisbursementCharged, + feeChargesPaid = feeChargesPaid, + feeChargesWaived = feeChargesWaived, + feeChargesWrittenOff = feeChargesWrittenOff, + feeChargesOutstanding = feeChargesOutstanding, + feeChargesOverdue = feeChargesOverdue, + penaltyChargesCharged = penaltyChargesCharged, + penaltyChargesPaid = penaltyChargesPaid, + penaltyChargesWaived = penaltyChargesWaived, + penaltyChargesWrittenOff = penaltyChargesWrittenOff, + penaltyChargesOutstanding = penaltyChargesOutstanding, + penaltyChargesOverdue = penaltyChargesOverdue, + totalExpectedRepayment = totalExpectedRepayment, + totalRepayment = totalRepayment, + totalExpectedCostOfLoan = totalExpectedCostOfLoan, + totalCostOfLoan = totalCostOfLoan, + totalWaived = totalWaived, + totalWrittenOff = totalWrittenOff, + totalOutstanding = totalOutstanding, + totalOverdue = totalOverdue, + overdueSinceDate = overdueSinceDate, + ) +} diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/loan/LoanWithAssociationsMapper.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/loan/LoanWithAssociationsMapper.kt new file mode 100644 index 00000000000..bd4fe86d6b9 --- /dev/null +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/mappers/loan/LoanWithAssociationsMapper.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.core.network.mappers.loan + +import com.mifos.core.model.objects.account.loan.Period +import com.mifos.core.model.objects.account.loan.RepaymentSchedule +import com.mifos.core.model.objects.account.loan.Transaction +import com.mifos.core.model.objects.account.loan.Type +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanAccountSummary +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanStatus +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanTimeline +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanType +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations +import com.mifos.core.model.objects.account.loan.loanWithAssociations.SavingAccountCurrency +import com.mifos.core.network.dto.loan.LoanCurrencyDto +import com.mifos.core.network.dto.loan.LoanRepaymentScheduleDto +import com.mifos.core.network.dto.loan.LoanStatusDto +import com.mifos.core.network.dto.loan.LoanSummaryDto +import com.mifos.core.network.dto.loan.LoanTimelineDto +import com.mifos.core.network.dto.loan.LoanTransactionDto +import com.mifos.core.network.dto.loan.LoanTypeDto +import com.mifos.core.network.dto.loan.LoanWithAssociationsDto + +fun LoanWithAssociationsDto.toDomain(): LoanWithAssociations = LoanWithAssociations( + id = this.id.toInt(), + accountNo = this.accountNo, + status = this.status?.toDomain(), + clientId = this.clientId.toInt(), + clientName = this.clientName, + loanProductId = this.loanProductId.toInt(), + loanProductName = this.loanProductName, + loanProductDescription = this.loanProductDescription, + loanOfficerId = this.loanOfficerId?.toInt(), + loanOfficerName = this.loanOfficerName, + loanType = this.loanType?.toDomain(), + currency = this.currency?.toDomain(), + principal = this.principal, + approvedPrincipal = this.approvedPrincipal, + termFrequency = this.termFrequency, + numberOfRepayments = this.numberOfRepayments, + annualInterestRate = this.annualInterestRate, + timeline = this.timeline?.toDomain(), + summary = this.summary?.toDomain(), + repaymentSchedule = this.repaymentSchedule?.toDomain(), + transactions = this.transactions?.map { it.toDomain() }, +) + +fun LoanStatusDto.toDomain() = LoanStatus( + id = this.id, + code = this.code, + value = this.value, + active = this.active, + closed = this.closed, + overpaid = this.overpaid, +) + +fun LoanTypeDto.toDomain() = LoanType( + id = this.id, + code = this.code, + value = this.value, +) + +fun LoanCurrencyDto.toDomain() = SavingAccountCurrency( + code = this.code, + name = this.name, + decimalPlaces = this.decimalPlaces, + displaySymbol = this.displaySymbol, + displayLabel = this.displayLabel, +) + +fun LoanTimelineDto.toDomain() = LoanTimeline( + submittedOnDate = this.submittedOnDate, + approvedOnDate = this.approvedOnDate, + expectedDisbursementDate = this.expectedDisbursementDate, + actualDisbursementDate = this.actualDisbursementDate, + expectedMaturityDate = this.expectedMaturityDate, +) + +fun LoanSummaryDto.toDomain() = LoanAccountSummary( + principalDisbursed = this.totalPrincipal, + principalPaid = this.principalPaid, + principalOutstanding = this.principalOutstanding, + totalExpectedRepayment = this.totalExpectedRepayment, + totalRepayment = this.totalRepayment, + totalOutstanding = this.totalOutstanding, +) + +fun LoanRepaymentScheduleDto.toDomain() = RepaymentSchedule( + loanTermInDays = this.loanTermInDays, + totalPrincipalDisbursed = this.totalPrincipalDisbursed, + totalOutstanding = this.totalOutstanding, + periods = this.periods?.map { + Period( + period = it.period, + dueDate = it.dueDate, + principalDue = it.principalDue, + principalPaid = it.principalPaid, + principalOutstanding = it.principalOutstanding, + totalDueForPeriod = it.totalDueForPeriod, + totalPaidForPeriod = it.totalPaidForPeriod, + totalOutstandingForPeriod = it.totalOutstandingForPeriod, + complete = it.complete, + ) + }, +) + +fun LoanTransactionDto.toDomain() = Transaction( + id = this.id.toInt(), + date = this.date?.let { ArrayList(it) } ?: ArrayList(), + amount = this.amount, + outstandingLoanBalance = this.outstandingLoanBalance, + manuallyReversed = this.manuallyReversed, + type = this.type?.let { + Type( + id = it.id, + code = it.code, + value = it.value, + disbursement = it.disbursement, + repayment = it.repayment, + ) + }, +) diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/services/LoanService.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/services/LoanService.kt index 259a9ed58b7..9fe668733c1 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/services/LoanService.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/services/LoanService.kt @@ -23,12 +23,12 @@ import com.mifos.core.model.objects.organisations.LoanProducts import com.mifos.core.model.objects.payloads.GroupLoanPayload import com.mifos.core.model.objects.template.loan.GroupLoanTemplate import com.mifos.core.network.GenericResponse +import com.mifos.core.network.dto.loan.LoanWithAssociationsDto import com.mifos.core.network.model.LoansPayload import com.mifos.room.basemodel.APIEndPoint import com.mifos.room.entities.accounts.loans.Loan import com.mifos.room.entities.accounts.loans.LoanRepaymentRequestEntity import com.mifos.room.entities.accounts.loans.LoanRepaymentResponseEntity -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import com.mifos.room.entities.client.ChargesEntity import com.mifos.room.entities.templates.loans.LoanRepaymentTemplateEntity import com.mifos.room.entities.templates.loans.LoanTemplate @@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.Flow */ interface LoanService { @GET(APIEndPoint.LOANS + "/{loanId}?associations=all&exclude=guarantors,futureSchedule") - suspend fun getLoanByIdWithAllAssociations(@Path("loanId") loanId: Int): LoanWithAssociationsEntity + suspend fun getLoanByIdWithAllAssociations(@Path("loanId") loanId: Int): LoanWithAssociationsDto @GET(APIEndPoint.LOANS + "/{loanId}/transactions/template?command=repayment") suspend fun getLoanRepaymentTemplate(@Path("loanId") loanId: Int): LoanRepaymentTemplateEntity @@ -74,10 +74,10 @@ interface LoanService { ): LoanRepaymentResponseEntity @GET(APIEndPoint.LOANS + "/{loanId}?associations=repaymentSchedule") - fun getLoanRepaymentSchedule(@Path("loanId") loanId: Int): Flow + fun getLoanRepaymentSchedule(@Path("loanId") loanId: Int): Flow @GET(APIEndPoint.LOANS + "/{loanId}?associations=transactions") - fun getLoanWithTransactions(@Path("loanId") loanId: Int): Flow + fun getLoanWithTransactions(@Path("loanId") loanId: Int): Flow @GET(APIEndPoint.CREATE_LOANS_PRODUCTS) fun getAllLoans(): Flow> diff --git a/feature/loan/src/commonMain/composeResources/values/strings.xml b/feature/loan/src/commonMain/composeResources/values/strings.xml index bf43e586588..1e4199dcfaf 100644 --- a/feature/loan/src/commonMain/composeResources/values/strings.xml +++ b/feature/loan/src/commonMain/composeResources/values/strings.xml @@ -235,6 +235,38 @@ Transaction ID: %1$s Payment Successful + Performance History + Loan Summary + Loan Details + + Number of Repayments + Maturity Date + + Component + Original + Paid + Waived + Written Off + Outstanding + Over Due + + Principal + Interest + Fees + Penalties + Total + + Disbursement Date + Loan Purpose + Loan Officer + Currency + Proposed Amount + Approved Amount + Disbursed Amount + + Unassigned + Not Available + New Loan Account diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/amountTransfer/AmountTransferViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/amountTransfer/AmountTransferViewModel.kt index f43b9176baf..2e8bcf6b545 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/amountTransfer/AmountTransferViewModel.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/amountTransfer/AmountTransferViewModel.kt @@ -27,6 +27,7 @@ import com.mifos.core.common.utils.DateHelper import com.mifos.core.data.repository.AmountTransferRepository import com.mifos.core.data.repository.ClientDetailsRepository import com.mifos.core.data.repository.LoanAccountSummaryRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.model.objects.account.loan.transfer.AccountOption import com.mifos.core.model.objects.account.loan.transfer.AccountTransferRequest import com.mifos.core.model.objects.account.loan.transfer.AccountTransferResponse @@ -36,7 +37,6 @@ import com.mifos.core.model.objects.account.loan.transfer.ClientOption import com.mifos.core.model.objects.account.loan.transfer.OfficeOption import com.mifos.core.ui.components.ResultStatus import com.mifos.core.ui.util.BaseViewModel -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update @@ -525,7 +525,7 @@ sealed interface AmountTransferAction { sealed interface Internal : AmountTransferAction { data class ReceiveLoanAccountDetailsResult( - val loanAccountDetailsResult: DataState, + val loanAccountDetailsResult: DataState, ) : Internal data class ReceiveInitialTemplateResult( diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/di/LoanModule.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/di/LoanModule.kt index 349c3f430e8..cb355978a71 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/di/LoanModule.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/di/LoanModule.kt @@ -13,6 +13,7 @@ import com.mifos.feature.loan.amountTransfer.AmountTransferViewModel import com.mifos.feature.loan.createLoanReschedules.LoanRescheduleFormViewModel import com.mifos.feature.loan.groupLoanAccount.GroupLoanAccountViewModel import com.mifos.feature.loan.loanAccount.LoanAccountViewModel +import com.mifos.feature.loan.loanAccountGeneral.LoanAccountGeneralViewModel import com.mifos.feature.loan.loanAccountProfile.LoanAccountProfileViewModel import com.mifos.feature.loan.loanAccountSummary.LoanAccountSummaryViewModel import com.mifos.feature.loan.loanApproval.LoanAccountApprovalViewModel @@ -29,6 +30,7 @@ import org.koin.dsl.module val LoanModule = module { viewModelOf(::GroupLoanAccountViewModel) viewModelOf(::LoanAccountViewModel) + viewModelOf(::LoanAccountGeneralViewModel) viewModelOf(::LoanAccountSummaryViewModel) viewModelOf(::LoanAccountApprovalViewModel) viewModelOf(::LoanChargeViewModel) diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralNavigation.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralNavigation.kt new file mode 100644 index 00000000000..c133766169c --- /dev/null +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralNavigation.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.feature.loan.loanAccountGeneral + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import kotlinx.serialization.Serializable + +@Serializable +data class LoanAccountGeneralRoute( + val loanId: Int, +) + +fun NavGraphBuilder.loanAccountGeneralDestination( + navController: NavController, +) { + composable { + LoanAccountGeneralScreen( + navController = navController, + ) + } +} + +fun NavController.navigateToLoanAccountGeneralScreen(loanId: Int) { + this.navigate(LoanAccountGeneralRoute(loanId = loanId)) +} diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralScreen.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralScreen.kt new file mode 100644 index 00000000000..8730cb775ef --- /dev/null +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralScreen.kt @@ -0,0 +1,438 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.feature.loan.loanAccountGeneral + +import androidclient.feature.loan.generated.resources.Res +import androidclient.feature.loan.generated.resources.feature_loan_general_maturity_date +import androidclient.feature.loan.generated.resources.feature_loan_general_number_of_repayments +import androidclient.feature.loan.generated.resources.feature_loan_general_section_loan_details +import androidclient.feature.loan.generated.resources.feature_loan_general_section_loan_summary +import androidclient.feature.loan.generated.resources.feature_loan_general_section_performance_history +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_col_component +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_col_original +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_col_outstanding +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_col_overdue +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_col_paid +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_col_waived +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_col_written_off +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_row_total +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import com.mifos.core.designsystem.component.MifosTableRow +import com.mifos.core.designsystem.theme.DesignToken +import com.mifos.core.designsystem.theme.MifosTheme +import com.mifos.core.ui.components.MifosBreadcrumbNavBar +import com.mifos.core.ui.components.MifosDefaultListingComponentFromStringResources +import com.mifos.core.ui.components.MifosErrorComponent +import com.mifos.core.ui.components.MifosProgressIndicator +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.viewmodel.koinViewModel +import template.core.base.designsystem.theme.KptTheme + +@Composable +internal fun LoanAccountGeneralScreen( + navController: NavController, + modifier: Modifier = Modifier, + viewModel: LoanAccountGeneralViewModel = koinViewModel(), +) { + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + val onAction = remember(viewModel) { { action: LoanAccountGeneralAction -> viewModel.trySendAction(action) } } + + LoanAccountGeneralContent( + state = state, + navController = navController, + modifier = modifier, + ) + + LoanAccountGeneralDialogs( + state = state, + onAction = onAction, + ) +} + +@Composable +private fun LoanAccountGeneralDialogs( + state: LoanAccountGeneralState, + onAction: (LoanAccountGeneralAction) -> Unit, +) { + when (state.dialogState) { + is LoanAccountGeneralState.DialogState.Error -> { + MifosErrorComponent( + isNetworkConnected = state.networkConnection, + message = state.dialogState.message, + isRetryEnabled = true, + onRetry = { onAction(LoanAccountGeneralAction.OnRetry) }, + ) + } + + LoanAccountGeneralState.DialogState.Loading -> MifosProgressIndicator() + + null -> Unit + } +} + +@Composable +private fun LoanAccountGeneralContent( + state: LoanAccountGeneralState, + navController: NavController, + modifier: Modifier = Modifier, +) { + if (state.dialogState == null) { + Column(modifier = modifier.fillMaxSize()) { + MifosBreadcrumbNavBar(navController = navController) + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + ) { + Text( + text = stringResource(Res.string.feature_loan_general_section_performance_history), + style = KptTheme.typography.labelLarge, + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) + + Spacer(Modifier.height(DesignToken.spacing.medium)) + + PerformanceHistoryCard( + state = state, + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) + + Spacer(Modifier.height(DesignToken.spacing.largeIncreased)) + + Text( + text = stringResource(Res.string.feature_loan_general_section_loan_summary), + style = KptTheme.typography.labelLarge, + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) + + Spacer(Modifier.height(DesignToken.spacing.medium)) + + LoanSummaryTable(state = state) + + Spacer(Modifier.height(DesignToken.spacing.largeIncreased)) + + Text( + text = stringResource(Res.string.feature_loan_general_section_loan_details), + style = KptTheme.typography.labelLarge, + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) + + Spacer(Modifier.height(DesignToken.spacing.medium)) + + LoanDetailsSection( + state = state, + modifier = Modifier.padding(horizontal = KptTheme.spacing.md), + ) + + Spacer(Modifier.height(KptTheme.spacing.xl)) + } + } + } +} + +@Composable +private fun PerformanceHistoryCard( + state: LoanAccountGeneralState, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .fillMaxWidth() + .clip(KptTheme.shapes.medium) + .background(KptTheme.colorScheme.primary) + .padding(KptTheme.spacing.lg), + contentAlignment = Alignment.Center, + ) { + Column( + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.sm), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + PerformanceHistoryRow( + label = stringResource(Res.string.feature_loan_general_number_of_repayments), + value = state.numberOfRepayments, + ) + PerformanceHistoryRow( + label = stringResource(Res.string.feature_loan_general_maturity_date), + value = state.maturityDate, + ) + } + } +} + +@Composable +private fun PerformanceHistoryRow( + label: String, + value: String, + modifier: Modifier = Modifier, + textStyle: TextStyle = TextStyle( + color = KptTheme.colorScheme.onPrimary, + fontStyle = KptTheme.typography.labelMedium.fontStyle, + ), +) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text(text = label, style = textStyle) + Text(text = value, style = textStyle) + } +} + +@Composable +private fun LoanSummaryTable( + state: LoanAccountGeneralState, + modifier: Modifier = Modifier, +) { + val scrollState = rememberScrollState() + val textColor = KptTheme.colorScheme.onBackground + val mediumWidth = DesignToken.sizes.tableCellWidthMedium + val colWidths = listOf( + mediumWidth, + mediumWidth, + mediumWidth, + mediumWidth, + mediumWidth, + mediumWidth, + mediumWidth, + ) + + val headers = listOf( + stringResource(Res.string.feature_loan_general_summary_col_component), + stringResource(Res.string.feature_loan_general_summary_col_original), + stringResource(Res.string.feature_loan_general_summary_col_paid), + stringResource(Res.string.feature_loan_general_summary_col_waived), + stringResource(Res.string.feature_loan_general_summary_col_written_off), + stringResource(Res.string.feature_loan_general_summary_col_outstanding), + stringResource(Res.string.feature_loan_general_summary_col_overdue), + ) + + val totalLabel = stringResource(Res.string.feature_loan_general_summary_row_total) + + Column( + modifier = modifier + .fillMaxWidth() + .horizontalScroll(scrollState), + ) { + MifosTableRow( + cells = headers.map { label -> + { + LoanSummaryTableCell( + text = label, + style = KptTheme.typography.titleSmall, + fontWeight = FontWeight.Bold, + textColor = textColor, + ) + } + }, + widths = colWidths, + backgroundColor = lerp( + KptTheme.colorScheme.surface, + KptTheme.colorScheme.primary, + 0.3f, + ), + edgeOffset = DesignToken.padding.medium, + cornerShape = DesignToken.shapes.topMedium, + ) + + state.summaryRows.forEach { row -> + val amounts = listOf(row.original, row.paid, row.waived, row.writtenOff, row.outstanding, row.overDue) + + MifosTableRow( + cells = buildList { + add { + LoanSummaryTableCell( + text = row.component, + style = KptTheme.typography.bodySmall, + fontWeight = FontWeight.Normal, + textColor = textColor, + ) + } + amounts.forEach { amount -> + add { + LoanSummaryTableCell( + text = amount, + style = KptTheme.typography.bodySmall, + fontWeight = FontWeight.Normal, + textColor = textColor, + ) + } + } + }, + widths = colWidths, + backgroundColor = KptTheme.colorScheme.surface, + edgeOffset = DesignToken.padding.medium, + ) + } + + val totals = listOf(state.totalOriginal, state.totalPaid, state.totalWaived, state.totalWrittenOff, state.totalOutstanding, state.totalOverDue) + + MifosTableRow( + cells = buildList { + add { + LoanSummaryTableCell( + text = totalLabel, + style = KptTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + textColor = textColor, + ) + } + totals.forEach { total -> + add { + LoanSummaryTableCell( + text = total, + style = KptTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + textColor = textColor, + ) + } + } + }, + widths = colWidths, + backgroundColor = lerp(KptTheme.colorScheme.surface, KptTheme.colorScheme.primary, 0.15f), + edgeOffset = DesignToken.padding.medium, + cornerShape = DesignToken.shapes.bottomMedium, + showBottomBorder = false, + ) + } +} + +@Composable +private fun LoanDetailsSection( + state: LoanAccountGeneralState, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(DesignToken.padding.medium), + ) { + state.details.forEach { detailGroup -> + val listingData = detailGroup.items.associate { item -> + when (item) { + is LoanAccountGeneralState.LoanDetailItem.Text -> item.label to item.value + } + } + MifosDefaultListingComponentFromStringResources(data = listingData) + } + } +} + +@Composable +private fun LoanSummaryTableCell( + text: String, + style: TextStyle, + fontWeight: FontWeight, + textColor: Color, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .fillMaxWidth() + .padding(vertical = KptTheme.spacing.sm, horizontal = KptTheme.spacing.xs), + ) { + Text( + text = text, + style = style, + fontWeight = fontWeight, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Left, + color = textColor, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun LoanAccountGeneralPreview() { + MifosTheme { + val previewState = LoanAccountGeneralState( + numberOfRepayments = "12", + maturityDate = "17 August 2026", + summaryRows = listOf( + LoanAccountGeneralState.SummaryRowState("Principal", "$1,243.00", "$1,243.00", "$0.00", "$0.00", "$0.00", "$0.00"), + LoanAccountGeneralState.SummaryRowState("Interest", "$946.09", "$946.09", "$0.00", "$0.00", "$0.00", "$0.00"), + LoanAccountGeneralState.SummaryRowState("Fees", "$20.00", "$20.00", "$0.00", "$0.00", "$0.00", "$0.00"), + LoanAccountGeneralState.SummaryRowState("Penalties", "$0.00", "$0.00", "$0.00", "$0.00", "$0.00", "$0.00"), + ), + totalOriginal = "$2,209.09", + totalPaid = "$2,209.09", + totalWaived = "$0.00", + totalWrittenOff = "$0.00", + totalOutstanding = "$0.00", + totalOverDue = "$0.00", + disbursementDate = "17 August 2025", + loanPurpose = "Not Available", + loanOfficer = "Unassigned", + currency = "US Dollar USD", + externalId = "Not Available", + proposedAmount = "$1,243.00", + approvedAmount = "$1,243.00", + disbursedAmount = "$1,243.00", + ) + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(horizontal = KptTheme.spacing.md), + ) { + Text( + text = stringResource(Res.string.feature_loan_general_section_performance_history), + style = KptTheme.typography.labelLarge, + ) + Spacer(Modifier.height(DesignToken.spacing.medium)) + PerformanceHistoryCard(state = previewState) + + Spacer(Modifier.height(DesignToken.spacing.largeIncreased)) + Text( + text = stringResource(Res.string.feature_loan_general_section_loan_summary), + style = KptTheme.typography.labelLarge, + ) + Spacer(Modifier.height(DesignToken.spacing.medium)) + LoanSummaryTable(state = previewState) + + Spacer(Modifier.height(DesignToken.spacing.largeIncreased)) + Text( + text = stringResource(Res.string.feature_loan_general_section_loan_details), + style = KptTheme.typography.labelLarge, + ) + Spacer(Modifier.height(DesignToken.spacing.medium)) + LoanDetailsSection(state = previewState) + + Spacer(Modifier.height(KptTheme.spacing.xl)) + } + } +} diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralViewModel.kt new file mode 100644 index 00000000000..65d4cb00e5a --- /dev/null +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountGeneral/LoanAccountGeneralViewModel.kt @@ -0,0 +1,301 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.feature.loan.loanAccountGeneral + +import androidclient.feature.loan.generated.resources.Res +import androidclient.feature.loan.generated.resources.feature_loan_general_detail_approved_amount +import androidclient.feature.loan.generated.resources.feature_loan_general_detail_currency +import androidclient.feature.loan.generated.resources.feature_loan_general_detail_disbursed_amount +import androidclient.feature.loan.generated.resources.feature_loan_general_detail_disbursement_date +import androidclient.feature.loan.generated.resources.feature_loan_general_detail_loan_officer +import androidclient.feature.loan.generated.resources.feature_loan_general_detail_loan_purpose +import androidclient.feature.loan.generated.resources.feature_loan_general_detail_proposed_amount +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_row_fees +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_row_interest +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_row_penalties +import androidclient.feature.loan.generated.resources.feature_loan_general_summary_row_principal +import androidclient.feature.loan.generated.resources.feature_loan_general_value_not_available +import androidclient.feature.loan.generated.resources.feature_loan_general_value_unassigned +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 androidx.lifecycle.SavedStateHandle +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.common.utils.DateHelper +import com.mifos.core.data.repository.LoanAccountGeneralRepository +import com.mifos.core.data.util.NetworkUnavailableException +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations +import com.mifos.core.ui.util.BaseViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.getString + +internal class LoanAccountGeneralViewModel( + savedStateHandle: SavedStateHandle, + private val repository: LoanAccountGeneralRepository, +) : BaseViewModel( + initialState = LoanAccountGeneralState(), +) { + private val loanId = savedStateHandle.toRoute().loanId + private var loadJob: Job? = null + + init { + loadLoanById() + } + + override fun handleAction(action: LoanAccountGeneralAction) { + when (action) { + LoanAccountGeneralAction.OnRetry -> loadLoanById() + } + } + + private fun loadLoanById() { + loadJob?.cancel() + loadJob = viewModelScope.launch { + repository.getLoanById(loanId).collect { dataState -> + when (dataState) { + is DataState.Loading -> { + mutableStateFlow.update { + it.copy(dialogState = LoanAccountGeneralState.DialogState.Loading) + } + } + + is DataState.Success -> { + val loan = dataState.data + if (loan != null) { + try { + fillGeneralState(loan) + } catch (e: Exception) { + mutableStateFlow.update { + it.copy(dialogState = LoanAccountGeneralState.DialogState.Error(e.message.toString())) + } + } + } else { + mutableStateFlow.update { + it.copy(dialogState = LoanAccountGeneralState.DialogState.Error(getString(Res.string.feature_loan_profile_error_details_not_found))) + } + } + } + + is DataState.Error -> { + val isNetworkError = dataState.exception is NetworkUnavailableException + mutableStateFlow.update { + it.copy( + networkConnection = !isNetworkError, + dialogState = LoanAccountGeneralState.DialogState.Error( + if (isNetworkError) { + getString(Res.string.feature_loan_profile_error_network_not_available) + } else { + dataState.message + }, + ), + ) + } + } + } + } + } + } + + private suspend fun fillGeneralState(loan: LoanWithAssociations) { + val currencyCode = loan.currency?.code + val maxDigits = loan.currency?.decimalPlaces + val summary = loan.summary + fun formatAmount(value: Double?) = CurrencyFormatter.format(value, currencyCode, maxDigits) + + val expectedMaturityDate = loan.timeline?.expectedMaturityDate + val maturityDate = if (!expectedMaturityDate.isNullOrEmpty()) { + DateHelper.getDateAsString(expectedMaturityDate) + } else { + "" + } + + val actualDisbursementDate = loan.timeline?.actualDisbursementDate + val disbursementDate = if (!actualDisbursementDate.isNullOrEmpty()) { + DateHelper.getDateAsString(actualDisbursementDate.filterNotNull()) + } else { + "" + } + + val currencyDisplay = if (!loan.currency?.name.isNullOrBlank() && !loan.currency?.code.isNullOrBlank()) { + "${loan.currency?.name} ${loan.currency?.code}" + } else { + "" + } + + val loanOfficer = loan.loanOfficerName?.takeIf { it.isNotBlank() } + ?: getString(Res.string.feature_loan_general_value_unassigned) + val loanPurpose = loan.loanPurposeName?.takeIf { it.isNotBlank() } + ?: getString(Res.string.feature_loan_general_value_not_available) + val externalId = loan.accountNo?.takeIf { it.isNotBlank() } + ?: getString(Res.string.feature_loan_general_value_not_available) + + val proposedAmountValue = formatAmount(loan.proposedPrincipal) + val approvedAmountValue = formatAmount(loan.approvedPrincipal) + val disbursedAmountValue = formatAmount(loan.principal) + + mutableStateFlow.update { + it.copy( + dialogState = null, + numberOfRepayments = loan.numberOfRepayments?.toString().orEmpty(), + maturityDate = maturityDate, + disbursementDate = disbursementDate, + loanOfficer = loanOfficer, + currency = currencyDisplay, + loanPurpose = loanPurpose, + externalId = externalId, + proposedAmount = proposedAmountValue, + approvedAmount = approvedAmountValue, + disbursedAmount = disbursedAmountValue, + details = listOf( + LoanAccountGeneralState.LoanDetailGroupState( + items = listOf( + LoanAccountGeneralState.LoanDetailItem.Text( + label = Res.string.feature_loan_general_detail_disbursement_date, + value = disbursementDate, + ), + LoanAccountGeneralState.LoanDetailItem.Text( + label = Res.string.feature_loan_general_detail_loan_purpose, + value = loanPurpose, + ), + LoanAccountGeneralState.LoanDetailItem.Text( + label = Res.string.feature_loan_general_detail_loan_officer, + value = loanOfficer, + ), + LoanAccountGeneralState.LoanDetailItem.Text( + label = Res.string.feature_loan_general_detail_currency, + value = currencyDisplay, + ), + ), + ), + LoanAccountGeneralState.LoanDetailGroupState( + items = listOf( + LoanAccountGeneralState.LoanDetailItem.Text( + label = Res.string.feature_loan_general_detail_proposed_amount, + value = proposedAmountValue, + ), + LoanAccountGeneralState.LoanDetailItem.Text( + label = Res.string.feature_loan_general_detail_approved_amount, + value = approvedAmountValue, + ), + LoanAccountGeneralState.LoanDetailItem.Text( + label = Res.string.feature_loan_general_detail_disbursed_amount, + value = disbursedAmountValue, + ), + ), + ), + ), + summaryRows = listOf( + LoanAccountGeneralState.SummaryRowState( + component = getString(Res.string.feature_loan_general_summary_row_principal), + original = formatAmount(summary?.principalDisbursed), + paid = formatAmount(summary?.principalPaid), + waived = formatAmount(summary?.principalWaived), + writtenOff = formatAmount(summary?.principalWrittenOff), + outstanding = formatAmount(summary?.principalOutstanding), + overDue = formatAmount(summary?.principalOverdue), + ), + LoanAccountGeneralState.SummaryRowState( + component = getString(Res.string.feature_loan_general_summary_row_interest), + original = formatAmount(summary?.interestCharged), + paid = formatAmount(summary?.interestPaid), + waived = formatAmount(summary?.interestWaived), + writtenOff = formatAmount(summary?.interestWrittenOff), + outstanding = formatAmount(summary?.interestOutstanding), + overDue = formatAmount(summary?.interestOverdue), + ), + LoanAccountGeneralState.SummaryRowState( + component = getString(Res.string.feature_loan_general_summary_row_fees), + original = formatAmount(summary?.feeChargesCharged), + paid = formatAmount(summary?.feeChargesPaid), + waived = formatAmount(summary?.feeChargesWaived), + writtenOff = formatAmount(summary?.feeChargesWrittenOff), + outstanding = formatAmount(summary?.feeChargesOutstanding), + overDue = formatAmount(summary?.feeChargesOverdue), + ), + LoanAccountGeneralState.SummaryRowState( + component = getString(Res.string.feature_loan_general_summary_row_penalties), + original = formatAmount(summary?.penaltyChargesCharged), + paid = formatAmount(summary?.penaltyChargesPaid), + waived = formatAmount(summary?.penaltyChargesWaived), + writtenOff = formatAmount(summary?.penaltyChargesWrittenOff), + outstanding = formatAmount(summary?.penaltyChargesOutstanding), + overDue = formatAmount(summary?.penaltyChargesOverdue), + ), + ), + totalOriginal = formatAmount(summary?.totalExpectedRepayment), + totalPaid = formatAmount(summary?.totalRepayment), + totalWaived = formatAmount(summary?.totalWaived), + totalWrittenOff = formatAmount(summary?.totalWrittenOff), + totalOutstanding = formatAmount(summary?.totalOutstanding), + totalOverDue = formatAmount(summary?.totalOverdue), + ) + } + } +} + +data class LoanAccountGeneralState( + val dialogState: DialogState? = null, + val networkConnection: Boolean = false, + val numberOfRepayments: String = "", + val maturityDate: String = "", + val details: List = emptyList(), + val summaryRows: List = emptyList(), + val totalOriginal: String = "", + val totalPaid: String = "", + val totalWaived: String = "", + val totalWrittenOff: String = "", + val totalOutstanding: String = "", + val totalOverDue: String = "", + val disbursementDate: String = "", + val loanPurpose: String = "", + val loanOfficer: String = "", + val currency: String = "", + val externalId: String = "", + val proposedAmount: String = "", + val approvedAmount: String = "", + val disbursedAmount: String = "", +) { + sealed interface DialogState { + data object Loading : DialogState + data class Error(val message: String) : DialogState + } + + data class LoanDetailGroupState( + val items: List, + ) + + sealed interface LoanDetailItem { + data class Text( + val label: StringResource, + val value: String, + ) : LoanDetailItem + } + + data class SummaryRowState( + val component: String, + val original: String, + val paid: String, + val waived: String, + val writtenOff: String, + val outstanding: String, + val overDue: String, + ) +} + +sealed interface LoanAccountGeneralEvent + +sealed interface LoanAccountGeneralAction { + data object OnRetry : LoanAccountGeneralAction +} diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileNavigation.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileNavigation.kt index 29cee6a555e..62da167f90e 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileNavigation.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileNavigation.kt @@ -12,7 +12,7 @@ package com.mifos.feature.loan.loanAccountProfile import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import kotlinx.serialization.Serializable @Serializable @@ -23,20 +23,22 @@ data class LoanAccountRoute( fun NavGraphBuilder.loanProfileAccountDestination( onNavigateBack: () -> Unit, navController: NavController, + navigateToGeneral: (Int) -> Unit, navigateToRepaymentSchedule: (Int) -> Unit, navigateToTransactions: (Int) -> Unit, navigateToCharges: (Int) -> Unit, navigateToDocuments: (Int) -> Unit, navigateToReschedules: (Int) -> Unit, navigateToNotes: (Int) -> Unit, - approveLoan: (Int, LoanWithAssociationsEntity) -> Unit, - onRepaymentClick: (LoanWithAssociationsEntity) -> Unit, + approveLoan: (Int) -> Unit, + onRepaymentClick: (LoanWithAssociations) -> Unit, navigateToTransferScreen: (loanId: Int) -> Unit, ) { composable { LoanAccountProfileScreen( onNavigateBack = onNavigateBack, navController = navController, + navigateToGeneral = navigateToGeneral, navigateToRepaymentSchedule = navigateToRepaymentSchedule, navigateToTransactions = navigateToTransactions, navigateToCharges = navigateToCharges, diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt index 9a3c30cede6..bf76fdbdfb0 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt @@ -55,6 +55,10 @@ import com.mifos.core.designsystem.icon.MifosIcons import com.mifos.core.designsystem.theme.AppColors import com.mifos.core.designsystem.theme.DesignToken import com.mifos.core.designsystem.theme.MifosTypography +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanAccountSummary +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanStatus +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations +import com.mifos.core.model.objects.account.loan.loanWithAssociations.SavingAccountCurrency import com.mifos.core.ui.components.MifosBreadcrumbNavBar import com.mifos.core.ui.components.MifosErrorComponent import com.mifos.core.ui.components.MifosProgressIndicator @@ -63,10 +67,6 @@ import com.mifos.core.ui.util.EventsEffect import com.mifos.core.ui.util.TextUtil import com.mifos.feature.loan.loanAccountProfile.components.LoanAccountProfileActionItem import com.mifos.feature.loan.loanAccountProfile.components.loanProfileActionItems -import com.mifos.room.entities.accounts.loans.LoanStatusEntity -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity -import com.mifos.room.entities.accounts.loans.LoansAccountSummaryEntity -import com.mifos.room.entities.accounts.savings.SavingAccountCurrencyEntity import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview import org.jetbrains.compose.ui.tooling.preview.PreviewParameter @@ -77,8 +77,9 @@ import template.core.base.designsystem.theme.KptTheme @Composable internal fun LoanAccountProfileScreen( onNavigateBack: () -> Unit, - approveLoan: (Int, LoanWithAssociationsEntity) -> Unit, - onRepaymentClick: (LoanWithAssociationsEntity) -> Unit, + approveLoan: (Int) -> Unit, + onRepaymentClick: (LoanWithAssociations) -> Unit, + navigateToGeneral: (Int) -> Unit, navigateToRepaymentSchedule: (Int) -> Unit, navigateToTransactions: (Int) -> Unit, navigateToCharges: (Int) -> Unit, @@ -99,12 +100,12 @@ internal fun LoanAccountProfileScreen( val account = state.loanAccount ?: return@EventsEffect when (event.action) { - LoanProfileAction.Approve -> approveLoan(account.id, account) + LoanProfileAction.Approve -> approveLoan(account.id ?: return@EventsEffect) LoanProfileAction.Repayment -> onRepaymentClick(account) LoanProfileAction.Transfer -> { - val account = state.loanAccount ?: return@EventsEffect + val account = state.loanAccount navigateToTransferScreen( - account.id, + account?.id ?: return@EventsEffect, ) } } @@ -113,6 +114,7 @@ internal fun LoanAccountProfileScreen( val loanId = state.loanAccount?.id ?: -1 when (event.detailItem) { + LoanAccountProfileActionItem.General -> navigateToGeneral(loanId) LoanAccountProfileActionItem.RepaymentSchedule -> navigateToRepaymentSchedule(loanId) LoanAccountProfileActionItem.Transactions -> navigateToTransactions(loanId) LoanAccountProfileActionItem.Charges -> navigateToCharges(loanId) @@ -220,7 +222,7 @@ private fun LoanAccountContent( @Composable private fun LoanAccountTopCard( - loanAccount: LoanWithAssociationsEntity, + loanAccount: LoanWithAssociations, statusUi: LoanStatusUiModel?, onClick: () -> Unit, modifier: Modifier = Modifier, @@ -228,8 +230,8 @@ private fun LoanAccountTopCard( val currencyCode = loanAccount.currency?.code val decimalPlaces = loanAccount.currency?.decimalPlaces - val balance = CurrencyFormatter.format(loanAccount.summary.totalOutstanding, currencyCode, decimalPlaces) - val arrears = CurrencyFormatter.format(loanAccount.summary.totalOverdue, currencyCode, decimalPlaces) + val balance = CurrencyFormatter.format(loanAccount.summary?.totalOutstanding, currencyCode, decimalPlaces) + val arrears = CurrencyFormatter.format(loanAccount.summary?.totalOverdue, currencyCode, decimalPlaces) val overpaid = CurrencyFormatter.format(loanAccount.totalOverpaid, currencyCode, decimalPlaces) MifosCard( @@ -373,21 +375,21 @@ private class LoanAccountPreviewProvider : PreviewParameterProvider get() = sequenceOf( LoanAccountState( - loanAccount = LoanWithAssociationsEntity( + loanAccount = LoanWithAssociations( id = 1, accountNo = "000000018", clientName = "MARIA", loanProductName = "PERSONAL", totalOverpaid = 0.0, - currency = SavingAccountCurrencyEntity( + currency = SavingAccountCurrency( code = "USD", decimalPlaces = 2, ), - summary = LoansAccountSummaryEntity( + summary = LoanAccountSummary( totalOutstanding = 1500.00, totalOverdue = 0.00, ), - status = LoanStatusEntity( + status = LoanStatus( active = true, pendingApproval = false, overpaid = false, diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt index f39fa23e9e7..4fc194c9515 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt @@ -29,10 +29,10 @@ import com.mifos.core.common.utils.DataState import com.mifos.core.data.repository.LoanAccountSummaryRepository import com.mifos.core.data.util.NetworkMonitor import com.mifos.core.designsystem.theme.AppColors +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanStatus +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.ui.util.BaseViewModel import com.mifos.feature.loan.loanAccountProfile.components.LoanAccountProfileActionItem -import com.mifos.room.entities.accounts.loans.LoanStatusEntity -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import kotlinx.coroutines.Job import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -156,7 +156,7 @@ internal class LoanAccountProfileViewModel( } } - private fun LoanStatusEntity?.toProfileStatus(): LoanProfileStatus { + private fun LoanStatus?.toProfileStatus(): LoanProfileStatus { if (this == null) return LoanProfileStatus.UNKNOWN return when { this.pendingApproval == true -> LoanProfileStatus.PENDING @@ -175,7 +175,7 @@ enum class LoanProfileStatus { } data class LoanAccountState( - val loanAccount: LoanWithAssociationsEntity? = null, + val loanAccount: LoanWithAssociations? = null, val dialogState: DialogState? = null, val networkConnection: Boolean = false, val statusUiModel: LoanStatusUiModel? = null, diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt index 8c7c6efd0ea..7f4417f0453 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt @@ -92,12 +92,12 @@ import com.mifos.core.designsystem.theme.AppColors import com.mifos.core.designsystem.theme.DesignToken import com.mifos.core.designsystem.theme.MifosTheme import com.mifos.core.designsystem.theme.MifosTypography +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanAccountSummary +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanStatus +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.ui.components.MifosBreadcrumbNavBar import com.mifos.core.ui.components.MifosProgressIndicator import com.mifos.core.ui.util.EventsEffect -import com.mifos.room.entities.accounts.loans.LoanStatusEntity -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity -import com.mifos.room.entities.accounts.loans.LoansAccountSummaryEntity import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview import org.jetbrains.compose.ui.tooling.preview.PreviewParameter @@ -113,9 +113,9 @@ internal fun LoanAccountSummaryScreenRoute( onRepaymentScheduleClicked: (loanId: Int) -> Unit, onDocumentsClicked: (loanId: Int) -> Unit, onChargesClicked: (loanId: Int) -> Unit, - approveLoan: (loadId: Int, loanWithAssociations: LoanWithAssociationsEntity) -> Unit, + approveLoan: (loadId: Int) -> Unit, disburseLoan: (loanId: Int) -> Unit, - onRepaymentClick: (loanWithAssociations: LoanWithAssociationsEntity) -> Unit, + onRepaymentClick: (loanWithAssociations: LoanWithAssociations) -> Unit, navController: NavController, viewModel: LoanAccountSummaryViewModel = koinViewModel(), ) { @@ -147,7 +147,7 @@ internal fun LoanAccountSummaryScreenRoute( } is LoanAccountSummaryEvent.NavigateToApproveLoan -> { - approveLoan(event.loanId, event.loanWithAssociations) + approveLoan(event.loanId) } is LoanAccountSummaryEvent.NavigateToDisburseLoan -> { @@ -261,7 +261,7 @@ private fun LoanAccountSummaryContent( Text( modifier = Modifier .fillMaxWidth(), - text = loanWithAssociations.clientName, + text = loanWithAssociations.clientName ?: "", style = KptTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, ) @@ -271,9 +271,9 @@ private fun LoanAccountSummaryContent( verticalAlignment = Alignment.CenterVertically, ) { val statusDescription = when { - loanWithAssociations.status.active == true -> "Active" - loanWithAssociations.status.pendingApproval == true -> "Pending Approval" - loanWithAssociations.status.waitingForDisbursal == true -> "Waiting for Disbursal" + loanWithAssociations.status?.active == true -> "Active" + loanWithAssociations.status?.pendingApproval == true -> "Pending Approval" + loanWithAssociations.status?.waitingForDisbursal == true -> "Waiting for Disbursal" else -> "Closed" } Canvas( @@ -285,15 +285,15 @@ private fun LoanAccountSummaryContent( onDraw = { drawCircle( color = when { - loanWithAssociations.status.active == true -> { + loanWithAssociations.status?.active == true -> { AppColors.loanIndicatorActive } - loanWithAssociations.status.pendingApproval == true -> { + loanWithAssociations.status?.pendingApproval == true -> { AppColors.loanIndicatorPending } - loanWithAssociations.status.waitingForDisbursal == true -> { + loanWithAssociations.status?.waitingForDisbursal == true -> { AppColors.loanIndicatorWaitingForDisbursal } @@ -306,7 +306,7 @@ private fun LoanAccountSummaryContent( ) Spacer(modifier = Modifier.width(DesignToken.spacing.mediumSmall)) Text( - text = loanWithAssociations.loanProductName, + text = loanWithAssociations.loanProductName ?: "", style = MifosTypography.bodyLarge, color = KptTheme.colorScheme.onSurface, ) @@ -324,7 +324,7 @@ private fun LoanAccountSummaryContent( Spacer(modifier = Modifier.width(KptTheme.spacing.xs)) IconButton( onClick = { - clipboardManager.setText(AnnotatedString(loanWithAssociations.accountNo)) + loanWithAssociations.accountNo?.let { clipboardManager.setText(AnnotatedString(it)) } onAction(LoanAccountSummaryAction.OnLoanIdCopied) }, modifier = Modifier.size(DesignToken.sizes.iconSmall), @@ -430,7 +430,7 @@ private fun LoanAccountSummaryContent( ) LoanSummaryFarApartTextItem( title = stringResource(Res.string.feature_loan_staff), - value = loanWithAssociations.loanOfficerName, + value = loanWithAssociations.loanOfficerName ?: "", ) } } @@ -438,17 +438,20 @@ private fun LoanAccountSummaryContent( LoanSummaryDataTable(state = state) - val primaryAction = loanWithAssociations.status.getPrimaryAction() + val primaryAction = loanWithAssociations.status?.getPrimaryAction() val buttonText = when (primaryAction) { LoanPrimaryAction.MAKE_REPAYMENT -> stringResource(Res.string.feature_loan_make_Repayment) LoanPrimaryAction.APPROVE_LOAN -> stringResource(Res.string.feature_loan_approve_loan) LoanPrimaryAction.DISBURSE_LOAN -> stringResource(Res.string.feature_loan_disburse_loan) LoanPrimaryAction.OVERPAID -> stringResource(Res.string.feature_loan_transfer_funds) LoanPrimaryAction.CLOSED -> stringResource(Res.string.feature_loan_closed) + else -> { + "" + } } Button( - enabled = loanWithAssociations.status.isButtonActive(), + enabled = loanWithAssociations.status?.isButtonActive() ?: false, modifier = Modifier .fillMaxWidth() .height(DesignToken.sizes.buttonHeightMedium), @@ -460,6 +463,7 @@ private fun LoanAccountSummaryContent( LoanPrimaryAction.DISBURSE_LOAN -> onAction(LoanAccountSummaryAction.OnDisburseLoan) LoanPrimaryAction.OVERPAID -> onAction(LoanAccountSummaryAction.NavigateToLoanTransfer) LoanPrimaryAction.CLOSED -> { } + else -> {} } }, colors = ButtonDefaults.buttonColors(containerColor = KptTheme.colorScheme.primary), @@ -700,13 +704,13 @@ private fun LoanSummaryDropdown( } } -private fun LoanStatusEntity.isButtonActive(): Boolean { +private fun LoanStatus.isButtonActive(): Boolean { return active == true || pendingApproval == true || waitingForDisbursal == true || overpaid == true } private class LoanAccountSummaryPreviewProvider : PreviewParameterProvider { - private val demoSummary = LoansAccountSummaryEntity( + private val demoSummary = LoanAccountSummary( loanId = 12345, principalDisbursed = 10000.0, principalPaid = 4000.0, @@ -750,9 +754,9 @@ private class LoanAccountSummaryPreviewProvider : dialogState = LoanAccountSummaryState.DialogState.Error("Could not fetch summary"), ), LoanAccountSummaryState( - loanWithAssociations = LoanWithAssociationsEntity( + loanWithAssociations = LoanWithAssociations( accountNo = "90927493938", - status = LoanStatusEntity( + status = LoanStatus( closedObligationsMet = true, ), clientName = "Pronay sarker", @@ -763,9 +767,9 @@ private class LoanAccountSummaryPreviewProvider : dialogState = null, ), LoanAccountSummaryState( - loanWithAssociations = LoanWithAssociationsEntity( + loanWithAssociations = LoanWithAssociations( accountNo = "12345678901", - status = LoanStatusEntity( + status = LoanStatus( active = true, ), clientName = "John Doe", diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreenRoute.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreenRoute.kt index 9320f65993d..2f94c12f309 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreenRoute.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreenRoute.kt @@ -12,7 +12,7 @@ package com.mifos.feature.loan.loanAccountSummary import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import kotlinx.serialization.Serializable @Serializable @@ -31,9 +31,9 @@ fun NavGraphBuilder.loanAccountSummary( onRepaymentScheduleClicked: (loanId: Int) -> Unit, onDocumentsClicked: (Int) -> Unit, onChargesClicked: (Int) -> Unit, - approveLoan: (loanId: Int, loanWithAssociations: LoanWithAssociationsEntity) -> Unit, + approveLoan: (loanId: Int) -> Unit, disburseLoan: (Int) -> Unit, - onRepaymentClick: (LoanWithAssociationsEntity) -> Unit, + onRepaymentClick: (LoanWithAssociations) -> Unit, navController: NavController, ) { composable { diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryViewModel.kt index 8abb48bc059..c3a65e7e1e9 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryViewModel.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryViewModel.kt @@ -18,9 +18,9 @@ import com.mifos.core.common.utils.CurrencyFormatter import com.mifos.core.common.utils.DataState import com.mifos.core.common.utils.DateHelper import com.mifos.core.data.repository.LoanAccountSummaryRepository +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanStatus +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.ui.util.BaseViewModel -import com.mifos.room.entities.accounts.loans.LoanStatusEntity -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString @@ -138,7 +138,7 @@ internal class LoanAccountSummaryViewModel( } is DataState.Success -> { - val loan: LoanWithAssociationsEntity? = dataState.data + val loan: LoanWithAssociations? = dataState.data if (loan != null) { fillLoanSummary(loan) } else { @@ -167,7 +167,7 @@ internal class LoanAccountSummaryViewModel( } } - private fun fillLoanSummary(loan: LoanWithAssociationsEntity) { + private fun fillLoanSummary(loan: LoanWithAssociations) { val actualDisbursementDate = formatActualDisbursementDate( loan.timeline?.actualDisbursementDate, ) @@ -271,7 +271,7 @@ internal class LoanAccountSummaryViewModel( } } - private fun LoanStatusEntity.shouldInflateLoanSummary(): Boolean { + private fun LoanStatus.shouldInflateLoanSummary(): Boolean { return active == true || closedObligationsMet == true || overpaid == true } @@ -292,7 +292,7 @@ internal class LoanAccountSummaryViewModel( } data class LoanAccountSummaryState( - val loanWithAssociations: LoanWithAssociationsEntity? = null, + val loanWithAssociations: LoanWithAssociations? = null, val dialogState: DialogState? = null, val showLoanIdCopiedMessage: Boolean = false, val openDropdown: Boolean = false, @@ -357,11 +357,11 @@ sealed interface LoanAccountSummaryEvent { data class NavigateToCharges(val loanId: Int) : LoanAccountSummaryEvent data class NavigateToApproveLoan( val loanId: Int, - val loanWithAssociations: LoanWithAssociationsEntity, + val loanWithAssociations: LoanWithAssociations, ) : LoanAccountSummaryEvent data class NavigateToDisburseLoan(val loanId: Int) : LoanAccountSummaryEvent - data class NavigateToMakeRepayment(val loanWithAssociations: LoanWithAssociationsEntity) : + data class NavigateToMakeRepayment(val loanWithAssociations: LoanWithAssociations) : LoanAccountSummaryEvent } @@ -387,7 +387,7 @@ sealed interface LoanAccountSummaryAction { /** * Extension function to determine the primary action button for a loan based on its status. */ -internal fun LoanStatusEntity.getPrimaryAction(): LoanPrimaryAction { +internal fun LoanStatus.getPrimaryAction(): LoanPrimaryAction { return when { active == true -> LoanPrimaryAction.MAKE_REPAYMENT pendingApproval == true -> LoanPrimaryAction.APPROVE_LOAN diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalNavigation.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalNavigation.kt new file mode 100644 index 00000000000..3ec928bc01a --- /dev/null +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalNavigation.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2026 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/mifos-x-field-officer-app/blob/master/LICENSE.md + */ +package com.mifos.feature.loan.loanApproval + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import kotlinx.serialization.Serializable + +@Serializable +data class LoanApprovalRoute( + val loanId: Int, +) + +fun NavGraphBuilder.loanApprovalDestination( + onBackPressed: () -> Unit, +) { + composable { + LoanAccountApprovalScreen( + navigateBack = onBackPressed, + ) + } +} + +fun NavController.navigateToLoanApprovalScreen( + loanId: Int, +) { + navigate(LoanApprovalRoute(loanId = loanId)) +} diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalScreen.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalScreen.kt index 7d8add210f5..a1794ab5aae 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalScreen.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalScreen.kt @@ -57,9 +57,9 @@ import com.mifos.core.designsystem.component.MifosOutlinedTextField import com.mifos.core.designsystem.component.MifosScaffold import com.mifos.core.designsystem.theme.DesignToken import com.mifos.core.model.objects.account.loan.LoanApproval +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.network.GenericResponse import com.mifos.core.ui.components.MifosProgressIndicator -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview @@ -90,7 +90,7 @@ internal fun LoanAccountApprovalScreen( @Composable internal fun LoanAccountApprovalScreen( uiState: LoanAccountApprovalUiState, - loanWithAssociations: LoanWithAssociationsEntity?, + loanWithAssociations: LoanWithAssociations?, navigateBack: () -> Unit, onLoanApprove: (loanApproval: LoanApproval) -> Unit, ) { @@ -156,7 +156,7 @@ internal fun LoanAccountApprovalScreen( @OptIn(ExperimentalMaterial3Api::class, ExperimentalTime::class) @Composable private fun LoanAccountApprovalContent( - loanWithAssociations: LoanWithAssociationsEntity?, + loanWithAssociations: LoanWithAssociations?, onLoanApprove: (loanApproval: LoanApproval) -> Unit, ) { var approvedAmount by rememberSaveable { @@ -375,7 +375,7 @@ private fun PreviewLoanAccountApprovalScreen( ) { LoanAccountApprovalScreen( uiState = loanAccountApprovalUiState, - loanWithAssociations = LoanWithAssociationsEntity(), + loanWithAssociations = LoanWithAssociations(), navigateBack = { }, ) { } diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalViewModel.kt index e981f1e5600..aa76f98edf2 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalViewModel.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanApproval/LoanAccountApprovalViewModel.kt @@ -16,6 +16,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.mifos.core.common.utils.DataState import com.mifos.core.data.repository.LoanAccountApprovalRepository +import com.mifos.core.model.objects.account.loan.LoanApproval import com.mifos.room.entities.accounts.loans.LoanApprovalData import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -38,7 +39,7 @@ class LoanAccountApprovalViewModel( var loanId = loanAccountData.loanID var loanWithAssociations = loanAccountData.loanWithAssociations - fun approveLoan(loanApproval: com.mifos.core.model.objects.account.loan.LoanApproval?) { + fun approveLoan(loanApproval: LoanApproval?) { viewModelScope.launch { repository.approveLoan(loanId, loanApproval).collect { dataState -> when (dataState) { diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreenRoute.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreenRoute.kt index 9b16249fbee..11e8e69fa46 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreenRoute.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentScreenRoute.kt @@ -12,7 +12,7 @@ package com.mifos.feature.loan.loanRepayment import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import kotlinx.serialization.Serializable @Serializable @@ -34,14 +34,14 @@ fun NavGraphBuilder.loanRepaymentScreen( } } -fun NavController.navigateToLoanRepaymentScreen(loanWithAssociations: LoanWithAssociationsEntity) { +fun NavController.navigateToLoanRepaymentScreen(loanWithAssociations: LoanWithAssociations) { navigate( LoanRepaymentScreenRoute( - clientName = loanWithAssociations.clientName, - loanId = loanWithAssociations.id, - loanAccountNumber = loanWithAssociations.accountNo, - loanProductName = loanWithAssociations.loanProductName, - amountInArrears = loanWithAssociations.summary.totalOverdue, + clientName = loanWithAssociations.clientName ?: "", + loanId = loanWithAssociations.id ?: 0, + loanAccountNumber = loanWithAssociations.accountNo ?: "", + loanProductName = loanWithAssociations.loanProductName ?: "", + amountInArrears = loanWithAssociations.summary?.totalOverdue, ), ) } diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt index a6136610e7e..6a42d6efb3d 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepayment/LoanRepaymentViewModel.kt @@ -74,11 +74,11 @@ class LoanRepaymentViewModel( return@collect } _loanDetailsState.value = _loanDetailsState.value.copy( - loanId = loanWithAssociations.id, - clientName = loanWithAssociations.clientName, - loanProductName = loanWithAssociations.loanProductName, - amountInArrears = loanWithAssociations.summary.totalOverdue, - loanAccountNumber = loanWithAssociations.accountNo, + loanId = loanWithAssociations.id ?: 0, + clientName = loanWithAssociations.clientName ?: "", + loanProductName = loanWithAssociations.loanProductName ?: "", + amountInArrears = loanWithAssociations.summary?.totalOverdue, + loanAccountNumber = loanWithAssociations.accountNo ?: "", ) checkDatabaseLoanRepaymentByLoanId() } diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleScreen.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleScreen.kt index 66a5cfe3046..9720219694d 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleScreen.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleScreen.kt @@ -76,6 +76,7 @@ import com.mifos.core.designsystem.icon.MifosIcons import com.mifos.core.designsystem.theme.DesignToken import com.mifos.core.designsystem.theme.MifosTheme import com.mifos.core.designsystem.theme.MifosTypography +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.ui.components.MifosDetailsCard import com.mifos.core.ui.components.MifosProgressIndicator import com.mifos.core.ui.util.pdf.Orientation @@ -84,7 +85,6 @@ import com.mifos.core.ui.util.pdf.PageSize import com.mifos.core.ui.util.pdf.rememberPdfGenerator import com.mifos.feature.loan.loanRepaymentSchedule.pdf.RepaymentScheduleHtmlGenerator import com.mifos.feature.loan.loanRepaymentSchedule.pdf.RepaymentSchedulePdfStrings -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.stringResource @@ -177,7 +177,7 @@ internal fun LoanRepaymentScheduleScreenContent( ) } - is DataState.Success -> { + is DataState.Success -> { state.repaymentScheduleTableData?.let { data -> LoanRepaymentScheduleContent( tableData = data, diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleViewModel.kt index bf83a89ab51..a1037ac7c33 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleViewModel.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanRepaymentSchedule/LoanRepaymentScheduleViewModel.kt @@ -26,8 +26,8 @@ import com.mifos.core.common.utils.DataState.Success import com.mifos.core.common.utils.DateHelper import com.mifos.core.data.repository.LoanRepaymentScheduleRepository import com.mifos.core.model.objects.account.loan.Period +import com.mifos.core.model.objects.account.loan.loanWithAssociations.LoanWithAssociations import com.mifos.core.ui.util.BaseViewModel -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString @@ -132,26 +132,26 @@ class LoanRepaymentScheduleViewModel( } } - private fun mapToTableData(loan: LoanWithAssociationsEntity): LoanRepaymentScheduleState.RepaymentScheduleTableData { - val currencyCode = loan.currency.code - val maxDigits = loan.currency.decimalPlaces + private fun mapToTableData(loan: LoanWithAssociations): LoanRepaymentScheduleState.RepaymentScheduleTableData { + val currencyCode = loan.currency?.code + val maxDigits = loan.currency?.decimalPlaces - val periods = loan.repaymentSchedule.periods?.filter { it.period != null } ?: emptyList() + val periods = loan.repaymentSchedule?.periods?.filter { it.period != null } ?: emptyList() return LoanRepaymentScheduleState.RepaymentScheduleTableData( - accountNo = loan.accountNo, - clientName = loan.clientName, - productName = loan.loanProductName, - disbursementDate = loan.timeline.actualDisbursementDate?.filterNotNull()?.let { + accountNo = loan.accountNo ?: "", + clientName = loan.clientName ?: "", + productName = loan.loanProductName ?: "", + disbursementDate = loan.timeline?.actualDisbursementDate?.filterNotNull()?.let { DateHelper.getDateAsString(it) } ?: "", loanAmount = CurrencyFormatter.format( - loan.summary.principalDisbursed, + loan.summary?.principalDisbursed, currencyCode, maxDigits, ), principalPaid = CurrencyFormatter.format( - loan.summary.principalPaid, + loan.summary?.principalPaid, currencyCode, maxDigits, ), @@ -292,7 +292,7 @@ data class LoanRepaymentScheduleState( val basicDetails: Map = emptyMap(), val repaymentScheduleTableData: RepaymentScheduleTableData? = null, val dialogState: DialogState? = null, - val dataState: DataState = Loading, + val dataState: DataState = Loading, ) { /** * Represents the possible dialog states. diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt index a13becbebc7..32e218c33f8 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt @@ -42,11 +42,11 @@ class LoanTransactionsViewModel( is DataState.Success -> { val loanWithAssociations = state.data - val currencyCode = loanWithAssociations.currency.code - val maxDigits = loanWithAssociations.currency.decimalPlaces + val currencyCode = loanWithAssociations.currency?.code + val maxDigits = loanWithAssociations.currency?.decimalPlaces val transactionsData = - loanWithAssociations.transactions.mapIndexed { index, transaction -> + loanWithAssociations.transactions?.mapIndexed { index, transaction -> LoanTransactionsUiState.LoanTransactionsTableData.TransactionRowData( number = (index + 1).toString(), @@ -99,9 +99,11 @@ class LoanTransactionsViewModel( _loanTransactionsUiState.value = LoanTransactionsUiState.ShowLoanTransaction( - transactionsTableData = LoanTransactionsUiState.LoanTransactionsTableData( - transactions = transactionsData, - ), + transactionsTableData = transactionsData?.let { + LoanTransactionsUiState.LoanTransactionsTableData( + transactions = it, + ) + }, ) } } diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/navigation/LoanNavigation.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/navigation/LoanNavigation.kt index 0c5c16ecf73..ca050dfd5f5 100644 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/navigation/LoanNavigation.kt +++ b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/navigation/LoanNavigation.kt @@ -11,16 +11,16 @@ package com.mifos.feature.loan.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType -import androidx.navigation.compose.composable -import androidx.navigation.navArgument import com.mifos.core.common.utils.Constants import com.mifos.feature.loan.amountTransfer.amountTransferScreen import com.mifos.feature.loan.amountTransfer.navigateToTransferScreen import com.mifos.feature.loan.createLoanReschedules.loanRescheduleFormScreen +import com.mifos.feature.loan.loanAccountGeneral.loanAccountGeneralDestination +import com.mifos.feature.loan.loanAccountGeneral.navigateToLoanAccountGeneralScreen import com.mifos.feature.loan.loanAccountProfile.loanProfileAccountDestination import com.mifos.feature.loan.loanAccountSummary.loanAccountSummary -import com.mifos.feature.loan.loanApproval.LoanAccountApprovalScreen +import com.mifos.feature.loan.loanApproval.loanApprovalDestination +import com.mifos.feature.loan.loanApproval.navigateToLoanApprovalScreen import com.mifos.feature.loan.loanCharge.loanChargeScreen import com.mifos.feature.loan.loanCharge.navigateToLoanChargesScreen import com.mifos.feature.loan.loanDisbursement.loanDisbursementScreen @@ -34,7 +34,6 @@ import com.mifos.feature.loan.loanReschedules.navigateToLoanReschedulesScreen import com.mifos.feature.loan.loanTransaction.loanTransactionScreen import com.mifos.feature.loan.loanTransaction.navigateToLoanTransactionScreen import com.mifos.feature.loan.newLoanAccount.newLoanAccountDestination -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity fun NavGraphBuilder.loanDestination( navController: NavController, @@ -59,9 +58,6 @@ fun NavGraphBuilder.loanDestination( loanDisbursementScreen { navController.popBackStack() } - loanApprovalScreen { - navController.popBackStack() - } loanRepaymentSchedule { navController.popBackStack() } @@ -80,12 +76,16 @@ fun NavGraphBuilder.loanDestination( onLoanCreated = onLoanCreated, navController = navController, ) + loanAccountGeneralDestination( + navController = navController, + ) loanProfileAccountDestination( onNavigateBack = navController::popBackStack, navController = navController, approveLoan = navController::navigateToLoanApprovalScreen, onRepaymentClick = navController::navigateToLoanRepaymentScreen, + navigateToGeneral = navController::navigateToLoanAccountGeneralScreen, navigateToRepaymentSchedule = navController::navigateToLoanRepaymentScheduleScreen, navigateToTransactions = navController::navigateToLoanTransactionScreen, navigateToCharges = navController::navigateToLoanChargesScreen, @@ -113,26 +113,12 @@ fun NavGraphBuilder.loanDestination( navController = navController, onBackPressed = navController::popBackStack, ) -} -fun NavGraphBuilder.loanApprovalScreen( - onBackPressed: () -> Unit, -) { - composable( - route = LoanScreens.LoanApprovalScreen.route, - arguments = listOf( - navArgument(name = "arg", builder = { type = NavType.StringType }), - ), - ) { - LoanAccountApprovalScreen( - navigateBack = onBackPressed, - ) - } -} + loanAccountGeneralDestination( + navController = navController, + ) -fun NavController.navigateToLoanApprovalScreen( - loanId: Int, - loanWithAssociations: LoanWithAssociationsEntity, -) { - navigate(LoanScreens.LoanApprovalScreen.argument(loanId, loanWithAssociations)) + loanApprovalDestination( + onBackPressed = navController::popBackStack, + ) } diff --git a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/navigation/LoanScreens.kt b/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/navigation/LoanScreens.kt deleted file mode 100644 index b9378bfd108..00000000000 --- a/feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/navigation/LoanScreens.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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/mifos-x-field-officer-app/blob/master/LICENSE.md - */ -package com.mifos.feature.loan.navigation - -import com.mifos.room.entities.accounts.loans.LoanApprovalData -import com.mifos.room.entities.accounts.loans.LoanWithAssociationsEntity -import kotlinx.serialization.json.Json - -// TODO : Migrate it to type safe while implementing this screen -sealed class LoanScreens(val route: String) { - - data object LoanApprovalScreen : LoanScreens("loan_approval_screen/{arg}") { - fun argument(loanAccountNumber: Int, loanWithAssociations: LoanWithAssociationsEntity): String { - val arg = LoanApprovalData(loanAccountNumber, loanWithAssociations) - val loanApprovalDataInJson = Json.encodeToString(LoanApprovalData.serializer(), arg) - - return "loan_approval_screen/$loanApprovalDataInJson" - } - } -}