Skip to content

Commit 4d1ba4f

Browse files
FINERACT-2354: Fifth step - Partially paid installment - adjust to last edge case of re-aging for Interest bearing loans - Default Behavior, interestRecalculation = true, without dueDate change
1 parent 63c220e commit 4d1ba4f

File tree

21 files changed

+373
-101
lines changed

21 files changed

+373
-101
lines changed

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3723,6 +3723,74 @@ Feature: LoanReAging
37233723
| 01 February 2024 | Repayment | 25.0 | 24.42 | 0.58 | 0.0 | 0.0 | 75.58 | false |
37243724
| 15 March 2024 | Re-age | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false |
37253725

3726+
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - with LAST_INSTALLMENT allocation rule and partial repayment - UC5.1
3727+
When Admin sets the business date to "01 January 2024"
3728+
When Admin creates a client with random data
3729+
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule
3730+
When Admin creates a fully customized loan with the following data:
3731+
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
3732+
| LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
3733+
And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
3734+
When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
3735+
Then Loan Repayment schedule has 6 periods, with the following data for periods:
3736+
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
3737+
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
3738+
| 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3739+
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3740+
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3741+
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3742+
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3743+
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
3744+
Then Loan Repayment schedule has the following data in Total row:
3745+
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
3746+
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 |
3747+
Then Loan Transactions tab has the following data:
3748+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
3749+
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
3750+
When Admin sets the business date to "01 February 2024"
3751+
And Customer makes "AUTOPAY" repayment on "01 February 2024" with 25.0 EUR transaction amount
3752+
Then Loan Repayment schedule has 6 periods, with the following data for periods:
3753+
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
3754+
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
3755+
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
3756+
| 2 | 29 | 01 March 2024 | | 67.0 | 16.57 | 0.44 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3757+
| 3 | 31 | 01 April 2024 | | 50.33 | 16.67 | 0.34 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3758+
| 4 | 30 | 01 May 2024 | | 33.57 | 16.76 | 0.25 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3759+
| 5 | 31 | 01 June 2024 | | 16.71 | 16.86 | 0.15 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
3760+
| 6 | 30 | 01 July 2024 | | 0.0 | 16.71 | 0.05 | 0.0 | 0.0 | 16.76 | 7.99 | 7.99 | 0.0 | 8.77 |
3761+
Then Loan Repayment schedule has the following data in Total row:
3762+
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
3763+
| 100.0 | 1.81 | 0.0 | 0.0 | 101.81 | 25.0 | 7.99 | 0.0 | 76.81 |
3764+
Then Loan Transactions tab has the following data:
3765+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
3766+
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
3767+
| 01 February 2024 | Repayment | 25.0 | 24.42 | 0.58 | 0.0 | 0.0 | 75.58 | false |
3768+
When Admin sets the business date to "15 March 2024"
3769+
When Admin creates a Loan re-aging transaction with the following data:
3770+
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
3771+
| 1 | MONTHS | 01 April 2024| 6 |
3772+
Then Loan Repayment schedule has 9 periods, with the following data for periods:
3773+
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
3774+
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
3775+
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
3776+
| 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3777+
| 3 | 0 | 01 April 2024 | 15 March 2024 | 75.58 | 7.99 | 0.0 | 0.0 | 0.0 | 7.99 | 7.99 | 7.99 | 0.0 | 0.0 |
3778+
| 4 | 31 | 01 April 2024 | | 63.53 | 12.05 | 0.88 | 0.0 | 0.0 | 12.93 | 0.0 | 0.0 | 0.0 | 12.93 |
3779+
| 5 | 30 | 01 May 2024 | | 50.97 | 12.56 | 0.37 | 0.0 | 0.0 | 12.93 | 0.0 | 0.0 | 0.0 | 12.93 |
3780+
| 6 | 31 | 01 June 2024 | | 38.34 | 12.63 | 0.3 | 0.0 | 0.0 | 12.93 | 0.0 | 0.0 | 0.0 | 12.93 |
3781+
| 7 | 30 | 01 July 2024 | | 25.63 | 12.71 | 0.22 | 0.0 | 0.0 | 12.93 | 0.0 | 0.0 | 0.0 | 12.93 |
3782+
| 8 | 31 | 01 August 2024 | | 12.85 | 12.78 | 0.15 | 0.0 | 0.0 | 12.93 | 0.0 | 0.0 | 0.0 | 12.93 |
3783+
| 9 | 31 | 01 September 2024| | 0.0 | 12.85 | 0.07 | 0.0 | 0.0 | 12.92 | 0.0 | 0.0 | 0.0 | 12.92 |
3784+
Then Loan Repayment schedule has the following data in Total row:
3785+
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
3786+
| 100.0 | 2.57 | 0.0 | 0.0 | 102.57 | 25.0 | 7.99 | 0.0 | 77.57 |
3787+
Then Loan Transactions tab has the following data:
3788+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
3789+
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
3790+
| 01 February 2024 | Repayment | 25.0 | 24.42 | 0.58 | 0.0 | 0.0 | 75.58 | false | false |
3791+
| 15 March 2024 | Re-age | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false | true |
3792+
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
3793+
37263794
@TestRailId:C4085 @AdvancedPaymentAllocation
37273795
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Chargeback after re-aging - UC6
37283796
When Admin sets the business date to "01 January 2024"

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -331,16 +331,18 @@ private LocalDate getNextUnpaidInstallmentDueDate(final Loan loan) {
331331
LocalDate nextUnpaidInstallmentDate = expectedMaturityDate;
332332

333333
for (final LoanRepaymentScheduleInstallment installment : installments) {
334-
boolean isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isBefore(currentBusinessDate, installment.getDueDate())
335-
&& DateUtils.isBefore(currentBusinessDate, expectedMaturityDate);
336-
if (installment.isDownPayment()) {
337-
isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isEqual(currentBusinessDate, installment.getDueDate())
334+
if (!installment.isPaidMovedPaidDuringReAging()) {
335+
boolean isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isBefore(currentBusinessDate, installment.getDueDate())
338336
&& DateUtils.isBefore(currentBusinessDate, expectedMaturityDate);
339-
}
340-
if (isCurrentDateBeforeInstallmentAndLoanPeriod) {
341-
if (installment.isNotFullyPaidOff()) {
342-
nextUnpaidInstallmentDate = installment.getDueDate();
343-
break;
337+
if (installment.isDownPayment()) {
338+
isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isEqual(currentBusinessDate, installment.getDueDate())
339+
&& DateUtils.isBefore(currentBusinessDate, expectedMaturityDate);
340+
}
341+
if (isCurrentDateBeforeInstallmentAndLoanPeriod) {
342+
if (installment.isNotFullyPaidOff()) {
343+
nextUnpaidInstallmentDate = installment.getDueDate();
344+
break;
345+
}
344346
}
345347
}
346348
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ public List<Long> fetchDisbursementIds() {
744744

745745
public LocalDate determineExpectedMaturityDate() {
746746
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
747-
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
747+
.filter(i -> !i.isDownPayment() && !i.isAdditional() && !i.isPaidMovedPaidDuringReAging()).toList();
748748
final int numberOfInstallments = installments.size();
749749
LocalDate maturityDate = installments.get(numberOfInstallments - 1).getDueDate();
750750
ListIterator<LoanRepaymentScheduleInstallment> iterator = installments.listIterator(numberOfInstallments);
@@ -1551,7 +1551,7 @@ public int fetchNumberOfInstallmentsAfterExceptions() {
15511551
int numberOfInstallments = 0;
15521552
for (final LoanRepaymentScheduleInstallment installment : installments) {
15531553
if (!installment.isRecalculatedInterestComponent() && !installment.isAdditional() && !installment.isDownPayment()
1554-
&& !installment.isReAged()) {
1554+
&& !installment.isReAged() && !installment.isPaidMovedPaidDuringReAging()) {
15551555
numberOfInstallments++;
15561556
}
15571557
}

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
161161
@Column(name = "is_re_aged", nullable = false)
162162
private boolean isReAged;
163163

164+
@Column(name = "is_paid_moved_during_re_aging", nullable = false)
165+
private boolean isPaidMovedPaidDuringReAging;
166+
164167
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "loanRepaymentScheduleInstallment")
165168
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
166169

@@ -242,7 +245,7 @@ public LoanRepaymentScheduleInstallment(final Loan loan) {
242245
public LoanRepaymentScheduleInstallment(Loan loan, Integer installmentNumber, LocalDate fromDate, LocalDate dueDate,
243246
BigDecimal principal, BigDecimal interestCharged, BigDecimal feeChargesCharged, BigDecimal penaltyCharges,
244247
BigDecimal creditedPrincipal, BigDecimal creditedInterest, BigDecimal creditedFee, BigDecimal creditedPenalty,
245-
boolean additional, boolean isDownPayment, boolean isReAged) {
248+
boolean additional, boolean isDownPayment, boolean isReAged, boolean isPaidMovedPaidDuringReAging) {
246249
this.loan = loan;
247250
this.installmentNumber = installmentNumber;
248251
this.fromDate = fromDate;
@@ -258,16 +261,25 @@ public LoanRepaymentScheduleInstallment(Loan loan, Integer installmentNumber, Lo
258261
this.additional = additional;
259262
this.isDownPayment = isDownPayment;
260263
this.isReAged = isReAged;
264+
this.isPaidMovedPaidDuringReAging = isPaidMovedPaidDuringReAging;
261265
}
262266

263267
public static LoanRepaymentScheduleInstallment newReAgedInstallment(final Loan loan, final Integer installmentNumber,
264268
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal) {
265269
return new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal, null, null, null, null, null,
266-
null, null, false, false, true);
270+
null, null, false, false, true, false);
271+
}
272+
273+
public static LoanRepaymentScheduleInstallment newInstallmentWithMovedPaidAmountDuringReAging(final Loan loan,
274+
final Integer installmentNumber, final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal,
275+
final BigDecimal interest) {
276+
return new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal, interest, null, null, null, null,
277+
null, null, false, false, false, true);
267278
}
268279

269-
public static LoanRepaymentScheduleInstallment getLastNonDownPaymentInstallment(List<LoanRepaymentScheduleInstallment> installments) {
270-
return installments.stream().filter(i -> !i.isDownPayment()).reduce((first, second) -> second).orElseThrow();
280+
public static LoanRepaymentScheduleInstallment getLastNormalInstallment(List<LoanRepaymentScheduleInstallment> installments) {
281+
return installments.stream().filter(i -> !i.isDownPayment() && !i.isPaidMovedPaidDuringReAging()).reduce((first, second) -> second)
282+
.orElseThrow();
271283
}
272284

273285
public Money getCreditedPrincipal(final MonetaryCurrency currency) {
@@ -1074,6 +1086,7 @@ public void copyFrom(final LoanScheduleModelPeriod period) {
10741086
setDownPayment(period.isDownPaymentPeriod());
10751087
setAdditional(false);
10761088
setReAged(false);
1089+
setPaidMovedPaidDuringReAging(false);
10771090
}
10781091

10791092
public void copyFrom(final LoanRepaymentScheduleInstallment installment) {
@@ -1091,6 +1104,7 @@ public void copyFrom(final LoanRepaymentScheduleInstallment installment) {
10911104
setAdditional(installment.isAdditional());
10921105
setReAged(installment.isReAged());
10931106
setDownPayment(installment.isDownPayment());
1107+
setPaidMovedPaidDuringReAging(installment.isPaidMovedPaidDuringReAging());
10941108
// Principal
10951109
setPrincipal(installment.getPrincipal());
10961110
setPrincipalCompleted(installment.getPrincipalCompleted());
@@ -1217,7 +1231,7 @@ private BigDecimal setScaleAndDefaultToNullIfZero(final BigDecimal value) {
12171231
}
12181232

12191233
public boolean isFirstNormalInstallment() {
1220-
return loan.getRepaymentScheduleInstallments().stream().filter(rp -> !rp.isDownPayment()).findFirst().stream()
1221-
.anyMatch(rp -> rp.equals(this));
1234+
return loan.getRepaymentScheduleInstallments().stream().filter(rp -> !rp.isDownPayment() && !rp.isPaidMovedPaidDuringReAging())
1235+
.findFirst().stream().anyMatch(rp -> rp.equals(this));
12221236
}
12231237
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ public void reprocess(final MonetaryCurrency currency, final LocalDate disbursem
4747
LocalDate startDate = disbursementDate;
4848
LoanRepaymentScheduleInstallment firstNormalPeriod = repaymentPeriods.stream()
4949
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
50-
.filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow();
50+
.filter(repaymentPeriod -> !repaymentPeriod.isDownPayment() && !repaymentPeriod.isPaidMovedPaidDuringReAging()).findFirst()
51+
.orElseThrow();
5152
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
5253

53-
if (!period.isDownPayment()) {
54+
if (!period.isDownPayment() && !period.isPaidMovedPaidDuringReAging()) {
5455

5556
boolean isFirstNonDownPaymentPeriod = period.equals(firstNormalPeriod);
5657

@@ -235,7 +236,8 @@ private BigDecimal getBaseAmount(MonetaryCurrency monetaryCurrency, LoanRepaymen
235236

236237
public static int fetchFirstNormalInstallmentNumber(List<LoanRepaymentScheduleInstallment> installments) {
237238
return installments.stream().sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
238-
.filter(repaymentPeriod -> !repaymentPeriod.isDownPayment()).findFirst().orElseThrow().getInstallmentNumber();
239+
.filter(repaymentPeriod -> !repaymentPeriod.isDownPayment() && !repaymentPeriod.isPaidMovedPaidDuringReAging()).findFirst()
240+
.orElseThrow().getInstallmentNumber();
239241
}
240242

241243
public static boolean isInPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment targetInstallment,

0 commit comments

Comments
 (0)