FINERACT-1971: Fix loan balance for additional (N+1) installment
This commit is contained in:
+5
-13
@@ -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");
|
||||
|
||||
|
||||
+21
-5
@@ -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
|
||||
|
||||
+144
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user