Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FINERACT-1109-rework-with-fixed-emi #1327

Merged
merged 1 commit into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ public void initialize() {

this.generalResponseSpec = new ResponseSpecBuilder().build();

// create all required entities
this.createRequiredEntities();
}

@AfterEach
Expand All @@ -90,6 +88,16 @@ private void createRequiredEntities() {
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
**/
Expand All @@ -114,6 +122,38 @@ private void createLoanProductEntity() {
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
**/
Expand Down Expand Up @@ -173,6 +213,8 @@ private void disableConfig() {

@Test
public void testCreateLoanRescheduleRequestWithInterestAppropriation() {
// create all required entities
this.createRequiredEntities();
this.createAndApproveLoanRescheduleRequestForInterestAppropriation();

}
Expand Down Expand Up @@ -210,8 +252,108 @@ private void createAndApproveLoanRescheduleRequestForInterestAppropriation() {
final HashMap loanSummary = this.loanTransactionHelper.getLoanSummary(requestSpec, generalResponseSpec, loanId);
final Float totalExpectedRepayment = (Float) loanSummary.get("totalExpectedRepayment");

assertEquals(12186, totalDueForPeriod.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK");
assertEquals(123682, totalExpectedRepayment.intValue(), "TOTAL EXPECTED LAST REPAYMENT is NOK");
assertEquals(12186, totalDueForPeriod.intValue(), "EXPECTED 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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe
loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate,
periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams,
lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges);

if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) {
installment.setEMIFixedSpecificToInstallmentTrue();
}

periods.add(installment);

// Updates principal paid map with efective date for reducing
Expand Down Expand Up @@ -373,15 +378,30 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe
if (loanApplicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMIEnabled()
&& loanApplicationTerms.getInterestTobeApproppriated() != null
&& 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);
interestFraction = interestFraction.minus(roundFraction);
roundFraction = roundFraction.multiply(new BigDecimal(periods.size()));
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.addTotalCumulativeInterest(loanApplicationTerms.getInterestTobeApproppriated());
loanApplicationTerms.setInterestTobeApproppriated(Money.zero(currency));
Expand Down Expand Up @@ -1079,6 +1099,7 @@ private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTer
if (loanTermVariationsData.isSpecificToInstallment()) {
loanApplicationTerms.setCurrentPeriodFixedEmiAmount(loanTermVariationsData.getDecimalValue());
recalculateAmounts = true;

} else {
loanApplicationTerms.setFixedEmiAmount(loanTermVariationsData.getDecimalValue());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
private final LocalDate disbursementDate;
private final Money principalDisbursed;
private final BigDecimal chargesDueAtTimeOfDisbursement;
private boolean isEMIFixedSpecificToInstallment = false;

public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
final BigDecimal chargesDueAtTimeOfDisbursement) {
Expand Down Expand Up @@ -127,4 +128,14 @@ public void addInterestAmount(@SuppressWarnings("unused") Money principalDue) {
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return null;
}

@Override
public void setEMIFixedSpecificToInstallmentTrue() {
this.isEMIFixedSpecificToInstallment = true;
}

@Override
public boolean isEMIFixedSpecificToInstallment() {
return isEMIFixedSpecificToInstallment;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ public interface LoanScheduleModelPeriod {
void addInterestAmount(Money interestDue);

Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails();

void setEMIFixedSpecificToInstallmentTrue();

boolean isEMIFixedSpecificToInstallment();
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
private Money totalDue;
private final boolean recalculatedInterestComponent;
private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
private boolean isEMIFixedSpecificToInstallment = false;

public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
Expand Down Expand Up @@ -161,4 +162,14 @@ public void addInterestAmount(Money interestDue) {
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return this.loanCompoundingDetails;
}

@Override
public boolean isEMIFixedSpecificToInstallment() {
return this.isEMIFixedSpecificToInstallment;
}

@Override
public void setEMIFixedSpecificToInstallmentTrue() {
this.isEMIFixedSpecificToInstallment = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,6 @@ public void validateForCreateAction(final JsonCommand jsonCommand, final Loan lo
"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);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
Expand Down