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 complete = rs.getBoolean("complete");
|
||||||
final boolean isAdditional = rs.getBoolean("isAdditional");
|
final boolean isAdditional = rs.getBoolean("isAdditional");
|
||||||
BigDecimal disbursedAmount = BigDecimal.ZERO;
|
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
|
// Add the Charge back or Credits to the initial amount to avoid negative balance
|
||||||
final BigDecimal credits = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalCredits");
|
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);
|
totalPrincipalDisbursed = totalPrincipalDisbursed.add(disbursedAmount);
|
||||||
|
|
||||||
@@ -1313,16 +1311,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal outstandingPrincipalBalanceOfLoan = this.outstandingLoanPrincipalBalance.subtract(principalDue);
|
BigDecimal outstandingPrincipalBalanceOfLoan = this.outstandingLoanPrincipalBalance.subtract(principalDue);
|
||||||
if (isAdditional) {
|
|
||||||
outstandingPrincipalBalanceOfLoan = this.outstandingLoanPrincipalBalance.add(principalDue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update based on current period values
|
// update based on current period values
|
||||||
this.lastDueDate = dueDate;
|
this.lastDueDate = dueDate;
|
||||||
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.subtract(principalDue);
|
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.subtract(principalDue);
|
||||||
if (isAdditional) {
|
|
||||||
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(principalDue);
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean isDownPayment = rs.getBoolean("isDownPayment");
|
final boolean isDownPayment = rs.getBoolean("isDownPayment");
|
||||||
|
|
||||||
|
|||||||
+21
-5
@@ -547,6 +547,13 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
outstandingTotalExpected, outstandingTotal));
|
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++;
|
installmentNumber++;
|
||||||
Assertions.assertEquals(installmentNumber, period.getPeriod());
|
Assertions.assertEquals(installmentNumber, period.getPeriod());
|
||||||
}
|
}
|
||||||
@@ -716,27 +723,35 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Installment installment(double principalAmount, Boolean completed, String dueDate) {
|
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,
|
protected Installment installment(double principalAmount, double interestAmount, double totalOutstandingAmount, Boolean completed,
|
||||||
String dueDate) {
|
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,
|
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double totalOutstandingAmount,
|
||||||
Boolean completed, String dueDate) {
|
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,
|
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount,
|
||||||
double totalOutstandingAmount, Boolean completed, String dueDate) {
|
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,
|
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount,
|
||||||
OutstandingAmounts outstandingAmounts, Boolean completed, String dueDate) {
|
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) {
|
protected OutstandingAmounts outstanding(double principal, double fee, double penalty, double total) {
|
||||||
@@ -921,6 +936,7 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
Boolean completed;
|
Boolean completed;
|
||||||
String dueDate;
|
String dueDate;
|
||||||
OutstandingAmounts outstandingAmounts;
|
OutstandingAmounts outstandingAmounts;
|
||||||
|
Double loanBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@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.PostLoansLoanIdChargesResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
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.PostLoansRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansResponse;
|
import org.apache.fineract.client.models.PostLoansResponse;
|
||||||
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
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