FINERACT-1981: Introduce Interest should not be calculated on past due principal amount

This commit is contained in:
Janos Meszaros
2024-11-29 11:59:18 +01:00
committed by Adam Saghy
parent 3858611190
commit 4cb849fa17
17 changed files with 287 additions and 85 deletions
@@ -146,6 +146,14 @@
"null", "null",
"boolean" "boolean"
] ]
},
{
"default": null,
"name": "disallowInterestCalculationOnPastDue",
"type": [
"null",
"boolean"
]
} }
] ]
} }
@@ -146,6 +146,14 @@
"null", "null",
"boolean" "boolean"
] ]
},
{
"default": null,
"name": "disallowInterestCalculationOnPastDue",
"type": [
"null",
"boolean"
]
} }
] ]
} }
@@ -47,6 +47,7 @@ public class LoanInterestRecalculationData {
private Boolean isCompoundingToBePostedAsTransaction; private Boolean isCompoundingToBePostedAsTransaction;
private CalendarData compoundingCalendarData; private CalendarData compoundingCalendarData;
private Boolean allowCompoundingOnEod; private Boolean allowCompoundingOnEod;
private Boolean disallowInterestCalculationOnPastDue;
public LoanInterestRecalculationData(final Long id, final Long loanId, final EnumOptionData interestRecalculationCompoundingType, public LoanInterestRecalculationData(final Long id, final Long loanId, final EnumOptionData interestRecalculationCompoundingType,
final EnumOptionData rescheduleStrategyType, final CalendarData calendarData, final EnumOptionData rescheduleStrategyType, final CalendarData calendarData,
@@ -56,7 +57,7 @@ public class LoanInterestRecalculationData {
final EnumOptionData recalculationCompoundingFrequencyType, final Integer recalculationCompoundingFrequencyInterval, final EnumOptionData recalculationCompoundingFrequencyType, final Integer recalculationCompoundingFrequencyInterval,
final EnumOptionData recalculationCompoundingFrequencyNthDay, final EnumOptionData recalculationCompoundingFrequencyWeekday, final EnumOptionData recalculationCompoundingFrequencyNthDay, final EnumOptionData recalculationCompoundingFrequencyWeekday,
final Integer recalculationCompoundingFrequencyOnDay, final Boolean isCompoundingToBePostedAsTransaction, final Integer recalculationCompoundingFrequencyOnDay, final Boolean isCompoundingToBePostedAsTransaction,
final Boolean allowCompoundingOnEod) { final Boolean allowCompoundingOnEod, final Boolean disallowInterestCalculationOnPastDue) {
this.id = id; this.id = id;
this.loanId = loanId; this.loanId = loanId;
this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
@@ -75,6 +76,7 @@ public class LoanInterestRecalculationData {
this.compoundingCalendarData = compoundingCalendarData; this.compoundingCalendarData = compoundingCalendarData;
this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod; this.allowCompoundingOnEod = allowCompoundingOnEod;
this.disallowInterestCalculationOnPastDue = disallowInterestCalculationOnPastDue;
} }
public LoanInterestRecalculationData withCalendarData(final CalendarData calendarData, CalendarData compoundingCalendarData) { public LoanInterestRecalculationData withCalendarData(final CalendarData calendarData, CalendarData compoundingCalendarData) {
@@ -88,6 +88,9 @@ public class LoanInterestRecalculationDetails extends AbstractPersistableCustom<
@Column(name = "allow_compounding_on_eod") @Column(name = "allow_compounding_on_eod")
private Boolean allowCompoundingOnEod; private Boolean allowCompoundingOnEod;
@Column(name = "disallow_interest_calc_on_past_due")
private Boolean disallowInterestCalculationOnPastDue;
protected LoanInterestRecalculationDetails() { protected LoanInterestRecalculationDetails() {
// Default constructor for jpa repository // 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, final Integer restFrequencyType, final Integer restInterval, final Integer restFrequencyNthDay, Integer restFrequencyWeekday,
Integer restFrequencyOnDay, Integer compoundingFrequencyType, Integer compoundingInterval, Integer compoundingFrequencyNthDay, Integer restFrequencyOnDay, Integer compoundingFrequencyType, Integer compoundingInterval, Integer compoundingFrequencyNthDay,
Integer compoundingFrequencyWeekday, Integer compoundingFrequencyOnDay, final boolean isCompoundingToBePostedAsTransaction, Integer compoundingFrequencyWeekday, Integer compoundingFrequencyOnDay, final boolean isCompoundingToBePostedAsTransaction,
final boolean allowCompoundingOnEod) { final boolean allowCompoundingOnEod, final boolean disallowInterestCalculationOnPastDue) {
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.restFrequencyNthDay = restFrequencyNthDay; this.restFrequencyNthDay = restFrequencyNthDay;
@@ -111,6 +114,7 @@ public class LoanInterestRecalculationDetails extends AbstractPersistableCustom<
this.compoundingInterval = compoundingInterval; this.compoundingInterval = compoundingInterval;
this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod; this.allowCompoundingOnEod = allowCompoundingOnEod;
this.disallowInterestCalculationOnPastDue = disallowInterestCalculationOnPastDue;
} }
public static LoanInterestRecalculationDetails createFrom( public static LoanInterestRecalculationDetails createFrom(
@@ -127,7 +131,8 @@ public class LoanInterestRecalculationDetails extends AbstractPersistableCustom<
loanProductInterestRecalculationDetails.getCompoundingFrequencyWeekday(), loanProductInterestRecalculationDetails.getCompoundingFrequencyWeekday(),
loanProductInterestRecalculationDetails.getCompoundingFrequencyOnDay(), loanProductInterestRecalculationDetails.getCompoundingFrequencyOnDay(),
loanProductInterestRecalculationDetails.getIsCompoundingToBePostedAsTransaction(), loanProductInterestRecalculationDetails.getIsCompoundingToBePostedAsTransaction(),
loanProductInterestRecalculationDetails.allowCompoundingOnEod()); loanProductInterestRecalculationDetails.allowCompoundingOnEod(),
loanProductInterestRecalculationDetails.disallowInterestCalculationOnPastDue());
} }
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
@@ -189,4 +194,8 @@ public class LoanInterestRecalculationDetails extends AbstractPersistableCustom<
public boolean allowCompoundingOnEod() { public boolean allowCompoundingOnEod() {
return this.allowCompoundingOnEod; return this.allowCompoundingOnEod;
} }
public Boolean disallowInterestCalculationOnPastDue() {
return disallowInterestCalculationOnPastDue;
}
} }
@@ -97,6 +97,7 @@ public interface LoanProductConstants {
String recalculationCompoundingFrequencyNthDayParamName = "recalculationCompoundingFrequencyNthDayType"; String recalculationCompoundingFrequencyNthDayParamName = "recalculationCompoundingFrequencyNthDayType";
String recalculationCompoundingFrequencyOnDayParamName = "recalculationCompoundingFrequencyOnDayType"; String recalculationCompoundingFrequencyOnDayParamName = "recalculationCompoundingFrequencyOnDayType";
String isCompoundingToBePostedAsTransactionParamName = "isCompoundingToBePostedAsTransaction"; String isCompoundingToBePostedAsTransactionParamName = "isCompoundingToBePostedAsTransaction";
String disallowInterestCalculationOnPastDueParamName = "disallowInterestCalculationOnPastDue";
// Guarantee related // Guarantee related
String holdGuaranteeFundsParamName = "holdGuaranteeFunds"; String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
@@ -198,6 +198,8 @@ final class LoanProductsApiResourceSwagger {
public Boolean isCompoundingToBePostedAsTransaction; public Boolean isCompoundingToBePostedAsTransaction;
@Schema(example = "false") @Schema(example = "false")
public Boolean allowCompoundingOnEod; public Boolean allowCompoundingOnEod;
@Schema(example = "false")
public Boolean disallowInterestCalculationOnPastDue;
// Accounting // Accounting
@Schema(example = "3") @Schema(example = "3")
@@ -520,6 +522,8 @@ final class LoanProductsApiResourceSwagger {
public GetLoanProductsPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy; public GetLoanProductsPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy;
@Schema(example = "true") @Schema(example = "true")
public Boolean isArrearsBasedOnOriginalSchedule; public Boolean isArrearsBasedOnOriginalSchedule;
@Schema(example = "false")
public Boolean disallowInterestCalculationOnPastDue;
@Schema(example = "true") @Schema(example = "true")
public Boolean isCompoundingToBePostedAsTransaction; public Boolean isCompoundingToBePostedAsTransaction;
@Schema(example = "true") @Schema(example = "true")
@@ -1182,7 +1182,7 @@ public class LoanProductData implements Serializable {
getInterestRecalculationRestOnDayType(), compoundingCalendarData, getRecalculationCompoundingFrequencyType(), getInterestRecalculationRestOnDayType(), compoundingCalendarData, getRecalculationCompoundingFrequencyType(),
getRecalculationCompoundingFrequencyInterval(), getInterestRecalculationCompoundingNthDayType(), getRecalculationCompoundingFrequencyInterval(), getInterestRecalculationCompoundingNthDayType(),
getInterestRecalculationCompoundingWeekDayType(), getInterestRecalculationCompoundingOnDayType(), getInterestRecalculationCompoundingWeekDayType(), getInterestRecalculationCompoundingOnDayType(),
isCompoundingToBePostedAsTransaction(), allowCompoundingOnEod()); isCompoundingToBePostedAsTransaction(), allowCompoundingOnEod(), disallowInterestCalculationOnPastDue());
} }
private EnumOptionData getRescheduleStrategyType() { private EnumOptionData getRescheduleStrategyType() {
@@ -1279,6 +1279,11 @@ public class LoanProductData implements Serializable {
return isInterestRecalculationEnabled() ? this.interestRecalculationData.isAllowCompoundingOnEod() : null; 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) { public void setLoanProductConfigurableAttributes(LoanProductConfigurableAttributes loanProductConfigurableAttributes) {
this.allowAttributeOverrides = loanProductConfigurableAttributes; this.allowAttributeOverrides = loanProductConfigurableAttributes;
} }
@@ -50,6 +50,7 @@ public class LoanProductInterestRecalculationData implements Serializable {
private final boolean isCompoundingToBePostedAsTransaction; private final boolean isCompoundingToBePostedAsTransaction;
private final EnumOptionData preClosureInterestCalculationStrategy; private final EnumOptionData preClosureInterestCalculationStrategy;
private final boolean allowCompoundingOnEod; private final boolean allowCompoundingOnEod;
private final Boolean disallowInterestCalculationOnPastDue;
public LoanProductInterestRecalculationData(final Long id, final Long productId, public LoanProductInterestRecalculationData(final Long id, final Long productId,
final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType, final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType,
@@ -59,7 +60,8 @@ public class LoanProductInterestRecalculationData implements Serializable {
final Integer recalculationCompoundingFrequencyInterval, final EnumOptionData recalculationCompoundingFrequencyNthDay, final Integer recalculationCompoundingFrequencyInterval, final EnumOptionData recalculationCompoundingFrequencyNthDay,
final EnumOptionData recalculationCompoundingFrequencyWeekday, final Integer recalculationCompoundingFrequencyOnDay, final EnumOptionData recalculationCompoundingFrequencyWeekday, final Integer recalculationCompoundingFrequencyOnDay,
final boolean isArrearsBasedOnOriginalSchedule, boolean isCompoundingToBePostedAsTransaction, final boolean isArrearsBasedOnOriginalSchedule, boolean isCompoundingToBePostedAsTransaction,
final EnumOptionData preCloseInterestCalculationStrategy, final boolean allowCompoundingOnEod) { final EnumOptionData preCloseInterestCalculationStrategy, final boolean allowCompoundingOnEod,
final Boolean disallowInterestCalculationOnPastDue) {
this.id = id; this.id = id;
this.productId = productId; this.productId = productId;
this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
@@ -78,6 +80,7 @@ public class LoanProductInterestRecalculationData implements Serializable {
this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy; this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy;
this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod; this.allowCompoundingOnEod = allowCompoundingOnEod;
this.disallowInterestCalculationOnPastDue = disallowInterestCalculationOnPastDue;
} }
public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() { public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() {
@@ -101,12 +104,15 @@ public class LoanProductInterestRecalculationData implements Serializable {
final EnumOptionData preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy( final EnumOptionData preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy(
LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE); LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE);
final boolean allowCompoundingOnEod = false; final boolean allowCompoundingOnEod = false;
final boolean disallowInterestCalculationOnPastDue = false;
return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType, return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType,
recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyNthDay, recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyNthDay,
recalculationRestFrequencyWeekday, recalculationRestFrequencyOnDay, recalculationCompoundingFrequencyType, recalculationRestFrequencyWeekday, recalculationRestFrequencyOnDay, recalculationCompoundingFrequencyType,
recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyNthDay, recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyNthDay,
recalculationCompoundingFrequencyWeekday, recalculationCompoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule, recalculationCompoundingFrequencyWeekday, recalculationCompoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule,
isCompoundingToBePostedAsTransaction, preCloseInterestCalculationStrategy, allowCompoundingOnEod); isCompoundingToBePostedAsTransaction, preCloseInterestCalculationStrategy, allowCompoundingOnEod,
disallowInterestCalculationOnPastDue);
} }
public boolean isArrearsBasedOnOriginalSchedule() { public boolean isArrearsBasedOnOriginalSchedule() {
@@ -124,4 +130,8 @@ public class LoanProductInterestRecalculationData implements Serializable {
public boolean isIsCompoundingToBePostedAsTransaction() { public boolean isIsCompoundingToBePostedAsTransaction() {
return isCompoundingToBePostedAsTransaction; return isCompoundingToBePostedAsTransaction;
} }
public Boolean disallowInterestCalculationOnPastDue() {
return disallowInterestCalculationOnPastDue;
}
} }
@@ -96,6 +96,9 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
@Column(name = "allow_compounding_on_eod") @Column(name = "allow_compounding_on_eod")
private Boolean allowCompoundingOnEod; private Boolean allowCompoundingOnEod;
@Column(name = "disallow_interest_calc_on_past_due")
private Boolean disallowInterestCalculationOnPastDue;
protected LoanProductInterestRecalculationDetails() { protected LoanProductInterestRecalculationDetails() {
// //
} }
@@ -162,11 +165,14 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
final boolean isCompoundingToBePostedAsTransaction = command final boolean isCompoundingToBePostedAsTransaction = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName); .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName);
final boolean disallowInterestCalculationOnPastDue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.disallowInterestCalculationOnPastDueParamName);
return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod, return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod,
recurrenceFrequency, recurrenceInterval, recurrenceOnNthDay, recurrenceOnDay, recurrenceOnWeekday, recurrenceFrequency, recurrenceInterval, recurrenceOnNthDay, recurrenceOnDay, recurrenceOnWeekday,
compoundingRecurrenceFrequency, compoundingInterval, compoundingRecurrenceOnNthDay, compoundingRecurrenceOnDay, compoundingRecurrenceFrequency, compoundingInterval, compoundingRecurrenceOnNthDay, compoundingRecurrenceOnDay,
compoundingRecurrenceOnWeekday, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy, compoundingRecurrenceOnWeekday, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy,
isCompoundingToBePostedAsTransaction, allowCompoundingOnEod); isCompoundingToBePostedAsTransaction, allowCompoundingOnEod, disallowInterestCalculationOnPastDue);
} }
private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod,
@@ -175,7 +181,8 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
Integer compoundingFrequencyType, Integer compoundingInterval, final Integer compoundingFrequencyNthDay, Integer compoundingFrequencyType, Integer compoundingInterval, final Integer compoundingFrequencyNthDay,
final Integer compoundingFrequencyOnDay, final Integer compoundingFrequencyWeekday, final Integer compoundingFrequencyOnDay, final Integer compoundingFrequencyWeekday,
final boolean isArrearsBasedOnOriginalSchedule, final Integer preCloseInterestCalculationStrategy, 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.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.restFrequencyType = restFrequencyType; this.restFrequencyType = restFrequencyType;
@@ -192,6 +199,7 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy; this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy;
this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction;
this.allowCompoundingOnEod = allowCompoundingOnEod; this.allowCompoundingOnEod = allowCompoundingOnEod;
this.disallowInterestCalculationOnPastDue = disallowInterestCalculationOnPastDue;
} }
public void updateProduct(final LoanProduct loanProduct) { public void updateProduct(final LoanProduct loanProduct) {
@@ -406,6 +414,13 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
this.isCompoundingToBePostedAsTransaction = newValue; 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() { public RecalculationFrequencyType getRestFrequencyType() {
@@ -463,4 +478,8 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable
public Boolean allowCompoundingOnEod() { public Boolean allowCompoundingOnEod() {
return this.allowCompoundingOnEod; return this.allowCompoundingOnEod;
} }
public Boolean disallowInterestCalculationOnPastDue() {
return disallowInterestCalculationOnPastDue;
}
} }
@@ -982,6 +982,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void adjustOverduePrincipalForInstallment(LocalDate currentDate, LoanRepaymentScheduleInstallment currentInstallment, private void adjustOverduePrincipalForInstallment(LocalDate currentDate, LoanRepaymentScheduleInstallment currentInstallment,
Money overduePrincipal, Money aggregatedOverDuePrincipal, ProgressiveTransactionCtx ctx) { Money overduePrincipal, Money aggregatedOverDuePrincipal, ProgressiveTransactionCtx ctx) {
if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue()) {
return;
}
LocalDate fromDate = currentInstallment.getFromDate(); LocalDate fromDate = currentInstallment.getFromDate();
LocalDate toDate = currentInstallment.getDueDate(); LocalDate toDate = currentInstallment.getDueDate();
boolean hasUpdate = false; boolean hasUpdate = false;
@@ -36,8 +36,11 @@ public class LoanInterestRecalculationCOBBusinessStep implements LoanCOBBusiness
@Override @Override
public Loan execute(Loan loan) { public Loan execute(Loan loan) {
if (!loan.isInterestBearing() || !loan.getStatus().isActive() || loan.isNpa() || loan.isChargedOff() if (!loan.isInterestBearing() || !loan.getStatus().isActive() || loan.isNpa() || loan.isChargedOff()
|| !loan.isInterestRecalculationEnabledForProduct()) { || !loan.isInterestRecalculationEnabledForProduct()
log.debug("Skip processing loan interest recalculation [{}] - reason: not interest bearing loan or not active.", loan.getId()); || 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; return loan;
} }
@@ -694,6 +694,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
+ " lir.compounding_frequency_on_day as compoundingFrequencyOnDay, " + " lir.compounding_frequency_on_day as compoundingFrequencyOnDay, "
+ " lir.is_compounding_to_be_posted_as_transaction as isCompoundingToBePostedAsTransaction, " + " lir.is_compounding_to_be_posted_as_transaction as isCompoundingToBePostedAsTransaction, "
+ " lir.allow_compounding_on_eod as allowCompoundingOnEod, " + " lir.allow_compounding_on_eod as allowCompoundingOnEod, "
+ " lir.disallow_interest_calc_on_past_due as disallowInterestCalculationOnPastDue, "
+ " l.is_floating_interest_rate as isFloatingInterestRate, " + " l.is_floating_interest_rate as isFloatingInterestRate, "
+ " l.interest_rate_differential as interestRateDifferential, " + " l.interest_rate_differential as interestRateDifferential, "
+ " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement, " + " 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 isCompoundingToBePostedAsTransaction = rs.getBoolean("isCompoundingToBePostedAsTransaction");
final Boolean allowCompoundingOnEod = rs.getBoolean("allowCompoundingOnEod"); final Boolean allowCompoundingOnEod = rs.getBoolean("allowCompoundingOnEod");
final Boolean disallowInterestCalculationOnPastDue = rs.getBoolean("disallowInterestCalculationOnPastDue");
interestRecalculationData = new LoanInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType, interestRecalculationData = new LoanInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType,
rescheduleStrategyType, calendarData, restFrequencyType, restFrequencyInterval, restFrequencyNthDayEnum, rescheduleStrategyType, calendarData, restFrequencyType, restFrequencyInterval, restFrequencyNthDayEnum,
restFrequencyWeekDayEnum, restFrequencyOnDay, compoundingCalendarData, compoundingFrequencyType, restFrequencyWeekDayEnum, restFrequencyOnDay, compoundingCalendarData, compoundingFrequencyType,
compoundingInterval, compoundingFrequencyNthDayEnum, compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay, compoundingInterval, compoundingFrequencyNthDayEnum, compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay,
isCompoundingToBePostedAsTransaction, allowCompoundingOnEod); isCompoundingToBePostedAsTransaction, allowCompoundingOnEod, disallowInterestCalculationOnPastDue);
} }
final boolean canUseForTopup = rs.getBoolean("canUseForTopup"); final boolean canUseForTopup = rs.getBoolean("canUseForTopup");
@@ -2039,42 +2041,49 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
@Override @Override
public Collection<Long> fetchLoansForInterestRecalculation() { public Collection<Long> fetchLoansForInterestRecalculation() {
StringBuilder sqlBuilder = new StringBuilder(); final String sql = """
sqlBuilder.append("SELECT ml.id FROM m_loan ml "); SELECT l.id
sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "); FROM m_loan l
sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id and dd.disbursedon_date is null "); INNER JOIN m_loan_repayment_schedule mr ON mr.loan_id = l.id
// For Floating rate changes LEFT JOIN m_loan_disbursement_detail dd ON dd.loan_id=l.id AND dd.disbursedon_date IS NULL
sqlBuilder.append( -- for past due interest recalculation
" left join m_product_loan_floating_rates pfr on ml.product_id = pfr.loan_product_id and ml.is_floating_interest_rate = true"); LEFT JOIN m_loan_recalculation_details rcd ON rcd.loan_id = l.id
sqlBuilder.append(" left join m_floating_rates fr on pfr.floating_rates_id = fr.id"); -- For Floating rate changes
sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id = frp.floating_rates_id "); LEFT JOIN m_product_loan_floating_rates pfr
sqlBuilder.append(" left join m_loan_reschedule_request lrr on lrr.loan_id = ml.id"); ON l.product_id = pfr.loan_product_id AND l.is_floating_interest_rate = TRUE
// this is to identify the applicable rates when base rate is changed LEFT JOIN m_floating_rates fr ON pfr.floating_rates_id = fr.id
sqlBuilder.append(" left join m_floating_rates bfr on bfr.is_base_lending_rate = true"); LEFT JOIN m_floating_rates_periods frp ON fr.id = frp.floating_rates_id
sqlBuilder.append(" left join m_floating_rates_periods bfrp on bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?"); LEFT JOIN m_loan_reschedule_request lrr ON lrr.loan_id = l.id
sqlBuilder.append(" WHERE ml.loan_status_id = ? "); -- this is to identify the applicable rates when base rate is changed
sqlBuilder.append(" and ml.is_npa = false and ml.is_charged_off = false and dd.is_reversed = false "); LEFT JOIN m_floating_rates bfr ON bfr.is_base_lending_rate = TRUE
sqlBuilder.append(" and (("); LEFT JOIN m_floating_rates_periods bfrp ON bfr.id = bfrp.floating_rates_id AND bfrp.created_date >= ?
sqlBuilder.append("ml.interest_recalculation_enabled = true "); WHERE l.loan_status_id = ?
sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)"); AND l.is_npa = FALSE
sqlBuilder.append(" and (("); AND l.is_charged_off = FALSE
sqlBuilder.append(" mr.completed_derived is false "); AND dd.is_reversed = FALSE
sqlBuilder.append(" and mr.duedate < ? )"); AND (
sqlBuilder.append(" or dd.expected_disburse_date < ? )) "); (l.interest_recalculation_enabled = TRUE
sqlBuilder.append(" or ("); AND (l.interest_recalcualated_on IS NULL OR l.interest_recalcualated_on <> ?)
sqlBuilder.append(" fr.is_active = true and frp.is_active = true"); AND ((mr.completed_derived IS FALSE AND mr.duedate < ?) OR dd.expected_disburse_date < ?)
sqlBuilder.append(" and (frp.created_date >= ? or "); AND rcd.disallow_interest_calc_on_past_due = FALSE)
sqlBuilder OR
.append("(bfrp.id is not null and frp.is_differential_to_base_lending_rate = true and frp.from_date >= bfrp.from_date)) "); -- float rate changes
sqlBuilder.append("and lrr.loan_id is null"); (fr.is_active = TRUE
sqlBuilder.append(" ))"); AND frp.is_active = TRUE
sqlBuilder.append(" group by ml.id"); 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 { try {
LocalDate currentdate = DateUtils.getBusinessLocalDate(); LocalDate currentdate = DateUtils.getBusinessLocalDate();
// will look only for yesterday modified rates // will look only for yesterday modified rates
LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1); LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1);
return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, yesterday, LoanStatus.ACTIVE.getValue(), currentdate, return this.jdbcTemplate.queryForList(sql, Long.class, yesterday, LoanStatus.ACTIVE.getValue(), currentdate, currentdate,
currentdate, currentdate, yesterday); currentdate, yesterday);
} catch (final EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
return null; return null;
} }
@@ -2085,45 +2094,50 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
LocalDate currentdate = DateUtils.getBusinessLocalDate(); LocalDate currentdate = DateUtils.getBusinessLocalDate();
// will look only for yesterday modified rates // will look only for yesterday modified rates
LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1); LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1);
StringBuilder sqlBuilder = new StringBuilder(); final String sql = """
sqlBuilder.append("SELECT ml.id FROM m_loan ml "); SELECT l.id
sqlBuilder.append(" left join m_client mc on mc.id = ml.client_id "); FROM m_loan l
sqlBuilder.append(" left join m_office o on mc.office_id = o.id "); LEFT JOIN m_client c ON c.id = l.client_id
sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "); LEFT JOIN m_office o ON c.office_id = o.id
sqlBuilder.append( INNER JOIN m_loan_repayment_schedule rps ON rps.loan_id = l.id
" LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id and dd.disbursedon_date is null and dd.is_reversed = false "); LEFT JOIN m_loan_disbursement_detail dd
// For Floating rate changes ON dd.loan_id=l.id AND dd.disbursedon_date IS NULL AND dd.is_reversed = FALSE
sqlBuilder.append( -- for past due interest recalculation
" left join m_product_loan_floating_rates pfr on ml.product_id = pfr.loan_product_id and ml.is_floating_interest_rate = true"); LEFT JOIN m_loan_recalculation_details rcd ON rcd.loan_id = l.id
sqlBuilder.append(" left join m_floating_rates fr on pfr.floating_rates_id = fr.id"); -- For Floating rate changes
sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id = frp.floating_rates_id "); LEFT JOIN m_product_loan_floating_rates pfr
sqlBuilder.append(" left join m_loan_reschedule_request lrr on lrr.loan_id = ml.id"); ON l.product_id = pfr.loan_product_id AND l.is_floating_interest_rate = TRUE
// this is to identify the applicable rates when base rate is changed LEFT JOIN m_floating_rates fr ON pfr.floating_rates_id = fr.id
sqlBuilder.append(" left join m_floating_rates bfr on bfr.is_base_lending_rate = true"); LEFT JOIN m_floating_rates_periods frp ON fr.id = frp.floating_rates_id
sqlBuilder.append(" left join m_floating_rates_periods bfrp on bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?"); LEFT JOIN m_loan_reschedule_request lrr ON lrr.loan_id = l.id
sqlBuilder.append(" WHERE ml.loan_status_id = ? "); -- this is to identify the applicable rates when base rate is changed
sqlBuilder.append(" and ml.is_npa = false and ml.is_charged_off = false "); LEFT JOIN m_floating_rates bfr ON bfr.is_base_lending_rate = TRUE
sqlBuilder.append(" and (("); LEFT JOIN m_floating_rates_periods bfrp ON bfr.id = bfrp.floating_rates_id AND bfrp.created_date >= ?
sqlBuilder.append("ml.interest_recalculation_enabled = true "); WHERE l.loan_status_id = ?
sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ? )"); AND l.is_npa = FALSE
sqlBuilder.append(" and (("); AND l.is_charged_off = FALSE
sqlBuilder.append(" mr.completed_derived is false "); AND (
sqlBuilder.append(" and mr.duedate < ? )"); (l.interest_recalculation_enabled = TRUE
sqlBuilder.append(" or dd.expected_disburse_date < ? )) "); AND (l.interest_recalcualated_on IS NULL OR l.interest_recalcualated_on <> ?)
sqlBuilder.append(" or ("); AND ((rps.completed_derived IS FALSE AND rps.duedate < ?) OR dd.expected_disburse_date < ?)
sqlBuilder.append(" fr.is_active = true and frp.is_active = true"); AND rcd.disallow_interest_calc_on_past_due = FALSE)
sqlBuilder.append(" and (frp.created_date >= ? or "); OR
sqlBuilder (fr.is_active = TRUE
.append("(bfrp.id is not null and frp.is_differential_to_base_lending_rate = true and frp.from_date >= bfrp.from_date)) "); AND frp.is_active = TRUE
sqlBuilder.append("and lrr.loan_id is null"); AND (frp.created_date >= ?
sqlBuilder.append(" ))"); OR (bfrp.id IS NOT NULL
sqlBuilder.append(" and ml.id >= ? and o.hierarchy like ? "); AND frp.is_differential_to_base_lending_rate = TRUE
sqlBuilder.append(" group by ml.id "); AND frp.from_date >= bfrp.from_date))
sqlBuilder.append(" limit ? "); AND lrr.loan_id IS NULL)
)
AND l.id >= ?
AND o.hierarchy like ?
GROUP BY l.id
LIMIT ?
""";
try { try {
return Collections.synchronizedList( return Collections.synchronizedList(this.jdbcTemplate.queryForList(sql, Long.class, yesterday, LoanStatus.ACTIVE.getValue(),
this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, yesterday, LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate, currentdate, yesterday, maxLoanIdInList, officeHierarchy, pageSize));
currentdate, currentdate, yesterday, maxLoanIdInList, officeHierarchy, pageSize));
} catch (final EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
return null; return null;
} }
@@ -174,7 +174,8 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationRestFrequencyWeekdayParamName, LoanProductConstants.recalculationRestFrequencyNthDayParamName, LoanProductConstants.recalculationRestFrequencyWeekdayParamName, LoanProductConstants.recalculationRestFrequencyNthDayParamName,
LoanProductConstants.recalculationRestFrequencyOnDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName,
LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName, 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.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE, LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE,
LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, 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)) { if (fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)) {
loanScheduleType = fromApiJsonHelper.extractStringNamed(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)) if (LoanScheduleType.CUMULATIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
&& LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.equals(loanRescheduleStrategyMethod)) { && LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.equals(loanRescheduleStrategyMethod)) {
baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).failWithCode( baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).failWithCode(
@@ -259,7 +259,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lpr.compounding_frequency_weekday_enum as compoundingFrequencyWeekDayEnum, " + "lpr.compounding_frequency_weekday_enum as compoundingFrequencyWeekDayEnum, "
+ "lpr.compounding_frequency_on_day as compoundingFrequencyOnDay, " + "lpr.compounding_frequency_on_day as compoundingFrequencyOnDay, "
+ "lpr.is_compounding_to_be_posted_as_transaction as isCompoundingToBePostedAsTransaction, " + "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.principal_threshold_for_last_installment as principalThresholdForLastInstallment, "
+ "lp.fixed_principal_percentage_per_installment fixedPrincipalPercentagePerInstallment, " + "lp.fixed_principal_percentage_per_installment fixedPrincipalPercentagePerInstallment, "
+ "lp.sync_expected_with_disbursement_date as syncExpectedWithDisbursementDate, " + "lp.sync_expected_with_disbursement_date as syncExpectedWithDisbursementDate, "
@@ -477,12 +479,14 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final EnumOptionData preCloseInterestCalculationStrategy = LoanEnumerations final EnumOptionData preCloseInterestCalculationStrategy = LoanEnumerations
.preCloseInterestCalculationStrategy(preCloseInterestCalculationStrategyEnumValue); .preCloseInterestCalculationStrategy(preCloseInterestCalculationStrategyEnumValue);
final boolean allowCompoundingOnEod = rs.getBoolean("allowCompoundingOnEod"); final boolean allowCompoundingOnEod = rs.getBoolean("allowCompoundingOnEod");
final boolean disallowInterestCalculationOnPastDue = rs.getBoolean("disallowInterestCalculationOnPastDue");
interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType, interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType,
rescheduleStrategyType, restFrequencyType, restFrequencyInterval, restFrequencyNthDayEnum, restFrequencyWeekDayEnum, rescheduleStrategyType, restFrequencyType, restFrequencyInterval, restFrequencyNthDayEnum, restFrequencyWeekDayEnum,
restFrequencyOnDay, compoundingFrequencyType, compoundingInterval, compoundingFrequencyNthDayEnum, restFrequencyOnDay, compoundingFrequencyType, compoundingInterval, compoundingFrequencyNthDayEnum,
compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule, compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule,
isCompoundingToBePostedAsTransaction, preCloseInterestCalculationStrategy, allowCompoundingOnEod); isCompoundingToBePostedAsTransaction, preCloseInterestCalculationStrategy, allowCompoundingOnEod,
disallowInterestCalculationOnPastDue);
} }
final boolean amortization = rs.getBoolean("amortizationBoolean"); final boolean amortization = rs.getBoolean("amortizationBoolean");
@@ -174,4 +174,5 @@
<include file="parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml" relativeToChangelogFile="true" /> <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/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/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> </databaseChangeLog>
@@ -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>
@@ -69,7 +69,7 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest {
Utils.initializeRESTAssured(); Utils.initializeRESTAssured();
requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); 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(); responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
schedulerJobHelper = new SchedulerJobHelper(requestSpec); schedulerJobHelper = new SchedulerJobHelper(requestSpec);
@@ -1222,4 +1222,68 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest {
payoffOnDateAndVerifyStatus("1 May 2023", loanIdRef.get()); 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);
});
}
} }