FINERACT-1109-rework-with-fixed-emi

This commit is contained in:
Manoj
2020-09-13 22:18:20 +05:30
parent cdf3c76856
commit 10dd1fa2b9
6 changed files with 198 additions and 14 deletions
@@ -71,8 +71,6 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
this.generalResponseSpec = new ResponseSpecBuilder().build(); this.generalResponseSpec = new ResponseSpecBuilder().build();
// create all required entities
this.createRequiredEntities();
} }
@AfterEach @AfterEach
@@ -90,6 +88,16 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
this.enableConfig(); this.enableConfig();
} }
/**
* Creates the client, loan product, and loan entities
**/
private void createRequiredEntitiesWithRecalculationEnabled() {
this.createClientEntity();
this.createLoanProductWithInterestRecalculation();
this.createLoanEntity();
this.enableConfig();
}
/** /**
* create a new client * create a new client
**/ **/
@@ -114,6 +122,38 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
LOG.info("Successfully created loan product (ID:{}) ", this.loanProductId); LOG.info("Successfully created loan product (ID:{}) ", this.loanProductId);
} }
private void createLoanProductWithInterestRecalculation() {
LOG.info(
"---------------------------------CREATING LOAN PRODUCT WITH RECALULATION ENABLED ------------------------------------------");
final String interestRecalculationCompoundingMethod = LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE;
final String rescheduleStrategyMethod = LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS;
final String recalculationRestFrequencyType = LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY;
final String recalculationRestFrequencyInterval = "0";
final String preCloseInterestCalculationStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE;
final String recalculationCompoundingFrequencyType = null;
final String recalculationCompoundingFrequencyInterval = null;
final Integer recalculationCompoundingFrequencyOnDayType = null;
final Integer recalculationCompoundingFrequencyDayOfWeekType = null;
final Integer recalculationRestFrequencyOnDayType = null;
final Integer recalculationRestFrequencyDayOfWeekType = null;
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount)
.withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod)
.withInterestRateFrequencyTypeAsYear().withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
.withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
preCloseInterestCalculationStrategy)
.withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval,
recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType)
.withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType,
recalculationCompoundingFrequencyDayOfWeekType)
.build(null);
this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
LOG.info("Successfully created loan product (ID:{}) ", this.loanProductId);
}
/** /**
* submit a new loan application, approve and disburse the loan * submit a new loan application, approve and disburse the loan
**/ **/
@@ -173,6 +213,8 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
@Test @Test
public void testCreateLoanRescheduleRequestWithInterestAppropriation() { public void testCreateLoanRescheduleRequestWithInterestAppropriation() {
// create all required entities
this.createRequiredEntities();
this.createAndApproveLoanRescheduleRequestForInterestAppropriation(); this.createAndApproveLoanRescheduleRequestForInterestAppropriation();
} }
@@ -210,8 +252,108 @@ public class LoanRescheduleOnDecliningBalanceLoanTest {
final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId); final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment"); final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
assertEquals(12186, totalDueForPeriod.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK"); assertEquals(12186, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK");
assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK"); assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK");
LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
}
@Test
public void testCreateLoanRescheduleRequestWithRecalculationEnabled() {
// create all required entities
this.createRequiredEntitiesWithRecalculationEnabled();
this.createAndApproveLoanRescheduleRequestWithRecalculationEnabled();
}
/**
* create new loan reschedule request with recalculation enabled in Loan product
**/
private void createAndApproveLoanRescheduleRequestWithRecalculationEnabled() {
LOG.info(
"---------------------------------CREATING LOAN RESCHEDULE REQUEST FOR LOAN WITH RECALCULATION------------------------------------");
final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null)
.updateExtraTerms(null).updateRescheduleFromDate("04 January 2015").updateAdjustedDueDate("04 October 2015")
.updateRecalculateInterest(true).build(this.loanId.toString());
this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
LOG.info("Successfully created loan reschedule request (ID: {} )", this.loanRescheduleRequestId);
final String aproveRequestJSON = new LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON();
this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, aproveRequestJSON);
final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
assertTrue((Boolean) response.get("approved"));
LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
final Map repaymentSchedule = (Map) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, loanId,
"repaymentSchedule");
final ArrayList periods = (ArrayList) repaymentSchedule.get("periods");
HashMap period = (HashMap) periods.get(5);
Float totalDueForPeriod = (Float) period.get("totalDueForPeriod");
final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
assertEquals(12326, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK");
assertEquals(131512, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK");
LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
}
@Test
public void testCreateLoanRescheduleRequestForInterestAppropriationAndFixedEMI() {
// create all required entities
this.createRequiredEntities();
this.createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI();
}
/**
* create new loan reschedule request with combination of date change, interest appropriation and fixed emi
**/
private void createAndApproveLoanRescheduleRequestForInterestAppropriationAndFixedEMI() {
LOG.info(
"---------------------------------CREATING LOAN RESCHEDULE REQUEST FOR INTEREST APPROPRIATTION-------------------------------------");
final String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnPrincipal(null).updateGraceOnInterest(null)
.updateExtraTerms(null).updateRescheduleFromDate("04 January 2015").updateAdjustedDueDate("04 July 2015").updateEMI("5000")
.updateEmiChangeEndDate("4 September 2015").updateRecalculateInterest(true).build(this.loanId.toString());
this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId);
LOG.info("Successfully created loan reschedule request (ID: {} )", this.loanRescheduleRequestId);
final String aproveRequestJSON = new LoanRescheduleRequestTestBuilder().getApproveLoanRescheduleRequestJSON();
this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, aproveRequestJSON);
final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum");
assertTrue((Boolean) response.get("approved"));
LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
final Map repaymentSchedule = (Map) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, loanId,
"repaymentSchedule");
final ArrayList periods = (ArrayList) repaymentSchedule.get("periods");
HashMap period = (HashMap) periods.get(5);
Float totalFixedDueForPeriod = (Float) period.get("totalDueForPeriod");
HashMap period2 = (HashMap) periods.get(8);
Float totalDueForPeriod = (Float) period2.get("totalDueForPeriod");
final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");
assertEquals(5000, totalFixedDueForPeriod.intValue(), "EXPECTED FIXED REPAYMENT is NOK");
assertEquals(15316, totalDueForPeriod.intValue(), "EXPECTED REPAYMENT is NOK");
assertEquals(120806, totalExpectedRepayment.intValue(), "TOTAL EXPECTED REPAYMENT is NOK");
LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId); LOG.info("Successfully approved loan reschedule request (ID: {})", this.loanRescheduleRequestId);
@@ -337,6 +337,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate, loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate,
periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams,
lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges); lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges);
if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) {
installment.setEMIFixedSpecificToInstallmentTrue();
}
periods.add(installment); periods.add(installment);
// Updates principal paid map with efective date for reducing // Updates principal paid map with efective date for reducing
@@ -373,15 +378,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled() if (loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled()
&& loanApplicationTerms.getInterestTobeApproppriated() != null && loanApplicationTerms.getInterestTobeApproppriated() != null
&& loanApplicationTerms.getInterestTobeApproppriated().isGreaterThanZero()) { && loanApplicationTerms.getInterestTobeApproppriated().isGreaterThanZero()) {
Money interestFraction = loanApplicationTerms.getInterestTobeApproppriated().dividedBy(periods.size(), mc.getRoundingMode()); int emisTobeChanged = 1;
for (LoanScheduleModelPeriod installment : (List<LoanScheduleModelPeriod>) periods) {
if (!installment.isEMIFixedSpecificToInstallment()) {
emisTobeChanged++;
}
}
if (emisTobeChanged > 1) {
emisTobeChanged--;
}
Money interestTobeApproppriated = loanApplicationTerms.getInterestTobeApproppriated();
Money interestFraction = interestTobeApproppriated.dividedBy(emisTobeChanged, mc.getRoundingMode());
BigDecimal roundFraction = interestFraction.getAmount().remainder(BigDecimal.ONE); BigDecimal roundFraction = interestFraction.getAmount().remainder(BigDecimal.ONE);
interestFraction = interestFraction.minus(roundFraction); interestFraction = interestFraction.minus(roundFraction);
roundFraction = roundFraction.multiply(new BigDecimal(periods.size()));
for (LoanScheduleModelPeriod installment : (List<LoanScheduleModelPeriod>) periods) { for (LoanScheduleModelPeriod installment : (List<LoanScheduleModelPeriod>) periods) {
installment.addInterestAmount(interestFraction); if (!installment.isEMIFixedSpecificToInstallment()) {
installment.addInterestAmount(interestFraction);
interestTobeApproppriated = interestTobeApproppriated.minus(interestFraction);
}
}
LoanScheduleModelPeriod lastInstallment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
if (interestTobeApproppriated.isGreaterThanZero()) {
lastInstallment.addInterestAmount(interestTobeApproppriated);
} }
LoanScheduleModelPeriod installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
installment.addInterestAmount(Money.of(currency, roundFraction));
scheduleParams.addTotalRepaymentExpected(loanApplicationTerms.getInterestTobeApproppriated()); scheduleParams.addTotalRepaymentExpected(loanApplicationTerms.getInterestTobeApproppriated());
scheduleParams.addTotalCumulativeInterest(loanApplicationTerms.getInterestTobeApproppriated()); scheduleParams.addTotalCumulativeInterest(loanApplicationTerms.getInterestTobeApproppriated());
loanApplicationTerms.setInterestTobeApproppriated(Money.zero(currency)); loanApplicationTerms.setInterestTobeApproppriated(Money.zero(currency));
@@ -1079,6 +1099,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (loanTermVariationsData.isSpecificToInstallment()) { if (loanTermVariationsData.isSpecificToInstallment()) {
loanApplicationTerms.setCurrentPeriodFixedEmiAmount(loanTermVariationsData.getDecimalValue()); loanApplicationTerms.setCurrentPeriodFixedEmiAmount(loanTermVariationsData.getDecimalValue());
recalculateAmounts = true; recalculateAmounts = true;
} else { } else {
loanApplicationTerms.setFixedEmiAmount(loanTermVariationsData.getDecimalValue()); loanApplicationTerms.setFixedEmiAmount(loanTermVariationsData.getDecimalValue());
} }
@@ -35,6 +35,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
private final LocalDate disbursementDate; private final LocalDate disbursementDate;
private final Money principalDisbursed; private final Money principalDisbursed;
private final BigDecimal chargesDueAtTimeOfDisbursement; private final BigDecimal chargesDueAtTimeOfDisbursement;
private boolean isEMIFixedSpecificToInstallment = false;
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms, public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
final BigDecimal chargesDueAtTimeOfDisbursement) { final BigDecimal chargesDueAtTimeOfDisbursement) {
@@ -127,4 +128,14 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() { public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return null; return null;
} }
@Override
public void setEMIFixedSpecificToInstallmentTrue() {
this.isEMIFixedSpecificToInstallment = true;
}
@Override
public boolean isEMIFixedSpecificToInstallment() {
return isEMIFixedSpecificToInstallment;
}
} }
@@ -54,4 +54,8 @@ public interface LoanScheduleModelPeriod {
void addInterestAmount(Money interestDue); void addInterestAmount(Money interestDue);
Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails(); Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails();
void setEMIFixedSpecificToInstallmentTrue();
boolean isEMIFixedSpecificToInstallment();
} }
@@ -42,6 +42,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
private Money totalDue; private Money totalDue;
private final boolean recalculatedInterestComponent; private final boolean recalculatedInterestComponent;
private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>(); private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
private boolean isEMIFixedSpecificToInstallment = false;
public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate, public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
@@ -161,4 +162,14 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() { public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return this.loanCompoundingDetails; return this.loanCompoundingDetails;
} }
@Override
public boolean isEMIFixedSpecificToInstallment() {
return this.isEMIFixedSpecificToInstallment;
}
@Override
public void setEMIFixedSpecificToInstallmentTrue() {
this.isEMIFixedSpecificToInstallment = true;
}
} }
@@ -202,11 +202,6 @@ public class LoanRescheduleRequestDataValidator {
"Loan rescheduling is not supported for multidisbursement loans"); "Loan rescheduling is not supported for multidisbursement loans");
} }
if (loan.isInterestRecalculationEnabledForProduct()) {
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
RescheduleLoansApiConstants.resheduleWithInterestRecalculationNotSupportedErrorCode,
"Loan rescheduling is not supported for the loan product with interest recalculation enabled");
}
validateForOverdueCharges(dataValidatorBuilder, loan, installment); validateForOverdueCharges(dataValidatorBuilder, loan, installment);
if (!dataValidationErrors.isEmpty()) { if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors); throw new PlatformApiDataValidationException(dataValidationErrors);