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 240914b13..c5704438a 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 @@ -123,6 +123,7 @@ public final class DepositsApiConstants { public static final String isSeniorCitizenParamName = "isSeniorCitizen"; public static final String seniorCitizenInterestRateParamName = "seniorCitizenInterestRate"; public static final String tenureInMonthsParamName = "tenureInMonths"; + public static final String tenureInDaysParamName = "tenureInDays"; public static final String interestCompoundingPeriodInMonthsParamName = "interestCompoundingPeriodInMonths"; // Preclosure parameters @@ -511,6 +512,7 @@ public final class DepositsApiConstants { fixedDepositInterestCalculationData.add(principalAmountParamName); fixedDepositInterestCalculationData.add(annualInterestRateParamName); fixedDepositInterestCalculationData.add(tenureInMonthsParamName); + fixedDepositInterestCalculationData.add(tenureInDaysParamName); fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName); fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName); // Exact calendar-based calculation params diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java index d0b4aa3f5..830f6f5a2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java @@ -284,6 +284,7 @@ public class FixedDepositAccountsApiResource { @QueryParam("principalAmount") @Parameter(description = "Deposit principal amount") final BigDecimal principalAmount, @QueryParam("annualInterestRate") @Parameter(description = "Annual interest rate (%)") final BigDecimal annualInterestRate, @QueryParam("tenureInMonths") @Parameter(description = "Tenure in months") final Long tenureInMonths, + @QueryParam("tenureInDays") @Parameter(description = "Tenure in days (alternative to tenureInMonths, for sub-month FD Single tenures)") final Long tenureInDays, @QueryParam("interestCompoundingPeriodInMonths") @Parameter(description = "Compounding period in months (1,3,6,12)") final Long interestCompoundingPeriodInMonths, @QueryParam("interestPostingPeriodInMonths") @Parameter(description = "Posting period in months (1,3,6,12)") final Long interestPostingPeriodInMonths, @QueryParam("submittedOnDate") @Parameter(description = "Submission date (yyyy-MM-dd) for exact calendar calculation") final String submittedOnDate, @@ -291,6 +292,9 @@ public class FixedDepositAccountsApiResource { HashMap request = new HashMap<>(); request.put("annualInterestRate", annualInterestRate); request.put("tenureInMonths", tenureInMonths); + if (tenureInDays != null) { + request.put("tenureInDays", tenureInDays); + } request.put("interestCompoundingPeriodInMonths", interestCompoundingPeriodInMonths); request.put("interestPostingPeriodInMonths", interestPostingPeriodInMonths); request.put("principalAmount", principalAmount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java index 3aa401060..34d15fdab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java @@ -43,6 +43,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClos import static org.apache.fineract.portfolio.savings.DepositsApiConstants.principalAmountParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInDaysParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInMonthsParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferToSavingsIdParamName; @@ -681,8 +682,16 @@ public class DepositAccountDataValidator { baseDataValidator.reset().parameter(annualInterestRateParamName).value(annualInterestRate).notNull() .notLessThanMin(BigDecimal.valueOf(0)); + // FD Single accounts under 30 days are entered in whole days rather than months (a whole-month + // tenure would round down to 0 months); tenureInDays is an alternative to tenureInMonths, not + // an addition, so only one of the two needs to be a positive value. Long tenureInMonths = this.fromApiJsonHelper.extractLongNamed(tenureInMonthsParamName, element); - baseDataValidator.reset().parameter(tenureInMonthsParamName).value(tenureInMonths).notNull().longGreaterThanZero(); + Long tenureInDays = this.fromApiJsonHelper.extractLongNamed(tenureInDaysParamName, element); + if (tenureInDays != null && tenureInDays > 0) { + baseDataValidator.reset().parameter(tenureInMonthsParamName).value(tenureInMonths).ignoreIfNull().longGreaterThanZero(); + } else { + baseDataValidator.reset().parameter(tenureInMonthsParamName).value(tenureInMonths).notNull().longGreaterThanZero(); + } Long interestPostingPeriodInMonths = this.fromApiJsonHelper.extractLongNamed(interestPostingPeriodInMonthsParamName, element); baseDataValidator.reset().parameter(interestPostingPeriodInMonthsParamName).value(interestPostingPeriodInMonths).notNull() diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImpl.java index 061e1ce94..0aed9ad73 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImpl.java @@ -22,6 +22,7 @@ package org.apache.fineract.portfolio.savings.service; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.annualInterestRateParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.interestCompoundingPeriodInMonthsParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.principalAmountParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInDaysParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInMonthsParamName; import com.google.gson.JsonElement; @@ -52,6 +53,7 @@ public class FixedDepositAccountInterestCalculationServiceImpl implements FixedD BigDecimal principalAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalAmountParamName, element); BigDecimal annualInterestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualInterestRateParamName, element); Long tenureInMonths = this.fromApiJsonHelper.extractLongNamed(tenureInMonthsParamName, element); + Long tenureInDays = this.fromApiJsonHelper.extractLongNamed(tenureInDaysParamName, element); Long interestCompoundingPeriodInMonths = this.fromApiJsonHelper.extractLongNamed(interestCompoundingPeriodInMonthsParamName, element); // Optional params for exact calendar-based calculation @@ -62,8 +64,8 @@ public class FixedDepositAccountInterestCalculationServiceImpl implements FixedD if (dateStr != null && dateFormat != null) { submittedOnDate = LocalDate.parse(dateStr, java.time.format.DateTimeFormatter.ofPattern(dateFormat)); } - } catch (Exception ignored) { - // intentionally ignored + } catch (Exception ignored) { + // intentionally ignored } Integer daysInYear = null; final Long daysInYearLong = this.fromApiJsonHelper.extractLongNamed("daysInYear", element); @@ -73,7 +75,8 @@ public class FixedDepositAccountInterestCalculationServiceImpl implements FixedD BigDecimal maturityAmount; if (submittedOnDate != null && daysInYear != null && interestPostingPeriodInMonths != null) { // Exact calendar-based calculation — same algorithm as account creation - final LocalDate maturityDate = submittedOnDate.plusMonths(tenureInMonths); + final LocalDate maturityDate = (tenureInDays != null && tenureInDays > 0) ? submittedOnDate.plusDays(tenureInDays) + : submittedOnDate.plusMonths(tenureInMonths); maturityAmount = this.calculateExact(principalAmount, annualInterestRate, submittedOnDate, maturityDate, interestCompoundingPeriodInMonths, interestPostingPeriodInMonths, daysInYear); } else {