Skip to content

Commit 13be231

Browse files
committed
FINERACT-848: Add ability to override interest rate chart in deposit accounts
1 parent 7409970 commit 13be231

14 files changed

Lines changed: 585 additions & 68 deletions

File tree

fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ private DepositsApiConstants() {
141141
public static final String depositMaxAmountParamName = "maxDepositAmount";
142142
public static final String depositPeriodParamName = "depositPeriod";
143143
public static final String depositPeriodFrequencyIdParamName = "depositPeriodFrequencyId";
144+
public static final String isRateChartOverriddenParamName = "isRateChartOverridden";
144145

145146
// recurring parameters
146147
public static final String mandatoryRecommendedDepositAmountParamName = "mandatoryRecommendedDepositAmount";
@@ -304,14 +305,14 @@ private static Set<String> recurringDepositProductResponseData() {
304305
* Depost Account parameters
305306
*/
306307

307-
private static final Set<String> DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
308-
Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName,
309-
clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName,
310-
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
311-
interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName,
312-
lockinPeriodFrequencyTypeParamName, chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName,
313-
depositPeriodFrequencyIdParamName, savingsAccounts, expectedFirstDepositOnDateParamName,
314-
SavingsApiConstants.withHoldTaxParamName, maturityInstructionIdParamName, transferToSavingsIdParamName));
308+
private static final Set<String> DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
309+
dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName,
310+
productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName,
311+
interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
312+
interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
313+
chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName, depositPeriodFrequencyIdParamName,
314+
savingsAccounts, expectedFirstDepositOnDateParamName, SavingsApiConstants.withHoldTaxParamName, maturityInstructionIdParamName,
315+
transferToSavingsIdParamName, isRateChartOverriddenParamName));
315316

316317
public static final Set<String> FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData();
317318
public static final Set<String> FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData();

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ private PostFixedDepositAccountsRequest() {}
308308
public Integer depositPeriod;
309309
@Schema(example = "2")
310310
public Long depositPeriodFrequencyId;
311+
@Schema(example = "false", description = "Determines whether product level interest rate chart is overridden by custom nominal annual interest rate")
312+
public Boolean isRateChartOverridden;
311313
}
312314

313315
@Schema(description = "PostFixedDepositAccountsResponse")
@@ -474,6 +476,8 @@ private GetFixedDepositAccountsAccountIdSummary() {}
474476
public LocalDate maturityDate;
475477
@Schema(example = "6")
476478
public Integer depositPeriod;
479+
@Schema(example = "false", description = "Determines whether product level interest rate chart is overridden by custom nominal annual interest rate")
480+
public Boolean isRateChartOverridden;
477481
public GetFixedDepositAccountsResponse.GetFixedDepositAccountsDepositPeriodFrequency depositPeriodFrequency;
478482
public GetFixedDepositAccountsAccountIdSummary summary;
479483
public GetFixedDepositAccountsAccountChart accountChart;

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResourceSwagger.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ private PostRecurringDepositAccountsRequest() {}
300300
public Integer recurringFrequencyType;
301301
@Schema(example = "2000")
302302
public Long mandatoryRecommendedDepositAmount;
303+
@Schema(example = "false", description = "Determines whether product level interest rate chart is overridden by custom nominal annual interest rate")
304+
public Boolean isRateChartOverridden;
303305
}
304306

305307
@Schema(description = "PostRecurringDepositAccountsResponse")
@@ -436,6 +438,8 @@ private GetRecurringDepositAccountsPeriodTypes() {}
436438
public GetRecurringDepositAccountsResponse.GetRecurringDepositAccountsRecurringDepositFrequencyType recurringDepositFrequencyType;
437439
@Schema(example = "6")
438440
public Integer depositPeriod;
441+
@Schema(example = "false", description = "Determines whether product level interest rate chart is overridden by custom nominal annual interest rate")
442+
public Boolean isRateChartOverridden;
439443
public GetRecurringDepositAccountsResponse.GetRecurringDepositAccountsDepositPeriodFrequency depositPeriodFrequency;
440444
public GetRecurringDepositAccountsResponse.GetRecurringDepositAccountsSummary summary;
441445
public GetRecurringDepositAccountsAccountChart accountChart;

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.interestPostingPeriodInMonthsParamName;
3131
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName;
3232
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
33+
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isRateChartOverriddenParamName;
3334
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.linkedAccountParamName;
3435
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.mandatoryRecommendedDepositAmountParamName;
3536
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maturityInstructionIdParamName;
@@ -576,6 +577,20 @@ private void validateDepositTermDeatilForSubmit(final JsonElement element, final
576577
element);
577578
baseDataValidator.reset().parameter(depositPeriodFrequencyIdParamName).value(depositPeriodFrequencyId)
578579
.isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
580+
581+
if (fromApiJsonHelper.parameterExists(isRateChartOverriddenParamName, element)) {
582+
final Boolean isRateChartOverridden = this.fromApiJsonHelper.extractBooleanNamed(isRateChartOverriddenParamName, element);
583+
584+
baseDataValidator.reset().parameter(isRateChartOverriddenParamName).value(isRateChartOverridden).notNull();
585+
586+
if (Boolean.TRUE.equals(isRateChartOverridden)) {
587+
final BigDecimal nominalAnnualInterestRate = this.fromApiJsonHelper
588+
.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element);
589+
590+
baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate).notNull()
591+
.zeroOrPositiveAmount();
592+
}
593+
}
579594
}
580595

581596
private void validateDepositTermDeatilForUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
@@ -602,6 +617,18 @@ private void validateDepositTermDeatilForUpdate(final JsonElement element, final
602617
baseDataValidator.reset().parameter(depositPeriodFrequencyIdParamName).value(depositPeriodFrequencyId)
603618
.isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
604619
}
620+
621+
if (fromApiJsonHelper.parameterExists(isRateChartOverriddenParamName, element)) {
622+
final Boolean isRateChartOverridden = this.fromApiJsonHelper.extractBooleanNamed(isRateChartOverriddenParamName, element);
623+
baseDataValidator.reset().parameter(isRateChartOverriddenParamName).value(isRateChartOverridden).notNull();
624+
625+
if (Boolean.TRUE.equals(isRateChartOverridden)) {
626+
final BigDecimal nominalAnnualInterestRate = this.fromApiJsonHelper
627+
.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element);
628+
baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate).notNull()
629+
.zeroOrPositiveAmount();
630+
}
631+
}
605632
}
606633

607634
private void validateForInterestCalc(final JsonElement element, final DataValidatorBuilder baseDataValidator) {

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.expectedFirstDepositOnDateParamName;
3232
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName;
3333
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
34+
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.isRateChartOverriddenParamName;
3435
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.mandatoryRecommendedDepositAmountParamName;
3536
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maturityInstructionIdParamName;
3637
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
@@ -404,9 +405,13 @@ public DepositAccountTermAndPreClosure assembleAccountTermAndPreClosure(final Js
404405
? DepositAccountOnClosureType.fromInt(accountOnClosureTypeId)
405406
: null;
406407
final Long transferToSavingsId = command.longValueOfParameterNamed(transferToSavingsIdParamName);
408+
Boolean isRateChartOverridden = false;
409+
if (command.parameterExists(isRateChartOverriddenParamName)) {
410+
isRateChartOverridden = command.booleanObjectValueOfParameterNamed(isRateChartOverriddenParamName);
411+
}
407412
return DepositAccountTermAndPreClosure.createNew(updatedProductPreClosure, updatedProductTerm, account, depositAmount,
408413
maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, expectedFirstDepositOnDate, accountOnClosureType,
409-
trasferInterest, transferToSavingsId);
414+
trasferInterest, transferToSavingsId, isRateChartOverridden);
410415
}
411416

412417
public DepositAccountRecurringDetail assembleAccountRecurringDetail(final JsonCommand command,

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -158,35 +158,36 @@ protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, fi
158158
protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate,
159159
final boolean isPreMatureClosure) {
160160

161-
// default it to nominalAnnualInterst rate. interest chart overrrides
162-
// this value.
163161
BigDecimal applicableInterestRate = this.nominalAnnualInterestRate;
164-
if (this.chart != null) {
165-
boolean applyPreMaturePenalty = false;
166-
BigDecimal penalInterest = BigDecimal.ZERO;
167-
LocalDate depositCloseDate = calculateMaturityDate();
168-
if (isPreMatureClosure) {
169-
if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) {
170-
applyPreMaturePenalty = true;
171-
penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest();
172-
final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure
173-
.depositPreClosureDetail().preClosurePenalInterestOnType();
174-
if (preClosurePenalInterestOnType == PreClosurePenalInterestOnType.WHOLE_TERM) {
175-
depositCloseDate = interestCalculatedUpto();
176-
} else if (preClosurePenalInterestOnType == PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL) {
177-
depositCloseDate = interestPostingUpToDate;
178-
}
162+
boolean applyPreMaturePenalty = false;
163+
BigDecimal penalInterest = BigDecimal.ZERO;
164+
LocalDate depositCloseDate = calculateMaturityDate();
165+
166+
if (isPreMatureClosure) {
167+
if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) {
168+
applyPreMaturePenalty = true;
169+
penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest();
170+
171+
final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure.depositPreClosureDetail()
172+
.preClosurePenalInterestOnType();
173+
if (preClosurePenalInterestOnType == PreClosurePenalInterestOnType.WHOLE_TERM) {
174+
depositCloseDate = interestCalculatedUpto();
175+
} else if (preClosurePenalInterestOnType == PreClosurePenalInterestOnType.TILL_PREMATURE_WITHDRAWAL) {
176+
depositCloseDate = interestPostingUpToDate;
179177
}
180178
}
179+
}
181180

181+
if (this.chart != null && !this.accountTermAndPreClosure.isRateChartOverridden()) {
182182
final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
183183
applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client);
184+
}
184185

185-
if (applyPreMaturePenalty) {
186-
applicableInterestRate = applicableInterestRate.subtract(penalInterest);
187-
applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate;
188-
}
186+
if (applyPreMaturePenalty) {
187+
applicableInterestRate = applicableInterestRate.subtract(penalInterest);
188+
applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate;
189189
}
190+
190191
this.nominalAnnualInterestRate = applicableInterestRate;
191192

192193
return applicableInterestRate.divide(BigDecimal.valueOf(100L), mc);
@@ -747,7 +748,7 @@ private void validateDomainRules(final DataValidatorBuilder baseDataValidator) {
747748
}
748749
}
749750

750-
if (this.chart != null) {
751+
if (this.chart != null && !this.accountTermAndPreClosure.isRateChartOverridden()) {
751752
final LocalDate chartFromDate = this.chart.getFromDate();
752753
LocalDate chartEndDate = this.chart.getEndDate();
753754
chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate;

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,11 @@ protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, fi
208208
protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate,
209209
final boolean isPreMatureClosure) {
210210

211+
BigDecimal applicableInterestRate = this.nominalAnnualInterestRate;
211212
boolean applyPreMaturePenalty = false;
212213
BigDecimal penalInterest = BigDecimal.ZERO;
213214
LocalDate depositCloseDate = calculateMaturityDate();
215+
214216
if (isPreMatureClosure) {
215217
if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) {
216218
applyPreMaturePenalty = true;
@@ -225,13 +227,14 @@ protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, fi
225227
}
226228
}
227229

228-
if (depositCloseDate == null) {
229-
depositCloseDate = DateUtils.getBusinessLocalDate();
230-
}
230+
if (this.chart != null && !this.accountTermAndPreClosure.isRateChartOverridden()) {
231+
if (depositCloseDate == null) {
232+
depositCloseDate = DateUtils.getBusinessLocalDate();
233+
}
231234

232-
final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
233-
BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate,
234-
this.client);
235+
final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
236+
applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client);
237+
}
235238

236239
if (applyPreMaturePenalty) {
237240
applicableInterestRate = applicableInterestRate.subtract(penalInterest);
@@ -1039,7 +1042,7 @@ public void validateApplicableInterestRate() {
10391042
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
10401043
.resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
10411044
LocalDate maturityDate = calculateMaturityDate();
1042-
if (this.chart != null) {
1045+
if (this.chart != null && !this.accountTermAndPreClosure.isRateChartOverridden()) {
10431046
final LocalDate chartFromDate = this.chart.getFromDate();
10441047
LocalDate chartEndDate = this.chart.getEndDate();
10451048
chartEndDate = chartEndDate == null ? DateUtils.getBusinessLocalDate() : chartEndDate;

0 commit comments

Comments
 (0)