diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 787d9db8771..f179d71c471 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -1636,6 +1636,10 @@ public List getActiveLoanTermVariations() { return this.loanTermVariations.stream().filter(LoanTermVariations::isActive).collect(Collectors.toList()); } + public void setLoanTermVariations(List loanTermVariations) { + this.loanTermVariations = loanTermVariations; + } + public boolean isTopup() { return this.isTopup; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index aa20e1fe405..9b96bccafe6 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -179,9 +179,11 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur } if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { + Loan loan = loanTransaction.getLoan(); // pass through for new transactions if (loanTransaction.getId() == null) { - processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null)); + processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null, + loan.getActiveLoanTermVariations())); loanTransaction.adjustInterestComponent(); } else { /** @@ -192,8 +194,8 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur // Reset derived component of new loan transaction and // re-process transaction - processLatestTransaction(newLoanTransaction, - new TransactionCtx(currency, installments, charges, overpaymentHolder, null)); + processLatestTransaction(newLoanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, + null, loan.getActiveLoanTermVariations())); newLoanTransaction.adjustInterestComponent(); /** * Check if the transaction amounts have changed. If so, reverse the original transaction and update diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/TransactionCtx.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/TransactionCtx.java index bda76073405..5ba265f48f8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/TransactionCtx.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/TransactionCtx.java @@ -26,6 +26,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; @Data @AllArgsConstructor @@ -36,4 +37,5 @@ public class TransactionCtx { private final Set charges; private final MoneyHolder overpaymentHolder; private final ChangedTransactionDetail changedTransactionDetail; + private final List activeLoanTermVariations; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/ApproveLoanRescheduleRequestCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/ApproveLoanRescheduleRequestCommandHandler.java index 20964b6199c..04f0d957147 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/ApproveLoanRescheduleRequestCommandHandler.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/ApproveLoanRescheduleRequestCommandHandler.java @@ -18,26 +18,22 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler; +import lombok.RequiredArgsConstructor; import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@RequiredArgsConstructor @CommandType(entity = "RESCHEDULELOAN", action = "APPROVE") public class ApproveLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler { private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService; - @Autowired - public ApproveLoanRescheduleRequestCommandHandler(LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) { - this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService; - } - @Transactional @Override public CommandProcessingResult processCommand(JsonCommand jsonCommand) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/CreateLoanRescheduleRequestCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/CreateLoanRescheduleRequestCommandHandler.java index e8dbda6b7c3..f2b98050c9e 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/CreateLoanRescheduleRequestCommandHandler.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/CreateLoanRescheduleRequestCommandHandler.java @@ -18,26 +18,22 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler; +import lombok.RequiredArgsConstructor; import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@RequiredArgsConstructor @CommandType(entity = "RESCHEDULELOAN", action = "CREATE") public class CreateLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler { private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService; - @Autowired - public CreateLoanRescheduleRequestCommandHandler(LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) { - this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService; - } - @Transactional @Override public CommandProcessingResult processCommand(JsonCommand jsonCommand) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/RejectLoanRescheduleRequestCommandHandler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/RejectLoanRescheduleRequestCommandHandler.java index a57eed4a420..2004cbb8815 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/RejectLoanRescheduleRequestCommandHandler.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/handler/RejectLoanRescheduleRequestCommandHandler.java @@ -18,26 +18,22 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler; +import lombok.RequiredArgsConstructor; import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@RequiredArgsConstructor @CommandType(entity = "RESCHEDULELOAN", action = "REJECT") public class RejectLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler { private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService; - @Autowired - public RejectLoanRescheduleRequestCommandHandler(LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) { - this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService; - } - @Transactional @Override public CommandProcessingResult processCommand(JsonCommand jsonCommand) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java index 6178c005f5c..576d883fa51 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.springframework.transaction.annotation.Propagation; @@ -33,5 +34,6 @@ public interface InterestRefundService { @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, Long loanId, - LocalDate relatedRefundTransactionDate, List newTransactions, List oldTransactionIds); + LocalDate relatedRefundTransactionDate, List newTransactions, List oldTransactionIds, + List activeLoanTermVariations); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java index e778fdb702b..d725279e19b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java @@ -597,7 +597,7 @@ private void handleChargePaidTransaction(final Loan loan, final LoanCharge charg loanCharges.add(charge); loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargesPayment, new TransactionCtx(loan.getCurrency(), chargePaymentInstallments, loanCharges, - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); loanLifecycleStateMachine.determineAndTransition(loan, chargesPayment.getTransactionDate()); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java index 858a27a41e1..378a48d3754 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java @@ -140,7 +140,7 @@ public void handleRepaymentOrRecoveryOrWaiverTransaction(final Loan loan, final if (processLatest) { loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); if (!loan.isProgressiveSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) { reprocessOnPostConditions = true; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java index a7c83d476a6..e367455e89c 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java @@ -83,7 +83,7 @@ private void handleRefundTransaction(final Loan loan, final LoanTransaction loan loadTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate()); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java index 944773e3f0e..4230db2400c 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/ProgressivePossibleNextRepaymentCalculationServiceImpl.java @@ -57,7 +57,8 @@ public BigDecimal calculateInterestRecalculationFutureOutstandingValue(Loan loan ProgressiveLoanInterestScheduleModel scheduleModel = optionalScheduleModel.get(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), repaymentScheduleInstallments, Set.of(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel, + loan.getActiveLoanTermVariations()); ctx.setChargedOff(loan.isChargedOff()); ctx.setWrittenOff(loan.isClosedWrittenOff()); ctx.setContractTerminated(loan.isContractTermination()); 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 0bb8f42fe62..3ec685deded 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 @@ -229,7 +229,7 @@ public Pair repr ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments, LoanConfigurationDetailsMapper.map(loan), installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc()); ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, - changedTransactionDetail, scheduleModel); + changedTransactionDetail, scheduleModel, loan.getActiveLoanTermVariations()); List changeOperations = createSortedChangeList(loanTermVariations, loanTransactions, charges); @@ -571,7 +571,7 @@ private void handleInterestRefund(final LoanTransaction loanTransaction, final T .filter(LoanTransaction::isNotReversed).filter(tr -> tr.getId() == null).toList()); if (validateInterestRefundTransactionRelation(loanTransaction)) { final Money interestAfterRefund = interestRefundService.totalInterestByTransactions(this, loan.getId(), targetDate, - modifiedTransactions, unmodifiedTransactionIds); + modifiedTransactions, unmodifiedTransactionIds, ctx.getActiveLoanTermVariations()); final Money newAmount = interestBeforeRefund.minus(progCtx.getSumOfInterestRefundAmount()).minus(interestAfterRefund); loanTransaction.updateAmount(newAmount.getAmount()); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java index 041fdde75f1..a0bff2228af 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java @@ -28,6 +28,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; @@ -52,14 +53,16 @@ public class ProgressiveTransactionCtx extends TransactionCtx { public ProgressiveTransactionCtx(MonetaryCurrency currency, List installments, Set charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail, - ProgressiveLoanInterestScheduleModel model) { - this(currency, installments, charges, overpaymentHolder, changedTransactionDetail, model, Money.zero(currency)); + ProgressiveLoanInterestScheduleModel model, List activeLoanTermVariations) { + this(currency, installments, charges, overpaymentHolder, changedTransactionDetail, model, Money.zero(currency), + activeLoanTermVariations); } public ProgressiveTransactionCtx(MonetaryCurrency currency, List installments, Set charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail, - ProgressiveLoanInterestScheduleModel model, Money sumOfInterestRefundAmount) { - super(currency, installments, charges, overpaymentHolder, changedTransactionDetail); + ProgressiveLoanInterestScheduleModel model, Money sumOfInterestRefundAmount, + List activeLoanTermVariations) { + super(currency, installments, charges, overpaymentHolder, changedTransactionDetail, activeLoanTermVariations); this.sumOfInterestRefundAmount = sumOfInterestRefundAmount; this.model = model; } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java index c5366563884..654af32509f 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java @@ -112,7 +112,8 @@ public Optional getSavedModel(Loan loan, L savedModel = extractModel(progressiveLoanModel); if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) { ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), - Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), savedModel.get()); + Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), savedModel.get(), + loan.getActiveLoanTermVariations()); ctx.setChargedOff(loan.isChargedOff()); ctx.setWrittenOff(loan.isClosedWrittenOff()); ctx.setContractTerminated(loan.isContractTermination()); diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java index d6304f72f10..ac2d59b404b 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java @@ -164,8 +164,8 @@ public void chargePaymentTransactionTestWithExactAmount() { when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); - underTest.processLatestTransaction(loanTransaction, - new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null)); + underTest.processLatestTransaction(loanTransaction, new TransactionCtx(currency, List.of(installment), Set.of(charge), + new MoneyHolder(overpaidAmount), null, loan.getActiveLoanTermVariations())); Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney)); Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero)); @@ -208,8 +208,8 @@ public void chargePaymentTransactionTestWithLessTransactionAmount() { when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); - underTest.processLatestTransaction(loanTransaction, - new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null)); + underTest.processLatestTransaction(loanTransaction, new TransactionCtx(currency, List.of(installment), Set.of(charge), + new MoneyHolder(overpaidAmount), null, loan.getActiveLoanTermVariations())); Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney)); Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney), refEq(zero)); @@ -260,8 +260,8 @@ public void chargePaymentTransactionTestWithMoreTransactionAmount() { when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL)); when(loanTransaction.isOn(transactionDate)).thenReturn(true); - underTest.processLatestTransaction(loanTransaction, - new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null)); + underTest.processLatestTransaction(loanTransaction, new TransactionCtx(currency, List.of(installment), Set.of(charge), + new MoneyHolder(overpaidAmount), null, loan.getActiveLoanTermVariations())); Mockito.verify(installment, times(1)).payFeeChargesComponent(transactionDate, chargeAmountMoney); Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero)); @@ -289,7 +289,8 @@ public void testProcessCreditTransactionWithAllocationRulePrincipalPenaltyFeeInt installments.add(installment); // when - TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null); + TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null, + loan.getActiveLoanTermVariations()); underTest.processCreditTransaction(chargebackTransaction, ctx); // verify principal @@ -346,7 +347,8 @@ public void testProcessCreditTransactionWithAllocationRulePenaltyFeePrincipalInt installments.add(installment); // when - TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null); + TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null, + loan.getActiveLoanTermVariations()); underTest.processCreditTransaction(chargebackTransaction, ctx); // verify charges on installment @@ -398,7 +400,8 @@ public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterestWi installments.add(installment2); // when - TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null); + TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null, + loan.getActiveLoanTermVariations()); underTest.processCreditTransaction(chargebackTransaction, ctx); // verify principal @@ -491,7 +494,7 @@ public void testProcessLatestTransaction_PassesThroughHandlingPaymentAllocationF // Set up TransactionCtx with installments and charges TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, Set.of(), overpaymentHolder, changedTransactionDetail, - model, null); + model, loan.getActiveLoanTermVariations()); // Mock additional necessary methods LoanCharge loanCharge = mock(LoanCharge.class); @@ -570,7 +573,7 @@ public void testDisbursementAfterMaturityDateWithEMICalculator() { when(model.getMaturityDate()).thenReturn(LocalDate.of(2023, 12, 31)); TransactionCtx ctx = new ProgressiveTransactionCtx(currency, spyInstallments, Set.of(), new MoneyHolder(Money.zero(currency)), - mock(ChangedTransactionDetail.class), model, Money.zero(currency)); + mock(ChangedTransactionDetail.class), model, Money.zero(currency), loan.getActiveLoanTermVariations()); underTest.processLatestTransaction(disbursementTransaction, ctx); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index f82d12a2aa0..f07b2dafe6f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -189,10 +189,11 @@ private LoanTransaction createInterestRefundLoanTransaction(Loan loan, LoanTrans } Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), refundTransaction.getTransactionDate(), - List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); + List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(), + loan.getActiveLoanTermVariations()); Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), refundTransaction.getTransactionDate(), List.of(refundTransaction), - loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); + loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(), loan.getActiveLoanTermVariations()); BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount(); if (MathUtil.isZero(interestRefundAmount)) { @@ -846,12 +847,13 @@ public Pair makeRefund(final Loan loan, final if (processLatest) { loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), refundTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); loan.getLoanTransactions().add(refundTransaction); if (interestRefundTransaction != null) { loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), - interestRefundTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), - loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + interestRefundTransaction, + new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); loan.addLoanTransaction(interestRefundTransaction); } } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index bff1713d735..f31125f68b5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -431,11 +431,6 @@ public CommandProcessingResult approve(JsonCommand jsonCommand) { } else { loanSchedule.updateLoanSchedule(loan, loanScheduleDTO.getLoanScheduleModel()); } - loanAccrualsProcessingService.reprocessExistingAccruals(loan, true); - loanChargeService.recalculateAllCharges(loan); - reprocessLoanTransactionsService.reprocessTransactions(loan); - - this.loanRepaymentScheduleHistoryRepository.saveAll(loanRepaymentScheduleHistoryList); loan.updateRescheduledByUser(appUser); loan.updateRescheduledOnDate(DateUtils.getBusinessLocalDate()); @@ -443,6 +438,12 @@ public CommandProcessingResult approve(JsonCommand jsonCommand) { // update the status of the request loanRescheduleRequest.approve(appUser, approvedOnDate); + loanAccrualsProcessingService.reprocessExistingAccruals(loan, true); + loanChargeService.recalculateAllCharges(loan); + reprocessLoanTransactionsService.reprocessTransactions(loan); + + this.loanRepaymentScheduleHistoryRepository.saveAll(loanRepaymentScheduleHistoryList); + Optional lastTransactionDateForReprocessing = loanTransactionRepository.findLastTransactionDateForReprocessing(loan); if (lastTransactionDateForReprocessing.isPresent()) { loanLifecycleStateMachine.determineAndTransition(loan, lastTransactionDateForReprocessing.get()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 669206db30a..46e86e88ad0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -2244,9 +2244,11 @@ public LoanTransactionData retrieveManualInterestRefundTemplate(final Long loanI final InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan); final Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(), - List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); + List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(), + loan.getActiveLoanTermVariations()); final Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(), - List.of(targetTxn), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); + List.of(targetTxn), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(), + loan.getActiveLoanTermVariations()); final BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount(); final Collection paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java index e4cfcfa5076..40682138478 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java @@ -211,7 +211,7 @@ private ChangedTransactionDetail processLatestTransactionProgressiveInterestReca ProgressiveLoanInterestScheduleModel model = savedModel.get(); ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), - new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan)); + new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan), loan.getActiveLoanTermVariations()); progressiveContext.getAlreadyProcessedTransactions().addAll(loanTransactionService.retrieveListOfTransactionsForReprocessing(loan)); progressiveContext.setChargedOff(loan.isChargedOff()); progressiveContext.setWrittenOff(loan.isClosedWrittenOff()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 0cfbcc3590a..6a9c0667d3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -964,8 +964,9 @@ private void recalculateLoanWithInterestPaymentWaiverTxn(Loan loan, LoanTransact if (isTransactionChronologicallyLatest && (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled()) && !loan.isForeclosure()) { loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), - newInterestPaymentWaiverTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), - loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + newInterestPaymentWaiverTransaction, + new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); reprocess = false; if (loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -2993,7 +2994,7 @@ public CommandProcessingResult makeManualInterestRefund(final Long loanId, final if (processLatest) { loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), interestRefundTxn, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); loan.addLoanTransaction(interestRefundTxn); } else { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -3053,7 +3054,7 @@ public void handleChargebackTransaction(final Loan loan, LoanTransaction chargeb loan.addLoanTransaction(chargebackTransaction); loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); } loanLifecycleStateMachine.determineAndTransition(loan, chargebackTransaction.getTransactionDate()); } @@ -3253,7 +3254,7 @@ private Optional closeAsWrittenOff(final Loan loan, final JsonC loan.addLoanTransaction(loanTransaction); loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); } loanBalanceService.updateLoanSummaryDerivedFields(loan); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan); @@ -3325,7 +3326,7 @@ private Optional close(final Loan loan, final JsonCommand comma loan.addLoanTransaction(loanTransaction); loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations())); loanBalanceService.updateLoanSummaryDerivedFields(loan); } else if (totalOutstanding.isGreaterThanZero()) { 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 5742a53284a..c87a726740b 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 @@ -35,6 +35,7 @@ import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData; import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; @@ -119,8 +120,10 @@ private boolean isTransactionNeededForInterestRefundCalculations(LoanTransaction @Override @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) public Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, final Long loanId, - LocalDate relatedRefundTransactionDate, List newTransactions, List oldTransactionIds) { + LocalDate relatedRefundTransactionDate, List newTransactions, List oldTransactionIds, + List activeLoanTermVariations) { Loan loan = loanAssembler.assembleFrom(loanId); + loan.setLoanTermVariations(activeLoanTermVariations); if (processor == null) { processor = loanTransactionProcessingService.getTransactionProcessor(loan.getTransactionProcessingStrategyCode()); } 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 e27bc157ee5..07e1b34aef7 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 @@ -100,7 +100,7 @@ public void processLatestTransaction(final LoanTransaction loanTransaction, fina final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), - new ChangedTransactionDetail(), savedModel.get()); + new ChangedTransactionDetail(), savedModel.get(), loan.getActiveLoanTermVariations()); progressiveTransactionCtx.setChargedOff(loan.isChargedOff()); progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff()); progressiveTransactionCtx.setContractTerminated(loan.isContractTermination()); @@ -108,7 +108,7 @@ public void processLatestTransaction(final LoanTransaction loanTransaction, fina } } else { transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()); + new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), loan.getActiveLoanTermVariations()); } final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java index c56458dce06..5e2844bc496 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.integrationtests; +import static org.junit.jupiter.api.Assertions.assertTrue; + import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; @@ -25,11 +27,15 @@ import io.restassured.specification.ResponseSpecification; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicReference; import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest; +import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse; import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PostLoansResponse; +import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest; import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper; @@ -153,6 +159,65 @@ public void testMerchantIssuedRefundDoesNotCreateInterestRefundWithLessThanOrEqu }); } + @Test + public void testMerchantIssuedRefundAndCreditBalanceRefundWithAdjustSchedule() { + final AtomicReference loanIdRef = new AtomicReference<>(); + + runAt("24 September 2025", () -> { + final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + final Long loanId = createLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND", "24 September 2025", 116.89, + 35.99, 3); + loanIdRef.set(loanId); + disburseLoan(loanId, BigDecimal.valueOf(116.89), "24 September 2025"); + }); + + runAt("26 September 2025", () -> { + executeInlineCOB(loanIdRef.get()); + addRepaymentForLoan(loanIdRef.get(), 117.12, "26 September 2025"); + }); + + runAt("06 October 2025", () -> { + executeInlineCOB(loanIdRef.get()); + loanTransactionHelper.makeMerchantIssuedRefund(loanIdRef.get(), new PostLoansLoanIdTransactionsRequest() + .dateFormat(DATETIME_PATTERN).transactionDate("06 October 2025").locale(LOCALE).transactionAmount(8.13)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanIdRef.get()); + // Validate Loan is Overpaid + assertTrue(loanDetails.getStatus().getOverpaid()); + + validateLoanSummaryBalances(loanDetails, 0.00, 117.12, 0.00, 116.89, 8.14); + }); + + runAt("07 October 2025", () -> { + executeInlineCOB(loanIdRef.get()); + loanTransactionHelper.makeCreditBalanceRefund(loanIdRef.get(), new PostLoansLoanIdTransactionsRequest() + .dateFormat(DATETIME_PATTERN).transactionDate("07 October 2025").locale(LOCALE).transactionAmount(8.14)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanIdRef.get()); + // Validate Loan is Closed + assertTrue(loanDetails.getStatus().getClosedObligationsMet()); + validateLoanSummaryBalances(loanDetails, 0.00, 117.12, 0.00, 116.89, null); + + PostCreateRescheduleLoansResponse rescheduleLoansResponse = loanRescheduleRequestHelper// + .createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest()// + .loanId(loanIdRef.get())// + .rescheduleReasonId(1L)// + .rescheduleFromDate("25 September 2025").dateFormat(DATETIME_PATTERN).locale(LOCALE)// + .submittedOnDate("07 October 2025")// + .newInterestRate(BigDecimal.valueOf(25.99)));// + + loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(), // + new PostUpdateRescheduleLoansRequest()// + .approvedOnDate("07 October 2025").locale(LOCALE).dateFormat(DATETIME_PATTERN));// + + loanDetails = loanTransactionHelper.getLoanDetails(loanIdRef.get()); + // Validate Loan is Overpaid + assertTrue(loanDetails.getStatus().getOverpaid()); + + validateLoanSummaryBalances(loanDetails, 0.00, 117.06, 0.00, 116.89, 0.06); + }); + } + private Long createAndDisburseLoanForMerchantIssuedRefundWithInterestRefund(Long clientId) { return createAndDisburseLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND"); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java index 6d2abb75f17..f85a004eacf 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests.common; +import java.time.Duration; import java.util.function.Consumer; import java.util.function.Function; import okhttp3.logging.HttpLoggingInterceptor; @@ -43,7 +44,7 @@ public static FineractClient createNewFineractClient(String username, String pas String url = System.getProperty("fineract.it.url", buildURI()); // insecure(true) should *ONLY* ever be used for https://localhost:8443, NOT in real clients!! FineractClient.Builder builder = FineractClient.builder().insecure(true).baseURL(url).tenant(ConfigProperties.Backend.TENANT) - .basicAuth(username, password).logging(HttpLoggingInterceptor.Level.NONE); + .basicAuth(username, password).logging(HttpLoggingInterceptor.Level.NONE).readTimeout(Duration.ofSeconds(0)); customizer.accept(builder); return builder.build(); }