FINERACT-1981: Introduce Interest should not be calculated on past due principal amount
This commit is contained in:
committed by
Adam Saghy
parent
3858611190
commit
4cb849fa17
@@ -146,6 +146,14 @@
|
|||||||
"null",
|
"null",
|
||||||
"boolean"
|
"boolean"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": null,
|
||||||
|
"name": "disallowInterestCalculationOnPastDue",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"boolean"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -146,6 +146,14 @@
|
|||||||
"null",
|
"null",
|
||||||
"boolean"
|
"boolean"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": null,
|
||||||
|
"name": "disallowInterestCalculationOnPastDue",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"boolean"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -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) {
|
||||||
|
|||||||
+11
-2
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -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";
|
||||||
|
|||||||
+4
@@ -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")
|
||||||
|
|||||||
+6
-1
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-2
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-2
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -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;
|
||||||
|
|||||||
+5
-2
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+85
-71
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-1
@@ -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(
|
||||||
|
|||||||
+6
-2
@@ -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>
|
||||||
|
|||||||
+39
@@ -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>
|
||||||
+65
-1
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user