update RD Prodcut code
This commit is contained in:
+79
@@ -292,6 +292,44 @@ public final class DepositsApiConstants {
|
|||||||
recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
|
recurringDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS);
|
||||||
recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS);
|
recurringDepositRequestData.addAll(RECURRING_DETAILS_REQUEST_DATA_PARAMETERS);
|
||||||
recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
|
recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
|
||||||
|
// Custom CBS fields — Details step
|
||||||
|
recurringDepositRequestData.add("accountType");
|
||||||
|
recurringDepositRequestData.add("customerSegment");
|
||||||
|
recurringDepositRequestData.add("productCode");
|
||||||
|
recurringDepositRequestData.add("effectiveTo");
|
||||||
|
recurringDepositRequestData.add("endDate");
|
||||||
|
recurringDepositRequestData.add("introducerRequired");
|
||||||
|
recurringDepositRequestData.add("shareLinkingEnabled");
|
||||||
|
// Custom CBS fields — Currency step
|
||||||
|
recurringDepositRequestData.add("dormancyAlert");
|
||||||
|
recurringDepositRequestData.add("balanceSwapOption");
|
||||||
|
recurringDepositRequestData.add("msme");
|
||||||
|
recurringDepositRequestData.add("nominationFacility");
|
||||||
|
recurringDepositRequestData.add("autoDebitFacility");
|
||||||
|
// Custom CBS fields — Terms step
|
||||||
|
recurringDepositRequestData.add("effectiveDate");
|
||||||
|
recurringDepositRequestData.add("interestCalculateSelector");
|
||||||
|
recurringDepositRequestData.add("averagePeriod");
|
||||||
|
recurringDepositRequestData.add("autoPost");
|
||||||
|
recurringDepositRequestData.add("frequencyFixDay");
|
||||||
|
recurringDepositRequestData.add("lastInterestDate");
|
||||||
|
// Custom CBS fields — Settings step
|
||||||
|
recurringDepositRequestData.add("loanFacility");
|
||||||
|
recurringDepositRequestData.add("prematureWithdrawalOption");
|
||||||
|
recurringDepositRequestData.add("tenureFlexibility");
|
||||||
|
recurringDepositRequestData.add("lienMarked");
|
||||||
|
recurringDepositRequestData.add("withdrawalFeeForTransfers");
|
||||||
|
recurringDepositRequestData.add("enableLockinPeriod");
|
||||||
|
recurringDepositRequestData.add("maturityInstructions");
|
||||||
|
recurringDepositRequestData.add("creditMaturityProceeds");
|
||||||
|
recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments");
|
||||||
|
recurringDepositRequestData.add("panelRequestApplicable");
|
||||||
|
recurringDepositRequestData.add("noPenaltyNomineeHeirClosure");
|
||||||
|
recurringDepositRequestData.add("deceasedSettlement");
|
||||||
|
recurringDepositRequestData.add("allowDebitTxnAfterDueDate");
|
||||||
|
recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing");
|
||||||
|
// Custom CBS fields — Interest rate slabs (sent inline but ignored server-side; saved separately)
|
||||||
|
recurringDepositRequestData.add("rdSlabs");
|
||||||
return recurringDepositRequestData;
|
return recurringDepositRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +340,47 @@ public final class DepositsApiConstants {
|
|||||||
recurringDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
|
recurringDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS);
|
||||||
recurringDepositRequestData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS);
|
recurringDepositRequestData.addAll(RECURRING_DETAILS_RESPONSE_DATA_PARAMETERS);
|
||||||
recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
|
recurringDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName);
|
||||||
|
// CBS extended fields — Options (for template=true)
|
||||||
|
recurringDepositRequestData.add("interestCalculationTypeOption");
|
||||||
|
recurringDepositRequestData.add("periodFrequencyOptions");
|
||||||
|
recurringDepositRequestData.add("yesNoOptions");
|
||||||
|
recurringDepositRequestData.add("accountTypeOptions");
|
||||||
|
recurringDepositRequestData.add("customerSegmentOptions");
|
||||||
|
// CBS extended fields — Details step
|
||||||
|
recurringDepositRequestData.add("accountType");
|
||||||
|
recurringDepositRequestData.add("customerSegment");
|
||||||
|
recurringDepositRequestData.add("productCode");
|
||||||
|
recurringDepositRequestData.add("effectiveDate");
|
||||||
|
recurringDepositRequestData.add("effectiveTo");
|
||||||
|
recurringDepositRequestData.add("endDate");
|
||||||
|
recurringDepositRequestData.add("introducerRequired");
|
||||||
|
recurringDepositRequestData.add("shareLinkingEnabled");
|
||||||
|
recurringDepositRequestData.add("nominationFacility");
|
||||||
|
recurringDepositRequestData.add("dormancyAlert");
|
||||||
|
// CBS extended fields — Terms step
|
||||||
|
recurringDepositRequestData.add("interestCalculateSelector");
|
||||||
|
recurringDepositRequestData.add("averagePeriod");
|
||||||
|
recurringDepositRequestData.add("autoPost");
|
||||||
|
recurringDepositRequestData.add("frequencyFixDay");
|
||||||
|
recurringDepositRequestData.add("lastInterestDate");
|
||||||
|
// CBS extended fields — Settings step
|
||||||
|
recurringDepositRequestData.add("enableLockinPeriod");
|
||||||
|
recurringDepositRequestData.add("balanceFrequency");
|
||||||
|
recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing");
|
||||||
|
recurringDepositRequestData.add("balanceSwapOption");
|
||||||
|
recurringDepositRequestData.add("msme");
|
||||||
|
recurringDepositRequestData.add("autoDebitFacility");
|
||||||
|
recurringDepositRequestData.add("loanFacility");
|
||||||
|
recurringDepositRequestData.add("prematureWithdrawalOption");
|
||||||
|
recurringDepositRequestData.add("tenureFlexibility");
|
||||||
|
recurringDepositRequestData.add("lienMarked");
|
||||||
|
recurringDepositRequestData.add("maturityInstructions");
|
||||||
|
recurringDepositRequestData.add("creditMaturityProceeds");
|
||||||
|
recurringDepositRequestData.add("penaltyGracePeriodMissedInstallments");
|
||||||
|
recurringDepositRequestData.add("panelRequestApplicable");
|
||||||
|
recurringDepositRequestData.add("noPenaltyNomineeHeirClosure");
|
||||||
|
recurringDepositRequestData.add("deceasedSettlement");
|
||||||
|
recurringDepositRequestData.add("allowDebitTxnAfterDueDate");
|
||||||
return recurringDepositRequestData;
|
return recurringDepositRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request payload for saving a new RD interest rate version.
|
||||||
|
* JSON structure:
|
||||||
|
* {
|
||||||
|
* "versionLabel": "Rate Version 2025-Q2",
|
||||||
|
* "remarks": "Revised rates",
|
||||||
|
* "slabs": [
|
||||||
|
* { "srNo": 1, "description": "15 Days to 29 Days", "periodTypeEnum": 1,
|
||||||
|
* "fromPeriod": 15, "toPeriod": 29, "annualInterestRate": 3.00 },
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RdInterestRateSaveRequest {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-4
@@ -79,6 +79,11 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYea
|
|||||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
import java.time.LocalDate;
|
||||||
import org.apache.fineract.portfolio.savings.data.RecurringDepositProductData;
|
import org.apache.fineract.portfolio.savings.data.RecurringDepositProductData;
|
||||||
import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService;
|
import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService;
|
import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService;
|
||||||
@@ -117,6 +122,10 @@ public class RecurringDepositProductsApiResource {
|
|||||||
private final TaxReadPlatformService taxReadPlatformService;
|
private final TaxReadPlatformService taxReadPlatformService;
|
||||||
private final CodeValueReadPlatformService codeValueReadPlatformService;
|
private final CodeValueReadPlatformService codeValueReadPlatformService;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(LocalDate.class,
|
||||||
|
(JsonSerializer<LocalDate>) (src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString()))
|
||||||
|
.create();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@@ -354,19 +363,21 @@ public class RecurringDepositProductsApiResource {
|
|||||||
private String generateNextProductCode(final String tableName, final String prefix) {
|
private String generateNextProductCode(final String tableName, final String prefix) {
|
||||||
try {
|
try {
|
||||||
String maxCode = jdbcTemplate.queryForObject(
|
String maxCode = jdbcTemplate.queryForObject(
|
||||||
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ? AND deposit_type_enum = 3",
|
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ? AND deposit_type_enum = 300",
|
||||||
String.class, prefix + "%");
|
String.class, prefix + "%");
|
||||||
if (maxCode == null || maxCode.isBlank()) {
|
if (maxCode == null || maxCode.isBlank()) {
|
||||||
return prefix + "00";
|
return prefix + "001";
|
||||||
}
|
}
|
||||||
String numStr = maxCode.substring(prefix.length());
|
String numStr = maxCode.substring(prefix.length());
|
||||||
int num = Integer.parseInt(numStr);
|
int num = Integer.parseInt(numStr);
|
||||||
return prefix + String.format("%02d", num + 1);
|
return prefix + String.format("%03d", num + 1);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return prefix + "00";
|
return prefix + "001";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── RD Interest Rate Chart endpoints ────────────────────────────────────
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("{productId}")
|
@Path("{productId}")
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
|||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
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.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.RdInterestRateEffectiveData;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Path("/v1/recurringdepositproducts/baseinterestrates")
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RecurringDepositProductsBaseInterestRatesApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final RdBaseInterestRateReadPlatformService rdBaseInterestRateReadPlatformService;
|
||||||
|
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 RD base interest rate charts", description = "Returns the current CBS base interest-rate version with slabs.")
|
||||||
|
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||||
|
public String retrieveAllActiveInterestRates() {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
|
||||||
|
final List<RdInterestRateEffectiveData> data = this.rdBaseInterestRateReadPlatformService.retrieveAllActiveInterestRates();
|
||||||
|
return gson.toJson(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("history")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Get all RD base interest rate charts", description = "Returns the full CBS base chart history.")
|
||||||
|
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||||
|
public String retrieveHistory() {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
|
||||||
|
final List<RdInterestRateEffectiveData> data = this.rdBaseInterestRateReadPlatformService.retrieveAllInterestRates();
|
||||||
|
return gson.toJson(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Save new RD base interest rate version", description = "Creates a new CBS base chart version 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.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME);
|
||||||
|
final RdInterestRateSaveRequest request = gson.fromJson(apiRequestBodyAsJson, RdInterestRateSaveRequest.class);
|
||||||
|
final Long createdById = this.context.authenticatedUser().getId();
|
||||||
|
final RdInterestRateEffectiveData saved = this.rdBaseInterestRateReadPlatformService.saveInterestRates(request, createdById);
|
||||||
|
return gson.toJson(saved);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-8
@@ -170,13 +170,21 @@ public class DepositProductDataValidator {
|
|||||||
|
|
||||||
validatePreClosureDetailForCreate(element, baseDataValidator);
|
validatePreClosureDetailForCreate(element, baseDataValidator);
|
||||||
|
|
||||||
validateDepositTermDeatilForCreate(element, baseDataValidator);
|
// When custom rdSlabs are provided, minDepositTerm / charts / depositAmount are not required
|
||||||
|
final boolean hasRdSlabs = this.fromApiJsonHelper.parameterExists("rdSlabs", element)
|
||||||
|
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element) != null
|
||||||
|
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element).size() > 0;
|
||||||
|
|
||||||
|
if (!hasRdSlabs) {
|
||||||
|
validateDepositTermDeatilForCreate(element, baseDataValidator);
|
||||||
|
}
|
||||||
|
|
||||||
validateRecurringDetailForCreate(element, baseDataValidator);
|
validateRecurringDetailForCreate(element, baseDataValidator);
|
||||||
|
|
||||||
validateChartsData(element, baseDataValidator);
|
if (!hasRdSlabs) {
|
||||||
|
validateChartsData(element, baseDataValidator);
|
||||||
validateDepositAmountForCreate(element, baseDataValidator);
|
validateDepositAmountForCreate(element, baseDataValidator);
|
||||||
|
}
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
@@ -198,13 +206,20 @@ public class DepositProductDataValidator {
|
|||||||
|
|
||||||
validatePreClosureDetailForUpdate(element, baseDataValidator);
|
validatePreClosureDetailForUpdate(element, baseDataValidator);
|
||||||
|
|
||||||
validateDepositTermDetailForUpdate(element, baseDataValidator);
|
final boolean hasRdSlabsForUpdate = this.fromApiJsonHelper.parameterExists("rdSlabs", element)
|
||||||
|
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element) != null
|
||||||
|
&& this.fromApiJsonHelper.extractJsonArrayNamed("rdSlabs", element).size() > 0;
|
||||||
|
|
||||||
|
if (!hasRdSlabsForUpdate) {
|
||||||
|
validateDepositTermDetailForUpdate(element, baseDataValidator);
|
||||||
|
}
|
||||||
|
|
||||||
validateRecurringDepositUpdate(element, baseDataValidator);
|
validateRecurringDepositUpdate(element, baseDataValidator);
|
||||||
|
|
||||||
validateChartsData(element, baseDataValidator);
|
if (!hasRdSlabsForUpdate) {
|
||||||
|
validateChartsData(element, baseDataValidator);
|
||||||
validateDepositAmountForUpdate(element, baseDataValidator);
|
validateDepositAmountForUpdate(element, baseDataValidator);
|
||||||
|
}
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|||||||
+31
@@ -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 RdInterestRateEffectiveData {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long productId;
|
||||||
|
private String versionLabel;
|
||||||
|
private LocalDate effectiveFromDate;
|
||||||
|
private LocalDate effectiveToDate;
|
||||||
|
private Boolean isActive;
|
||||||
|
private String remarks;
|
||||||
|
private List<RdInterestRateSlabData> slabs;
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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 RdInterestRateSlabData {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Integer srNo;
|
||||||
|
private String description;
|
||||||
|
private Integer periodTypeEnum;
|
||||||
|
private String periodTypeLabel;
|
||||||
|
private Integer fromPeriod;
|
||||||
|
private Integer toPeriod;
|
||||||
|
private BigDecimal annualInterestRate;
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* 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_rd_base_interest_rate_effective")
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RdBaseInterestRateEffective {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@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 RdBaseInterestRateEffective create(String versionLabel, LocalDate effectiveFromDate, String remarks, Long createdById) {
|
||||||
|
RdBaseInterestRateEffective e = new RdBaseInterestRateEffective();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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 RdBaseInterestRateEffectiveRepository extends JpaRepository<RdBaseInterestRateEffective, Long> {
|
||||||
|
|
||||||
|
List<RdBaseInterestRateEffective> findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
||||||
|
|
||||||
|
List<RdBaseInterestRateEffective> findByOrderByEffectiveFromDateDesc();
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* 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 lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_rd_base_interest_rate_slab_history")
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RdBaseInterestRateSlabHistory {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
public static RdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo,
|
||||||
|
String description, Integer periodTypeEnum, Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate) {
|
||||||
|
RdBaseInterestRateSlabHistory s = new RdBaseInterestRateSlabHistory();
|
||||||
|
s.effectiveId = effectiveId;
|
||||||
|
s.srNo = srNo;
|
||||||
|
s.description = description;
|
||||||
|
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
|
||||||
|
s.fromPeriod = fromPeriod;
|
||||||
|
s.toPeriod = toPeriod;
|
||||||
|
s.annualInterestRate = annualInterestRate;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -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 RdBaseInterestRateSlabHistoryRepository extends JpaRepository<RdBaseInterestRateSlabHistory, Long> {
|
||||||
|
|
||||||
|
List<RdBaseInterestRateSlabHistory> findByEffectiveIdOrderBySrNo(Long effectiveId);
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps to: m_rd_interest_rate_effective
|
||||||
|
* Stores one "version" of the bank's RD interest rate chart for a product.
|
||||||
|
* When a new version is saved the old one is deactivated (is_active=0) but kept for history.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_rd_interest_rate_effective")
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RdInterestRateEffective {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "product_id")
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@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 RdInterestRateEffective create(Long productId, String versionLabel,
|
||||||
|
LocalDate effectiveFromDate, String remarks, Long createdById) {
|
||||||
|
RdInterestRateEffective e = new RdInterestRateEffective();
|
||||||
|
e.productId = productId;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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 RdInterestRateEffectiveRepository extends JpaRepository<RdInterestRateEffective, Long> {
|
||||||
|
|
||||||
|
List<RdInterestRateEffective> findByProductIdOrderByEffectiveFromDateDesc(Long productId);
|
||||||
|
|
||||||
|
List<RdInterestRateEffective> findByProductIdAndIsActiveTrueOrderByEffectiveFromDateDesc(Long productId);
|
||||||
|
|
||||||
|
List<RdInterestRateEffective> findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* 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 lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps to: m_rd_interest_rate_slab_history
|
||||||
|
* One row per slab per version. All historical slabs are preserved.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_rd_interest_rate_slab_history")
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RdInterestRateSlabHistory {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
public static RdInterestRateSlabHistory create(Long effectiveId, Integer srNo,
|
||||||
|
String description, Integer periodTypeEnum,
|
||||||
|
Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate) {
|
||||||
|
RdInterestRateSlabHistory s = new RdInterestRateSlabHistory();
|
||||||
|
s.effectiveId = effectiveId;
|
||||||
|
s.srNo = srNo;
|
||||||
|
s.description = description;
|
||||||
|
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
|
||||||
|
s.fromPeriod = fromPeriod;
|
||||||
|
s.toPeriod = toPeriod;
|
||||||
|
s.annualInterestRate = annualInterestRate;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -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 RdInterestRateSlabHistoryRepository extends JpaRepository<RdInterestRateSlabHistory, Long> {
|
||||||
|
|
||||||
|
List<RdInterestRateSlabHistory> findByEffectiveIdOrderBySrNo(Long effectiveId);
|
||||||
|
}
|
||||||
+71
-2
@@ -301,9 +301,26 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
|||||||
sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, ");
|
sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, ");
|
||||||
sqlBuilder.append("sp.account_type as accountType, sp.customer_segment as customerSegment, ");
|
sqlBuilder.append("sp.account_type as accountType, sp.customer_segment as customerSegment, ");
|
||||||
sqlBuilder.append("sp.product_code as productCode, ");
|
sqlBuilder.append("sp.product_code as productCode, ");
|
||||||
|
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.interest_calculate_selector as interestCalculateSelector, ");
|
sqlBuilder.append("sp.interest_calculate_selector as interestCalculateSelector, ");
|
||||||
sqlBuilder.append("sp.average_period as averagePeriod, ");
|
sqlBuilder.append("sp.average_period as averagePeriod, ");
|
||||||
sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay ");
|
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("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("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("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id ");
|
||||||
sqlBuilder.append("left join m_deposit_product_recurring_detail dprd on sp.id=dprd.savings_product_id ");
|
sqlBuilder.append("left join m_deposit_product_recurring_detail dprd on sp.id=dprd.savings_product_id ");
|
||||||
@@ -350,10 +367,36 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
|||||||
final String accountType = rs.getString("accountType");
|
final String accountType = rs.getString("accountType");
|
||||||
final String customerSegment = rs.getString("customerSegment");
|
final String customerSegment = rs.getString("customerSegment");
|
||||||
final String productCode = rs.getString("productCode");
|
final String productCode = rs.getString("productCode");
|
||||||
|
final java.time.LocalDate effectiveDate = JdbcSupport.getLocalDate(rs, "effectiveDate");
|
||||||
|
final java.time.LocalDate effectiveTo = JdbcSupport.getLocalDate(rs, "effectiveTo");
|
||||||
|
final java.time.LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
|
||||||
|
final boolean introducerRequired = rs.getBoolean("introducerRequired");
|
||||||
|
final boolean shareLinkingEnabled = rs.getBoolean("shareLinkingEnabled");
|
||||||
|
final boolean nominationFacility = rs.getBoolean("nominationFacility");
|
||||||
|
final boolean dormancyAlert = rs.getBoolean("dormancyAlert");
|
||||||
final Integer interestCalculateSelector = JdbcSupport.getInteger(rs, "interestCalculateSelector");
|
final Integer interestCalculateSelector = JdbcSupport.getInteger(rs, "interestCalculateSelector");
|
||||||
final String averagePeriod = rs.getString("averagePeriod");
|
final String averagePeriod = rs.getString("averagePeriod");
|
||||||
final Boolean autoPost = rs.getBoolean("autoPost");
|
final boolean autoPost = rs.getBoolean("autoPost");
|
||||||
final Integer frequencyFixDay = JdbcSupport.getInteger(rs, "frequencyFixDay");
|
final Integer frequencyFixDay = JdbcSupport.getInteger(rs, "frequencyFixDay");
|
||||||
|
final java.time.LocalDate lastInterestDate = JdbcSupport.getLocalDate(rs, "lastInterestDate");
|
||||||
|
final boolean enableLockinPeriod = rs.getBoolean("enableLockinPeriod");
|
||||||
|
final String balanceFrequency = rs.getString("balanceFrequency");
|
||||||
|
final boolean accountClosedZeroBalanceInDayClosing = rs.getBoolean("accountClosedZeroBalanceInDayClosing");
|
||||||
|
final boolean balanceSwapOption = rs.getBoolean("balanceSwapOption");
|
||||||
|
final boolean msme = rs.getBoolean("msme");
|
||||||
|
final boolean autoDebitFacility = rs.getBoolean("autoDebitFacility");
|
||||||
|
final boolean loanFacility = rs.getBoolean("loanFacility");
|
||||||
|
final boolean prematureWithdrawalOption = rs.getBoolean("prematureWithdrawalOption");
|
||||||
|
final boolean tenureFlexibility = rs.getBoolean("tenureFlexibility");
|
||||||
|
final boolean lienMarked = rs.getBoolean("lienMarked");
|
||||||
|
final boolean maturityInstructions = rs.getBoolean("maturityInstructions");
|
||||||
|
final boolean creditMaturityProceeds = rs.getBoolean("creditMaturityProceeds");
|
||||||
|
final boolean penaltyGracePeriodMissedInstallments = rs.getBoolean("penaltyGracePeriodMissedInstallments");
|
||||||
|
final boolean panelRequestApplicable = rs.getBoolean("panelRequestApplicable");
|
||||||
|
final boolean noPenaltyNomineeHeirClosure = rs.getBoolean("noPenaltyNomineeHeirClosure");
|
||||||
|
final boolean deceasedSettlement = rs.getBoolean("deceasedSettlement");
|
||||||
|
final boolean allowDebitTxnAfterDueDate = rs.getBoolean("allowDebitTxnAfterDueDate");
|
||||||
|
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
|
||||||
|
|
||||||
RecurringDepositProductData result = RecurringDepositProductData.instance(depositProductData,
|
RecurringDepositProductData result = RecurringDepositProductData.instance(depositProductData,
|
||||||
preClosurePenalApplicable, preClosurePenalInterest,
|
preClosurePenalApplicable, preClosurePenalInterest,
|
||||||
@@ -363,10 +406,36 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
|||||||
result.setAccountType(accountType);
|
result.setAccountType(accountType);
|
||||||
result.setCustomerSegment(customerSegment);
|
result.setCustomerSegment(customerSegment);
|
||||||
result.setProductCode(productCode);
|
result.setProductCode(productCode);
|
||||||
|
result.setEffectiveDate(effectiveDate);
|
||||||
|
result.setEffectiveTo(effectiveTo);
|
||||||
|
result.setEndDate(endDate);
|
||||||
|
result.setIntroducerRequired(introducerRequired);
|
||||||
|
result.setShareLinkingEnabled(shareLinkingEnabled);
|
||||||
|
result.setNominationFacility(nominationFacility);
|
||||||
|
result.setDormancyAlert(dormancyAlert);
|
||||||
result.setInterestCalculateSelector(interestCalculateSelector);
|
result.setInterestCalculateSelector(interestCalculateSelector);
|
||||||
result.setAveragePeriod(averagePeriod);
|
result.setAveragePeriod(averagePeriod);
|
||||||
result.setAutoPost(autoPost);
|
result.setAutoPost(autoPost);
|
||||||
result.setFrequencyFixDay(frequencyFixDay);
|
result.setFrequencyFixDay(frequencyFixDay);
|
||||||
|
result.setLastInterestDate(lastInterestDate);
|
||||||
|
result.setEnableLockinPeriod(enableLockinPeriod);
|
||||||
|
result.setBalanceFrequency(balanceFrequency);
|
||||||
|
result.setAccountClosedZeroBalanceInDayClosing(accountClosedZeroBalanceInDayClosing);
|
||||||
|
result.setBalanceSwapOption(balanceSwapOption);
|
||||||
|
result.setMsme(msme);
|
||||||
|
result.setAutoDebitFacility(autoDebitFacility);
|
||||||
|
result.setLoanFacility(loanFacility);
|
||||||
|
result.setPrematureWithdrawalOption(prematureWithdrawalOption);
|
||||||
|
result.setTenureFlexibility(tenureFlexibility);
|
||||||
|
result.setLienMarked(lienMarked);
|
||||||
|
result.setMaturityInstructions(maturityInstructions);
|
||||||
|
result.setCreditMaturityProceeds(creditMaturityProceeds);
|
||||||
|
result.setPenaltyGracePeriodMissedInstallments(penaltyGracePeriodMissedInstallments);
|
||||||
|
result.setPanelRequestApplicable(panelRequestApplicable);
|
||||||
|
result.setNoPenaltyNomineeHeirClosure(noPenaltyNomineeHeirClosure);
|
||||||
|
result.setDeceasedSettlement(deceasedSettlement);
|
||||||
|
result.setAllowDebitTxnAfterDueDate(allowDebitTxnAfterDueDate);
|
||||||
|
result.setWithdrawalFeeForTransfers(withdrawalFeeForTransfers);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.RdInterestRateSaveRequest;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.RdInterestRateEffectiveData;
|
||||||
|
|
||||||
|
public interface RdBaseInterestRateReadPlatformService {
|
||||||
|
|
||||||
|
List<RdInterestRateEffectiveData> retrieveAllInterestRates();
|
||||||
|
|
||||||
|
List<RdInterestRateEffectiveData> retrieveAllActiveInterestRates();
|
||||||
|
|
||||||
|
RdInterestRateEffectiveData saveInterestRates(RdInterestRateSaveRequest request, Long createdById);
|
||||||
|
}
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
/**
|
||||||
|
* 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.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.portfolio.savings.api.RdInterestRateSaveRequest;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.RdInterestRateEffectiveData;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.RdInterestRateSlabData;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateEffective;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateEffectiveRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateSlabHistory;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateSlabHistoryRepository;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RdBaseInterestRateReadPlatformServiceImpl implements RdBaseInterestRateReadPlatformService {
|
||||||
|
|
||||||
|
private final RdBaseInterestRateEffectiveRepository effectiveRepository;
|
||||||
|
private final RdBaseInterestRateSlabHistoryRepository slabRepository;
|
||||||
|
|
||||||
|
private static final String[] PERIOD_LABELS = { "", "Days", "Months", "Years" };
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RdInterestRateEffectiveData> retrieveAllInterestRates() {
|
||||||
|
return effectiveRepository.findByOrderByEffectiveFromDateDesc().stream().map(this::toData).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RdInterestRateEffectiveData> retrieveAllActiveInterestRates() {
|
||||||
|
return effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc().stream().map(this::toData).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public RdInterestRateEffectiveData saveInterestRates(RdInterestRateSaveRequest request, Long createdById) {
|
||||||
|
validateRequest(request);
|
||||||
|
final List<RdInterestRateSaveRequest.SlabRequest> normalizedSlabs = normalizeSlabs(request.getSlabs());
|
||||||
|
|
||||||
|
final LocalDate today = LocalDate.now();
|
||||||
|
final List<RdBaseInterestRateEffective> activeVersions = effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
||||||
|
for (RdBaseInterestRateEffective version : activeVersions) {
|
||||||
|
version.deactivate(today);
|
||||||
|
}
|
||||||
|
if (!activeVersions.isEmpty()) {
|
||||||
|
effectiveRepository.saveAll(activeVersions);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String label = request.getVersionLabel() != null && !request.getVersionLabel().isBlank()
|
||||||
|
? request.getVersionLabel()
|
||||||
|
: "CBS Base Rate Version " + LocalDate.now();
|
||||||
|
|
||||||
|
RdBaseInterestRateEffective newVersion = RdBaseInterestRateEffective.create(label, LocalDate.now(), request.getRemarks(), createdById);
|
||||||
|
newVersion = effectiveRepository.saveAndFlush(newVersion);
|
||||||
|
|
||||||
|
final Long versionId = newVersion.getId();
|
||||||
|
final AtomicInteger srCounter = new AtomicInteger(1);
|
||||||
|
for (RdInterestRateSaveRequest.SlabRequest slabRequest : normalizedSlabs) {
|
||||||
|
final int sr = slabRequest.getSrNo() != null ? slabRequest.getSrNo() : srCounter.getAndIncrement();
|
||||||
|
final RdBaseInterestRateSlabHistory slab = RdBaseInterestRateSlabHistory.create(versionId, sr, slabRequest.getDescription(),
|
||||||
|
slabRequest.getPeriodTypeEnum(), slabRequest.getFromPeriod(), slabRequest.getToPeriod(),
|
||||||
|
slabRequest.getAnnualInterestRate());
|
||||||
|
slabRepository.save(slab);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toData(newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequest(final RdInterestRateSaveRequest request) {
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder validator = new DataValidatorBuilder(errors).resource("RD_BASE_INTEREST_RATE_VERSION");
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
validator.failWithCodeNoParameterAddedToErrorCode("request.cannot.be.null");
|
||||||
|
throwExceptionIfValidationWarningsExist(errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getSlabs() == null || request.getSlabs().isEmpty()) {
|
||||||
|
validator.parameter("slabs").failWithCode("cannot.be.blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getVersionLabel() != null && request.getVersionLabel().length() > 100) {
|
||||||
|
validator.parameter("versionLabel").value(request.getVersionLabel()).failWithCode("exceeds.max.length");
|
||||||
|
}
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RdInterestRateSaveRequest.SlabRequest> normalizeSlabs(final List<RdInterestRateSaveRequest.SlabRequest> slabs) {
|
||||||
|
final List<RdInterestRateSaveRequest.SlabRequest> sorted = new ArrayList<>(slabs);
|
||||||
|
sorted.sort(Comparator.comparing((RdInterestRateSaveRequest.SlabRequest slab) -> slab.getSrNo() == null ? Integer.MAX_VALUE : slab.getSrNo())
|
||||||
|
.thenComparing(slab -> slab.getFromPeriod() == null ? Integer.MAX_VALUE : slab.getFromPeriod()));
|
||||||
|
|
||||||
|
final List<RdInterestRateSaveRequest.SlabRequest> normalized = new ArrayList<>(sorted.size());
|
||||||
|
Integer expectedFrom = 0;
|
||||||
|
for (int i = 0; i < sorted.size(); i++) {
|
||||||
|
final RdInterestRateSaveRequest.SlabRequest source = sorted.get(i);
|
||||||
|
final RdInterestRateSaveRequest.SlabRequest target = new RdInterestRateSaveRequest.SlabRequest();
|
||||||
|
|
||||||
|
final Integer fromPeriod = source.getFromPeriod();
|
||||||
|
if (fromPeriod == null || !fromPeriod.equals(expectedFrom)) {
|
||||||
|
throwValidationError("fromPeriod", "must.start.from.previous.range.end");
|
||||||
|
}
|
||||||
|
target.setFromPeriod(fromPeriod);
|
||||||
|
|
||||||
|
final Integer toPeriod = source.getToPeriod();
|
||||||
|
if (i < sorted.size() - 1) {
|
||||||
|
if (toPeriod == null) {
|
||||||
|
throwValidationError("toPeriod", "cannot.be.blank.for.intermediate.slab");
|
||||||
|
}
|
||||||
|
if (toPeriod < fromPeriod) {
|
||||||
|
throwValidationError("toPeriod", "cannot.be.less.than.fromPeriod");
|
||||||
|
}
|
||||||
|
expectedFrom = toPeriod + 1;
|
||||||
|
target.setToPeriod(toPeriod);
|
||||||
|
} else {
|
||||||
|
target.setToPeriod(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.setSrNo(source.getSrNo() == null ? i + 1 : source.getSrNo());
|
||||||
|
target.setDescription(source.getDescription());
|
||||||
|
target.setPeriodTypeEnum(source.getPeriodTypeEnum() == null ? 1 : source.getPeriodTypeEnum());
|
||||||
|
target.setAnnualInterestRate(source.getAnnualInterestRate());
|
||||||
|
|
||||||
|
if (target.getAnnualInterestRate() == null) {
|
||||||
|
throwValidationError("annualInterestRate", "cannot.be.blank");
|
||||||
|
}
|
||||||
|
if (target.getAnnualInterestRate().signum() < 0) {
|
||||||
|
throwValidationError("annualInterestRate", "must.be.non.negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized.add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalized.isEmpty() && normalized.get(0).getFromPeriod() != 0) {
|
||||||
|
throwValidationError("fromPeriod", "must.start.from.zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwValidationError(final String parameterName, final String code) {
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
new DataValidatorBuilder(errors).resource("RD_BASE_INTEREST_RATE_VERSION").parameter(parameterName).failWithCode(code);
|
||||||
|
throwExceptionIfValidationWarningsExist(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||||
|
if (!dataValidationErrors.isEmpty()) {
|
||||||
|
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RdInterestRateEffectiveData toData(RdBaseInterestRateEffective e) {
|
||||||
|
List<RdInterestRateSlabData> slabs = slabRepository.findByEffectiveIdOrderBySrNo(e.getId()).stream()
|
||||||
|
.map(s -> RdInterestRateSlabData.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())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return RdInterestRateEffectiveData.builder()
|
||||||
|
.id(e.getId())
|
||||||
|
.productId(null)
|
||||||
|
.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 || code < 1 || code >= PERIOD_LABELS.length) return "Days";
|
||||||
|
return PERIOD_LABELS[code];
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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.RdInterestRateSaveRequest;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.RdInterestRateEffectiveData;
|
||||||
|
|
||||||
|
public interface RdInterestRateReadPlatformService {
|
||||||
|
|
||||||
|
/** All versions (with slabs) for a specific product, newest first. */
|
||||||
|
List<RdInterestRateEffectiveData> retrieveInterestRates(Long productId);
|
||||||
|
|
||||||
|
/** All active versions across all RD products – used as reference on the create-product page. */
|
||||||
|
List<RdInterestRateEffectiveData> retrieveAllActiveInterestRates();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a NEW version of the rate chart for the given product.
|
||||||
|
* Deactivates the previous active version first (immutable history).
|
||||||
|
* Returns the newly created version's data.
|
||||||
|
*/
|
||||||
|
RdInterestRateEffectiveData saveInterestRates(Long productId, RdInterestRateSaveRequest request, Long createdById);
|
||||||
|
}
|
||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* 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.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.portfolio.savings.api.RdInterestRateSaveRequest;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.RdInterestRateEffectiveData;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.RdInterestRateSlabData;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdInterestRateEffective;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdInterestRateEffectiveRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdInterestRateSlabHistory;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.RdInterestRateSlabHistoryRepository;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RdInterestRateReadPlatformServiceImpl implements RdInterestRateReadPlatformService {
|
||||||
|
|
||||||
|
private final RdInterestRateEffectiveRepository effectiveRepository;
|
||||||
|
private final RdInterestRateSlabHistoryRepository slabRepository;
|
||||||
|
|
||||||
|
private static final String[] PERIOD_LABELS = { "", "Days", "Months", "Years" };
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RdInterestRateEffectiveData> retrieveInterestRates(Long productId) {
|
||||||
|
return effectiveRepository.findByProductIdOrderByEffectiveFromDateDesc(productId)
|
||||||
|
.stream().map(this::toData).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RdInterestRateEffectiveData> retrieveAllActiveInterestRates() {
|
||||||
|
return effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc()
|
||||||
|
.stream().map(this::toData).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public RdInterestRateEffectiveData saveInterestRates(Long productId,
|
||||||
|
RdInterestRateSaveRequest request, Long createdById) {
|
||||||
|
|
||||||
|
validateRequest(request);
|
||||||
|
final List<RdInterestRateSaveRequest.SlabRequest> normalizedSlabs = normalizeSlabs(request.getSlabs());
|
||||||
|
|
||||||
|
// 1. Deactivate all current active versions for this product, preserving end dates for audit/history.
|
||||||
|
final LocalDate today = LocalDate.now();
|
||||||
|
final List<RdInterestRateEffective> activeVersions = effectiveRepository
|
||||||
|
.findByProductIdAndIsActiveTrueOrderByEffectiveFromDateDesc(productId);
|
||||||
|
for (RdInterestRateEffective version : activeVersions) {
|
||||||
|
version.deactivate(today);
|
||||||
|
}
|
||||||
|
if (!activeVersions.isEmpty()) {
|
||||||
|
effectiveRepository.saveAll(activeVersions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create the new version header
|
||||||
|
String label = request.getVersionLabel() != null && !request.getVersionLabel().isBlank()
|
||||||
|
? request.getVersionLabel()
|
||||||
|
: "Rate Version " + LocalDate.now();
|
||||||
|
|
||||||
|
RdInterestRateEffective newVersion = RdInterestRateEffective.create(
|
||||||
|
productId, label, LocalDate.now(), request.getRemarks(), createdById);
|
||||||
|
newVersion = effectiveRepository.saveAndFlush(newVersion);
|
||||||
|
|
||||||
|
// 3. Insert slabs for the new version
|
||||||
|
final Long versionId = newVersion.getId();
|
||||||
|
AtomicInteger srCounter = new AtomicInteger(1);
|
||||||
|
for (RdInterestRateSaveRequest.SlabRequest s : normalizedSlabs) {
|
||||||
|
int sr = s.getSrNo() != null ? s.getSrNo() : srCounter.getAndIncrement();
|
||||||
|
RdInterestRateSlabHistory slab = RdInterestRateSlabHistory.create(
|
||||||
|
versionId, sr, s.getDescription(),
|
||||||
|
s.getPeriodTypeEnum(), s.getFromPeriod(), s.getToPeriod(),
|
||||||
|
s.getAnnualInterestRate());
|
||||||
|
slabRepository.save(slab);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toData(newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequest(final RdInterestRateSaveRequest request) {
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder validator = new DataValidatorBuilder(errors).resource("RD_INTEREST_RATE_VERSION");
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
validator.failWithCodeNoParameterAddedToErrorCode("request.cannot.be.null");
|
||||||
|
throwExceptionIfValidationWarningsExist(errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getSlabs() == null || request.getSlabs().isEmpty()) {
|
||||||
|
validator.parameter("slabs").failWithCode("cannot.be.blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getVersionLabel() != null && request.getVersionLabel().length() > 100) {
|
||||||
|
validator.parameter("versionLabel").value(request.getVersionLabel()).failWithCode("exceeds.max.length");
|
||||||
|
}
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RdInterestRateSaveRequest.SlabRequest> normalizeSlabs(final List<RdInterestRateSaveRequest.SlabRequest> slabs) {
|
||||||
|
final List<RdInterestRateSaveRequest.SlabRequest> sorted = new ArrayList<>(slabs);
|
||||||
|
sorted.sort(Comparator.comparing((RdInterestRateSaveRequest.SlabRequest slab) -> slab.getSrNo() == null ? Integer.MAX_VALUE : slab.getSrNo())
|
||||||
|
.thenComparing(slab -> slab.getFromPeriod() == null ? Integer.MAX_VALUE : slab.getFromPeriod()));
|
||||||
|
|
||||||
|
final List<RdInterestRateSaveRequest.SlabRequest> normalized = new ArrayList<>(sorted.size());
|
||||||
|
Integer expectedFrom = 0;
|
||||||
|
for (int i = 0; i < sorted.size(); i++) {
|
||||||
|
final RdInterestRateSaveRequest.SlabRequest source = sorted.get(i);
|
||||||
|
final RdInterestRateSaveRequest.SlabRequest target = new RdInterestRateSaveRequest.SlabRequest();
|
||||||
|
|
||||||
|
final Integer fromPeriod = source.getFromPeriod();
|
||||||
|
if (fromPeriod == null || !fromPeriod.equals(expectedFrom)) {
|
||||||
|
throwValidationError("fromPeriod", "must.start.from.previous.range.end");
|
||||||
|
}
|
||||||
|
target.setFromPeriod(fromPeriod);
|
||||||
|
|
||||||
|
final Integer toPeriod = source.getToPeriod();
|
||||||
|
if (i < sorted.size() - 1) {
|
||||||
|
if (toPeriod == null) {
|
||||||
|
throwValidationError("toPeriod", "cannot.be.blank.for.intermediate.slab");
|
||||||
|
}
|
||||||
|
if (toPeriod < fromPeriod) {
|
||||||
|
throwValidationError("toPeriod", "cannot.be.less.than.fromPeriod");
|
||||||
|
}
|
||||||
|
expectedFrom = toPeriod + 1;
|
||||||
|
target.setToPeriod(toPeriod);
|
||||||
|
} else {
|
||||||
|
target.setToPeriod(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.setSrNo(source.getSrNo() == null ? i + 1 : source.getSrNo());
|
||||||
|
target.setDescription(source.getDescription());
|
||||||
|
target.setPeriodTypeEnum(source.getPeriodTypeEnum() == null ? 1 : source.getPeriodTypeEnum());
|
||||||
|
target.setAnnualInterestRate(source.getAnnualInterestRate());
|
||||||
|
|
||||||
|
if (target.getAnnualInterestRate() == null) {
|
||||||
|
throwValidationError("annualInterestRate", "cannot.be.blank");
|
||||||
|
}
|
||||||
|
if (target.getAnnualInterestRate().signum() < 0) {
|
||||||
|
throwValidationError("annualInterestRate", "must.be.non.negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized.add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalized.isEmpty() && normalized.get(0).getFromPeriod() != 0) {
|
||||||
|
throwValidationError("fromPeriod", "must.start.from.zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwValidationError(final String parameterName, final String code) {
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
new DataValidatorBuilder(errors).resource("RD_INTEREST_RATE_VERSION").parameter(parameterName).failWithCode(code);
|
||||||
|
throwExceptionIfValidationWarningsExist(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||||
|
if (!dataValidationErrors.isEmpty()) {
|
||||||
|
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private RdInterestRateEffectiveData toData(RdInterestRateEffective e) {
|
||||||
|
List<RdInterestRateSlabData> slabs = slabRepository
|
||||||
|
.findByEffectiveIdOrderBySrNo(e.getId())
|
||||||
|
.stream()
|
||||||
|
.map(s -> RdInterestRateSlabData.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())
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return RdInterestRateEffectiveData.builder()
|
||||||
|
.id(e.getId())
|
||||||
|
.productId(e.getProductId())
|
||||||
|
.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 || code < 1 || code >= PERIOD_LABELS.length) return "Days";
|
||||||
|
return PERIOD_LABELS[code];
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -144,6 +144,10 @@ import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformS
|
|||||||
import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformServiceImpl;
|
import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformServiceImpl;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformService;
|
import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformService;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformServiceJpaRepositoryImpl;
|
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.service.RdBaseInterestRateReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformServiceImpl;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPoster;
|
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPoster;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask;
|
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask;
|
||||||
import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService;
|
import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService;
|
||||||
@@ -159,6 +163,14 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class SavingsConfiguration {
|
public class SavingsConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(RdBaseInterestRateReadPlatformService.class)
|
||||||
|
public RdBaseInterestRateReadPlatformService rdBaseInterestRateReadPlatformService(
|
||||||
|
RdBaseInterestRateEffectiveRepository effectiveRepository,
|
||||||
|
RdBaseInterestRateSlabHistoryRepository slabRepository) {
|
||||||
|
return new RdBaseInterestRateReadPlatformServiceImpl(effectiveRepository, slabRepository);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
|
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
|
||||||
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
|
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
|
||||||
|
|||||||
@@ -271,4 +271,9 @@
|
|||||||
<include file="parts/0251_add_savings_product_enhanced_fields.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0251_add_savings_product_enhanced_fields.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0255_create_current_product_tables.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0255_create_current_product_tables.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0256_add_recurring_deposit_product_codes.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0256_add_recurring_deposit_product_codes.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0257_create_rd_interest_rate_tables.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0267_seed_rd_interest_rate_data.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0270_add_rd_product_extra_fields.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0271_create_rd_base_interest_rate_tables.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0272_seed_rd_base_interest_rate_data.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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
|
||||||
|
-->
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
m_rd_interest_rate_effective — one row per rate-version per product.
|
||||||
|
m_rd_interest_rate_slab_history — slabs for every version (immutable once written).
|
||||||
|
|
||||||
|
Period Type Enum: 1 = Days, 2 = Months, 3 = Years
|
||||||
|
-->
|
||||||
|
|
||||||
|
<changeSet id="0257a_create_rd_interest_rate_effective" author="fineract">
|
||||||
|
<validCheckSum>ANY</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_rd_interest_rate_effective"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_rd_interest_rate_effective">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="product_id" type="BIGINT">
|
||||||
|
<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>
|
||||||
|
<addForeignKeyConstraint
|
||||||
|
baseTableName="m_rd_interest_rate_effective"
|
||||||
|
baseColumnNames="product_id"
|
||||||
|
referencedTableName="m_savings_product"
|
||||||
|
referencedColumnNames="id"
|
||||||
|
constraintName="fk_rd_rate_effective_product"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="0257b_create_rd_interest_rate_slab_history" author="fineract">
|
||||||
|
<validCheckSum>ANY</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_rd_interest_rate_slab_history"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_rd_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)"/>
|
||||||
|
<column name="period_type_enum" type="INT" defaultValueNumeric="1">
|
||||||
|
<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>
|
||||||
|
<column name="created_date" type="DATE"/>
|
||||||
|
</createTable>
|
||||||
|
<addForeignKeyConstraint
|
||||||
|
baseTableName="m_rd_interest_rate_slab_history"
|
||||||
|
baseColumnNames="effective_id"
|
||||||
|
referencedTableName="m_rd_interest_rate_effective"
|
||||||
|
referencedColumnNames="id"
|
||||||
|
constraintName="fk_rd_rate_slab_effective"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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
|
||||||
|
-->
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Seed RD Interest Rate Data (runs only if both tables are empty)
|
||||||
|
Sr Particular Rate
|
||||||
|
1 0 Days to 29 Days 3.00%
|
||||||
|
2 30 Days to 45 Days 3.00%
|
||||||
|
3 46 Days to 90 Days 3.00%
|
||||||
|
4 91 Days to 180 Days 4.50%
|
||||||
|
5 181 Days to below 1 Year 5.50%
|
||||||
|
6 1 Year to Below 2 Year 7.00%
|
||||||
|
7 444 Days 7.00%
|
||||||
|
8 400 Days 7.00%
|
||||||
|
9 500 Days 7.00%
|
||||||
|
10 2 Year to Below 3 Year 7.00%
|
||||||
|
11 3 Year to Below 5 Year 7.25%
|
||||||
|
12 5 Year & Above 6.50%
|
||||||
|
-->
|
||||||
|
|
||||||
|
<changeSet id="0267_insert_rd_rate_version" author="fineract">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_rd_interest_rate_effective</sqlCheck>
|
||||||
|
</preConditions>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_rd_interest_rate_effective
|
||||||
|
(product_id, version_label, effective_from_date, effective_to_date, is_active, remarks, created_by_id, created_date)
|
||||||
|
SELECT id, 'Initial Rate Version', '2025-01-01', NULL, TRUE,
|
||||||
|
'Standard interest rate card published for RD products', 1, CURRENT_DATE
|
||||||
|
FROM m_savings_product
|
||||||
|
WHERE deposit_type_enum = 300
|
||||||
|
ORDER BY id LIMIT 1;
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="0268_insert_rd_rate_slabs" author="fineract">
|
||||||
|
<validCheckSum>9:e35a0696046940f370f7cfb55c20740b</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_rd_interest_rate_slab_history</sqlCheck>
|
||||||
|
</preConditions>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 1, '0 Days to 29 Days', 1, 0, 29, 3.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 2, '30 Days to 45 Days', 1, 30, 45, 3.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 3, '46 Days to 90 Days', 1, 46, 90, 3.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 4, '91 Days to 180 Days', 1, 91, 180, 4.500000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 5, '181 Days to below 1 Year', 1, 181, 364, 5.500000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 6, '1 Year to Below 2 Year', 1, 365, 729, 7.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 7, '444 Days', 1, 444, 444, 7.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 8, '400 Days', 1, 400, 400, 7.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 9, '500 Days', 1, 500, 500, 7.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 10, '2 Year to Below 3 Year', 1, 730, 1094, 7.000000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 11, '3 Year to Below 5 Year', 1, 1095, 1824, 7.250000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_interest_rate_slab_history (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT e.id, 12, '5 Year & Above', 1, 1825, NULL, 6.500000, CURRENT_DATE FROM m_rd_interest_rate_effective e WHERE e.version_label='Initial Rate Version' AND e.product_id=(SELECT id FROM m_savings_product WHERE deposit_type_enum=300 ORDER BY id LIMIT 1);
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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
|
||||||
|
-->
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!-- balance_frequency — may already exist from a prior manual migration -->
|
||||||
|
<changeSet id="0270a_add_balance_frequency" author="fineract">
|
||||||
|
<validCheckSum>ANY</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><columnExists tableName="m_savings_product" columnName="balance_frequency"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<addColumn tableName="m_savings_product">
|
||||||
|
<column name="balance_frequency" type="varchar(50)">
|
||||||
|
<constraints nullable="true"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<!-- account_closed_zero_balance_in_day_closing — may already exist -->
|
||||||
|
<changeSet id="0270b_add_account_closed_zero_balance" author="fineract">
|
||||||
|
<validCheckSum>ANY</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><columnExists tableName="m_savings_product" columnName="account_closed_zero_balance_in_day_closing"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<addColumn tableName="m_savings_product">
|
||||||
|
<column name="account_closed_zero_balance_in_day_closing" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<!-- 14 new RD-specific boolean flags -->
|
||||||
|
<changeSet id="0270c_add_rd_product_flags" author="fineract">
|
||||||
|
<validCheckSum>ANY</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><columnExists tableName="m_savings_product" columnName="balance_swap_option"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<addColumn tableName="m_savings_product">
|
||||||
|
<column name="balance_swap_option" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="msme" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="auto_debit_facility" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="loan_facility" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="premature_withdrawal_option" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="tenure_flexibility" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="lien_marked" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="maturity_instructions" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="credit_maturity_proceeds" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="penalty_grace_period_missed_installments" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="panel_request_applicable" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="no_penalty_nominee_heir_closure" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="deceased_settlement" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="allow_debit_txn_after_due_date" type="boolean" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
<?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="0271a_create_rd_base_interest_rate_effective" author="fineract">
|
||||||
|
<validCheckSum>ANY</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_rd_base_interest_rate_effective"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_rd_base_interest_rate_effective">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true">
|
||||||
|
<constraints primaryKey="true" 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>
|
||||||
|
|
||||||
|
<changeSet id="0271b_create_rd_base_interest_rate_slab_history" author="fineract">
|
||||||
|
<validCheckSum>ANY</validCheckSum>
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_rd_base_interest_rate_slab_history"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_rd_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)"/>
|
||||||
|
<column name="period_type_enum" type="INT" defaultValueNumeric="1">
|
||||||
|
<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>
|
||||||
|
<column name="created_date" type="DATE"/>
|
||||||
|
</createTable>
|
||||||
|
<addForeignKeyConstraint
|
||||||
|
baseTableName="m_rd_base_interest_rate_slab_history"
|
||||||
|
baseColumnNames="effective_id"
|
||||||
|
referencedTableName="m_rd_base_interest_rate_effective"
|
||||||
|
referencedColumnNames="id"
|
||||||
|
constraintName="fk_rd_base_rate_slab_effective"/>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
<?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="0272_insert_rd_base_rate_version" author="fineract">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_rd_base_interest_rate_effective</sqlCheck>
|
||||||
|
</preConditions>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_rd_base_interest_rate_effective
|
||||||
|
(version_label, effective_from_date, effective_to_date, is_active, remarks, created_by_id, created_date)
|
||||||
|
VALUES
|
||||||
|
('CBS Base Rate Version', '2025-01-01', NULL, TRUE, 'Central RD base chart', 1, CURRENT_DATE);
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 1, '15 Days to 29 Days', 1, 15, 29, 3.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 2, '30 Days to 45 Days', 1, 30, 45, 3.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 3, '46 Days to 90 Days', 1, 46, 90, 3.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 4, '91 Days to 180 Days', 1, 91, 180, 4.500000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 5, '181 Days to below 1 Year', 1, 181, 364, 5.500000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 6, '1 Year to Below 2 Year', 1, 365, 729, 7.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 7, '444 Days', 1, 444, 444, 7.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 8, '400 Days', 1, 400, 400, 7.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 9, '500 Days', 1, 500, 500, 7.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 10, '2 Year to Below 3 Year', 1, 730, 1094, 7.000000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 11, '3 Year to Below 5 Year', 1, 1095, 1824, 7.250000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
|
||||||
|
INSERT INTO m_rd_base_interest_rate_slab_history
|
||||||
|
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, created_date)
|
||||||
|
SELECT id, 12, '5 Year & Above', 1, 1825, NULL, 6.500000, CURRENT_DATE
|
||||||
|
FROM m_rd_base_interest_rate_effective WHERE version_label = 'CBS Base Rate Version';
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+31
@@ -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 final class RdInterestRateEffectiveData {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long productId;
|
||||||
|
private final String versionLabel;
|
||||||
|
private final LocalDate effectiveFromDate;
|
||||||
|
private final LocalDate effectiveToDate;
|
||||||
|
private final Boolean isActive;
|
||||||
|
private final String remarks;
|
||||||
|
private final List<RdInterestRateSlabData> slabs;
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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 final class RdInterestRateSlabData {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Integer srNo;
|
||||||
|
private final String description;
|
||||||
|
private final Integer periodTypeEnum;
|
||||||
|
private final String periodTypeLabel;
|
||||||
|
private final Integer fromPeriod;
|
||||||
|
private final Integer toPeriod;
|
||||||
|
private final BigDecimal annualInterestRate;
|
||||||
|
}
|
||||||
+116
-6
@@ -65,10 +65,36 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
private String accountType;
|
private String accountType;
|
||||||
private String customerSegment;
|
private String customerSegment;
|
||||||
private String productCode;
|
private String productCode;
|
||||||
|
private java.time.LocalDate effectiveDate;
|
||||||
|
private java.time.LocalDate effectiveTo;
|
||||||
|
private java.time.LocalDate endDate;
|
||||||
|
private Boolean introducerRequired;
|
||||||
|
private Boolean shareLinkingEnabled;
|
||||||
|
private Boolean nominationFacility;
|
||||||
|
private Boolean dormancyAlert;
|
||||||
private Integer interestCalculateSelector;
|
private Integer interestCalculateSelector;
|
||||||
private String averagePeriod;
|
private String averagePeriod;
|
||||||
private Boolean autoPost;
|
private Boolean autoPost;
|
||||||
private Integer frequencyFixDay;
|
private Integer frequencyFixDay;
|
||||||
|
private java.time.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;
|
||||||
|
private Boolean withdrawalFeeForTransfers;
|
||||||
|
|
||||||
public void setInterestCalculationTypeOption(Collection<EnumOptionData> v) { this.interestCalculationTypeOption = v; }
|
public void setInterestCalculationTypeOption(Collection<EnumOptionData> v) { this.interestCalculationTypeOption = v; }
|
||||||
public void setPeriodFrequencyOptions(Collection<EnumOptionData> v) { this.periodFrequencyOptions = v; }
|
public void setPeriodFrequencyOptions(Collection<EnumOptionData> v) { this.periodFrequencyOptions = v; }
|
||||||
@@ -78,10 +104,84 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
public void setAccountType(String v) { this.accountType = v; }
|
public void setAccountType(String v) { this.accountType = v; }
|
||||||
public void setCustomerSegment(String v) { this.customerSegment = v; }
|
public void setCustomerSegment(String v) { this.customerSegment = v; }
|
||||||
public void setProductCode(String v) { this.productCode = v; }
|
public void setProductCode(String v) { this.productCode = v; }
|
||||||
|
public void setEffectiveDate(java.time.LocalDate v) { this.effectiveDate = v; }
|
||||||
|
public void setEffectiveTo(java.time.LocalDate v) { this.effectiveTo = v; }
|
||||||
|
public void setEndDate(java.time.LocalDate v) { this.endDate = 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 setInterestCalculateSelector(Integer v) { this.interestCalculateSelector = v; }
|
public void setInterestCalculateSelector(Integer v) { this.interestCalculateSelector = v; }
|
||||||
public void setAveragePeriod(String v) { this.averagePeriod = v; }
|
public void setAveragePeriod(String v) { this.averagePeriod = v; }
|
||||||
public void setAutoPost(Boolean v) { this.autoPost = v; }
|
public void setAutoPost(Boolean v) { this.autoPost = v; }
|
||||||
public void setFrequencyFixDay(Integer v) { this.frequencyFixDay = v; }
|
public void setFrequencyFixDay(Integer v) { this.frequencyFixDay = v; }
|
||||||
|
public void setLastInterestDate(java.time.LocalDate v) { this.lastInterestDate = v; }
|
||||||
|
public void setEnableLockinPeriod(Boolean v) { this.enableLockinPeriod = v; }
|
||||||
|
public void setBalanceFrequency(String v) { this.balanceFrequency = v; }
|
||||||
|
public void setAccountClosedZeroBalanceInDayClosing(Boolean v) { this.accountClosedZeroBalanceInDayClosing = v; }
|
||||||
|
public void setBalanceSwapOption(Boolean v) { this.balanceSwapOption = v; }
|
||||||
|
public void setMsme(Boolean v) { this.msme = 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 setWithdrawalFeeForTransfers(Boolean v) { this.withdrawalFeeForTransfers = v; }
|
||||||
|
|
||||||
|
/** Copy all CBS custom fields from source into this instance. */
|
||||||
|
public void copyCbsFieldsFrom(final RecurringDepositProductData src) {
|
||||||
|
if (src == null) return;
|
||||||
|
this.accountType = src.accountType;
|
||||||
|
this.customerSegment = src.customerSegment;
|
||||||
|
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.interestCalculateSelector = src.interestCalculateSelector;
|
||||||
|
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;
|
||||||
|
this.withdrawalFeeForTransfers = src.withdrawalFeeForTransfers;
|
||||||
|
// options
|
||||||
|
this.interestCalculationTypeOption = src.interestCalculationTypeOption;
|
||||||
|
this.periodFrequencyOptions = src.periodFrequencyOptions;
|
||||||
|
this.yesNoOptions = src.yesNoOptions;
|
||||||
|
this.accountTypeOptions = src.accountTypeOptions;
|
||||||
|
this.customerSegmentOptions = src.customerSegmentOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductCode() {
|
||||||
|
return productCode;
|
||||||
|
}
|
||||||
|
|
||||||
public static RecurringDepositProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
|
public static RecurringDepositProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
|
||||||
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
|
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
|
||||||
@@ -146,7 +246,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
|
|
||||||
public static RecurringDepositProductData withCharges(final RecurringDepositProductData existingProduct,
|
public static RecurringDepositProductData withCharges(final RecurringDepositProductData existingProduct,
|
||||||
final Collection<ChargeData> charges) {
|
final Collection<ChargeData> charges) {
|
||||||
return new RecurringDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
|
final RecurringDepositProductData result = new RecurringDepositProductData(existingProduct.id, existingProduct.name,
|
||||||
|
existingProduct.shortName,
|
||||||
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
|
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
|
||||||
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
|
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
|
||||||
existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
|
existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
|
||||||
@@ -166,6 +267,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal, existingProduct.adjustAdvanceTowardsFuturePayments,
|
existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal, existingProduct.adjustAdvanceTowardsFuturePayments,
|
||||||
existingProduct.periodFrequencyTypeOptions, existingProduct.minDepositAmount, existingProduct.depositAmount,
|
existingProduct.periodFrequencyTypeOptions, existingProduct.minDepositAmount, existingProduct.depositAmount,
|
||||||
existingProduct.maxDepositAmount, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions);
|
existingProduct.maxDepositAmount, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions);
|
||||||
|
result.copyCbsFieldsFrom(existingProduct);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,7 +290,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
|
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
|
||||||
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<TaxGroupData> taxGroupOptions) {
|
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<TaxGroupData> taxGroupOptions) {
|
||||||
|
|
||||||
return new RecurringDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
|
final RecurringDepositProductData result = new RecurringDepositProductData(existingProduct.id, existingProduct.name,
|
||||||
|
existingProduct.shortName,
|
||||||
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
|
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
|
||||||
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
|
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
|
||||||
existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
|
existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
|
||||||
@@ -206,7 +310,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
existingProduct.adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, existingProduct.minDepositAmount,
|
existingProduct.adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, existingProduct.minDepositAmount,
|
||||||
existingProduct.depositAmount, existingProduct.maxDepositAmount, existingProduct.withHoldTax, existingProduct.taxGroup,
|
existingProduct.depositAmount, existingProduct.maxDepositAmount, existingProduct.withHoldTax, existingProduct.taxGroup,
|
||||||
taxGroupOptions);
|
taxGroupOptions);
|
||||||
|
result.copyCbsFieldsFrom(existingProduct);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecurringDepositProductData withAccountingDetails(final RecurringDepositProductData existingProduct,
|
public static RecurringDepositProductData withAccountingDetails(final RecurringDepositProductData existingProduct,
|
||||||
@@ -227,7 +332,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
final Collection<ChargeData> chargeOptions = null;
|
final Collection<ChargeData> chargeOptions = null;
|
||||||
final Collection<ChargeData> penaltyOptions = null;
|
final Collection<ChargeData> penaltyOptions = null;
|
||||||
|
|
||||||
return new RecurringDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName,
|
final RecurringDepositProductData result = new RecurringDepositProductData(existingProduct.id, existingProduct.name,
|
||||||
|
existingProduct.shortName,
|
||||||
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
|
existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate,
|
||||||
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
|
existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType,
|
||||||
existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
|
existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType,
|
||||||
@@ -245,6 +351,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
existingProduct.adjustAdvanceTowardsFuturePayments, existingProduct.periodFrequencyTypeOptions,
|
existingProduct.adjustAdvanceTowardsFuturePayments, existingProduct.periodFrequencyTypeOptions,
|
||||||
existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
|
existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount,
|
||||||
existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions);
|
existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions);
|
||||||
|
result.copyCbsFieldsFrom(existingProduct);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecurringDepositProductData instance(final DepositProductData depositProductData, final boolean preClosurePenalApplicable,
|
public static RecurringDepositProductData instance(final DepositProductData depositProductData, final boolean preClosurePenalApplicable,
|
||||||
@@ -367,7 +475,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
|
|
||||||
public static RecurringDepositProductData withInterestChart(final RecurringDepositProductData product,
|
public static RecurringDepositProductData withInterestChart(final RecurringDepositProductData product,
|
||||||
final Collection<InterestRateChartData> interestRateCharts) {
|
final Collection<InterestRateChartData> interestRateCharts) {
|
||||||
return new RecurringDepositProductData(product.id, product.name, product.shortName, product.description, product.currency,
|
final RecurringDepositProductData result = new RecurringDepositProductData(product.id, product.name, product.shortName,
|
||||||
|
product.description, product.currency,
|
||||||
product.nominalAnnualInterestRate, product.interestCompoundingPeriodType, product.interestPostingPeriodType,
|
product.nominalAnnualInterestRate, product.interestCompoundingPeriodType, product.interestPostingPeriodType,
|
||||||
product.interestCalculationType, product.interestCalculationDaysInYearType, product.lockinPeriodFrequency,
|
product.interestCalculationType, product.interestCalculationDaysInYearType, product.lockinPeriodFrequency,
|
||||||
product.lockinPeriodFrequencyType, product.minBalanceForInterestCalculation, product.accountingRule,
|
product.lockinPeriodFrequencyType, product.minBalanceForInterestCalculation, product.accountingRule,
|
||||||
@@ -383,7 +492,8 @@ public final class RecurringDepositProductData extends DepositProductData {
|
|||||||
product.inMultiplesOfDepositTermType, product.isMandatoryDeposit, product.allowWithdrawal,
|
product.inMultiplesOfDepositTermType, product.isMandatoryDeposit, product.allowWithdrawal,
|
||||||
product.adjustAdvanceTowardsFuturePayments, product.periodFrequencyTypeOptions, product.minDepositAmount,
|
product.adjustAdvanceTowardsFuturePayments, product.periodFrequencyTypeOptions, product.minDepositAmount,
|
||||||
product.depositAmount, product.maxDepositAmount, product.withHoldTax, product.taxGroup, product.taxGroupOptions);
|
product.depositAmount, product.maxDepositAmount, product.withHoldTax, product.taxGroup, product.taxGroupOptions);
|
||||||
|
result.copyCbsFieldsFrom(product);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RecurringDepositProductData(final Long id, final String name, final String shortName, final String description,
|
private RecurringDepositProductData(final Long id, final String name, final String shortName, final String description,
|
||||||
|
|||||||
+69
@@ -58,9 +58,13 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHold
|
|||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.fineract.accounting.common.AccountingRuleType;
|
import org.apache.fineract.accounting.common.AccountingRuleType;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
@@ -277,6 +281,47 @@ public class DepositProductAssembler {
|
|||||||
productTermAndPreClosure.updateProductReference(recurringDepositProduct);
|
productTermAndPreClosure.updateProductReference(recurringDepositProduct);
|
||||||
productRecurringDetail.updateProductReference(recurringDepositProduct);
|
productRecurringDetail.updateProductReference(recurringDepositProduct);
|
||||||
|
|
||||||
|
// ── CBS Custom Fields (from 0251 migration) ─────────────────────────
|
||||||
|
recurringDepositProduct.setAccountType(command.stringValueOfParameterNamed("accountType"));
|
||||||
|
recurringDepositProduct.setCustomerSegment(command.stringValueOfParameterNamed("customerSegment"));
|
||||||
|
recurringDepositProduct.setProductCode(command.stringValueOfParameterNamed("productCode"));
|
||||||
|
recurringDepositProduct.setEffectiveDate(parseCbsDate(command.stringValueOfParameterNamed("effectiveDate")));
|
||||||
|
recurringDepositProduct.setEffectiveTo(parseCbsDate(command.stringValueOfParameterNamed("effectiveTo")));
|
||||||
|
recurringDepositProduct.setEndDate(parseCbsDate(command.stringValueOfParameterNamed("endDate")));
|
||||||
|
recurringDepositProduct.setIntroducerRequired(command.booleanPrimitiveValueOfParameterNamed("introducerRequired"));
|
||||||
|
recurringDepositProduct.setShareLinkingEnabled(command.booleanPrimitiveValueOfParameterNamed("shareLinkingEnabled"));
|
||||||
|
recurringDepositProduct.setNominationFacility(command.booleanPrimitiveValueOfParameterNamed("nominationFacility"));
|
||||||
|
recurringDepositProduct.setDormancyAlert(command.booleanPrimitiveValueOfParameterNamed("dormancyAlert"));
|
||||||
|
recurringDepositProduct.setInterestCalculateSelector(command.integerValueOfParameterNamed("interestCalculateSelector"));
|
||||||
|
recurringDepositProduct.setAveragePeriod(command.stringValueOfParameterNamed("averagePeriod"));
|
||||||
|
recurringDepositProduct.setAutoPost(command.booleanPrimitiveValueOfParameterNamed("autoPost"));
|
||||||
|
recurringDepositProduct.setFrequencyFixDay(command.integerValueOfParameterNamed("frequencyFixDay"));
|
||||||
|
recurringDepositProduct.setLastInterestDate(parseCbsDate(command.stringValueOfParameterNamed("lastInterestDate")));
|
||||||
|
recurringDepositProduct.setEnableLockinPeriod(command.booleanPrimitiveValueOfParameterNamed("enableLockinPeriod"));
|
||||||
|
recurringDepositProduct.setBalanceFrequency(command.stringValueOfParameterNamed("balanceFrequency"));
|
||||||
|
if (command.parameterExists("accountClosedZeroBalanceInDayClosing")) {
|
||||||
|
recurringDepositProduct.setAccountClosedZeroBalanceInDayClosing(
|
||||||
|
command.booleanPrimitiveValueOfParameterNamed("accountClosedZeroBalanceInDayClosing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── RD-specific flags (from 0270 migration) ──────────────────────────
|
||||||
|
recurringDepositProduct.setBalanceSwapOption(command.booleanPrimitiveValueOfParameterNamed("balanceSwapOption"));
|
||||||
|
recurringDepositProduct.setMsme(command.booleanPrimitiveValueOfParameterNamed("msme"));
|
||||||
|
recurringDepositProduct.setAutoDebitFacility(command.booleanPrimitiveValueOfParameterNamed("autoDebitFacility"));
|
||||||
|
recurringDepositProduct.setLoanFacility(command.booleanPrimitiveValueOfParameterNamed("loanFacility"));
|
||||||
|
recurringDepositProduct.setPrematureWithdrawalOption(command.booleanPrimitiveValueOfParameterNamed("prematureWithdrawalOption"));
|
||||||
|
recurringDepositProduct.setTenureFlexibility(command.booleanPrimitiveValueOfParameterNamed("tenureFlexibility"));
|
||||||
|
recurringDepositProduct.setLienMarked(command.booleanPrimitiveValueOfParameterNamed("lienMarked"));
|
||||||
|
recurringDepositProduct.setMaturityInstructions(command.booleanPrimitiveValueOfParameterNamed("maturityInstructions"));
|
||||||
|
recurringDepositProduct.setCreditMaturityProceeds(command.booleanPrimitiveValueOfParameterNamed("creditMaturityProceeds"));
|
||||||
|
recurringDepositProduct.setPenaltyGracePeriodMissedInstallments(
|
||||||
|
command.booleanPrimitiveValueOfParameterNamed("penaltyGracePeriodMissedInstallments"));
|
||||||
|
recurringDepositProduct.setPanelRequestApplicable(command.booleanPrimitiveValueOfParameterNamed("panelRequestApplicable"));
|
||||||
|
recurringDepositProduct.setNoPenaltyNomineeHeirClosure(
|
||||||
|
command.booleanPrimitiveValueOfParameterNamed("noPenaltyNomineeHeirClosure"));
|
||||||
|
recurringDepositProduct.setDeceasedSettlement(command.booleanPrimitiveValueOfParameterNamed("deceasedSettlement"));
|
||||||
|
recurringDepositProduct.setAllowDebitTxnAfterDueDate(command.booleanPrimitiveValueOfParameterNamed("allowDebitTxnAfterDueDate"));
|
||||||
|
|
||||||
recurringDepositProduct.validateDomainRules();
|
recurringDepositProduct.validateDomainRules();
|
||||||
|
|
||||||
return recurringDepositProduct;
|
return recurringDepositProduct;
|
||||||
@@ -500,6 +545,30 @@ public class DepositProductAssembler {
|
|||||||
return new DepositProductAmountDetails(minDepositAmount, depositAmount, maxDepositAmount);
|
return new DepositProductAmountDetails(minDepositAmount, depositAmount, maxDepositAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a CBS date string using common formats (e.g. "17 April 2026", "2026-04-17").
|
||||||
|
* Returns null if the input is null or blank.
|
||||||
|
*/
|
||||||
|
private LocalDate parseCbsDate(final String dateStr) {
|
||||||
|
if (dateStr == null || dateStr.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DateTimeFormatter[] formatters = {
|
||||||
|
DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.ENGLISH),
|
||||||
|
DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.ENGLISH),
|
||||||
|
DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH),
|
||||||
|
DateTimeFormatter.ofPattern("dd/MM/yyyy", Locale.ENGLISH)
|
||||||
|
};
|
||||||
|
for (DateTimeFormatter fmt : formatters) {
|
||||||
|
try {
|
||||||
|
return LocalDate.parse(dateStr, fmt);
|
||||||
|
} catch (DateTimeParseException ignored) {
|
||||||
|
// try next format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public TaxGroup assembleTaxGroup(final JsonCommand command) {
|
public TaxGroup assembleTaxGroup(final JsonCommand command) {
|
||||||
final Long taxGroupId = command.longValueOfParameterNamed(taxGroupIdParamName);
|
final Long taxGroupId = command.longValueOfParameterNamed(taxGroupIdParamName);
|
||||||
TaxGroup taxGroup = null;
|
TaxGroup taxGroup = null;
|
||||||
|
|||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_rd_interest_rate_effective")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RdInterestRateEffective {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "product_id", nullable = false)
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@Column(name = "version_label", nullable = false, length = 100)
|
||||||
|
private String versionLabel;
|
||||||
|
|
||||||
|
@Column(name = "effective_from_date", nullable = false)
|
||||||
|
private LocalDate effectiveFromDate;
|
||||||
|
|
||||||
|
@Column(name = "effective_to_date")
|
||||||
|
private LocalDate effectiveToDate;
|
||||||
|
|
||||||
|
@Column(name = "is_active", nullable = false)
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
@Column(name = "remarks", length = 500)
|
||||||
|
private String remarks;
|
||||||
|
|
||||||
|
@Column(name = "created_by_id")
|
||||||
|
private Long createdById;
|
||||||
|
|
||||||
|
@Column(name = "created_date")
|
||||||
|
private LocalDate createdDate;
|
||||||
|
|
||||||
|
public static RdInterestRateEffective create(Long productId, String versionLabel,
|
||||||
|
LocalDate effectiveFromDate, String remarks, Long createdById) {
|
||||||
|
RdInterestRateEffective e = new RdInterestRateEffective();
|
||||||
|
e.productId = productId;
|
||||||
|
e.versionLabel = versionLabel;
|
||||||
|
e.effectiveFromDate = effectiveFromDate;
|
||||||
|
e.isActive = true;
|
||||||
|
e.remarks = remarks;
|
||||||
|
e.createdById = createdById;
|
||||||
|
e.createdDate = LocalDate.now();
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -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.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface RdInterestRateEffectiveRepository extends JpaRepository<RdInterestRateEffective, Long> {
|
||||||
|
|
||||||
|
List<RdInterestRateEffective> findByProductIdOrderByEffectiveFromDateDesc(Long productId);
|
||||||
|
|
||||||
|
List<RdInterestRateEffective> findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE RdInterestRateEffective e SET e.isActive = false WHERE e.productId = :productId AND e.isActive = true")
|
||||||
|
void deactivateAllForProduct(@Param("productId") Long productId);
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* 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_rd_interest_rate_slab_history")
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RdInterestRateSlabHistory {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "effective_id", nullable = false)
|
||||||
|
private Long effectiveId;
|
||||||
|
|
||||||
|
@Column(name = "sr_no", nullable = false)
|
||||||
|
private Integer srNo;
|
||||||
|
|
||||||
|
@Column(name = "description", length = 200)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column(name = "period_type_enum", nullable = false)
|
||||||
|
private Integer periodTypeEnum;
|
||||||
|
|
||||||
|
@Column(name = "from_period", nullable = false)
|
||||||
|
private Integer fromPeriod;
|
||||||
|
|
||||||
|
@Column(name = "to_period")
|
||||||
|
private Integer toPeriod;
|
||||||
|
|
||||||
|
@Column(name = "annual_interest_rate", nullable = false, precision = 19, scale = 6)
|
||||||
|
private BigDecimal annualInterestRate;
|
||||||
|
|
||||||
|
@Column(name = "created_date")
|
||||||
|
private LocalDate createdDate;
|
||||||
|
|
||||||
|
public static RdInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description,
|
||||||
|
Integer periodTypeEnum, Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate) {
|
||||||
|
RdInterestRateSlabHistory s = new RdInterestRateSlabHistory();
|
||||||
|
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.createdDate = LocalDate.now();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface RdInterestRateSlabHistoryRepository extends JpaRepository<RdInterestRateSlabHistory, Long> {
|
||||||
|
|
||||||
|
List<RdInterestRateSlabHistory> findByEffectiveIdOrderBySrNo(Long effectiveId);
|
||||||
|
}
|
||||||
+10
-6
@@ -150,12 +150,16 @@ public class RecurringDepositProduct extends FixedDepositProduct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateDomainRules(final DataValidatorBuilder baseDataValidator) {
|
private void validateDomainRules(final DataValidatorBuilder baseDataValidator) {
|
||||||
final DepositTermDetail termDetails = this.depositProductTermAndPreClosure().depositTermDetail();
|
final DepositTermDetail termDetails = this.depositProductTermAndPreClosure() != null
|
||||||
final boolean isMinTermGreaterThanMax = termDetails.isMinDepositTermGreaterThanMaxDepositTerm();
|
? this.depositProductTermAndPreClosure().depositTermDetail()
|
||||||
if (isMinTermGreaterThanMax) {
|
: null;
|
||||||
final Integer maxTerm = termDetails.maxDepositTerm();
|
if (termDetails != null) {
|
||||||
baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm)
|
final boolean isMinTermGreaterThanMax = termDetails.isMinDepositTermGreaterThanMaxDepositTerm();
|
||||||
.failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term");
|
if (isMinTermGreaterThanMax) {
|
||||||
|
final Integer maxTerm = termDetails.maxDepositTerm();
|
||||||
|
baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm)
|
||||||
|
.failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.charts != null) {
|
if (this.charts != null) {
|
||||||
|
|||||||
+191
-16
@@ -90,6 +90,9 @@ import jakarta.persistence.Table;
|
|||||||
import jakarta.persistence.UniqueConstraint;
|
import jakarta.persistence.UniqueConstraint;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -290,6 +293,49 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
|
|||||||
@Column(name = "account_closed_zero_balance_in_day_closing")
|
@Column(name = "account_closed_zero_balance_in_day_closing")
|
||||||
private Boolean accountClosedZeroBalanceInDayClosing = false;
|
private Boolean accountClosedZeroBalanceInDayClosing = false;
|
||||||
|
|
||||||
|
// RD-specific flags
|
||||||
|
@Column(name = "balance_swap_option")
|
||||||
|
private boolean balanceSwapOption = false;
|
||||||
|
|
||||||
|
@Column(name = "msme")
|
||||||
|
private boolean msme = false;
|
||||||
|
|
||||||
|
@Column(name = "auto_debit_facility")
|
||||||
|
private boolean autoDebitFacility = false;
|
||||||
|
|
||||||
|
@Column(name = "loan_facility")
|
||||||
|
private boolean loanFacility = false;
|
||||||
|
|
||||||
|
@Column(name = "premature_withdrawal_option")
|
||||||
|
private boolean prematureWithdrawalOption = false;
|
||||||
|
|
||||||
|
@Column(name = "tenure_flexibility")
|
||||||
|
private boolean tenureFlexibility = false;
|
||||||
|
|
||||||
|
@Column(name = "lien_marked")
|
||||||
|
private boolean lienMarked = false;
|
||||||
|
|
||||||
|
@Column(name = "maturity_instructions")
|
||||||
|
private boolean maturityInstructions = false;
|
||||||
|
|
||||||
|
@Column(name = "credit_maturity_proceeds")
|
||||||
|
private boolean creditMaturityProceeds = false;
|
||||||
|
|
||||||
|
@Column(name = "penalty_grace_period_missed_installments")
|
||||||
|
private boolean penaltyGracePeriodMissedInstallments = false;
|
||||||
|
|
||||||
|
@Column(name = "panel_request_applicable")
|
||||||
|
private boolean panelRequestApplicable = false;
|
||||||
|
|
||||||
|
@Column(name = "no_penalty_nominee_heir_closure")
|
||||||
|
private boolean noPenaltyNomineeHeirClosure = false;
|
||||||
|
|
||||||
|
@Column(name = "deceased_settlement")
|
||||||
|
private boolean deceasedSettlement = false;
|
||||||
|
|
||||||
|
@Column(name = "allow_debit_txn_after_due_date")
|
||||||
|
private boolean allowDebitTxnAfterDueDate = false;
|
||||||
|
|
||||||
// Relationships for complex objects
|
// Relationships for complex objects
|
||||||
@OneToOne(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
|
@OneToOne(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
|
||||||
private SavingsProductFacilityConfig facilityConfig;
|
private SavingsProductFacilityConfig facilityConfig;
|
||||||
@@ -750,20 +796,26 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
|
|||||||
actualChanges.put(productCodeParamName, newValue);
|
actualChanges.put(productCodeParamName, newValue);
|
||||||
this.productCode = newValue;
|
this.productCode = newValue;
|
||||||
}
|
}
|
||||||
if (command.isChangeInLocalDateParameterNamed(effectiveDateParamName, this.effectiveDate)) {
|
if (command.parameterExists(effectiveDateParamName)) {
|
||||||
final LocalDate newValue = command.localDateValueOfParameterNamed(effectiveDateParamName);
|
final LocalDate newValue = parseCbsLocalDate(command.stringValueOfParameterNamed(effectiveDateParamName));
|
||||||
actualChanges.put(effectiveDateParamName, newValue);
|
if (!Objects.equals(newValue, this.effectiveDate)) {
|
||||||
this.effectiveDate = newValue;
|
actualChanges.put(effectiveDateParamName, newValue);
|
||||||
|
this.effectiveDate = newValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (command.isChangeInLocalDateParameterNamed(effectiveToParamName, this.effectiveTo)) {
|
if (command.parameterExists(effectiveToParamName)) {
|
||||||
final LocalDate newValue = command.localDateValueOfParameterNamed(effectiveToParamName);
|
final LocalDate newValue = parseCbsLocalDate(command.stringValueOfParameterNamed(effectiveToParamName));
|
||||||
actualChanges.put(effectiveToParamName, newValue);
|
if (!Objects.equals(newValue, this.effectiveTo)) {
|
||||||
this.effectiveTo = newValue;
|
actualChanges.put(effectiveToParamName, newValue);
|
||||||
|
this.effectiveTo = newValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (command.isChangeInLocalDateParameterNamed(endDateParamName, this.endDate)) {
|
if (command.parameterExists(endDateParamName)) {
|
||||||
final LocalDate newValue = command.localDateValueOfParameterNamed(endDateParamName);
|
final LocalDate newValue = parseCbsLocalDate(command.stringValueOfParameterNamed(endDateParamName));
|
||||||
actualChanges.put(endDateParamName, newValue);
|
if (!Objects.equals(newValue, this.endDate)) {
|
||||||
this.endDate = newValue;
|
actualChanges.put(endDateParamName, newValue);
|
||||||
|
this.endDate = newValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (command.isChangeInBooleanParameterNamed(introducerRequiredParamName, this.introducerRequired)) {
|
if (command.isChangeInBooleanParameterNamed(introducerRequiredParamName, this.introducerRequired)) {
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(introducerRequiredParamName);
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(introducerRequiredParamName);
|
||||||
@@ -805,10 +857,12 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
|
|||||||
actualChanges.put(frequencyFixDayParamName, newValue);
|
actualChanges.put(frequencyFixDayParamName, newValue);
|
||||||
this.frequencyFixDay = newValue;
|
this.frequencyFixDay = newValue;
|
||||||
}
|
}
|
||||||
if (command.isChangeInLocalDateParameterNamed(lastInterestDateParamName, this.lastInterestDate)) {
|
if (command.parameterExists(lastInterestDateParamName)) {
|
||||||
final LocalDate newValue = command.localDateValueOfParameterNamed(lastInterestDateParamName);
|
final LocalDate newValue = parseCbsLocalDate(command.stringValueOfParameterNamed(lastInterestDateParamName));
|
||||||
actualChanges.put(lastInterestDateParamName, newValue);
|
if (!Objects.equals(newValue, this.lastInterestDate)) {
|
||||||
this.lastInterestDate = newValue;
|
actualChanges.put(lastInterestDateParamName, newValue);
|
||||||
|
this.lastInterestDate = newValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (command.isChangeInBooleanParameterNamed(enableLockinPeriodParamName, this.enableLockinPeriod)) {
|
if (command.isChangeInBooleanParameterNamed(enableLockinPeriodParamName, this.enableLockinPeriod)) {
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enableLockinPeriodParamName);
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enableLockinPeriodParamName);
|
||||||
@@ -830,6 +884,64 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
|
|||||||
this.accountClosedZeroBalanceInDayClosing = newValue;
|
this.accountClosedZeroBalanceInDayClosing = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update RD-specific flags (0270 migration fields)
|
||||||
|
if (command.isChangeInBooleanParameterNamed("balanceSwapOption", this.balanceSwapOption)) {
|
||||||
|
this.balanceSwapOption = command.booleanPrimitiveValueOfParameterNamed("balanceSwapOption");
|
||||||
|
actualChanges.put("balanceSwapOption", this.balanceSwapOption);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("msme", this.msme)) {
|
||||||
|
this.msme = command.booleanPrimitiveValueOfParameterNamed("msme");
|
||||||
|
actualChanges.put("msme", this.msme);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("autoDebitFacility", this.autoDebitFacility)) {
|
||||||
|
this.autoDebitFacility = command.booleanPrimitiveValueOfParameterNamed("autoDebitFacility");
|
||||||
|
actualChanges.put("autoDebitFacility", this.autoDebitFacility);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("loanFacility", this.loanFacility)) {
|
||||||
|
this.loanFacility = command.booleanPrimitiveValueOfParameterNamed("loanFacility");
|
||||||
|
actualChanges.put("loanFacility", this.loanFacility);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("prematureWithdrawalOption", this.prematureWithdrawalOption)) {
|
||||||
|
this.prematureWithdrawalOption = command.booleanPrimitiveValueOfParameterNamed("prematureWithdrawalOption");
|
||||||
|
actualChanges.put("prematureWithdrawalOption", this.prematureWithdrawalOption);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("tenureFlexibility", this.tenureFlexibility)) {
|
||||||
|
this.tenureFlexibility = command.booleanPrimitiveValueOfParameterNamed("tenureFlexibility");
|
||||||
|
actualChanges.put("tenureFlexibility", this.tenureFlexibility);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("lienMarked", this.lienMarked)) {
|
||||||
|
this.lienMarked = command.booleanPrimitiveValueOfParameterNamed("lienMarked");
|
||||||
|
actualChanges.put("lienMarked", this.lienMarked);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("maturityInstructions", this.maturityInstructions)) {
|
||||||
|
this.maturityInstructions = command.booleanPrimitiveValueOfParameterNamed("maturityInstructions");
|
||||||
|
actualChanges.put("maturityInstructions", this.maturityInstructions);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("creditMaturityProceeds", this.creditMaturityProceeds)) {
|
||||||
|
this.creditMaturityProceeds = command.booleanPrimitiveValueOfParameterNamed("creditMaturityProceeds");
|
||||||
|
actualChanges.put("creditMaturityProceeds", this.creditMaturityProceeds);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("penaltyGracePeriodMissedInstallments", this.penaltyGracePeriodMissedInstallments)) {
|
||||||
|
this.penaltyGracePeriodMissedInstallments = command.booleanPrimitiveValueOfParameterNamed("penaltyGracePeriodMissedInstallments");
|
||||||
|
actualChanges.put("penaltyGracePeriodMissedInstallments", this.penaltyGracePeriodMissedInstallments);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("panelRequestApplicable", this.panelRequestApplicable)) {
|
||||||
|
this.panelRequestApplicable = command.booleanPrimitiveValueOfParameterNamed("panelRequestApplicable");
|
||||||
|
actualChanges.put("panelRequestApplicable", this.panelRequestApplicable);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("noPenaltyNomineeHeirClosure", this.noPenaltyNomineeHeirClosure)) {
|
||||||
|
this.noPenaltyNomineeHeirClosure = command.booleanPrimitiveValueOfParameterNamed("noPenaltyNomineeHeirClosure");
|
||||||
|
actualChanges.put("noPenaltyNomineeHeirClosure", this.noPenaltyNomineeHeirClosure);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("deceasedSettlement", this.deceasedSettlement)) {
|
||||||
|
this.deceasedSettlement = command.booleanPrimitiveValueOfParameterNamed("deceasedSettlement");
|
||||||
|
actualChanges.put("deceasedSettlement", this.deceasedSettlement);
|
||||||
|
}
|
||||||
|
if (command.isChangeInBooleanParameterNamed("allowDebitTxnAfterDueDate", this.allowDebitTxnAfterDueDate)) {
|
||||||
|
this.allowDebitTxnAfterDueDate = command.booleanPrimitiveValueOfParameterNamed("allowDebitTxnAfterDueDate");
|
||||||
|
actualChanges.put("allowDebitTxnAfterDueDate", this.allowDebitTxnAfterDueDate);
|
||||||
|
}
|
||||||
|
|
||||||
validateLockinDetails();
|
validateLockinDetails();
|
||||||
esnureOverdraftLimitsSetForOverdraftAccounts();
|
esnureOverdraftLimitsSetForOverdraftAccounts();
|
||||||
|
|
||||||
@@ -1142,6 +1254,48 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
|
|||||||
this.accountClosedZeroBalanceInDayClosing = accountClosedZeroBalanceInDayClosing;
|
this.accountClosedZeroBalanceInDayClosing = accountClosedZeroBalanceInDayClosing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBalanceSwapOption() { return balanceSwapOption; }
|
||||||
|
public void setBalanceSwapOption(boolean v) { this.balanceSwapOption = v; }
|
||||||
|
|
||||||
|
public boolean isMsme() { return msme; }
|
||||||
|
public void setMsme(boolean v) { this.msme = v; }
|
||||||
|
|
||||||
|
public boolean isAutoDebitFacility() { return autoDebitFacility; }
|
||||||
|
public void setAutoDebitFacility(boolean v) { this.autoDebitFacility = v; }
|
||||||
|
|
||||||
|
public boolean isLoanFacility() { return loanFacility; }
|
||||||
|
public void setLoanFacility(boolean v) { this.loanFacility = v; }
|
||||||
|
|
||||||
|
public boolean isPrematureWithdrawalOption() { return prematureWithdrawalOption; }
|
||||||
|
public void setPrematureWithdrawalOption(boolean v) { this.prematureWithdrawalOption = v; }
|
||||||
|
|
||||||
|
public boolean isTenureFlexibility() { return tenureFlexibility; }
|
||||||
|
public void setTenureFlexibility(boolean v) { this.tenureFlexibility = v; }
|
||||||
|
|
||||||
|
public boolean isLienMarked() { return lienMarked; }
|
||||||
|
public void setLienMarked(boolean v) { this.lienMarked = v; }
|
||||||
|
|
||||||
|
public boolean isMaturityInstructions() { return maturityInstructions; }
|
||||||
|
public void setMaturityInstructions(boolean v) { this.maturityInstructions = v; }
|
||||||
|
|
||||||
|
public boolean isCreditMaturityProceeds() { return creditMaturityProceeds; }
|
||||||
|
public void setCreditMaturityProceeds(boolean v) { this.creditMaturityProceeds = v; }
|
||||||
|
|
||||||
|
public boolean isPenaltyGracePeriodMissedInstallments() { return penaltyGracePeriodMissedInstallments; }
|
||||||
|
public void setPenaltyGracePeriodMissedInstallments(boolean v) { this.penaltyGracePeriodMissedInstallments = v; }
|
||||||
|
|
||||||
|
public boolean isPanelRequestApplicable() { return panelRequestApplicable; }
|
||||||
|
public void setPanelRequestApplicable(boolean v) { this.panelRequestApplicable = v; }
|
||||||
|
|
||||||
|
public boolean isNoPenaltyNomineeHeirClosure() { return noPenaltyNomineeHeirClosure; }
|
||||||
|
public void setNoPenaltyNomineeHeirClosure(boolean v) { this.noPenaltyNomineeHeirClosure = v; }
|
||||||
|
|
||||||
|
public boolean isDeceasedSettlement() { return deceasedSettlement; }
|
||||||
|
public void setDeceasedSettlement(boolean v) { this.deceasedSettlement = v; }
|
||||||
|
|
||||||
|
public boolean isAllowDebitTxnAfterDueDate() { return allowDebitTxnAfterDueDate; }
|
||||||
|
public void setAllowDebitTxnAfterDueDate(boolean v) { this.allowDebitTxnAfterDueDate = v; }
|
||||||
|
|
||||||
public SavingsProductFacilityConfig getFacilityConfig() {
|
public SavingsProductFacilityConfig getFacilityConfig() {
|
||||||
return facilityConfig;
|
return facilityConfig;
|
||||||
}
|
}
|
||||||
@@ -1174,4 +1328,25 @@ public class SavingsProduct extends AbstractPersistableCustom<Long> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses CBS date strings (e.g. "17 April 2026") without requiring a dateFormat parameter.
|
||||||
|
*/
|
||||||
|
public static LocalDate parseCbsLocalDate(final String dateStr) {
|
||||||
|
if (dateStr == null || dateStr.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (DateTimeFormatter fmt : Arrays.asList(
|
||||||
|
DateTimeFormatter.ofPattern("dd MMMM yyyy", java.util.Locale.ENGLISH),
|
||||||
|
DateTimeFormatter.ofPattern("dd MMM yyyy", java.util.Locale.ENGLISH),
|
||||||
|
DateTimeFormatter.ofPattern("yyyy-MM-dd", java.util.Locale.ENGLISH),
|
||||||
|
DateTimeFormatter.ofPattern("dd/MM/yyyy", java.util.Locale.ENGLISH))) {
|
||||||
|
try {
|
||||||
|
return LocalDate.parse(dateStr, fmt);
|
||||||
|
} catch (DateTimeParseException ignored) {
|
||||||
|
// try next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user