fd product releated update code

This commit is contained in:
Nitesh Yadav
2026-05-05 15:18:35 +05:30
parent be220d8778
commit 4735111a4f
33 changed files with 63380 additions and 44790 deletions
@@ -274,6 +274,46 @@ public final class DepositsApiConstants {
fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS);
fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
fixedDepositRequestData.add("accountType");
fixedDepositRequestData.add("customerSegment");
fixedDepositRequestData.add("productCode");
fixedDepositRequestData.add("fdType");
fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName);
fixedDepositRequestData.add("effectiveDate");
fixedDepositRequestData.add("effectiveTo");
fixedDepositRequestData.add("endDate");
fixedDepositRequestData.add("introducerRequired");
fixedDepositRequestData.add("shareLinkingEnabled");
fixedDepositRequestData.add("nominationFacility");
fixedDepositRequestData.add("dormancyAlert");
fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName);
fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName);
fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName);
fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName);
fixedDepositRequestData.add("interestCalculateSelector");
fixedDepositRequestData.add("averagePeriod");
fixedDepositRequestData.add("autoPost");
fixedDepositRequestData.add("frequencyFixDay");
fixedDepositRequestData.add("lastInterestDate");
fixedDepositRequestData.add("enableLockinPeriod");
fixedDepositRequestData.add("balanceFrequency");
fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing");
fixedDepositRequestData.add("autoDebitFacility");
fixedDepositRequestData.add("loanFacility");
fixedDepositRequestData.add("prematureWithdrawalOption");
fixedDepositRequestData.add("tenureFlexibility");
fixedDepositRequestData.add("lienMarked");
fixedDepositRequestData.add("withdrawalFeeForTransfers");
fixedDepositRequestData.add("maturityInstructions");
fixedDepositRequestData.add("creditMaturityProceeds");
fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments");
fixedDepositRequestData.add("panelRequestApplicable");
fixedDepositRequestData.add("noPenaltyNomineeHeirClosure");
fixedDepositRequestData.add("deceasedSettlement");
fixedDepositRequestData.add("allowDebitTxnAfterDueDate");
fixedDepositRequestData.add("escheatLiabilityId");
fixedDepositRequestData.add("fdSlabs");
return fixedDepositRequestData;
}
@@ -282,6 +322,54 @@ public final class DepositsApiConstants {
fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS);
fixedDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS);
fixedDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
fixedDepositRequestData.add("interestCalculationTypeOption");
fixedDepositRequestData.add("periodFrequencyOptions");
fixedDepositRequestData.add("yesNoOptions");
fixedDepositRequestData.add("accountTypeOptions");
fixedDepositRequestData.add("customerSegmentOptions");
fixedDepositRequestData.add("accountType");
fixedDepositRequestData.add("customerSegment");
fixedDepositRequestData.add("productCode");
fixedDepositRequestData.add("fdType");
fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName);
fixedDepositRequestData.add("glAccountName");
fixedDepositRequestData.add("effectiveDate");
fixedDepositRequestData.add("effectiveTo");
fixedDepositRequestData.add("endDate");
fixedDepositRequestData.add("introducerRequired");
fixedDepositRequestData.add("shareLinkingEnabled");
fixedDepositRequestData.add("nominationFacility");
fixedDepositRequestData.add("dormancyAlert");
fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName);
fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName);
fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName);
fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName);
fixedDepositRequestData.add("interestCalculateSelector");
fixedDepositRequestData.add("averagePeriod");
fixedDepositRequestData.add("autoPost");
fixedDepositRequestData.add("frequencyFixDay");
fixedDepositRequestData.add("lastInterestDate");
fixedDepositRequestData.add("enableLockinPeriod");
fixedDepositRequestData.add("balanceFrequency");
fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing");
fixedDepositRequestData.add("autoDebitFacility");
fixedDepositRequestData.add("loanFacility");
fixedDepositRequestData.add("prematureWithdrawalOption");
fixedDepositRequestData.add("tenureFlexibility");
fixedDepositRequestData.add("lienMarked");
fixedDepositRequestData.add("withdrawalFeeForTransfers");
fixedDepositRequestData.add("maturityInstructions");
fixedDepositRequestData.add("creditMaturityProceeds");
fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments");
fixedDepositRequestData.add("panelRequestApplicable");
fixedDepositRequestData.add("noPenaltyNomineeHeirClosure");
fixedDepositRequestData.add("deceasedSettlement");
fixedDepositRequestData.add("allowDebitTxnAfterDueDate");
fixedDepositRequestData.add("accountingMappings");
fixedDepositRequestData.add("paymentChannelToFundSourceMappings");
fixedDepositRequestData.add("feeToIncomeAccountMappings");
fixedDepositRequestData.add("penaltyToIncomeAccountMappings");
return fixedDepositRequestData;
}
@@ -332,6 +420,7 @@ public final class DepositsApiConstants {
recurringDepositRequestData.add("deceasedSettlement");
recurringDepositRequestData.add("allowDebitTxnAfterDueDate");
recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing");
recurringDepositRequestData.add("escheatLiabilityId");
// Custom CBS fields — Interest rate slabs (sent inline but ignored server-side; saved separately)
recurringDepositRequestData.add("rdSlabs");
return recurringDepositRequestData;
@@ -387,6 +476,11 @@ public final class DepositsApiConstants {
recurringDepositRequestData.add("noPenaltyNomineeHeirClosure");
recurringDepositRequestData.add("deceasedSettlement");
recurringDepositRequestData.add("allowDebitTxnAfterDueDate");
recurringDepositRequestData.add("escheatLiabilityId");
recurringDepositRequestData.add("accountingMappings");
recurringDepositRequestData.add("paymentChannelToFundSourceMappings");
recurringDepositRequestData.add("feeToIncomeAccountMappings");
recurringDepositRequestData.add("penaltyToIncomeAccountMappings");
return recurringDepositRequestData;
}
@@ -517,6 +611,10 @@ public final class DepositsApiConstants {
recurringDepositResponseData.add("noPenaltyNomineeHeirClosure");
recurringDepositResponseData.add("deceasedSettlement");
recurringDepositResponseData.add("allowDebitTxnAfterDueDate");
recurringDepositResponseData.add("accountingMappings");
recurringDepositResponseData.add("paymentChannelToFundSourceMappings");
recurringDepositResponseData.add("feeToIncomeAccountMappings");
recurringDepositResponseData.add("penaltyToIncomeAccountMappings");
return recurringDepositResponseData;
}
@@ -32,6 +32,8 @@ public class SavingsApiConstants {
public static final String CURRENT_PRODUCT_CUSTOMER_SEGMENT = "CurrentProductCustomerSegment";
public static final String RD_PRODUCT_ACCOUNT_TYPE = "RecurringDepositProductAccountType";
public static final String RD_PRODUCT_CUSTOMER_SEGMENT = "RecurringDepositProductCustomerSegment";
public static final String FD_PRODUCT_ACCOUNT_TYPE = "FixedDepositProductAccountType";
public static final String FD_PRODUCT_CUSTOMER_SEGMENT = "FixedDepositProductCustomerSegment";
public static final String PERIOD_FREQUENCY = "PeriodFrequency";
public static final String INTEREST_CALCULATION_TYPE_OPTION = "InterestCalculationTypeOption";
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.api;
import java.math.BigDecimal;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class FdInterestRateSaveRequest {
private String fdType;
private String versionLabel;
private String remarks;
private List<SlabRequest> slabs;
@Getter
@Setter
@NoArgsConstructor
public static class SlabRequest {
private Integer srNo;
private String description;
private Integer periodTypeEnum;
private Integer fromPeriod;
private Integer toPeriod;
private BigDecimal annualInterestRate;
private BigDecimal seniorCitizenRate;
}
}
@@ -54,9 +54,13 @@ import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import java.util.Locale;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
@@ -110,6 +114,8 @@ public class FixedDepositProductsApiResource {
private final DropdownReadPlatformService dropdownReadPlatformService;
private final PaymentTypeReadService paymentTypeReadPlatformService;
private final TaxReadPlatformService taxReadPlatformService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final org.springframework.jdbc.core.JdbcTemplate jdbcTemplate;
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@@ -315,6 +321,16 @@ public class FixedDepositProductsApiResource {
// interest rate chart template
final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template();
// CBS custom options
final Collection<EnumOptionData> interestCalculationTypeOption = retrieveInterestCalculationTypeOption();
final Collection<EnumOptionData> periodFrequencyOptions = retrievePeriodFrequencyOptions();
final List<CodeValueData> yesNoOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.YES_NO));
final List<CodeValueData> accountTypeOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE));
final List<CodeValueData> customerSegmentOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT));
FixedDepositProductData fixedDepositProductToReturn = null;
if (savingsProduct != null) {
fixedDepositProductToReturn = FixedDepositProductData.withTemplate(savingsProduct, currencyOptions,
@@ -329,11 +345,49 @@ public class FixedDepositProductsApiResource {
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions);
fixedDepositProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "FD-IND-"));
}
fixedDepositProductToReturn.setInterestCalculationTypeOption(interestCalculationTypeOption);
fixedDepositProductToReturn.setPeriodFrequencyOptions(periodFrequencyOptions);
fixedDepositProductToReturn.setYesNoOptions(yesNoOptions);
fixedDepositProductToReturn.setAccountTypeOptions(accountTypeOptions);
fixedDepositProductToReturn.setCustomerSegmentOptions(customerSegmentOptions);
return fixedDepositProductToReturn;
}
private Collection<EnumOptionData> retrievePeriodFrequencyOptions() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.PERIOD_FREQUENCY).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"periodFrequencyType." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'), codeValue.getName()))
.toList();
}
private Collection<EnumOptionData> retrieveInterestCalculationTypeOption() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.INTEREST_CALCULATION_TYPE_OPTION).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"interestCalculationTypeOption." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'),
codeValue.getName()))
.toList();
}
private String generateNextProductCode(final String tableName, final String prefix) {
try {
String maxCode = jdbcTemplate.queryForObject(
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ? AND deposit_type_enum = 200",
String.class, prefix + "%");
if (maxCode == null || maxCode.isBlank()) {
return prefix + "001";
}
String numStr = maxCode.substring(prefix.length());
int num = Integer.parseInt(numStr);
return prefix + String.format("%03d", num + 1);
} catch (Exception e) {
return prefix + "001";
}
}
@DELETE
@Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON })
@@ -342,6 +342,7 @@ final class FixedDepositProductsApiResourceSwagger {
public GetFixedDepositProductsGlAccount interestOnSavingsAccount;
public GetFixedDepositProductsGlAccount savingsControlAccount;
public GetFixedDepositProductsGlAccount transfersInSuspenseAccount;
public GetFixedDepositProductsGlAccount escheatLiabilityAccount;
public GetFixedDepositProductsGlAccount interestPayableAccount;
}
@@ -0,0 +1,87 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.api;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
import org.springframework.stereotype.Component;
@Path("/v1/fixeddepositproducts/baseinterestrates")
@Component
@RequiredArgsConstructor
public class FixedDepositProductsBaseInterestRatesApiResource {
private final PlatformSecurityContext context;
private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService;
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class,
(JsonSerializer<LocalDate>) (src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString()))
.create();
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Get all active FD base interest rate charts", description = "Returns active CBS FD base interest-rate versions with slabs. Optionally filter by fdType (SINGLE, DOUBLE, SPECIAL).")
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
public String retrieveAllActiveInterestRates(@QueryParam("fdType") final String fdType) {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
final List<FdInterestRateEffectiveData> data = fdType != null && !fdType.isBlank()
? this.fdBaseInterestRateReadPlatformService.retrieveActiveInterestRatesByType(fdType)
: this.fdBaseInterestRateReadPlatformService.retrieveAllActiveInterestRates();
return gson.toJson(data);
}
@GET
@Path("history")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Get all FD base interest rate charts", description = "Returns the full FD base chart history.")
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
public String retrieveHistory() {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
final List<FdInterestRateEffectiveData> data = this.fdBaseInterestRateReadPlatformService.retrieveAllInterestRates();
return gson.toJson(data);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Save new FD base interest rate version", description = "Creates a new FD CBS base chart version for the given fdType and deactivates the previous active version.")
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
public String saveInterestRates(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
final FdInterestRateSaveRequest request = gson.fromJson(apiRequestBodyAsJson, FdInterestRateSaveRequest.class);
final Long createdById = this.context.authenticatedUser().getId();
final FdInterestRateEffectiveData saved = this.fdBaseInterestRateReadPlatformService.saveInterestRates(request, createdById);
return gson.toJson(saved);
}
}
@@ -360,6 +360,7 @@ final class RecurringDepositProductsApiResourceSwagger {
public GetRecurringDepositProductsGlAccount interestOnSavingsAccount;
public GetRecurringDepositProductsGlAccount savingsControlAccount;
public GetRecurringDepositProductsGlAccount transfersInSuspenseAccount;
public GetRecurringDepositProductsGlAccount escheatLiabilityAccount;
public GetRecurringDepositProductsGlAccount feeReceivableAccount;
public GetRecurringDepositProductsGlAccount penaltyReceivableAccount;
public GetRecurringDepositProductsGlAccount interestPayableAccount;
@@ -285,6 +285,7 @@ final class SavingsProductsApiResourceSwagger {
public GetSavingsProductsGlAccount writeOffAccount;
public GetSavingsProductsGlAccount savingsControlAccount;
public GetSavingsProductsGlAccount transfersInSuspenseAccount;
public GetSavingsProductsGlAccount escheatLiabilityAccount;
public GetSavingsProductsGlAccount interestPayableAccount;
}
@@ -632,8 +632,12 @@ public class DepositProductDataValidator {
}
private void validateDepositAmountForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) {
final BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
final BigDecimal depositAmount = this.fromApiJsonHelper.parameterExists(depositAmountParamName, element)
? this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element)
: null;
if (depositAmount != null) {
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).positiveAmount();
}
BigDecimal depositMinAmount = null;
if (this.fromApiJsonHelper.parameterExists(depositMinAmountParamName, element)) {
@@ -667,7 +671,7 @@ public class DepositProductDataValidator {
if (this.fromApiJsonHelper.parameterExists(depositAmountParamName, element)) {
depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).positiveAmount();
}
BigDecimal depositMinAmount = null;
@@ -0,0 +1,31 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.data;
import java.time.LocalDate;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class FdInterestRateEffectiveData {
private Long id;
private String fdType;
private String versionLabel;
private LocalDate effectiveFromDate;
private LocalDate effectiveToDate;
private Boolean isActive;
private String remarks;
private List<FdInterestRateSlabData> slabs;
}
@@ -0,0 +1,31 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.data;
import java.math.BigDecimal;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class FdInterestRateSlabData {
private Long id;
private Integer srNo;
private String description;
private Integer periodTypeEnum;
private String periodTypeLabel;
private Integer fromPeriod;
private Integer toPeriod;
private BigDecimal annualInterestRate;
private BigDecimal seniorCitizenRate;
}
@@ -0,0 +1,75 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDate;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "m_fd_base_interest_rate_effective")
@Getter
@NoArgsConstructor
public class FdBaseInterestRateEffective {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "fd_type", length = 20)
private String fdType;
@Column(name = "version_label")
private String versionLabel;
@Column(name = "effective_from_date")
private LocalDate effectiveFromDate;
@Column(name = "effective_to_date")
private LocalDate effectiveToDate;
@Column(name = "is_active")
private Boolean isActive;
@Column(name = "remarks")
private String remarks;
@Column(name = "created_by_id")
private Long createdById;
@Column(name = "created_date")
private LocalDate createdDate;
public static FdBaseInterestRateEffective create(String fdType, String versionLabel, LocalDate effectiveFromDate, String remarks,
Long createdById) {
FdBaseInterestRateEffective e = new FdBaseInterestRateEffective();
e.fdType = fdType;
e.versionLabel = versionLabel;
e.effectiveFromDate = effectiveFromDate;
e.isActive = true;
e.remarks = remarks;
e.createdById = createdById;
e.createdDate = LocalDate.now();
return e;
}
public void deactivate(LocalDate toDate) {
this.isActive = false;
this.effectiveToDate = toDate;
}
}
@@ -0,0 +1,26 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FdBaseInterestRateEffectiveRepository extends JpaRepository<FdBaseInterestRateEffective, Long> {
List<FdBaseInterestRateEffective> findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(String fdType);
List<FdBaseInterestRateEffective> findByFdTypeOrderByEffectiveFromDateDesc(String fdType);
List<FdBaseInterestRateEffective> findByIsActiveTrueOrderByEffectiveFromDateDesc();
List<FdBaseInterestRateEffective> findByOrderByEffectiveFromDateDesc();
}
@@ -0,0 +1,76 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "m_fd_base_interest_rate_slab_history")
@Getter
@NoArgsConstructor
public class FdBaseInterestRateSlabHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "effective_id")
private Long effectiveId;
@Column(name = "sr_no")
private Integer srNo;
@Column(name = "description")
private String description;
@Column(name = "period_type_enum")
private Integer periodTypeEnum;
@Column(name = "from_period")
private Integer fromPeriod;
@Column(name = "to_period")
private Integer toPeriod;
@Column(name = "annual_interest_rate")
private BigDecimal annualInterestRate;
@Column(name = "senior_citizen_rate")
private BigDecimal seniorCitizenRate;
@Column(name = "created_date")
private LocalDate createdDate;
public static FdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description, Integer periodTypeEnum,
Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate, BigDecimal seniorCitizenRate) {
FdBaseInterestRateSlabHistory s = new FdBaseInterestRateSlabHistory();
s.effectiveId = effectiveId;
s.srNo = srNo;
s.description = description;
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
s.fromPeriod = fromPeriod;
s.toPeriod = toPeriod;
s.annualInterestRate = annualInterestRate;
s.seniorCitizenRate = seniorCitizenRate;
s.createdDate = LocalDate.now();
return s;
}
}
@@ -0,0 +1,20 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FdBaseInterestRateSlabHistoryRepository extends JpaRepository<FdBaseInterestRateSlabHistory, Long> {
List<FdBaseInterestRateSlabHistory> findByEffectiveIdOrderBySrNo(Long effectiveId);
}
@@ -230,11 +230,37 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, ");
sqlBuilder.append("dptp.min_deposit_amount as minDepositAmount, dptp.deposit_amount as depositAmount, ");
sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount ");
sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount, ");
sqlBuilder.append("sp.account_type as accountType, sp.fd_type as fdType, sp.product_code as productCode, ");
sqlBuilder.append("sp.customer_segment as customerSegment, ");
sqlBuilder.append("sp.gl_account_id as glAccountId, gla.name as glAccountName, ");
sqlBuilder.append("sp.effective_date as effectiveDate, sp.effective_to as effectiveTo, sp.end_date as endDate, ");
sqlBuilder.append("sp.introducer_required as introducerRequired, sp.share_linking_enabled as shareLinkingEnabled, ");
sqlBuilder.append("sp.nomination_facility as nominationFacility, sp.dormancy_alert as dormancyAlert, ");
sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, ");
sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, ");
sqlBuilder.append("sp.days_to_escheat as daysToEscheat, ");
sqlBuilder.append("sp.interest_calculate_selector as interestCalculateSelector, ");
sqlBuilder.append("sp.average_period as averagePeriod, ");
sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, ");
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, sp.loan_facility as loanFacility, ");
sqlBuilder.append("sp.premature_withdrawal_option as prematureWithdrawalOption, sp.tenure_flexibility as tenureFlexibility, ");
sqlBuilder.append("sp.lien_marked as lienMarked, sp.maturity_instructions as maturityInstructions, ");
sqlBuilder.append("sp.credit_maturity_proceeds as creditMaturityProceeds, ");
sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, ");
sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, ");
sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, ");
sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, ");
sqlBuilder.append("sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, ");
sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers ");
sqlBuilder.append("from m_savings_product sp ");
sqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id ");
sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
sqlBuilder.append(" left join m_tax_group tg on tg.id = sp.tax_group_id ");
sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id ");
sqlBuilder.append("left join acc_gl_account gla on gla.id = sp.gl_account_id ");
this.schemaSql = sqlBuilder.toString();
}
@@ -271,9 +297,55 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount");
final BigDecimal maxDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxDepositAmount");
return FixedDepositProductData.instance(depositProductData, preClosurePenalApplicable, preClosurePenalInterest,
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, maxDepositAmount);
final FixedDepositProductData result = FixedDepositProductData.instance(depositProductData, preClosurePenalApplicable,
preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType,
maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount,
maxDepositAmount);
// CBS Custom Fields
result.setAccountType(rs.getString("accountType"));
result.setFdType(rs.getString("fdType"));
result.setProductCode(rs.getString("productCode"));
result.setCustomerSegment(rs.getString("customerSegment"));
result.setGlAccountId(JdbcSupport.getLong(rs, "glAccountId"));
result.setGlAccountName(rs.getString("glAccountName"));
final java.time.LocalDate effectiveDate = JdbcSupport.getLocalDate(rs, "effectiveDate");
result.setEffectiveDate(effectiveDate != null ? effectiveDate.toString() : null);
final java.time.LocalDate effectiveTo = JdbcSupport.getLocalDate(rs, "effectiveTo");
result.setEffectiveTo(effectiveTo != null ? effectiveTo.toString() : null);
final java.time.LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
result.setEndDate(endDate != null ? endDate.toString() : null);
result.setIntroducerRequired(rs.getBoolean("introducerRequired"));
result.setShareLinkingEnabled(rs.getBoolean("shareLinkingEnabled"));
result.setNominationFacility(rs.getBoolean("nominationFacility"));
result.setDormancyAlert(rs.getBoolean("dormancyAlert"));
result.setIsDormancyTrackingActive(rs.getBoolean("isDormancyTrackingActive"));
result.setDaysToInactive(JdbcSupport.getLong(rs, "daysToInactive"));
result.setDaysToDormancy(JdbcSupport.getLong(rs, "daysToDormancy"));
result.setDaysToEscheat(JdbcSupport.getLong(rs, "daysToEscheat"));
result.setInterestCalculateSelector(JdbcSupport.getInteger(rs, "interestCalculateSelector"));
result.setAveragePeriod(rs.getString("averagePeriod"));
result.setAutoPost(rs.getBoolean("autoPost"));
result.setFrequencyFixDay(JdbcSupport.getInteger(rs, "frequencyFixDay"));
final java.time.LocalDate lastInterestDate = JdbcSupport.getLocalDate(rs, "lastInterestDate");
result.setLastInterestDate(lastInterestDate != null ? lastInterestDate.toString() : null);
result.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod"));
result.setBalanceFrequency(rs.getString("balanceFrequency"));
result.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing"));
result.setAutoDebitFacility(rs.getBoolean("autoDebitFacility"));
result.setLoanFacility(rs.getBoolean("loanFacility"));
result.setPrematureWithdrawalOption(rs.getBoolean("prematureWithdrawalOption"));
result.setTenureFlexibility(rs.getBoolean("tenureFlexibility"));
result.setLienMarked(rs.getBoolean("lienMarked"));
result.setMaturityInstructions(rs.getBoolean("maturityInstructions"));
result.setCreditMaturityProceeds(rs.getBoolean("creditMaturityProceeds"));
result.setPenaltyGracePeriodMissedInstallments(rs.getBoolean("penaltyGracePeriodMissedInstallments"));
result.setPanelRequestApplicable(rs.getBoolean("panelRequestApplicable"));
result.setNoPenaltyNomineeHeirClosure(rs.getBoolean("noPenaltyNomineeHeirClosure"));
result.setDeceasedSettlement(rs.getBoolean("deceasedSettlement"));
result.setAllowDebitTxnAfterDueDate(rs.getBoolean("allowDebitTxnAfterDueDate"));
result.setWithdrawalFeeForTransfers(rs.getBoolean("withdrawalFeeForTransfers"));
return result;
}
}
@@ -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
*/
package org.apache.fineract.portfolio.savings.service;
import java.util.List;
import org.apache.fineract.portfolio.savings.api.FdInterestRateSaveRequest;
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
public interface FdBaseInterestRateReadPlatformService {
List<FdInterestRateEffectiveData> retrieveAllInterestRates();
List<FdInterestRateEffectiveData> retrieveAllActiveInterestRates();
List<FdInterestRateEffectiveData> retrieveActiveInterestRatesByType(String fdType);
FdInterestRateEffectiveData saveInterestRates(FdInterestRateSaveRequest request, Long createdById);
}
@@ -0,0 +1,128 @@
/**
* 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
*/
package org.apache.fineract.portfolio.savings.service;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.savings.api.FdInterestRateSaveRequest;
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
import org.apache.fineract.portfolio.savings.data.FdInterestRateSlabData;
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffective;
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository;
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistory;
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
public class FdBaseInterestRateReadPlatformServiceImpl implements FdBaseInterestRateReadPlatformService {
private final FdBaseInterestRateEffectiveRepository effectiveRepository;
private final FdBaseInterestRateSlabHistoryRepository slabRepository;
@Override
public List<FdInterestRateEffectiveData> retrieveAllInterestRates() {
return effectiveRepository.findByOrderByEffectiveFromDateDesc().stream().map(this::toData).collect(Collectors.toList());
}
@Override
public List<FdInterestRateEffectiveData> retrieveAllActiveInterestRates() {
return effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc().stream().map(this::toData)
.collect(Collectors.toList());
}
@Override
public List<FdInterestRateEffectiveData> retrieveActiveInterestRatesByType(String fdType) {
return effectiveRepository.findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(fdType).stream().map(this::toData)
.collect(Collectors.toList());
}
@Override
@Transactional
public FdInterestRateEffectiveData saveInterestRates(FdInterestRateSaveRequest request, Long createdById) {
final String fdType = request.getFdType();
final LocalDate today = LocalDate.now();
final List<FdBaseInterestRateEffective> activeVersions = effectiveRepository
.findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(fdType);
for (FdBaseInterestRateEffective version : activeVersions) {
version.deactivate(today);
}
if (!activeVersions.isEmpty()) {
effectiveRepository.saveAll(activeVersions);
}
final String label = request.getVersionLabel() != null && !request.getVersionLabel().isBlank()
? request.getVersionLabel()
: "FD " + fdType + " Rate Version " + LocalDate.now();
FdBaseInterestRateEffective newVersion = FdBaseInterestRateEffective.create(fdType, label, today, request.getRemarks(), createdById);
newVersion = effectiveRepository.saveAndFlush(newVersion);
final Long versionId = newVersion.getId();
int srCounter = 1;
if (request.getSlabs() != null) {
for (FdInterestRateSaveRequest.SlabRequest slabRequest : request.getSlabs()) {
final int sr = slabRequest.getSrNo() != null ? slabRequest.getSrNo() : srCounter++;
final FdBaseInterestRateSlabHistory slab = FdBaseInterestRateSlabHistory.create(versionId, sr,
slabRequest.getDescription(), slabRequest.getPeriodTypeEnum(), slabRequest.getFromPeriod(),
slabRequest.getToPeriod(), slabRequest.getAnnualInterestRate(), slabRequest.getSeniorCitizenRate());
slabRepository.save(slab);
}
}
return toData(newVersion);
}
private FdInterestRateEffectiveData toData(FdBaseInterestRateEffective e) {
List<FdInterestRateSlabData> slabs = slabRepository.findByEffectiveIdOrderBySrNo(e.getId()).stream()
.map(s -> FdInterestRateSlabData.builder()
.id(s.getId())
.srNo(s.getSrNo())
.description(s.getDescription())
.periodTypeEnum(s.getPeriodTypeEnum())
.periodTypeLabel(periodLabel(s.getPeriodTypeEnum()))
.fromPeriod(s.getFromPeriod())
.toPeriod(s.getToPeriod())
.annualInterestRate(s.getAnnualInterestRate())
.seniorCitizenRate(s.getSeniorCitizenRate())
.build())
.collect(Collectors.toList());
return FdInterestRateEffectiveData.builder()
.id(e.getId())
.fdType(e.getFdType())
.versionLabel(e.getVersionLabel())
.effectiveFromDate(e.getEffectiveFromDate())
.effectiveToDate(e.getEffectiveToDate())
.isActive(e.getIsActive())
.remarks(e.getRemarks())
.slabs(slabs)
.build();
}
private String periodLabel(Integer code) {
if (code == null) {
return "Days";
}
return switch (code) {
case 0 -> "Days";
case 1 -> "Weeks";
case 2 -> "Months";
case 3 -> "Years";
default -> "Days";
};
}
}
@@ -1391,8 +1391,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public SavingsAccountData fallbackPostInterest(SavingsAccountData savingsAccountData, boolean postInterestAs, LocalDate transactionDate,
boolean backdatedTxnsAllowedTill, Throwable t) {
// NOTE: allow caller to catch the exceptions
// NOTE: wrap throwable only if really necessary
throw errorHandler.getMappable(t, null, null, "savings.postinterest");
throw org.apache.fineract.infrastructure.core.exception.ErrorHandler.getMappable(t, null, null, "savings.postinterest");
}
@Transactional
@@ -146,8 +146,12 @@ import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatform
import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformServiceJpaRepositoryImpl;
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateEffectiveRepository;
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateSlabHistoryRepository;
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository;
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository;
import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformService;
import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformServiceImpl;
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformServiceImpl;
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPoster;
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask;
import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService;
@@ -171,6 +175,14 @@ public class SavingsConfiguration {
return new RdBaseInterestRateReadPlatformServiceImpl(effectiveRepository, slabRepository);
}
@Bean
@ConditionalOnMissingBean(FdBaseInterestRateReadPlatformService.class)
public FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService(
FdBaseInterestRateEffectiveRepository effectiveRepository,
FdBaseInterestRateSlabHistoryRepository slabRepository) {
return new FdBaseInterestRateReadPlatformServiceImpl(effectiveRepository, slabRepository);
}
@Bean
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
@@ -286,4 +286,7 @@
<include file="parts/0276_create_current_account_tables.xml" relativeToChangelogFile="true"/>
<include file="parts/0277_fix_current_account_audit_columns.xml" relativeToChangelogFile="true"/>
<include file="parts/0278_add_is_mandatory_to_joint_holder.xml" relativeToChangelogFile="true"/>
<include file="parts/0279_add_fd_type_and_base_interest_rate_tables.xml" relativeToChangelogFile="true"/>
<include file="parts/0280_add_fixed_deposit_product_codes.xml" relativeToChangelogFile="true"/>
<include file="parts/0281_update_fd_base_interest_rate_seed_data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,300 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!-- 1. Add fd_type column to m_savings_product -->
<changeSet id="0279a_add_fd_type_to_savings_product" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<not><columnExists tableName="m_savings_product" columnName="fd_type"/></not>
</preConditions>
<addColumn tableName="m_savings_product">
<column name="fd_type" type="VARCHAR(20)" defaultValue="SINGLE">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
<!-- 2. Create FD base interest rate effective (version) table -->
<changeSet id="0279b_create_fd_base_interest_rate_effective" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_fd_base_interest_rate_effective"/></not>
</preConditions>
<createTable tableName="m_fd_base_interest_rate_effective">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="fd_type" type="VARCHAR(20)">
<constraints nullable="false"/>
</column>
<column name="version_label" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
<column name="effective_from_date" type="DATE">
<constraints nullable="false"/>
</column>
<column name="effective_to_date" type="DATE"/>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="remarks" type="VARCHAR(500)"/>
<column name="created_by_id" type="BIGINT"/>
<column name="created_date" type="DATE"/>
</createTable>
</changeSet>
<!-- 3. Create FD base interest rate slab history table -->
<changeSet id="0279c_create_fd_base_interest_rate_slab_history" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_fd_base_interest_rate_slab_history"/></not>
</preConditions>
<createTable tableName="m_fd_base_interest_rate_slab_history">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="effective_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="sr_no" type="INT">
<constraints nullable="false"/>
</column>
<column name="description" type="VARCHAR(200)"/>
<!-- period_type_enum: 0=DAYS, 1=WEEKS, 2=MONTHS, 3=YEARS -->
<column name="period_type_enum" type="INT" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="from_period" type="INT">
<constraints nullable="false"/>
</column>
<column name="to_period" type="INT"/>
<column name="annual_interest_rate" type="DECIMAL(19,6)">
<constraints nullable="false"/>
</column>
<!-- senior_citizen_rate: additional rate for senior citizens -->
<column name="senior_citizen_rate" type="DECIMAL(19,6)" defaultValueNumeric="0"/>
<column name="created_date" type="DATE"/>
</createTable>
<addForeignKeyConstraint
baseTableName="m_fd_base_interest_rate_slab_history"
baseColumnNames="effective_id"
referencedTableName="m_fd_base_interest_rate_effective"
referencedColumnNames="id"
constraintName="fk_fd_base_rate_slab_effective"/>
</changeSet>
<!-- 4. Seed initial FD base interest rate data -->
<changeSet id="0279d_seed_fd_base_interest_rate_single" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE'</sqlCheck>
</preConditions>
<!-- FD SINGLE: &lt; 1 year, period in DAYS -->
<insert tableName="m_fd_base_interest_rate_effective">
<column name="fd_type" value="SINGLE"/>
<column name="version_label" value="FD-SINGLE-V1"/>
<column name="effective_from_date" value="2025-01-01"/>
<column name="is_active" valueBoolean="true"/>
<column name="remarks" value="Initial FD Single (Short Term) rates"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
</changeSet>
<changeSet id="0279e_seed_fd_single_slabs" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'SINGLE'</sqlCheck>
</preConditions>
<!-- 1-29 days -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
<column name="sr_no" valueNumeric="1"/>
<column name="description" value="1 to 29 Days"/>
<column name="period_type_enum" valueNumeric="0"/>
<column name="from_period" valueNumeric="1"/>
<column name="to_period" valueNumeric="29"/>
<column name="annual_interest_rate" valueNumeric="4.50"/>
<column name="senior_citizen_rate" valueNumeric="5.00"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 30-89 days -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
<column name="sr_no" valueNumeric="2"/>
<column name="description" value="30 to 89 Days"/>
<column name="period_type_enum" valueNumeric="0"/>
<column name="from_period" valueNumeric="30"/>
<column name="to_period" valueNumeric="89"/>
<column name="annual_interest_rate" valueNumeric="5.00"/>
<column name="senior_citizen_rate" valueNumeric="5.50"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 90-179 days -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
<column name="sr_no" valueNumeric="3"/>
<column name="description" value="90 to 179 Days"/>
<column name="period_type_enum" valueNumeric="0"/>
<column name="from_period" valueNumeric="90"/>
<column name="to_period" valueNumeric="179"/>
<column name="annual_interest_rate" valueNumeric="5.50"/>
<column name="senior_citizen_rate" valueNumeric="6.00"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 180+ days -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
<column name="sr_no" valueNumeric="4"/>
<column name="description" value="180+ Days"/>
<column name="period_type_enum" valueNumeric="0"/>
<column name="from_period" valueNumeric="180"/>
<column name="annual_interest_rate" valueNumeric="6.00"/>
<column name="senior_citizen_rate" valueNumeric="6.50"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
</changeSet>
<changeSet id="0279f_seed_fd_base_interest_rate_double" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE'</sqlCheck>
</preConditions>
<!-- FD DOUBLE: >= 1 year, period in YEARS -->
<insert tableName="m_fd_base_interest_rate_effective">
<column name="fd_type" value="DOUBLE"/>
<column name="version_label" value="FD-DOUBLE-V1"/>
<column name="effective_from_date" value="2025-01-01"/>
<column name="is_active" valueBoolean="true"/>
<column name="remarks" value="Initial FD Double (Long Term) rates"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
</changeSet>
<changeSet id="0279g_seed_fd_double_slabs" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'DOUBLE'</sqlCheck>
</preConditions>
<!-- 1-2 years -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
<column name="sr_no" valueNumeric="1"/>
<column name="description" value="1 to 2 Years"/>
<column name="period_type_enum" valueNumeric="3"/>
<column name="from_period" valueNumeric="1"/>
<column name="to_period" valueNumeric="2"/>
<column name="annual_interest_rate" valueNumeric="6.50"/>
<column name="senior_citizen_rate" valueNumeric="7.00"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 3-5 years -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
<column name="sr_no" valueNumeric="2"/>
<column name="description" value="3 to 5 Years"/>
<column name="period_type_enum" valueNumeric="3"/>
<column name="from_period" valueNumeric="3"/>
<column name="to_period" valueNumeric="5"/>
<column name="annual_interest_rate" valueNumeric="6.75"/>
<column name="senior_citizen_rate" valueNumeric="7.25"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 6-10 years -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
<column name="sr_no" valueNumeric="3"/>
<column name="description" value="6 to 10 Years"/>
<column name="period_type_enum" valueNumeric="3"/>
<column name="from_period" valueNumeric="6"/>
<column name="to_period" valueNumeric="10"/>
<column name="annual_interest_rate" valueNumeric="7.00"/>
<column name="senior_citizen_rate" valueNumeric="7.50"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 11+ years -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
<column name="sr_no" valueNumeric="4"/>
<column name="description" value="11+ Years"/>
<column name="period_type_enum" valueNumeric="3"/>
<column name="from_period" valueNumeric="11"/>
<column name="annual_interest_rate" valueNumeric="7.25"/>
<column name="senior_citizen_rate" valueNumeric="7.75"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
</changeSet>
<changeSet id="0279h_seed_fd_base_interest_rate_special" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL'</sqlCheck>
</preConditions>
<!-- FD SPECIAL: monthly interest payout, period in MONTHS -->
<insert tableName="m_fd_base_interest_rate_effective">
<column name="fd_type" value="SPECIAL"/>
<column name="version_label" value="FD-SPECIAL-V1"/>
<column name="effective_from_date" value="2025-01-01"/>
<column name="is_active" valueBoolean="true"/>
<column name="remarks" value="Initial FD Special (Monthly Payout) rates"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
</changeSet>
<changeSet id="0279i_seed_fd_special_slabs" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'SPECIAL'</sqlCheck>
</preConditions>
<!-- 1-12 months -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
<column name="sr_no" valueNumeric="1"/>
<column name="description" value="1 to 12 Months"/>
<column name="period_type_enum" valueNumeric="2"/>
<column name="from_period" valueNumeric="1"/>
<column name="to_period" valueNumeric="12"/>
<column name="annual_interest_rate" valueNumeric="6.00"/>
<column name="senior_citizen_rate" valueNumeric="6.50"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 13-24 months -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
<column name="sr_no" valueNumeric="2"/>
<column name="description" value="13 to 24 Months"/>
<column name="period_type_enum" valueNumeric="2"/>
<column name="from_period" valueNumeric="13"/>
<column name="to_period" valueNumeric="24"/>
<column name="annual_interest_rate" valueNumeric="6.50"/>
<column name="senior_citizen_rate" valueNumeric="7.00"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 25-60 months -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
<column name="sr_no" valueNumeric="3"/>
<column name="description" value="25 to 60 Months"/>
<column name="period_type_enum" valueNumeric="2"/>
<column name="from_period" valueNumeric="25"/>
<column name="to_period" valueNumeric="60"/>
<column name="annual_interest_rate" valueNumeric="7.00"/>
<column name="senior_citizen_rate" valueNumeric="7.50"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
<!-- 61+ months -->
<insert tableName="m_fd_base_interest_rate_slab_history">
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
<column name="sr_no" valueNumeric="4"/>
<column name="description" value="61+ Months"/>
<column name="period_type_enum" valueNumeric="2"/>
<column name="from_period" valueNumeric="61"/>
<column name="annual_interest_rate" valueNumeric="7.25"/>
<column name="senior_citizen_rate" valueNumeric="7.75"/>
<column name="created_date" valueDate="2025-01-01"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!-- FixedDepositProductAccountType code and values -->
<changeSet id="0280a_add_fd_product_account_type_code" author="system">
<validCheckSum>ANY</validCheckSum>
<sql>
INSERT INTO m_code (code_name, is_system_defined)
SELECT 'FixedDepositProductAccountType', false
WHERE NOT EXISTS (SELECT 1 FROM m_code WHERE code_name = 'FixedDepositProductAccountType');
</sql>
<sql>
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
SELECT c.id, v.code_value, v.order_position, true, false
FROM m_code c,
(VALUES
('Fd Account', 1)
) AS v(code_value, order_position)
WHERE c.code_name = 'FixedDepositProductAccountType'
AND NOT EXISTS (
SELECT 1 FROM m_code_value cv
WHERE cv.code_id = c.id AND cv.code_value = v.code_value
);
</sql>
</changeSet>
<!-- FixedDepositProductCustomerSegment code and values -->
<changeSet id="0280b_add_fd_product_customer_segment_code" author="system">
<validCheckSum>ANY</validCheckSum>
<sql>
INSERT INTO m_code (code_name, is_system_defined)
SELECT 'FixedDepositProductCustomerSegment', false
WHERE NOT EXISTS (SELECT 1 FROM m_code WHERE code_name = 'FixedDepositProductCustomerSegment');
</sql>
<sql>
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
SELECT c.id, v.code_value, v.order_position, true, false
FROM m_code c,
(VALUES
('Individual', 1),
('Non-Individual', 2)
) AS v(code_value, order_position)
WHERE c.code_name = 'FixedDepositProductCustomerSegment'
AND NOT EXISTS (
SELECT 1 FROM m_code_value cv
WHERE cv.code_id = c.id AND cv.code_value = v.code_value
);
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet id="0281_update_fd_base_interest_rate_seed_data" author="fineract">
<validCheckSum>ANY</validCheckSum>
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_fd_base_interest_rate_effective"/>
</preConditions>
<sql>
UPDATE m_fd_base_interest_rate_slab_history
SET description = '1 to 29 Days',
from_period = 1,
to_period = 29
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
AND sr_no = 1;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '30 to 89 Days',
from_period = 30,
to_period = 89
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
AND sr_no = 2;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '90 to 179 Days',
from_period = 90,
to_period = 179
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
AND sr_no = 3;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '180+ Days',
from_period = 180,
to_period = NULL
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
AND sr_no = 4;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '1 to 2 Years',
from_period = 1,
to_period = 2
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
AND sr_no = 1;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '3 to 5 Years',
from_period = 3,
to_period = 5
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
AND sr_no = 2;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '6 to 10 Years',
from_period = 6,
to_period = 10
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
AND sr_no = 3;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '11+ Years',
from_period = 11,
to_period = NULL
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
AND sr_no = 4;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '1 to 12 Months',
from_period = 1,
to_period = 12
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1')
AND sr_no = 1;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '13 to 24 Months',
from_period = 13,
to_period = 24
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1')
AND sr_no = 2;
UPDATE m_fd_base_interest_rate_slab_history
SET description = '25 to 60 Months',
from_period = 25,
to_period = 60
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1')
AND sr_no = 3;
INSERT INTO m_fd_base_interest_rate_slab_history
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, senior_citizen_rate, created_date)
SELECT e.id, 4, '61+ Months', 2, 61, NULL, 7.250000, 7.750000, CURRENT_DATE
FROM m_fd_base_interest_rate_effective e
WHERE e.fd_type = 'SPECIAL' AND e.version_label = 'FD-SPECIAL-V1'
AND NOT EXISTS (
SELECT 1
FROM m_fd_base_interest_rate_slab_history sh
WHERE sh.effective_id = e.id
AND sh.sr_no = 4
);
</sql>
</changeSet>
</databaseChangeLog>
@@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
public class AccountNumberGeneratorTest {
@@ -54,6 +55,7 @@ public class AccountNumberGeneratorTest {
private SavingsAccountRepository savingsRepo;
private GLAccountRepository glAccountRepo;
private SavingsProductRepository savingsProductRepo;
private JdbcTemplate jdbcTemplate;
private AccountNumberGenerator generator;
@@ -65,8 +67,9 @@ public class AccountNumberGeneratorTest {
savingsRepo = mock(SavingsAccountRepository.class);
glAccountRepo = mock(GLAccountRepository.class);
savingsProductRepo = mock(SavingsProductRepository.class);
jdbcTemplate = mock(JdbcTemplate.class);
generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo, glAccountRepo, savingsProductRepo);
generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo, glAccountRepo, savingsProductRepo, jdbcTemplate);
GlobalConfigurationPropertyData accountLengthConfig = mock(GlobalConfigurationPropertyData.class);
when(accountLengthConfig.getValue()).thenReturn(Long.valueOf("9"));
@@ -112,9 +112,9 @@ public final class FixedDepositProductData extends DepositProductData {
}
public static FixedDepositProductData withCharges(final FixedDepositProductData existingProduct, final Collection<ChargeData> charges) {
return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule,
existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions,
@@ -131,6 +131,8 @@ public final class FixedDepositProductData extends DepositProductData {
existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup,
existingProduct.taxGroupOptions);
result.copyCbsFieldsFrom(existingProduct);
return result;
}
/**
@@ -152,9 +154,9 @@ public final class FixedDepositProductData extends DepositProductData {
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<TaxGroupData> taxGroupOptions) {
return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule,
existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, currencyOptions,
@@ -169,6 +171,8 @@ public final class FixedDepositProductData extends DepositProductData {
existingProduct.inMultiplesOfDepositTermType, existingProduct.minDepositAmount, existingProduct.depositAmount,
existingProduct.maxDepositAmount, periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup,
taxGroupOptions);
result.copyCbsFieldsFrom(existingProduct);
return result;
}
public static FixedDepositProductData withAccountingDetails(final FixedDepositProductData existingProduct,
@@ -189,9 +193,9 @@ public final class FixedDepositProductData extends DepositProductData {
final Collection<ChargeData> chargeOptions = null;
final Collection<ChargeData> penaltyOptions = null;
return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name,
existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule,
accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
@@ -206,6 +210,8 @@ public final class FixedDepositProductData extends DepositProductData {
existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup,
existingProduct.taxGroupOptions);
result.copyCbsFieldsFrom(existingProduct);
return result;
}
public static FixedDepositProductData instance(final DepositProductData depositProductData, final boolean preClosurePenalApplicable,
@@ -243,7 +249,7 @@ public final class FixedDepositProductData extends DepositProductData {
depositProductData.interestCompoundingPeriodType, depositProductData.interestPostingPeriodType,
depositProductData.interestCalculationType, depositProductData.interestCalculationDaysInYearType,
depositProductData.lockinPeriodFrequency, depositProductData.lockinPeriodFrequencyType,
depositProductData.minBalanceForInterestCalculation, depositProductData.accountingRule, accountingMappings,
depositProductData.getMinBalanceForInterestCalculation(), depositProductData.accountingRule, accountingMappings,
paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions,
@@ -321,9 +327,9 @@ public final class FixedDepositProductData extends DepositProductData {
public static FixedDepositProductData withInterestChart(final FixedDepositProductData existingProduct,
final Collection<InterestRateChartData> interestRateCharts) {
return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description,
existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name,
existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType,
existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency,
existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule,
existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions,
@@ -340,7 +346,8 @@ public final class FixedDepositProductData extends DepositProductData {
existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup,
existingProduct.taxGroupOptions);
result.copyCbsFieldsFrom(existingProduct);
return result;
}
private FixedDepositProductData(final Long id, final String name, final String shortName, final String description,
@@ -445,4 +452,160 @@ public final class FixedDepositProductData extends DepositProductData {
return preClosurePenalApplicable;
}
// CBS Custom Fields
private Collection<org.apache.fineract.infrastructure.core.data.EnumOptionData> interestCalculationTypeOption;
private Collection<org.apache.fineract.infrastructure.core.data.EnumOptionData> periodFrequencyOptions;
private java.util.List<org.apache.fineract.infrastructure.codes.data.CodeValueData> yesNoOptions;
private java.util.List<org.apache.fineract.infrastructure.codes.data.CodeValueData> accountTypeOptions;
private java.util.List<org.apache.fineract.infrastructure.codes.data.CodeValueData> customerSegmentOptions;
private String productCode;
private String accountType;
private String fdType;
private Long glAccountId;
private String glAccountName;
private String customerSegment;
private Boolean introducerRequired;
private Boolean shareLinkingEnabled;
private Boolean nominationFacility;
private Boolean dormancyAlert;
private Boolean isDormancyTrackingActive;
private Long daysToInactive;
private Long daysToDormancy;
private Long daysToEscheat;
private String effectiveDate;
private String effectiveTo;
private String endDate;
private Integer interestCalculateSelector;
private String averagePeriod;
private Boolean autoPost;
private Integer frequencyFixDay;
private String lastInterestDate;
private Boolean enableLockinPeriod;
private Boolean autoDebitFacility;
private Boolean loanFacility;
private Boolean prematureWithdrawalOption;
private Boolean tenureFlexibility;
private Boolean lienMarked;
private Boolean maturityInstructions;
private Boolean creditMaturityProceeds;
private Boolean penaltyGracePeriodMissedInstallments;
private Boolean panelRequestApplicable;
private Boolean noPenaltyNomineeHeirClosure;
private Boolean deceasedSettlement;
private Boolean allowDebitTxnAfterDueDate;
private Boolean accountClosedZeroBalanceInDayClosing;
private String balanceFrequency;
// Template option setters
public void setInterestCalculationTypeOption(Collection<org.apache.fineract.infrastructure.core.data.EnumOptionData> v) { this.interestCalculationTypeOption = v; }
public void setPeriodFrequencyOptions(Collection<org.apache.fineract.infrastructure.core.data.EnumOptionData> v) { this.periodFrequencyOptions = v; }
public void setYesNoOptions(java.util.List<org.apache.fineract.infrastructure.codes.data.CodeValueData> v) { this.yesNoOptions = v; }
public void setAccountTypeOptions(java.util.List<org.apache.fineract.infrastructure.codes.data.CodeValueData> v) { this.accountTypeOptions = v; }
public void setCustomerSegmentOptions(java.util.List<org.apache.fineract.infrastructure.codes.data.CodeValueData> v) { this.customerSegmentOptions = v; }
// CBS field setters
public void setProductCode(String v) { this.productCode = v; }
public void setAccountType(String v) { this.accountType = v; }
public void setFdType(String v) { this.fdType = v; }
public void setGlAccountId(Long v) { this.glAccountId = v; }
public void setGlAccountName(String v) { this.glAccountName = v; }
public void setCustomerSegment(String v) { this.customerSegment = v; }
public void setIntroducerRequired(Boolean v) { this.introducerRequired = v; }
public void setShareLinkingEnabled(Boolean v) { this.shareLinkingEnabled = v; }
public void setNominationFacility(Boolean v) { this.nominationFacility = v; }
public void setDormancyAlert(Boolean v) { this.dormancyAlert = v; }
public void setIsDormancyTrackingActive(Boolean v) { this.isDormancyTrackingActive = v; }
public void setDaysToInactive(Long v) { this.daysToInactive = v; }
public void setDaysToDormancy(Long v) { this.daysToDormancy = v; }
public void setDaysToEscheat(Long v) { this.daysToEscheat = v; }
public void setEffectiveDate(String v) { this.effectiveDate = v; }
public void setEffectiveTo(String v) { this.effectiveTo = v; }
public void setEndDate(String v) { this.endDate = v; }
public void setInterestCalculateSelector(Integer v) { this.interestCalculateSelector = v; }
public void setAveragePeriod(String v) { this.averagePeriod = v; }
public void setAutoPost(Boolean v) { this.autoPost = v; }
public void setFrequencyFixDay(Integer v) { this.frequencyFixDay = v; }
public void setLastInterestDate(String v) { this.lastInterestDate = v; }
public void setEnableLockinPeriod(Boolean v) { this.enableLockinPeriod = v; }
public void setAutoDebitFacility(Boolean v) { this.autoDebitFacility = v; }
public void setLoanFacility(Boolean v) { this.loanFacility = v; }
public void setPrematureWithdrawalOption(Boolean v) { this.prematureWithdrawalOption = v; }
public void setTenureFlexibility(Boolean v) { this.tenureFlexibility = v; }
public void setLienMarked(Boolean v) { this.lienMarked = v; }
public void setMaturityInstructions(Boolean v) { this.maturityInstructions = v; }
public void setCreditMaturityProceeds(Boolean v) { this.creditMaturityProceeds = v; }
public void setPenaltyGracePeriodMissedInstallments(Boolean v) { this.penaltyGracePeriodMissedInstallments = v; }
public void setPanelRequestApplicable(Boolean v) { this.panelRequestApplicable = v; }
public void setNoPenaltyNomineeHeirClosure(Boolean v) { this.noPenaltyNomineeHeirClosure = v; }
public void setDeceasedSettlement(Boolean v) { this.deceasedSettlement = v; }
public void setAllowDebitTxnAfterDueDate(Boolean v) { this.allowDebitTxnAfterDueDate = v; }
public void setAccountClosedZeroBalanceInDayClosing(Boolean v) { this.accountClosedZeroBalanceInDayClosing = v; }
public void setBalanceFrequency(String v) { this.balanceFrequency = v; }
// CBS field getters
public String getProductCode() { return productCode; }
public String getAccountType() { return accountType; }
public String getFdType() { return fdType; }
public Long getGlAccountId() { return glAccountId; }
public String getGlAccountName() { return glAccountName; }
public String getCustomerSegment() { return customerSegment; }
public Boolean getIntroducerRequired() { return introducerRequired; }
public Boolean getShareLinkingEnabled() { return shareLinkingEnabled; }
public Boolean getNominationFacility() { return nominationFacility; }
public Boolean getDormancyAlert() { return dormancyAlert; }
public Boolean getIsDormancyTrackingActive() { return isDormancyTrackingActive; }
public Long getDaysToInactive() { return daysToInactive; }
public Long getDaysToDormancy() { return daysToDormancy; }
public Long getDaysToEscheat() { return daysToEscheat; }
public void copyCbsFieldsFrom(FixedDepositProductData src) {
if (src == null) return;
this.accountType = src.accountType;
this.fdType = src.fdType;
this.customerSegmentOptions = src.customerSegmentOptions;
this.productCode = src.productCode;
this.glAccountId = src.glAccountId;
this.glAccountName = src.glAccountName;
this.interestCalculationTypeOption = src.interestCalculationTypeOption;
this.periodFrequencyOptions = src.periodFrequencyOptions;
this.yesNoOptions = src.yesNoOptions;
this.accountTypeOptions = src.accountTypeOptions;
this.customerSegment = src.customerSegment;
this.introducerRequired = src.introducerRequired;
this.shareLinkingEnabled = src.shareLinkingEnabled;
this.nominationFacility = src.nominationFacility;
this.dormancyAlert = src.dormancyAlert;
this.isDormancyTrackingActive = src.isDormancyTrackingActive;
this.daysToInactive = src.daysToInactive;
this.daysToDormancy = src.daysToDormancy;
this.daysToEscheat = src.daysToEscheat;
this.effectiveDate = src.effectiveDate;
this.effectiveTo = src.effectiveTo;
this.endDate = src.endDate;
this.interestCalculateSelector = src.interestCalculateSelector;
this.averagePeriod = src.averagePeriod;
this.autoPost = src.autoPost;
this.frequencyFixDay = src.frequencyFixDay;
this.lastInterestDate = src.lastInterestDate;
this.enableLockinPeriod = src.enableLockinPeriod;
this.autoDebitFacility = src.autoDebitFacility;
this.loanFacility = src.loanFacility;
this.prematureWithdrawalOption = src.prematureWithdrawalOption;
this.tenureFlexibility = src.tenureFlexibility;
this.lienMarked = src.lienMarked;
this.maturityInstructions = src.maturityInstructions;
this.creditMaturityProceeds = src.creditMaturityProceeds;
this.penaltyGracePeriodMissedInstallments = src.penaltyGracePeriodMissedInstallments;
this.panelRequestApplicable = src.panelRequestApplicable;
this.noPenaltyNomineeHeirClosure = src.noPenaltyNomineeHeirClosure;
this.deceasedSettlement = src.deceasedSettlement;
this.allowDebitTxnAfterDueDate = src.allowDebitTxnAfterDueDate;
this.accountClosedZeroBalanceInDayClosing = src.accountClosedZeroBalanceInDayClosing;
this.balanceFrequency = src.balanceFrequency;
this.withdrawalFeeForTransfers = src.withdrawalFeeForTransfers;
}
private Boolean withdrawalFeeForTransfers;
public void setWithdrawalFeeForTransfers(Boolean v) { this.withdrawalFeeForTransfers = v; }
public Boolean getWithdrawalFeeForTransfers() { return withdrawalFeeForTransfers; }
}
@@ -187,6 +187,65 @@ public class DepositProductAssembler {
// update product reference
productTermAndPreClosure.updateProductReference(fixedDepositProduct);
// CBS Custom Fields
fixedDepositProduct.setAccountType(command.stringValueOfParameterNamed("accountType"));
fixedDepositProduct.setCustomerSegment(command.stringValueOfParameterNamed("customerSegment"));
final String fdProductCode = command.stringValueOfParameterNamed("productCode");
fixedDepositProduct.setProductCode((fdProductCode != null && !fdProductCode.isBlank()) ? fdProductCode : null);
final String fdType = command.stringValueOfParameterNamed("fdType");
fixedDepositProduct.setFdType((fdType != null && !fdType.isBlank()) ? fdType : null);
if (command.parameterExists("glAccountId")) {
fixedDepositProduct.setGlAccountId(command.longValueOfParameterNamed("glAccountId"));
}
fixedDepositProduct.setIntroducerRequired(command.booleanPrimitiveValueOfParameterNamed("introducerRequired"));
fixedDepositProduct.setShareLinkingEnabled(command.booleanPrimitiveValueOfParameterNamed("shareLinkingEnabled"));
fixedDepositProduct.setEffectiveDate(parseCbsDate(command.stringValueOfParameterNamed("effectiveDate")));
fixedDepositProduct.setEffectiveTo(parseCbsDate(command.stringValueOfParameterNamed("effectiveTo")));
fixedDepositProduct.setEndDate(parseCbsDate(command.stringValueOfParameterNamed("endDate")));
fixedDepositProduct.setNominationFacility(command.booleanPrimitiveValueOfParameterNamed("nominationFacility"));
final boolean dormancyAlertValue = command.booleanPrimitiveValueOfParameterNamed("dormancyAlert");
fixedDepositProduct.setDormancyAlert(dormancyAlertValue);
// isDormancyTrackingActive is driven by dormancyAlert for FD products (same as RD)
fixedDepositProduct.setIsDormancyTrackingActive(dormancyAlertValue);
if (command.parameterExists("daysToInactive")) {
fixedDepositProduct.setDaysToInactive(command.longValueOfParameterNamed("daysToInactive"));
}
if (command.parameterExists("daysToDormancy")) {
fixedDepositProduct.setDaysToDormancy(command.longValueOfParameterNamed("daysToDormancy"));
}
if (command.parameterExists("daysToEscheat")) {
fixedDepositProduct.setDaysToEscheat(command.longValueOfParameterNamed("daysToEscheat"));
}
fixedDepositProduct.setInterestCalculateSelector(command.integerValueOfParameterNamed("interestCalculateSelector"));
fixedDepositProduct.setAveragePeriod(command.stringValueOfParameterNamed("averagePeriod"));
fixedDepositProduct.setAutoPost(command.booleanPrimitiveValueOfParameterNamed("autoPost"));
fixedDepositProduct.setFrequencyFixDay(command.integerValueOfParameterNamed("frequencyFixDay"));
fixedDepositProduct.setLastInterestDate(parseCbsDate(command.stringValueOfParameterNamed("lastInterestDate")));
fixedDepositProduct.setEnableLockinPeriod(command.booleanPrimitiveValueOfParameterNamed("enableLockinPeriod"));
fixedDepositProduct.setAutoDebitFacility(command.booleanPrimitiveValueOfParameterNamed("autoDebitFacility"));
fixedDepositProduct.setLoanFacility(command.booleanPrimitiveValueOfParameterNamed("loanFacility"));
fixedDepositProduct.setPrematureWithdrawalOption(command.booleanPrimitiveValueOfParameterNamed("prematureWithdrawalOption"));
fixedDepositProduct.setTenureFlexibility(command.booleanPrimitiveValueOfParameterNamed("tenureFlexibility"));
fixedDepositProduct.setLienMarked(command.booleanPrimitiveValueOfParameterNamed("lienMarked"));
fixedDepositProduct.setMaturityInstructions(command.booleanPrimitiveValueOfParameterNamed("maturityInstructions"));
fixedDepositProduct.setCreditMaturityProceeds(command.booleanPrimitiveValueOfParameterNamed("creditMaturityProceeds"));
fixedDepositProduct.setPenaltyGracePeriodMissedInstallments(
command.booleanPrimitiveValueOfParameterNamed("penaltyGracePeriodMissedInstallments"));
fixedDepositProduct.setPanelRequestApplicable(command.booleanPrimitiveValueOfParameterNamed("panelRequestApplicable"));
fixedDepositProduct.setNoPenaltyNomineeHeirClosure(
command.booleanPrimitiveValueOfParameterNamed("noPenaltyNomineeHeirClosure"));
fixedDepositProduct.setDeceasedSettlement(command.booleanPrimitiveValueOfParameterNamed("deceasedSettlement"));
fixedDepositProduct.setAllowDebitTxnAfterDueDate(command.booleanPrimitiveValueOfParameterNamed("allowDebitTxnAfterDueDate"));
if (command.parameterExists("accountClosedZeroBalanceInDayClosing")) {
fixedDepositProduct.setAccountClosedZeroBalanceInDayClosing(
command.booleanPrimitiveValueOfParameterNamed("accountClosedZeroBalanceInDayClosing"));
}
fixedDepositProduct.setBalanceFrequency(command.stringValueOfParameterNamed("balanceFrequency"));
if (command.parameterExists("withdrawalFeeForTransfers")) {
fixedDepositProduct.setWithdrawalFeeApplicableForTransfer(command.booleanPrimitiveValueOfParameterNamed("withdrawalFeeForTransfers"));
}
fixedDepositProduct.validateDomainRules();
return fixedDepositProduct;
@@ -337,6 +396,10 @@ public class DepositProductAssembler {
recurringDepositProduct.setDeceasedSettlement(command.booleanPrimitiveValueOfParameterNamed("deceasedSettlement"));
recurringDepositProduct.setAllowDebitTxnAfterDueDate(command.booleanPrimitiveValueOfParameterNamed("allowDebitTxnAfterDueDate"));
if (command.parameterExists("withdrawalFeeForTransfers")) {
recurringDepositProduct.setWithdrawalFeeApplicableForTransfer(command.booleanPrimitiveValueOfParameterNamed("withdrawalFeeForTransfers"));
}
recurringDepositProduct.validateDomainRules();
return recurringDepositProduct;
@@ -26,6 +26,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepo
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
@@ -34,6 +35,7 @@ import jakarta.persistence.JoinTable;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Transient;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
@@ -76,6 +78,17 @@ public class FixedDepositProduct extends SavingsProduct {
@Transient
protected InterestRateChartAssembler chartAssembler;
@Column(name = "fd_type", length = 50)
private String fdType;
public String getFdType() {
return fdType;
}
public void setFdType(String fdType) {
this.fdType = fdType;
}
protected FixedDepositProduct() {
}
@@ -161,6 +174,12 @@ public class FixedDepositProduct extends SavingsProduct {
actualChanges.putAll(super.update(command));
if (command.isChangeInStringParameterNamed("fdType", this.fdType)) {
final String newValue = command.stringValueOfParameterNamed("fdType");
actualChanges.put("fdType", newValue);
this.fdType = (newValue != null && !newValue.isBlank()) ? newValue : null;
}
if (this.productTermAndPreClosure != null) {
actualChanges.putAll(this.productTermAndPreClosure.update(command, baseDataValidator));
}
@@ -1034,6 +1034,10 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
return this.withdrawalFeeApplicableForTransfer;
}
public void setWithdrawalFeeApplicableForTransfer(boolean withdrawalFeeApplicableForTransfer) {
this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer;
}
public boolean isAllowOverdraft() {
return this.allowOverdraft;
}