diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java index c5704438a..03c685961 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java @@ -143,6 +143,9 @@ public final class DepositsApiConstants { public static final String inMultiplesOfDepositTermParamName = "inMultiplesOfDepositTerm"; public static final String inMultiplesOfDepositTermTypeIdParamName = "inMultiplesOfDepositTermTypeId"; public static final String inMultiplesOfDepositTermType = "inMultiplesOfDepositTermType"; + // Days after maturity a depositor still has to walk in and renew at the original maturity date -- + // null/0 means this product keeps the instant, same-night auto-renewal every FD product had before. + public static final String renewalGracePeriodDaysParamName = "renewalGracePeriodDays"; public static final String depositAmountParamName = "depositAmount"; public static final String depositMinAmountParamName = "minDepositAmount"; @@ -248,11 +251,13 @@ public final class DepositsApiConstants { private static final Set DEPOSIT_TERM_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName, maxDepositTermParamName, minDepositTermTypeIdParamName, maxDepositTermTypeIdParamName, inMultiplesOfDepositTermParamName, - inMultiplesOfDepositTermTypeIdParamName, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName)); + inMultiplesOfDepositTermTypeIdParamName, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName, + renewalGracePeriodDaysParamName)); private static final Set DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(minDepositTermParamName, maxDepositTermParamName, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTermParamName, - inMultiplesOfDepositTermType, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName)); + inMultiplesOfDepositTermType, depositAmountParamName, depositMinAmountParamName, depositMaxAmountParamName, + renewalGracePeriodDaysParamName)); private static final Set RECURRING_DETAILS_REQUEST_DATA_PARAMETERS = new HashSet<>( Arrays.asList(mandatoryRecommendedDepositAmountParamName, isMandatoryDepositParamName, allowWithdrawalParamName, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java index d431acb5b..24f1b6aaf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java @@ -309,6 +309,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); sqlBuilder.append("dptp.min_deposit_amount as minDepositAmount, dptp.deposit_amount as depositAmount, "); sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount, "); + sqlBuilder.append("dptp.renewal_grace_period_days as renewalGracePeriodDays, "); sqlBuilder.append("sp.account_type as accountType, sp.fd_type as fdType, sp.product_code as productCode, "); sqlBuilder.append("sp.customer_segment as customerSegment, "); sqlBuilder.append("sp.gl_account_id as glAccountId, gla.name as glAccountName, "); @@ -385,6 +386,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead // CBS Custom Fields result.setAccountType(rs.getString("accountType")); result.setFdType(rs.getString("fdType")); + result.setRenewalGracePeriodDays(JdbcSupport.getInteger(rs, "renewalGracePeriodDays")); result.setProductCode(rs.getString("productCode")); result.setCustomerSegment(rs.getString("customerSegment")); result.setGlAccountId(JdbcSupport.getLong(rs, "glAccountId")); diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0572_add_renewal_grace_period_to_deposit_product_term.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0572_add_renewal_grace_period_to_deposit_product_term.xml new file mode 100644 index 000000000..cce65c650 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0572_add_renewal_grace_period_to_deposit_product_term.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0573_add_renewal_grace_period_to_saving_product_temp.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0573_add_renewal_grace_period_to_saving_product_temp.xml new file mode 100644 index 000000000..119cefcc6 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0573_add_renewal_grace_period_to_saving_product_temp.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java index 53c2e9199..88004012f 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java @@ -496,6 +496,7 @@ public final class FixedDepositProductData extends DepositProductData { private Boolean accountClosedZeroBalanceInDayClosing; private String balanceFrequency; private Boolean isPendingApproval; + private Integer renewalGracePeriodDays; // Template option setters public void setInterestCalculationTypeOption(Collection v) { this.interestCalculationTypeOption = v; } @@ -543,6 +544,7 @@ public final class FixedDepositProductData extends DepositProductData { public void setAccountClosedZeroBalanceInDayClosing(Boolean v) { this.accountClosedZeroBalanceInDayClosing = v; } public void setBalanceFrequency(String v) { this.balanceFrequency = v; } public void setIsPendingApproval(Boolean v) { this.isPendingApproval = v; } + public void setRenewalGracePeriodDays(Integer v) { this.renewalGracePeriodDays = v; } // CBS field getters public String getProductCode() { return productCode; } @@ -559,6 +561,7 @@ public final class FixedDepositProductData extends DepositProductData { public Long getDaysToInactive() { return daysToInactive; } public Long getDaysToDormancy() { return daysToDormancy; } public Long getDaysToEscheat() { return daysToEscheat; } + public Integer getRenewalGracePeriodDays() { return renewalGracePeriodDays; } public void copyCbsFieldsFrom(FixedDepositProductData src) { if (src == null) return; @@ -605,6 +608,7 @@ public final class FixedDepositProductData extends DepositProductData { this.accountClosedZeroBalanceInDayClosing = src.accountClosedZeroBalanceInDayClosing; this.balanceFrequency = src.balanceFrequency; this.withdrawalFeeForTransfers = src.withdrawalFeeForTransfers; + this.renewalGracePeriodDays = src.renewalGracePeriodDays; } private Boolean withdrawalFeeForTransfers; diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java index 96d4996d1..64402d2e7 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java @@ -36,6 +36,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepo import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.renewalGracePeriodDaysParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName; @@ -473,8 +474,10 @@ public class DepositProductAssembler { final SavingsPeriodFrequencyType inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsPeriodFrequencyType.fromInt(inMultiplesOfDepositTermTypeId); - return DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType); + final DepositTermDetail depositTermDetail = DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType, + maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType); + depositTermDetail.setRenewalGracePeriodDays(command.integerValueOfParameterNamed(renewalGracePeriodDaysParamName)); + return depositTermDetail; } public DepositTermDetail assembleDepositTermDetail(final JsonCommand command, final DepositTermDetail prodDepositTermDetail) { @@ -531,8 +534,17 @@ public class DepositProductAssembler { final SavingsPeriodFrequencyType inMultiplesOfDepositTermType = (inMultiplesOfDepositTermTypeId == null) ? null : SavingsPeriodFrequencyType.fromInt(inMultiplesOfDepositTermTypeId); - return DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType); + Integer renewalGracePeriodDays = null; + if (command.parameterExists(renewalGracePeriodDaysParamName)) { + renewalGracePeriodDays = command.integerValueOfParameterNamed(renewalGracePeriodDaysParamName); + } else if (prodDepositTermDetail != null) { + renewalGracePeriodDays = prodDepositTermDetail.renewalGracePeriodDays(); + } + + final DepositTermDetail depositTermDetail = DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType, + maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType); + depositTermDetail.setRenewalGracePeriodDays(renewalGracePeriodDays); + return depositTermDetail; } public DepositRecurringDetail assembleRecurringDetail(final JsonCommand command) { diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java index 360e64ada..e892c733d 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java @@ -24,6 +24,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepo import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepositTermTypeIdParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepositTermTypeIdParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.renewalGracePeriodDaysParamName; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -61,6 +62,14 @@ public class DepositTermDetail { @Column(name = "in_multiples_of_deposit_term_type_enum", nullable = true) private Integer inMultiplesOfDepositTermType; + // Days after maturity a depositor can still walk in and renew backdated to the original maturity + // date, at the currently active rate. Null/0 means this product keeps the instant, same-night + // auto-renewal every FD product had before this field existed -- see + // DepositAccountWritePlatformServiceJpaRepositoryImpl#updateMaturityDetails, which only skips the + // automatic renewal when this is a positive value. + @Column(name = "renewal_grace_period_days", nullable = true) + private Integer renewalGracePeriodDays; + public static DepositTermDetail createFrom(final Integer minDepositTerm, final Integer maxDepositTerm, final SavingsPeriodFrequencyType minDepositTermType, final SavingsPeriodFrequencyType maxDepositTermType, final Integer inMultiplesOfDepositTerm, final SavingsPeriodFrequencyType inMultiplesOfDepositTermType) { @@ -123,6 +132,12 @@ public class DepositTermDetail { this.inMultiplesOfDepositTermType = newValue; } + if (command.isChangeInIntegerParameterNamed(renewalGracePeriodDaysParamName, this.renewalGracePeriodDays)) { + final Integer newValue = command.integerValueOfParameterNamed(renewalGracePeriodDaysParamName); + actualChanges.put(renewalGracePeriodDaysParamName, newValue); + this.renewalGracePeriodDays = newValue; + } + if (isMinDepositTermGreaterThanMaxDepositTerm()) { baseDataValidator.parameter(minDepositTermParamName).value(this.minDepositTerm).failWithCode(".greater.than.maxDepositTerm", this.minDepositTerm, this.maxDepositTerm); @@ -166,6 +181,14 @@ public class DepositTermDetail { return this.inMultiplesOfDepositTermType; } + public Integer renewalGracePeriodDays() { + return this.renewalGracePeriodDays; + } + + public void setRenewalGracePeriodDays(final Integer renewalGracePeriodDays) { + this.renewalGracePeriodDays = renewalGracePeriodDays; + } + public boolean isDepositBetweenMinAndMax(LocalDate depositStartDate, LocalDate depositEndDate) { return isEqualOrGreaterThanMin(depositStartDate, depositEndDate) && isEqualOrLessThanMax(depositStartDate, depositEndDate); } @@ -260,7 +283,9 @@ public class DepositTermDetail { final SavingsPeriodFrequencyType inMultiplesOfDepositTermType = SavingsPeriodFrequencyType .fromInt(this.inMultiplesOfDepositTermType()); - return DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType); + final DepositTermDetail copy = DepositTermDetail.createFrom(minDepositTerm, maxDepositTerm, minDepositTermType, + maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType); + copy.renewalGracePeriodDays = this.renewalGracePeriodDays; + return copy; } } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositEditFields.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositEditFields.java index 8699d91cb..360a9d6dd 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositEditFields.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositEditFields.java @@ -68,4 +68,7 @@ public class FixedDepositEditFields { @Column(name = "deposit_amount") private BigDecimal depositAmount; + + @Column(name = "renewal_grace_period_days") + private Integer renewalGracePeriodDays; } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/ProductPendingEditFieldMapper.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/ProductPendingEditFieldMapper.java index 9fa9fe453..04534c5ac 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/ProductPendingEditFieldMapper.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/ProductPendingEditFieldMapper.java @@ -942,6 +942,10 @@ public final class ProductPendingEditFieldMapper { f.setDepositAmount(command.bigDecimalValueOfParameterNamed("depositAmount")); present.add("depositAmount"); } + if (command.hasParameter("renewalGracePeriodDays")) { + f.setRenewalGracePeriodDays(command.integerValueOfParameterNamed("renewalGracePeriodDays")); + present.add("renewalGracePeriodDays"); + } return present; } @@ -962,6 +966,7 @@ public final class ProductPendingEditFieldMapper { if (f.getMinDepositAmount() != null) putNumber(root, submitted, "minDepositAmount", f.getMinDepositAmount()); if (f.getMaxDepositAmount() != null) putNumber(root, submitted, "maxDepositAmount", f.getMaxDepositAmount()); if (f.getDepositAmount() != null) putNumber(root, submitted, "depositAmount", f.getDepositAmount()); + if (f.getRenewalGracePeriodDays() != null) putNumber(root, submitted, "renewalGracePeriodDays", f.getRenewalGracePeriodDays()); } public static Set populateRecurringDepositFields(final JsonCommand command, final RecurringDepositEditFields f) {