From cfdfa6cd57bb7da6ade67cf0700578989a3f8e9c Mon Sep 17 00:00:00 2001 From: Janos Meszaros Date: Fri, 28 Jun 2024 14:34:17 +0200 Subject: [PATCH] FINERACT-1981: EMI Calculation, Daily, Weekly, Monthly support plug to Repayment Schedule --- .../data/LoanSchedulePeriodData.java | 2 +- .../domain/ScheduledDateGenerator.java | 2 +- ...tractProgressiveLoanScheduleGenerator.java | 25 ++++++++++++------- .../ProgressiveLoanScheduleGenerator.java | 7 ++++-- .../calc/ProgressiveEMICalculator.java | 5 +--- .../calc/ProgressiveEMICalculatorTest.java | 6 ++--- .../domain/DefaultScheduledDateGenerator.java | 7 +++--- .../DefaultScheduledDateGeneratorTest.java | 3 ++- 8 files changed, 31 insertions(+), 26 deletions(-) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java index fc6b5c468..fb50ee5f8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePeriodData.java @@ -275,7 +275,7 @@ public final class LoanSchedulePeriodData { this.dueDate = dueDate; this.obligationsMetOnDate = null; this.complete = null; - this.daysInPeriod = 1; + this.daysInPeriod = 1; // TODO: check this may should be 0 or somewhere else should be 1? this.principalDisbursed = null; this.principalOriginalDue = principalDue; this.principalDue = principalOriginalDue; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java index 1fd14d85f..88271f95c 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java @@ -27,7 +27,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; public interface ScheduledDateGenerator { - List generateRepaymentPeriods(LoanApplicationTerms loanApplicationTerms, + List generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO); LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java index 960e79dd7..c98935cbc 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java @@ -85,11 +85,7 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc List periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams, chargesDueAtTimeOfDisbursement); - List expectedRepaymentPeriods = getScheduledDateGenerator() - .generateRepaymentPeriods(loanApplicationTerms, holidayDetailDTO); - EMICalculationResult emiCalculationResult = getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams, - loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, mc); - + EMICalculationResult emiCalculationResult = null; boolean isFirstRepayment = true; // charges which depends on total loan interest will be added to this @@ -105,7 +101,6 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator() .adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO); scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate()); - isFirstRepayment = false; LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate(); // Loan Schedule Exceptions that need to be applied for Loan Account @@ -117,8 +112,16 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc .addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate))); ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO); - - processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate); + final boolean hasAnyProcessedDisbursement = processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, + scheduleParams, periods, scheduledDueDate); + if (isFirstRepayment || hasAnyProcessedDisbursement) { + final LocalDate startDate = isFirstRepayment ? loanApplicationTerms.getRepaymentStartDate() + : scheduleParams.getPeriodStartDate(); + List expectedRepaymentPeriods = getScheduledDateGenerator() + .generateRepaymentPeriods(startDate, loanApplicationTerms, holidayDetailDTO); + emiCalculationResult = getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams, + loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, mc); + } // 5 determine principal,interest of repayment period PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(loanApplicationTerms, @@ -189,6 +192,7 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc // } thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream() .anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate())); + isFirstRepayment = false; } // If the disbursement happened after maturity date @@ -327,9 +331,10 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc /** * Method add extra disbursement periods (if applicable) and update the schedule params */ - private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement, + private boolean processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement, LoanScheduleParams scheduleParams, final Collection periods, final LocalDate scheduledDueDate) { Iterator> iter = scheduleParams.getDisburseDetailMap().entrySet().iterator(); + boolean hasProcessedDisbursement = false; while (iter.hasNext()) { Map.Entry disburseDetail = iter.next(); if ((disburseDetail.getKey().isEqual(scheduleParams.getPeriodStartDate()) @@ -373,8 +378,10 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc } } iter.remove(); + hasProcessedDisbursement = true; } } + return hasProcessedDisbursement; } private void applyChargesForCurrentPeriod(final Set loanCharges, final MonetaryCurrency currency, diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index 10bb88987..3f51b727d 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -54,8 +54,11 @@ public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanSch public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final LoanApplicationTerms loanApplicationTerms, final LoanScheduleParams loanScheduleParams, final EMICalculationResult emiCalculationResult, final MathContext mc) { - final Money equalMonthlyInstallmentValue = Money.of(loanApplicationTerms.getCurrency(), - emiCalculationResult.getEqualMonthlyInstallmentValue()); + final Money equalMonthlyInstallmentValue = loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null + ? Money.of(loanApplicationTerms.getCurrency(), + Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(), + loanApplicationTerms.getInstallmentAmountInMultiplesOf())) + : Money.of(loanApplicationTerms.getCurrency(), emiCalculationResult.getEqualMonthlyInstallmentValue()); final BigDecimal rateFactorMinus1 = emiCalculationResult.getNextRepaymentPeriodRateFactorMinus1(); final Money calculatedInterest = loanScheduleParams.getOutstandingBalance().multipliedBy(rateFactorMinus1); final Money calculatedPrincipal = equalMonthlyInstallmentValue.minus(calculatedInterest); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 477ff9377..f0812a6ed 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -58,12 +58,9 @@ public final class ProgressiveEMICalculator implements EMICalculator { public EMICalculationResult calculateEMIValueAndRateFactors(final LoanScheduleParams scheduleParams, final LoanProductRelatedDetail loanProductRelatedDetail, final List expectedRepaymentPeriods, final MathContext mc) { - // TODO: decide which outstanding balance method needed in the future final BigDecimal nominalInterestRatePerPeriod = calcNominalInterestRatePerPeriod( loanProductRelatedDetail.getNominalInterestRatePerPeriod(), mc); - final BigDecimal outstandingBalance = scheduleParams.getOutstandingBalanceAsPerRest().getAmount().compareTo(BigDecimal.ZERO) > 0 - ? scheduleParams.getOutstandingBalanceAsPerRest().getAmount() - : loanProductRelatedDetail.getPrincipal().getAmount(); + final BigDecimal outstandingBalance = scheduleParams.getOutstandingBalanceAsPerRest().getAmount(); final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()); final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType()); final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java index 447948e8b..94cb18a26 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java @@ -66,7 +66,7 @@ class ProgressiveEMICalculatorTest { @BeforeAll public static void init() { periods = new ArrayList<>(); - LocalDate startDate = LocalDate.of(2024, 01, 1); + LocalDate startDate = LocalDate.of(2024, 1, 1); periods.add(createPeriod(1, startDate, startDate.plusMonths(1))); periods.add(createPeriod(2, startDate.plusMonths(1), startDate.plusMonths(2))); periods.add(createPeriod(3, startDate.plusMonths(2), startDate.plusMonths(3))); @@ -495,11 +495,9 @@ class ProgressiveEMICalculatorTest { final MathContext mc = MoneyHelper.getMathContext(); final BigDecimal interestRate = BigDecimal.valueOf(0); - final Money principal = Money.of(monetaryCurrency, BigDecimal.valueOf(1000)); - final Money outstandingBalance = Money.of(monetaryCurrency, BigDecimal.ZERO); + final Money outstandingBalance = Money.of(monetaryCurrency, BigDecimal.valueOf(1000)); Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance); - Mockito.when(loanProductRelatedDetail.getPrincipal()).thenReturn(principal); Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate); Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 1d2e9f487..83245dfbb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -43,12 +43,11 @@ import org.springframework.stereotype.Component; public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override - public List generateRepaymentPeriods(final LoanApplicationTerms loanApplicationTerms, - final HolidayDetailDTO holidayDetailDTO) { + public List generateRepaymentPeriods(final LocalDate scheduledDueDate, + final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); final ArrayList repaymentPeriods = new ArrayList<>(numberOfRepayments); - // TODO: check periodStartDate - LocalDate lastRepaymentDate = loanApplicationTerms.getRepaymentStartDate(); + LocalDate lastRepaymentDate = scheduledDueDate; LocalDate nextRepaymentDate; boolean isFirstRepayment = true; for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) { diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java index 5b6987bea..666c1b6e8 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java @@ -98,7 +98,8 @@ public class DefaultScheduledDateGeneratorTest { submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null); // when - List result = underTest.generateRepaymentPeriods(loanApplicationTerms, holidayDetailDTO); + List result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms, + holidayDetailDTO); // then assertThat(result.size()).isEqualTo(4);