FINERACT-2327: when Interest Refund was created manually, we should only return the newly created Interest Refund transaction identifiers in the result.
This commit is contained in:
+1
-2
@@ -309,8 +309,7 @@ public class EventCheckHelper {
|
||||
Response<PostLoansLoanIdTransactionsResponse> transactionResponse, TransactionType transactionType, String externalOwnerId)
|
||||
throws IOException {
|
||||
Long loanId = transactionResponse.body().getLoanId();
|
||||
Long transactionId = transactionType.equals(TransactionType.INTEREST_REFUND) ? transactionResponse.body().getSubResourceId()
|
||||
: transactionResponse.body().getResourceId();
|
||||
Long transactionId = transactionResponse.body().getResourceId();
|
||||
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
|
||||
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
|
||||
GetLoansLoanIdTransactions transactionFound = transactions//
|
||||
|
||||
+2
-4
@@ -3034,10 +3034,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withLoanId(loan.getId()) //
|
||||
.withEntityId(targetTransaction.getId()) //
|
||||
.withEntityExternalId(targetTransaction.getExternalId()) //
|
||||
.withSubEntityId(interestRefundTxn.getId()) //
|
||||
.withSubEntityExternalId(interestRefundTxn.getExternalId()) //
|
||||
.withEntityId(interestRefundTxn.getId()) //
|
||||
.withEntityExternalId(interestRefundTxn.getExternalId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()) //
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.builder.ResponseSpecBuilder;
|
||||
import io.restassured.http.ContentType;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import io.restassured.specification.ResponseSpecification;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
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.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.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests validate that manual Interest Refund transactions return the correct response structure: - entityId should
|
||||
* contain the Interest Refund transaction ID - entityExternalId should contain the Interest Refund external ID -
|
||||
* subEntityId should be null/not set - subEntityExternalId should be null/not set
|
||||
*/
|
||||
@Slf4j
|
||||
public class LoanManualInterestRefundResponseStructureTest extends BaseLoanIntegrationTest {
|
||||
|
||||
private ResponseSpecification responseSpec;
|
||||
private RequestSpecification requestSpec;
|
||||
private LoanTransactionHelper loanTransactionHelper;
|
||||
private PostClientsResponse client;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
Utils.initializeRESTAssured();
|
||||
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||
this.client = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualInterestRefundResponseStructureWithoutExternalIds() {
|
||||
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
AtomicReference<Long> targetTransactionIdRef = new AtomicReference<>();
|
||||
|
||||
runAt("01 January 2024", () -> {
|
||||
// Create loan product that supports manual interest refund
|
||||
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.ACTUAL)
|
||||
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND)
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY));
|
||||
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "01 January 2024", 1000.0, 9.9,
|
||||
12, null);
|
||||
assertNotNull(loanId);
|
||||
loanIdRef.set(loanId);
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1000), "01 January 2024");
|
||||
});
|
||||
|
||||
runAt("15 January 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
|
||||
// Make a merchant issued refund to have a target transaction that supports manual interest refund
|
||||
PostLoansLoanIdTransactionsResponse refundResponse = makeLoanMerchantIssuedRefund(loanId, "15 January 2024", 100.0);
|
||||
assertNotNull(refundResponse);
|
||||
assertNotNull(refundResponse.getResourceId());
|
||||
targetTransactionIdRef.set(refundResponse.getResourceId());
|
||||
|
||||
// Create manual interest refund via API
|
||||
PostLoansLoanIdTransactionsResponse interestRefundResponse = createManualInterestRefund(loanId, refundResponse.getResourceId(),
|
||||
"15 January 2024", 5.0, null);
|
||||
|
||||
assertNotNull(interestRefundResponse, "Interest refund response should not be null");
|
||||
assertNotNull(interestRefundResponse.getResourceId(), "Interest refund resource ID should not be null");
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
GetLoansLoanIdTransactions interestRefundTransaction = findTransactionByType(loanDetails, "Interest Refund");
|
||||
assertNotNull(interestRefundTransaction, "Interest Refund transaction should exist");
|
||||
|
||||
assertEquals(interestRefundTransaction.getId(), interestRefundResponse.getResourceId(),
|
||||
"Response entityId should be the Interest Refund transaction ID");
|
||||
|
||||
// entityExternalId should be null (since no external ID was provided)
|
||||
assertNull(interestRefundResponse.getResourceExternalId(), "entityExternalId should be null when no external ID provided");
|
||||
|
||||
// subEntityId should be null (not the target transaction ID)
|
||||
assertNull(interestRefundResponse.getSubResourceId(), "subEntityId should be null");
|
||||
|
||||
// subEntityExternalId should be null
|
||||
assertNull(interestRefundResponse.getSubResourceExternalId(), "subEntityExternalId should be null");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualInterestRefundResponseStructureWithExternalIds() {
|
||||
AtomicReference<String> loanExternalIdRef = new AtomicReference<>();
|
||||
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
AtomicReference<String> targetTransactionExternalIdRef = new AtomicReference<>();
|
||||
|
||||
String loanExternalId = UUID.randomUUID().toString();
|
||||
loanExternalIdRef.set(loanExternalId);
|
||||
|
||||
runAt("01 February 2024", () -> {
|
||||
// Create loan product that supports manual interest refund
|
||||
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.ACTUAL)
|
||||
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND)
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY));
|
||||
|
||||
Long loanId = applyAndApproveProgressiveLoanWithExternalId(client.getClientId(), loanProduct.getResourceId(), loanExternalId,
|
||||
"01 February 2024", 1000.0, 9.9, 12, null);
|
||||
assertNotNull(loanId);
|
||||
loanIdRef.set(loanId);
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1000), "01 February 2024");
|
||||
});
|
||||
|
||||
runAt("15 February 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
String repaymentExternalId = UUID.randomUUID().toString();
|
||||
targetTransactionExternalIdRef.set(repaymentExternalId);
|
||||
|
||||
// Make a merchant issued refund with external ID (without automatic interest refund)
|
||||
PostLoansLoanIdTransactionsResponse refundResponse = makeLoanMerchantIssuedRefundWithExternalId(loanId, repaymentExternalId,
|
||||
"15 February 2024", 100.0);
|
||||
assertNotNull(refundResponse);
|
||||
assertNotNull(refundResponse.getResourceId());
|
||||
|
||||
// Create manual interest refund with external ID
|
||||
String interestRefundExternalId = UUID.randomUUID().toString();
|
||||
PostLoansLoanIdTransactionsResponse interestRefundResponse = createManualInterestRefund(loanId, refundResponse.getResourceId(),
|
||||
"15 February 2024", 5.0, interestRefundExternalId);
|
||||
|
||||
assertNotNull(interestRefundResponse, "Interest refund response should not be null");
|
||||
assertNotNull(interestRefundResponse.getResourceId(), "Interest refund resource ID should not be null");
|
||||
|
||||
// Get the actual interest refund transaction to verify
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
GetLoansLoanIdTransactions interestRefundTransaction = findTransactionByType(loanDetails, "Interest Refund");
|
||||
assertNotNull(interestRefundTransaction, "Interest Refund transaction should exist");
|
||||
|
||||
assertEquals(interestRefundTransaction.getId(), interestRefundResponse.getResourceId(),
|
||||
"Response entityId should be the Interest Refund transaction ID");
|
||||
|
||||
assertEquals(interestRefundExternalId, interestRefundResponse.getResourceExternalId(),
|
||||
"entityExternalId should be the Interest Refund external ID");
|
||||
|
||||
assertNull(interestRefundResponse.getSubResourceId(), "subEntityId should be null");
|
||||
|
||||
assertNull(interestRefundResponse.getSubResourceExternalId(), "subEntityExternalId should be null");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create manual interest refund transaction
|
||||
*/
|
||||
private PostLoansLoanIdTransactionsResponse createManualInterestRefund(Long loanId, Long targetTransactionId, String transactionDate,
|
||||
Double amount, String externalId) {
|
||||
|
||||
PostLoansLoanIdTransactionsTransactionIdRequest request = new PostLoansLoanIdTransactionsTransactionIdRequest()
|
||||
.transactionAmount(amount).dateFormat("dd MMMM yyyy").locale("en");
|
||||
|
||||
if (externalId != null) {
|
||||
request.externalId(externalId);
|
||||
}
|
||||
|
||||
return loanTransactionHelper.manualInterestRefund(loanId, targetTransactionId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to make loan merchant issued refund (without automatic interest refund)
|
||||
*/
|
||||
private PostLoansLoanIdTransactionsResponse makeLoanMerchantIssuedRefund(Long loanId, String transactionDate, Double amount) {
|
||||
// Create merchant issued refund transaction without automatic interest refund
|
||||
org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest request = new org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest()
|
||||
.transactionDate(transactionDate).transactionAmount(amount).interestRefundCalculation(false).dateFormat("dd MMMM yyyy")
|
||||
.locale("en");
|
||||
return loanTransactionHelper.makeMerchantIssuedRefund(loanId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to make loan merchant issued refund with external ID (without automatic interest refund)
|
||||
*/
|
||||
private PostLoansLoanIdTransactionsResponse makeLoanMerchantIssuedRefundWithExternalId(Long loanId, String externalId,
|
||||
String transactionDate, Double amount) {
|
||||
// Create merchant issued refund transaction with external ID but without automatic interest refund
|
||||
org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest request = new org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest()
|
||||
.transactionDate(transactionDate).transactionAmount(amount).externalId(externalId).interestRefundCalculation(false)
|
||||
.dateFormat("dd MMMM yyyy").locale("en");
|
||||
return loanTransactionHelper.makeMerchantIssuedRefund(loanId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to find transaction by type
|
||||
*/
|
||||
private GetLoansLoanIdTransactions findTransactionByType(GetLoansLoanIdResponse loanDetails, String transactionType) {
|
||||
return loanDetails.getTransactions().stream().filter(t -> transactionType.equals(t.getType().getValue())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to apply and approve progressive loan with external ID
|
||||
*/
|
||||
private Long applyAndApproveProgressiveLoanWithExternalId(Long clientId, Long productId, String loanExternalId, String submittedDate,
|
||||
Double amount, Double interestRate, Integer termFrequency,
|
||||
java.util.function.Consumer<org.apache.fineract.client.models.PostLoansRequest> customizer) {
|
||||
|
||||
org.apache.fineract.client.models.PostLoansRequest request = applyLP2ProgressiveLoanRequest(clientId, productId, submittedDate,
|
||||
amount, interestRate, termFrequency, customizer);
|
||||
request.externalId(loanExternalId);
|
||||
|
||||
org.apache.fineract.client.models.PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(request);
|
||||
Long loanId = loanResponse.getLoanId();
|
||||
|
||||
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(amount, submittedDate));
|
||||
return loanId;
|
||||
}
|
||||
}
|
||||
+6
@@ -1181,6 +1181,12 @@ public class LoanTransactionHelper {
|
||||
FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "chargeRefund"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse manualInterestRefund(final Long loanId, final Long targetTransactionId,
|
||||
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
|
||||
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction(loanId, targetTransactionId,
|
||||
request, "interest-refund"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse makeGoodwillCredit(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
|
||||
return Calls
|
||||
.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId, request, "goodwillCredit"));
|
||||
|
||||
Reference in New Issue
Block a user