This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 833f4a0d33 FINERACT-2228: Fix incorrect current balance calculation
and unintentional transaction reversals
833f4a0d33 is described below
commit 833f4a0d33a9cbaa19c7426fcc33412fe697211b
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Mar 28 21:36:14 2025 +0100
FINERACT-2228: Fix incorrect current balance calculation and unintentional
transaction reversals
---
.../test/resources/features/LoanRepayment.feature | 120 ++++++++++++++++++++-
.../ReprocessLoanTransactionsServiceImpl.java | 1 -
.../domain/ProgressiveLoanScheduleGenerator.java | 3 +-
.../portfolio/loanproduct/calc/EMICalculator.java | 3 +-
.../loanproduct/calc/ProgressiveEMICalculator.java | 105 +++++++++++-------
.../data/ProgressiveLoanInterestScheduleModel.java | 9 ++
.../loanproduct/calc/data/RepaymentPeriod.java | 4 +
.../LoanChargeWritePlatformServiceImpl.java | 2 +-
.../adjustment/LoanAdjustmentServiceImpl.java | 4 +-
9 files changed, 202 insertions(+), 49 deletions(-)
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
index 27d875a38a..0f67b5a1a8 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
@@ -4449,4 +4449,122 @@ Feature: LoanRepayment
| Transaction date | Transaction Type | Amount | Principal | Interest |
Fees | Penalties | Loan Balance | Reverted | Replayed |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 |
0.0 | 0.0 | 100.0 | false | false |
| 01 February 2024 | Repayment | 20.0 | 19.42 | 0.58 |
0.0 | 0.0 | 80.58 | false | false |
- When Admin set
"LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product
"DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation
rule
\ No newline at end of file
+ When Admin set
"LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product
"DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation
rule
+
+ @TestRailId:C3569
+ Scenario: Verify Loan is fully paid and closed after partial repayments,
Merchant issued refund which overpays the loan, partial CBR and reversal of 1st
repayment
+ When Admin sets the business date to "28 March 2025"
+ When Admin creates a client with random data
+ When Admin creates a fully customized loan with the following data:
+ | 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 |
+ | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 26
March 2025 | 120 | 35.29 | DECLINING_BALANCE |
DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS
| 1 | MONTHS | 3 | 0
| 0 | 0 |
ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "26 March 2025" with "120"
amount and expected disbursement date on "26 March 2025"
+ When Admin successfully disburse the loan on "26 March 2025" with "120"
EUR transaction amount
+ Then Loan Repayment schedule has 3 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 26 March 2025 | | 120.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 26 April 2025 | | 81.15 |
38.85 | 3.53 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0
| 42.38 |
+ | 2 | 30 | 26 May 2025 | | 41.16 |
39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0
| 42.38 |
+ | 3 | 31 | 26 June 2025 | | 0.0 |
41.16 | 1.21 | 0.0 | 0.0 | 42.37 | 0.0 | 0.0 | 0.0
| 42.37 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 120.0 | 7.13 | 0.0 | 0.0 | 127.13 | 0.0 | 0.0
| 0.0 | 127.13 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal | Interest |
Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 |
0.0 | 0.0 | 120.0 | false | false |
+ And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR
transaction amount
+ Then Loan Repayment schedule has 3 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 26 March 2025 | | 120.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 26 April 2025 | | 80.58 |
39.42 | 2.96 | 0.0 | 0.0 | 42.38 | 20.0 | 20.0 | 0.0
| 22.38 |
+ | 2 | 30 | 26 May 2025 | | 40.57 |
40.01 | 2.37 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0
| 42.38 |
+ | 3 | 31 | 26 June 2025 | | 0.0 |
40.57 | 1.19 | 0.0 | 0.0 | 41.76 | 0.0 | 0.0 | 0.0
| 41.76 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 120.0 | 6.52 | 0.0 | 0.0 | 126.52 | 20.0 | 20.0
| 0.0 | 106.52 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal | Interest |
Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 |
0.0 | 0.0 | 120.0 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 |
0.0 | 0.0 | 100.11 | false | false |
+ And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR
transaction amount
+ Then Loan Repayment schedule has 3 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 26 March 2025 | | 120.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 26 April 2025 | | 80.01 |
39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 40.0 | 40.0 | 0.0
| 2.38 |
+ | 2 | 30 | 26 May 2025 | | 39.98 |
40.03 | 2.35 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0
| 42.38 |
+ | 3 | 31 | 26 June 2025 | | 0.0 |
39.98 | 1.18 | 0.0 | 0.0 | 41.16 | 0.0 | 0.0 | 0.0
| 41.16 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 120.0 | 5.92 | 0.0 | 0.0 | 125.92 | 40.0 | 40.0
| 0.0 | 85.92 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal | Interest |
Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 |
0.0 | 0.0 | 120.0 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 |
0.0 | 0.0 | 100.11 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 |
0.0 | 0.0 | 80.11 | false | false |
+ And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY"
payment type on "27 March 2025" with 120 EUR transaction amount and
self-generated Idempotency key
+ Then Loan Repayment schedule has 3 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 26 March 2025 | | 120.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 |
39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0
| 0.0 |
+ | 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 |
37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0
| 0.0 |
+ | 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 |
42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0
| 0.0 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11
| 0.0 | 0.0 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0
| 0.0 | 0.0 | 120.0 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11
| 0.0 | 0.0 | 100.11 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0
| 0.0 | 0.0 | 80.11 | false | false |
+ | 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ | 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ | 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11
| 0.0 | 0.0 | 0.0 | false | false |
+ | 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11
| 0.0 | 0.0 | 0.0 | false | false |
+ Then Loan status will be "OVERPAID"
+ Then Loan has 0 outstanding amount
+ Then Loan has 40 overpaid amount
+ And Admin makes Credit Balance Refund transaction on "28 March 2025" with
20 EUR transaction amount
+ Then Loan Repayment schedule has 3 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 26 March 2025 | | 120.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 |
39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0
| 0.0 |
+ | 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 |
37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0
| 0.0 |
+ | 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 |
42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0
| 0.0 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11
| 0.0 | 0.0 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0
| 0.0 | 0.0 | 120.0 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11
| 0.0 | 0.0 | 100.11 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0
| 0.0 | 0.0 | 80.11 | false | false |
+ | 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ | 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ | 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11
| 0.0 | 0.0 | 0.0 | false | false |
+ | 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11
| 0.0 | 0.0 | 0.0 | false | false |
+ | 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ Then Loan status will be "OVERPAID"
+ Then Loan has 0 outstanding amount
+ Then Loan has 20 overpaid amount
+ When Customer undo "1"th "Repayment" transaction made on "27 March 2025"
+ Then Loan Repayment schedule has 3 periods, with the following data for
periods:
+ | Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
+ | | | 26 March 2025 | | 120.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 31 | 26 April 2025 | 27 March 2025 | 84.76 |
35.24 | 0.11 | 0.0 | 0.0 | 35.35 | 35.35 | 35.35 | 0.0
| 0.0 |
+ | 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 |
42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0
| 0.0 |
+ | 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 |
42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0
| 0.0 |
+ Then Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11
| 0.0 | 0.0 |
+ Then Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0
| 0.0 | 0.0 | 120.0 | false | false |
+ | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11
| 0.0 | 0.0 | 100.11 | true | false |
+ | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11
| 0.0 | 0.0 | 100.11 | false | true |
+ | 27 March 2025 | Merchant Issued Refund | 120.0 | 100.11 | 0.0
| 0.0 | 0.0 | 0.0 | false | true |
+ | 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | false | true |
+ | 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11
| 0.0 | 0.0 | 0.0 | false | true |
+ | 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11
| 0.0 | 0.0 | 0.0 | false | false |
+ | 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ Then Loan status will be "CLOSED_OBLIGATIONS_MET"
+ Then Loan has 0 outstanding amount
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
index 9d023d7296..afc9c0ed7d 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
@@ -60,7 +60,6 @@ public class ReprocessLoanTransactionsServiceImpl implements
ReprocessLoanTransa
public void reprocessTransactionsWithPostTransactionChecks(final Loan
loan, final LocalDate transactionDate) {
final ChangedTransactionDetail changedTransactionDetail =
reprocessTransactionsAndFetchChangedTransactions(loan,
loan.retrieveListOfTransactionsForReprocessing());
- loan.doPostLoanTransactionChecks(transactionDate,
loan.getLoanLifecycleStateMachine());
handleChangedDetail(changedTransactionDetail);
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
index b495af02f5..6673d77a80 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
@@ -275,8 +275,7 @@ public class ProgressiveLoanScheduleGenerator implements
LoanScheduleGenerator {
continue;
}
- Money outstandingBalance =
emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel,
periodDueDate,
- disbursementDate);
+ Money outstandingBalance =
emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel,
disbursementDate);
final Money disbursedAmount =
Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(),
mc);
final LoanScheduleModelDisbursementPeriod disbursementPeriod =
LoanScheduleModelDisbursementPeriod
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
index 312a116b7f..24487c499a 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
@@ -119,8 +119,7 @@ public interface EMICalculator {
Money getPeriodInterestTillDate(@NotNull
ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate
periodDueDate,
@NotNull LocalDate targetDate, boolean includeChargebackInterest);
- Money
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel
interestScheduleModel, LocalDate repaymentPeriodDueDate,
- LocalDate targetDate);
+ Money
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel
interestScheduleModel, LocalDate targetDate);
OutstandingDetails
getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model,
LocalDate targetDate);
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
index 03be2b04cd..35c1b5c474 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
@@ -256,7 +256,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
public PeriodDueDetails getDueAmounts(@NotNull
ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate
periodDueDate,
@NotNull LocalDate targetDate) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate
= recalculateScheduleModelTillDate(scheduleModel,
- periodDueDate, targetDate);
+ targetDate);
RepaymentPeriod repaymentPeriod =
recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
long notFullyRepaidRepaymentPeriodCount =
recalculatedScheduleModelTillDate.repaymentPeriods().stream()
.filter(rp -> !rp.isFullyPaid()).count();
@@ -265,6 +265,11 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
if (multiplePeriodIsUnpaid) {
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
+ Money totalOutstandingPrincipal =
recalculatedScheduleModelTillDate.getTotalOutstandingPrincipal();
+ Money outstandingPrincipal =
repaymentPeriod.getOutstandingPrincipal();
+ // If there are less outstanding principal than anticipated
+ Money emiAdjustment =
MathUtil.negativeToZero(outstandingPrincipal.minus(totalOutstandingPrincipal));
+
repaymentPeriod.setEmi(repaymentPeriod.getEmi().minus(emiAdjustment));
} else if (repaymentPeriod.isFullyPaid() && onePeriodIsUnpaid) {
repaymentPeriod.setEmi(MathUtil.min(repaymentPeriod.getOriginalEmi(), //
recalculatedScheduleModelTillDate.getTotalDuePrincipal() //
@@ -285,7 +290,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
public Money getPeriodInterestTillDate(@NotNull
ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate
periodDueDate,
@NotNull LocalDate targetDate, boolean includeChargebackInterest) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate
= recalculateScheduleModelTillDate(scheduleModel,
- periodDueDate, targetDate);
+ targetDate);
RepaymentPeriod repaymentPeriod =
recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
return includeChargebackInterest ?
repaymentPeriod.getCalculatedDueInterest()
:
repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(),
@@ -293,11 +298,18 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
}
@Override
- public Money
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate periodDueDate,
- LocalDate targetDate) {
+ public Money
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate targetDate) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate
= recalculateScheduleModelTillDate(scheduleModel,
- periodDueDate, targetDate);
- RepaymentPeriod repaymentPeriod =
recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
+ targetDate);
+ RepaymentPeriod repaymentPeriod =
recalculatedScheduleModelTillDate.findRepaymentPeriod(targetDate).orElseGet(()
-> {
+ // If target date is after maturity date
+ if
(targetDate.isAfter(recalculatedScheduleModelTillDate.getLastRepaymentPeriod().getDueDate()))
{
+ return
recalculatedScheduleModelTillDate.getLastRepaymentPeriod();
+ } else {
+ // if target date is before 1st disbursement date, we use 1st
repayment period
+ return
recalculatedScheduleModelTillDate.repaymentPeriods().get(0);
+ }
+ });
return repaymentPeriod.getOutstandingLoanBalance();
}
@@ -343,43 +355,58 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
@NotNull
private ProgressiveLoanInterestScheduleModel
recalculateScheduleModelTillDate(
- @NotNull ProgressiveLoanInterestScheduleModel scheduleModel,
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) {
+ @NotNull ProgressiveLoanInterestScheduleModel scheduleModel,
@NotNull LocalDate targetDate) {
MathContext mc = scheduleModel.mc();
ProgressiveLoanInterestScheduleModel scheduleModelCopy =
scheduleModel.deepCopy(mc);
- RepaymentPeriod repaymentPeriod =
scheduleModelCopy.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
-
- LocalDate adjustedTargetDate = targetDate;
- InterestPeriod interestPeriod;
- if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
- interestPeriod = repaymentPeriod.getFirstInterestPeriod();
- adjustedTargetDate = repaymentPeriod.getFromDate();
- } else if (targetDate.isAfter(repaymentPeriod.getDueDate())) {
- interestPeriod = repaymentPeriod.getLastInterestPeriod();
- adjustedTargetDate = repaymentPeriod.getDueDate();
+ boolean isBeforeFirstDisbursement =
targetDate.isBefore(scheduleModelCopy.repaymentPeriods().get(0).getFromDate());
+ boolean isAfterMaturityDate =
!targetDate.isBefore(scheduleModelCopy.getLastRepaymentPeriod().getDueDate());
+ if (isBeforeFirstDisbursement) {
+ scheduleModelCopy.repaymentPeriods().forEach(rp ->
rp.getInterestPeriods().clear());
+ return scheduleModelCopy;
+ } else if (isAfterMaturityDate) {
+ return scheduleModelCopy;
} else {
- interestPeriod =
repaymentPeriod.findInterestPeriod(targetDate).orElseThrow();
- }
- // TODO use findInterestPeriod
- scheduleModelCopy.findRepaymentPeriod(targetDate)//
- .flatMap(rp -> rp.findInterestPeriod(targetDate)).ifPresent(ip
-> ip.setDueDate(targetDate)); //
- interestPeriod.setDueDate(adjustedTargetDate);
- int index =
repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
- int nextIdx = index + 1;
- boolean thereIsInterestPeriodFromDateOnTargetDate =
repaymentPeriod.getInterestPeriods().size() > nextIdx
- &&
repaymentPeriod.getInterestPeriods().get(nextIdx).getFromDate().isEqual(targetDate);
- if (thereIsInterestPeriodFromDateOnTargetDate) {
- // NOTE: If there is a next interest period with fromDate on the
target date
- // then the related chargeback amounts comes from the next
interest period too.
- InterestPeriod nextInterestPeriod =
repaymentPeriod.getInterestPeriods().get(nextIdx);
-
interestPeriod.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
-
interestPeriod.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
+ RepaymentPeriod repaymentPeriod =
scheduleModelCopy.findRepaymentPeriod(targetDate).orElseThrow();
+
+ scheduleModelCopy.repaymentPeriods().forEach(rp -> {
+ if (rp.getDueDate().isAfter(targetDate)) {
+ if (rp.equals(repaymentPeriod)) {
+ rp.findInterestPeriod(targetDate).ifPresent(ip -> {
+ ip.setDueDate(targetDate);
+ int index = rp.getInterestPeriods().indexOf(ip);
+ int nextIdx = index + 1;
+ boolean thereIsInterestPeriodFromDateOnTargetDate
= ip.getRepaymentPeriod().getInterestPeriods()
+ .size() > nextIdx;
+ if (thereIsInterestPeriodFromDateOnTargetDate) {
+ // NOTE: If there is a next interest period
with fromDate on the target date
+ // then the related chargeback amounts comes
from the next interest period too.
+ InterestPeriod nextInterestPeriod =
ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx);
+
ip.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
+
ip.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
+ }
+ ip.getRepaymentPeriod().getInterestPeriods()
+ .subList(nextIdx,
ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
+ });
+ } else if (rp.getPrevious().isPresent() &&
rp.getPrevious().get().equals(repaymentPeriod)
+ &&
(rp.getInterestPeriods().get(0).getChargebackInterest().isGreaterThanZero()
+ ||
rp.getInterestPeriods().get(0).getChargebackPrincipal().isGreaterThanZero())) {
+ // NOTE: we need to check whether there is chargeback
on the 1st interest period of the next
+ // period
+ // if so, we need to retain that interest period, but
need to update due date to match with from
+ // date -> 0 interest
+
rp.getInterestPeriods().get(0).setDueDate(rp.getInterestPeriods().get(0).getFromDate());
+ if (rp.getInterestPeriods().size() > 1) {
+ rp.getInterestPeriods().subList(1,
rp.getInterestPeriods().size()).clear();
+ }
+ } else {
+ rp.getInterestPeriods().clear();
+ }
+ }
+ });
+
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(),
scheduleModelCopy);
+ calculateOutstandingBalance(scheduleModelCopy);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
}
- repaymentPeriod.getInterestPeriods().subList(nextIdx,
repaymentPeriod.getInterestPeriods().size()).clear();
- scheduleModelCopy.repaymentPeriods().forEach(rp ->
rp.getInterestPeriods().removeIf(ip -> ip.getDueDate().isAfter(targetDate)));
- calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(),
scheduleModelCopy);
- calculateOutstandingBalance(scheduleModelCopy);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
-
return scheduleModelCopy;
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
index e29bfc51c1..3a74d32f23 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
@@ -42,6 +42,7 @@ import java.util.stream.Collectors;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
@@ -338,6 +339,14 @@ public class ProgressiveLoanInterestScheduleModel {
return
repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero,
Money::plus);
}
+ public Money getTotalOutstandingPrincipal() {
+ return
MathUtil.negativeToZero(getTotalDuePrincipal().minus(getTotalPaidPrincipal()));
+ }
+
+ public Money getTotalOutstandingInterest() {
+ return
MathUtil.negativeToZero(getTotalDueInterest().minus(getTotalPaidInterest()));
+ }
+
public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate
transactionDate) {
return repaymentPeriods.stream() //
.filter(period -> isInPeriod(transactionDate,
period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
index 8aa2e595da..5390c17058 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
@@ -318,4 +318,8 @@ public final class RepaymentPeriod {
public boolean isFirstRepaymentPeriod() {
return previous == null;
}
+
+ public Money getOutstandingPrincipal() {
+ return getDuePrincipal().minus(getPaidPrincipal());
+ }
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index cfb030d17e..d9b10d53ca 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -302,7 +302,7 @@ public class LoanChargeWritePlatformServiceImpl implements
LoanChargeWritePlatfo
} else {
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
-
+ loan.doPostLoanTransactionChecks(transactionDate,
loan.getLoanLifecycleStateMachine());
loan =
loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
index a57a3954c2..3d1215353f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
@@ -184,8 +184,8 @@ public class LoanAdjustmentServiceImpl implements
LoanAdjustmentService {
if (!transactionIds.isEmpty()) {
this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountTransactions(transactionIds,
PortfolioAccountType.LOAN);
- loan.updateLoanSummaryAndStatus();
}
+ loan.updateLoanSummaryAndStatus();
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
@@ -267,8 +267,6 @@ public class LoanAdjustmentServiceImpl implements
LoanAdjustmentService {
writeOffTransaction.reverse();
}
- loan.updateLoanSummaryAndStatus();
-
if (newTransactionDetail.isRepaymentLikeType() ||
newTransactionDetail.isInterestWaiver()) {
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan,
newTransactionDetail,
loanLifecycleStateMachine, transactionForAdjustment,
scheduleGeneratorDTO);