Skip to content

Commit

Permalink
FINERACT-2181: Fix accrual activity reversal logic: prevent duplicate…
Browse files Browse the repository at this point in the history
… reverse-replay, copy external ID correctly
  • Loading branch information
oleksii-novikov-onix committed Mar 7, 2025
1 parent 8223c72 commit 60d7e38
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.format.DateTimeFormatter;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -198,7 +199,8 @@ private void loanAccountDataV1Check(Class<? extends AbstractLoanEvent> eventClaz
Long clientIdExpected = body.getClientId();
BigDecimal principalDisbursedActual = loanAccountDataV1.getSummary().getPrincipalDisbursed();
Double principalDisbursedExpectedDouble = body.getSummary().getPrincipalDisbursed();
BigDecimal principalDisbursedExpected = new BigDecimal(principalDisbursedExpectedDouble, MathContext.DECIMAL64);
BigDecimal principalDisbursedExpected = new BigDecimal(principalDisbursedExpectedDouble, MathContext.DECIMAL64)
.setScale(8, RoundingMode.HALF_DOWN);
String actualDisbursementDateActual = loanAccountDataV1.getTimeline().getActualDisbursementDate();
String actualDisbursementDateExpected = FORMATTER_EVENTS.format(body.getTimeline().getActualDisbursementDate());
String currencyCodeActual = loanAccountDataV1.getSummary().getCurrency().getCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.google.gson.Gson;
Expand All @@ -49,6 +50,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -124,6 +126,7 @@
import org.apache.fineract.test.messaging.event.loan.LoanStatusChangedEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanAdjustTransactionBusinessEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeAdjustmentPostBusinessEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffEvent;
import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffUndoEvent;
Expand Down Expand Up @@ -2384,6 +2387,73 @@ public void checkLoanAccrualTransactionNotCreatedBusinessEvent(String date) thro
.noneMatch(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue()));
}

@Then("LoanAdjustTransactionBusinessEvent is raised for the origin of Accrual Activity on {string} but not raised for the replayed one")
public void checkLoanAdjustTransactionBusinessEvent(String date) throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();

Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);

List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();

GetLoansLoanIdTransactions loadTransaction = transactions.stream()
.filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("No Accrual Activity transaction found on %s", date)));
Long replayedTransactionId = loadTransaction.getId();

Set<GetLoansLoanIdLoanTransactionRelation> transactionRelations = loadTransaction.getTransactionRelations();
Long originalTransactionId = transactionRelations.stream().map(GetLoansLoanIdLoanTransactionRelation::getToLoanTransaction)
.filter(Objects::nonNull).findFirst().get();

eventAssertion.assertEventRaised(LoanAdjustTransactionBusinessEvent.class, originalTransactionId);
eventAssertion.assertEventNotRaised(LoanAdjustTransactionBusinessEvent.class, replayedTransactionId);
}

@Then("LoanAdjustTransactionBusinessEvent is not raised on {string}")
public void checkLoanAdjustTransactionBusinessEventNotCreated(String date) throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();

Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);

List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();

assertThat(transactions).as("Unexpected Accrual Adjustment transaction found on %s", date)
.noneMatch(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Adjustment".equals(t.getType().getValue()));
}

@Then("External ID for the replayed Accrual Activity on {string} is present but is null for the original transaction")
public void checkExternalIdForReplayedAccrualActivity(String date) throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();

Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);

List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();

GetLoansLoanIdTransactions loadTransaction = transactions.stream()
.filter(t -> date.equals(FORMATTER.format(t.getDate())) && "Accrual Activity".equals(t.getType().getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException(String.format("No Accrual Activity transaction found on %s", date)));
Long replayedTransactionId = loadTransaction.getId();

Set<GetLoansLoanIdLoanTransactionRelation> transactionRelations = loadTransaction.getTransactionRelations();
Long originalTransactionId = transactionRelations.stream().map(GetLoansLoanIdLoanTransactionRelation::getToLoanTransaction)
.filter(Objects::nonNull).findFirst().get();

Response<GetLoansLoanIdTransactionsTransactionIdResponse> replayedTransaction = loanTransactionsApi
.retrieveTransaction(loanId, replayedTransactionId, "").execute();
assertNotNull(String.format("Replayed transaction external id is null %n%s", replayedTransaction.body()),
replayedTransaction.body().getExternalId());

Response<GetLoansLoanIdTransactionsTransactionIdResponse> originalTransaction = loanTransactionsApi
.retrieveTransaction(loanId, originalTransactionId, "").execute();
assertNull(String.format("Original transaction external id is not null %n%s", originalTransaction.body()),
originalTransaction.body().getExternalId());
}

@Then("LoanTransactionAccrualActivityPostBusinessEvent is raised on {string}")
public void checkLoanTransactionAccrualActivityPostBusinessEvent(String date) throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
Expand Down
Loading

0 comments on commit 60d7e38

Please sign in to comment.