FINERACT-1971: Fix loan balance for additional (N+1) installment

This commit is contained in:
Adam Saghy
2024-03-19 22:11:26 +01:00
parent e66da3eaba
commit aac6f7cd21
3 changed files with 170 additions and 18 deletions
@@ -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");
@@ -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
@@ -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));
});
}
}