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:
+10
-3
@@ -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);
|
||||
|
||||
+28
@@ -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);
|
||||
|
||||
+78
@@ -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<>();
|
||||
|
||||
+323
-20
@@ -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"));
|
||||
|
||||
+11
-2
@@ -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), "");
|
||||
|
||||
Reference in New Issue
Block a user