FINERACT-2326: Do not remove external id if transaction got not replayed

This commit is contained in:
Adam Saghy
2025-10-08 21:33:04 +02:00
parent b692248a8a
commit ebae20654a
5 changed files with 123 additions and 10 deletions
@@ -54,6 +54,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.LoanProduct;
import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostClientsResponse;
@@ -69,10 +70,12 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionI
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
import org.apache.fineract.client.models.PutLoansLoanIdRequest;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
@@ -136,6 +139,10 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
commonLoanProductId = createLoanProduct("500", "15", "4", true, "25", true, LoanScheduleType.PROGRESSIVE,
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
// setup COB Business Steps to prevent test failing due other integration test configurations
new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
"CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
"EXTERNAL_ASSET_OWNER_TRANSFER", "CHECK_DUE_INSTALLMENTS", "ACCRUAL_ACTIVITY_POSTING", "LOAN_INTEREST_RECALCULATION");
}
// UC1: Simple payments
@@ -6146,6 +6153,104 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
// UC157: Progressive loan with Accrual Activity reverse-replay
@Test
public void uc157() {
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID,
new PutGlobalConfigurationsRequest().enabled(true));
final String operationDate = "13 September 2025";
AtomicLong createdLoanId = new AtomicLong();
GetLoansLoanIdTransactions[] accrualActivityId = new GetLoansLoanIdTransactions[1];
final BigDecimal interestRatePerPeriod = BigDecimal.valueOf(11.32);
final BigDecimal principalAmount = BigDecimal.valueOf(135.94);
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, List.of(//
Pair.of(1, 10), //
Pair.of(11, 30), //
Pair.of(31, 60), //
Pair.of(61, null)//
));
runAt(operationDate, () -> {
final ArrayList<String> interestRefundTypes = new ArrayList<String>();
interestRefundTypes.add("PAYOUT_REFUND");
interestRefundTypes.add("MERCHANT_ISSUED_REFUND");
Long clientId = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestRateFrequencyType(YEARS)//
.daysInMonthType(DaysInMonthType.DAYS_30)//
.daysInYearType(DaysInYearType.DAYS_360)//
.numberOfRepayments(6)//
.repaymentEvery(1)//
.repaymentFrequencyType(2L)//
.chargeOffBehaviour("ZERO_INTEREST")//
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
.repaymentStartDateType(LoanProduct.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())//
.enableDownPayment(false)//
.enableAccrualActivityPosting(true)//
.allowPartialPeriodInterestCalcualtion(null)//
.enableAutoRepaymentForDownPayment(null)//
.isInterestRecalculationEnabled(true)//
.delinquencyBucketId(delinquencyBucketId.longValue())//
.enableInstallmentLevelDelinquency(true)//
.interestRecalculationCompoundingMethod(0)//
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
.installmentAmountInMultiplesOf(null)//
.supportedInterestRefundTypes(interestRefundTypes) //
.rescheduleStrategyMethod(LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.getValue())//
.recalculationRestFrequencyType(2)//
.recalculationRestFrequencyInterval(1)//
.enableAccrualActivityPosting(true);
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate,
principalAmount.doubleValue(), 6).interestCalculationPeriodType(DAYS)//
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
.interestRatePerPeriod(interestRatePerPeriod)//
.repaymentEvery(1)//
.repaymentFrequencyType(MONTHS)//
.loanTermFrequency(6)//
.loanTermFrequencyType(MONTHS);
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
createdLoanId.set(loanResponse.getLoanId());
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(principalAmount)
.dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
.dateFormat(DATETIME_PATTERN).locale("en").transactionAmount(principalAmount));
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
assertTrue(loanDetails.getStatus().getActive());
});
runAt("22 October 2025", () -> {
executeInlineCOB(createdLoanId.get());
verifyTransactions(createdLoanId.get(), //
transaction(135.94, "Disbursement", "13 September 2025", 135.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
transaction(1.28, "Accrual Activity", "13 October 2025", 0.0, 0.0, 1.28, 0.0, 0.0, 0.0, 0.0),
transaction(1.61, "Accrual", "21 October 2025", 0.0, 0.0, 1.61, 0.0, 0.0, 0.0, 0.0));
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
loanDetails.getTransactions().stream().filter(t -> "loanTransactionType.accrualActivity".equals(t.getType().getCode()))
.findFirst().ifPresent(t -> {
accrualActivityId[0] = t;
});
assertNotNull(accrualActivityId[0]);
assertNotNull(accrualActivityId[0].getExternalId());
loanTransactionHelper.makeLoanRepayment(createdLoanId.get(), new PostLoansLoanIdTransactionsRequest() //
.transactionDate("13 September 2025") //
.transactionAmount(135.94) //
.locale("en") //
.dateFormat(DATETIME_PATTERN)); //
GetLoansLoanIdTransactionsTransactionIdResponse loanTransactionDetails = loanTransactionHelper
.getLoanTransactionDetails(createdLoanId.get(), accrualActivityId[0].getId());
assertNotNull(loanTransactionDetails.getExternalId());
assertEquals(LocalDate.of(2025, 10, 22), loanTransactionDetails.getReversedOnDate());
});
}
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");