This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch release/1.13.0
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/release/1.13.0 by this push:
new 7791939860 FINERACT-2326: Do not remove external id if transaction got
not replayed
7791939860 is described below
commit 779193986049d874278ce61010982c13ff6af71a
Author: Adam Saghy <[email protected]>
AuthorDate: Wed Oct 8 16:27:12 2025 +0200
FINERACT-2326: Do not remove external id if transaction got not replayed
---
...dvancedPaymentScheduleTransactionProcessor.java | 4 +-
.../LoanAccrualActivityProcessingServiceImpl.java | 13 ++-
.../ProgressiveLoanInterestRefundServiceImpl.java | 2 +-
.../ReprocessLoanTransactionsServiceImpl.java | 9 +-
...PaymentAllocationLoanRepaymentScheduleTest.java | 105 +++++++++++++++++++++
5 files changed, 123 insertions(+), 10 deletions(-)
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 4afa5e881e..cbbac987c5 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -1214,7 +1214,9 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
protected void createNewTransaction(final LoanTransaction oldTransaction,
final LoanTransaction newTransaction,
final TransactionCtx ctx) {
- oldTransaction.updateExternalId(null);
+ if (newTransaction.isNotReversed()) {
+ oldTransaction.updateExternalId(null);
+ }
oldTransaction.getLoanChargesPaid().clear();
if (newTransaction.getTypeOf().isInterestRefund()) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
index 2d531c9a7c..0d64ed973f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
@@ -125,11 +125,14 @@ public class LoanAccrualActivityProcessingServiceImpl
implements LoanAccrualActi
protected void createNewTransaction(LoanTransaction loanTransaction,
LoanTransaction newLoanTransaction,
ChangedTransactionDetail changedTransactionDetail) {
loanTransaction.reverse();
- loanTransaction.updateExternalId(null);
-
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
- // Adding Replayed relation from newly created transaction to reversed
transaction
- newLoanTransaction.getLoanTransactionRelations().add(
- LoanTransactionRelation.linkToTransaction(newLoanTransaction,
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
+
+ if (newLoanTransaction.isNotReversed()) {
+ loanTransaction.updateExternalId(null);
+
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
+ // Adding Replayed relation from newly created transaction to
reversed transaction
+
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
+ loanTransaction,
LoanTransactionRelationTypeEnum.REPLAYED));
+ }
changedTransactionDetail.addTransactionChange(new
TransactionChangeData(loanTransaction, newLoanTransaction));
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
index b35746d991..ec46968932 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
@@ -92,7 +92,7 @@ public class ProgressiveLoanInterestRefundServiceImpl
implements InterestRefundS
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
relatedRefundTransactionDate, transactionsToReprocess,
loan.getCurrency(), installmentsToReprocess,
loan.getActiveCharges());
final List<LoanTransaction> newTransactions =
reprocessResult.getLeft().getTransactionChanges().stream()
- .map(TransactionChangeData::getNewTransaction).toList();
+
.map(TransactionChangeData::getNewTransaction).toList().stream().filter(LoanTransaction::isNotReversed).toList();
loan.getLoanTransactions().addAll(newTransactions);
ProgressiveLoanInterestScheduleModel modelAfter =
reprocessResult.getRight();
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
index 4b73229378..91b2940ca5 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
@@ -175,7 +175,8 @@ public class ReprocessLoanTransactionsServiceImpl
implements ReprocessLoanTransa
final ChangedTransactionDetail changedTransactionDetail =
loanTransactionProcessingService
.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
loanTransaction, transactionCtx);
final List<LoanTransaction> newTransactions =
changedTransactionDetail.getTransactionChanges().stream()
-
.map(TransactionChangeData::getNewTransaction).peek(transaction ->
transaction.updateLoan(loan)).toList();
+
.map(TransactionChangeData::getNewTransaction).toList().stream().filter(LoanTransaction::isNotReversed)
+ .peek(transaction -> transaction.updateLoan(loan)).toList();
loan.getLoanTransactions().addAll(newTransactions);
loanBalanceService.updateLoanSummaryDerivedFields(loan);
@@ -206,10 +207,12 @@ public class ReprocessLoanTransactionsServiceImpl
implements ReprocessLoanTransa
: new
LoanAccrualAdjustmentTransactionBusinessEvent(newTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
+ if (oldTransaction != null) {
+
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(),
newTransaction);
+ }
}
if (oldTransaction != null) {
-
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(),
newTransaction);
// Create reversal journal entries for old transaction if it
exists (reverse-replay scenario)
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(oldTransaction,
false, false);
}
@@ -226,7 +229,7 @@ public class ReprocessLoanTransactionsServiceImpl
implements ReprocessLoanTransa
change.getNewTransaction().updateLoan(loan);
}
final List<LoanTransaction> newTransactions =
changedTransactionDetail.getTransactionChanges().stream()
- .map(TransactionChangeData::getNewTransaction).toList();
+
.map(TransactionChangeData::getNewTransaction).toList().stream().filter(LoanTransaction::isNotReversed).toList();
loan.getLoanTransactions().addAll(newTransactions);
loanBalanceService.updateLoanSummaryDerivedFields(loan);
loanAccrualActivityProcessingService.recalculateAccrualActivityTransaction(loan,
changedTransactionDetail);
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index 62cdc035c6..540a37267a 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -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
---------------------------------------");