FINERACT-1981: Reverse Interest Refund transaction not allowed directly

This commit is contained in:
Jose Alberto Hernandez
2024-11-27 17:43:08 +01:00
committed by Adam Saghy
parent 4956bdd574
commit 1ef9c0f4bb
2 changed files with 73 additions and 3 deletions
@@ -1412,19 +1412,24 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
new LoanAdjustTransactionBusinessEvent(new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust)));
if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) {
throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed",
"Loan transaction:" + transactionId + " update not allowed as it involves in account transfer", transactionId);
"Loan transaction: " + transactionId + " update not allowed as it involves in account transfer", transactionId);
}
if (loan.isClosedWrittenOff()) {
throw new PlatformServiceUnavailableException("error.msg.loan.written.off.update.not.allowed",
"Loan transaction:" + transactionId + " update not allowed as loan status is written off", transactionId);
"Loan transaction: " + transactionId + " update not allowed as loan status is written off", transactionId);
}
if (transactionToAdjust.hasChargebackLoanTransactionRelations()) {
throw new PlatformServiceUnavailableException("error.msg.loan.transaction.update.not.allowed",
"Loan transaction:" + transactionId + " update not allowed as loan transaction is linked to other transactions",
"Loan transaction: " + transactionId + " update not allowed as loan transaction is linked to other transactions",
transactionId);
}
if (transactionToAdjust.isInterestRefund()) {
throw new PlatformServiceUnavailableException("error.msg.loan.transaction.update.not.allowed",
"Interest refund transaction: " + transactionId + " cannot be reversed or adjusted directly", transactionId);
}
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
@@ -19,6 +19,9 @@
package org.apache.fineract.integrationtests;
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.REPLAYED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
@@ -29,13 +32,17 @@ import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.Utils;
@@ -1193,6 +1200,64 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
});
}
// UC19: Interest Refund reverse transaction only when the related transactions, Merchant Issued Refund or Payout
// Refund are reversed
// 1. Create a Loan Product that supports Interest Refund Types
// 2. Submit, Approve and Disburse the loan
// 3. Apply a Merchant Issued Refund Transaction
// 4. Try to reverse the Interest Refund Transaction expecting to have an Exception
// 5. Reverse the Merchant Issued Refund transaction and review the Interest Refund Transction is reversed too
@Test
public void verifyUC19() {
AtomicReference<Long> loanIdRef = new AtomicReference<>();
runAt("1 January 2021", () -> {
PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
.daysInYearType(DaysInYearType.ACTUAL) //
.multiDisburseLoan(true)//
.disallowExpectedDisbursements(true)//
.maxTrancheCount(2)//
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.PAYOUT_REFUND) //
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) //
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
);
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.9,
12, null);
Assertions.assertNotNull(loanId);
loanIdRef.set(loanId);
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
});
runAt("22 January 2021", () -> {
Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("MerchantIssuedRefund", "22 January 2021", 1000F, loanId.intValue());
logLoanTransactions(loanId);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
Optional<GetLoansLoanIdTransactions> optInterestRefundTransaction = loanDetails.getTransactions().stream()
.filter(item -> Objects.equals(item.getType().getValue(), "Interest Refund")).findFirst();
final Long interestRefundTransactionId = optInterestRefundTransaction.get().getId();
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper.reverseLoanTransaction(loanId, interestRefundTransactionId,
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN)
.transactionDate("22 January 2021").transactionAmount(0.0).locale("en")));
assertEquals(503, exception.getResponse().code());
assertTrue(exception.getMessage().contains("error.msg.loan.transaction.update.not.allowed"));
Optional<GetLoansLoanIdTransactions> optMerchantIssuedTransaction = loanDetails.getTransactions().stream()
.filter(item -> Objects.equals(item.getType().getValue(), "Merchant Issued Refund")).findFirst();
final Long merchantIssuedTransactionId = optMerchantIssuedTransaction.get().getId();
loanTransactionHelper.reverseLoanTransaction(loanId, merchantIssuedTransactionId,
new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("22 January 2021")
.transactionAmount(0.0).locale("en"));
loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
optInterestRefundTransaction = loanDetails.getTransactions().stream()
.filter(item -> Objects.equals(item.getType().getValue(), "Interest Refund")).findFirst();
assertEquals(Boolean.TRUE, optInterestRefundTransaction.get().getManuallyReversed());
});
}
private void logInstallmentsOfLoanDetails(GetLoansLoanIdResponse loanDetails) {
log.info("index, dueDate, principal, fee, penalty, interest");
if (loanDetails != null && loanDetails.getRepaymentSchedule() != null && loanDetails.getRepaymentSchedule().getPeriods() != null) {