FINERACT-1968: Progressive Loan schedule handling - interest handling (FLAT interest, Same as repayment period, Equal installments)

This commit is contained in:
Soma Sörös
2025-07-09 14:39:18 +02:00
committed by Adam Saghy
parent 3f359bb368
commit 40f8b59ab4
21 changed files with 710 additions and 77 deletions
@@ -232,7 +232,7 @@ public class LoanScheduleAssembler {
Boolean allowPartialPeriodInterestCalcualtion = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, element);
if (allowPartialPeriodInterestCalcualtion == null) {
allowPartialPeriodInterestCalcualtion = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion();
allowPartialPeriodInterestCalcualtion = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation();
}
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
@@ -1359,15 +1359,15 @@ public class LoanScheduleAssembler {
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion())) {
loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation())) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME);
changes.put(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, newValue);
loanProductRelatedDetail.setAllowPartialPeriodInterestCalcualtion(newValue);
loanProductRelatedDetail.setAllowPartialPeriodInterestCalculation(newValue);
}
if (loanProductRelatedDetail.getInterestCalculationPeriodMethod().isDaily()) {
loanProductRelatedDetail.setAllowPartialPeriodInterestCalcualtion(false);
loanProductRelatedDetail.setAllowPartialPeriodInterestCalculation(false);
}
final String graceOnPrincipalPaymentParamName = "graceOnPrincipalPayment";
@@ -1667,14 +1667,20 @@ public final class LoanApplicationValidator {
}
}
public void validateLoanMultiDisbursementDate(final JsonElement element, LocalDate expectedDisbursementDate, BigDecimal principal) {
public void validateLoanMultiDisbursementDate(final JsonElement element, LocalDate expectedDisbursementDate, BigDecimal principal,
Loan loan) {
Validator.validateOrThrow("loan", baseDataValidator -> {
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal, loan);
});
}
public void validateLoanMultiDisbursementDate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
LocalDate expectedDisbursement, BigDecimal totalPrincipal) {
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursement, totalPrincipal, null);
}
public void validateLoanMultiDisbursementDate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
LocalDate expectedDisbursement, BigDecimal totalPrincipal, Loan loan) {
this.validateDisbursementsAreDatewiseOrdered(element, baseDataValidator);
final JsonObject topLevelJsonElement = element.getAsJsonObject();
@@ -1732,11 +1738,31 @@ public final class LoanApplicationValidator {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName)
.failWithCode(LoanApiConstants.APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES);
}
final Integer interestType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.interestTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).ignoreIfNull()
.integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
if (loan == null) {
final String transactionProcessingStrategyCode = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
if (transactionProcessingStrategyCode != null) {
final Integer interestType = this.fromApiJsonHelper.extractIntegerNamed(LoanApiConstants.interestTypeParameterName,
element, Locale.getDefault());
String processorCode = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode).getCode();
boolean isProgressive = "advanced-payment-allocation-strategy".equals(processorCode);
if (isProgressive) {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType)
.ignoreIfNull().inMinMaxRange(0, 1);
} else {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType)
.ignoreIfNull().integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
}
}
} else {
if (loan.isCumulativeSchedule()) {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName)
.value(loan.getLoanProductRelatedDetail().getInterestMethod()).ignoreIfNull()
.value(InterestMethod.DECLINING_BALANCE);
}
}
}
}
}
@@ -1756,7 +1782,7 @@ public final class LoanApplicationValidator {
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
.fromInt(interestCalculationPeriodType);
boolean considerPartialPeriodUpdates = interestCalculationPeriodMethod.isDaily() ? interestCalculationPeriodMethod.isDaily()
: loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion();
: loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
element)) {
final Boolean considerPartialInterestEnabled = this.fromApiJsonHelper
@@ -1780,7 +1806,8 @@ public final class LoanApplicationValidator {
.failWithCode("not.supported.for.selected.interest.calcualtion.type");
}
if (loanProduct.isMultiDisburseLoan()) {
if (loanProduct.isMultiDisburseLoan()
&& !"advanced-payment-allocation-strategy".equals(loanProduct.getTransactionProcessingStrategyCode())) {
baseDataValidator.reset().parameter(LoanProductConstants.MULTI_DISBURSE_LOAN_PARAMETER_NAME)
.failWithCode("not.supported.for.selected.interest.calcualtion.type");
}
@@ -2045,7 +2072,7 @@ public final class LoanApplicationValidator {
LoanProduct loanProduct = loan.loanProduct();
if (loanProduct.isMultiDisburseLoan()) {
validateLoanMultiDisbursementDate(element, expectedDisbursementDate, principal);
validateLoanMultiDisbursementDate(element, expectedDisbursementDate, principal, loan);
final JsonArray disbursementDataArray = this.fromApiJsonHelper
.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName, element);
@@ -201,15 +201,15 @@ public class LoanProductRelatedDetailUpdateUtil {
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
loanRepaymentScheduleDetail.isAllowPartialPeriodInterestCalcualtion())) {
loanRepaymentScheduleDetail.isAllowPartialPeriodInterestCalculation())) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME);
actualChanges.put(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, newValue);
loanRepaymentScheduleDetail.setAllowPartialPeriodInterestCalcualtion(newValue);
loanRepaymentScheduleDetail.setAllowPartialPeriodInterestCalculation(newValue);
}
if (loanRepaymentScheduleDetail.getInterestCalculationPeriodMethod().isDaily()) {
loanRepaymentScheduleDetail.setAllowPartialPeriodInterestCalcualtion(false);
loanRepaymentScheduleDetail.setAllowPartialPeriodInterestCalculation(false);
}
final String graceOnPrincipalPaymentParamName = "graceOnPrincipalPayment";
@@ -2232,7 +2232,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate,
boolean isDisallowExpectedDisbursements) {
boolean isDisallowExpectedDisbursements, Loan loan) {
final String json = command.json();
final JsonElement element = this.fromApiJsonHelper.parse(json);
@@ -2254,7 +2254,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
loanApplicationValidator.validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
loanApplicationValidator.validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal, loan);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
@@ -2296,7 +2296,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final String errorMessage = "cannot.modify.tranches.if.loan.is.pendingapproval.closed.overpaid.writtenoff";
throw new LoanMultiDisbursementException(errorMessage);
}
validateMultiDisbursementData(command, expectedDisbursementDate, loan.loanProduct().isDisallowExpectedDisbursements());
validateMultiDisbursementData(command, expectedDisbursementDate, loan.loanProduct().isDisallowExpectedDisbursements(), loan);
this.validateForAddAndDeleteTranche(loan);
@@ -786,7 +786,7 @@ public final class LoanProductDataValidator {
}
}
validateMultiDisburseLoanData(baseDataValidator, element);
validateMultiDisburseLoanData(baseDataValidator, element, null);
validateLoanConfigurableAttributes(baseDataValidator, element);
@@ -1014,7 +1014,8 @@ public final class LoanProductDataValidator {
}
}
private void validateMultiDisburseLoanData(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
private void validateMultiDisburseLoanData(final DataValidatorBuilder baseDataValidator, final JsonElement element,
final LoanProduct loanProduct) {
Boolean multiDisburseLoan = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.MULTI_DISBURSE_LOAN_PARAMETER_NAME, element)) {
multiDisburseLoan = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.MULTI_DISBURSE_LOAN_PARAMETER_NAME,
@@ -1045,14 +1046,34 @@ public final class LoanProductDataValidator {
.integerGreaterThanZero();
final Integer interestType = this.fromApiJsonHelper.extractIntegerNamed(INTEREST_TYPE, element, Locale.getDefault());
baseDataValidator.reset().parameter(INTEREST_TYPE).value(interestType).ignoreIfNull()
.integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
boolean isProgressive = isProgressive(element, loanProduct);
if (isProgressive) {
baseDataValidator.reset().parameter(INTEREST_TYPE).value(interestType).ignoreIfNull().inMinMaxRange(0, 1);
} else {
baseDataValidator.reset().parameter(INTEREST_TYPE).value(interestType).ignoreIfNull()
.integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
}
}
final String overAppliedCalculationType = this.fromApiJsonHelper.extractStringNamed(OVER_APPLIED_CALCULATION_TYPE, element);
baseDataValidator.reset().parameter(OVER_APPLIED_CALCULATION_TYPE).value(overAppliedCalculationType).notExceedingLengthOf(10);
}
private boolean isProgressive(JsonElement element, LoanProduct loanProduct) {
String processorCode = null;
if (loanProduct != null) {
processorCode = loanProduct.getTransactionProcessingStrategyCode();
}
final String transactionProcessingStrategyCode = this.fromApiJsonHelper.extractStringNamed(TRANSACTION_PROCESSING_STRATEGY_CODE,
element);
if (transactionProcessingStrategyCode != null) {
processorCode = loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode)
.getCode();
}
return "advanced-payment-allocation-strategy".equals(processorCode);
}
private void validateInterestRecalculationParams(final JsonElement element, final DataValidatorBuilder baseDataValidator,
final LoanProduct loanProduct) {
@@ -1868,7 +1889,7 @@ public final class LoanProductDataValidator {
}
}
validateMultiDisburseLoanData(baseDataValidator, element);
validateMultiDisburseLoanData(baseDataValidator, element, loanProduct);
// validateLoanConfigurableAttributes(baseDataValidator,element);
@@ -2549,7 +2570,7 @@ public final class LoanProductDataValidator {
}
} else if (loanProduct != null) {
if (!interestCalculationPeriodMethod.isDaily()) {
considerPartialPeriodUpdates = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion();
considerPartialPeriodUpdates = loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation();
}
}
@@ -2574,7 +2595,7 @@ public final class LoanProductDataValidator {
} else if (loanProduct != null) {
multiDisburseLoan = loanProduct.isMultiDisburseLoan();
}
if (multiDisburseLoan != null && multiDisburseLoan) {
if (multiDisburseLoan != null && multiDisburseLoan && !isProgressive(element, loanProduct)) {
baseDataValidator.reset().parameter(LoanProductConstants.MULTI_DISBURSE_LOAN_PARAMETER_NAME)
.failWithCode("not.supported.for.selected.interest.calculation.type");
}