Skip to content

Commit

Permalink
FINERACT-2081: Fetch configurations by name
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsaghy committed Oct 1, 2024
1 parent 03d320e commit 39d95f3
Show file tree
Hide file tree
Showing 87 changed files with 1,539 additions and 1,726 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* 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.api;

public final class GlobalConfigurationConstants {

public static final String MAKER_CHECKER = "maker-checker";
public static final String AMAZON_S3 = "amazon-s3";
public static final String RESCHEDULE_FUTURE_REPAYMENTS = "reschedule-future-repayments";
public static final String RESCHEDULE_REPAYMENTS_ON_HOLIDAYS = "reschedule-repayments-on-holidays";
public static final String ALLOW_TRANSACTIONS_ON_HOLIDAY = "allow-transactions-on-holiday";
public static final String ALLOW_TRANSACTIONS_ON_NON_WORKING_DAY = "allow-transactions-on-non-workingday";
public static final String CONSTRAINT_APPROACH_FOR_DATATABLES = "constraint-approach-for-datatables";
public static final String PENALTY_WAIT_PERIOD = "penalty-wait-period";
public static final String FORCE_PASSWORD_RESET_DAYS = "force-password-reset-days";
public static final String GRACE_ON_PENALTY_POSTING = "grace-on-penalty-posting";
public static final String SAVINGS_INTEREST_POSTING_CURRENT_PERIOD_END = "savings-interest-posting-current-period-end";
public static final String FINANCIAL_YEAR_BEGINNING_MONTH = "financial-year-beginning-month";
public static final String MIN_CLIENTS_IN_GROUP = "min-clients-in-group";
public static final String MAX_CLIENTS_IN_GROUP = "max-clients-in-group";
public static final String MEETINGS_MANDATORY_FOR_JLG_LOANS = "meetings-mandatory-for-jlg-loans";
public static final String OFFICE_SPECIFIC_PRODUCTS_ENABLED = "office-specific-products-enabled";
public static final String RESTRICT_PRODUCTS_TO_USER_OFFICE = "restrict-products-to-user-office";
public static final String OFFICE_OPENING_BALANCES_CONTRA_ACCOUNT = "office-opening-balances-contra-account";
public static final String ROUNDING_MODE = "rounding-mode";
public static final String BACKDATE_PENALTIES_ENABLED = "backdate-penalties-enabled";
public static final String ORGANISATION_START_DATE = "organisation-start-date";
public static final String PAYMENT_TYPE_APPLICABLE_FOR_DISBURSEMENT_CHARGES = "paymenttype-applicable-for-disbursement-charges";
public static final String INTEREST_CHARGED_FROM_DATE_SAME_AS_DISBURSAL_DATE = "interest-charged-from-date-same-as-disbursal-date";
public static final String SKIP_REPAYMENT_ON_FIRST_DAY_OF_MONTH = "skip-repayment-on-first-day-of-month";
public static final String CHANGE_EMI_IF_REPAYMENT_DATE_SAME_AS_DISBURSEMENT_DATE = "change-emi-if-repaymentdate-same-as-disbursementdate";
public static final String DAILY_TPT_LIMIT = "daily-tpt-limit";
public static final String ENABLE_ADDRESS = "enable-address";
public static final String SUB_RATES = "sub-rates";
public static final String LOAN_RESCHEDULE_IS_FIRST_PAYDAY_ALLOWED_ON_HOLIDAY = "loan-reschedule-is-first-payday-allowed-on-holiday";
public static final String ACCOUNT_MAPPING_FOR_PAYMENT_TYPE = "account-mapping-for-payment-type";
public static final String ACCOUNT_MAPPING_FOR_CHARGE = "account-mapping-for-charge";
public static final String FIXED_DEPOSIT_TRANSFER_INTEREST_NEXT_DAY_FOR_PERIOD_END_POSTING = "fixed-deposit-transfer-interest-next-day-for-period-end-posting";
public static final String ALLOW_BACKDATED_TRANSACTION_BEFORE_INTEREST_POSTING = "allow-backdated-transaction-before-interest-posting";
public static final String ALLOW_BACKDATED_TRANSACTION_BEFORE_INTEREST_POSTING_DATE_FOR_DAYS = "allow-backdated-transaction-before-interest-posting-date-for-days";
public static final String CUSTOM_ACCOUNT_NUMBER_LENGTH = "custom-account-number-length";
public static final String RANDOM_ACCOUNT_NUMBER = "random-account-number";
public static final String IS_INTEREST_TO_BE_RECOVERED_FIRST_WHEN_GREATER_THAN_EMI = "is-interest-to-be-recovered-first-when-greater-than-emi";
public static final String IS_PRINCIPAL_COMPOUNDING_DISABLED_FOR_OVERDUE_LOANS = "is-principal-compounding-disabled-for-overdue-loans";
public static final String ENABLE_BUSINESS_DATE = "enable-business-date";
public static final String ENABLE_AUTOMATIC_COB_DATE_ADJUSTMENT = "enable-automatic-cob-date-adjustment";
public static final String ENABLE_POST_REVERSAL_TXNS_FOR_REVERSE_TRANSACTIONS = "enable-post-reversal-txns-for-reverse-transactions";
public static final String PURGE_EXTERNAL_EVENTS_OLDER_THAN_DAYS = "purge-external-events-older-than-days";
public static final String DAYS_BEFORE_REPAYMENT_IS_DUE = "days-before-repayment-is-due";
public static final String DAYS_AFTER_REPAYMENT_IS_OVERDUE = "days-after-repayment-is-overdue";
public static final String ENABLE_AUTO_GENERATED_EXTERNAL_ID = "enable-auto-generated-external-id";
public static final String PURGE_PROCESSED_COMMANDS_OLDER_THAN_DAYS = "purge-processed-commands-older-than-days";
public static final String ENABLE_COB_BULK_EVENT = "enable-cob-bulk-event";
public static final String EXTERNAL_EVENT_BATCH_SIZE = "external-event-batch-size";
public static final String REPORT_EXPORT_S3_FOLDER_NAME = "report-export-s3-folder-name";
public static final String LOAN_ARREARS_DELINQUENCY_DISPLAY_DATA = "loan-arrears-delinquency-display-data";
public static final String CHARGE_ACCRUAL_DATE = "charge-accrual-date";
public static final String ASSET_EXTERNALIZATION_OF_NON_ACTIVE_LOANS = "asset-externalization-of-non-active-loans";
public static final String ENABLE_SAME_MAKER_CHECKER = "enable-same-maker-checker";
public static final String NEXT_PAYMENT_DUE_DATE = "next-payment-due-date";
public static final String ENABLE_PAYMENT_HUB_INTEGRATION = "enable-payment-hub-integration";

private GlobalConfigurationConstants() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 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.exception;

public class GlobalConfigurationException extends RuntimeException {

public GlobalConfigurationException(final String name) {
super("Global configuration with name: '" + name
+ "' is not in the supported format! Global configuration name can only contains lowercase letters or -, and must start with letters!");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* 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 static org.apache.commons.collections4.CollectionUtils.isNotEmpty;

import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationException;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.JdbcTemplateFactory;
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@RequiredArgsConstructor
@Service
public class GlobalConfigurationValidationService implements InitializingBean {

private static final String GLOBAL_CONFIGURATION_NAME_PATTERN = "^[a-z][a-z0-9-]*$";
private final TenantDetailsService tenantDetailsService;
private final JdbcTemplateFactory jdbcTemplateFactory;

@Override
public void afterPropertiesSet() throws Exception {
validateGlobalConfigurationNames();
}

private void validateGlobalConfigurationNames() {
List<FineractPlatformTenant> tenants = tenantDetailsService.findAllTenants();

if (isNotEmpty(tenants)) {
for (FineractPlatformTenant tenant : tenants) {
validateGlobalConfigurationForIndividualTenant(tenant);
}
}
}

private void validateGlobalConfigurationForIndividualTenant(FineractPlatformTenant tenant) {
log.debug("Validating global configuration for {}", tenant.getTenantIdentifier());
List<String> globalConfigurationNames = getGlobalConfigurationNames(tenant);

globalConfigurationNames.forEach(globalConfigurationName -> {
if (!globalConfigurationName.matches(GLOBAL_CONFIGURATION_NAME_PATTERN)) {
throw new GlobalConfigurationException(globalConfigurationName);
}
});
}

private List<String> getGlobalConfigurationNames(FineractPlatformTenant tenant) {
final JdbcTemplate jdbcTemplate = jdbcTemplateFactory.create(tenant);
return jdbcTemplate.queryForList("select gc.name as name from c_configuration gc", String.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.event.external.service;
package org.apache.fineract.infrastructure.core.service;

import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.JdbcTemplateFactory;
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import org.apache.fineract.infrastructure.event.business.domain.BulkBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalConfigurationGlobalInitializerStep implements FineractGlobalInitializerStep {

public static final String CONFIG_KEY_ENABLE_ADDRESS = "Enable-Address";
public static final String CONFIG_KEY_ENABLE_ADDRESS = "enable-address";
public static final String CONFIG_KEY_ENABLE_INTEREST_CALCULATION = "interest-charged-from-date-same-as-disbursal-date";
public static final String CONFIG_KEY_ENABLE_BUSINESS_DATE = "enable_business_date";
public static final String CONFIG_KEY_ENABLE_RECALCULATE_COB_DATE = "enable_automatic_cob_date_adjustment";
public static final String CONFIG_KEY_ENABLE_BUSINESS_DATE = "enable-business-date";
public static final String CONFIG_KEY_ENABLE_RECALCULATE_COB_DATE = "enable-automatic-cob-date-adjustment";
public static final String CONFIG_KEY_DAYS_BEFORE_REPAYMENT_IS_DUE = "days-before-repayment-is-due";
public static final String CONFIG_KEY_DAYS_AFTER_REPAYMENT_IS_OVERDUE = "days-after-repayment-is-overdue";
public static final String CONFIG_KEY_ENABLE_AUTO_GENERATED_EXTERNAL_ID = "enable-auto-generated-external-id";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ Feature: BusinessDate


Scenario: As a user I would like to enable the Business date configuration
Given Global configuration "enable_business_date" is enabled
Given Global configuration "enable-business-date" is enabled


Scenario: As a user I would like to disable the Business date configuration
Given Global configuration "enable_business_date" is disabled
Given Global configuration "enable-business-date" is disabled


Scenario: As a user I would like to set the business date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Feature: Client


Scenario Outline: Client creation with address functionality for Fineract
When Global configuration "Enable-Address" is enabled
When Global configuration "enable-address" is enabled
When Admin creates a client with Firstname <firstName> and Lastname <lastName> with address
Then Client is created successfully
Examples:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5659,7 +5659,7 @@ Feature: Loan
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule

Scenario: Interest recalculation - S1 daily for overdue loan
Given Global configuration "enable_business_date" is enabled
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "1 January 2024"
When Admin creates a client with random data
When Admin creates a fully customized loan with the following data:
Expand All @@ -5682,7 +5682,7 @@ Feature: Loan
When Admin removes "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow

Scenario: Interest recalculation - S2 2 overdue
Given Global configuration "enable_business_date" is enabled
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "1 January 2024"
When Admin creates a client with random data
When Admin creates a fully customized loan with the following data:
Expand All @@ -5705,7 +5705,7 @@ Feature: Loan
When Admin removes "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow

Scenario: Interest recalculation - S3 1 paid, 1 overdue
Given Global configuration "enable_business_date" is enabled
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "1 January 2024"
When Admin creates a client with random data
When Admin creates a fully customized loan with the following data:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
@Tag(name = "Global Configuration", description = "Global configuration related to set of supported enable/disable configurations:\n" + "\n"
+ "maker-checker - defaults to false - if true turns on maker-checker functionality\n"
+ "reschedule-future-repayments - defaults to false - if true reschedules repayemnts which falls on a non-working day to configured repayment rescheduling rule\n"
+ "allow-transactions-on-non_workingday - defaults to false - if true allows transactions on non-working days\n"
+ "allow-transactions-on-non-workingday - defaults to false - if true allows transactions on non-working days\n"
+ "reschedule-repayments-on-holidays - defaults to false - if true reschedules repayemnts which falls on a non-working day to defined reschedule date\n"
+ "allow-transactions-on-holiday - defaults to false - if true allows transactions on holidays\n"
+ "savings-interest-posting-current-period-end - Set it at the database level before any savings interest is posted. When set as false(default), interest will be posted on the first date of next period. If set as true, interest will be posted on last date of current period. There is no difference in the interest amount posted.\n"
Expand Down Expand Up @@ -123,7 +123,7 @@ public String retrieveOne(@PathParam("configId") @Parameter(description = "confi
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Global Configuration", description = "Returns a global enable/disable configuration.\n" + "\n"
+ "Example Requests:\n" + "\n" + "configurations/name/Enable-Address")
+ "Example Requests:\n" + "\n" + "configurations/name/enable-address")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GlobalConfigurationPropertyData.class))) })
public String retrieveOneByName(@PathParam("name") @Parameter(description = "name") final String name, @Context final UriInfo uriInfo) {
Expand Down Expand Up @@ -156,4 +156,29 @@ public String updateConfiguration(@PathParam("configId") @Parameter(description

return this.toApiJsonSerializer.serialize(result);
}

@PUT
@Path("/name/{configName}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Global Configuration by name", description = "Updates an enable/disable global configuration item by name")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = GlobalConfigurationApiResourceSwagger.PutGlobalConfigurationsRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GlobalConfigurationApiResourceSwagger.PutGlobalConfigurationsResponse.class))) })
public String updateConfigurationByName(@PathParam("configName") @Parameter(description = "configName") final String configName,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {

// TODO: Would be better to support string based identifier in Commands and resolve the entity by name in the
// service
final GlobalConfigurationPropertyData configurationData = this.readPlatformService.retrieveGlobalConfiguration(configName);

final CommandWrapper commandRequest = new CommandWrapperBuilder() //
.updateGlobalConfiguration(configurationData.getId()) //
.withJson(apiRequestBodyAsJson) //
.build();

final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);

return this.toApiJsonSerializer.serialize(result);
}
}
Loading

0 comments on commit 39d95f3

Please sign in to comment.