rd and fd product and account code update

This commit is contained in:
Nitesh Yadav
2026-05-08 15:55:22 +05:30
45 changed files with 2154 additions and 98 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;
}
@@ -428,6 +522,27 @@ public final class DepositsApiConstants {
fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
fixedDepositRequestData.add(linkedAccountParamName);
fixedDepositRequestData.add(transferInterestToSavingsParamName);
fixedDepositRequestData.add("accountType");
fixedDepositRequestData.add("customerSegment");
fixedDepositRequestData.add(SavingsApiConstants.accountStatusParamName);
fixedDepositRequestData.add(SavingsApiConstants.modeOfOperationParamName);
fixedDepositRequestData.add(SavingsApiConstants.specialCategoryParamName);
fixedDepositRequestData.add(SavingsApiConstants.weakerSectionParamName);
fixedDepositRequestData.add(SavingsApiConstants.guardianNameParamName);
fixedDepositRequestData.add(SavingsApiConstants.guardianCifNumberParamName);
fixedDepositRequestData.add(SavingsApiConstants.guardianRelationshipParamName);
fixedDepositRequestData.add(SavingsApiConstants.guardianClientIdParamName);
fixedDepositRequestData.add(SavingsApiConstants.minorDateOfBirthParamName);
fixedDepositRequestData.add(SavingsApiConstants.introducerIdParamName);
fixedDepositRequestData.add(SavingsApiConstants.introducerNameParamName);
fixedDepositRequestData.add(SavingsApiConstants.introducerCifNumberParamName);
fixedDepositRequestData.add(SavingsApiConstants.nomineeParamName);
fixedDepositRequestData.add(SavingsApiConstants.linkedAccountIdParamName);
fixedDepositRequestData.add(SavingsApiConstants.linkedAccountTypeParamName);
fixedDepositRequestData.add(SavingsApiConstants.linkedAccountNoParamName);
fixedDepositRequestData.add(SavingsApiConstants.jointHoldersParamName);
fixedDepositRequestData.add(SavingsApiConstants.autoDebitFacilityParamName);
fixedDepositRequestData.add(SavingsApiConstants.nominationFacilityParamName);
return fixedDepositRequestData;
}
@@ -438,6 +553,31 @@ public final class DepositsApiConstants {
fixedDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
fixedDepositResponseData.add(linkedAccountParamName);
fixedDepositResponseData.add(transferInterestToSavingsParamName);
fixedDepositResponseData.add("accountType");
fixedDepositResponseData.add("customerSegment");
fixedDepositResponseData.add("accountTypeOptions");
fixedDepositResponseData.add("customerSegmentOptions");
fixedDepositResponseData.add("productCode");
fixedDepositResponseData.add("previewAccountNo");
fixedDepositResponseData.add(SavingsApiConstants.accountStatusParamName);
fixedDepositResponseData.add(SavingsApiConstants.modeOfOperationParamName);
fixedDepositResponseData.add(SavingsApiConstants.specialCategoryParamName);
fixedDepositResponseData.add(SavingsApiConstants.weakerSectionParamName);
fixedDepositResponseData.add(SavingsApiConstants.guardianNameParamName);
fixedDepositResponseData.add(SavingsApiConstants.guardianCifNumberParamName);
fixedDepositResponseData.add(SavingsApiConstants.guardianRelationshipParamName);
fixedDepositResponseData.add(SavingsApiConstants.guardianClientIdParamName);
fixedDepositResponseData.add(SavingsApiConstants.minorDateOfBirthParamName);
fixedDepositResponseData.add(SavingsApiConstants.introducerIdParamName);
fixedDepositResponseData.add(SavingsApiConstants.introducerNameParamName);
fixedDepositResponseData.add(SavingsApiConstants.introducerCifNumberParamName);
fixedDepositResponseData.add(SavingsApiConstants.nomineeParamName);
fixedDepositResponseData.add(SavingsApiConstants.linkedAccountIdParamName);
fixedDepositResponseData.add(SavingsApiConstants.linkedAccountTypeParamName);
fixedDepositResponseData.add(SavingsApiConstants.linkedAccountNoParamName);
fixedDepositResponseData.add(SavingsApiConstants.jointHoldersParamName);
fixedDepositResponseData.add(SavingsApiConstants.nominationFacilityParamName);
fixedDepositResponseData.add(SavingsApiConstants.autoDebitFacilityParamName);
return fixedDepositResponseData;
}
@@ -517,6 +657,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";
@@ -226,7 +226,7 @@ public class CurrentProduct extends AbstractPersistableCustom<Long> {
@Column(name = "customer_segment", length = 50)
private String customerSegment;
@Column(name = "product_code", length = 20, unique = true)
@Column(name = "product_code", length = 20)
private String productCode;
@Column(name = "gl_account_id")
@@ -189,7 +189,16 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
@Override
public PortfolioAccountData retriveSavingsAccount(final Long savingsId) {
String accountNo = jdbcTemplate.queryForObject("select account_no from m_savings_account where id = ?", String.class, savingsId);
String accountNo = null;
try {
accountNo = jdbcTemplate.queryForObject("select account_no from m_savings_account where id = ?", String.class, savingsId);
} catch (EmptyResultDataAccessException e) {
try {
accountNo = jdbcTemplate.queryForObject("select account_no from m_current_account where id = ?", String.class, savingsId);
} catch (EmptyResultDataAccessException ex) {
return null;
}
}
return PortfolioAccountData.lookup(savingsId, accountNo);
}
}
@@ -102,21 +102,6 @@ public class CurrentProductsApiResource {
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final JdbcTemplate jdbcTemplate;
private String generateNextProductCode(final String tableName, final String prefix) {
try {
String maxCode = jdbcTemplate.queryForObject(
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?",
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";
}
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@@ -306,7 +291,7 @@ public class CurrentProductsApiResource {
interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions, accountMappingForPayment);
savingsProductToReturn.setProductCode(generateNextProductCode("m_current_product", "CA-IND-"));
// product code removed
}
return savingsProductToReturn;
@@ -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,34 @@ public class FixedDepositProductsApiResource {
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions);
// product code removed
}
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();
}
@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);
}
}
@@ -45,6 +45,7 @@ import jakarta.ws.rs.core.UriInfo;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import lombok.RequiredArgsConstructor;
@@ -77,6 +78,7 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService;
import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
import org.apache.fineract.portfolio.savings.service.RecurringDepositAccountInterestCalculationService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
@@ -99,6 +101,7 @@ public class RecurringDepositAccountsApiResource {
private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
private final RecurringDepositAccountInterestCalculationService recurringDepositAccountInterestCalculationService;
private final SqlValidator sqlValidator;
@GET
@@ -369,6 +372,18 @@ public class RecurringDepositAccountsApiResource {
return this.toApiJsonSerializer.serialize(result);
}
@POST
@Path("calculate-rd-interest")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String calculateRecurringDepositInterest(@Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson);
HashMap<String, Object> result = recurringDepositAccountInterestCalculationService
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, jsonElement, fromJsonHelper));
return toApiJsonSerializer.serializeResult(result);
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
@@ -333,7 +333,7 @@ public class RecurringDepositProductsApiResource {
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions);
recurringDepositProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "RD-IND-"));
// product code removed
}
recurringDepositProductToReturn.setInterestCalculationTypeOption(interestCalculationTypeOption);
@@ -360,21 +360,6 @@ public class RecurringDepositProductsApiResource {
.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 = 300",
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";
}
}
// ── RD Interest Rate Chart endpoints ────────────────────────────────────
@@ -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;
@@ -106,21 +106,6 @@ public class SavingsProductsApiResource {
private final ConfigurationDomainService configurationDomainService;
private final JdbcTemplate jdbcTemplate;
private String generateNextProductCode(final String tableName, final String prefix) {
try {
String maxCode = jdbcTemplate.queryForObject(
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?",
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";
}
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@@ -315,7 +300,7 @@ public class SavingsProductsApiResource {
periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions,
accountMappingForPayment);
savingsProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "SB-IND-"));
// product code generation removed
}
return savingsProductToReturn;
@@ -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;
}
@@ -120,6 +120,7 @@ public class DepositAccountAssembler {
private final PaymentDetailAssembler paymentDetailAssembler;
private final ExternalIdFactory externalIdFactory;
private final SavingsAccountAssembler savingsAccountAssembler;
@Autowired
public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
@@ -130,7 +131,8 @@ public class DepositAccountAssembler {
final DepositProductAssembler depositProductAssembler,
final RecurringDepositProductRepository recurringDepositProductRepository,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final PlatformSecurityContext context,
final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory) {
final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory,
final SavingsAccountAssembler savingsAccountAssembler) {
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
this.clientRepository = clientRepository;
@@ -146,6 +148,7 @@ public class DepositAccountAssembler {
this.context = context;
this.paymentDetailAssembler = paymentDetailAssembler;
this.externalIdFactory = externalIdFactory;
this.savingsAccountAssembler = savingsAccountAssembler;
}
/**
@@ -358,6 +361,9 @@ public class DepositAccountAssembler {
if (account != null) {
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
account.validateNewApplicationState(depositAccountType.resourceName());
// Apply CBS-specific fields (accountStatus, customerSegment, autoDebitFacility,
// linkedAccount, nominee, jointHolders, guardian, etc.)
this.savingsAccountAssembler.applyCbsFields(command, element, account);
}
return account;
@@ -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);
}
@@ -40,6 +40,8 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minorDat
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nomineeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.specialCategoryParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.weakerSectionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
@@ -384,6 +386,14 @@ public class SavingsAccountAssembler {
account.setSpecialCategory(this.fromApiJsonHelper.extractStringNamed(specialCategoryParamName, element));
account.setWeakerSection(this.fromApiJsonHelper.extractStringNamed(weakerSectionParamName, element));
if (command.parameterExists(nominationFacilityParamName)) {
account.setNominationFacility(this.fromApiJsonHelper.extractBooleanNamed(nominationFacilityParamName, element));
}
if (command.parameterExists(autoDebitFacilityParamName)) {
account.setAutoDebitFacility(this.fromApiJsonHelper.extractBooleanNamed(autoDebitFacilityParamName, element));
}
account.setGuardianName(this.fromApiJsonHelper.extractStringNamed(guardianNameParamName, element));
account.setGuardianCifNumber(this.fromApiJsonHelper.extractStringNamed(guardianCifNumberParamName, element));
account.setGuardianRelationship(this.fromApiJsonHelper.extractStringNamed(guardianRelationshipParamName, element));
@@ -408,6 +408,24 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions);
template = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) template, accountChart);
try {
final Collection<CodeValueData> accountTypeOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE);
final Collection<CodeValueData> customerSegmentOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT);
((FixedDepositAccountData) template).setAccountTypeOptions(accountTypeOpts);
((FixedDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts);
} catch (final Exception ex) {
log.warn("Failed to load FD account type/customer segment options: {}", ex.getMessage());
}
try {
final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId);
template.setPreviewAccountNo(preview);
} catch (final Exception ex) {
log.warn("Failed to generate preview account number for FD productId={}: {}", productId, ex.getMessage());
}
} else if (depositAccountType.isRecurringDeposit()) {
// Save CBS fields from mapper result before factory methods create new objects
final RecurringDepositAccountData rdTemplateWithCbs = (RecurringDepositAccountData) template;
@@ -696,6 +714,16 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
selectFieldsSqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, ");
selectFieldsSqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,");
selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, ");
selectFieldsSqlBuilder.append("sa.cbs_account_type as accountType, sa.customer_segment as customerSegment, ");
selectFieldsSqlBuilder.append("sa.account_status as accountStatus, sa.mode_of_operation as modeOfOperation, ");
selectFieldsSqlBuilder.append("sa.special_category as specialCategory, sa.weaker_section as weakerSection, ");
selectFieldsSqlBuilder.append("sa.guardian_name as guardianName, sa.guardian_cif_number as guardianCifNumber, ");
selectFieldsSqlBuilder.append("sa.guardian_relationship as guardianRelationship, sa.guardian_client_id as guardianClientId, ");
selectFieldsSqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, ");
selectFieldsSqlBuilder.append("sa.introducer_client_id as introducerClientId, sa.introducer_name as introducerName, ");
selectFieldsSqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, ");
selectFieldsSqlBuilder.append("sa.linked_account_id as linkedAccountId, sa.linked_account_type as linkedAccountType, ");
selectFieldsSqlBuilder.append("sa.linked_account_no as linkedAccountNo, ");
selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, ");
selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,");
selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName ");
@@ -706,6 +734,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
selectTablesSqlBuilder.append("from m_savings_account sa ");
selectTablesSqlBuilder.append("left join m_deposit_account_term_and_preclosure datp on sa.id = datp.savings_account_id ");
selectTablesSqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
selectTablesSqlBuilder.append("left join m_code_value icv on icv.id = sp.interest_calculate_selector ");
selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
selectTablesSqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
selectTablesSqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
@@ -714,7 +743,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
selectTablesSqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid ");
selectTablesSqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid ");
selectTablesSqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid ");
selectTablesSqlBuilder.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid ");
selectTablesSqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid ");
selectTablesSqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid ");
selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id ");
@@ -904,7 +933,28 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, ");
sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, ");
sqlBuilder.append("datp.transfer_interest_to_linked_account as transferInterestToSavings, ");
sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId ");
sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId, ");
sqlBuilder.append("sp.fd_type as fdType, sp.product_code as productCode, sp.effective_date as effectiveDate, ");
sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, ");
sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, ");
sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, ");
sqlBuilder.append("sp.dormancy_alert as dormancyAlert, 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, sp.interest_calculate_selector as interestCalculateSelector, ");
sqlBuilder.append("icv.code_value as interestCalculationTypeName, 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.balance_swap_option as balanceSwapOption, sp.msme as msme, ");
sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, ");
sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, ");
sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, ");
sqlBuilder.append("sp.maturity_instructions as maturityInstructions, 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, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate ");
sqlBuilder.append(super.selectTablesSql());
@@ -954,10 +1004,26 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final Long transferToSavingsId = JdbcSupport.getLong(rs, TRANSFER_TO_SAVINGS_ID);
return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, transferToSavingsId);
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"),
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"),
JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"),
rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"),
rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"),
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
return result;
}
}
@@ -1014,7 +1080,28 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
sqlBuilder.append("datp.maturity_date as maturityDate, ");
sqlBuilder.append("datp.deposit_period as depositPeriod, ");
sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, ");
sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId ");
sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, ");
sqlBuilder.append("sp.product_code as productCode, sp.effective_date as effectiveDate, ");
sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, ");
sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, ");
sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, ");
sqlBuilder.append("sp.dormancy_alert as dormancyAlert, 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, sp.interest_calculate_selector as interestCalculateSelector, ");
sqlBuilder.append("icv.code_value as interestCalculationTypeName, 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.balance_swap_option as balanceSwapOption, sp.msme as msme, ");
sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, ");
sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, ");
sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, ");
sqlBuilder.append("sp.maturity_instructions as maturityInstructions, 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, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate ");
sqlBuilder.append(this.selectTablesSql());
sqlBuilder.append("left join m_deposit_account_recurring_detail dard on sa.id = dard.savings_account_id ");
@@ -1071,12 +1158,30 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
: SavingsEnumerations.depositAccountOnClosureType(onAccountClosureId);
final LocalDate expectedFirstDepositOnDate = JdbcSupport.getLocalDate(rs, EXPECTED_FIRST_DEPOSIT_ON_DATE);
return RecurringDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, onAccountClosureType, expectedFirstDepositOnDate,
totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments,
isCalendarInherited);
final RecurringDepositAccountData result = RecurringDepositAccountData.instance(depositAccountData,
preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm,
minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount,
maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, mandatoryRecommendedDepositAmount,
onAccountClosureType, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit,
allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited);
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("productCode"),
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
JdbcSupport.getInteger(rs, "daysToInactive"), JdbcSupport.getInteger(rs, "daysToDormancy"),
JdbcSupport.getInteger(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"),
rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"),
rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"),
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
return result;
}
}
@@ -1423,6 +1528,31 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, ");
sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
sqlBuilder.append("sa.account_type as accountType, sa.customer_segment as customerSegment, sa.fd_type as fdType, ");
sqlBuilder.append("sa.product_code as productCode, ");
sqlBuilder.append("sa.effective_date as effectiveDate, sa.effective_to as effectiveTo, sa.end_date as endDate, ");
sqlBuilder.append("sa.introducer_required as introducerRequired, sa.share_linking_enabled as shareLinkingEnabled, ");
sqlBuilder.append("sa.nomination_facility as nominationFacility, sa.dormancy_alert as dormancyAlert, ");
sqlBuilder.append("sa.is_dormancy_tracking_active as isDormancyTrackingActive, ");
sqlBuilder.append("sa.days_to_inactive as daysToInactive, sa.days_to_dormancy as daysToDormancy, ");
sqlBuilder.append("sa.days_to_escheat as daysToEscheat, ");
sqlBuilder.append("sa.interest_calculate_selector as interestCalculateSelector, ");
sqlBuilder.append("icv.code_value as interestCalculationTypeName, ");
sqlBuilder.append("sa.average_period as averagePeriod, ");
sqlBuilder.append("sa.auto_post as autoPost, sa.frequency_fix_day as frequencyFixDay, ");
sqlBuilder.append("sa.last_interest_date as lastInterestDate, sa.enable_lockin_period as enableLockinPeriod, ");
sqlBuilder.append("sa.balance_frequency as balanceFrequency, ");
sqlBuilder.append("sa.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
sqlBuilder.append("sa.balance_swap_option as balanceSwapOption, sa.msme as msme, ");
sqlBuilder.append("sa.auto_debit_facility as autoDebitFacility, sa.loan_facility as loanFacility, ");
sqlBuilder.append("sa.premature_withdrawal_option as prematureWithdrawalOption, sa.tenure_flexibility as tenureFlexibility, ");
sqlBuilder.append("sa.lien_marked as lienMarked, sa.maturity_instructions as maturityInstructions, ");
sqlBuilder.append("sa.credit_maturity_proceeds as creditMaturityProceeds, ");
sqlBuilder.append("sa.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, ");
sqlBuilder.append("sa.panel_request_applicable as panelRequestApplicable, ");
sqlBuilder.append("sa.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, ");
sqlBuilder.append("sa.deceased_settlement as deceasedSettlement, ");
sqlBuilder.append("sa.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, ");
sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, ");
sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, ");
sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, ");
@@ -1431,6 +1561,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId ");
sqlBuilder.append(super.selectTablesSql());
sqlBuilder.append("left join m_code_value icv on icv.id = sa.interest_calculate_selector ");
this.schemaSql = sqlBuilder.toString();
}
@@ -1472,10 +1603,26 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final EnumOptionData onAccountClosureType = null;
final Boolean transferInterestToSavings = false;
return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null);
final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable,
preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType,
maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount,
maturityDate, depositPeriod, depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null);
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"),
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"),
JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"),
rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"),
rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"),
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
return result;
}
}
@@ -172,6 +172,9 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth);
this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account);
this.fixedDepositAccountRepository.saveAndFlush(account);
if (account.isAccountNumberRequiresAutoGeneration()) {
@@ -350,6 +353,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
DepositAccountType.FIXED_DEPOSIT);
checkClientOrGroupActive(account);
account.modifyApplication(command, changes);
this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account);
account.validateNewApplicationState(DepositAccountType.FIXED_DEPOSIT.resourceName());
if (!changes.isEmpty()) {
@@ -440,6 +444,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
DepositAccountType.RECURRING_DEPOSIT);
checkClientOrGroupActive(account);
account.modifyApplication(command, changes);
this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account);
account.validateNewApplicationState(DepositAccountType.RECURRING_DEPOSIT.resourceName());
if (!changes.isEmpty()) {
@@ -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 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";
};
}
}
@@ -0,0 +1,28 @@
/**
* 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.portfolio.savings.service;
import java.util.HashMap;
import org.apache.fineract.infrastructure.core.api.JsonQuery;
public interface RecurringDepositAccountInterestCalculationService {
HashMap<String, Object> calculateInterest(JsonQuery query);
}
@@ -0,0 +1,138 @@
/**
* 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.portfolio.savings.service;
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.annualInterestRateParamName;
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.interestCompoundingPeriodInMonthsParamName;
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInMonthsParamName;
import com.google.gson.JsonElement;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.HashMap;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonQuery;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RecurringDepositAccountInterestCalculationServiceImpl implements RecurringDepositAccountInterestCalculationService {
private final FromJsonHelper fromApiJsonHelper;
@Override
public HashMap<String, Object> calculateInterest(JsonQuery query) {
JsonElement element = query.parsedJson();
BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
BigDecimal annualInterestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualInterestRateParamName, element);
Long tenureInMonths = this.fromApiJsonHelper.extractLongNamed(tenureInMonthsParamName, element);
Long interestCompoundingPeriodInMonths = this.fromApiJsonHelper.extractLongNamed(interestCompoundingPeriodInMonthsParamName,
element);
LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
if (submittedOnDate == null) {
submittedOnDate = LocalDate.now();
}
BigDecimal maturityAmount = this.calculateInterestInternal(depositAmount, annualInterestRate, tenureInMonths,
interestCompoundingPeriodInMonths, submittedOnDate);
BigDecimal totalPrincipal = depositAmount.multiply(BigDecimal.valueOf(tenureInMonths));
BigDecimal totalInterest = maturityAmount.subtract(totalPrincipal);
String warning = "This is an approximate calculated amount - it may vary slightly when the account is created";
HashMap<String, Object> result = new HashMap<>();
result.put("maturityAmount", maturityAmount);
result.put("totalPrincipal", totalPrincipal);
result.put("totalInterest", totalInterest);
result.put("daysInYear", 365); // Standard for this product as per image
result.put("warning", warning);
return result;
}
public BigDecimal calculateInterestInternal(BigDecimal installmentAmount, BigDecimal annualInterestRate, Long tenureInMonths,
Long interestCompoundingPeriodInMonths, LocalDate submittedOnDate) {
MathContext mc = MoneyHelper.getMathContext();
BigDecimal maturityAmount = BigDecimal.ZERO;
int totalMonths = Math.toIntExact(tenureInMonths);
if (interestCompoundingPeriodInMonths == 0) {
// Standard Banking Approach for Daily Compounding RD using Actual Calendar Days
BigDecimal currentBalance = BigDecimal.ZERO;
double dailyRate = annualInterestRate.doubleValue() / 36500.0;
LocalDate date = submittedOnDate;
for (int m = 1; m <= totalMonths; m++) {
// Add monthly installment at the start of the month
currentBalance = currentBalance.add(installmentAmount);
// Get actual days in the current month
int daysInMonth = date.lengthOfMonth();
// Calculate interest for the month using daily compounding
double multiplier = Math.pow(1.0 + dailyRate, daysInMonth);
currentBalance = currentBalance.multiply(BigDecimal.valueOf(multiplier), mc);
// Move to next month
date = date.plusMonths(1);
}
maturityAmount = currentBalance;
} else {
// Periodic Compounding (Monthly, Quarterly, etc.)
BigDecimal monthlyRate = annualInterestRate.divide(BigDecimal.valueOf(1200), mc);
BigDecimal i = monthlyRate;
int n = totalMonths;
// Standard RD formula for monthly compounding:
// M = P * ((1+i)^n - 1) / i * (1+i)
// For other periods, we can approximate or use a loop.
// Using loop for consistency with various compounding periods.
BigDecimal compoundingFrequencyMonths = BigDecimal.valueOf(interestCompoundingPeriodInMonths);
BigDecimal ratePerCompounding = annualInterestRate.divide(BigDecimal.valueOf(100), mc)
.multiply(compoundingFrequencyMonths).divide(BigDecimal.valueOf(12), mc);
for (int m = 1; m <= totalMonths; m++) {
BigDecimal monthsRemaining = BigDecimal.valueOf(totalMonths - m + 1);
BigDecimal numberOfCompoundings = monthsRemaining.divide(compoundingFrequencyMonths, mc);
BigDecimal installmentMaturity = installmentAmount.multiply(
BigDecimal.ONE.add(ratePerCompounding).pow(numberOfCompoundings.intValue(), mc), mc);
// Add remaining partial compounding period interest (simple interest for the rest)
BigDecimal remainingMonths = monthsRemaining.remainder(compoundingFrequencyMonths);
if (remainingMonths.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal partialInterest = installmentMaturity.multiply(annualInterestRate).multiply(remainingMonths)
.divide(BigDecimal.valueOf(1200), mc);
installmentMaturity = installmentMaturity.add(partialInterest);
}
maturityAmount = maturityAmount.add(installmentMaturity);
}
}
return maturityAmount.setScale(2, RoundingMode.HALF_UP);
}
}
@@ -800,7 +800,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sa.linked_account_no as linkedAccountNo, ");
sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, ");
sqlBuilder.append("sp.msme as msme, ");
sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, ");
sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, ");
sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, ");
// Facility config fields
sqlBuilder.append("safc.atm_facility as atmFacility, ");
sqlBuilder.append("safc.atm_withdrawal_facility as atmWithdrawalFacility, ");
@@ -1054,6 +1055,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
accountData.setLinkedAccountNo(rs.getString("linkedAccountNo"));
accountData.setBalanceSwapOption(rs.getObject("balanceSwapOption") != null ? rs.getBoolean("balanceSwapOption") : null);
accountData.setMsme(rs.getObject("msme") != null ? rs.getBoolean("msme") : null);
accountData.setNominationFacility(rs.getObject("nominationFacility") != null ? rs.getBoolean("nominationFacility") : null);
accountData.setAutoDebitFacility(rs.getObject("autoDebitFacility") != null ? rs.getBoolean("autoDebitFacility") : null);
// Nominee as parsed map
@@ -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,
@@ -290,4 +290,8 @@
<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/0284_remove_product_code_unique_constraints.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>
@@ -0,0 +1,25 @@
<?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="0284_remove_product_code_unique_constraints" author="system">
<!-- For Savings/RD/FD products -->
<dropUniqueConstraint tableName="m_savings_product" constraintName="m_savings_product_product_code_key"/>
<!-- For Current products -->
<dropUniqueConstraint tableName="m_current_product" constraintName="cp_unq_product_code"/>
<!-- Set existing empty or blank product_codes to NULL to avoid future issues -->
<update tableName="m_savings_product">
<column name="product_code" valueComputed="NULL"/>
<where>product_code = '' OR product_code IS NULL</where>
</update>
<update tableName="m_current_product">
<column name="product_code" valueComputed="NULL"/>
<where>product_code = '' OR product_code IS NULL</where>
</update>
</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"));
@@ -24,6 +24,7 @@ import java.util.Collection;
import lombok.Getter;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.staff.data.StaffData;
@@ -73,6 +74,48 @@ public final class FixedDepositAccountData extends DepositAccountData {
private Collection<PaymentTypeData> paymentTypeOptions;
private final Collection<EnumOptionData> maturityInstructionOptions;
// CBS product/account fields used by the FD UI
private String accountType;
private String customerSegment;
private String fdType;
private Collection<CodeValueData> accountTypeOptions;
private Collection<CodeValueData> customerSegmentOptions;
private String productCode;
private LocalDate effectiveDate;
private LocalDate effectiveTo;
private LocalDate endDate;
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 Integer interestCalculateSelector;
private String interestCalculationTypeName;
private String averagePeriod;
private Boolean autoPost;
private Integer frequencyFixDay;
private LocalDate lastInterestDate;
private Boolean enableLockinPeriod;
private String balanceFrequency;
private Boolean accountClosedZeroBalanceInDayClosing;
private Boolean balanceSwapOption;
private Boolean msme;
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;
// import fields
private transient Integer rowIndex;
private String dateFormat;
@@ -92,6 +135,123 @@ public final class FixedDepositAccountData extends DepositAccountData {
locale, dateFormat);
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public void setCustomerSegment(String customerSegment) {
this.customerSegment = customerSegment;
}
public void setAccountTypeOptions(Collection<CodeValueData> accountTypeOptions) {
this.accountTypeOptions = accountTypeOptions;
}
public void setCustomerSegmentOptions(Collection<CodeValueData> customerSegmentOptions) {
this.customerSegmentOptions = customerSegmentOptions;
}
public void setFdType(String fdType) { this.fdType = fdType; }
public String getFdType() { return fdType; }
public void setCbsProductFields(final String accountType, final String customerSegment, final String fdType, final String productCode,
final LocalDate effectiveDate, final LocalDate effectiveTo, final LocalDate endDate, final Boolean introducerRequired,
final Boolean shareLinkingEnabled, final Boolean nominationFacility, final Boolean dormancyAlert,
final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat,
final Integer interestCalculateSelector, final String interestCalculationTypeName, final String averagePeriod,
final Boolean autoPost, final Integer frequencyFixDay, final LocalDate lastInterestDate, final Boolean enableLockinPeriod,
final String balanceFrequency, final Boolean accountClosedZeroBalanceInDayClosing, final Boolean balanceSwapOption,
final Boolean msme, final Boolean autoDebitFacility, final Boolean loanFacility, final Boolean prematureWithdrawalOption,
final Boolean tenureFlexibility, final Boolean lienMarked, final Boolean maturityInstructions,
final Boolean creditMaturityProceeds, final Boolean penaltyGracePeriodMissedInstallments,
final Boolean panelRequestApplicable, final Boolean noPenaltyNomineeHeirClosure, final Boolean deceasedSettlement,
final Boolean allowDebitTxnAfterDueDate) {
this.accountType = accountType;
this.customerSegment = customerSegment;
this.fdType = fdType;
this.productCode = productCode;
this.effectiveDate = effectiveDate;
this.effectiveTo = effectiveTo;
this.endDate = endDate;
this.introducerRequired = introducerRequired;
this.shareLinkingEnabled = shareLinkingEnabled;
this.nominationFacility = nominationFacility;
this.dormancyAlert = dormancyAlert;
this.isDormancyTrackingActive = isDormancyTrackingActive;
this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat;
this.interestCalculateSelector = interestCalculateSelector;
this.interestCalculationTypeName = interestCalculationTypeName;
this.averagePeriod = averagePeriod;
this.autoPost = autoPost;
this.frequencyFixDay = frequencyFixDay;
this.lastInterestDate = lastInterestDate;
this.enableLockinPeriod = enableLockinPeriod;
this.balanceFrequency = balanceFrequency;
this.accountClosedZeroBalanceInDayClosing = accountClosedZeroBalanceInDayClosing;
this.balanceSwapOption = balanceSwapOption;
this.msme = msme;
this.autoDebitFacility = autoDebitFacility;
this.loanFacility = loanFacility;
this.prematureWithdrawalOption = prematureWithdrawalOption;
this.tenureFlexibility = tenureFlexibility;
this.lienMarked = lienMarked;
this.maturityInstructions = maturityInstructions;
this.creditMaturityProceeds = creditMaturityProceeds;
this.penaltyGracePeriodMissedInstallments = penaltyGracePeriodMissedInstallments;
this.panelRequestApplicable = panelRequestApplicable;
this.noPenaltyNomineeHeirClosure = noPenaltyNomineeHeirClosure;
this.deceasedSettlement = deceasedSettlement;
this.allowDebitTxnAfterDueDate = allowDebitTxnAfterDueDate;
}
public void copyCbsFieldsFrom(final FixedDepositAccountData src) {
if (src == null) {
return;
}
this.accountType = src.accountType;
this.customerSegment = src.customerSegment;
this.fdType = src.fdType;
this.accountTypeOptions = src.accountTypeOptions;
this.customerSegmentOptions = src.customerSegmentOptions;
this.productCode = src.productCode;
this.effectiveDate = src.effectiveDate;
this.effectiveTo = src.effectiveTo;
this.endDate = src.endDate;
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.interestCalculateSelector = src.interestCalculateSelector;
this.interestCalculationTypeName = src.interestCalculationTypeName;
this.averagePeriod = src.averagePeriod;
this.autoPost = src.autoPost;
this.frequencyFixDay = src.frequencyFixDay;
this.lastInterestDate = src.lastInterestDate;
this.enableLockinPeriod = src.enableLockinPeriod;
this.balanceFrequency = src.balanceFrequency;
this.accountClosedZeroBalanceInDayClosing = src.accountClosedZeroBalanceInDayClosing;
this.balanceSwapOption = src.balanceSwapOption;
this.msme = src.msme;
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;
}
private FixedDepositAccountData(Long clientId, Long productId, Long fieldofficerId, LocalDate submittedOnDate,
EnumOptionData interestCompoundingPeriodType, EnumOptionData interestPostingPeriodType, EnumOptionData interestCalculationType,
EnumOptionData interestCalculationDaysInYearType, Integer lockinPeriodFrequency, EnumOptionData lockinPeriodFrequencyType,
@@ -150,7 +310,7 @@ public final class FixedDepositAccountData extends DepositAccountData {
final Collection<PaymentTypeData> paymentTypeOptions = null;
final Collection<SavingsAccountData> savingsAccountDatas = null;
return new FixedDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId,
final FixedDepositAccountData result = new FixedDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId,
depositAccountData.groupId, depositAccountData.groupName, depositAccountData.clientId, depositAccountData.clientName,
depositAccountData.depositProductId, depositAccountData.depositProductName, depositAccountData.fieldOfficerId,
depositAccountData.fieldOfficerName, depositAccountData.status, depositAccountData.timeline, depositAccountData.currency,
@@ -170,11 +330,15 @@ public final class FixedDepositAccountData extends DepositAccountData {
depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions,
paymentTypeOptions, savingsAccountDatas, linkedAccount, transferInterestToSavings, depositAccountData.withHoldTax,
depositAccountData.taxGroup, maturityInstructionOptions, transferToSavingsId, transferToSavingsAccount);
if (depositAccountData instanceof FixedDepositAccountData) {
result.copyCbsFieldsFrom((FixedDepositAccountData) depositAccountData);
}
return result;
}
public static FixedDepositAccountData withInterestChart(final FixedDepositAccountData account,
final DepositAccountInterestRateChartData accountChart) {
return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
@@ -192,6 +356,8 @@ public final class FixedDepositAccountData extends DepositAccountData {
account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts,
account.linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup,
account.maturityInstructionOptions, account.transferToSavingsId, account.transferToSavingsAccount);
result.copyCbsFieldsFrom(account);
return result;
}
public static FixedDepositAccountData associationsAndTemplate(final FixedDepositAccountData account, FixedDepositAccountData template,
@@ -202,7 +368,7 @@ public final class FixedDepositAccountData extends DepositAccountData {
template = account;
}
return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
@@ -220,6 +386,8 @@ public final class FixedDepositAccountData extends DepositAccountData {
account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, template.savingsAccounts,
linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup, account.maturityInstructionOptions,
account.transferToSavingsId, transferToSavingsAccount);
result.copyCbsFieldsFrom(account);
return result;
}
public static FixedDepositAccountData withTemplateOptions(final FixedDepositAccountData account,
@@ -234,7 +402,7 @@ public final class FixedDepositAccountData extends DepositAccountData {
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<SavingsAccountData> savingsAccounts,
final Collection<EnumOptionData> maturityInstructionOptions) {
return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
@@ -251,6 +419,8 @@ public final class FixedDepositAccountData extends DepositAccountData {
account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, savingsAccounts,
account.linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup, maturityInstructionOptions,
account.transferToSavingsId, account.transferToSavingsAccount);
result.copyCbsFieldsFrom(account);
return result;
}
public static FixedDepositAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -427,7 +597,7 @@ public final class FixedDepositAccountData extends DepositAccountData {
final Collection<EnumOptionData> onAccountClosureOptions, final Collection<PaymentTypeData> paymentTypeOptions,
final Collection<SavingsAccountData> savingsAccountDatas) {
return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate,
account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType,
@@ -445,7 +615,8 @@ public final class FixedDepositAccountData extends DepositAccountData {
account.onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, account.linkedAccount,
account.transferInterestToSavings, account.withHoldTax, account.taxGroup, account.maturityInstructionOptions,
account.transferToSavingsId, account.transferToSavingsAccount);
result.copyCbsFieldsFrom(account);
return result;
}
private FixedDepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId,
@@ -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;
@@ -338,6 +397,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));
}
@@ -3957,6 +3957,13 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
@Column(name = "linked_account_no", length = 50)
private String linkedAccountNo;
@Column(name = "nomination_facility")
private Boolean nominationFacility;
@Column(name = "auto_debit_facility")
private Boolean autoDebitFacility;
@jakarta.persistence.OneToOne(cascade = jakarta.persistence.CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true, fetch = FetchType.LAZY)
private SavingsAccountFacilityConfig facilityConfig;
@@ -4022,4 +4029,10 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
public List<SavingsAccountJointHolder> getJointHolders() { return jointHolders; }
public void setJointHolders(List<SavingsAccountJointHolder> v) { this.jointHolders = v; }
public Boolean getNominationFacility() { return nominationFacility; }
public void setNominationFacility(Boolean v) { this.nominationFacility = v; }
public Boolean getAutoDebitFacility() { return autoDebitFacility; }
public void setAutoDebitFacility(Boolean v) { this.autoDebitFacility = v; }
}
@@ -246,7 +246,7 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
@Column(name = "customer_segment", length = 50)
private String customerSegment;
@Column(name = "product_code", length = 20, unique = true)
@Column(name = "product_code", length = 20)
private String productCode;
@Column(name = "gl_account_id")
@@ -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;
}