diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 0c9021998..a9a70e4b7 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -183,17 +183,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency)); final Loan loan = loanTransactions.get(0).getLoan(); - final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf(); - final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); - ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments, - loanProductRelatedDetail, installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc()); - ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, - changedTransactionDetail, scheduleModel); - LoanTermVariationsDataWrapper loanTermVariations = Optional .ofNullable(loan.getActiveLoanTermVariations()).map(loanTermVariationsSet -> loanTermVariationsSet.stream() .map(LoanTermVariations::toData).collect(Collectors.toCollection(ArrayList::new))) .map(LoanTermVariationsDataWrapper::new).orElse(null); + final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf(); + final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); + ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments, + loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc()); + ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, + changedTransactionDetail, scheduleModel); + List changeOperations = createSortedChangeList(loanTermVariations, loanTransactions, charges); List overpaidTransactions = new ArrayList<>(); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java index 94bc3f6eb..543ecb776 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java @@ -37,6 +37,7 @@ import lombok.Data; import lombok.experimental.Accessors; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; @Data @@ -46,35 +47,38 @@ public class ProgressiveLoanInterestScheduleModel { private final List repaymentPeriods; private final TreeSet interestRates; private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail; + private final LoanTermVariationsDataWrapper loanTermVariations; private final Integer installmentAmountInMultiplesOf; private final MathContext mc; private final Money zero; public ProgressiveLoanInterestScheduleModel(final List repaymentPeriods, - final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, - final MathContext mc) { + final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, + final LoanTermVariationsDataWrapper loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { this.repaymentPeriods = repaymentPeriods; this.interestRates = new TreeSet<>(Collections.reverseOrder()); this.loanProductRelatedDetail = loanProductRelatedDetail; + this.loanTermVariations = loanTermVariations; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.mc = mc; this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); } private ProgressiveLoanInterestScheduleModel(final List repaymentPeriods, final TreeSet interestRates, - final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, - final MathContext mc) { + final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, + final LoanTermVariationsDataWrapper loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { this.mc = mc; this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods, (previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod, mc)); this.interestRates = new TreeSet<>(interestRates); this.loanProductRelatedDetail = loanProductRelatedDetail; + this.loanTermVariations = loanTermVariations; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); } public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) { - return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail, + return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); } @@ -82,7 +86,7 @@ public class ProgressiveLoanInterestScheduleModel { final List repaymentPeriodCopies = copyRepaymentPeriods(repaymentPeriods, (previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate(), repaymentPeriod.getEmi().zero(), mc)); - return new ProgressiveLoanInterestScheduleModel(repaymentPeriodCopies, interestRates, loanProductRelatedDetail, + return new ProgressiveLoanInterestScheduleModel(repaymentPeriodCopies, interestRates, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); } 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 ab58c4237..d6053752c 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 @@ -90,7 +90,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { periodStartDate, loanApplicationTerms, holidayDetailDTO); final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), - loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); + loanApplicationTerms.getLoanTermVariations(), loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); final List periods = new ArrayList<>(expectedRepaymentPeriods.size()); prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java index 3f8641600..193e859da 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java @@ -25,6 +25,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Optional; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails; @@ -37,14 +38,14 @@ public interface EMICalculator { @NotNull ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List periods, - @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, - MathContext mc); + @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, + LoanTermVariationsDataWrapper loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc); @NotNull ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( @NotNull List installments, - @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, - MathContext mc); + @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, + LoanTermVariationsDataWrapper loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc); Optional findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate); 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 994d16432..e9d67f72d 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 @@ -35,6 +35,7 @@ import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.EmiAdjustment; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.InterestPeriod; @@ -57,9 +58,10 @@ public final class ProgressiveEMICalculator implements EMICalculator { @NotNull public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List periods, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, - final Integer installmentAmountInMultiplesOf, final MathContext mc) { + LoanTermVariationsDataWrapper loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate, - LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, loanTermVariations, + installmentAmountInMultiplesOf, mc); } @Override @@ -67,16 +69,17 @@ public final class ProgressiveEMICalculator implements EMICalculator { public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( @NotNull List installments, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, - final Integer installmentAmountInMultiplesOf, final MathContext mc) { + LoanTermVariationsDataWrapper loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList(); return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate, - LoanRepaymentScheduleInstallment::getDueDate, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + LoanRepaymentScheduleInstallment::getDueDate, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, + mc); } @NotNull private ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List periods, Function from, Function to, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, - final Integer installmentAmountInMultiplesOf, final MathContext mc) { + LoanTermVariationsDataWrapper loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); final AtomicReference prev = new AtomicReference<>(); List repaymentPeriods = periods.stream().map(e -> { @@ -84,7 +87,8 @@ public final class ProgressiveEMICalculator implements EMICalculator { prev.set(rp); return rp; }).toList(); - return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, loanProductRelatedDetail, loanTermVariations, + installmentAmountInMultiplesOf, mc); } @Override @@ -310,7 +314,8 @@ public final class ProgressiveEMICalculator implements EMICalculator { } calculateOutstandingBalance(scheduleModel); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); - if (onlyOnActualModelShouldApply) { + if (onlyOnActualModelShouldApply + && (scheduleModel.loanTermVariations() == null || scheduleModel.loanTermVariations().getDueDateVariation().isEmpty())) { checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods); } } @@ -415,9 +420,9 @@ public final class ProgressiveEMICalculator implements EMICalculator { final ProgressiveLoanInterestScheduleModel scheduleModel) { repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> { interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.getFromDate(), - interestPeriod.getDueDate())); + interestPeriod.getDueDate(), false)); interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, - interestPeriod.getFromDate(), repaymentPeriod.getDueDate())); + interestPeriod.getFromDate(), repaymentPeriod.getDueDate(), true)); }); } @@ -425,7 +430,8 @@ public final class ProgressiveEMICalculator implements EMICalculator { * Calculate Rate Factor for an exact Period */ private BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, - final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) { + final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate, + final boolean isTillDate) { final MathContext mc = scheduleModel.mc(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate), @@ -444,6 +450,14 @@ public final class ProgressiveEMICalculator implements EMICalculator { final int numberOfYearsDifferenceInPeriod = interestPeriodDueDate.getYear() - interestPeriodFromDate.getYear(); final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0; + long addedDays = !isTillDate || scheduleModel.loanTermVariations() == null ? 0L + : scheduleModel.loanTermVariations().getDueDateVariation().stream() + .filter(x -> !repaymentPeriod.getFromDate().isAfter(x.getTermVariationApplicableFrom()) + && !repaymentPeriod.getDueDate().isBefore(x.getTermVariationApplicableFrom()) + && !repaymentPeriod.getDueDate().isAfter(x.getDateValue())) + .map(x -> DateUtils.getDifferenceInDays(x.getTermVariationApplicableFrom(), x.getDateValue())) + .reduce(0L, Long::sum); + // TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data // TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30) // TODO refactor this logic to represent in interest period @@ -454,7 +468,7 @@ public final class ProgressiveEMICalculator implements EMICalculator { } return calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth, - daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc); + daysInYear, actualDaysInPeriod, calculatedDaysInPeriod.subtract(BigDecimal.valueOf(addedDays), mc), mc); } /** 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 edf91cd7a..852e778a6 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 @@ -32,7 +32,10 @@ import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; +import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.InterestPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; @@ -43,6 +46,7 @@ import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.ExtendWith; @@ -60,6 +64,7 @@ class ProgressiveEMICalculatorTest { private static MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN); private static LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = Mockito .mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class); + private static LoanTermVariationsDataWrapper loanTermVariations = Mockito.mock(LoanTermVariationsDataWrapper.class); private static final CurrencyData currency = new CurrencyData("USD", "USD", 2, 1, "$", "USD"); @@ -82,6 +87,11 @@ class ProgressiveEMICalculatorTest { moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN)); } + @BeforeEach + public void setup() { + Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new ArrayList<>()); + } + @AfterAll public static void tearDown() { threadLocalContextUtil.close(); @@ -168,7 +178,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); Assertions.assertTrue(interestScheduleModel != null); Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null); @@ -207,7 +217,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -251,7 +261,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -287,7 +297,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -323,7 +333,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -347,42 +357,6 @@ class ProgressiveEMICalculatorTest { checkPeriod(interestSchedule, 5, 0, 42.61, 0.007901833333, 0.33, 42.28, 0.0); } - @Test - public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { - final List expectedRepaymentPeriods = new ArrayList<>(); - - expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1))); - expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 15))); - expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 15), LocalDate.of(2024, 4, 15))); - expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 15), LocalDate.of(2024, 5, 15))); - expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 15), LocalDate.of(2024, 6, 15))); - expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 15), LocalDate.of(2024, 7, 15))); - - final BigDecimal interestRate = BigDecimal.valueOf(9.4822); - final Integer installmentAmountInMultiplesOf = null; - - Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); - Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); - Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); - Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); - Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); - - final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); - - final Money disbursedAmount = toMoney(100.0); - emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); - - checkPeriod(interestSchedule, 0, 0, 17.13, 0.0, 0.0, 0.79, 16.34, 83.66); - checkPeriod(interestSchedule, 0, 1, 17.13, 0.007901833333, 0.79, 16.34, 83.66); - checkPeriod(interestSchedule, 1, 0, 17.13, 0.007901833333, 0.66, 16.47, 67.19); - checkPeriod(interestSchedule, 2, 0, 17.13, 0.007901833333, 0.53, 16.60, 50.59); - checkPeriod(interestSchedule, 3, 0, 17.13, 0.007901833333, 0.40, 16.73, 33.86); - checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0); - checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0); - } - @Test public void test_reschedule_interest_on0201_4per_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { final List expectedRepaymentPeriods = new ArrayList<>(); @@ -407,7 +381,7 @@ class ProgressiveEMICalculatorTest { threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -449,7 +423,7 @@ class ProgressiveEMICalculatorTest { threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -496,7 +470,7 @@ class ProgressiveEMICalculatorTest { threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -542,7 +516,7 @@ class ProgressiveEMICalculatorTest { threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -588,7 +562,7 @@ class ProgressiveEMICalculatorTest { threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -695,7 +669,7 @@ class ProgressiveEMICalculatorTest { threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -766,7 +740,7 @@ class ProgressiveEMICalculatorTest { threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -843,7 +817,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); Money disbursedAmount = toMoney(100); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -892,7 +866,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -941,7 +915,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount); @@ -987,7 +961,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount); @@ -1023,7 +997,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), toMoney(300.0)); Assertions.assertEquals(6.15, toDouble(interestSchedule.getTotalDueInterest())); @@ -1065,7 +1039,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(1000.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -1098,7 +1072,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -1131,7 +1105,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -1164,7 +1138,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -1206,7 +1180,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(1000.0); LocalDate disbursementDate = LocalDate.of(2024, 1, 1); @@ -1254,7 +1228,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(1000.0); emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -1316,7 +1290,7 @@ class ProgressiveEMICalculatorTest { Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); final Money disbursedAmount1st = toMoney(1000.0); final Money disbursedAmount2nd = toMoney(1000.0); @@ -1368,6 +1342,126 @@ class ProgressiveEMICalculatorTest { checkDailyInterest(interestModel, dueDate, startDay, 31, 0.38, 9.03); } + @Test + public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 3, 1)), + repayment(2, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(3, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(4, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(5, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)), + repayment(6, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 8, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new ArrayList<>(List.of(// + new LoanTermVariationsData(1L, LoanTermVariationType.DUE_DATE.getValue(), // + LocalDate.of(2024, 2, 1), null, // + LocalDate.of(2024, 3, 1), false)))); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 1.13, 15.88, 84.12); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.13, 15.88, 84.12); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49, 16.52, 67.60); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 16.62, 50.98); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30, 16.71, 34.27); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20, 16.81, 17.46); + checkPeriod(interestSchedule, 5, 0, 17.56, 0.005833333333, 0.10, 17.46, 0.0); + } + + @Test + public void test_two_reschedules_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 3, 1)), + repayment(2, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(3, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 6, 1)), + repayment(4, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)), + repayment(5, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 8, 1)), + repayment(6, LocalDate.of(2024, 8, 1), LocalDate.of(2024, 9, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new ArrayList<>(List.of(// + new LoanTermVariationsData(1L, LoanTermVariationType.DUE_DATE.getValue(), // + LocalDate.of(2024, 2, 1), null, // + LocalDate.of(2024, 3, 1), false), // + new LoanTermVariationsData(2L, LoanTermVariationType.DUE_DATE.getValue(), // + LocalDate.of(2024, 5, 1), null, // + LocalDate.of(2024, 6, 1), false)))); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 1.13, 15.88, 84.12); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.13, 15.88, 84.12); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49, 16.52, 67.60); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.80, 16.21, 51.39); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30, 16.71, 34.68); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20, 16.81, 17.87); + checkPeriod(interestSchedule, 5, 0, 17.97, 0.005833333333, 0.10, 17.87, 0.0); + } + + @Test + public void test_reschedule_partial_period_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 4, 15)), + repayment(3, LocalDate.of(2024, 4, 15), LocalDate.of(2024, 5, 15)), + repayment(4, LocalDate.of(2024, 5, 15), LocalDate.of(2024, 6, 15)), + repayment(5, LocalDate.of(2024, 6, 15), LocalDate.of(2024, 7, 15)), + repayment(6, LocalDate.of(2024, 7, 15), LocalDate.of(2024, 8, 15))); + + final BigDecimal interestRate = BigDecimal.valueOf(7); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new ArrayList<>(List.of(// + new LoanTermVariationsData(1L, LoanTermVariationType.DUE_DATE.getValue(), // + LocalDate.of(2024, 3, 1), null, // + LocalDate.of(2024, 4, 15), false)))); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 16.43, 83.57); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 1.24, 15.77, 67.80); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.40, 16.61, 51.19); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30, 16.71, 34.48); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20, 16.81, 17.67); + checkPeriod(interestSchedule, 5, 0, 17.77, 0.005833333333, 0.10, 17.67, 0.0); + } + private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) { final Money zeroAmount = Money.zero(currency); return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,