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.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -198,7 +199,8 @@ public class EventCheckHelper {
|
|||||||
Long clientIdExpected = body.getClientId();
|
Long clientIdExpected = body.getClientId();
|
||||||
BigDecimal principalDisbursedActual = loanAccountDataV1.getSummary().getPrincipalDisbursed();
|
BigDecimal principalDisbursedActual = loanAccountDataV1.getSummary().getPrincipalDisbursed();
|
||||||
Double principalDisbursedExpectedDouble = body.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 actualDisbursementDateActual = loanAccountDataV1.getTimeline().getActualDisbursementDate();
|
||||||
String actualDisbursementDateExpected = FORMATTER_EVENTS.format(body.getTimeline().getActualDisbursementDate());
|
String actualDisbursementDateExpected = FORMATTER_EVENTS.format(body.getTimeline().getActualDisbursementDate());
|
||||||
String currencyCodeActual = loanAccountDataV1.getSummary().getCurrency().getCode();
|
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.awaitility.Awaitility.await;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
@@ -49,6 +50,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.LoanStatusChangedEvent;
|
||||||
import org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
|
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.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.LoanChargeAdjustmentPostBusinessEvent;
|
||||||
import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffEvent;
|
import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffEvent;
|
||||||
import org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffUndoEvent;
|
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()));
|
.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}")
|
@Then("LoanTransactionAccrualActivityPostBusinessEvent is raised on {string}")
|
||||||
public void checkLoanTransactionAccrualActivityPostBusinessEvent(String date) throws IOException {
|
public void checkLoanTransactionAccrualActivityPostBusinessEvent(String date) throws IOException {
|
||||||
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
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 |
|
| 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 |
|
| 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 | 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.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||||
|
|
||||||
|
newLoanTransaction.updateExternalId(loanTransaction.getExternalId());
|
||||||
|
loanTransaction.reverse();
|
||||||
|
loanTransaction.updateExternalId(null);
|
||||||
|
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
|
||||||
|
|
||||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
|
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
|
||||||
loan.addLoanTransaction(newLoanTransaction);
|
loan.addLoanTransaction(newLoanTransaction);
|
||||||
|
|
||||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||||
data.setNewTransactionDetail(newLoanTransaction);
|
data.setNewTransactionDetail(newLoanTransaction);
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||||
|
} else {
|
||||||
|
reverseAccrualActivityTransaction(loanTransaction);
|
||||||
}
|
}
|
||||||
reverseAccrualActivityTransaction(loanTransaction);
|
|
||||||
return newLoanTransaction;
|
return newLoanTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user