FINERACT-2354: Work with DTOs instead of entities inside ProgressiveEMICalculator
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
+6
-5
@@ -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() {
|
||||||
|
|||||||
+20
-5
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+7
-1
@@ -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();
|
||||||
}
|
}
|
||||||
+1
-9
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-4
@@ -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");
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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());
|
||||||
|
|
||||||
|
|||||||
+42
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -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);
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -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,
|
||||||
|
|||||||
+2
-2
@@ -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;
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-10
@@ -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.
|
||||||
|
|||||||
+43
-104
@@ -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());
|
||||||
|
|||||||
+2
-1
@@ -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 //
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-7
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-6
@@ -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);
|
||||||
|
|||||||
+2
-3
@@ -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");
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -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);
|
||||||
|
|||||||
+3
-3
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user