diff --git a/core/data/src/main/java/com/mifos/core/data/repository/CreateNewClientRepository.kt b/core/data/src/main/java/com/mifos/core/data/repository/CreateNewClientRepository.kt index c2066f9977c..26e76a6f7d4 100644 --- a/core/data/src/main/java/com/mifos/core/data/repository/CreateNewClientRepository.kt +++ b/core/data/src/main/java/com/mifos/core/data/repository/CreateNewClientRepository.kt @@ -13,6 +13,8 @@ import com.mifos.core.objects.client.Client import com.mifos.core.objects.client.ClientPayload import com.mifos.core.objects.organisation.Office import com.mifos.core.objects.organisation.Staff +import com.mifos.core.objects.templates.clients.AddressConfiguration +import com.mifos.core.objects.templates.clients.AddressTemplate import com.mifos.core.objects.templates.clients.ClientsTemplate import okhttp3.MultipartBody import okhttp3.ResponseBody @@ -32,4 +34,8 @@ interface CreateNewClientRepository { fun createClient(clientPayload: ClientPayload): Observable fun uploadClientImage(id: Int, file: MultipartBody.Part?): Observable + + suspend fun getAddressConfiguration(): AddressConfiguration + + suspend fun getAddressTemplate(): AddressTemplate } diff --git a/core/data/src/main/java/com/mifos/core/data/repositoryImp/CreateNewClientRepositoryImp.kt b/core/data/src/main/java/com/mifos/core/data/repositoryImp/CreateNewClientRepositoryImp.kt index 5e3d2cebfb9..9ef69ea6153 100644 --- a/core/data/src/main/java/com/mifos/core/data/repositoryImp/CreateNewClientRepositoryImp.kt +++ b/core/data/src/main/java/com/mifos/core/data/repositoryImp/CreateNewClientRepositoryImp.kt @@ -17,6 +17,8 @@ import com.mifos.core.objects.client.Client import com.mifos.core.objects.client.ClientPayload import com.mifos.core.objects.organisation.Office import com.mifos.core.objects.organisation.Staff +import com.mifos.core.objects.templates.clients.AddressConfiguration +import com.mifos.core.objects.templates.clients.AddressTemplate import com.mifos.core.objects.templates.clients.ClientsTemplate import okhttp3.MultipartBody import okhttp3.ResponseBody @@ -51,4 +53,12 @@ class CreateNewClientRepositoryImp @Inject constructor( override fun uploadClientImage(id: Int, file: MultipartBody.Part?): Observable { return dataManagerClient.uploadClientImage(id, file) } + + override suspend fun getAddressConfiguration(): AddressConfiguration { + return dataManagerClient.getAddressConfiguration() + } + + override suspend fun getAddressTemplate(): AddressTemplate { + return dataManagerClient.getAddressTemplate() + } } diff --git a/core/database/src/main/java/com/mifos/core/objects/client/Address.kt b/core/database/src/main/java/com/mifos/core/objects/client/Address.kt index dd9f5c7b042..81b8a4527cf 100644 --- a/core/database/src/main/java/com/mifos/core/objects/client/Address.kt +++ b/core/database/src/main/java/com/mifos/core/objects/client/Address.kt @@ -17,13 +17,21 @@ import kotlinx.parcelize.Parcelize */ @Parcelize data class Address( - var addressTypeId: Int? = null, + var addressTypeId: Int = -1, - var active: Boolean? = null, + var isActive: Boolean = false, - var street: String? = null, + var addressLine1: String = "", - var stateProvinceId: Int? = null, + var addressLine2: String = "", - var countryId: Int? = null, + var addressLine3: String = "", + + var city: String = "", + + var stateProvinceId: Int = -1, + + var countryId: Int = -1, + + var postalCode: String = "", ) : Parcelable diff --git a/core/database/src/main/java/com/mifos/core/objects/client/ClientPayload.kt b/core/database/src/main/java/com/mifos/core/objects/client/ClientPayload.kt index d187eb0c5cd..e03853ce1da 100644 --- a/core/database/src/main/java/com/mifos/core/objects/client/ClientPayload.kt +++ b/core/database/src/main/java/com/mifos/core/objects/client/ClientPayload.kt @@ -84,7 +84,7 @@ data class ClientPayload( @Column var clientClassificationId: Int? = null, - var address: List
? = ArrayList(), + var address: List
? = emptyList(), @Column var dateFormat: String? = "dd MMMM yyyy", diff --git a/core/database/src/main/java/com/mifos/core/objects/templates/clients/AddressTemplate.kt b/core/database/src/main/java/com/mifos/core/objects/templates/clients/AddressTemplate.kt new file mode 100644 index 00000000000..bb9e96670a0 --- /dev/null +++ b/core/database/src/main/java/com/mifos/core/objects/templates/clients/AddressTemplate.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/android-client/blob/master/LICENSE.md + */ +package com.mifos.core.objects.templates.clients + +data class AddressTemplate( + val addressTypeIdOptions: List = emptyList(), + val countryIdOptions: List = emptyList(), + val stateProvinceIdOptions: List = emptyList(), +) + +data class AddressConfiguration( + val enabled: Boolean = false, +) diff --git a/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerClient.kt b/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerClient.kt index 4e61d33ca8f..07f1bec6e2b 100644 --- a/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerClient.kt +++ b/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerClient.kt @@ -28,6 +28,8 @@ import com.mifos.core.objects.noncore.Identifier import com.mifos.core.objects.noncore.IdentifierCreationResponse import com.mifos.core.objects.noncore.IdentifierPayload import com.mifos.core.objects.noncore.IdentifierTemplate +import com.mifos.core.objects.templates.clients.AddressConfiguration +import com.mifos.core.objects.templates.clients.AddressTemplate import com.mifos.core.objects.templates.clients.ClientsTemplate import kotlinx.coroutines.flow.Flow import okhttp3.MultipartBody @@ -345,4 +347,25 @@ class DataManagerClient @Inject constructor( "activate", ) } + + /** + * Gets the address configuration. + * + * @return The address configuration. + */ + suspend fun getAddressConfiguration(): AddressConfiguration { + return mBaseApiManager.clientsApi.getAddressConfiguration() + } + + /** + * Gets the address template. + * + * The address template is a predefined format for addresses that can be used to ensure consistency + * and accuracy when collecting and storing address information. + * + * @return The address template. + */ + suspend fun getAddressTemplate(): AddressTemplate { + return mBaseApiManager.clientsApi.getAddressTemplate() + } } diff --git a/core/network/src/main/java/com/mifos/core/network/services/ClientService.kt b/core/network/src/main/java/com/mifos/core/network/services/ClientService.kt index 5d50cef85e2..849ddb4e6c9 100644 --- a/core/network/src/main/java/com/mifos/core/network/services/ClientService.kt +++ b/core/network/src/main/java/com/mifos/core/network/services/ClientService.kt @@ -20,6 +20,8 @@ import com.mifos.core.objects.noncore.Identifier import com.mifos.core.objects.noncore.IdentifierCreationResponse import com.mifos.core.objects.noncore.IdentifierPayload import com.mifos.core.objects.noncore.IdentifierTemplate +import com.mifos.core.objects.templates.clients.AddressConfiguration +import com.mifos.core.objects.templates.clients.AddressTemplate import com.mifos.core.objects.templates.clients.ClientsTemplate import kotlinx.coroutines.flow.Flow import okhttp3.MultipartBody @@ -220,4 +222,21 @@ interface ClientService { @Body clientActivate: PostClientsClientIdRequest, @Query("command") command: String? = null, ): PostClientsClientIdResponse + + /** + * Retrieves address configuration from Global Configuration. + * + * @return The AddressConfiguration object + */ + @GET("configurations/name/enable-address") + suspend fun getAddressConfiguration(): AddressConfiguration + + /** + * Retrieves an address template. + * This template can be used to pre-fill address forms or guide users in providing address information. + * + * @return An [AddressTemplate] object containing the structure for an address. + */ + @GET("client/addresses/template") + suspend fun getAddressTemplate(): AddressTemplate } diff --git a/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientScreen.kt b/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientScreen.kt index efff02ed4e2..67b107de453 100644 --- a/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientScreen.kt +++ b/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientScreen.kt @@ -93,6 +93,7 @@ import androidx.core.net.toFile import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.rememberAsyncImagePainter +import com.mifos.core.common.utils.Network import com.mifos.core.designsystem.component.MifosCircularProgress import com.mifos.core.designsystem.component.MifosDatePickerTextField import com.mifos.core.designsystem.component.MifosOutlinedTextField @@ -106,10 +107,12 @@ import com.mifos.core.designsystem.theme.BluePrimaryDark import com.mifos.core.designsystem.theme.BlueSecondary import com.mifos.core.designsystem.theme.DarkGray import com.mifos.core.designsystem.theme.White +import com.mifos.core.objects.client.Address import com.mifos.core.objects.client.ClientPayload import com.mifos.core.objects.noncore.DataTable import com.mifos.core.objects.organisation.Office import com.mifos.core.objects.organisation.Staff +import com.mifos.core.objects.templates.clients.AddressTemplate import com.mifos.core.objects.templates.clients.ClientsTemplate import com.mifos.feature.client.R import kotlinx.coroutines.launch @@ -189,6 +192,8 @@ internal fun CreateNewClientScreen( officeList = officeList, staffInOffices = staffInOffices, clientTemplate = uiState.clientsTemplate, + addressTemplate = uiState.addressTemplate, + isAddressEnabled = uiState.isAddressEnabled, loadStaffInOffice = loadStaffInOffice, createClient = createClient, onHasDatatables = hasDatatables, @@ -258,6 +263,8 @@ private fun CreateNewClientContent( officeList: List, staffInOffices: List, clientTemplate: ClientsTemplate, + addressTemplate: AddressTemplate?, + isAddressEnabled: Boolean, loadStaffInOffice: (officeId: Int) -> Unit, createClient: (clientPayload: ClientPayload) -> Unit, onHasDatatables: (datatables: List, clientPayload: ClientPayload) -> Unit, @@ -271,8 +278,21 @@ private fun CreateNewClientContent( var gender by rememberSaveable { mutableStateOf("") } var genderId by rememberSaveable { mutableIntStateOf(0) } - var client by rememberSaveable { mutableStateOf("") } - var selectedClientId by rememberSaveable { mutableIntStateOf(0) } + var selectedAddressType by rememberSaveable { mutableStateOf("") } + var selectedAddressTypeId by rememberSaveable { mutableIntStateOf(0) } + var addressLine1 by rememberSaveable { mutableStateOf("") } + var addressLine2 by rememberSaveable { mutableStateOf("") } + var addressLine3 by rememberSaveable { mutableStateOf("") } + var city by rememberSaveable { mutableStateOf("") } + var selectedStateName by rememberSaveable { mutableStateOf("") } + var selectedStateProvinceId by rememberSaveable { mutableIntStateOf(0) } + var selectedCountryName by rememberSaveable { mutableStateOf("") } + var selectedCountryId by rememberSaveable { mutableIntStateOf(0) } + var postalCode by rememberSaveable { mutableStateOf("") } + var isAddressActive by rememberSaveable { mutableStateOf(false) } + + var clientType by rememberSaveable { mutableStateOf("") } + var selectedClientTypeId by rememberSaveable { mutableIntStateOf(0) } var clientClassification by rememberSaveable { mutableStateOf("") } var selectedClientClassificationId by rememberSaveable { mutableIntStateOf(0) } var selectedOffice by rememberSaveable { mutableStateOf("") } @@ -303,7 +323,9 @@ private fun CreateNewClientContent( context.packageName + ".provider", file, ) + Log.d("Debuguripath", context.packageName) + val cameraLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.TakePicture(), onResult = { success -> @@ -334,11 +356,13 @@ private fun CreateNewClientContent( officeList[0].id?.let { loadStaffInOffice.invoke(it) } } } + LaunchedEffect(key1 = staffInOffices) { if (staffInOffices.isEmpty()) { Toast.makeText( context, - context.resources.getString(R.string.feature_client_no_staff_associated_with_office), + context.resources + .getString(R.string.feature_client_no_staff_associated_with_office), Toast.LENGTH_SHORT, ).show() staff = "" @@ -462,6 +486,7 @@ private fun CreateNewClientContent( ClientImageSection(selectedImageUri = selectedImageUri) { showImagePickerDialog = true } + ClientInputTextFields( firstName = firstName, middleName = middleName, @@ -474,6 +499,55 @@ private fun CreateNewClientContent( onMobileNumberChange = { mobileNumber = it }, onExternalIdChange = { externalId = it }, ) + + if (isAddressEnabled && addressTemplate != null) { + val sortedAddressTypeOptions = addressTemplate.addressTypeIdOptions.sortedBy { it.name } + val sortedCountryOptions = addressTemplate.countryIdOptions.sortedBy { it.name } + val sortedStateOptions = addressTemplate.stateProvinceIdOptions.sortedBy { it.name } + + Spacer(modifier = Modifier.height(16.dp)) + + AddressInputTextFields( + addressLine1 = addressLine1, + onAddressLine1Change = { addressLine1 = it }, + addressLine2 = addressLine2, + onAddressLine2Change = { addressLine2 = it }, + addressLine3 = addressLine3, + onAddressLine3Change = { addressLine3 = it }, + city = city, + onCityChange = { city = it }, + postalCode = postalCode, + onPostalCodeChange = { postalCode = it }, + selectedAddressType = selectedAddressType, + onAddressTypeChanged = { selectedAddressType = it }, + onAddressTypeSelected = { index, value -> + selectedAddressType = value + selectedAddressTypeId = sortedAddressTypeOptions[index].id + }, + addressTypeOptions = sortedAddressTypeOptions.map { it.name }, + selectedStateName = selectedStateName, + onStateNameChanged = { selectedStateName = it }, + onStateSelected = { index, value -> + selectedStateName = value + selectedStateProvinceId = sortedStateOptions[index].id + }, + stateOptions = sortedStateOptions.map { it.name }, + + selectedCountryName = selectedCountryName, + onCountryNameChanged = { selectedCountryName = it }, + onCountrySelected = { index, value -> + selectedCountryName = value + selectedCountryId = sortedCountryOptions[index].id + }, + countryOptions = sortedCountryOptions.map { it.name }, + + isAddressActive = isAddressActive, + onAddressActiveChange = { isAddressActive = it }, + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + MifosTextFieldDropdown( value = gender, onValueChanged = { gender = it }, @@ -499,13 +573,13 @@ private fun CreateNewClientContent( Spacer(modifier = Modifier.height(16.dp)) MifosTextFieldDropdown( - value = client, - onValueChanged = { client = it }, + value = clientType, + onValueChanged = { clientType = it }, onOptionSelected = { index, value -> - client = value - selectedClientId = clientTemplate.clientTypeOptions[index].id + clientType = value + selectedClientTypeId = clientTemplate.clientTypeOptions[index].id }, - label = R.string.feature_client_client, + label = R.string.feature_client_client_type, options = clientTemplate.clientTypeOptions.sortedBy { it.name }.map { it.name }, readOnly = true, ) @@ -613,9 +687,11 @@ private fun CreateNewClientContent( handleSubmitClick( context, clientNames, clientTemplate, createClient, isActive, onHasDatatables, selectedImageUri, setUriForUpload, staffInOffices, hasDatatables, - selectedOfficeId, selectedClientId, selectedClientClassificationId, - genderId, selectedStaffId, activationDate, dateOfBirth, - mobileNumber, externalId, + selectedOfficeId, selectedClientTypeId, selectedClientClassificationId, genderId, + selectedStaffId, activationDate, dateOfBirth, mobileNumber, externalId, + isAddressEnabled, isAddressActive, selectedAddressTypeId, addressLine1, + addressLine2, addressLine3, city, selectedStateProvinceId, + selectedCountryId, postalCode, ) }, ) { @@ -650,18 +726,29 @@ private fun handleSubmitClick( dateOfBirth: Long, mobileNumber: String, externalId: String, + isAddressEnabled: Boolean, + isAddressActive: Boolean, + addressTypeId: Int, + addressLine1: String, + addressLine2: String, + addressLine3: String, + city: String, + stateProvinceId: Int, + countryId: Int, + postalCode: String, ) { if (!isAllFieldsValid( context, clientNames.firstName, clientNames.middleName, clientNames.lastName, + addressTypeId = addressTypeId, ) ) { return } - if (!com.mifos.core.common.utils.Network.isOnline(context)) { + if (!Network.isOnline(context)) { Toast.makeText( context, context.resources.getString(R.string.feature_client_error_not_connected_internet), @@ -672,9 +759,10 @@ private fun handleSubmitClick( val clientPayload = createClientPayload( clientNames.firstName, clientNames.lastName, selectedOfficeId, staffInOffices, isActive, - activationDate, dateOfBirth, clientNames.middleName, mobileNumber, - externalId, clientTemplate, genderId, selectedStaffId, - selectedClientId, selectedClientClassificationId, + activationDate, dateOfBirth, clientNames.middleName, mobileNumber, externalId, + clientTemplate, genderId, selectedStaffId, selectedClientId, + selectedClientClassificationId, isAddressEnabled, isAddressActive, addressTypeId, addressLine1, addressLine2, + addressLine3, city, stateProvinceId, countryId, postalCode, ) if (hasDatatables) { @@ -702,6 +790,16 @@ private fun createClientPayload( selectedStaffId: Int?, selectedClientId: Int, selectedClientClassificationId: Int, + isAddressEnabled: Boolean, + isAddressActive: Boolean, + addressTypeId: Int, + addressLine1: String, + addressLine2: String, + addressLine3: String, + city: String, + stateProvinceId: Int, + countryId: Int, + postalCode: String, ): ClientPayload { val clientPayload = ClientPayload() @@ -712,6 +810,21 @@ private fun createClientPayload( // Optional fields with default values clientPayload.active = isActive + + if (isAddressEnabled) { + val address = Address() + address.addressTypeId = addressTypeId + address.addressLine1 = addressLine1 + address.addressLine2 = addressLine2 + address.addressLine3 = addressLine3 + address.city = city + address.stateProvinceId = stateProvinceId + address.countryId = countryId + address.postalCode = postalCode + address.isActive = isAddressActive + clientPayload.address = listOf(address) + } + clientPayload.activationDate = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format(activationDate) clientPayload.dateOfBirth = @@ -801,8 +914,129 @@ private fun ClientInputTextFields( label = stringResource(id = R.string.feature_client_external_id), error = null, ) + } +} + +@Composable +private fun AddressInputTextFields( + addressLine1: String, + onAddressLine1Change: (String) -> Unit, + addressLine2: String, + onAddressLine2Change: (String) -> Unit, + addressLine3: String, + onAddressLine3Change: (String) -> Unit, + city: String, + onCityChange: (String) -> Unit, + postalCode: String, + onPostalCodeChange: (String) -> Unit, + selectedAddressType: String, + onAddressTypeChanged: (String) -> Unit, + onAddressTypeSelected: (Int, String) -> Unit, + addressTypeOptions: List, + selectedStateName: String, + onStateNameChanged: (String) -> Unit, + onStateSelected: (Int, String) -> Unit, + stateOptions: List, + selectedCountryName: String, + onCountryNameChanged: (String) -> Unit, + onCountrySelected: (Int, String) -> Unit, + countryOptions: List, + isAddressActive: Boolean, + onAddressActiveChange: (Boolean) -> Unit, +) { + Column { + MifosTextFieldDropdown( + value = selectedAddressType, + onValueChanged = onAddressTypeChanged, + onOptionSelected = onAddressTypeSelected, + label = R.string.feature_client_address_type, + options = addressTypeOptions, + readOnly = true, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + MifosOutlinedTextField( + value = addressLine1, + onValueChange = onAddressLine1Change, + label = stringResource(id = R.string.feature_client_address_line_1), + error = null, + ) Spacer(modifier = Modifier.height(16.dp)) + + MifosOutlinedTextField( + value = addressLine2, + onValueChange = onAddressLine2Change, + label = stringResource(id = R.string.feature_client_address_line_2), + error = null, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + MifosOutlinedTextField( + value = addressLine3, + onValueChange = onAddressLine3Change, + label = stringResource(id = R.string.feature_client_address_line_3), + error = null, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + MifosOutlinedTextField( + value = city, + onValueChange = onCityChange, + label = stringResource(id = R.string.feature_client_city), + error = null, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + MifosOutlinedTextField( + value = postalCode, + onValueChange = onPostalCodeChange, + label = stringResource(id = R.string.feature_client_postal_code), + error = null, + keyboardType = KeyboardType.Number, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + MifosTextFieldDropdown( + value = selectedStateName, + onValueChanged = onStateNameChanged, + onOptionSelected = onStateSelected, + options = stateOptions, + label = R.string.feature_client_state_province, + readOnly = true, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + MifosTextFieldDropdown( + value = selectedCountryName, + onValueChanged = onCountryNameChanged, + onOptionSelected = onCountrySelected, + options = countryOptions, + label = R.string.feature_client_country, + readOnly = true, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Checkbox( + checked = isAddressActive, + onCheckedChange = { onAddressActiveChange(!isAddressActive) }, + colors = CheckboxDefaults.colors( + if (isSystemInDarkTheme()) BluePrimaryDark else BluePrimary, + ), + ) + Text(text = stringResource(id = R.string.feature_client_address_active)) + } } } @@ -943,6 +1177,7 @@ private fun isAllFieldsValid( firstName: String, middleName: String, lastName: String, + addressTypeId: Int, ): Boolean { return when { !isFirstNameValid(firstName, context) -> { @@ -957,6 +1192,10 @@ private fun isAllFieldsValid( false } + !isAddressTypeIdValid(addressTypeId, context) -> { + false + } + else -> true } } @@ -1028,6 +1267,20 @@ private fun isMiddleNameValid(name: String, context: Context): Boolean { } } +private fun isAddressTypeIdValid(addressTypeId: Int, context: Context): Boolean { + return when { + addressTypeId < 0 -> { + Toast.makeText( + context, + context.resources.getString(R.string.feature_client_error_address_type_is_required), + Toast.LENGTH_SHORT, + ).show() + return false + } + else -> true + } +} + private class CreateNewClientScreenPreviewProvider : PreviewParameterProvider { override val values: Sequence @@ -1043,6 +1296,8 @@ private class CreateNewClientScreenPreviewProvider : savingProductOptions = listOf(), dataTables = listOf(), ), + isAddressEnabled = false, + addressTemplate = AddressTemplate(), ), CreateNewClientUiState.ShowProgressbar, CreateNewClientUiState.ShowClientCreatedSuccessfully(R.string.feature_client_client_created_successfully), @@ -1056,7 +1311,7 @@ private class CreateNewClientScreenPreviewProvider : private fun PreviewCreateNewClientScreen( @PreviewParameter(CreateNewClientScreenPreviewProvider::class) createNewClientUiState: CreateNewClientUiState, ) { - // ToDo : FIX Preview + // TODO : Fix Preview CreateNewClientScreen( uiState = createNewClientUiState, diff --git a/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientUiState.kt b/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientUiState.kt index 9681c3c5a36..bec34326510 100644 --- a/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientUiState.kt +++ b/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientUiState.kt @@ -9,6 +9,7 @@ */ package com.mifos.feature.client.createNewClient +import com.mifos.core.objects.templates.clients.AddressTemplate import com.mifos.core.objects.templates.clients.ClientsTemplate /** @@ -26,7 +27,11 @@ sealed class CreateNewClientUiState { data class OnImageUploadSuccess(val message: Int) : CreateNewClientUiState() - data class ShowClientTemplate(val clientsTemplate: ClientsTemplate) : CreateNewClientUiState() + data class ShowClientTemplate( + val clientsTemplate: ClientsTemplate, + val isAddressEnabled: Boolean, + val addressTemplate: AddressTemplate? = null, + ) : CreateNewClientUiState() data class ShowClientCreatedSuccessfully(val message: Int) : CreateNewClientUiState() diff --git a/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientViewModel.kt b/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientViewModel.kt index a362920cc4c..ba9afd39d36 100644 --- a/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientViewModel.kt +++ b/feature/client/src/main/java/com/mifos/feature/client/createNewClient/CreateNewClientViewModel.kt @@ -21,6 +21,7 @@ import com.mifos.core.objects.client.Client import com.mifos.core.objects.client.ClientPayload import com.mifos.core.objects.organisation.Office import com.mifos.core.objects.organisation.Staff +import com.mifos.core.objects.templates.clients.AddressTemplate import com.mifos.core.objects.templates.clients.ClientsTemplate import com.mifos.feature.client.R import dagger.hilt.android.lifecycle.HiltViewModel @@ -61,6 +62,15 @@ class CreateNewClientViewModel @Inject constructor( private val _showOffices = MutableStateFlow>(emptyList()) val showOffices: StateFlow> get() = _showOffices + private val _isAddressEnabled = MutableStateFlow(false) + val isAddressEnabled: StateFlow get() = _isAddressEnabled + + private val _addressTemplate = MutableStateFlow(null) + val addressTemplate: StateFlow get() = _addressTemplate + + private val _clientsTemplate = MutableStateFlow(null) + val clientsTemplate: StateFlow get() = _clientsTemplate + fun loadOfficeAndClientTemplate() { _createNewClientUiState.value = CreateNewClientUiState.ShowProgressbar loadClientTemplate() @@ -76,9 +86,18 @@ class CreateNewClientViewModel @Inject constructor( is Resource.Loading -> Unit - is Resource.Success -> + is Resource.Success -> { + _clientsTemplate.value = result.data + + loadAddressConfiguration() + _createNewClientUiState.value = - CreateNewClientUiState.ShowClientTemplate(result.data ?: ClientsTemplate()) + CreateNewClientUiState.ShowClientTemplate( + result.data ?: ClientsTemplate(), + isAddressEnabled = _isAddressEnabled.value, + addressTemplate = _addressTemplate.value ?: AddressTemplate(), + ) + } } } } @@ -195,4 +214,30 @@ class CreateNewClientViewModel @Inject constructor( }, ) } + + suspend fun loadAddressConfiguration() { + try { + val addressConfig = repository.getAddressConfiguration() + _isAddressEnabled.value = addressConfig.enabled + + if (addressConfig.enabled) { + loadAddressTemplate() + } + } catch (e: Exception) { + _createNewClientUiState.value = + CreateNewClientUiState.ShowError(R.string.feature_client_failed_to_fetch_address_configuration) + Log.e("CreateNewClientViewModel", "Error checking address configuration", e) + } + } + + suspend fun loadAddressTemplate() { + try { + val template = repository.getAddressTemplate() + _addressTemplate.value = template + } catch (e: Exception) { + _createNewClientUiState.value = + CreateNewClientUiState.ShowError(R.string.feature_client_failed_to_fetch_address_template) + Log.e("CreateNewClientViewModel", "Error loading address template", e) + } + } } diff --git a/feature/client/src/main/res/values/strings.xml b/feature/client/src/main/res/values/strings.xml index 15a4e37f8c3..07a22f72bce 100644 --- a/feature/client/src/main/res/values/strings.xml +++ b/feature/client/src/main/res/values/strings.xml @@ -142,7 +142,7 @@ Last Name* Gender Date of Birth - Client + Client Type Client Classification Office* Staff @@ -167,4 +167,21 @@ Image Upload Successful Not connected to Network + Add Address + Address Type* + Address Line 1 + Address Line 2 + Address Line 3 + Street + City + Town/Village + County/District + Country + State/Province + Postal Code + Address Active + + Address Type is required + Failed to fetch address configuration + Failed to fetch address template \ No newline at end of file diff --git a/mifosng-android/prodRelease-badging.txt b/mifosng-android/prodRelease-badging.txt new file mode 100644 index 00000000000..d2e26e9aeb2 --- /dev/null +++ b/mifosng-android/prodRelease-badging.txt @@ -0,0 +1,124 @@ +package: name='com.mifos.mifosxdroid' versionCode='1' versionName='2025.5.4-beta.0.0' platformBuildVersionName='14' platformBuildVersionCode='34' compileSdkVersion='34' compileSdkVersionCodename='14' +sdkVersion:'26' +targetSdkVersion:'34' +uses-permission: name='android.permission.INTERNET' +uses-permission: name='android.permission.CAMERA' +uses-permission: name='android.permission.VIBRATE' +uses-permission: name='android.permission.ACCESS_NETWORK_STATE' +uses-permission: name='com.mifos.mifosxdroid.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION' +uses-permission: name='android.permission.REORDER_TASKS' +application-label:'MifosXDroid' +application-label-af:'MifosXDroid' +application-label-am:'MifosXDroid' +application-label-ar:'MifosXDroid' +application-label-as:'MifosXDroid' +application-label-az:'MifosXDroid' +application-label-be:'MifosXDroid' +application-label-bg:'MifosXDroid' +application-label-bn:'MifosXDroid' +application-label-bs:'MifosXDroid' +application-label-ca:'MifosXDroid' +application-label-cs:'MifosXDroid' +application-label-da:'MifosXDroid' +application-label-de:'MifosXDroid' +application-label-el:'MifosXDroid' +application-label-en:'MifosXDroid' +application-label-en-AU:'MifosXDroid' +application-label-en-CA:'MifosXDroid' +application-label-en-GB:'MifosXDroid' +application-label-en-IN:'MifosXDroid' +application-label-en-XC:'MifosXDroid' +application-label-es:'MifosXDroid' +application-label-es-US:'MifosXDroid' +application-label-et:'MifosXDroid' +application-label-eu:'MifosXDroid' +application-label-fa:'MifosXDroid' +application-label-fi:'MifosXDroid' +application-label-fr:'MifosXDroid' +application-label-fr-CA:'MifosXDroid' +application-label-gl:'MifosXDroid' +application-label-gu:'MifosXDroid' +application-label-hi:'MifosXDroid' +application-label-hr:'MifosXDroid' +application-label-hu:'MifosXDroid' +application-label-hy:'MifosXDroid' +application-label-in:'MifosXDroid' +application-label-is:'MifosXDroid' +application-label-it:'MifosXDroid' +application-label-iw:'MifosXDroid' +application-label-ja:'MifosXDroid' +application-label-ka:'MifosXDroid' +application-label-kk:'MifosXDroid' +application-label-km:'MifosXDroid' +application-label-kn:'MifosXDroid' +application-label-ko:'MifosXDroid' +application-label-ky:'MifosXDroid' +application-label-lo:'MifosXDroid' +application-label-lt:'MifosXDroid' +application-label-lv:'MifosXDroid' +application-label-mk:'MifosXDroid' +application-label-ml:'MifosXDroid' +application-label-mn:'MifosXDroid' +application-label-mr:'MifosXDroid' +application-label-ms:'MifosXDroid' +application-label-my:'MifosXDroid' +application-label-nb:'MifosXDroid' +application-label-ne:'MifosXDroid' +application-label-nl:'MifosXDroid' +application-label-or:'MifosXDroid' +application-label-pa:'MifosXDroid' +application-label-pl:'MifosXDroid' +application-label-pt:'MifosXDroid' +application-label-pt-BR:'MifosXDroid' +application-label-pt-PT:'MifosXDroid' +application-label-ro:'MifosXDroid' +application-label-ru:'MifosXDroid' +application-label-si:'MifosXDroid' +application-label-sk:'MifosXDroid' +application-label-sl:'MifosXDroid' +application-label-sq:'MifosXDroid' +application-label-sr:'MifosXDroid' +application-label-sr-Latn:'MifosXDroid' +application-label-sv:'MifosXDroid' +application-label-sw:'MifosXDroid' +application-label-ta:'MifosXDroid' +application-label-te:'MifosXDroid' +application-label-th:'MifosXDroid' +application-label-tl:'MifosXDroid' +application-label-tr:'MifosXDroid' +application-label-uk:'MifosXDroid' +application-label-ur:'MifosXDroid' +application-label-uz:'MifosXDroid' +application-label-vi:'MifosXDroid' +application-label-zh:'MifosXDroid' +application-label-zh-CN:'MifosXDroid' +application-label-zh-HK:'MifosXDroid' +application-label-zh-TW:'MifosXDroid' +application-label-zu:'MifosXDroid' +application-icon-120:'res/drawable/feature_client_ic_launcher.png' +application-icon-160:'res/drawable/feature_client_ic_launcher.png' +application-icon-240:'res/drawable/feature_client_ic_launcher.png' +application-icon-320:'res/drawable/feature_client_ic_launcher.png' +application-icon-480:'res/drawable/feature_client_ic_launcher.png' +application-icon-640:'res/drawable/feature_client_ic_launcher.png' +application-icon-65534:'res/drawable/feature_client_ic_launcher.png' +application: label='MifosXDroid' icon='res/drawable/feature_client_ic_launcher.png' +launchable-activity: name='com.mifos.mifosxdroid.AndroidClientActivity' label='' icon='' +uses-library-not-required:'org.apache.http.legacy' +uses-library-not-required:'androidx.window.extensions' +uses-library-not-required:'androidx.window.sidecar' +feature-group: label='' + uses-gl-es: '0x20000' + uses-feature: name='android.hardware.camera.any' + uses-feature: name='android.hardware.camera' + uses-implied-feature: name='android.hardware.camera' reason='requested android.permission.CAMERA permission' + uses-feature: name='android.hardware.faketouch' + uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' +main +other-activities +other-receivers +other-services +supports-screens: 'small' 'normal' 'large' 'xlarge' +supports-any-density: 'true' +locales: '--_--' 'af' 'am' 'ar' 'as' 'az' 'be' 'bg' 'bn' 'bs' 'ca' 'cs' 'da' 'de' 'el' 'en' 'en-AU' 'en-CA' 'en-GB' 'en-IN' 'en-XC' 'es' 'es-US' 'et' 'eu' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'gu' 'hi' 'hr' 'hu' 'hy' 'in' 'is' 'it' 'iw' 'ja' 'ka' 'kk' 'km' 'kn' 'ko' 'ky' 'lo' 'lt' 'lv' 'mk' 'ml' 'mn' 'mr' 'ms' 'my' 'nb' 'ne' 'nl' 'or' 'pa' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'si' 'sk' 'sl' 'sq' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'te' 'th' 'tl' 'tr' 'uk' 'ur' 'uz' 'vi' 'zh' 'zh-CN' 'zh-HK' 'zh-TW' 'zu' +densities: '120' '160' '240' '320' '480' '640' '65534'