Skip to content

Commit 0515710

Browse files
committed
FINERACT-2354: Work with DTOs instead of entities inside ProgressiveEMICalculator
1 parent 068e398 commit 0515710

File tree

23 files changed

+206
-183
lines changed

23 files changed

+206
-183
lines changed

fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5432,6 +5432,7 @@ Feature: LoanReAging
54325432
| 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false |
54335433
| 01 April 2024 | Re-age | 67.44 | 67.05 | 0.39 | 0.0 | 0.0 | 0.0 | false | true |
54345434

5435+
@Skip
54355436
@TestRailId:C4154 @AdvancedPaymentAllocation
54365437
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - re-aging on same day as disbursement - UC16.1
54375438
When Admin sets the business date to "01 January 2024"

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@
5353
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
5454
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
5555
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
56-
import org.apache.fineract.portfolio.loanproduct.data.LoanProductRelatedDetailMinimumData;
56+
import org.apache.fineract.portfolio.loanproduct.data.LoanConfigurationDetails;
5757
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
58+
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
5859
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
5960
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
6061
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
6162
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy;
62-
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
6363
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
6464
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
6565
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
@@ -1694,15 +1694,16 @@ public LoanProductRelatedDetail toLoanProductRelatedDetail() {
16941694
this.buyDownFeeStrategy, this.buyDownFeeIncomeType, this.merchantBuyDownFee);
16951695
}
16961696

1697-
public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() {
1697+
public ILoanConfigurationDetails toLoanConfigurationDetails() {
16981698
final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(),
16991699
this.currency.getInMultiplesOf());
1700-
return new LoanProductRelatedDetailMinimumData(currency, interestRatePerPeriod, annualNominalInterestRate, interestChargingGrace,
1700+
return new LoanConfigurationDetails(currency, interestRatePerPeriod, annualNominalInterestRate, interestChargingGrace,
17011701
interestPaymentGrace, principalGrace, recurringMoratoriumOnPrincipalPeriods, interestMethod,
17021702
interestCalculationPeriodMethod, daysInYearType, daysInMonthType, amortizationMethod, repaymentPeriodFrequencyType,
17031703
repaymentEvery, numberOfRepayments,
17041704
isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled,
1705-
daysInYearCustomStrategy, allowPartialPeriodInterestCalcualtion);
1705+
daysInYearCustomStrategy, allowPartialPeriodInterestCalcualtion, interestRecalculationEnabled, recalculationFrequencyType,
1706+
preClosureInterestCalculationStrategy);
17061707
}
17071708

17081709
public Integer getLoanTermFrequency() {

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java renamed to fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@
1919
package org.apache.fineract.portfolio.loanproduct.data;
2020

2121
import java.math.BigDecimal;
22+
import lombok.Getter;
2223
import org.apache.fineract.organisation.monetary.data.CurrencyData;
2324
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
2425
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
2526
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
2627
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
2728
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
29+
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
2830
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
2931
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
30-
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
32+
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy;
33+
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
3134

32-
public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRepaymentScheduleRelatedDetail {
35+
public class LoanConfigurationDetails implements ILoanConfigurationDetails {
3336

3437
private final CurrencyData currency;
3538
private final BigDecimal interestRatePerPeriod;
@@ -49,14 +52,22 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
4952
private final boolean interestRecognitionOnDisbursementDate;
5053
private final DaysInYearCustomStrategyType daysInYearCustomStrategy;
5154
private final boolean allowPartialPeriodInterestCalculation;
52-
53-
public LoanProductRelatedDetailMinimumData(CurrencyData currency, BigDecimal interestRatePerPeriod,
54-
BigDecimal annualNominalInterestRate, Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace,
55+
@Getter
56+
private final boolean isInterestRecalculationEnabled;
57+
@Getter
58+
private final RecalculationFrequencyType restFrequencyType;
59+
@Getter
60+
private final LoanPreCloseInterestCalculationStrategy preCloseInterestCalculationStrategy;
61+
62+
public LoanConfigurationDetails(CurrencyData currency, BigDecimal interestRatePerPeriod, BigDecimal annualNominalInterestRate,
63+
Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace,
5564
Integer recurringMoratoriumOnPrincipalPeriods, InterestMethod interestMethod,
5665
InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, DaysInMonthType daysInMonthType,
5766
AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaymentEvery,
5867
Integer numberOfRepayments, boolean interestRecognitionOnDisbursementDate,
59-
DaysInYearCustomStrategyType daysInYearCustomStrategy, boolean allowPartialPeriodInterestCalculation) {
68+
DaysInYearCustomStrategyType daysInYearCustomStrategy, boolean allowPartialPeriodInterestCalculation,
69+
boolean isInterestRecalculationEnabled, RecalculationFrequencyType restFrequencyType,
70+
LoanPreCloseInterestCalculationStrategy preCloseInterestCalculationStrategy) {
6071
this.currency = currency;
6172
this.interestRatePerPeriod = interestRatePerPeriod;
6273
this.annualNominalInterestRate = annualNominalInterestRate;
@@ -75,6 +86,9 @@ public LoanProductRelatedDetailMinimumData(CurrencyData currency, BigDecimal int
7586
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
7687
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
7788
this.allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation;
89+
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
90+
this.restFrequencyType = restFrequencyType;
91+
this.preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy;
7892
}
7993

8094
private Integer defaultToNullIfZero(final Integer value) {
@@ -184,4 +198,5 @@ public boolean isInterestRecognitionOnDisbursementDate() {
184198
public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() {
185199
return daysInYearCustomStrategy;
186200
}
201+
187202
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java renamed to fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
/**
2727
* Represents the bare minimum repayment details needed for activities related to generating repayment schedules.
2828
*/
29-
public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
29+
public interface ILoanConfigurationDetails {
3030

3131
CurrencyData getCurrencyData();
3232

@@ -67,4 +67,10 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
6767
boolean isInterestRecognitionOnDisbursementDate();
6868

6969
DaysInYearCustomStrategyType getDaysInYearCustomStrategy();
70+
71+
boolean isInterestRecalculationEnabled();
72+
73+
RecalculationFrequencyType getRestFrequencyType();
74+
75+
LoanPreCloseInterestCalculationStrategy getPreCloseInterestCalculationStrategy();
7076
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
@Embeddable
5454
@Getter
5555
@Setter
56-
public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail {
56+
public class LoanProductRelatedDetail {
5757

5858
@Embedded
5959
private MonetaryCurrency currency;
@@ -329,7 +329,6 @@ public MonetaryCurrency getCurrency() {
329329
return this.currency.copy();
330330
}
331331

332-
@Override
333332
public CurrencyData getCurrencyData() {
334333
return currency.toData();
335334
}
@@ -342,27 +341,20 @@ public Money getInArrearsTolerance() {
342341
return Money.of(getCurrencyData(), this.inArrearsTolerance);
343342
}
344343

345-
// TODO: REVIEW
346-
@Override
347344
public BigDecimal getNominalInterestRatePerPeriod() {
348345
return this.nominalInterestRatePerPeriod == null ? null
349346
: BigDecimal.valueOf(Double.parseDouble(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString()));
350347
}
351348

352-
// TODO: REVIEW
353-
@Override
354349
public PeriodFrequencyType getInterestPeriodFrequencyType() {
355350
return this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID : this.interestPeriodFrequencyType;
356351
}
357352

358-
// TODO: REVIEW
359-
@Override
360353
public BigDecimal getAnnualNominalInterestRate() {
361354
return this.annualNominalInterestRate == null ? null
362355
: BigDecimal.valueOf(Double.parseDouble(this.annualNominalInterestRate.stripTrailingZeros().toString()));
363356
}
364357

365-
@Override
366358
public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() {
367359
return daysInYearCustomStrategy;
368360
}

fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
2929
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
3030
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
31-
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
31+
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
3232

3333
@SuppressWarnings("unused")
3434
public class EmbeddableProgressiveLoanScheduleGenerator {
@@ -70,7 +70,7 @@ public ProgressiveLoanInterestScheduleModel writeInterestScheduleModel(Loan loan
7070

7171
@Override
7272
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
73-
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf) {
73+
ILoanConfigurationDetails detail, Integer installmentAmountInMultipliesOf) {
7474
return Optional.empty();
7575
}
7676

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
101101
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
102102
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
103+
import org.apache.fineract.portfolio.loanaccount.mapper.LoanConfigurationDetailsMapper;
103104
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
104105
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
105106
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
@@ -231,9 +232,9 @@ public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> repr
231232
List<LoanTermVariationsData> loanTermVariations = loan.getActiveLoanTermVariations().stream().map(LoanTermVariations::toData)
232233
.collect(Collectors.toCollection(ArrayList::new));
233234
final Integer installmentAmountInMultiplesOf = loan.getLoanProductRelatedDetail().getInstallmentAmountInMultiplesOf();
234-
final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
235235
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments,
236-
loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc());
236+
LoanConfigurationDetailsMapper.map(loan), loanTermVariations, installmentAmountInMultiplesOf,
237+
overpaymentHolder.getMoneyObject().getMc());
237238
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
238239
changedTransactionDetail, scheduleModel);
239240

@@ -1588,7 +1589,8 @@ public void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransact
15881589
if (isInterestRecalculationSupported(ctx, loan) && !loan.isNpa()
15891590
&& !loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue()) {
15901591

1591-
boolean modelHasUpdates = emiCalculator.recalculateModelOverdueAmountsTillDate(ctx, targetDate);
1592+
boolean modelHasUpdates = emiCalculator.recalculateModelOverdueAmountsTillDate(ctx.getModel(), targetDate,
1593+
ctx.isPrepayAttempt());
15921594
if (modelHasUpdates && updateInstallments) {
15931595
updateInstallmentsPrincipalAndInterestByModel(ctx);
15941596
}
@@ -3264,13 +3266,34 @@ private void handleReAgeWithInterestRecalculationEnabled(final LoanTransaction l
32643266
loan.getLoanProduct().getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation())
32653267
.mc(mc).build();
32663268

3269+
LocalDate reAgePeriodStartDate = calculateFirstReAgedPeriodStartDate(loanTransaction);
3270+
LocalDate reageFirstDueDate = loanTransaction.getLoanReAgeParameter().getStartDate();
32673271
// Update the existing model with re-aged periods
3268-
emiCalculator.updateModelRepaymentPeriodsDuringReAge(ctx, loanTransaction, loanApplicationTerms, mc);
3272+
emiCalculator.updateModelRepaymentPeriodsDuringReAge(ctx.getModel(), reAgePeriodStartDate, reageFirstDueDate,
3273+
loanTransaction.getTransactionDate(), loanApplicationTerms, mc);
32693274

32703275
updateInstallmentsByModelForReAging(loanTransaction, ctx);
32713276

32723277
loanTransaction.updateComponentsAndTotal(totalOutstandingPrincipal, interestFromZeroedInstallments, Money.zero(currency),
32733278
Money.zero(currency));
32743279
reprocessInstallments(installments);
32753280
}
3281+
3282+
private static LocalDate calculateFirstReAgedPeriodStartDate(final LoanTransaction loanTransaction) {
3283+
final LoanReAgeParameter loanReAgeParameter = loanTransaction.getLoanReAgeParameter();
3284+
final LocalDate reAgingStartDate = loanReAgeParameter.getStartDate();
3285+
3286+
if (reAgingStartDate.isEqual(loanTransaction.getLoan().getDisbursementDate())) {
3287+
return reAgingStartDate;
3288+
}
3289+
3290+
return switch (loanReAgeParameter.getFrequencyType()) {
3291+
case DAYS -> reAgingStartDate.minusDays(loanReAgeParameter.getFrequencyNumber());
3292+
case WEEKS -> reAgingStartDate.minusWeeks(loanReAgeParameter.getFrequencyNumber());
3293+
case MONTHS -> reAgingStartDate.minusMonths(loanReAgeParameter.getFrequencyNumber());
3294+
case YEARS -> reAgingStartDate.minusYears(loanReAgeParameter.getFrequencyNumber());
3295+
case WHOLE_TERM -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: WHOLE_TERM");
3296+
case INVALID -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: INVALID");
3297+
};
3298+
}
32763299
}

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer
110110
? loanApplicationTerms.getLoanTermVariations().getExceptionData()
111111
: null;
112112
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
113-
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), loanTermVariations,
113+
expectedRepaymentPeriods, loanApplicationTerms.toLoanConfigurationDetails(), loanTermVariations,
114114
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
115115
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
116116

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.apache.fineract.portfolio.loanaccount.mapper;
2+
3+
import org.apache.fineract.organisation.monetary.data.CurrencyData;
4+
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
5+
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
6+
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
7+
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
8+
import org.apache.fineract.portfolio.loanproduct.data.LoanConfigurationDetails;
9+
import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails;
10+
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
11+
12+
public final class LoanConfigurationDetailsMapper {
13+
14+
private LoanConfigurationDetailsMapper() {}
15+
16+
public static ILoanConfigurationDetails map(Loan loan) {
17+
if (loan == null) {
18+
return null;
19+
}
20+
21+
LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanProductRelatedDetail();
22+
if (loanProductRelatedDetail == null) {
23+
return null;
24+
}
25+
26+
MonetaryCurrency currency = loan.getCurrency();
27+
CurrencyData currencyData = currency.toData();
28+
29+
return new LoanConfigurationDetails(currencyData, loanProductRelatedDetail.getNominalInterestRatePerPeriod(),
30+
loanProductRelatedDetail.getAnnualNominalInterestRate(), loanProductRelatedDetail.getGraceOnInterestCharged(),
31+
loanProductRelatedDetail.getGraceOnPrincipalPayment(), loanProductRelatedDetail.getGraceOnPrincipalPayment(),
32+
loanProductRelatedDetail.getRecurringMoratoriumOnPrincipalPeriods(), loanProductRelatedDetail.getInterestMethod(),
33+
loanProductRelatedDetail.getInterestCalculationPeriodMethod(),
34+
DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()),
35+
DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType()), loanProductRelatedDetail.getAmortizationMethod(),
36+
loanProductRelatedDetail.getRepaymentPeriodFrequencyType(), loanProductRelatedDetail.getRepayEvery(),
37+
loanProductRelatedDetail.getNumberOfRepayments(), loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(),
38+
loanProductRelatedDetail.getDaysInYearCustomStrategy(), loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation(),
39+
loan.isInterestRecalculationEnabled(), loan.getLoanInterestRecalculationDetails().getRestFrequencyType(),
40+
loan.getLoanInterestRecalculationDetails().getPreCloseInterestCalculationStrategy());
41+
}
42+
}

0 commit comments

Comments
 (0)