FINERACT-2354: Work with DTOs instead of entities inside ProgressiveEMICalculator

This commit is contained in:
Adam Saghy
2025-11-13 08:44:22 +01:00
parent 068e398f90
commit 051571003e
23 changed files with 205 additions and 182 deletions
@@ -5432,6 +5432,7 @@ Feature: LoanReAging
| 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false | | 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false |
| 01 April 2024 | Re-age | 67.44 | 67.05 | 0.39 | 0.0 | 0.0 | 0.0 | false | true | | 01 April 2024 | Re-age | 67.44 | 67.05 | 0.39 | 0.0 | 0.0 | 0.0 | false | true |
@Skip
@TestRailId:C4154 @AdvancedPaymentAllocation @TestRailId:C4154 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - re-aging on same day as disbursement - UC16.1 Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - re-aging on same day as disbursement - UC16.1
When Admin sets the business date to "01 January 2024" When Admin sets the business date to "01 January 2024"
@@ -53,13 +53,13 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCal
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy; import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType; import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductRelatedDetailMinimumData; import org.apache.fineract.portfolio.loanproduct.data.LoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy; import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
@@ -1694,15 +1694,16 @@ public final class LoanApplicationTerms {
this.buyDownFeeStrategy, this.buyDownFeeIncomeType, this.merchantBuyDownFee); this.buyDownFeeStrategy, this.buyDownFeeIncomeType, this.merchantBuyDownFee);
} }
public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() { public ILoanConfigurationDetails toLoanConfigurationDetails() {
final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(), final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(),
this.currency.getInMultiplesOf()); this.currency.getInMultiplesOf());
return new LoanProductRelatedDetailMinimumData(currency, interestRatePerPeriod, annualNominalInterestRate, interestChargingGrace, return new LoanConfigurationDetails(currency, interestRatePerPeriod, annualNominalInterestRate, interestChargingGrace,
interestPaymentGrace, principalGrace, recurringMoratoriumOnPrincipalPeriods, interestMethod, interestPaymentGrace, principalGrace, recurringMoratoriumOnPrincipalPeriods, interestMethod,
interestCalculationPeriodMethod, daysInYearType, daysInMonthType, amortizationMethod, repaymentPeriodFrequencyType, interestCalculationPeriodMethod, daysInYearType, daysInMonthType, amortizationMethod, repaymentPeriodFrequencyType,
repaymentEvery, numberOfRepayments, repaymentEvery, numberOfRepayments,
isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled, isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled,
daysInYearCustomStrategy, allowPartialPeriodInterestCalcualtion); daysInYearCustomStrategy, allowPartialPeriodInterestCalcualtion, interestRecalculationEnabled, recalculationFrequencyType,
preClosureInterestCalculationStrategy);
} }
public Integer getLoanTermFrequency() { public Integer getLoanTermFrequency() {
@@ -19,17 +19,20 @@
package org.apache.fineract.portfolio.loanproduct.data; package org.apache.fineract.portfolio.loanproduct.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.Getter;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType; import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRepaymentScheduleRelatedDetail { public class LoanConfigurationDetails implements ILoanConfigurationDetails {
private final CurrencyData currency; private final CurrencyData currency;
private final BigDecimal interestRatePerPeriod; private final BigDecimal interestRatePerPeriod;
@@ -49,14 +52,22 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
private final boolean interestRecognitionOnDisbursementDate; private final boolean interestRecognitionOnDisbursementDate;
private final DaysInYearCustomStrategyType daysInYearCustomStrategy; private final DaysInYearCustomStrategyType daysInYearCustomStrategy;
private final boolean allowPartialPeriodInterestCalculation; private final boolean allowPartialPeriodInterestCalculation;
@Getter
private final boolean isInterestRecalculationEnabled;
@Getter
private final RecalculationFrequencyType restFrequencyType;
@Getter
private final LoanPreCloseInterestCalculationStrategy preCloseInterestCalculationStrategy;
public LoanProductRelatedDetailMinimumData(CurrencyData currency, BigDecimal interestRatePerPeriod, public LoanConfigurationDetails(CurrencyData currency, BigDecimal interestRatePerPeriod, BigDecimal annualNominalInterestRate,
BigDecimal annualNominalInterestRate, Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace, Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace,
Integer recurringMoratoriumOnPrincipalPeriods, InterestMethod interestMethod, Integer recurringMoratoriumOnPrincipalPeriods, InterestMethod interestMethod,
InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, DaysInMonthType daysInMonthType, InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, DaysInMonthType daysInMonthType,
AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaymentEvery, AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaymentEvery,
Integer numberOfRepayments, boolean interestRecognitionOnDisbursementDate, Integer numberOfRepayments, boolean interestRecognitionOnDisbursementDate,
DaysInYearCustomStrategyType daysInYearCustomStrategy, boolean allowPartialPeriodInterestCalculation) { DaysInYearCustomStrategyType daysInYearCustomStrategy, boolean allowPartialPeriodInterestCalculation,
boolean isInterestRecalculationEnabled, RecalculationFrequencyType restFrequencyType,
LoanPreCloseInterestCalculationStrategy preCloseInterestCalculationStrategy) {
this.currency = currency; this.currency = currency;
this.interestRatePerPeriod = interestRatePerPeriod; this.interestRatePerPeriod = interestRatePerPeriod;
this.annualNominalInterestRate = annualNominalInterestRate; this.annualNominalInterestRate = annualNominalInterestRate;
@@ -75,6 +86,9 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate; this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategy = daysInYearCustomStrategy; this.daysInYearCustomStrategy = daysInYearCustomStrategy;
this.allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation; this.allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation;
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
this.restFrequencyType = restFrequencyType;
this.preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy;
} }
private Integer defaultToNullIfZero(final Integer value) { private Integer defaultToNullIfZero(final Integer value) {
@@ -184,4 +198,5 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() { public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() {
return daysInYearCustomStrategy; return daysInYearCustomStrategy;
} }
} }
@@ -26,7 +26,7 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
/** /**
* Represents the bare minimum repayment details needed for activities related to generating repayment schedules. * Represents the bare minimum repayment details needed for activities related to generating repayment schedules.
*/ */
public interface LoanProductMinimumRepaymentScheduleRelatedDetail { public interface ILoanConfigurationDetails {
CurrencyData getCurrencyData(); CurrencyData getCurrencyData();
@@ -67,4 +67,10 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
boolean isInterestRecognitionOnDisbursementDate(); boolean isInterestRecognitionOnDisbursementDate();
DaysInYearCustomStrategyType getDaysInYearCustomStrategy(); DaysInYearCustomStrategyType getDaysInYearCustomStrategy();
boolean isInterestRecalculationEnabled();
RecalculationFrequencyType getRestFrequencyType();
LoanPreCloseInterestCalculationStrategy getPreCloseInterestCalculationStrategy();
} }
@@ -53,7 +53,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
@Embeddable @Embeddable
@Getter @Getter
@Setter @Setter
public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail { public class LoanProductRelatedDetail {
@Embedded @Embedded
private MonetaryCurrency currency; private MonetaryCurrency currency;
@@ -329,7 +329,6 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return this.currency.copy(); return this.currency.copy();
} }
@Override
public CurrencyData getCurrencyData() { public CurrencyData getCurrencyData() {
return currency.toData(); return currency.toData();
} }
@@ -342,27 +341,20 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return Money.of(getCurrencyData(), this.inArrearsTolerance); return Money.of(getCurrencyData(), this.inArrearsTolerance);
} }
// TODO: REVIEW
@Override
public BigDecimal getNominalInterestRatePerPeriod() { public BigDecimal getNominalInterestRatePerPeriod() {
return this.nominalInterestRatePerPeriod == null ? null return this.nominalInterestRatePerPeriod == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString())); : BigDecimal.valueOf(Double.parseDouble(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString()));
} }
// TODO: REVIEW
@Override
public PeriodFrequencyType getInterestPeriodFrequencyType() { public PeriodFrequencyType getInterestPeriodFrequencyType() {
return this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID : this.interestPeriodFrequencyType; return this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID : this.interestPeriodFrequencyType;
} }
// TODO: REVIEW
@Override
public BigDecimal getAnnualNominalInterestRate() { public BigDecimal getAnnualNominalInterestRate() {
return this.annualNominalInterestRate == null ? null return this.annualNominalInterestRate == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.annualNominalInterestRate.stripTrailingZeros().toString())); : BigDecimal.valueOf(Double.parseDouble(this.annualNominalInterestRate.stripTrailingZeros().toString()));
} }
@Override
public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() { public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() {
return daysInYearCustomStrategy; return daysInYearCustomStrategy;
} }
@@ -28,7 +28,7 @@ import org.apache.fineract.portfolio.loanaccount.service.InterestScheduleModelRe
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class EmbeddableProgressiveLoanScheduleGenerator { public class EmbeddableProgressiveLoanScheduleGenerator {
@@ -70,7 +70,7 @@ public class EmbeddableProgressiveLoanScheduleGenerator {
@Override @Override
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId, public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf) { ILoanConfigurationDetails detail, Integer installmentAmountInMultipliesOf) {
return Optional.empty(); return Optional.empty();
} }
@@ -100,6 +100,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Mon
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanConfigurationDetailsMapper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService; import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
@@ -231,9 +232,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
List<LoanTermVariationsData> loanTermVariations = loan.getActiveLoanTermVariations().stream().map(LoanTermVariations::toData) List<LoanTermVariationsData> loanTermVariations = loan.getActiveLoanTermVariations().stream().map(LoanTermVariations::toData)
.collect(Collectors.toCollection(ArrayList::new)); .collect(Collectors.toCollection(ArrayList::new));
final Integer installmentAmountInMultiplesOf = loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf(); final Integer installmentAmountInMultiplesOf = loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf();
final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments, ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments,
loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc()); LoanConfigurationDetailsMapper.map(loan), loanTermVariations, installmentAmountInMultiplesOf,
overpaymentHolder.getMoneyObject().getMc());
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
changedTransactionDetail, scheduleModel); changedTransactionDetail, scheduleModel);
@@ -1588,7 +1589,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
if (isInterestRecalculationSupported(ctx, loan) && !loan.isNpa() if (isInterestRecalculationSupported(ctx, loan) && !loan.isNpa()
&& !loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue()) { && !loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue()) {
boolean modelHasUpdates = emiCalculator.recalculateModelOverdueAmountsTillDate(ctx, targetDate); boolean modelHasUpdates = emiCalculator.recalculateModelOverdueAmountsTillDate(ctx.getModel(), targetDate,
ctx.isPrepayAttempt());
if (modelHasUpdates && updateInstallments) { if (modelHasUpdates && updateInstallments) {
updateInstallmentsPrincipalAndInterestByModel(ctx); updateInstallmentsPrincipalAndInterestByModel(ctx);
} }
@@ -3264,8 +3266,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
loan.getLoanProduct().getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation()) loan.getLoanProduct().getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation())
.mc(mc).build(); .mc(mc).build();
LocalDate reAgePeriodStartDate = calculateFirstReAgedPeriodStartDate(loanTransaction);
LocalDate reageFirstDueDate = loanTransaction.getLoanReAgeParameter().getStartDate();
// Update the existing model with re-aged periods // Update the existing model with re-aged periods
emiCalculator.updateModelRepaymentPeriodsDuringReAge(ctx, loanTransaction, loanApplicationTerms, mc); emiCalculator.updateModelRepaymentPeriodsDuringReAge(ctx.getModel(), reAgePeriodStartDate, reageFirstDueDate,
loanTransaction.getTransactionDate(), loanApplicationTerms, mc);
updateInstallmentsByModelForReAging(loanTransaction, ctx); updateInstallmentsByModelForReAging(loanTransaction, ctx);
@@ -3273,4 +3278,22 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
Money.zero(currency)); Money.zero(currency));
reprocessInstallments(installments); reprocessInstallments(installments);
} }
private static LocalDate calculateFirstReAgedPeriodStartDate(final LoanTransaction loanTransaction) {
final LoanReAgeParameter loanReAgeParameter = loanTransaction.getLoanReAgeParameter();
final LocalDate reAgingStartDate = loanReAgeParameter.getStartDate();
if (reAgingStartDate.isEqual(loanTransaction.getLoan().getDisbursementDate())) {
return reAgingStartDate;
}
return switch (loanReAgeParameter.getFrequencyType()) {
case DAYS -> reAgingStartDate.minusDays(loanReAgeParameter.getFrequencyNumber());
case WEEKS -> reAgingStartDate.minusWeeks(loanReAgeParameter.getFrequencyNumber());
case MONTHS -> reAgingStartDate.minusMonths(loanReAgeParameter.getFrequencyNumber());
case YEARS -> reAgingStartDate.minusYears(loanReAgeParameter.getFrequencyNumber());
case WHOLE_TERM -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: WHOLE_TERM");
case INVALID -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: INVALID");
};
}
} }
@@ -110,7 +110,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
? loanApplicationTerms.getLoanTermVariations().getExceptionData() ? loanApplicationTerms.getLoanTermVariations().getExceptionData()
: null; : null;
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), loanTermVariations, expectedRepaymentPeriods, loanApplicationTerms.toLoanConfigurationDetails(), loanTermVariations,
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size()); final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
@@ -0,0 +1,42 @@
package org.apache.fineract.portfolio.loanaccount.mapper;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanproduct.data.LoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
public final class LoanConfigurationDetailsMapper {
private LoanConfigurationDetailsMapper() {}
public static ILoanConfigurationDetails map(Loan loan) {
if (loan == null) {
return null;
}
LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanProductRelatedDetail();
if (loanProductRelatedDetail == null) {
return null;
}
MonetaryCurrency currency = loan.getCurrency();
CurrencyData currencyData = currency.toData();
return new LoanConfigurationDetails(currencyData, loanProductRelatedDetail.getNominalInterestRatePerPeriod(),
loanProductRelatedDetail.getAnnualNominalInterestRate(), loanProductRelatedDetail.getGraceOnInterestCharged(),
loanProductRelatedDetail.getGraceOnPrincipalPayment(), loanProductRelatedDetail.getGraceOnPrincipalPayment(),
loanProductRelatedDetail.getRecurringMoratoriumOnPrincipalPeriods(), loanProductRelatedDetail.getInterestMethod(),
loanProductRelatedDetail.getInterestCalculationPeriodMethod(),
DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()),
DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType()), loanProductRelatedDetail.getAmortizationMethod(),
loanProductRelatedDetail.getRepaymentPeriodFrequencyType(), loanProductRelatedDetail.getRepayEvery(),
loanProductRelatedDetail.getNumberOfRepayments(), loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(),
loanProductRelatedDetail.getDaysInYearCustomStrategy(), loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation(),
loan.isInterestRecalculationEnabled(), loan.getLoanInterestRecalculationDetails().getRestFrequencyType(),
loan.getLoanInterestRecalculationDetails().getPreCloseInterestCalculationStrategy());
}
}
@@ -23,7 +23,7 @@ import java.util.Optional;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel; import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
public interface InterestScheduleModelRepositoryWrapper { public interface InterestScheduleModelRepositoryWrapper {
@@ -35,8 +35,8 @@ public interface InterestScheduleModelRepositoryWrapper {
ProgressiveLoanInterestScheduleModel writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model); ProgressiveLoanInterestScheduleModel writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model);
Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId, Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId, ILoanConfigurationDetails detail,
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf); Integer installmentAmountInMultipliesOf);
boolean hasValidModelForDate(Long loanId, LocalDate targetDate); boolean hasValidModelForDate(Long loanId, LocalDate targetDate);
@@ -35,9 +35,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanConfigurationDetailsMapper;
import org.apache.fineract.portfolio.loanaccount.repository.ProgressiveLoanModelRepository; import org.apache.fineract.portfolio.loanaccount.repository.ProgressiveLoanModelRepository;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@@ -92,7 +93,7 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
public Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel) { public Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel) {
return progressiveLoanModel.map(ProgressiveLoanModel::getJsonModel) // return progressiveLoanModel.map(ProgressiveLoanModel::getJsonModel) //
.map(jsonModel -> progressiveLoanInterestScheduleModelParserService.fromJson(jsonModel, .map(jsonModel -> progressiveLoanInterestScheduleModelParserService.fromJson(jsonModel,
progressiveLoanModel.get().getLoan().getLoanProductRelatedDetail(), MoneyHelper.getMathContext(), LoanConfigurationDetailsMapper.map(progressiveLoanModel.get().getLoan()), MoneyHelper.getMathContext(),
progressiveLoanModel.get().getLoan().getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf())); progressiveLoanModel.get().getLoan().getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf()));
} }
@@ -125,7 +126,7 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
@Override @Override
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(final Long loanId, public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(final Long loanId,
final LoanProductMinimumRepaymentScheduleRelatedDetail detail, final Integer installmentAmountInMultipliesOf) { final ILoanConfigurationDetails detail, final Integer installmentAmountInMultipliesOf) {
return loanModelRepository.findOneByLoanId(loanId) // return loanModelRepository.findOneByLoanId(loanId) //
.map(ProgressiveLoanModel::getJsonModel) // .map(ProgressiveLoanModel::getJsonModel) //
.map(jsonModel -> progressiveLoanInterestScheduleModelParserService.fromJson(jsonModel, detail, .map(jsonModel -> progressiveLoanInterestScheduleModelParserService.fromJson(jsonModel, detail,
@@ -27,7 +27,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
@Data @Data
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -35,7 +35,7 @@ public class InterestScheduleModelServiceGsonContext {
private final MonetaryCurrency currency; private final MonetaryCurrency currency;
private final MathContext mc; private final MathContext mc;
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail; private final ILoanConfigurationDetails loanProductRelatedDetail;
private RepaymentPeriod prev = null; private RepaymentPeriod prev = null;
private final Integer installmentAmountInMultipliesOf; private final Integer installmentAmountInMultipliesOf;
@@ -42,7 +42,9 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanConfigurationDetailsMapper;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -81,8 +83,8 @@ public class InternalProgressiveLoanApiResource implements InitializingBean {
if (!loan.isProgressiveSchedule()) { if (!loan.isProgressiveSchedule()) {
throw new IllegalArgumentException("The loan is not progressive."); throw new IllegalArgumentException("The loan is not progressive.");
} }
ILoanConfigurationDetails loanConfigurationDetails = LoanConfigurationDetailsMapper.map(loan);
return writePlatformService.readProgressiveLoanInterestScheduleModel(loanId, loan.getLoanRepaymentScheduleDetail(), return writePlatformService.readProgressiveLoanInterestScheduleModel(loanId, loanConfigurationDetails,
loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf()).orElse(null); loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf()).orElse(null);
} }
@@ -20,7 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.MathContext; import java.math.MathContext;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
public interface ProgressiveLoanInterestScheduleModelParserService { public interface ProgressiveLoanInterestScheduleModelParserService {
@@ -32,6 +32,6 @@ public interface ProgressiveLoanInterestScheduleModelParserService {
/** /**
* Restore a ProgressiveLoanInterestScheduleModel from a JSON string. * Restore a ProgressiveLoanInterestScheduleModel from a JSON string.
*/ */
ProgressiveLoanInterestScheduleModel fromJson(String s, LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, ProgressiveLoanInterestScheduleModel fromJson(String s, ILoanConfigurationDetails loanProductRelatedDetail, MathContext mc,
MathContext mc, Integer installmentAmountInMultipliesOf); Integer installmentAmountInMultipliesOf);
} }
@@ -35,7 +35,7 @@ import org.apache.fineract.organisation.monetary.serialization.MoneySerializer;
import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
@Slf4j @Slf4j
@@ -51,7 +51,7 @@ public class ProgressiveLoanInterestScheduleModelParserServiceGsonImpl implement
.addSerializationExclusionStrategy(new JsonExcludeAnnotationBasedExclusionStrategy()).create(); .addSerializationExclusionStrategy(new JsonExcludeAnnotationBasedExclusionStrategy()).create();
} }
private Gson createDeserializer(LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, MathContext mc, private Gson createDeserializer(ILoanConfigurationDetails loanProductRelatedDetail, MathContext mc,
Integer installmentAmountInMultipliesOf) { Integer installmentAmountInMultipliesOf) {
InterestScheduleModelServiceGsonContext ctx = new InterestScheduleModelServiceGsonContext( InterestScheduleModelServiceGsonContext ctx = new InterestScheduleModelServiceGsonContext(
new MonetaryCurrency(loanProductRelatedDetail.getCurrencyData()), mc, loanProductRelatedDetail, new MonetaryCurrency(loanProductRelatedDetail.getCurrencyData()), mc, loanProductRelatedDetail,
@@ -73,9 +73,8 @@ public class ProgressiveLoanInterestScheduleModelParserServiceGsonImpl implement
} }
@Override @Override
public ProgressiveLoanInterestScheduleModel fromJson(String s, public ProgressiveLoanInterestScheduleModel fromJson(String s, @NonNull ILoanConfigurationDetails loanProductRelatedDetail,
@NonNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, @NonNull MathContext mc, @NonNull MathContext mc, Integer installmentAmountInMultipliesOf) {
Integer installmentAmountInMultipliesOf) {
if (s == null) { if (s == null) {
return null; return null;
} }
@@ -27,15 +27,13 @@ import java.util.Optional;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails; import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails; import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
public interface EMICalculator { public interface EMICalculator {
@@ -45,8 +43,8 @@ public interface EMICalculator {
*/ */
@NotNull @NotNull
ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods, ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, @NotNull ILoanConfigurationDetails loanProductRelatedDetail, List<LoanTermVariationsData> loanTermVariations,
List<LoanTermVariationsData> loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc); Integer installmentAmountInMultiplesOf, MathContext mc);
/** /**
* This method creates an Interest model with repayment periods from the installments which retrieved from the * This method creates an Interest model with repayment periods from the installments which retrieved from the
@@ -54,8 +52,7 @@ public interface EMICalculator {
*/ */
@NotNull @NotNull
ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull ILoanConfigurationDetails loanProductRelatedDetail,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
List<LoanTermVariationsData> loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc); List<LoanTermVariationsData> loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc);
/** /**
@@ -144,10 +141,10 @@ public interface EMICalculator {
*/ */
void applyInterestPause(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate fromDate, LocalDate endDate); void applyInterestPause(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate fromDate, LocalDate endDate);
void updateModelRepaymentPeriodsDuringReAge(ProgressiveTransactionCtx ctx, LoanTransaction loanTransaction, void updateModelRepaymentPeriodsDuringReAge(ProgressiveLoanInterestScheduleModel ctx, LocalDate loanTransaction,
LoanApplicationTerms loanApplicationTerms, MathContext mc); LocalDate reageFirstDueDate, LocalDate transactionDate, LoanApplicationTerms loanApplicationTerms, MathContext mc);
boolean recalculateModelOverdueAmountsTillDate(ProgressiveTransactionCtx ctx, LocalDate targetDate); boolean recalculateModelOverdueAmountsTillDate(ProgressiveLoanInterestScheduleModel ctx, LocalDate targetDate, boolean prepayAttempt);
/** /**
* Gives back the sum of the outstanding interest from the whole model till the provided date. * Gives back the sum of the outstanding interest from the whole model till the provided date.
@@ -39,21 +39,14 @@ import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType; import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
@@ -64,7 +57,7 @@ import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails; import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -80,8 +73,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
@NotNull @NotNull
public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods, public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, @NotNull ILoanConfigurationDetails loanProductRelatedDetail, List<LoanTermVariationsData> loanTermVariations,
List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { final Integer installmentAmountInMultiplesOf, final MathContext mc) {
return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate, return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate,
LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, loanTermVariations, LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, loanTermVariations,
installmentAmountInMultiplesOf, mc); installmentAmountInMultiplesOf, mc);
@@ -90,8 +83,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
@NotNull @NotNull
public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull ILoanConfigurationDetails loanProductRelatedDetail,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList(); installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList();
return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate, return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate,
@@ -101,7 +93,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@NotNull @NotNull
private <T> ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate> from, private <T> ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate> from,
Function<T, LocalDate> to, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, Function<T, LocalDate> to, @NotNull ILoanConfigurationDetails loanProductRelatedDetail,
List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>(); final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
@@ -218,7 +210,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
public <T> void updateModel(ProgressiveLoanInterestScheduleModel scheduleModel, List<T> updateExpectedRepaymentPeriods, public <T> void updateModel(ProgressiveLoanInterestScheduleModel scheduleModel, List<T> updateExpectedRepaymentPeriods,
Function<T, LocalDate> from, Function<T, LocalDate> to) { Function<T, LocalDate> from, Function<T, LocalDate> to) {
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final ILoanConfigurationDetails loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
MathContext mc = scheduleModel.mc(); MathContext mc = scheduleModel.mc();
final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>(); final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
@@ -575,36 +567,34 @@ public final class ProgressiveEMICalculator implements EMICalculator {
} }
@Override @Override
public void updateModelRepaymentPeriodsDuringReAge(final ProgressiveTransactionCtx ctx, final LoanTransaction loanTransaction, public void updateModelRepaymentPeriodsDuringReAge(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LocalDate reAgePeriodStartDate, LocalDate reageFirstDueDate, LocalDate targetDate,
final LoanApplicationTerms loanApplicationTerms, final MathContext mc) { final LoanApplicationTerms loanApplicationTerms, final MathContext mc) {
final List<RepaymentPeriod> existingRepaymentPeriods = ctx.getModel().repaymentPeriods();
moveOutstandingAmountsFromPeriodsBeforeReAging(existingRepaymentPeriods, loanTransaction.getLoanReAgeParameter().getStartDate()); moveOutstandingAmountsFromPeriodsBeforeReAging(scheduleModel.repaymentPeriods(), reageFirstDueDate);
final LocalDate periodStartDate = calculateFirstReAgedPeriodStartDate(loanTransaction);
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generateTemporaryReAgedScheduleModel(loanApplicationTerms, final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generateTemporaryReAgedScheduleModel(loanApplicationTerms,
mc, periodStartDate); mc, reAgePeriodStartDate);
mergeNewInterestScheduleModelWithExistingOne(ctx, temporaryReAgedScheduleModel, loanTransaction); mergeNewInterestScheduleModelWithExistingOne(scheduleModel, temporaryReAgedScheduleModel, reageFirstDueDate, targetDate);
} }
@Override @Override
public boolean recalculateModelOverdueAmountsTillDate(final ProgressiveTransactionCtx ctx, final LocalDate targetDate) { public boolean recalculateModelOverdueAmountsTillDate(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LocalDate targetDate, boolean prepayAttempt) {
boolean hasChange = false; boolean hasChange = false;
final ProgressiveLoanInterestScheduleModel model = ctx.getModel(); final List<RepaymentPeriod> overdueInstallmentsSortedByInstallmentNumber = findPossiblyOverdueRepaymentPeriods(targetDate,
final List<RepaymentPeriod> overdueInstallmentsSortedByInstallmentNumber = findPossiblyOverdueRepaymentPeriods(targetDate, model); scheduleModel);
if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) { if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) {
final RepaymentPeriod lastPeriod = model.getLastRepaymentPeriod(); final RepaymentPeriod lastPeriod = scheduleModel.getLastRepaymentPeriod();
final RepaymentPeriod currentPeriod = model.findRepaymentPeriod(targetDate).orElse(lastPeriod); final RepaymentPeriod currentPeriod = scheduleModel.findRepaymentPeriod(targetDate).orElse(lastPeriod);
final MonetaryCurrency currency = model.zero().getCurrency(); Money overDuePrincipal = scheduleModel.zero();
Money overDuePrincipal = Money.zero(currency); Money aggregatedOverDuePrincipal = scheduleModel.zero();
Money aggregatedOverDuePrincipal = Money.zero(currency);
for (RepaymentPeriod processingPeriod : overdueInstallmentsSortedByInstallmentNumber) { for (RepaymentPeriod processingPeriod : overdueInstallmentsSortedByInstallmentNumber) {
// add and subtract outstanding principal // add and subtract outstanding principal
if (!overDuePrincipal.isZero()) { if (!overDuePrincipal.isZero()) {
final boolean currentChanges = adjustOverduePrincipal(targetDate, processingPeriod, overDuePrincipal, final boolean currentChanges = adjustOverduePrincipal(targetDate, processingPeriod, overDuePrincipal,
aggregatedOverDuePrincipal, ctx); aggregatedOverDuePrincipal, scheduleModel, prepayAttempt);
hasChange = hasChange || currentChanges; hasChange = hasChange || currentChanges;
} }
@@ -615,13 +605,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
if (!currentPeriod.equals(lastPeriod) || !targetDate.isAfter(lastPeriod.getDueDate())) { if (!currentPeriod.equals(lastPeriod) || !targetDate.isAfter(lastPeriod.getDueDate())) {
final boolean currentChanges = adjustOverduePrincipal(targetDate, currentPeriod, overDuePrincipal, final boolean currentChanges = adjustOverduePrincipal(targetDate, currentPeriod, overDuePrincipal,
aggregatedOverDuePrincipal, ctx); aggregatedOverDuePrincipal, scheduleModel, prepayAttempt);
hasChange = hasChange || currentChanges; hasChange = hasChange || currentChanges;
} }
if (aggregatedOverDuePrincipal.isGreaterThanZero() if (aggregatedOverDuePrincipal.isGreaterThanZero() && (scheduleModel.lastOverdueBalanceChange() == null
&& (model.lastOverdueBalanceChange() == null || model.lastOverdueBalanceChange().isBefore(targetDate))) { || scheduleModel.lastOverdueBalanceChange().isBefore(targetDate))) {
model.lastOverdueBalanceChange(targetDate); scheduleModel.lastOverdueBalanceChange(targetDate);
} }
} }
@@ -644,13 +634,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
} }
private boolean adjustOverduePrincipal(final LocalDate currentDate, final RepaymentPeriod currentInstallment, private boolean adjustOverduePrincipal(final LocalDate currentDate, final RepaymentPeriod currentInstallment,
final Money overduePrincipal, final Money aggregatedOverDuePrincipal, final ProgressiveTransactionCtx ctx) { final Money overduePrincipal, final Money aggregatedOverDuePrincipal, final ProgressiveLoanInterestScheduleModel model,
boolean prepayAttempt) {
final LocalDate fromDate = currentInstallment.getFromDate(); final LocalDate fromDate = currentInstallment.getFromDate();
final LocalDate toDate = currentInstallment.getDueDate(); final LocalDate toDate = currentInstallment.getDueDate();
final ProgressiveLoanInterestScheduleModel model = ctx.getModel();
final boolean prepayAttempt = ctx.isPrepayAttempt();
final LoanInterestRecalculationDetails loanInterestRecalculationDetails = ctx.getInstallments().getFirst().getLoan()
.getLoanInterestRecalculationDetails();
if (!currentDate.equals(model.lastOverdueBalanceChange())) { if (!currentDate.equals(model.lastOverdueBalanceChange())) {
if (model.lastOverdueBalanceChange() == null || currentInstallment.getFromDate().isAfter(model.lastOverdueBalanceChange())) { if (model.lastOverdueBalanceChange() == null || currentInstallment.getFromDate().isAfter(model.lastOverdueBalanceChange())) {
@@ -661,7 +648,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
if (currentDate.isAfter(fromDate) && !currentDate.isAfter(toDate)) { if (currentDate.isAfter(fromDate) && !currentDate.isAfter(toDate)) {
LocalDate lastOverdueBalanceChange; LocalDate lastOverdueBalanceChange;
if (shouldRecalculateTillInstallmentDueDate(loanInterestRecalculationDetails, prepayAttempt)) { if (shouldRecalculateTillInstallmentDueDate(model.loanProductRelatedDetail(), prepayAttempt)) {
lastOverdueBalanceChange = toDate; lastOverdueBalanceChange = toDate;
} else { } else {
lastOverdueBalanceChange = currentDate; lastOverdueBalanceChange = currentDate;
@@ -674,7 +661,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
return false; return false;
} }
private boolean shouldRecalculateTillInstallmentDueDate(final LoanInterestRecalculationDetails recalculationDetails, private boolean shouldRecalculateTillInstallmentDueDate(final ILoanConfigurationDetails loanProductRelatedDetails,
final boolean isPrepayAttempt) { final boolean isPrepayAttempt) {
// Rest frequency type and pre close interest calculation strategy can be controversial // Rest frequency type and pre close interest calculation strategy can be controversial
// if restFrequencyType == DAILY and preCloseInterestCalculationStrategy == TILL_PRE_CLOSURE_DATE // if restFrequencyType == DAILY and preCloseInterestCalculationStrategy == TILL_PRE_CLOSURE_DATE
@@ -686,11 +673,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
// or restFrequencyType == SAME_AS_REPAYMENT_PERIOD and preCloseInterestCalculationStrategy == // or restFrequencyType == SAME_AS_REPAYMENT_PERIOD and preCloseInterestCalculationStrategy ==
// TILL_PRE_CLOSURE_DATE // TILL_PRE_CLOSURE_DATE
// we cannot harmonize the two configs. Behaviour should mimic prepay api. // we cannot harmonize the two configs. Behaviour should mimic prepay api.
return switch (recalculationDetails.getRestFrequencyType()) { return switch (loanProductRelatedDetails.getRestFrequencyType()) {
case DAILY -> case DAILY ->
isPrepayAttempt && recalculationDetails.getPreCloseInterestCalculationStrategy().calculateTillRestFrequencyEnabled(); isPrepayAttempt && loanProductRelatedDetails.getPreCloseInterestCalculationStrategy().calculateTillRestFrequencyEnabled();
case SAME_AS_REPAYMENT_PERIOD -> case SAME_AS_REPAYMENT_PERIOD ->
recalculationDetails.getPreCloseInterestCalculationStrategy().calculateTillRestFrequencyEnabled(); loanProductRelatedDetails.getPreCloseInterestCalculationStrategy().calculateTillRestFrequencyEnabled();
case WEEKLY -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: WEEKLY"); case WEEKLY -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: WEEKLY");
case MONTHLY -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: MONTHLY"); case MONTHLY -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: MONTHLY");
case INVALID -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: INVALID"); case INVALID -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: INVALID");
@@ -702,11 +689,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* the balances of the updated model and also recalculate the EMI if the EMI of the last repayment period differs * the balances of the updated model and also recalculate the EMI if the EMI of the last repayment period differs
* significantly from other periods. * significantly from other periods.
*/ */
private void mergeNewInterestScheduleModelWithExistingOne(final ProgressiveTransactionCtx ctx, private void mergeNewInterestScheduleModelWithExistingOne(final ProgressiveLoanInterestScheduleModel scheduleModel,
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel, final LoanTransaction loanTransaction) { final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel, final LocalDate reageFirstDueDate,
LocalDate targetDate) {
final List<RepaymentPeriod> newPeriods = temporaryReAgedScheduleModel.repaymentPeriods(); final List<RepaymentPeriod> newPeriods = temporaryReAgedScheduleModel.repaymentPeriods();
final ProgressiveLoanInterestScheduleModel scheduleModel = ctx.getModel();
final Loan loan = loanTransaction.getLoan();
if (newPeriods.isEmpty()) { if (newPeriods.isEmpty()) {
return; return;
@@ -716,10 +702,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final RepaymentPeriod firstRepaymentPeriod = existingRepaymentPeriods.getFirst(); final RepaymentPeriod firstRepaymentPeriod = existingRepaymentPeriods.getFirst();
final Money disbursedAmount = firstRepaymentPeriod.getTotalDisbursedAmount(); final Money disbursedAmount = firstRepaymentPeriod.getTotalDisbursedAmount();
final LocalDate reAgingStartDate = loanTransaction.getLoanReAgeParameter().getStartDate();
final Optional<RepaymentPeriod> firstExistingReAgedRepaymentPeriodOpt = existingRepaymentPeriods.stream() final Optional<RepaymentPeriod> firstExistingReAgedRepaymentPeriodOpt = existingRepaymentPeriods.stream()
.filter(period -> period.getDueDate().equals(reAgingStartDate)).findFirst(); .filter(period -> period.getDueDate().equals(reageFirstDueDate)).findFirst();
for (final RepaymentPeriod newPeriod : newPeriods) { for (final RepaymentPeriod newPeriod : newPeriods) {
final Optional<RepaymentPeriod> existingRepaymentPeriodOpt = existingRepaymentPeriods.stream().filter( final Optional<RepaymentPeriod> existingRepaymentPeriodOpt = existingRepaymentPeriods.stream().filter(
@@ -736,34 +720,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final RepaymentPeriod rp = RepaymentPeriod.create( final RepaymentPeriod rp = RepaymentPeriod.create(
previousExistingRepaymentPeriodOpt.orElseGet(existingRepaymentPeriods::getLast), newPeriod.getFromDate(), previousExistingRepaymentPeriodOpt.orElseGet(existingRepaymentPeriods::getLast), newPeriod.getFromDate(),
newPeriod.getDueDate(), newPrincipal.add(newInterest), MoneyHelper.getMathContext(), newPeriod.getDueDate(), newPrincipal.add(newInterest), scheduleModel.mc(), scheduleModel.loanProductRelatedDetail());
loanTransaction.getLoan().getLoanProductRelatedDetail());
rp.setTotalDisbursedAmount(disbursedAmount); rp.setTotalDisbursedAmount(disbursedAmount);
if (existingRepaymentPeriodOpt.isPresent()) { existingRepaymentPeriodOpt.ifPresent(existingRepaymentPeriods::remove);
// Add an interest period on the disbursement date for the very first replaced period
if (existingRepaymentPeriodOpt.get().equals(firstRepaymentPeriod)) {
final LocalDate disbursementDate = loanTransaction.getLoan().getDisbursementDate();
rp.getInterestPeriods().getFirst().setDisbursementAmount(disbursedAmount);
rp.getInterestPeriods().getFirst().setFromDate(disbursementDate);
rp.getInterestPeriods().getFirst().setDueDate(disbursementDate);
rp.getInterestPeriods().add(InterestPeriod.withEmptyAmounts(rp, rp.getFromDate(), rp.getDueDate()));
}
existingRepaymentPeriods.remove(existingRepaymentPeriodOpt.get());
}
existingRepaymentPeriods.add(rp); existingRepaymentPeriods.add(rp);
calculateRateFactorForRepaymentPeriod(rp, scheduleModel); calculateRateFactorForRepaymentPeriod(rp, scheduleModel);
} }
if (reAgingStartDate.isBefore(DateUtils.getBusinessLocalDate()) && isInterestRecalculationIsAllowed(ctx, loan)) {
scheduleModel.lastOverdueBalanceChange(null);
recalculateModelOverdueAmountsTillDate(ctx, loanTransaction.getSubmittedOnDate());
}
final List<RepaymentPeriod> reAgedRepaymentPeriods = existingRepaymentPeriods.stream() final List<RepaymentPeriod> reAgedRepaymentPeriods = existingRepaymentPeriods.stream()
.filter(repaymentPeriod -> (!repaymentPeriod.getFromDate().isBefore(reAgingStartDate) .filter(repaymentPeriod -> (!repaymentPeriod.getFromDate().isBefore(reageFirstDueDate)
|| repaymentPeriod.getDueDate().isEqual(reAgingStartDate)) || repaymentPeriod.getDueDate().isEqual(reageFirstDueDate))
&& !repaymentPeriod.getDueDate().isAfter(newPeriods.getLast().getDueDate())) && !repaymentPeriod.getDueDate().isAfter(newPeriods.getLast().getDueDate()))
.toList(); .toList();
@@ -775,16 +743,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
} }
calculateOutstandingBalance(scheduleModel); calculateOutstandingBalance(scheduleModel);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, loanTransaction.getTransactionDate()); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, targetDate);
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, reAgedRepaymentPeriods); checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, reAgedRepaymentPeriods);
} }
private boolean isInterestRecalculationIsAllowed(final ProgressiveTransactionCtx ctx, final Loan loan) {
return loan.isInterestBearingAndInterestRecalculationEnabled() && !ctx.isChargedOff() && !ctx.isWrittenOff()
&& !ctx.isContractTerminated() && !loan.isNpa()
&& !loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue();
}
/** /**
* * Generates temporary interestScheduleModel with re-aged repayment periods * * Generates temporary interestScheduleModel with re-aged repayment periods
*/ */
@@ -794,36 +756,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc, final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
periodStartDate, loanApplicationTerms, null); periodStartDate, loanApplicationTerms, null);
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), null, expectedRepaymentPeriods, loanApplicationTerms.toLoanConfigurationDetails(), null,
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
addDisbursement(temporaryReAgedScheduleModel, EmiChangeOperation.disburse(periodStartDate, loanApplicationTerms.getPrincipal())); addDisbursement(temporaryReAgedScheduleModel, EmiChangeOperation.disburse(periodStartDate, loanApplicationTerms.getPrincipal()));
return temporaryReAgedScheduleModel; return temporaryReAgedScheduleModel;
} }
/**
* * Based on the re-aging start date and frequency data calculates start date for the first re-aged period, which
* is used to generate re-aged repayment periods
*/
@NotNull
private static LocalDate calculateFirstReAgedPeriodStartDate(final LoanTransaction loanTransaction) {
final LoanReAgeParameter loanReAgeParameter = loanTransaction.getLoanReAgeParameter();
final LocalDate reAgingStartDate = loanReAgeParameter.getStartDate();
if (reAgingStartDate.isEqual(loanTransaction.getLoan().getDisbursementDate())) {
return reAgingStartDate;
}
return switch (loanReAgeParameter.getFrequencyType()) {
case DAYS -> reAgingStartDate.minusDays(loanReAgeParameter.getFrequencyNumber());
case WEEKS -> reAgingStartDate.minusWeeks(loanReAgeParameter.getFrequencyNumber());
case MONTHS -> reAgingStartDate.minusMonths(loanReAgeParameter.getFrequencyNumber());
case YEARS -> reAgingStartDate.minusYears(loanReAgeParameter.getFrequencyNumber());
case WHOLE_TERM -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: WHOLE_TERM");
case INVALID -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: INVALID");
};
}
/** /**
* * Zeroing out the EMI of the repayment periods, that are before re-aging and not been fully paid. And decreases * * Zeroing out the EMI of the repayment periods, that are before re-aging and not been fully paid. And decreases
* the balance correction amount (added during interest recalculation for the business date) by the amount of the * the balance correction amount (added during interest recalculation for the business date) by the amount of the
@@ -1008,7 +947,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
BigDecimal calculateRateFactorPerPeriodForInterest(final ProgressiveLoanInterestScheduleModel scheduleModel, BigDecimal calculateRateFactorPerPeriodForInterest(final ProgressiveLoanInterestScheduleModel scheduleModel,
final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) { final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) {
final MathContext mc = scheduleModel.mc(); final MathContext mc = scheduleModel.mc();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final ILoanConfigurationDetails loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate), final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate),
scheduleModel.mc()); scheduleModel.mc());
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()); final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
@@ -1136,7 +1075,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
private BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, 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 MathContext mc = scheduleModel.mc(); final MathContext mc = scheduleModel.mc();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final ILoanConfigurationDetails loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate), final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate),
scheduleModel.mc()); scheduleModel.mc());
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()); final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
@@ -20,5 +20,6 @@ package org.apache.fineract.portfolio.loanproduct.calc.data;
public enum LoanInterestScheduleModelModifiers { // public enum LoanInterestScheduleModelModifiers { //
EMI_RECALCULATION, // EMI_RECALCULATION, //
COPY // COPY, //
INTEREST_RECALCULATION_ENABLED //
} }
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanproduct.calc.data;
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isInPeriod; import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isInPeriod;
import static org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.COPY; import static org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.COPY;
import static org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.EMI_RECALCULATION; import static org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.EMI_RECALCULATION;
import static org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.INTEREST_RECALCULATION_ENABLED;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -50,7 +51,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
@Data @Data
@Accessors(fluent = true) @Accessors(fluent = true)
@@ -60,7 +61,7 @@ public class ProgressiveLoanInterestScheduleModel {
private final List<RepaymentPeriod> repaymentPeriods; private final List<RepaymentPeriod> repaymentPeriods;
private final TreeSet<InterestRate> interestRates; private final TreeSet<InterestRate> interestRates;
@JsonExclude @JsonExclude
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail; private final ILoanConfigurationDetails loanProductRelatedDetail;
private final Map<LoanTermVariationType, List<LoanTermVariationsData>> loanTermVariations; private final Map<LoanTermVariationType, List<LoanTermVariationsData>> loanTermVariations;
private final Integer installmentAmountInMultiplesOf; private final Integer installmentAmountInMultiplesOf;
@JsonExclude @JsonExclude
@@ -73,8 +74,8 @@ public class ProgressiveLoanInterestScheduleModel {
private LocalDate lastOverdueBalanceChange; private LocalDate lastOverdueBalanceChange;
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final ILoanConfigurationDetails loanProductRelatedDetail, final List<LoanTermVariationsData> loanTermVariations,
final List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) { final Integer installmentAmountInMultiplesOf, final MathContext mc) {
this.repaymentPeriods = new ArrayList<>(repaymentPeriods); this.repaymentPeriods = new ArrayList<>(repaymentPeriods);
this.interestRates = new TreeSet<>(Collections.reverseOrder()); this.interestRates = new TreeSet<>(Collections.reverseOrder());
this.loanProductRelatedDetail = loanProductRelatedDetail; this.loanProductRelatedDetail = loanProductRelatedDetail;
@@ -82,11 +83,12 @@ public class ProgressiveLoanInterestScheduleModel {
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.mc = mc; this.mc = mc;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, false)); modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, false, INTEREST_RECALCULATION_ENABLED,
loanProductRelatedDetail.isInterestRecalculationEnabled()));
} }
private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates, private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates,
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final ILoanConfigurationDetails loanProductRelatedDetail,
final Map<LoanTermVariationType, List<LoanTermVariationsData>> loanTermVariations, final Integer installmentAmountInMultiplesOf, final Map<LoanTermVariationType, List<LoanTermVariationsData>> loanTermVariations, final Integer installmentAmountInMultiplesOf,
final MathContext mc, final boolean isCopiedForCalculation) { final MathContext mc, final boolean isCopiedForCalculation) {
this.mc = mc; this.mc = mc;
@@ -97,7 +99,8 @@ public class ProgressiveLoanInterestScheduleModel {
this.loanTermVariations = loanTermVariations; this.loanTermVariations = loanTermVariations;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, isCopiedForCalculation)); modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, isCopiedForCalculation, INTEREST_RECALCULATION_ENABLED,
loanProductRelatedDetail.isInterestRecalculationEnabled()));
} }
public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) { public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) {
@@ -440,4 +443,8 @@ public class ProgressiveLoanInterestScheduleModel {
default -> throw new UnsupportedOperationException(); default -> throw new UnsupportedOperationException();
}; };
} }
public boolean isInterestRecalculationIsAllowed() {
return modifiers.get(INTEREST_RECALCULATION_ENABLED);
}
} }
@@ -35,7 +35,7 @@ import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.util.Memo; import org.apache.fineract.portfolio.util.Memo;
@ToString(exclude = { "previous" }) @ToString(exclude = { "previous" })
@@ -84,13 +84,13 @@ public class RepaymentPeriod {
private Money totalCapitalizedIncomeAmount; private Money totalCapitalizedIncomeAmount;
@JsonExclude @JsonExclude
@Getter @Getter
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail; private final ILoanConfigurationDetails loanProductRelatedDetail;
@JsonExclude @JsonExclude
private MonetaryCurrency currency; private MonetaryCurrency currency;
protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods, protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc, Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc,
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) { ILoanConfigurationDetails loanProductRelatedDetail) {
this.previous = previous; this.previous = previous;
this.fromDate = fromDate; this.fromDate = fromDate;
this.dueDate = dueDate; this.dueDate = dueDate;
@@ -104,13 +104,12 @@ public class RepaymentPeriod {
this.loanProductRelatedDetail = loanProductRelatedDetail; this.loanProductRelatedDetail = loanProductRelatedDetail;
} }
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc, public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc, ILoanConfigurationDetails loanProductRelatedDetail) {
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, null, mc, loanProductRelatedDetail); return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, null, mc, loanProductRelatedDetail);
} }
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc, public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc,
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) { ILoanConfigurationDetails loanProductRelatedDetail) {
final Money zero = emi.zero(); final Money zero = emi.zero();
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero, final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
zero, mc, loanProductRelatedDetail); zero, mc, loanProductRelatedDetail);
@@ -47,9 +47,9 @@ import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails; import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@@ -72,8 +72,7 @@ class ProgressiveEMICalculatorTest {
private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class); private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class);
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class); private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
private static MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN); private static MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
private static LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = Mockito private static ILoanConfigurationDetails loanProductRelatedDetail = Mockito.mock(ILoanConfigurationDetails.class);
.mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class);
private static final CurrencyData currency = new CurrencyData("USD", "USD", 2, 1, "$", "USD"); private static final CurrencyData currency = new CurrencyData("USD", "USD", 2, 1, "$", "USD");
@@ -32,9 +32,9 @@ import java.util.List;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -86,8 +86,7 @@ class InterestPeriodTest {
InterestPeriod period = InterestPeriod.empty(repaymentPeriod, MC); InterestPeriod period = InterestPeriod.empty(repaymentPeriod, MC);
when(repaymentPeriod.getInterestPeriods()).thenReturn(List.of(period)); when(repaymentPeriod.getInterestPeriods()).thenReturn(List.of(period));
when(repaymentPeriod.getFirstInterestPeriod()).thenReturn(period); when(repaymentPeriod.getFirstInterestPeriod()).thenReturn(period);
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = mock( ILoanConfigurationDetails loanProductRelatedDetail = mock(ILoanConfigurationDetails.class);
LoanProductMinimumRepaymentScheduleRelatedDetail.class);
when(loanProductRelatedDetail.getCurrencyData()).thenReturn(USD); when(loanProductRelatedDetail.getCurrencyData()).thenReturn(USD);
when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE); when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE);
when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY); when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY);
@@ -31,9 +31,9 @@ import java.time.ZoneId;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -47,7 +47,7 @@ class RepaymentPeriodTest {
private static final Money ZERO = Money.of(USD, BigDecimal.ZERO, MC); private static final Money ZERO = Money.of(USD, BigDecimal.ZERO, MC);
private static MockedStatic<MoneyHelper> moneyHelper; private static MockedStatic<MoneyHelper> moneyHelper;
private static LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail; private static ILoanConfigurationDetails loanProductRelatedDetail;
@BeforeAll @BeforeAll
static void init() { static void init() {
@@ -55,7 +55,7 @@ class RepaymentPeriodTest {
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(MC); moneyHelper.when(MoneyHelper::getMathContext).thenReturn(MC);
loanProductRelatedDetail = mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class); loanProductRelatedDetail = mock(ILoanConfigurationDetails.class);
when(loanProductRelatedDetail.getCurrencyData()).thenReturn(USD); when(loanProductRelatedDetail.getCurrencyData()).thenReturn(USD);
when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE); when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE);
when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY); when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY);