Skip to content

Commit 7b9e75a

Browse files
committed
feat(loan): implement create guarantor screen
1 parent 928fc44 commit 7b9e75a

28 files changed

Lines changed: 1439 additions & 23 deletions

File tree

core/data/src/commonMain/kotlin/com/mifos/core/data/di/RepositoryModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import com.mifos.core.data.repository.LoanAccountSummaryRepository
4747
import com.mifos.core.data.repository.LoanChargeFormRepository
4848
import com.mifos.core.data.repository.LoanChargeOffRepository
4949
import com.mifos.core.data.repository.LoanChargeRepository
50+
import com.mifos.core.data.repository.LoanCreateGuarantorRepository
5051
import com.mifos.core.data.repository.LoanRepaymentRepository
5152
import com.mifos.core.data.repository.LoanRepaymentScheduleRepository
5253
import com.mifos.core.data.repository.LoanReschedulesRepository
@@ -117,6 +118,7 @@ import com.mifos.core.data.repositoryImp.LoanAccountSummaryRepositoryImp
117118
import com.mifos.core.data.repositoryImp.LoanChargeFormRepositoryImp
118119
import com.mifos.core.data.repositoryImp.LoanChargeOffRepositoryImpl
119120
import com.mifos.core.data.repositoryImp.LoanChargeRepositoryImp
121+
import com.mifos.core.data.repositoryImp.LoanCreateGuarantorRepositoryImp
120122
import com.mifos.core.data.repositoryImp.LoanRepaymentRepositoryImp
121123
import com.mifos.core.data.repositoryImp.LoanRepaymentScheduleRepositoryImp
122124
import com.mifos.core.data.repositoryImp.LoanReschedulesRepositoryImpl
@@ -200,6 +202,7 @@ val RepositoryModule = module {
200202
singleOf(::LoanTransactionsRepositoryImp) bind LoanTransactionsRepository::class
201203
singleOf(::LoanReschedulesRepositoryImpl) bind LoanReschedulesRepository::class
202204
singleOf(::LoanChargeOffRepositoryImpl) bind LoanChargeOffRepository::class
205+
singleOf(::LoanCreateGuarantorRepositoryImp) bind LoanCreateGuarantorRepository::class
203206

204207
// Account Transfer
205208
singleOf(::AmountTransferRepositoryImp) bind AmountTransferRepository::class
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mifos-x-field-officer-app/blob/master/LICENSE.md
9+
*/
10+
package com.mifos.core.data.mappers.loan
11+
12+
import com.mifos.core.model.objects.account.loan.guarantor.CreateGuarantorInput
13+
import com.mifos.core.model.objects.account.loan.guarantor.CreateGuarantorResponseDto
14+
import com.mifos.core.model.objects.account.loan.guarantor.CreatedGuarantor
15+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorAccountTemplate
16+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorRelationshipOption
17+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorTemplate
18+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorType
19+
import com.mifos.core.network.dto.loans.GuarantorAccountTemplateDto
20+
import com.mifos.core.network.dto.loans.GuarantorRelationshipOptionDto
21+
import com.mifos.core.network.dto.loans.GuarantorRequestDto
22+
import com.mifos.core.network.dto.loans.GuarantorTemplateDto
23+
import com.mifos.core.network.dto.loans.GuarantorTypeDto
24+
25+
fun GuarantorTemplateDto.toDomain(): GuarantorTemplate =
26+
GuarantorTemplate(
27+
allowedClientRelationshipTypes = allowedClientRelationshipTypes.map { it.toDomain() },
28+
)
29+
30+
fun GuarantorRelationshipOptionDto.toDomain(): GuarantorRelationshipOption =
31+
GuarantorRelationshipOption(
32+
id = id,
33+
name = name,
34+
)
35+
36+
fun CreateGuarantorResponseDto.toDomain(): CreatedGuarantor =
37+
CreatedGuarantor(
38+
resourceId = resourceId,
39+
loanId = loanId,
40+
officeId = officeId,
41+
)
42+
43+
fun CreateGuarantorInput.toDto(): GuarantorRequestDto {
44+
return GuarantorRequestDto(
45+
clientRelationshipTypeId = clientRelationshipTypeId,
46+
dateFormat = dateFormat,
47+
entityId = entityId,
48+
guarantorTypeId = guarantorTypeId,
49+
locale = locale,
50+
firstname = firstname,
51+
lastname = lastname,
52+
dateOfBirth = dateOfBirth,
53+
addressLine1 = addressLine1,
54+
addressLine2 = addressLine2,
55+
city = city,
56+
zip = zip,
57+
mobileNumber = mobileNumber,
58+
housePhoneNumber = housePhoneNumber,
59+
)
60+
}
61+
62+
fun GuarantorAccountTemplateDto.toDomain(): GuarantorAccountTemplate {
63+
return GuarantorAccountTemplate(
64+
guarantorType = guarantorType.toDomain(),
65+
status = status,
66+
externalGuarantor = externalGuarantor,
67+
existingGroup = existingGroup,
68+
existingClient = existingClient,
69+
staffMember = staffMember,
70+
)
71+
}
72+
73+
fun GuarantorTypeDto.toDomain(): GuarantorType {
74+
return GuarantorType(
75+
id = id,
76+
code = code,
77+
value = value,
78+
)
79+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mifos-x-field-officer-app/blob/master/LICENSE.md
9+
*/
10+
package com.mifos.core.data.repository
11+
12+
import com.mifos.core.common.utils.DataState
13+
import com.mifos.core.model.objects.account.loan.guarantor.CreateGuarantorInput
14+
import com.mifos.core.model.objects.account.loan.guarantor.CreatedGuarantor
15+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorAccountTemplate
16+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorTemplate
17+
18+
interface LoanCreateGuarantorRepository {
19+
20+
suspend fun getGuarantorTemplate(loanId: Int): DataState<GuarantorTemplate>
21+
22+
suspend fun createGuarantor(
23+
loanId: Int,
24+
createGuarantorInput: CreateGuarantorInput,
25+
): DataState<CreatedGuarantor>
26+
27+
suspend fun getGuarantorAccountTemplate(
28+
loanId: Int,
29+
clientId: Int,
30+
): DataState<GuarantorAccountTemplate>
31+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mifos-x-field-officer-app/blob/master/LICENSE.md
9+
*/
10+
package com.mifos.core.data.repositoryImp
11+
12+
import com.mifos.core.common.utils.DataState
13+
import com.mifos.core.data.mappers.loan.toDomain
14+
import com.mifos.core.data.mappers.loan.toDto
15+
import com.mifos.core.data.repository.LoanCreateGuarantorRepository
16+
import com.mifos.core.data.util.NetworkMonitor
17+
import com.mifos.core.data.util.runAsDataState
18+
import com.mifos.core.model.objects.account.loan.guarantor.CreateGuarantorInput
19+
import com.mifos.core.model.objects.account.loan.guarantor.CreatedGuarantor
20+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorAccountTemplate
21+
import com.mifos.core.model.objects.account.loan.guarantor.GuarantorTemplate
22+
import com.mifos.core.network.datamanager.DataManagerLoan
23+
import kotlinx.coroutines.CoroutineDispatcher
24+
25+
class LoanCreateGuarantorRepositoryImp(
26+
private val dataManagerLoan: DataManagerLoan,
27+
private val ioDispatcher: CoroutineDispatcher,
28+
private val networkMonitor: NetworkMonitor,
29+
) : LoanCreateGuarantorRepository {
30+
31+
override suspend fun getGuarantorTemplate(loanId: Int): DataState<GuarantorTemplate> {
32+
return runAsDataState(
33+
networkMonitor,
34+
ioDispatcher,
35+
) {
36+
dataManagerLoan.getGuarantorTemplate(loanId).toDomain()
37+
}
38+
}
39+
40+
override suspend fun createGuarantor(
41+
loanId: Int,
42+
createGuarantorInput: CreateGuarantorInput,
43+
): DataState<CreatedGuarantor> {
44+
return runAsDataState(
45+
networkMonitor,
46+
ioDispatcher,
47+
) {
48+
dataManagerLoan.createGuarantor(loanId, createGuarantorInput.toDto()).toDomain()
49+
}
50+
}
51+
52+
override suspend fun getGuarantorAccountTemplate(
53+
loanId: Int,
54+
clientId: Int,
55+
): DataState<GuarantorAccountTemplate> {
56+
return runAsDataState(
57+
networkMonitor,
58+
ioDispatcher,
59+
) {
60+
dataManagerLoan
61+
.getGuarantorAccountTemplate(
62+
loanId = loanId,
63+
clientId = clientId,
64+
)
65+
.toDomain()
66+
}
67+
}
68+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mifos-x-field-officer-app/blob/master/LICENSE.md
9+
*/
10+
package com.mifos.core.designsystem.component
11+
12+
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.foundation.text.KeyboardOptions
14+
import androidx.compose.material3.DropdownMenuItem
15+
import androidx.compose.material3.ExperimentalMaterial3Api
16+
import androidx.compose.material3.ExposedDropdownMenuAnchorType
17+
import androidx.compose.material3.ExposedDropdownMenuBox
18+
import androidx.compose.material3.ExposedDropdownMenuDefaults
19+
import androidx.compose.material3.OutlinedTextField
20+
import androidx.compose.material3.OutlinedTextFieldDefaults
21+
import androidx.compose.material3.Text
22+
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.getValue
24+
import androidx.compose.runtime.mutableStateOf
25+
import androidx.compose.runtime.remember
26+
import androidx.compose.runtime.setValue
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.draw.clip
29+
import androidx.compose.ui.text.input.ImeAction
30+
import com.mifos.core.designsystem.theme.DesignToken
31+
import com.mifos.core.designsystem.theme.MifosTypography
32+
import template.core.base.designsystem.theme.KptTheme
33+
34+
@OptIn(ExperimentalMaterial3Api::class)
35+
@Composable
36+
fun MifosSearchableDropdown(
37+
value: String,
38+
onValueChanged: (String) -> Unit,
39+
onOptionSelected: (Int, String) -> Unit,
40+
options: List<String>,
41+
modifier: Modifier = Modifier
42+
.clip(DesignToken.shapes.medium)
43+
.fillMaxWidth(),
44+
label: String? = null,
45+
enabled: Boolean = true,
46+
errorMessage: String? = null,
47+
) {
48+
var expanded by remember { mutableStateOf(false) }
49+
50+
ExposedDropdownMenuBox(
51+
expanded = expanded && enabled && options.isNotEmpty(),
52+
onExpandedChange = {
53+
if (enabled) {
54+
expanded = !expanded
55+
}
56+
},
57+
) {
58+
OutlinedTextField(
59+
value = value,
60+
onValueChange = {
61+
onValueChanged(it)
62+
expanded = true
63+
},
64+
enabled = enabled,
65+
readOnly = false,
66+
isError = errorMessage != null,
67+
supportingText = {
68+
errorMessage?.let {
69+
Text(
70+
text = it,
71+
color = KptTheme.colorScheme.error,
72+
)
73+
}
74+
},
75+
label = {
76+
label?.let {
77+
Text(text = it)
78+
}
79+
},
80+
modifier = modifier.menuAnchor(
81+
type = ExposedDropdownMenuAnchorType.PrimaryEditable,
82+
enabled = enabled,
83+
),
84+
shape = DesignToken.shapes.medium,
85+
colors = OutlinedTextFieldDefaults.colors(
86+
focusedBorderColor = KptTheme.colorScheme.secondaryContainer,
87+
unfocusedBorderColor = KptTheme.colorScheme.secondaryContainer,
88+
),
89+
maxLines = 1,
90+
textStyle = MifosTypography.bodyLarge,
91+
keyboardOptions = KeyboardOptions.Default.copy(
92+
imeAction = ImeAction.Done,
93+
),
94+
trailingIcon = {
95+
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
96+
},
97+
)
98+
99+
ExposedDropdownMenu(
100+
expanded = expanded && options.isNotEmpty(),
101+
onDismissRequest = {
102+
expanded = false
103+
},
104+
) {
105+
options.forEachIndexed { index, item ->
106+
DropdownMenuItem(
107+
text = {
108+
Text(text = item)
109+
},
110+
onClick = {
111+
expanded = false
112+
onValueChanged(item)
113+
onOptionSelected(index, item)
114+
},
115+
)
116+
}
117+
}
118+
}
119+
}

core/domain/src/commonMain/kotlin/com/mifos/core/domain/di/UseCaseModule.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ import com.mifos.core.domain.useCases.ValidateServerEndPointUseCase
8989
import com.mifos.core.domain.useCases.ValidateServerPortUseCase
9090
import com.mifos.core.domain.useCases.ValidateServerProtocolUseCase
9191
import com.mifos.core.domain.useCases.ValidateServerTenantUseCase
92+
import com.mifos.core.domain.useCases.createGuarantor.CreateGuarantorUseCase
93+
import com.mifos.core.domain.useCases.createGuarantor.GetGuarantorTemplateUseCase
9294
import com.mifos.core.domain.useCases.loanChargeOff.GetLoanChargeOffTemplateUseCase
9395
import com.mifos.core.domain.useCases.loanChargeOff.LoanChargeOffUseCase
9496
import org.koin.core.module.dsl.factoryOf
@@ -104,6 +106,7 @@ val UseCaseModule = module {
104106
factoryOf(::ApproveCheckerUseCase)
105107
factoryOf(::ApproveSavingsApplicationUseCase)
106108
factoryOf(::CreateChargesUseCase)
109+
factoryOf(::CreateGuarantorUseCase)
107110
factoryOf(::CreateClientIdentifierUseCase)
108111
factoryOf(::CalculateLoanScheduleUseCase)
109112
factoryOf(::CreateGroupLoansAccountUseCase)
@@ -136,6 +139,7 @@ val UseCaseModule = module {
136139
factoryOf(::GetDocumentsListUseCase)
137140
factoryOf(::GetGroupLoansAccountTemplateUseCase)
138141
factoryOf(::GetGroupSavingsAccountTemplateByProductUseCase)
142+
factoryOf(::GetGuarantorTemplateUseCase)
139143
factoryOf(::GetGroupsByCenterUseCase)
140144
factoryOf(::GetGroupsByOfficeUseCase)
141145
factoryOf(::GetIndividualCollectionSheetUseCase)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2026 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mifos-x-field-officer-app/blob/master/LICENSE.md
9+
*/
10+
package com.mifos.core.domain.useCases.createGuarantor
11+
12+
import com.mifos.core.common.utils.DataState
13+
import com.mifos.core.data.repository.LoanCreateGuarantorRepository
14+
import com.mifos.core.model.objects.account.loan.guarantor.CreateGuarantorInput
15+
import com.mifos.core.model.objects.account.loan.guarantor.CreatedGuarantor
16+
17+
class CreateGuarantorUseCase(
18+
private val repository: LoanCreateGuarantorRepository,
19+
) {
20+
suspend operator fun invoke(
21+
loanId: Int,
22+
createGuarantorInput: CreateGuarantorInput,
23+
): DataState<CreatedGuarantor> = repository.createGuarantor(loanId, createGuarantorInput)
24+
}

0 commit comments

Comments
 (0)