FINERACT-2204: Fix accrual activity reversal logic: prevent duplicate reverse-replay, copy external ID correctly
This commit is contained in:
committed by
Adam Saghy
parent
03bbad21e8
commit
d32ddc91df
+3
-1
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
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;
|
||||
@@ -198,7 +199,8 @@ public class EventCheckHelper {
|
||||
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();
|
||||
|
||||
+70
@@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
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;
|
||||
@@ -49,6 +50,7 @@ import java.util.List;
|
||||
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;
|
||||
@@ -124,6 +126,7 @@ import org.apache.fineract.test.messaging.event.loan.LoanRescheduledDueAdjustSch
|
||||
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;
|
||||
@@ -2384,6 +2387,73 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
.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);
|
||||
|
||||
@@ -6036,4 +6036,4 @@ Feature: LoanAccrualActivity
|
||||
| 29 June 2024 | Accrual | 0.07 | 0.0 | 0.07 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 30 June 2024 | Accrual | 0.06 | 0.0 | 0.06 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 01 July 2024 | Repayment | 340.17 | 336.11 | 1.96 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 01 July 2024 | Accrual Activity | 1.96 | 0.0 | 1.96 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 01 July 2024 | Accrual Activity | 1.96 | 0.0 | 1.96 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
|
||||
+8
-1
@@ -207,14 +207,21 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||
|
||||
newLoanTransaction.updateExternalId(loanTransaction.getExternalId());
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
|
||||
|
||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
|
||||
loan.addLoanTransaction(newLoanTransaction);
|
||||
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
data.setNewTransactionDetail(newLoanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
} else {
|
||||
reverseAccrualActivityTransaction(loanTransaction);
|
||||
}
|
||||
reverseAccrualActivityTransaction(loanTransaction);
|
||||
return newLoanTransaction;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user