Skip to content

Commit c7a0fe9

Browse files
committed
FINERACT-2354: Re-aging: -Interest Handling Option: Equal amortization
1 parent 63c220e commit c7a0fe9

File tree

18 files changed

+2233
-154
lines changed

18 files changed

+2233
-154
lines changed

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,11 @@ public void createReAgingTransaction(DataTable table) throws IOException {
5757
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
5858
long loanId = loanResponse.body().getLoanId();
5959

60-
List<String> data = table.asLists().get(1);
61-
int frequencyNumber = Integer.parseInt(data.get(0));
62-
String frequencyType = data.get(1);
63-
String startDate = data.get(2);
64-
int numberOfInstallments = Integer.parseInt(data.get(3));
65-
66-
PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory//
67-
.defaultReAgingRequest()//
68-
.frequencyNumber(frequencyNumber)//
69-
.frequencyType(frequencyType)//
70-
.startDate(startDate)//
71-
.numberOfInstallments(numberOfInstallments);//
60+
PostLoansLoanIdTransactionsRequest reAgingRequest = setReAgeingRequestProperties(//
61+
LoanRequestFactory.defaultReAgingRequest(), //
62+
table.row(0), //
63+
table.row(1) //
64+
);
7265

7366
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi.executeLoanTransaction(loanId, reAgingRequest, "reAge")
7467
.execute();
@@ -81,18 +74,11 @@ public void createReAgingTransactionByLoanExternalId(DataTable table) throws IOE
8174
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
8275
String loanExternalId = loanResponse.body().getResourceExternalId();
8376

84-
List<String> data = table.asLists().get(1);
85-
int frequencyNumber = Integer.parseInt(data.get(0));
86-
String frequencyType = data.get(1);
87-
String startDate = data.get(2);
88-
int numberOfInstallments = Integer.parseInt(data.get(3));
89-
90-
PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory//
91-
.defaultReAgingRequest()//
92-
.frequencyNumber(frequencyNumber)//
93-
.frequencyType(frequencyType)//
94-
.startDate(startDate)//
95-
.numberOfInstallments(numberOfInstallments);//
77+
PostLoansLoanIdTransactionsRequest reAgingRequest = setReAgeingRequestProperties(//
78+
LoanRequestFactory.defaultReAgingRequest(), //
79+
table.row(0), //
80+
table.row(1) //
81+
);
9682

9783
Response<PostLoansLoanIdTransactionsResponse> response = loanTransactionsApi
9884
.executeLoanTransaction1(loanExternalId, reAgingRequest, "reAge").execute();
@@ -197,4 +183,20 @@ public void reAgeContractTerminatedLoanFailure(final DataTable table) throws IOE
197183
assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(403);
198184
assertThat(developerMessage).matches(ErrorMessageHelper.reAgeContractTerminatedLoanFailure());
199185
}
186+
187+
PostLoansLoanIdTransactionsRequest setReAgeingRequestProperties(PostLoansLoanIdTransactionsRequest request, List<String> headers,
188+
List<String> values) {
189+
for (int i = 0; i < headers.size(); i++) {
190+
String header = headers.get(i).toLowerCase().trim().replaceAll(" ", "");
191+
switch (header) {
192+
case "frequencynumber" -> request.setFrequencyNumber(Integer.parseInt(values.get(i)));
193+
case "frequencytype" -> request.setFrequencyType(values.get(i));
194+
case "startdate" -> request.setStartDate(values.get(i));
195+
case "numberofinstallments" -> request.setNumberOfInstallments(Integer.parseInt(values.get(i)));
196+
case "reageinteresthandling" -> request.setReAgeInterestHandling(values.get(i));
197+
default -> throw new IllegalStateException("Unknown header: " + header);
198+
}
199+
}
200+
return request;
201+
}
200202
}

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

Lines changed: 1676 additions & 3 deletions
Large diffs are not rendered by default.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
7575
import org.apache.fineract.portfolio.fund.domain.Fund;
7676
import org.apache.fineract.portfolio.group.domain.Group;
77+
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
7778
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
7879
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
7980
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -1824,6 +1825,14 @@ public boolean hasContractTerminationTransaction() {
18241825
return getLoanTransactions().stream().anyMatch(t -> t.isContractTermination() && t.isNotReversed());
18251826
}
18261827

1828+
public LoanReAgeInterestHandlingType getReAgeInterestHandlingType() {
1829+
Optional<LoanTransaction> lastReAgeTransaction = loanTransactions.stream()//
1830+
.filter(LoanTransaction::isNotReversed)//
1831+
.filter(LoanTransaction::isReAge)//
1832+
.findFirst();//
1833+
return lastReAgeTransaction.map(loanTransaction -> loanTransaction.getLoanReAgeParameter().getInterestHandlingType()).orElse(null);
1834+
}
1835+
18271836
public boolean hasReAgingTransaction() {
18281837
return getLoanTransactions().stream().anyMatch(t -> t.isReAge() && t.isNotReversed());
18291838
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,21 @@ public LoanRepaymentScheduleInstallment(Loan loan, Integer installmentNumber, Lo
261261
}
262262

263263
public static LoanRepaymentScheduleInstallment newReAgedInstallment(final Loan loan, final Integer installmentNumber,
264-
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal) {
265-
return new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal, null, null, null, null, null,
266-
null, null, false, false, true);
264+
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal fees,
265+
final BigDecimal penalties, final BigDecimal interestAccrued, final BigDecimal feeAccrued, final BigDecimal penaltyAccrued) {
266+
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate,
267+
principal, interest, fees, penalties, null, null, null, null, false, false, true);
268+
installment.setInterestAccrued(interestAccrued);
269+
installment.setFeeAccrued(feeAccrued);
270+
installment.setPenaltyAccrued(penaltyAccrued);
271+
return installment;
272+
}
273+
274+
public static LoanRepaymentScheduleInstallment newReAgedInstallment(final Loan loan, final Integer installmentNumber,
275+
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal fees,
276+
final BigDecimal penalties) {
277+
return new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal, interest, fees, penalties, null,
278+
null, null, null, false, false, true);
267279
}
268280

269281
public static LoanRepaymentScheduleInstallment getLastNonDownPaymentInstallment(List<LoanRepaymentScheduleInstallment> installments) {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,4 +481,13 @@ boolean existsNonReversedByLoanAndTypeAndDate(@Param("loan") Loan loan, @Param("
481481
""")
482482
CodeValue fetchClassificationCodeValueByTransactionId(@Param("transactionId") Long transactionId);
483483

484+
@Query("""
485+
SELECT lt FROM LoanTransaction lt
486+
WHERE lt.loan = :loan
487+
AND lt.reversed = false
488+
AND lt.typeOf = :type
489+
AND lt.dateOf <= :onDate
490+
""")
491+
Optional<LoanTransaction> findNonReversedTransactionActiveOnDate(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
492+
@Param("onDate") LocalDate onDate);
484493
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
3030
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
3131
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
32+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
3233
import org.springframework.stereotype.Service;
3334

3435
@Service
@@ -56,4 +57,7 @@ private Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
5657
|| !transaction.isNonMonetaryTransaction() || transaction.isContractTermination());
5758
}
5859

60+
public Optional<LoanTransaction> getActiveReageTransactionOnDate(Loan loan, LocalDate onDate) {
61+
return loanTransactionRepository.findNonReversedTransactionActiveOnDate(loan, LoanTransactionType.REAGE, onDate);
62+
}
5963
}

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ public BigDecimal calculateInterestRecalculationFutureOutstandingValue(Loan loan
5858
}
5959
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
6060
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), repaymentScheduleInstallments, Set.of(),
61-
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel);
61+
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel,
62+
loan.getReAgeInterestHandlingType());
6263
ctx.setChargedOff(loan.isChargedOff());
6364
ctx.setWrittenOff(loan.isClosedWrittenOff());
6465
ctx.setContractTerminated(loan.isContractTermination());

0 commit comments

Comments
 (0)