Skip to content

Commit

Permalink
FINERACT-2081: Fix Interest Payment Waiver reverse-replay transaction…
Browse files Browse the repository at this point in the history
… on transaction creation.
  • Loading branch information
somasorosdpc committed Jan 28, 2025
1 parent af37e07 commit ea07023
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1142,14 +1142,21 @@ private ChangedTransactionDetail reprocessChangedLoanTransactions(Loan loan,
ScheduleGeneratorDTO scheduleGeneratorDTO) {
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);

} else if (loan.getLoanProductRelatedDetail() != null
&& loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE)
&& loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
return loan.reprocessTransactions();

ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();

if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
}

return changedTransactionDetail;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,11 @@ protected Installment fullyRepaidInstallment(double principalAmount, double inte
return new Installment(principalAmount, interestAmount, null, null, 0.0, true, dueDate, null, null);
}

protected Installment unpaidInstallment(double principalAmount, double interestAmount, String dueDate) {
Double amount = principalAmount + interestAmount;
return new Installment(principalAmount, interestAmount, null, null, amount, false, dueDate, null, null);
}

protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double totalOutstandingAmount,
Boolean completed, String dueDate) {
return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate, null, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,33 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.client.models.GlobalConfigurationPropertyData;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.ExternalEventConfigurationHelper;
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper;
import org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

Expand All @@ -79,6 +84,7 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
private static ResponseSpecification responseSpec;
private static RequestSpecification requestSpec;
Long chargeId = createCharge(111.0, "USD").getResourceId();
private final ExternalEventHelper externalEventHelper = new ExternalEventHelper();

@BeforeAll
public static void beforeAll() {
Expand Down Expand Up @@ -165,8 +171,8 @@ public void testExternalBusinessEventLoanBalanceChangedBusinessEventOnMultiDisbu
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent01() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive().currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 9.99,
4, null);
Expand Down Expand Up @@ -206,8 +212,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent01() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent02() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive().currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 9.99,
4, null);
Expand Down Expand Up @@ -273,8 +279,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent02() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent03() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive().currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 9.99,
4, null);
Expand Down Expand Up @@ -348,8 +354,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent03() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent04() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(false).currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 9.99,
Expand Down Expand Up @@ -390,8 +396,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent04() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent05() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(false).currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 9.99,
Expand Down Expand Up @@ -458,8 +464,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent05() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent06() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(false).currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 9.99,
Expand Down Expand Up @@ -533,8 +539,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent06() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent07() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(false).currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 0.0, 4,
Expand Down Expand Up @@ -574,8 +580,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent07() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent08() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(false).currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 0.0, 4,
Expand Down Expand Up @@ -641,8 +647,8 @@ public void verifyLoanChargeAdjustmentPostBusinessEvent08() {
@Test
public void verifyLoanChargeAdjustmentPostBusinessEvent09() {
runAt("1 January 2021", () -> {
enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanChargeAdjustmentPostBusinessEvent");
externalEventHelper.enableBusinessEvent("LoanTransactionMakeRepaymentPostBusinessEvent");
PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(false).currencyCode("USD"));
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 0.0, 4,
Expand Down Expand Up @@ -769,33 +775,105 @@ public void testExternalBusinessEventLoanRescheduledDueAdjustScheduleBusinessEve
});
}

private static void enableLoanBalanceChangedBusinessEvent() {
enableBusinessEvent("LoanBalanceChangedBusinessEvent");
}
@Nested
class ExternalIdGenerationTest {

private static void configureBusinessEvent(String eventName, boolean enabled) {
String value = enabled ? "true" : "false";
final Map<String, Boolean> updatedConfigurations = ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
responseSpec, "{\"externalEventConfigurations\":{\"" + eventName + "\":" + value + "}}\n");
Assertions.assertEquals(1, updatedConfigurations.size());
Assertions.assertTrue(updatedConfigurations.containsKey(eventName));
Assertions.assertEquals(enabled, updatedConfigurations.get(eventName));
}
Boolean actualConfiguration = null;

@BeforeEach
void setUpEnableExternalIdGenerationIfActuallyDisabled() {
if (actualConfiguration == null) {
GlobalConfigurationPropertyData globalConfigurationByName = globalConfigurationHelper
.getGlobalConfigurationByName(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID);
if (globalConfigurationByName != null) {
actualConfiguration = globalConfigurationByName.getEnabled();
Assertions.assertNotNull(actualConfiguration);
}
}
if (!actualConfiguration) {
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, true);
}
}

private static void enableBusinessEvent(String eventName) {
configureBusinessEvent(eventName, true);
@AfterEach
void tearDownDisableExternalIdGenerationIfPreviouslyDisabled() {
if (!actualConfiguration) {
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, false);
}
}

@Test
public void testInterestPaymentWaiverNotReverseReplayOnCreationAndHasGeneratedExternalId() {
externalEventHelper.enableBusinessEvent("LoanAdjustTransactionBusinessEvent");
AtomicReference<Long> loanIdRef = new AtomicReference<>();
runAt("15 January 2025", () -> {
PostLoanProductsResponse loanProductResponse = loanProductHelper
.createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(true));

Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductResponse.getResourceId(), "15 January 2025",
430.0, 9.9, 4, null);
loanIdRef.set(loanId);

loanTransactionHelper.disburseLoan(loanId, new PostLoansLoanIdRequest().actualDisbursementDate("15 January 2025")
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(430.0)).locale("en"));

verifyTransactions(loanId, transaction(430.0, "Disbursement", "15 January 2025") //
);

verifyRepaymentSchedule(loanId, //
installment(430.0, null, "15 January 2025"), //
unpaidInstallment(106.18, 3.55, "15 February 2025"), //
unpaidInstallment(107.06, 2.67, "15 March 2025"), //
unpaidInstallment(107.94, 1.79, "15 April 2025"), //
unpaidInstallment(108.82, 0.9, "15 May 2025") //
);
});
runAt("16 January 2025", () -> {
Long loanId = loanIdRef.get();
executeInlineCOB(loanId);
verifyTransactions(loanId, transaction(430.0, "Disbursement", "15 January 2025") //
);
});
runAt("17 January 2025", () -> {
Long loanId = loanIdRef.get();
executeInlineCOB(loanId);
verifyTransactions(loanId, transaction(430.0, "Disbursement", "15 January 2025"), //
transaction(0.11, "Accrual", "16 January 2025"));
deleteAllExternalEvents();
PostLoansLoanIdTransactionsResponse interestPaymentWaiverResponse = loanTransactionHelper.makeLoanRepayment(loanId,
"InterestPaymentWaiver", "17 January 2025", 10.0);

List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
List<ExternalEventDTO> adjustments = allExternalEvents.stream()
.filter(e -> "LoanAdjustTransactionBusinessEvent".equals(e.getType())).toList();
Assertions.assertEquals(0, adjustments.size());
Assertions.assertNotNull(interestPaymentWaiverResponse);
Assertions.assertNotNull(interestPaymentWaiverResponse.getResourceExternalId());

verifyTransactions(loanId, transaction(430.0, "Disbursement", "15 January 2025"), //
transaction(10.0, "Interest Payment Waiver", "17 January 2025"), //
transaction(0.11, "Accrual", "16 January 2025"));
verifyRepaymentSchedule(loanId, //
installment(430.0, null, "15 January 2025"), //
installment(106.26, 3.47, 99.73, false, "15 February 2025"), //
unpaidInstallment(107.06, 2.67, "15 March 2025"), //
unpaidInstallment(107.94, 1.79, "15 April 2025"), //
unpaidInstallment(108.74, 0.9, "15 May 2025") //
);
});
}
}

private static void disableBusinessEvent(String eventName) {
configureBusinessEvent(eventName, false);
private void enableLoanBalanceChangedBusinessEvent() {
externalEventHelper.enableBusinessEvent("LoanBalanceChangedBusinessEvent");
}

private static void enableLoanRescheduledDueAdjustScheduleBusinessEvent() {
enableBusinessEvent("LoanRescheduledDueAdjustScheduleBusinessEvent");
private void enableLoanRescheduledDueAdjustScheduleBusinessEvent() {
externalEventHelper.enableBusinessEvent("LoanRescheduledDueAdjustScheduleBusinessEvent");
}

private static void disableLoanBalanceChangedBusinessEvent() {
disableBusinessEvent("LoanBalanceChangedBusinessEvent");
private void disableLoanBalanceChangedBusinessEvent() {
externalEventHelper.disableBusinessEvent("LoanBalanceChangedBusinessEvent");
}

private void deleteAllExternalEvents() {
Expand Down Expand Up @@ -871,8 +949,8 @@ private void logBusinessEvents(List<ExternalEventDTO> allExternalEvents) {
});
}

private static void enableLoanInterestRefundPstBusinessEvent(boolean enabled) {
configureBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", enabled);
private void enableLoanInterestRefundPstBusinessEvent(boolean enabled) {
externalEventHelper.configureBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", enabled);
}

public void verifyBusinessEvents(BusinessEvent... businessEvents) {
Expand Down
Loading

0 comments on commit ea07023

Please sign in to comment.