FINERACT-1981: EMI Calculation, Daily, Weekly, Monthly support plug to Repayment Schedule
This commit is contained in:
committed by
Adam Saghy
parent
112787cf07
commit
cfdfa6cd57
+1
-1
@@ -275,7 +275,7 @@ public final class LoanSchedulePeriodData {
|
|||||||
this.dueDate = dueDate;
|
this.dueDate = dueDate;
|
||||||
this.obligationsMetOnDate = null;
|
this.obligationsMetOnDate = null;
|
||||||
this.complete = 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.principalDisbursed = null;
|
||||||
this.principalOriginalDue = principalDue;
|
this.principalOriginalDue = principalDue;
|
||||||
this.principalDue = principalOriginalDue;
|
this.principalDue = principalOriginalDue;
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
|||||||
|
|
||||||
public interface ScheduledDateGenerator {
|
public interface ScheduledDateGenerator {
|
||||||
|
|
||||||
List<PreGeneratedLoanSchedulePeriod> generateRepaymentPeriods(LoanApplicationTerms loanApplicationTerms,
|
List<PreGeneratedLoanSchedulePeriod> generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms,
|
||||||
HolidayDetailDTO holidayDetailDTO);
|
HolidayDetailDTO holidayDetailDTO);
|
||||||
|
|
||||||
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
|
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
|
||||||
|
|||||||
+16
-9
@@ -85,11 +85,7 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
|
|||||||
List<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams,
|
List<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams,
|
||||||
chargesDueAtTimeOfDisbursement);
|
chargesDueAtTimeOfDisbursement);
|
||||||
|
|
||||||
List<PreGeneratedLoanSchedulePeriod> expectedRepaymentPeriods = getScheduledDateGenerator()
|
EMICalculationResult emiCalculationResult = null;
|
||||||
.generateRepaymentPeriods(loanApplicationTerms, holidayDetailDTO);
|
|
||||||
EMICalculationResult emiCalculationResult = getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams,
|
|
||||||
loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, mc);
|
|
||||||
|
|
||||||
boolean isFirstRepayment = true;
|
boolean isFirstRepayment = true;
|
||||||
|
|
||||||
// charges which depends on total loan interest will be added to this
|
// 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()
|
AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator()
|
||||||
.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
|
.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
|
||||||
scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
|
scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
|
||||||
isFirstRepayment = false;
|
|
||||||
LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
|
LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
|
||||||
|
|
||||||
// Loan Schedule Exceptions that need to be applied for Loan Account
|
// 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)));
|
.addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate)));
|
||||||
|
|
||||||
ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO);
|
ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO);
|
||||||
|
final boolean hasAnyProcessedDisbursement = processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement,
|
||||||
processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate);
|
scheduleParams, periods, scheduledDueDate);
|
||||||
|
if (isFirstRepayment || hasAnyProcessedDisbursement) {
|
||||||
|
final LocalDate startDate = isFirstRepayment ? loanApplicationTerms.getRepaymentStartDate()
|
||||||
|
: scheduleParams.getPeriodStartDate();
|
||||||
|
List<PreGeneratedLoanSchedulePeriod> expectedRepaymentPeriods = getScheduledDateGenerator()
|
||||||
|
.generateRepaymentPeriods(startDate, loanApplicationTerms, holidayDetailDTO);
|
||||||
|
emiCalculationResult = getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams,
|
||||||
|
loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, mc);
|
||||||
|
}
|
||||||
|
|
||||||
// 5 determine principal,interest of repayment period
|
// 5 determine principal,interest of repayment period
|
||||||
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(loanApplicationTerms,
|
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(loanApplicationTerms,
|
||||||
@@ -189,6 +192,7 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
|
|||||||
// }
|
// }
|
||||||
thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
|
thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
|
||||||
.anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
|
.anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
|
||||||
|
isFirstRepayment = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the disbursement happened after maturity date
|
// 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
|
* 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<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
|
LoanScheduleParams scheduleParams, final Collection<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
|
||||||
Iterator<Map.Entry<LocalDate, Money>> iter = scheduleParams.getDisburseDetailMap().entrySet().iterator();
|
Iterator<Map.Entry<LocalDate, Money>> iter = scheduleParams.getDisburseDetailMap().entrySet().iterator();
|
||||||
|
boolean hasProcessedDisbursement = false;
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
Map.Entry<LocalDate, Money> disburseDetail = iter.next();
|
Map.Entry<LocalDate, Money> disburseDetail = iter.next();
|
||||||
if ((disburseDetail.getKey().isEqual(scheduleParams.getPeriodStartDate())
|
if ((disburseDetail.getKey().isEqual(scheduleParams.getPeriodStartDate())
|
||||||
@@ -373,8 +378,10 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
iter.remove();
|
iter.remove();
|
||||||
|
hasProcessedDisbursement = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return hasProcessedDisbursement;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
|
private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
|
||||||
|
|||||||
+5
-2
@@ -54,8 +54,11 @@ public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanSch
|
|||||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final LoanApplicationTerms loanApplicationTerms,
|
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final LoanApplicationTerms loanApplicationTerms,
|
||||||
final LoanScheduleParams loanScheduleParams, final EMICalculationResult emiCalculationResult, final MathContext mc) {
|
final LoanScheduleParams loanScheduleParams, final EMICalculationResult emiCalculationResult, final MathContext mc) {
|
||||||
|
|
||||||
final Money equalMonthlyInstallmentValue = Money.of(loanApplicationTerms.getCurrency(),
|
final Money equalMonthlyInstallmentValue = loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null
|
||||||
emiCalculationResult.getEqualMonthlyInstallmentValue());
|
? Money.of(loanApplicationTerms.getCurrency(),
|
||||||
|
Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(),
|
||||||
|
loanApplicationTerms.getInstallmentAmountInMultiplesOf()))
|
||||||
|
: Money.of(loanApplicationTerms.getCurrency(), emiCalculationResult.getEqualMonthlyInstallmentValue());
|
||||||
final BigDecimal rateFactorMinus1 = emiCalculationResult.getNextRepaymentPeriodRateFactorMinus1();
|
final BigDecimal rateFactorMinus1 = emiCalculationResult.getNextRepaymentPeriodRateFactorMinus1();
|
||||||
final Money calculatedInterest = loanScheduleParams.getOutstandingBalance().multipliedBy(rateFactorMinus1);
|
final Money calculatedInterest = loanScheduleParams.getOutstandingBalance().multipliedBy(rateFactorMinus1);
|
||||||
final Money calculatedPrincipal = equalMonthlyInstallmentValue.minus(calculatedInterest);
|
final Money calculatedPrincipal = equalMonthlyInstallmentValue.minus(calculatedInterest);
|
||||||
|
|||||||
+1
-4
@@ -58,12 +58,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
public EMICalculationResult calculateEMIValueAndRateFactors(final LoanScheduleParams scheduleParams,
|
public EMICalculationResult calculateEMIValueAndRateFactors(final LoanScheduleParams scheduleParams,
|
||||||
final LoanProductRelatedDetail loanProductRelatedDetail, final List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
|
final LoanProductRelatedDetail loanProductRelatedDetail, final List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
|
||||||
final MathContext mc) {
|
final MathContext mc) {
|
||||||
// TODO: decide which outstanding balance method needed in the future
|
|
||||||
final BigDecimal nominalInterestRatePerPeriod = calcNominalInterestRatePerPeriod(
|
final BigDecimal nominalInterestRatePerPeriod = calcNominalInterestRatePerPeriod(
|
||||||
loanProductRelatedDetail.getNominalInterestRatePerPeriod(), mc);
|
loanProductRelatedDetail.getNominalInterestRatePerPeriod(), mc);
|
||||||
final BigDecimal outstandingBalance = scheduleParams.getOutstandingBalanceAsPerRest().getAmount().compareTo(BigDecimal.ZERO) > 0
|
final BigDecimal outstandingBalance = scheduleParams.getOutstandingBalanceAsPerRest().getAmount();
|
||||||
? scheduleParams.getOutstandingBalanceAsPerRest().getAmount()
|
|
||||||
: loanProductRelatedDetail.getPrincipal().getAmount();
|
|
||||||
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
|
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
|
||||||
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
|
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
|
||||||
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
|
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
|
||||||
|
|||||||
+2
-4
@@ -66,7 +66,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void init() {
|
public static void init() {
|
||||||
periods = new ArrayList<>();
|
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(1, startDate, startDate.plusMonths(1)));
|
||||||
periods.add(createPeriod(2, startDate.plusMonths(1), startDate.plusMonths(2)));
|
periods.add(createPeriod(2, startDate.plusMonths(1), startDate.plusMonths(2)));
|
||||||
periods.add(createPeriod(3, startDate.plusMonths(2), startDate.plusMonths(3)));
|
periods.add(createPeriod(3, startDate.plusMonths(2), startDate.plusMonths(3)));
|
||||||
@@ -495,11 +495,9 @@ class ProgressiveEMICalculatorTest {
|
|||||||
final MathContext mc = MoneyHelper.getMathContext();
|
final MathContext mc = MoneyHelper.getMathContext();
|
||||||
|
|
||||||
final BigDecimal interestRate = BigDecimal.valueOf(0);
|
final BigDecimal interestRate = BigDecimal.valueOf(0);
|
||||||
final Money principal = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
|
final Money outstandingBalance = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
|
||||||
final Money outstandingBalance = Money.of(monetaryCurrency, BigDecimal.ZERO);
|
|
||||||
|
|
||||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||||
Mockito.when(loanProductRelatedDetail.getPrincipal()).thenReturn(principal);
|
|
||||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
|
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
|
||||||
|
|||||||
+3
-4
@@ -43,12 +43,11 @@ import org.springframework.stereotype.Component;
|
|||||||
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PreGeneratedLoanSchedulePeriod> generateRepaymentPeriods(final LoanApplicationTerms loanApplicationTerms,
|
public List<PreGeneratedLoanSchedulePeriod> generateRepaymentPeriods(final LocalDate scheduledDueDate,
|
||||||
final HolidayDetailDTO holidayDetailDTO) {
|
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||||
final ArrayList<PreGeneratedLoanSchedulePeriod> repaymentPeriods = new ArrayList<>(numberOfRepayments);
|
final ArrayList<PreGeneratedLoanSchedulePeriod> repaymentPeriods = new ArrayList<>(numberOfRepayments);
|
||||||
// TODO: check periodStartDate
|
LocalDate lastRepaymentDate = scheduledDueDate;
|
||||||
LocalDate lastRepaymentDate = loanApplicationTerms.getRepaymentStartDate();
|
|
||||||
LocalDate nextRepaymentDate;
|
LocalDate nextRepaymentDate;
|
||||||
boolean isFirstRepayment = true;
|
boolean isFirstRepayment = true;
|
||||||
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
|
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
|
||||||
|
|||||||
+2
-1
@@ -98,7 +98,8 @@ public class DefaultScheduledDateGeneratorTest {
|
|||||||
submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null);
|
submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
List<PreGeneratedLoanSchedulePeriod> result = underTest.generateRepaymentPeriods(loanApplicationTerms, holidayDetailDTO);
|
List<PreGeneratedLoanSchedulePeriod> result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms,
|
||||||
|
holidayDetailDTO);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result.size()).isEqualTo(4);
|
assertThat(result.size()).isEqualTo(4);
|
||||||
|
|||||||
Reference in New Issue
Block a user