FINERACT-2326: Do not remove external id if transaction got not replayed
This commit is contained in:
+3
-1
@@ -1214,7 +1214,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
protected void createNewTransaction(final LoanTransaction oldTransaction, final LoanTransaction newTransaction,
|
protected void createNewTransaction(final LoanTransaction oldTransaction, final LoanTransaction newTransaction,
|
||||||
final TransactionCtx ctx) {
|
final TransactionCtx ctx) {
|
||||||
oldTransaction.updateExternalId(null);
|
if (newTransaction.isNotReversed()) {
|
||||||
|
oldTransaction.updateExternalId(null);
|
||||||
|
}
|
||||||
oldTransaction.getLoanChargesPaid().clear();
|
oldTransaction.getLoanChargesPaid().clear();
|
||||||
|
|
||||||
if (newTransaction.getTypeOf().isInterestRefund()) {
|
if (newTransaction.getTypeOf().isInterestRefund()) {
|
||||||
|
|||||||
+8
-5
@@ -125,11 +125,14 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
|||||||
protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
||||||
ChangedTransactionDetail changedTransactionDetail) {
|
ChangedTransactionDetail changedTransactionDetail) {
|
||||||
loanTransaction.reverse();
|
loanTransaction.reverse();
|
||||||
loanTransaction.updateExternalId(null);
|
|
||||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
if (newLoanTransaction.isNotReversed()) {
|
||||||
// Adding Replayed relation from newly created transaction to reversed transaction
|
loanTransaction.updateExternalId(null);
|
||||||
newLoanTransaction.getLoanTransactionRelations().add(
|
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||||
LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
// 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));
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, newLoanTransaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -92,7 +92,7 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
|
|||||||
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess,
|
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess,
|
||||||
loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges());
|
loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges());
|
||||||
final List<LoanTransaction> newTransactions = reprocessResult.getLeft().getTransactionChanges().stream()
|
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);
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
ProgressiveLoanInterestScheduleModel modelAfter = reprocessResult.getRight();
|
ProgressiveLoanInterestScheduleModel modelAfter = reprocessResult.getRight();
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -175,7 +175,8 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService
|
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService
|
||||||
.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, transactionCtx);
|
.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, transactionCtx);
|
||||||
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
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);
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
|
|
||||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
@@ -206,10 +207,12 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
: new LoanAccrualAdjustmentTransactionBusinessEvent(newTransaction);
|
: new LoanAccrualAdjustmentTransactionBusinessEvent(newTransaction);
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
|
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
|
||||||
}
|
}
|
||||||
|
if (oldTransaction != null) {
|
||||||
|
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(), newTransaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldTransaction != null) {
|
if (oldTransaction != null) {
|
||||||
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(), newTransaction);
|
|
||||||
// Create reversal journal entries for old transaction if it exists (reverse-replay scenario)
|
// Create reversal journal entries for old transaction if it exists (reverse-replay scenario)
|
||||||
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(oldTransaction, false, false);
|
loanJournalEntryPoster.postJournalEntriesForLoanTransaction(oldTransaction, false, false);
|
||||||
}
|
}
|
||||||
@@ -226,7 +229,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
change.getNewTransaction().updateLoan(loan);
|
change.getNewTransaction().updateLoan(loan);
|
||||||
}
|
}
|
||||||
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
.map(TransactionChangeData::getNewTransaction).toList();
|
.map(TransactionChangeData::getNewTransaction).toList().stream().filter(LoanTransaction::isNotReversed).toList();
|
||||||
loan.getLoanTransactions().addAll(newTransactions);
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
loanAccrualActivityProcessingService.recalculateAccrualActivityTransaction(loan, changedTransactionDetail);
|
loanAccrualActivityProcessingService.recalculateAccrualActivityTransaction(loan, changedTransactionDetail);
|
||||||
|
|||||||
+105
@@ -54,6 +54,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
|||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
|
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.LoanProduct;
|
||||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
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.PostLoansRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansResponse;
|
import org.apache.fineract.client.models.PostLoansResponse;
|
||||||
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
|
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.models.PutLoansLoanIdRequest;
|
||||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||||
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||||
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
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.ClientHelper;
|
||||||
import org.apache.fineract.integrationtests.common.CommonConstants;
|
import org.apache.fineract.integrationtests.common.CommonConstants;
|
||||||
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
|
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,
|
commonLoanProductId = createLoanProduct("500", "15", "4", true, "25", true, LoanScheduleType.PROGRESSIVE,
|
||||||
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
|
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
|
||||||
client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
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
|
// 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,
|
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||||
|
|||||||
Reference in New Issue
Block a user