FINERACT-2081: Fix Accrual Activity Reversal on Installment Due Date.

* Accrual Activity is not recalculated during reopen transaction recalculation
* integration tests cover reverse replay verification and extra use cases
This commit is contained in:
Soma Sörös
2025-01-30 12:45:38 +01:00
committed by Adam Saghy
parent 8b80f5a934
commit 78cd5b106a
5 changed files with 450 additions and 25 deletions
@@ -185,7 +185,10 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
* changedTransactionDetail accordingly
**/
if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
if (newLoanTransaction.isReversed()) {
loanTransaction.reverse();
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), loanTransaction);
} else if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
} else {
@@ -225,8 +228,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
.filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment,
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)))
.findFirst().orElseThrow();
if (loanTransaction.getDateOf().isEqual(currentInstallment.getDueDate()) || installments.stream()
.filter(i -> !i.isAdditional() && !i.isDownPayment()).noneMatch(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)) {
if (currentInstallment.isNotFullyPaidOff() && (currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
|| (currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
&& loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate())))) {
loanTransaction.reverse();
} else {
loanTransaction.resetDerivedComponents();
final Money principalPortion = Money.zero(currency);
Money interestPortion = currentInstallment.getInterestCharged(currency);
@@ -50,6 +50,7 @@ import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
@@ -62,6 +63,7 @@ import org.apache.fineract.client.models.BusinessDateRequest;
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdStatus;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.JournalEntryTransactionItem;
import org.apache.fineract.client.models.PaymentAllocationOrder;
@@ -238,6 +240,32 @@ public abstract class BaseLoanIntegrationTest {
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
}
/**
* Verifies the loan status by applying the given extractor function to the status of the loan details. This method
* ensures that the loan details, loan status, and the result of the extractor are not null and asserts that the
* result of the extractor function is true.
*
* @param loanDetails
* the loan details object containing the loan status
* @param extractor
* a function that extracts a boolean value from the loan status for verification
* @throws AssertionError
* if any of the following conditions are not met:
* <ul>
* <li>The loan details object is not null</li>
* <li>The loan status in the loan details is not null</li>
* <li>The value extracted by the extractor function is not null</li>
* <li>The value extracted by the extractor function is true</li>
* </ul>
*/
protected void verifyLoanStatus(GetLoansLoanIdResponse loanDetails, Function<GetLoansLoanIdStatus, Boolean> extractor) {
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
Boolean actualValue = extractor.apply(loanDetails.getStatus());
Assertions.assertNotNull(actualValue);
Assertions.assertTrue(actualValue);
}
private String getNonByPassUserAuthKey(RequestSpecification requestSpec, ResponseSpecification responseSpec) {
// creates the user
UserHelper.getSimpleUserWithoutBypassPermission(requestSpec, responseSpec);
@@ -42,6 +42,8 @@ import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdStatus;
import org.apache.fineract.client.models.GlobalConfigurationPropertyData;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
@@ -53,6 +55,7 @@ import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
@@ -715,6 +718,81 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
});
}
/**
* Using Interest bearing Progressive Loan, Accrual Activity Posting, InterestRecalculation, 25% yearly interest 6
* repayment 450 USD principal.
* <li>apply, approve and disburse backdated on 17 August 2024</li>
* <li>repay 600 on 17 January 2025</li>
* <li>verify Accrual and Accrual Activity transaction creation</li>
* <li>verify that the loan become overpaid</li>
* <li>reverse repayment on same day</li>
* <li>verify there is no reverse replayed transaction during reversing the repayment</li>
* <li>verify transaction reversals</li>
*/
@Test
public void testInterestBearingProgressiveInterestRecalculationReopenDueReverseRepayment() {
runAt("17 January 2025", () -> {
externalEventHelper.enableBusinessEvent("LoanAdjustTransactionBusinessEvent");
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() //
.description("Interest bearing Progressive Loan USD, Accrual Activity Posting, NO InterestRecalculation") //
.enableAccrualActivityPosting(true) //
.daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.isInterestRecalculationEnabled(false));//
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
loanProductsResponse.getResourceId(), "17 August 2024", 450.0, 25.0, 6, null));
Long loanId = postLoansResponse.getLoanId();
Assertions.assertNotNull(loanId);
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(450.0, "17 August 2024"));
disburseLoan(loanId, BigDecimal.valueOf(450.0), "17 August 2024");
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024") //
);
Long repaymentId = loanTransactionHelper.makeLoanRepayment("17 January 2025", 600.0f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(600.0, "Repayment", "17 January 2025"), //
transaction(33.52, "Accrual", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(4.99, "Accrual Activity", "17 January 2025")); //
deleteAllExternalEvents();
loanTransactionHelper.reverseRepayment(loanId.intValue(), repaymentId.intValue(), "17 January 2025");
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
// Verify that there were no reverse-replay event
List<ExternalEventDTO> list = allExternalEvents.stream() //
.filter(x -> "LoanAdjustTransactionBusinessEvent".equals(x.getType()) //
&& x.getPayLoad().get("newTransactionDetail") != null //
&& x.getPayLoad().get("transactionToAdjust") != null) //
.toList(); //
Assertions.assertEquals(0, list.size());
// verify that there were 2 transaction reversal event
list = allExternalEvents.stream() //
.filter(x -> "LoanAdjustTransactionBusinessEvent".equals(x.getType()) //
&& x.getPayLoad().get("newTransactionDetail") == null //
&& x.getPayLoad().get("transactionToAdjust") != null) //
.toList(); //
Assertions.assertEquals(2, list.size());
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(33.52, "Accrual", "17 January 2025"), //
reversedTransaction(600.0, "Repayment", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024")); //
});
}
@Test
public void verifyInterestRefundPostBusinessEventCreatedForMerchantIssuedRefundWithInterestRefund() {
AtomicReference<Long> loanIdRef = new AtomicReference<>();
@@ -43,6 +43,7 @@ import org.apache.fineract.client.models.ChargeToGLAccountMapper;
import org.apache.fineract.client.models.GetLoanFeeToIncomeAccountMappings;
import org.apache.fineract.client.models.GetLoanPaymentChannelToFundSourceMappings;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdStatus;
import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostChargesRequest;
import org.apache.fineract.client.models.PostChargesResponse;
@@ -108,6 +109,323 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
"EXTERNAL_ASSET_OWNER_TRANSFER", "ACCRUAL_ACTIVITY_POSTING");
}
/**
* Using Interest bearing Progressive Loan, Accrual Activity Posting, NO InterestRecalculation, 25% yearly interest
* 6 repayment 450 USD principal.
* <li>apply, approve and disburse backdated on 17 August 2024</li>
* <li>repay 600 on 17 January 2025</li>
* <li>verify Accrual and Accrual Activity transaction creation</li>
* <li>verify that the loan become overpaid</li>
* <li>reverse repayment on same day</li>
* <li>verify transaction reversals</li>
*/
@Test
public void testInterestBearingProgressiveNoInterestRecalculationReopenDueReverseRepayment1() {
runAt("17 January 2025", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() //
.description("Interest bearing Progressive Loan USD, Accrual Activity Posting, NO InterestRecalculation") //
.enableAccrualActivityPosting(true) //
.daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.isInterestRecalculationEnabled(false));//
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
loanProductsResponse.getResourceId(), "17 August 2024", 450.0, 25.0, 6, null));
Long loanId = postLoansResponse.getLoanId();
Assertions.assertNotNull(loanId);
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(450.0, "17 August 2024"));
disburseLoan(loanId, BigDecimal.valueOf(450.0), "17 August 2024");
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024") //
);
Long repaymentId = loanTransactionHelper.makeLoanRepayment("17 January 2025", 600.0f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(600.0, "Repayment", "17 January 2025"), //
transaction(33.52, "Accrual", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(4.99, "Accrual Activity", "17 January 2025")); //
loanTransactionHelper.reverseRepayment(loanId.intValue(), repaymentId.intValue(), "17 January 2025");
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(33.52, "Accrual", "17 January 2025"), //
reversedTransaction(600.0, "Repayment", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024")); //
});
}
/**
* Using Interest bearing Progressive Loan, Accrual Activity Posting, InterestRecalculation, 25% yearly interest 6
* repayment 450 USD principal.
* <li>apply, approve and disburse backdated on 17 August 2024</li>
* <li>repay 600 on 17 January 2025</li>
* <li>verify Accrual and Accrual Activity transaction creation</li>
* <li>verify that the loan become overpaid</li>
* <li>reverse repayment on same day</li>
* <li>verify transaction reversals</li>
*/
@Test
public void testInterestBearingProgressiveInterestRecalculationReopenDueReverseRepayment() {
runAt("17 January 2025", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() //
.description("Interest bearing Progressive Loan USD, Accrual Activity Posting, NO InterestRecalculation") //
.enableAccrualActivityPosting(true) //
.daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.isInterestRecalculationEnabled(false));//
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
loanProductsResponse.getResourceId(), "17 August 2024", 450.0, 25.0, 6, null));
Long loanId = postLoansResponse.getLoanId();
Assertions.assertNotNull(loanId);
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(450.0, "17 August 2024"));
disburseLoan(loanId, BigDecimal.valueOf(450.0), "17 August 2024");
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024") //
);
Long repaymentId = loanTransactionHelper.makeLoanRepayment("17 January 2025", 600.0f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(600.0, "Repayment", "17 January 2025"), //
transaction(33.52, "Accrual", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(4.99, "Accrual Activity", "17 January 2025")); //
loanTransactionHelper.reverseRepayment(loanId.intValue(), repaymentId.intValue(), "17 January 2025");
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(33.52, "Accrual", "17 January 2025"), //
reversedTransaction(600.0, "Repayment", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024")); //
});
}
/**
* Using Interest bearing Progressive Loan, Accrual Activity Posting, NO InterestRecalculation, 25% yearly interest
* 6 repayment 450 USD principal.
* <li>apply, approve and disburse backdated on 17 August 2024</li>
* <li>repay 600 on 17 January 2025</li>
* <li>verify Accrual and Accrual Activity transaction creation</li>
* <li>verify that the loan become overpaid</li>
* <li>reverse repayment on same day</li>
* <li>verify transaction reversals</li>
*/
@Test
public void testInterestBearingProgressiveNoInterestRecalculationReopenDueReverseRepayment1b() {
runAt("18 January 2025", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() //
.description("Interest bearing Progressive Loan USD, Accrual Activity Posting, NO InterestRecalculation") //
.enableAccrualActivityPosting(true) //
.daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.isInterestRecalculationEnabled(false));//
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
loanProductsResponse.getResourceId(), "17 August 2024", 450.0, 25.0, 6, null));
Long loanId = postLoansResponse.getLoanId();
Assertions.assertNotNull(loanId);
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(450.0, "17 August 2024"));
disburseLoan(loanId, BigDecimal.valueOf(450.0), "17 August 2024");
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024") //
);
Long repaymentId = loanTransactionHelper.makeLoanRepayment("17 January 2025", 600.0f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(600.0, "Repayment", "17 January 2025"), //
transaction(33.52, "Accrual", "18 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(4.99, "Accrual Activity", "17 January 2025")); //
loanTransactionHelper.reverseRepayment(loanId.intValue(), repaymentId.intValue(), "17 January 2025");
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(33.52, "Accrual", "18 January 2025"), //
reversedTransaction(600.0, "Repayment", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(3.31, "Accrual Activity", "17 January 2025")); //
});
}
@Test
public void testAccrualActivityPostingAndReversalsInterestBearingProgressiveInterestRecalculationMerchantIssuedRefund() {
runAt("17 January 2025", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() //
.description("Interest bearing Progressive Loan USD, Accrual Activity Posting, InterestRecalculation") //
.enableAccrualActivityPosting(true) //
.currencyCode("USD") //
.daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.isInterestRecalculationEnabled(true));//
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
loanProductsResponse.getResourceId(), "17 August 2024", 450.0, 25.0, 6, null));
Long loanId = postLoansResponse.getLoanId();
Assertions.assertNotNull(loanId);
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(450.0, "17 August 2024"));
disburseLoan(loanId, BigDecimal.valueOf(450.0), "17 August 2024");
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024") //
);
Long repaymentId = loanTransactionHelper.makeLoanRepayment("17 January 2025", 497.04f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getClosedObligationsMet);
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(497.04, "Repayment", "17 January 2025"), //
transaction(47.04, "Accrual", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(9.22, "Accrual Activity", "17 October 2024"), //
transaction(9.53, "Accrual Activity", "17 November 2024"), //
transaction(9.22, "Accrual Activity", "17 December 2024"), //
transaction(9.54, "Accrual Activity", "17 January 2025")); //
loanTransactionHelper.makeLoanRepayment("MerchantIssuedRefund", "17 August 2024", 450.0f, loanId.intValue()).getResourceId();
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(450.0, "Merchant Issued Refund", "17 August 2024"), //
transaction(497.04, "Repayment", "17 January 2025"), //
transaction(47.04, "Accrual", "17 January 2025"), //
transaction(47.04, "Accrual Adjustment", "17 January 2025")); //
});
}
/**
* Using Interest bearing Progressive Loan, Accrual Activity Posting, NO InterestRecalculation, 25% yearly interest
* 6 repayment 450 USD principal.
* <li>apply, approve and disburse backdated on 17 August 2024</li>
* <li>repay 600 on 17 January 2025</li>
* <li>verify Accrual and Accrual Activity transaction creation</li>
* <li>verify that the loan become overpaid</li>
* <li>reverse repayment on same day verify transaction reversals</li>
*/
@Test
public void testInterestBearingProgressiveNoInterestRecalculationReopenDueReverseRepayment2b() {
runAt("17 January 2025", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() //
.description("Interest bearing Progressive Loan USD, Accrual Activity Posting, NO InterestRecalculation") //
.enableAccrualActivityPosting(true) //
.currencyCode("USD") //
.daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.isInterestRecalculationEnabled(false));//
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
loanProductsResponse.getResourceId(), "17 August 2024", 450.0, 25.0, 6, null));
Long loanId = postLoansResponse.getLoanId();
Assertions.assertNotNull(loanId);
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(450.0, "17 August 2024"));
disburseLoan(loanId, BigDecimal.valueOf(450.0), "17 August 2024");
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024") //
);
Long repaymentId = loanTransactionHelper.makeLoanRepayment("17 January 2025", 483.52f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getClosedObligationsMet);
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(483.52, "Repayment", "17 January 2025"), //
transaction(33.52, "Accrual", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(4.99, "Accrual Activity", "17 January 2025")); //
addCharge(loanId, false, 15.0, "15 January 2025");
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(33.52, "Accrual", "17 January 2025"), //
transaction(483.52, "Repayment", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024")); //
});
}
/**
* Using Interest bearing Progressive Loan, Accrual Activity Posting, NO InterestRecalculation, 25% yearly interest
* 6 repayment 450 USD principal.
* <li>apply, approve and disburse backdated on 17 August 2024</li>
* <li>repay 600 on 17 January 2025</li>
* <li>verify Accrual and Accrual Activity transaction creation</li>
* <li>verify that the loan become overpaid</li>
* <li>reverse repayment on same day verify transaction reversals</li>
*/
@Test
public void testInterestBearingProgressiveNoInterestRecalculationReopenDueReverseRepayment2c() {
runAt("18 January 2025", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() //
.description("Interest bearing Progressive Loan USD, Accrual Activity Posting, NO InterestRecalculation") //
.enableAccrualActivityPosting(true) //
.currencyCode("USD") //
.daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.isInterestRecalculationEnabled(false));//
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
loanProductsResponse.getResourceId(), "17 August 2024", 450.0, 25.0, 6, null));
Long loanId = postLoansResponse.getLoanId();
Assertions.assertNotNull(loanId);
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(450.0, "17 August 2024"));
disburseLoan(loanId, BigDecimal.valueOf(450.0), "17 August 2024");
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024") //
);
Long repaymentId = loanTransactionHelper.makeLoanRepayment("17 January 2025", 483.52f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getClosedObligationsMet);
verifyTransactions(loanId, //
transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(483.52, "Repayment", "17 January 2025"), //
transaction(33.52, "Accrual", "18 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(4.99, "Accrual Activity", "17 January 2025")); //
addCharge(loanId, false, 15.0, "15 January 2025");
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(450.0, "Disbursement", "17 August 2024"), //
transaction(33.52, "Accrual", "18 January 2025"), //
transaction(483.52, "Repayment", "17 January 2025"), //
transaction(9.53, "Accrual Activity", "17 September 2024"), //
transaction(7.77, "Accrual Activity", "17 October 2024"), //
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(18.31, "Accrual Activity", "17 January 2025")); //
});
}
// Create Loan with Interest and enabled Accrual Activity Posting
// Approve and disburse loan
// charge penalty with due date as 1st installment
@@ -955,10 +1273,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
Long repaymentId = loanTransactionHelper.makeLoanRepayment("02 January 2024", 370.0f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
Assertions.assertNotNull(loanDetails.getStatus().getOverpaid());
Assertions.assertTrue(loanDetails.getStatus().getOverpaid());
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 January 2024"),
transaction(100.0, "Down Payment", "01 January 2024"), transaction(8.76, "Accrual", "02 January 2024"),
@@ -991,10 +1306,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
Long repaymentId = loanTransactionHelper.makeLoanRepayment("01 January 2024", 370.0f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
Assertions.assertNotNull(loanDetails.getStatus().getOverpaid());
Assertions.assertTrue(loanDetails.getStatus().getOverpaid());
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 January 2024"),
transaction(100.0, "Down Payment", "01 January 2024"), transaction(8.76, "Accrual", "01 January 2024"),
@@ -1002,10 +1314,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
loanTransactionHelper.reverseRepayment(loanId.intValue(), repaymentId.intValue(), "01 January 2024");
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
Assertions.assertNotNull(loanDetails.getStatus().getActive());
Assertions.assertTrue(loanDetails.getStatus().getActive());
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 January 2024"),
transaction(100.0, "Down Payment", "01 January 2024"), transaction(8.76, "Accrual", "01 January 2024"),
reversedTransaction(370.0, "Repayment", "01 January 2024"));
@@ -1038,20 +1347,14 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
Long repaymentId = loanTransactionHelper.makeLoanRepayment("01 January 2024", 370.0f, loanId.intValue()).getResourceId();
Assertions.assertNotNull(repaymentId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
Assertions.assertNotNull(loanDetails.getStatus().getOverpaid());
Assertions.assertTrue(loanDetails.getStatus().getOverpaid());
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getOverpaid);
verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 January 2024"),
transaction(100.0, "Down Payment", "01 January 2024"), transaction(38.76, "Accrual", "01 January 2024"),
transaction(38.76, "Accrual Activity", "01 January 2024"), transaction(370.0, "Repayment", "01 January 2024"));
loanTransactionHelper.reverseRepayment(loanId.intValue(), repaymentId.intValue(), "01 January 2024");
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
Assertions.assertNotNull(loanDetails.getStatus().getActive());
Assertions.assertTrue(loanDetails.getStatus().getActive());
verifyLoanStatus(loanDetails, GetLoansLoanIdStatus::getActive);
verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 January 2024"),
transaction(100.0, "Down Payment", "01 January 2024"), transaction(38.76, "Accrual", "01 January 2024"),
reversedTransaction(370.0, "Repayment", "01 January 2024"));
@@ -100,6 +100,7 @@ import org.apache.poi.ss.usermodel.Workbook;
@SuppressWarnings({ "rawtypes", "unchecked" })
public class LoanTransactionHelper extends IntegrationTest {
public static final String DATE_FORMAT = "d MMMM yyyy";
public static final String DATE_TIME_FORMAT = "dd MMMM yyyy HH:mm";
private static final String LOAN_PRODUCTS_URL = "/fineract-provider/api/v1/loanproducts";
private static final String CREATE_LOAN_PRODUCT_URL = "/fineract-provider/api/v1/loanproducts?" + Utils.TENANT_IDENTIFIER;
@@ -871,9 +872,10 @@ public class LoanTransactionHelper extends IntegrationTest {
}
public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final Long loanId, final String command, final String date,
final Double amountToBePaid) {
final Double amount) {
log.info("Make {} with amount {} in {} for Loan {}", command, amount, date, loanId);
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, new PostLoansLoanIdTransactionsRequest()
.transactionAmount(amountToBePaid).transactionDate(date).dateFormat("dd MMMM yyyy").locale("en"), command));
.transactionAmount(amount).transactionDate(date).dateFormat("dd MMMM yyyy").locale("en"), command));
}
// TODO: Rewrite to use fineract-client instead!
@@ -1210,6 +1212,13 @@ public class LoanTransactionHelper extends IntegrationTest {
return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, transactionId, request, "undo"));
}
public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Long loanId, final Long transactionId, String date) {
return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, transactionId,
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATE_FORMAT).transactionDate(date).transactionAmount(0.0)
.locale("en"),
"undo"));
}
public HashMap makeRepaymentWithPDC(final String date, final Float amountToBePaid, final Integer loanID, final Long paymentType) {
return (HashMap) performLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID),
getRepaymentWithPDCBodyAsJSON(date, amountToBePaid, paymentType), "");