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 aac6f7cd2 FINERACT-1971: Fix loan balance for additional (N+1)
installment
aac6f7cd2 is described below
commit aac6f7cd21a06c0cdc6384a1d2e84228535e3817
Author: Adam Saghy <[email protected]>
AuthorDate: Tue Mar 19 18:17:52 2024 +0100
FINERACT-1971: Fix loan balance for additional (N+1) installment
---
.../service/LoanReadPlatformServiceImpl.java | 18 +--
.../integrationtests/BaseLoanIntegrationTest.java | 26 +++-
.../loan/reaging/LoanReAgingIntegrationTest.java | 144 +++++++++++++++++++++
3 files changed, 170 insertions(+), 18 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index bfa5b96b5..ef43af882 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -1225,16 +1225,14 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
final boolean complete = rs.getBoolean("complete");
final boolean isAdditional = rs.getBoolean("isAdditional");
BigDecimal disbursedAmount = BigDecimal.ZERO;
- if (!isAdditional) {
- disbursedAmount =
processDisbursementData(loanScheduleType, disbursementData, fromDate, dueDate,
disbursementPeriodIds,
- disbursementChargeAmount, waivedChargeAmount,
periods);
- }
+ disbursedAmount = processDisbursementData(loanScheduleType,
disbursementData, fromDate, dueDate, disbursementPeriodIds,
+ disbursementChargeAmount, waivedChargeAmount, periods);
+
// Add the Charge back or Credits to the initial amount to
avoid negative balance
final BigDecimal credits =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalCredits");
- if (!isAdditional) {
- this.outstandingLoanPrincipalBalance =
this.outstandingLoanPrincipalBalance.add(credits);
- }
+
+ this.outstandingLoanPrincipalBalance =
this.outstandingLoanPrincipalBalance.add(credits);
totalPrincipalDisbursed =
totalPrincipalDisbursed.add(disbursedAmount);
@@ -1313,16 +1311,10 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
}
BigDecimal outstandingPrincipalBalanceOfLoan =
this.outstandingLoanPrincipalBalance.subtract(principalDue);
- if (isAdditional) {
- outstandingPrincipalBalanceOfLoan =
this.outstandingLoanPrincipalBalance.add(principalDue);
- }
// update based on current period values
this.lastDueDate = dueDate;
this.outstandingLoanPrincipalBalance =
this.outstandingLoanPrincipalBalance.subtract(principalDue);
- if (isAdditional) {
- this.outstandingLoanPrincipalBalance =
this.outstandingLoanPrincipalBalance.add(principalDue);
- }
final boolean isDownPayment = rs.getBoolean("isDownPayment");
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index fe7b07331..95156bc93 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -547,6 +547,13 @@ public abstract class BaseLoanIntegrationTest {
outstandingTotalExpected,
outstandingTotal));
}
+ Double loanBalanceExpected = installments[i].loanBalance;
+ Double loanBalance =
period.getPrincipalLoanBalanceOutstanding();
+ if (loanBalanceExpected != null) {
+ Assertions.assertEquals(loanBalanceExpected, loanBalance,
+ "%d. installment's loan balance is different,
expected: %.2f, actual: %.2f".formatted(i, loanBalanceExpected,
+ loanBalance));
+ }
installmentNumber++;
Assertions.assertEquals(installmentNumber, period.getPeriod());
}
@@ -716,27 +723,35 @@ public abstract class BaseLoanIntegrationTest {
}
protected Installment installment(double principalAmount, Boolean
completed, String dueDate) {
- return new Installment(principalAmount, null, null, null, null,
completed, dueDate, null);
+ return new Installment(principalAmount, null, null, null, null,
completed, dueDate, null, null);
}
protected Installment installment(double principalAmount, double
interestAmount, double totalOutstandingAmount, Boolean completed,
String dueDate) {
- return new Installment(principalAmount, interestAmount, null, null,
totalOutstandingAmount, completed, dueDate, null);
+ return new Installment(principalAmount, interestAmount, null, null,
totalOutstandingAmount, completed, dueDate, null, null);
}
protected Installment installment(double principalAmount, double
interestAmount, double feeAmount, double totalOutstandingAmount,
Boolean completed, String dueDate) {
- return new Installment(principalAmount, interestAmount, feeAmount,
null, totalOutstandingAmount, completed, dueDate, null);
+ return new Installment(principalAmount, interestAmount, feeAmount,
null, totalOutstandingAmount, completed, dueDate, null, null);
}
protected Installment installment(double principalAmount, double
interestAmount, double feeAmount, double penaltyAmount,
double totalOutstandingAmount, Boolean completed, String dueDate) {
- return new Installment(principalAmount, interestAmount, feeAmount,
penaltyAmount, totalOutstandingAmount, completed, dueDate, null);
+ return new Installment(principalAmount, interestAmount, feeAmount,
penaltyAmount, totalOutstandingAmount, completed, dueDate, null,
+ null);
}
protected Installment installment(double principalAmount, double
interestAmount, double feeAmount, double penaltyAmount,
OutstandingAmounts outstandingAmounts, Boolean completed, String
dueDate) {
- return new Installment(principalAmount, interestAmount, feeAmount,
penaltyAmount, null, completed, dueDate, outstandingAmounts);
+ return new Installment(principalAmount, interestAmount, feeAmount,
penaltyAmount, null, completed, dueDate, outstandingAmounts,
+ null);
+ }
+
+ protected Installment installment(double principalAmount, double
interestAmount, double feeAmount, double penaltyAmount,
+ double totalOutstanding, Boolean completed, String dueDate, double
loanBalance) {
+ return new Installment(principalAmount, interestAmount, feeAmount,
penaltyAmount, totalOutstanding, completed, dueDate, null,
+ loanBalance);
}
protected OutstandingAmounts outstanding(double principal, double fee,
double penalty, double total) {
@@ -921,6 +936,7 @@ public abstract class BaseLoanIntegrationTest {
Boolean completed;
String dueDate;
OutstandingAmounts outstandingAmounts;
+ Double loanBalance;
}
@AllArgsConstructor
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
index e3bebe228..2db311f86 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
@@ -28,6 +28,7 @@ import
org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
+import
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
@@ -262,4 +263,147 @@ public class LoanReAgingIntegrationTest extends
BaseLoanIntegrationTest {
});
}
+ @Test
+ public void test_LoanReAgeTransaction_WithChargeback_Works() {
+ AtomicLong createdLoanId = new AtomicLong();
+
+ runAt("01 January 2023", () -> {
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ int numberOfRepayments = 3;
+ int repaymentEvery = 1;
+
+ // Create Loan Product
+ PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
//
+ .numberOfRepayments(numberOfRepayments) //
+ .repaymentEvery(repaymentEvery) //
+ .installmentAmountInMultiplesOf(null) //
+ .enableDownPayment(true) //
+
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25)) //
+ .enableAutoRepaymentForDownPayment(true) //
+
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
+
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(product);
+ Long loanProductId = loanProductResponse.getResourceId();
+
+ // Apply and Approve Loan
+ double amount = 1250.0;
+
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
loanProductId, "01 January 2023", amount, numberOfRepayments)//
+
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+ .repaymentEvery(repaymentEvery)//
+ .loanTermFrequency(numberOfRepayments)//
+ .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+ .loanTermFrequencyType(RepaymentFrequencyType.MONTHS);
+
+ PostLoansResponse postLoansResponse =
loanTransactionHelper.applyLoan(applicationRequest);
+
+ PostLoansLoanIdResponse approvedLoanResult =
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+ approveLoanRequest(amount, "01 January 2023"));
+
+ Long loanId = approvedLoanResult.getLoanId();
+
+ // disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January
2023");
+
+ // verify transactions
+ verifyTransactions(loanId, //
+ transaction(1250.0, "Disbursement", "01 January 2023"), //
+ transaction(312.5, "Down Payment", "01 January 2023") //
+ );
+
+ // verify schedule
+ verifyRepaymentSchedule(loanId, //
+ installment(1250, null, "01 January 2023"), //
+ installment(312.5, true, "01 January 2023"), //
+ installment(312.5, false, "01 February 2023"), //
+ installment(312.5, false, "01 March 2023"), //
+ installment(312.5, false, "01 April 2023") //
+ );
+ checkMaturityDates(loanId, LocalDate.of(2023, 4, 1),
LocalDate.of(2023, 4, 1));
+ createdLoanId.set(loanId);
+ });
+
+ String repaymentExternalId = UUID.randomUUID().toString();
+ runAt("01 February 2023", () -> {
+ long loanId = createdLoanId.get();
+
+ loanTransactionHelper.makeLoanRepayment(loanId, new
PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
+ .transactionDate("01 February
2023").locale("en").transactionAmount(100.0).externalId(repaymentExternalId));
+
+ // verify transactions
+ verifyTransactions(loanId, //
+ transaction(1250.0, "Disbursement", "01 January 2023"), //
+ transaction(312.5, "Down Payment", "01 January 2023"), //
+ transaction(100.0, "Repayment", "01 February 2023") //
+ );
+
+ // verify schedule
+ verifyRepaymentSchedule(loanId, //
+ installment(1250, null, "01 January 2023"), //
+ installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023"),
//
+ installment(312.5, 0, 0, 0, 212.5, false, "01 February
2023"), //
+ installment(312.5, 0, 0, 0, 312.5, false, "01 March
2023"), //
+ installment(312.5, 0, 0, 0, 312.5, false, "01 April 2023")
//
+ );
+ });
+
+ runAt("10 April 2023", () -> {
+ long loanId = createdLoanId.get();
+
+ // disburse Loan
+ loanTransactionHelper.chargebackLoanTransaction(loanId,
repaymentExternalId,
+ new
PostLoansLoanIdTransactionsTransactionIdRequest().locale("en").transactionAmount(100.0));
+
+ // verify transactions
+ verifyTransactions(loanId, //
+ transaction(1250.0, "Disbursement", "01 January 2023"), //
+ transaction(312.5, "Down Payment", "01 January 2023"), //
+ transaction(100.0, "Repayment", "01 February 2023"), //
+ transaction(100.0, "Chargeback", "10 April 2023") //
+ );
+
+ // verify schedule
+ verifyRepaymentSchedule(loanId, //
+ installment(1250, null, "01 January 2023"), //
+ installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023",
937.5), //
+ installment(312.5, 0, 0, 0, 212.5, false, "01 February
2023", 625.0), //
+ installment(312.5, 0, 0, 0, 312.5, false, "01 March 2023",
312.5), //
+ installment(312.5, 0, 0, 0, 312.5, false, "01 April 2023",
0.0), //
+ installment(100.0, 0.0, 0.0, 0.0, 100.0, false, "10 April
2023", 0.0) //
+ );
+ checkMaturityDates(loanId, LocalDate.of(2023, 4, 1),
LocalDate.of(2023, 4, 1));
+ });
+
+ runAt("12 April 2023", () -> {
+ long loanId = createdLoanId.get();
+
+ // create re-age transaction
+ reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12
April 2023", 4);
+
+ // verify transactions
+ verifyTransactions(loanId, //
+ transaction(1250.0, "Disbursement", "01 January 2023"), //
+ transaction(312.5, "Down Payment", "01 January 2023"), //
+ transaction(100.0, "Repayment", "01 February 2023"), //
+ transaction(100.0, "Chargeback", "10 April 2023"), //
+ transaction(937.5, "Re-age", "12 April 2023") //
+ );
+
+ verifyRepaymentSchedule(loanId, //
+ installment(1250, null, "01 January 2023"), //
+ installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023",
937.5), //
+ installment(100.0, 0, 0, 0, 0.0, true, "01 February 2023",
837.5), //
+ installment(0.0, 0, 0, 0, 0.0, true, "01 March 2023",
837.5), //
+ installment(0.0, 0, 0, 0, 0.0, true, "01 April 2023",
837.5), //
+ installment(0.0, 0.0, 0.0, 0.0, 0.0, true, "10 April
2023", 937.5), //
+ installment(234.38, 0.0, 0.0, 0.0, 234.38, false, "12
April 2023", 703.12), //
+ installment(234.38, 0.0, 0.0, 0.0, 234.38, false, "12 May
2023", 468.74), //
+ installment(234.38, 0.0, 0.0, 0.0, 234.38, false, "12 June
2023", 234.36), //
+ installment(234.36, 0.0, 0.0, 0.0, 234.36, false, "12 July
2023", 0.0) //
+ );
+ checkMaturityDates(loanId, LocalDate.of(2023, 7, 12),
LocalDate.of(2023, 7, 12));
+ });
+ }
}