This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 4cb849fa1 FINERACT-1981: Introduce Interest should not be calculated
on past due principal amount
4cb849fa1 is described below
commit 4cb849fa17a52356d690c04e7dcd5e4b63e07cfc
Author: Janos Meszaros <[email protected]>
AuthorDate: Wed Nov 27 17:52:40 2024 +0100
FINERACT-1981: Introduce Interest should not be calculated on past due
principal amount
---
.../loan/v1/LoanInterestRecalculationDataV1.avsc | 8 ++
.../v1/LoanProductInterestRecalculationDataV1.avsc | 8 ++
.../data/LoanInterestRecalculationData.java | 4 +-
.../domain/LoanInterestRecalculationDetails.java | 13 +-
.../loanproduct/LoanProductConstants.java | 1 +
.../api/LoanProductsApiResourceSwagger.java | 4 +
.../loanproduct/data/LoanProductData.java | 7 +-
.../data/LoanProductInterestRecalculationData.java | 14 +-
.../LoanProductInterestRecalculationDetails.java | 23 ++-
...dvancedPaymentScheduleTransactionProcessor.java | 4 +
.../LoanInterestRecalculationCOBBusinessStep.java | 7 +-
.../service/LoanReadPlatformServiceImpl.java | 156 +++++++++++----------
.../serialization/LoanProductDataValidator.java | 9 +-
.../LoanProductReadPlatformServiceImpl.java | 8 +-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
...dd_disallow_interest_calc_on_past_due_field.xml | 39 ++++++
.../LoanInterestRecalculationCOBTest.java | 66 ++++++++-
17 files changed, 287 insertions(+), 85 deletions(-)
diff --git
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
index c799b4240..29e789ced 100644
---
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
+++
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
@@ -146,6 +146,14 @@
"null",
"boolean"
]
+ },
+ {
+ "default": null,
+ "name": "disallowInterestCalculationOnPastDue",
+ "type": [
+ "null",
+ "boolean"
+ ]
}
]
}
diff --git
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
index 483116378..fffb4dce8 100644
---
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
+++
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
@@ -146,6 +146,14 @@
"null",
"boolean"
]
+ },
+ {
+ "default": null,
+ "name": "disallowInterestCalculationOnPastDue",
+ "type": [
+ "null",
+ "boolean"
+ ]
}
]
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
index 6c5c6aa41..60b08374c 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
@@ -47,6 +47,7 @@ public class LoanInterestRecalculationData {
private Boolean isCompoundingToBePostedAsTransaction;
private CalendarData compoundingCalendarData;
private Boolean allowCompoundingOnEod;
+ private Boolean disallowInterestCalculationOnPastDue;
public LoanInterestRecalculationData(final Long id, final Long loanId,
final EnumOptionData interestRecalculationCompoundingType,
final EnumOptionData rescheduleStrategyType, final CalendarData
calendarData,
@@ -56,7 +57,7 @@ public class LoanInterestRecalculationData {
final EnumOptionData recalculationCompoundingFrequencyType, final
Integer recalculationCompoundingFrequencyInterval,
final EnumOptionData recalculationCompoundingFrequencyNthDay,
final EnumOptionData recalculationCompoundingFrequencyWeekday,
final Integer recalculationCompoundingFrequencyOnDay, final
Boolean isCompoundingToBePostedAsTransaction,
- final Boolean allowCompoundingOnEod) {
+ final Boolean allowCompoundingOnEod, final Boolean
disallowInterestCalculationOnPastDue) {
this.id = id;
this.loanId = loanId;
this.interestRecalculationCompoundingType =
interestRecalculationCompoundingType;
@@ -75,6 +76,7 @@ public class LoanInterestRecalculationData {
this.compoundingCalendarData = compoundingCalendarData;
this.isCompoundingToBePostedAsTransaction =
isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod;
+ this.disallowInterestCalculationOnPastDue =
disallowInterestCalculationOnPastDue;
}
public LoanInterestRecalculationData withCalendarData(final CalendarData
calendarData, CalendarData compoundingCalendarData) {
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
index 5da30a483..454e524e0 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
@@ -88,6 +88,9 @@ public class LoanInterestRecalculationDetails extends
AbstractPersistableCustom<
@Column(name = "allow_compounding_on_eod")
private Boolean allowCompoundingOnEod;
+ @Column(name = "disallow_interest_calc_on_past_due")
+ private Boolean disallowInterestCalculationOnPastDue;
+
protected LoanInterestRecalculationDetails() {
// Default constructor for jpa repository
}
@@ -96,7 +99,7 @@ public class LoanInterestRecalculationDetails extends
AbstractPersistableCustom<
final Integer restFrequencyType, final Integer restInterval, final
Integer restFrequencyNthDay, Integer restFrequencyWeekday,
Integer restFrequencyOnDay, Integer compoundingFrequencyType,
Integer compoundingInterval, Integer compoundingFrequencyNthDay,
Integer compoundingFrequencyWeekday, Integer
compoundingFrequencyOnDay, final boolean isCompoundingToBePostedAsTransaction,
- final boolean allowCompoundingOnEod) {
+ final boolean allowCompoundingOnEod, final boolean
disallowInterestCalculationOnPastDue) {
this.interestRecalculationCompoundingMethod =
interestRecalculationCompoundingMethod;
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.restFrequencyNthDay = restFrequencyNthDay;
@@ -111,6 +114,7 @@ public class LoanInterestRecalculationDetails extends
AbstractPersistableCustom<
this.compoundingInterval = compoundingInterval;
this.isCompoundingToBePostedAsTransaction =
isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod;
+ this.disallowInterestCalculationOnPastDue =
disallowInterestCalculationOnPastDue;
}
public static LoanInterestRecalculationDetails createFrom(
@@ -127,7 +131,8 @@ public class LoanInterestRecalculationDetails extends
AbstractPersistableCustom<
loanProductInterestRecalculationDetails.getCompoundingFrequencyWeekday(),
loanProductInterestRecalculationDetails.getCompoundingFrequencyOnDay(),
loanProductInterestRecalculationDetails.getIsCompoundingToBePostedAsTransaction(),
-
loanProductInterestRecalculationDetails.allowCompoundingOnEod());
+
loanProductInterestRecalculationDetails.allowCompoundingOnEod(),
+
loanProductInterestRecalculationDetails.disallowInterestCalculationOnPastDue());
}
public void updateLoan(final Loan loan) {
@@ -189,4 +194,8 @@ public class LoanInterestRecalculationDetails extends
AbstractPersistableCustom<
public boolean allowCompoundingOnEod() {
return this.allowCompoundingOnEod;
}
+
+ public Boolean disallowInterestCalculationOnPastDue() {
+ return disallowInterestCalculationOnPastDue;
+ }
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
index 55ab6a1ff..834f131c6 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
@@ -97,6 +97,7 @@ public interface LoanProductConstants {
String recalculationCompoundingFrequencyNthDayParamName =
"recalculationCompoundingFrequencyNthDayType";
String recalculationCompoundingFrequencyOnDayParamName =
"recalculationCompoundingFrequencyOnDayType";
String isCompoundingToBePostedAsTransactionParamName =
"isCompoundingToBePostedAsTransaction";
+ String disallowInterestCalculationOnPastDueParamName =
"disallowInterestCalculationOnPastDue";
// Guarantee related
String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index a97d1d9c9..bc13d8629 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -198,6 +198,8 @@ final class LoanProductsApiResourceSwagger {
public Boolean isCompoundingToBePostedAsTransaction;
@Schema(example = "false")
public Boolean allowCompoundingOnEod;
+ @Schema(example = "false")
+ public Boolean disallowInterestCalculationOnPastDue;
// Accounting
@Schema(example = "3")
@@ -520,6 +522,8 @@ final class LoanProductsApiResourceSwagger {
public GetLoanProductsPreClosureInterestCalculationStrategy
preClosureInterestCalculationStrategy;
@Schema(example = "true")
public Boolean isArrearsBasedOnOriginalSchedule;
+ @Schema(example = "false")
+ public Boolean disallowInterestCalculationOnPastDue;
@Schema(example = "true")
public Boolean isCompoundingToBePostedAsTransaction;
@Schema(example = "true")
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index 5ffb54479..3a1d14bed 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -1182,7 +1182,7 @@ public class LoanProductData implements Serializable {
getInterestRecalculationRestOnDayType(),
compoundingCalendarData, getRecalculationCompoundingFrequencyType(),
getRecalculationCompoundingFrequencyInterval(),
getInterestRecalculationCompoundingNthDayType(),
getInterestRecalculationCompoundingWeekDayType(),
getInterestRecalculationCompoundingOnDayType(),
- isCompoundingToBePostedAsTransaction(),
allowCompoundingOnEod());
+ isCompoundingToBePostedAsTransaction(),
allowCompoundingOnEod(), disallowInterestCalculationOnPastDue());
}
private EnumOptionData getRescheduleStrategyType() {
@@ -1279,6 +1279,11 @@ public class LoanProductData implements Serializable {
return isInterestRecalculationEnabled() ?
this.interestRecalculationData.isAllowCompoundingOnEod() : null;
}
+ @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
+ public Boolean disallowInterestCalculationOnPastDue() {
+ return isInterestRecalculationEnabled() ?
this.interestRecalculationData.disallowInterestCalculationOnPastDue() : null;
+ }
+
public void
setLoanProductConfigurableAttributes(LoanProductConfigurableAttributes
loanProductConfigurableAttributes) {
this.allowAttributeOverrides = loanProductConfigurableAttributes;
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
index 8c6fe4110..9820a6316 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
@@ -50,6 +50,7 @@ public class LoanProductInterestRecalculationData implements
Serializable {
private final boolean isCompoundingToBePostedAsTransaction;
private final EnumOptionData preClosureInterestCalculationStrategy;
private final boolean allowCompoundingOnEod;
+ private final Boolean disallowInterestCalculationOnPastDue;
public LoanProductInterestRecalculationData(final Long id, final Long
productId,
final EnumOptionData interestRecalculationCompoundingType, final
EnumOptionData rescheduleStrategyType,
@@ -59,7 +60,8 @@ public class LoanProductInterestRecalculationData implements
Serializable {
final Integer recalculationCompoundingFrequencyInterval, final
EnumOptionData recalculationCompoundingFrequencyNthDay,
final EnumOptionData recalculationCompoundingFrequencyWeekday,
final Integer recalculationCompoundingFrequencyOnDay,
final boolean isArrearsBasedOnOriginalSchedule, boolean
isCompoundingToBePostedAsTransaction,
- final EnumOptionData preCloseInterestCalculationStrategy, final
boolean allowCompoundingOnEod) {
+ final EnumOptionData preCloseInterestCalculationStrategy, final
boolean allowCompoundingOnEod,
+ final Boolean disallowInterestCalculationOnPastDue) {
this.id = id;
this.productId = productId;
this.interestRecalculationCompoundingType =
interestRecalculationCompoundingType;
@@ -78,6 +80,7 @@ public class LoanProductInterestRecalculationData implements
Serializable {
this.preClosureInterestCalculationStrategy =
preCloseInterestCalculationStrategy;
this.isCompoundingToBePostedAsTransaction =
isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod;
+ this.disallowInterestCalculationOnPastDue =
disallowInterestCalculationOnPastDue;
}
public static LoanProductInterestRecalculationData
sensibleDefaultsForNewLoanProductCreation() {
@@ -101,12 +104,15 @@ public class LoanProductInterestRecalculationData
implements Serializable {
final EnumOptionData preCloseInterestCalculationStrategy =
preCloseInterestCalculationStrategy(
LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE);
final boolean allowCompoundingOnEod = false;
+ final boolean disallowInterestCalculationOnPastDue = false;
+
return new LoanProductInterestRecalculationData(id, productId,
interestRecalculationCompoundingType, rescheduleStrategyType,
recalculationRestFrequencyType,
recalculationRestFrequencyInterval, recalculationRestFrequencyNthDay,
recalculationRestFrequencyWeekday,
recalculationRestFrequencyOnDay, recalculationCompoundingFrequencyType,
recalculationCompoundingFrequencyInterval,
recalculationCompoundingFrequencyNthDay,
recalculationCompoundingFrequencyWeekday,
recalculationCompoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule,
- isCompoundingToBePostedAsTransaction,
preCloseInterestCalculationStrategy, allowCompoundingOnEod);
+ isCompoundingToBePostedAsTransaction,
preCloseInterestCalculationStrategy, allowCompoundingOnEod,
+ disallowInterestCalculationOnPastDue);
}
public boolean isArrearsBasedOnOriginalSchedule() {
@@ -124,4 +130,8 @@ public class LoanProductInterestRecalculationData
implements Serializable {
public boolean isIsCompoundingToBePostedAsTransaction() {
return isCompoundingToBePostedAsTransaction;
}
+
+ public Boolean disallowInterestCalculationOnPastDue() {
+ return disallowInterestCalculationOnPastDue;
+ }
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
index b46c1bae5..ecca9c1dd 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
@@ -96,6 +96,9 @@ public class LoanProductInterestRecalculationDetails extends
AbstractPersistable
@Column(name = "allow_compounding_on_eod")
private Boolean allowCompoundingOnEod;
+ @Column(name = "disallow_interest_calc_on_past_due")
+ private Boolean disallowInterestCalculationOnPastDue;
+
protected LoanProductInterestRecalculationDetails() {
//
}
@@ -162,11 +165,14 @@ public class LoanProductInterestRecalculationDetails
extends AbstractPersistable
final boolean isCompoundingToBePostedAsTransaction = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName);
+ final boolean disallowInterestCalculationOnPastDue = command
+
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.disallowInterestCalculationOnPastDueParamName);
+
return new
LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod,
loanRescheduleStrategyMethod,
recurrenceFrequency, recurrenceInterval, recurrenceOnNthDay,
recurrenceOnDay, recurrenceOnWeekday,
compoundingRecurrenceFrequency, compoundingInterval,
compoundingRecurrenceOnNthDay, compoundingRecurrenceOnDay,
compoundingRecurrenceOnWeekday,
isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy,
- isCompoundingToBePostedAsTransaction, allowCompoundingOnEod);
+ isCompoundingToBePostedAsTransaction, allowCompoundingOnEod,
disallowInterestCalculationOnPastDue);
}
private LoanProductInterestRecalculationDetails(final Integer
interestRecalculationCompoundingMethod,
@@ -175,7 +181,8 @@ public class LoanProductInterestRecalculationDetails
extends AbstractPersistable
Integer compoundingFrequencyType, Integer compoundingInterval,
final Integer compoundingFrequencyNthDay,
final Integer compoundingFrequencyOnDay, final Integer
compoundingFrequencyWeekday,
final boolean isArrearsBasedOnOriginalSchedule, final Integer
preCloseInterestCalculationStrategy,
- final boolean isCompoundingToBePostedAsTransaction, final boolean
allowCompoundingOnEod) {
+ final boolean isCompoundingToBePostedAsTransaction, final boolean
allowCompoundingOnEod,
+ final boolean disallowInterestCalculationOnPastDue) {
this.interestRecalculationCompoundingMethod =
interestRecalculationCompoundingMethod;
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.restFrequencyType = restFrequencyType;
@@ -192,6 +199,7 @@ public class LoanProductInterestRecalculationDetails
extends AbstractPersistable
this.preClosureInterestCalculationStrategy =
preCloseInterestCalculationStrategy;
this.isCompoundingToBePostedAsTransaction =
isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod;
+ this.disallowInterestCalculationOnPastDue =
disallowInterestCalculationOnPastDue;
}
public void updateProduct(final LoanProduct loanProduct) {
@@ -406,6 +414,13 @@ public class LoanProductInterestRecalculationDetails
extends AbstractPersistable
this.isCompoundingToBePostedAsTransaction = newValue;
}
+ if
(command.isChangeInBooleanParameterNamed(LoanProductConstants.disallowInterestCalculationOnPastDueParamName,
+ this.disallowInterestCalculationOnPastDue)) {
+ final boolean newValue = command
+
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.disallowInterestCalculationOnPastDueParamName);
+
actualChanges.put(LoanProductConstants.disallowInterestCalculationOnPastDueParamName,
newValue);
+ this.disallowInterestCalculationOnPastDue = newValue;
+ }
}
public RecalculationFrequencyType getRestFrequencyType() {
@@ -463,4 +478,8 @@ public class LoanProductInterestRecalculationDetails
extends AbstractPersistable
public Boolean allowCompoundingOnEod() {
return this.allowCompoundingOnEod;
}
+
+ public Boolean disallowInterestCalculationOnPastDue() {
+ return disallowInterestCalculationOnPastDue;
+ }
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index dc35c20c3..8c180124c 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -982,6 +982,10 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
private void adjustOverduePrincipalForInstallment(LocalDate currentDate,
LoanRepaymentScheduleInstallment currentInstallment,
Money overduePrincipal, Money aggregatedOverDuePrincipal,
ProgressiveTransactionCtx ctx) {
+ if
(currentInstallment.getLoan().getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue())
{
+ return;
+ }
+
LocalDate fromDate = currentInstallment.getFromDate();
LocalDate toDate = currentInstallment.getDueDate();
boolean hasUpdate = false;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
index cc0c401f3..e19e25f75 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
@@ -36,8 +36,11 @@ public class LoanInterestRecalculationCOBBusinessStep
implements LoanCOBBusiness
@Override
public Loan execute(Loan loan) {
if (!loan.isInterestBearing() || !loan.getStatus().isActive() ||
loan.isNpa() || loan.isChargedOff()
- || !loan.isInterestRecalculationEnabledForProduct()) {
- log.debug("Skip processing loan interest recalculation [{}] -
reason: not interest bearing loan or not active.", loan.getId());
+ || !loan.isInterestRecalculationEnabledForProduct()
+ ||
loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue())
{
+ log.debug(
+ "Skip processing loan interest recalculation [{}] -
Possible reasons: Loan is not an interest bearing loan, Loan is not active,
Interest recalculation on past due is disabled on this loan",
+ loan.getId());
return loan;
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 652fe67f0..c88e42e56 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -694,6 +694,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
+ " lir.compounding_frequency_on_day as
compoundingFrequencyOnDay, "
+ " lir.is_compounding_to_be_posted_as_transaction as
isCompoundingToBePostedAsTransaction, "
+ " lir.allow_compounding_on_eod as allowCompoundingOnEod,
"
+ + " lir.disallow_interest_calc_on_past_due as
disallowInterestCalculationOnPastDue, "
+ " l.is_floating_interest_rate as isFloatingInterestRate,
"
+ " l.interest_rate_differential as
interestRateDifferential, "
+ " l.create_standing_instruction_at_disbursement as
createStandingInstructionAtDisbursement, "
@@ -1037,11 +1038,12 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
final Boolean isCompoundingToBePostedAsTransaction =
rs.getBoolean("isCompoundingToBePostedAsTransaction");
final Boolean allowCompoundingOnEod =
rs.getBoolean("allowCompoundingOnEod");
+ final Boolean disallowInterestCalculationOnPastDue =
rs.getBoolean("disallowInterestCalculationOnPastDue");
interestRecalculationData = new
LoanInterestRecalculationData(lprId, productId,
interestRecalculationCompoundingType,
rescheduleStrategyType, calendarData,
restFrequencyType, restFrequencyInterval, restFrequencyNthDayEnum,
restFrequencyWeekDayEnum, restFrequencyOnDay,
compoundingCalendarData, compoundingFrequencyType,
compoundingInterval, compoundingFrequencyNthDayEnum,
compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay,
- isCompoundingToBePostedAsTransaction,
allowCompoundingOnEod);
+ isCompoundingToBePostedAsTransaction,
allowCompoundingOnEod, disallowInterestCalculationOnPastDue);
}
final boolean canUseForTopup = rs.getBoolean("canUseForTopup");
@@ -2039,42 +2041,49 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
@Override
public Collection<Long> fetchLoansForInterestRecalculation() {
- StringBuilder sqlBuilder = new StringBuilder();
- sqlBuilder.append("SELECT ml.id FROM m_loan ml ");
- sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on
mr.loan_id = ml.id ");
- sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on
dd.loan_id=ml.id and dd.disbursedon_date is null ");
- // For Floating rate changes
- sqlBuilder.append(
- " left join m_product_loan_floating_rates pfr on ml.product_id
= pfr.loan_product_id and ml.is_floating_interest_rate = true");
- sqlBuilder.append(" left join m_floating_rates fr on
pfr.floating_rates_id = fr.id");
- sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id =
frp.floating_rates_id ");
- sqlBuilder.append(" left join m_loan_reschedule_request lrr on
lrr.loan_id = ml.id");
- // this is to identify the applicable rates when base rate is changed
- sqlBuilder.append(" left join m_floating_rates bfr on
bfr.is_base_lending_rate = true");
- sqlBuilder.append(" left join m_floating_rates_periods bfrp on
bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?");
- sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
- sqlBuilder.append(" and ml.is_npa = false and ml.is_charged_off =
false and dd.is_reversed = false ");
- sqlBuilder.append(" and ((");
- sqlBuilder.append("ml.interest_recalculation_enabled = true ");
- sqlBuilder.append(" and (ml.interest_recalcualated_on is null or
ml.interest_recalcualated_on <> ?)");
- sqlBuilder.append(" and ((");
- sqlBuilder.append(" mr.completed_derived is false ");
- sqlBuilder.append(" and mr.duedate < ? )");
- sqlBuilder.append(" or dd.expected_disburse_date < ? )) ");
- sqlBuilder.append(" or (");
- sqlBuilder.append(" fr.is_active = true and frp.is_active = true");
- sqlBuilder.append(" and (frp.created_date >= ? or ");
- sqlBuilder
- .append("(bfrp.id is not null and
frp.is_differential_to_base_lending_rate = true and frp.from_date >=
bfrp.from_date)) ");
- sqlBuilder.append("and lrr.loan_id is null");
- sqlBuilder.append(" ))");
- sqlBuilder.append(" group by ml.id");
+ final String sql = """
+ SELECT l.id
+ FROM m_loan l
+ INNER JOIN m_loan_repayment_schedule mr ON mr.loan_id = l.id
+ LEFT JOIN m_loan_disbursement_detail dd ON dd.loan_id=l.id AND
dd.disbursedon_date IS NULL
+ -- for past due interest recalculation
+ LEFT JOIN m_loan_recalculation_details rcd ON rcd.loan_id =
l.id
+ -- For Floating rate changes
+ LEFT JOIN m_product_loan_floating_rates pfr
+ ON l.product_id = pfr.loan_product_id AND
l.is_floating_interest_rate = TRUE
+ LEFT JOIN m_floating_rates fr ON pfr.floating_rates_id = fr.id
+ LEFT JOIN m_floating_rates_periods frp ON fr.id =
frp.floating_rates_id
+ LEFT JOIN m_loan_reschedule_request lrr ON lrr.loan_id = l.id
+ -- this is to identify the applicable rates when base rate is
changed
+ LEFT JOIN m_floating_rates bfr ON bfr.is_base_lending_rate =
TRUE
+ LEFT JOIN m_floating_rates_periods bfrp ON bfr.id =
bfrp.floating_rates_id AND bfrp.created_date >= ?
+ WHERE l.loan_status_id = ?
+ AND l.is_npa = FALSE
+ AND l.is_charged_off = FALSE
+ AND dd.is_reversed = FALSE
+ AND (
+ (l.interest_recalculation_enabled = TRUE
+ AND (l.interest_recalcualated_on IS NULL OR
l.interest_recalcualated_on <> ?)
+ AND ((mr.completed_derived IS FALSE AND mr.duedate
< ?) OR dd.expected_disburse_date < ?)
+ AND rcd.disallow_interest_calc_on_past_due = FALSE)
+ OR
+ -- float rate changes
+ (fr.is_active = TRUE
+ AND frp.is_active = TRUE
+ AND (frp.created_date >= ?
+ OR (bfrp.id IS NOT NULL
+ AND
frp.is_differential_to_base_lending_rate = TRUE
+ AND frp.from_date >= bfrp.from_date))
+ AND lrr.loan_id IS NULL)
+ )
+ GROUP BY l.id
+ """;
try {
LocalDate currentdate = DateUtils.getBusinessLocalDate();
// will look only for yesterday modified rates
LocalDate yesterday =
DateUtils.getBusinessLocalDate().minusDays(1);
- return this.jdbcTemplate.queryForList(sqlBuilder.toString(),
Long.class, yesterday, LoanStatus.ACTIVE.getValue(), currentdate,
- currentdate, currentdate, yesterday);
+ return this.jdbcTemplate.queryForList(sql, Long.class, yesterday,
LoanStatus.ACTIVE.getValue(), currentdate, currentdate,
+ currentdate, yesterday);
} catch (final EmptyResultDataAccessException e) {
return null;
}
@@ -2085,45 +2094,50 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
LocalDate currentdate = DateUtils.getBusinessLocalDate();
// will look only for yesterday modified rates
LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1);
- StringBuilder sqlBuilder = new StringBuilder();
- sqlBuilder.append("SELECT ml.id FROM m_loan ml ");
- sqlBuilder.append(" left join m_client mc on mc.id = ml.client_id ");
- sqlBuilder.append(" left join m_office o on mc.office_id = o.id ");
- sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on
mr.loan_id = ml.id ");
- sqlBuilder.append(
- " LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id
and dd.disbursedon_date is null and dd.is_reversed = false ");
- // For Floating rate changes
- sqlBuilder.append(
- " left join m_product_loan_floating_rates pfr on ml.product_id
= pfr.loan_product_id and ml.is_floating_interest_rate = true");
- sqlBuilder.append(" left join m_floating_rates fr on
pfr.floating_rates_id = fr.id");
- sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id =
frp.floating_rates_id ");
- sqlBuilder.append(" left join m_loan_reschedule_request lrr on
lrr.loan_id = ml.id");
- // this is to identify the applicable rates when base rate is changed
- sqlBuilder.append(" left join m_floating_rates bfr on
bfr.is_base_lending_rate = true");
- sqlBuilder.append(" left join m_floating_rates_periods bfrp on
bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?");
- sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
- sqlBuilder.append(" and ml.is_npa = false and ml.is_charged_off =
false ");
- sqlBuilder.append(" and ((");
- sqlBuilder.append("ml.interest_recalculation_enabled = true ");
- sqlBuilder.append(" and (ml.interest_recalcualated_on is null or
ml.interest_recalcualated_on <> ? )");
- sqlBuilder.append(" and ((");
- sqlBuilder.append(" mr.completed_derived is false ");
- sqlBuilder.append(" and mr.duedate < ? )");
- sqlBuilder.append(" or dd.expected_disburse_date < ? )) ");
- sqlBuilder.append(" or (");
- sqlBuilder.append(" fr.is_active = true and frp.is_active = true");
- sqlBuilder.append(" and (frp.created_date >= ? or ");
- sqlBuilder
- .append("(bfrp.id is not null and
frp.is_differential_to_base_lending_rate = true and frp.from_date >=
bfrp.from_date)) ");
- sqlBuilder.append("and lrr.loan_id is null");
- sqlBuilder.append(" ))");
- sqlBuilder.append(" and ml.id >= ? and o.hierarchy like ? ");
- sqlBuilder.append(" group by ml.id ");
- sqlBuilder.append(" limit ? ");
+ final String sql = """
+ SELECT l.id
+ FROM m_loan l
+ LEFT JOIN m_client c ON c.id = l.client_id
+ LEFT JOIN m_office o ON c.office_id = o.id
+ INNER JOIN m_loan_repayment_schedule rps ON rps.loan_id = l.id
+ LEFT JOIN m_loan_disbursement_detail dd
+ ON dd.loan_id=l.id AND dd.disbursedon_date IS NULL AND
dd.is_reversed = FALSE
+ -- for past due interest recalculation
+ LEFT JOIN m_loan_recalculation_details rcd ON rcd.loan_id =
l.id
+ -- For Floating rate changes
+ LEFT JOIN m_product_loan_floating_rates pfr
+ ON l.product_id = pfr.loan_product_id AND
l.is_floating_interest_rate = TRUE
+ LEFT JOIN m_floating_rates fr ON pfr.floating_rates_id = fr.id
+ LEFT JOIN m_floating_rates_periods frp ON fr.id =
frp.floating_rates_id
+ LEFT JOIN m_loan_reschedule_request lrr ON lrr.loan_id = l.id
+ -- this is to identify the applicable rates when base rate is
changed
+ LEFT JOIN m_floating_rates bfr ON bfr.is_base_lending_rate =
TRUE
+ LEFT JOIN m_floating_rates_periods bfrp ON bfr.id =
bfrp.floating_rates_id AND bfrp.created_date >= ?
+ WHERE l.loan_status_id = ?
+ AND l.is_npa = FALSE
+ AND l.is_charged_off = FALSE
+ AND (
+ (l.interest_recalculation_enabled = TRUE
+ AND (l.interest_recalcualated_on IS NULL OR
l.interest_recalcualated_on <> ?)
+ AND ((rps.completed_derived IS FALSE AND
rps.duedate < ?) OR dd.expected_disburse_date < ?)
+ AND rcd.disallow_interest_calc_on_past_due =
FALSE)
+ OR
+ (fr.is_active = TRUE
+ AND frp.is_active = TRUE
+ AND (frp.created_date >= ?
+ OR (bfrp.id IS NOT NULL
+ AND
frp.is_differential_to_base_lending_rate = TRUE
+ AND frp.from_date >= bfrp.from_date))
+ AND lrr.loan_id IS NULL)
+ )
+ AND l.id >= ?
+ AND o.hierarchy like ?
+ GROUP BY l.id
+ LIMIT ?
+ """;
try {
- return Collections.synchronizedList(
- this.jdbcTemplate.queryForList(sqlBuilder.toString(),
Long.class, yesterday, LoanStatus.ACTIVE.getValue(), currentdate,
- currentdate, currentdate, yesterday,
maxLoanIdInList, officeHierarchy, pageSize));
+ return
Collections.synchronizedList(this.jdbcTemplate.queryForList(sql, Long.class,
yesterday, LoanStatus.ACTIVE.getValue(),
+ currentdate, currentdate, currentdate, yesterday,
maxLoanIdInList, officeHierarchy, pageSize));
} catch (final EmptyResultDataAccessException e) {
return null;
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 7ecc81e6f..51813bb31 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -174,7 +174,8 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationRestFrequencyWeekdayParamName,
LoanProductConstants.recalculationRestFrequencyNthDayParamName,
LoanProductConstants.recalculationRestFrequencyOnDayParamName,
LoanProductConstants.isCompoundingToBePostedAsTransactionParamName,
LoanProductConstants.allowCompoundingOnEodParamName,
- LoanProductConstants.CAN_USE_FOR_TOPUP,
LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM,
LoanProductConstants.RATES_PARAM_NAME,
+
LoanProductConstants.disallowInterestCalculationOnPastDueParamName,
LoanProductConstants.CAN_USE_FOR_TOPUP,
+ LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM,
LoanProductConstants.RATES_PARAM_NAME,
LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED,
LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE,
LoanProductConstants.OVER_APPLIED_NUMBER,
LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
@@ -1070,6 +1071,12 @@ public final class LoanProductDataValidator {
if
(fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE,
element)) {
loanScheduleType =
fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE,
element);
}
+ if
(LoanScheduleType.CUMULATIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
+ &&
fromApiJsonHelper.parameterExists(LoanProductConstants.disallowInterestCalculationOnPastDueParamName,
element)) {
+
baseDataValidator.reset().parameter(LoanProductConstants.disallowInterestCalculationOnPastDueParamName).failWithCode(
+
"disallow.interest.calculation.on.past.due.not.supported.for.loan.schedule.type.cumulative",
+ "Do not calculate interest on past due principal
balances is only supported for Progressive loan schedule type");
+ }
if
(LoanScheduleType.CUMULATIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
&&
LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.equals(loanRescheduleStrategyMethod))
{
baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).failWithCode(
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index feddcf502..1e88cffea 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -259,7 +259,9 @@ public class LoanProductReadPlatformServiceImpl implements
LoanProductReadPlatfo
+ "lpr.compounding_frequency_weekday_enum as
compoundingFrequencyWeekDayEnum, "
+ "lpr.compounding_frequency_on_day as
compoundingFrequencyOnDay, "
+ "lpr.is_compounding_to_be_posted_as_transaction as
isCompoundingToBePostedAsTransaction, "
- + "lpr.allow_compounding_on_eod as allowCompoundingOnEod,
" + "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+ + "lpr.allow_compounding_on_eod as allowCompoundingOnEod, "
+ + "lpr.disallow_interest_calc_on_past_due as
disallowInterestCalculationOnPastDue, "
+ + "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+ "lp.principal_threshold_for_last_installment as
principalThresholdForLastInstallment, "
+ "lp.fixed_principal_percentage_per_installment
fixedPrincipalPercentagePerInstallment, "
+ "lp.sync_expected_with_disbursement_date as
syncExpectedWithDisbursementDate, "
@@ -477,12 +479,14 @@ public class LoanProductReadPlatformServiceImpl
implements LoanProductReadPlatfo
final EnumOptionData preCloseInterestCalculationStrategy =
LoanEnumerations
.preCloseInterestCalculationStrategy(preCloseInterestCalculationStrategyEnumValue);
final boolean allowCompoundingOnEod =
rs.getBoolean("allowCompoundingOnEod");
+ final boolean disallowInterestCalculationOnPastDue =
rs.getBoolean("disallowInterestCalculationOnPastDue");
interestRecalculationData = new
LoanProductInterestRecalculationData(lprId, productId,
interestRecalculationCompoundingType,
rescheduleStrategyType, restFrequencyType,
restFrequencyInterval, restFrequencyNthDayEnum, restFrequencyWeekDayEnum,
restFrequencyOnDay, compoundingFrequencyType,
compoundingInterval, compoundingFrequencyNthDayEnum,
compoundingFrequencyWeekDayEnum,
compoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule,
- isCompoundingToBePostedAsTransaction,
preCloseInterestCalculationStrategy, allowCompoundingOnEod);
+ isCompoundingToBePostedAsTransaction,
preCloseInterestCalculationStrategy, allowCompoundingOnEod,
+ disallowInterestCalculationOnPastDue);
}
final boolean amortization = rs.getBoolean("amortizationBoolean");
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index af723c51a..b01cf4d54 100644
---
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -174,4 +174,5 @@
<include
file="parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml"
relativeToChangelogFile="true" />
<include file="parts/0154_add_interest_refund_to_r_enum_value.xml"
relativeToChangelogFile="true" />
<include
file="parts/0155_add_configuration_enable_immediate_charge_accrual_post_maturity.xml"
relativeToChangelogFile="true" />
+ <include
file="parts/0156_add_disallow_interest_calc_on_past_due_field.xml"
relativeToChangelogFile="true" />
</databaseChangeLog>
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0156_add_disallow_interest_calc_on_past_due_field.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0156_add_disallow_interest_calc_on_past_due_field.xml
new file mode 100644
index 000000000..c078794d2
--- /dev/null
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0156_add_disallow_interest_calc_on_past_due_field.xml
@@ -0,0 +1,39 @@
+<?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
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<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.3.xsd">
+
+ <changeSet id="1" author="fineract">
+ <addColumn tableName="m_product_loan_recalculation_details">
+ <column name="disallow_interest_calc_on_past_due"
defaultValueBoolean="false" type="boolean">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+
+ <addColumn tableName="m_loan_recalculation_details">
+ <column name="disallow_interest_calc_on_past_due"
defaultValueBoolean="false" type="boolean">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+</databaseChangeLog>
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
index 63395b1b9..c87f5bac6 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
@@ -69,7 +69,7 @@ public class LoanInterestRecalculationCOBTest extends
BaseLoanIntegrationTest {
Utils.initializeRESTAssured();
requestSpec = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
- requestSpec.header("Fineract-Platform-TenantId", "default");
+ requestSpec.header("Fineract-Platform-TenantId", Utils.DEFAULT_TENANT);
responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
loanTransactionHelper = new LoanTransactionHelper(requestSpec,
responseSpec);
schedulerJobHelper = new SchedulerJobHelper(requestSpec);
@@ -1222,4 +1222,68 @@ public class LoanInterestRecalculationCOBTest extends
BaseLoanIntegrationTest {
payoffOnDateAndVerifyStatus("1 May 2023", loanIdRef.get());
}
+ @Test
+ public void
verifyEarlyLateRepaymentOnProgressiveLoanThePastDueHasNoEffect() {
+ AtomicReference<Long> loanIdRef = new AtomicReference<>();
+ runAt("1 January 2024", () -> {
+ PostLoanProductsResponse loanProduct = loanProductHelper //
+ .createLoanProduct(create4IProgressive() //
+
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
+ .disallowInterestCalculationOnPastDue(true) //
+ );
+
+ Long loanId = applyAndApproveProgressiveLoan(client.getClientId(),
loanProduct.getResourceId(), "1 January 2024", 100.0, 7.0, 6,
+ null);
+ loanIdRef.set(loanId);
+
+ disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
+ logLoanDetails(loanDetails);
+
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 1, "01 February
2024", 16.43, 0.0, 0.0, 0.58);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 2, "01 March
2024", 16.52, 0.0, 0.0, 0.49);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April
2024", 16.62, 0.0, 0.0, 0.39);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2024",
16.72, 0.0, 0.0, 0.29);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 5, "01 June 2024",
16.81, 0.0, 0.0, 0.20);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 6, "01 July 2024",
16.90, 0.0, 0.0, 0.10);
+
+ });
+ runAt("15 February 2024", () -> { // we have past due and should not
count extra interest
+ Long loanId = loanIdRef.get();
+
+ inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
+ loanTransactionHelper.makeLoanRepayment("15 February 2024", 15.0F,
loanId.intValue());
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
+ logLoanDetails(loanDetails);
+
+ validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1),
16.43, 15.0, 1.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.58, 0,
+ 0.58, 0.0, 15.0);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 2, "01 March
2024", 16.52, 0.0, 0.0, 0.49);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April
2024", 16.62, 0.0, 0.0, 0.39);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2024",
16.72, 0.0, 0.0, 0.29);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 5, "01 June 2024",
16.81, 0.0, 0.0, 0.20);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 6, "01 July 2024",
16.90, 0.0, 0.0, 0.10);
+ });
+ runAt("15 February 2024", () -> { // we turn from past due into early
repayment and should have less interest
+ Long loanId = loanIdRef.get();
+
+ inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
+ loanTransactionHelper.makeLoanRepayment("15 February 2024",
19.02F, loanId.intValue());
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
+ logLoanDetails(loanDetails);
+
+ validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1),
16.43, 16.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.58, 0.58,
+ 0.0, 0.0, 17.01);
+ validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 3, 1),
16.77, 16.77, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.24, 0.24,
+ 0.0, 17.01, 0.0);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April
2024", 16.42, 0.0, 0.0, 0.59);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2024",
16.72, 0.0, 0.0, 0.29);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 5, "01 June 2024",
16.81, 0.0, 0.0, 0.20);
+ validateFullyUnpaidRepaymentPeriod(loanDetails, 6, "01 July 2024",
16.85, 0.0, 0.0, 0.10);
+ });
+ }
+
}