diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java index d6689a2fc..be576336d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationProperty.java @@ -22,18 +22,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import java.time.LocalDate; -import java.util.LinkedHashMap; -import java.util.Map; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationPropertyData; -import org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationPropertyCannotBeModfied; -import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; -import org.apache.fineract.infrastructure.security.exception.ForcePasswordResetException; @Entity @Table(name = "c_configuration") @@ -64,53 +58,6 @@ public class GlobalConfigurationProperty extends AbstractPersistableCustom @Column(name = "is_trap_door", nullable = false) private boolean isTrapDoor; - public Map update(final JsonCommand command) { - final Map actualChanges = new LinkedHashMap<>(7); - - if (this.isTrapDoor) { - throw new GlobalConfigurationPropertyCannotBeModfied(this.getId()); - } - - final String enabledParamName = "enabled"; - if (command.isChangeInBooleanParameterNamed(enabledParamName, this.enabled)) { - final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enabledParamName); - actualChanges.put(enabledParamName, newValue); - this.enabled = newValue; - } - - final String valueParamName = "value"; - final Long previousValue = this.value; - if (command.isChangeInLongParameterNamed(valueParamName, this.value)) { - final Long newValue = command.longValueOfParameterNamed(valueParamName); - actualChanges.put(valueParamName, newValue); - this.value = newValue; - } - - final String dateValueParamName = "dateValue"; - if (command.isChangeInDateParameterNamed(dateValueParamName, this.dateValue)) { - final LocalDate newDateValue = command.localDateValueOfParameterNamed(dateValueParamName); - actualChanges.put(dateValueParamName, newDateValue); - this.dateValue = newDateValue; - } - - final String stringValueParamName = "stringValue"; - if (command.isChangeInStringParameterNamed(stringValueParamName, this.stringValue)) { - final String newStringValue = command.stringValueOfParameterNamed(stringValueParamName); - actualChanges.put(stringValueParamName, newStringValue); - this.stringValue = newStringValue; - } - - final String passwordPropertyName = GlobalConfigurationConstants.FORCE_PASSWORD_RESET_DAYS; - if (this.name.equalsIgnoreCase(passwordPropertyName)) { - if ((this.enabled == true && command.hasParameter(valueParamName) && (this.value == 0)) - || (this.enabled == true && !command.hasParameter(valueParamName) && (previousValue == 0))) { - throw new ForcePasswordResetException(); - } - } - - return actualChanges; - } - public static GlobalConfigurationProperty newSurveyConfiguration(final String name) { return new GlobalConfigurationProperty().setName(name); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationPropertyUpdateService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationPropertyUpdateService.java new file mode 100644 index 000000000..c55f5f643 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationPropertyUpdateService.java @@ -0,0 +1,93 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.configuration.service; + +import java.time.LocalDate; +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationProperty; +import org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationPropertyCannotBeModfied; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.security.exception.ForcePasswordResetException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class GlobalConfigurationPropertyUpdateService { + + private final JdbcTemplate jdbcTemplate; + + public Map update(final GlobalConfigurationProperty property, final JsonCommand command) { + final Map actualChanges = new LinkedHashMap<>(7); + + if (property.isTrapDoor() && isAnyProductAlreadyCreated()) { + throw new GlobalConfigurationPropertyCannotBeModfied(property.getId()); + } + + final String enabledParamName = "enabled"; + if (command.isChangeInBooleanParameterNamed(enabledParamName, property.isEnabled())) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enabledParamName); + actualChanges.put(enabledParamName, newValue); + property.setEnabled(newValue); + } + + final String valueParamName = "value"; + final Long previousValue = property.getValue(); + if (command.isChangeInLongParameterNamed(valueParamName, property.getValue())) { + final Long newValue = command.longValueOfParameterNamed(valueParamName); + actualChanges.put(valueParamName, newValue); + property.setValue(newValue); + } + + final String dateValueParamName = "dateValue"; + if (command.isChangeInDateParameterNamed(dateValueParamName, property.getDateValue())) { + final LocalDate newDateValue = command.localDateValueOfParameterNamed(dateValueParamName); + actualChanges.put(dateValueParamName, newDateValue); + property.setDateValue(newDateValue); + } + + final String stringValueParamName = "stringValue"; + if (command.isChangeInStringParameterNamed(stringValueParamName, property.getStringValue())) { + final String newStringValue = command.stringValueOfParameterNamed(stringValueParamName); + actualChanges.put(stringValueParamName, newStringValue); + property.setStringValue(newStringValue); + } + + final String passwordPropertyName = GlobalConfigurationConstants.FORCE_PASSWORD_RESET_DAYS; + if (property.getName().equalsIgnoreCase(passwordPropertyName)) { + if ((property.isEnabled() && command.hasParameter(valueParamName) && (property.getValue() == 0)) + || (property.isEnabled() && !command.hasParameter(valueParamName) && (previousValue == 0))) { + throw new ForcePasswordResetException(); + } + } + + return actualChanges; + } + + private boolean isAnyProductAlreadyCreated() { + String productExistenceSql = "SELECT EXISTS (SELECT 1 FROM m_loan) OR EXISTS (SELECT 1 FROM m_savings_account) OR" + + " EXISTS (SELECT 1 FROM m_share_account) AS products_exist"; + Boolean productsExist = this.jdbcTemplate.queryForObject(productExistenceSql, Boolean.class); + return BooleanUtils.isTrue(productsExist); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java index 4beaba349..6a5651f6a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/GlobalConfigurationWritePlatformServiceJpaRepositoryImpl.java @@ -47,6 +47,7 @@ public class GlobalConfigurationWritePlatformServiceJpaRepositoryImpl implements private final GlobalConfigurationRepositoryWrapper repository; private final GlobalConfigurationDataValidator globalConfigurationDataValidator; private final ConfigurationDomainService configurationDomainService; + private final GlobalConfigurationPropertyUpdateService globalConfigurationPropertyUpdateService; @Transactional @Override @@ -56,7 +57,7 @@ public class GlobalConfigurationWritePlatformServiceJpaRepositoryImpl implements final GlobalConfigurationProperty configItemForUpdate = this.repository.findOneWithNotFoundDetection(configId); - final Map changes = configItemForUpdate.update(command); + final Map changes = globalConfigurationPropertyUpdateService.update(configItemForUpdate, command); if (!changes.isEmpty()) { this.configurationDomainService.removeGlobalConfigurationPropertyDataFromCache(configItemForUpdate.getName()); diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 9f264f1ae..9bb89cf38 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -46,6 +46,7 @@ fineract.tenant.read-only-name=${FINERACT_DEFAULT_TENANTDB_RO_NAME:} fineract.tenant.config.min-pool-size=${FINERACT_CONFIG_MIN_POOL_SIZE:-1} fineract.tenant.config.max-pool-size=${FINERACT_CONFIG_MAX_POOL_SIZE:-1} +fineract.tenant.config.rounding-mode=${FINERACT_CONFIG_ROUNDING_MODE:6} fineract.mode.read-enabled=${FINERACT_MODE_READ_ENABLED:true} fineract.mode.write-enabled=${FINERACT_MODE_WRITE_ENABLED:true} @@ -410,6 +411,7 @@ spring.liquibase.parameters.fineract.tenant.read-only-username=${fineract.tenant spring.liquibase.parameters.fineract.tenant.read-only-password=${fineract.tenant.read-only-password} spring.liquibase.parameters.fineract.tenant.read-only-parameters=${fineract.tenant.read-only-parameters} spring.liquibase.parameters.fineract.tenant.read-only-name=${fineract.tenant.read-only-name} +spring.liquibase.parameters.fineract.tenant.rounding-mode=${fineract.tenant.config.rounding-mode} springdoc.api-docs.path=${SPRINGDOC_API_DOCS_PATH:/api-docs} springdoc.api-docs.enabled=${SPRINGDOC_API_DOCS_ENABLED:true} diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml index 69cb27587..a4037a9fa 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml @@ -217,7 +217,7 @@ - + diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/TrapDoorConfigurationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/TrapDoorConfigurationTest.java new file mode 100644 index 000000000..63853db2d --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/TrapDoorConfigurationTest.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationProperty; +import org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationPropertyCannotBeModfied; +import org.apache.fineract.infrastructure.configuration.service.GlobalConfigurationPropertyUpdateService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +@ExtendWith(MockitoExtension.class) +public class TrapDoorConfigurationTest { + + @Mock + private JdbcTemplate jdbcTemplate; + + @InjectMocks + private GlobalConfigurationPropertyUpdateService propertyUpdateService; + + private GlobalConfigurationProperty configurationProperty; + + private JsonCommand jsonCommand; + + @BeforeEach + public void setUp() { + configurationProperty = new GlobalConfigurationProperty(); + configurationProperty.setId(1L); + configurationProperty.setName("config-name"); + configurationProperty.setTrapDoor(true); + configurationProperty.setEnabled(true); + configurationProperty.setValue(3L); + + JsonCommand jsonCommandMock = Mockito.mock(JsonCommand.class); + + Mockito.lenient().when(jsonCommandMock.isChangeInBooleanParameterNamed(Mockito.any(), Mockito.any())).thenReturn(false); + + Mockito.lenient().when(jsonCommandMock.isChangeInLongParameterNamed(Mockito.any(), Mockito.any())).thenReturn(true); + Mockito.lenient().when(jsonCommandMock.longValueOfParameterNamed(Mockito.any())).thenReturn(4L); + + Mockito.lenient().when(jsonCommandMock.isChangeInDateParameterNamed(Mockito.any(), Mockito.any())).thenReturn(false); + Mockito.lenient().when(jsonCommandMock.isChangeInStringParameterNamed(Mockito.any(), Mockito.any())).thenReturn(false); + + jsonCommand = jsonCommandMock; + } + + @Test + public void testTrapDoorConfigurationWhenProductsExist() { + Mockito.when(jdbcTemplate.queryForObject(Mockito.anyString(), Mockito.eq(Boolean.class))).thenReturn(true); + + Assertions.assertThrows(GlobalConfigurationPropertyCannotBeModfied.class, + () -> propertyUpdateService.update(configurationProperty, jsonCommand)); + } + + @Test + public void testTrapDoorConfigurationWhenNoProductsExist() { + Mockito.when(jdbcTemplate.queryForObject(Mockito.anyString(), Mockito.eq(Boolean.class))).thenReturn(false); + + Assertions.assertDoesNotThrow(() -> propertyUpdateService.update(configurationProperty, jsonCommand)); + } +}