This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 6286d1f82 FINERACT-2107: Interest Refund - Transaction Amount
Recalculation
6286d1f82 is described below
commit 6286d1f8211dd9b60a1e372b48f1f060f089d25b
Author: Soma Sörös <[email protected]>
AuthorDate: Thu Nov 7 18:18:11 2024 +0100
FINERACT-2107: Interest Refund - Transaction Amount Recalculation
---
.../test/resources/features/LoanRepayment.feature | 2 +-
.../portfolio/loanaccount/domain/Loan.java | 6 +
.../loanaccount/domain/LoanTransaction.java | 4 +
.../loanaccount/service/InterestRefundService.java | 14 ++-
...dvancedPaymentScheduleTransactionProcessor.java | 28 ++++-
.../impl/ProgressiveTransactionCtx.java | 7 ++
.../portfolio/loanproduct/calc/EMICalculator.java | 2 +
.../loanproduct/calc/ProgressiveEMICalculator.java | 18 +++
...cedPaymentScheduleTransactionProcessorTest.java | 2 +-
.../domain/LoanAccountDomainServiceJpa.java | 24 ++--
.../service/InterestRefundServiceDelegate.java | 2 +
.../ProgressiveLoanInterestRefundServiceImpl.java | 116 +++++++++++--------
.../starter/LoanAccountAutoStarter.java | 7 +-
.../integrationtests/LoanInterestRefundTest.java | 127 ++++++++++++++++++---
14 files changed, 280 insertions(+), 79 deletions(-)
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
index 81fe59167..5e19a7c79 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
@@ -3969,7 +3969,7 @@ Feature: LoanRepayment
| 15 January 2024 | Merchant Issued Refund | 50.0 | 48.9 | 1.1
| 0.0 | 0.0 | 126.1 | false |
| 15 January 2024 | Interest Refund | 0.29 | 0.29 | 0.0
| 0.0 | 0.0 | 125.81 | false |
| 16 January 2024 | Payout Refund | 50.0 | 49.95 | 0.05
| 0.0 | 0.0 | 75.86 | false |
- | 16 January 2024 | Interest Refund | 0.31 | 0.31 | 0.0
| 0.0 | 0.0 | 75.55 | false |
+ | 16 January 2024 | Interest Refund | 0.3 | 0.3 | 0.0
| 0.0 | 0.0 | 75.56 | false |
Then In Loan Transactions the "4"th Transaction has relationship
type=RELATED with the "3"th Transaction
Then In Loan Transactions the "6"th Transaction has relationship
type=RELATED with the "5"th Transaction
When Customer undo "1"th "Merchant Issued Refund" transaction made on "15
January 2024"
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 e039e505d..4ba2c3b24 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
@@ -141,6 +141,7 @@ import
org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCom
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import
org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import
org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
@@ -5452,6 +5453,11 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
}
}
+ public List<LoanTransactionType>
getSupportedInterestRefundTransactionTypes() {
+ return
getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
+
.map(LoanSupportedInterestRefundTypes::getTransactionType).toList();
+ }
+
public LoanTransaction getLastUserTransaction() {
return getLoanTransactions().stream() //
.filter(LoanTransaction::isNotReversed) //
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 9f14dd045..98b5166c8 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -1052,6 +1052,10 @@ public class LoanTransaction extends
AbstractAuditableWithUTCDateTimeCustom<Long
return getTypeOf().isInterestRefund();
}
+ public void updateAmount(BigDecimal bigDecimal) {
+ this.amount = bigDecimal;
+ }
+
// TODO missing hashCode(), equals(Object obj), but probably OK as long as
// this is never stored in a Collection.
}
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 266d025bf..ba27cc078 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
@@ -18,15 +18,23 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
-import java.math.BigDecimal;
import java.time.LocalDate;
+import java.util.List;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
public interface InterestRefundService {
boolean canHandle(Loan loan);
- BigDecimal calculateInterestRefundAmount(Long loanId, BigDecimal
relatedRefundTransactionAmount,
- LocalDate relatedRefundTransactionDate);
+ @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
+ Money
totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor
processor, Long loanId,
+ LocalDate relatedRefundTransactionDate, List<LoanTransaction>
newTransactions, List<Long> oldTransactionIds);
+ Money getTotalInterestRefunded(List<LoanTransaction> loanTransactions,
MonetaryCurrency currency);
}
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 ab321b70c..dc35c20c3 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
@@ -57,6 +57,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair;
+import
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -85,6 +86,7 @@ import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Tra
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
import
org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
@@ -102,6 +104,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME =
"Advanced payment allocation strategy";
public final EMICalculator emiCalculator;
+ public final InterestRefundService interestRefundService;
@Override
public String getCode() {
@@ -196,6 +199,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
LoanTransaction transaction =
changeOperation.getLoanTransaction().get();
processSingleTransaction(transaction, ctx);
transaction =
getProcessedTransaction(changedTransactionDetail, transaction);
+ ctx.getAlreadyProcessedTransactions().add(transaction);
if (transaction.isOverPaid() &&
transaction.isRepaymentLikeType()) { // TODO CREDIT, DEBIT
overpaidTransactions.add(transaction);
}
@@ -268,9 +272,10 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
case CHARGEBACK -> handleChargeback(loanTransaction, ctx);
case CREDIT_BALANCE_REFUND ->
handleCreditBalanceRefund(loanTransaction, ctx.getCurrency(),
ctx.getInstallments(), ctx.getOverpaymentHolder());
- case INTEREST_REFUND, REPAYMENT, MERCHANT_ISSUED_REFUND,
PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT,
- DOWN_PAYMENT, WAIVE_INTEREST, RECOVERY_REPAYMENT,
INTEREST_PAYMENT_WAIVER ->
+ case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND,
GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
+ WAIVE_INTEREST, RECOVERY_REPAYMENT,
INTEREST_PAYMENT_WAIVER ->
handleRepayment(loanTransaction, ctx);
+ case INTEREST_REFUND -> handleInterestRefund(loanTransaction, ctx);
case CHARGE_OFF -> handleChargeOff(loanTransaction, ctx);
case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, ctx);
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will
not be processed.");
@@ -284,6 +289,25 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
}
+ private void handleInterestRefund(LoanTransaction loanTransaction,
TransactionCtx ctx) {
+
+ if (ctx instanceof ProgressiveTransactionCtx progCtx) {
+ Money interestBeforeRefund =
emiCalculator.getSumOfDueInterestsOnDate(progCtx.getModel(),
loanTransaction.getDateOf());
+ List<Long> unmodifiedTransactionIds =
progCtx.getAlreadyProcessedTransactions().stream().filter(LoanTransaction::isNotReversed)
+ .map(AbstractPersistableCustom::getId).toList();
+ List<LoanTransaction> modifiedTransactions = new
ArrayList<>(progCtx.getAlreadyProcessedTransactions().stream()
+ .filter(LoanTransaction::isNotReversed).filter(tr ->
tr.getId() == null).toList());
+ if (!modifiedTransactions.isEmpty()) {
+ Money interestAfterRefund =
interestRefundService.totalInterestByTransactions(this,
loanTransaction.getLoan().getId(),
+ loanTransaction.getDateOf(), modifiedTransactions,
unmodifiedTransactionIds);
+ Money newAmount =
interestBeforeRefund.minus(progCtx.getSumOfInterestRefundAmount()).minus(interestAfterRefund);
+ loanTransaction.updateAmount(newAmount.getAmount());
+ }
+
progCtx.setSumOfInterestRefundAmount(progCtx.getSumOfInterestRefundAmount().add(loanTransaction.getAmount()));
+ }
+ handleRepayment(loanTransaction, ctx);
+ }
+
private void handleReAmortization(LoanTransaction loanTransaction,
TransactionCtx transactionCtx) {
LocalDate transactionDate = loanTransaction.getTransactionDate();
List<LoanRepaymentScheduleInstallment> previousInstallments =
transactionCtx.getInstallments().stream() //
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 05fe590ff..833311fd2 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
@@ -19,14 +19,17 @@
package
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
import java.time.LocalDate;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
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.LoanTransaction;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
@@ -37,11 +40,15 @@ public class ProgressiveTransactionCtx extends
TransactionCtx {
private final ProgressiveLoanInterestScheduleModel model;
@Setter
private LocalDate lastOverdueBalanceChange = null;
+ private List<LoanTransaction> alreadyProcessedTransactions = new
ArrayList<>();
+ @Setter
+ private Money sumOfInterestRefundAmount;
public ProgressiveTransactionCtx(MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, MoneyHolder overpaymentHolder,
ChangedTransactionDetail changedTransactionDetail,
ProgressiveLoanInterestScheduleModel model) {
super(currency, installments, charges, overpaymentHolder,
changedTransactionDetail);
+ sumOfInterestRefundAmount = model.getZero();
this.model = model;
}
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
index 0f9221174..f701cdfca 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
@@ -62,4 +62,6 @@ public interface EMICalculator {
LocalDate targetDate);
OutstandingDetails
getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model,
LocalDate targetDate);
+
+ Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate subjectDate);
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
index e33c2fec8..e9d21cf85 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
@@ -716,4 +716,22 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
return new ProgressiveLoanInterestScheduleModel(repaymentModels,
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
}
+
+ /**
+ * Calculates the sum of due interests on interest periods.
+ *
+ * @param scheduleModel
+ * schedule model
+ * @param subjectDate
+ * the date to calculate the interest for.
+ * @return sum of due interests
+ */
+ @Override
+ public Money
getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel scheduleModel,
LocalDate subjectDate) {
+ return
scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueDate) //
+ .map(repaymentPeriodDueDate -> getDueAmounts(scheduleModel,
repaymentPeriodDueDate, subjectDate) //
+ .getDueInterest()) //
+ .reduce(scheduleModel.getZero(), Money::add); //
+ }
+
}
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 d04ba491e..cb1c285fd 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
@@ -109,7 +109,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
@BeforeEach
public void setUp() {
- underTest = new
AdvancedPaymentScheduleTransactionProcessor(emiCalculator);
+ underTest = new
AdvancedPaymentScheduleTransactionProcessor(emiCalculator, null);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L,
"default", "Default", "Asia/Kolkata", null));
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
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 dcbba6ed0..af9617b25 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
@@ -34,6 +34,7 @@ import
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
@@ -143,8 +144,8 @@ public class LoanAccountDomainServiceJpa implements
LoanAccountDomainService {
private final DelinquencyEffectivePauseHelper
delinquencyEffectivePauseHelper;
private final DelinquencyReadPlatformService
delinquencyReadPlatformService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
- private final InterestRefundServiceDelegate interestRefundServiceDelegate;
private final LoanRepaymentScheduleTransactionProcessorFactory
transactionProcessorFactory;
+ private final InterestRefundServiceDelegate interestRefundServiceDelegate;
@Transactional
@Override
@@ -165,17 +166,26 @@ public class LoanAccountDomainServiceJpa implements
LoanAccountDomainService {
this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet);
}
- private LoanTransaction createInterestRefundLoanTransaction(Loan loan,
final LocalDate transactionDate,
- BigDecimal relatedRefundTransactionAmount) {
+ private LoanTransaction createInterestRefundLoanTransaction(Loan loan,
LoanTransaction refundTransaction) {
+
InterestRefundService interestRefundService =
interestRefundServiceDelegate.lookupInterestRefundService(loan);
if (interestRefundService == null) {
return null;
}
- BigDecimal interestRefundAmount =
interestRefundService.calculateInterestRefundAmount(loan.getId(),
relatedRefundTransactionAmount,
- transactionDate);
+
+ Money totalInterest =
interestRefundService.totalInterestByTransactions(null, loan.getId(),
refundTransaction.getTransactionDate(),
+ List.of(),
loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
+ Money previouslyRefundedInterests =
interestRefundService.getTotalInterestRefunded(loan.getLoanTransactions(),
loan.getCurrency());
+
+ Money newTotalInterest =
interestRefundService.totalInterestByTransactions(null, loan.getId(),
+ refundTransaction.getTransactionDate(),
List.of(refundTransaction),
+
loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
+ BigDecimal interestRefundAmount =
totalInterest.minus(previouslyRefundedInterests).minus(newTotalInterest).getAmount();
+
final ExternalId txnExternalId = externalIdFactory.create();
businessEventNotifierService.notifyPreBusinessEvent(new
LoanTransactionInterestRefundPreBusinessEvent(loan));
- return LoanTransaction.interestRefund(loan, interestRefundAmount,
transactionDate, txnExternalId);
+ return LoanTransaction.interestRefund(loan, interestRefundAmount,
refundTransaction.getDateOf(), txnExternalId);
+
}
@Transactional
@@ -867,7 +877,7 @@ public class LoanAccountDomainServiceJpa implements
LoanAccountDomainService {
LoanTransaction interestRefundTransaction = null;
if (shouldCreateInterestRefundTransaction) {
- interestRefundTransaction =
createInterestRefundLoanTransaction(loan, transactionDate, transactionAmount);
+ interestRefundTransaction =
createInterestRefundLoanTransaction(loan, refundTransaction);
if (interestRefundTransaction != null) {
interestRefundTransaction.getLoanTransactionRelations().add(LoanTransactionRelation
.linkToTransaction(interestRefundTransaction,
refundTransaction, LoanTransactionRelationTypeEnum.RELATED));
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundServiceDelegate.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundServiceDelegate.java
similarity index 95%
rename from
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundServiceDelegate.java
rename to
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundServiceDelegate.java
index a4aa2f062..7635b0a59 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundServiceDelegate.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundServiceDelegate.java
@@ -21,12 +21,14 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class InterestRefundServiceDelegate {
+ @Lazy
private final List<InterestRefundService> interestRefundService;
public InterestRefundService lookupInterestRefundService(final Loan loan) {
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 62ec46dce..8dc0ab31b 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
@@ -25,19 +25,22 @@ import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
import
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
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;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
import
org.apache.fineract.portfolio.loanaccount.starter.AdvancedPaymentScheduleTransactionProcessorCondition;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
-import
org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
@@ -49,18 +52,13 @@ import
org.springframework.transaction.annotation.Transactional;
@Service
public class ProgressiveLoanInterestRefundServiceImpl implements
InterestRefundService {
- private final AdvancedPaymentScheduleTransactionProcessor processor;
private final EMICalculator emiCalculator;
private final LoanAssembler loanAssembler;
- @Override
- public boolean canHandle(Loan loan) {
- return loan != null && loan.isInterestBearing() &&
processor.accept(loan.getTransactionProcessingStrategyCode());
- }
-
private static void simulateRepaymentForDisbursements(LoanTransaction lt,
final AtomicReference<BigDecimal> refundFinal,
List<LoanTransaction> collect) {
- collect.add(lt);
+ collect.add(new LoanTransaction(lt.getLoan(),
lt.getLoan().getOffice(), lt.getTypeOf().getValue(), lt.getDateOf(),
lt.getAmount(),
+ BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO, false, null, null));
if (lt.getTypeOf().isDisbursement() &&
refundFinal.get().compareTo(BigDecimal.ZERO) > 0) {
if (lt.getAmount().compareTo(refundFinal.get()) <= 0) {
collect.add(
@@ -76,52 +74,70 @@ public class ProgressiveLoanInterestRefundServiceImpl
implements InterestRefundS
}
}
- private BigDecimal totalInterest(final Loan loan, BigDecimal refundAmount,
LocalDate relatedRefundTransactionDate) {
- final AtomicReference<BigDecimal> refundFinal = new
AtomicReference<>(refundAmount);
-
- BigDecimal payableInterest = BigDecimal.ZERO;
- if
(loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isDisbursement))
{
- List<LoanTransaction> transactionsToReprocess = new ArrayList<>();
- List<LoanTransactionType> interestRefundTypes =
loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
-
.map(LoanSupportedInterestRefundTypes::getTransactionType).toList();
- // add already interest refunded amounts to refund amount
- // it is necessary to avoid multi disbursed refund
- loan.getLoanTransactions().stream() //
- .filter(lt -> !lt.isReversed()) //
- .filter(lt ->
interestRefundTypes.contains(lt.getTypeOf())) //
- .forEach(t ->
refundFinal.set(refundFinal.get().add(t.getAmount()))); //
- loan.getLoanTransactions().stream() //
- .filter(lt -> !lt.isReversed()) //
- .filter(lt -> !lt.isAccrual() && !lt.isAccrualActivity()
&& !lt.isInterestRefund()) //
- .filter(loanTransaction ->
!interestRefundTypes.contains(loanTransaction.getTypeOf())) //
- .forEach(lt -> simulateRepaymentForDisbursements(lt,
refundFinal, transactionsToReprocess)); //
-
- List<LoanRepaymentScheduleInstallment> installmentsToReprocess =
new ArrayList<>(
- loan.getRepaymentScheduleInstallments().stream().filter(i
-> !i.isReAged() && !i.isAdditional()).toList());
-
- Pair<ChangedTransactionDetail,
ProgressiveLoanInterestScheduleModel> reprocessResult = processor
-
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
relatedRefundTransactionDate, transactionsToReprocess,
- loan.getCurrency(), installmentsToReprocess,
loan.getActiveCharges());
-
loan.getLoanTransactions().addAll(reprocessResult.getLeft().getCurrentTransactionToOldId().keySet());
- ProgressiveLoanInterestScheduleModel modelAfter =
reprocessResult.getRight();
-
- payableInterest = installmentsToReprocess.stream() //
- .map(installment -> emiCalculator //
- .getDueAmounts(modelAfter,
installment.getDueDate(), relatedRefundTransactionDate) //
- .getDueInterest() //
- .getAmount()) //
- .reduce(BigDecimal.ZERO, BigDecimal::add); //
- }
- return payableInterest;
+ private Money
recalculateTotalInterest(AdvancedPaymentScheduleTransactionProcessor processor,
Loan loan,
+ LocalDate relatedRefundTransactionDate, List<LoanTransaction>
transactionsToReprocess) {
+ List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new
ArrayList<>(
+ loan.getRepaymentScheduleInstallments().stream().filter(i ->
!i.isReAged() && !i.isAdditional()).toList());
+
+ Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel>
reprocessResult = processor
+
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
relatedRefundTransactionDate, transactionsToReprocess,
+ loan.getCurrency(), installmentsToReprocess,
loan.getActiveCharges());
+
loan.getLoanTransactions().addAll(reprocessResult.getLeft().getCurrentTransactionToOldId().keySet());
+ ProgressiveLoanInterestScheduleModel modelAfter =
reprocessResult.getRight();
+
+ return emiCalculator.getSumOfDueInterestsOnDate(modelAfter,
relatedRefundTransactionDate);
+ }
+
+ @Override
+ public boolean canHandle(Loan loan) {
+ String s = loan.getTransactionProcessingStrategyCode();
+ return
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME.equalsIgnoreCase(s)
+ ||
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equalsIgnoreCase(s);
+ }
+
+ private boolean
isTransactionNeededForInterestRefundCalculations(LoanTransaction lt) {
+ return lt.isNotReversed() && !lt.isAccrual() &&
!lt.isAccrualActivity() && !lt.isInterestRefund();
}
@Override
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
- public BigDecimal calculateInterestRefundAmount(Long loanId, BigDecimal
relatedRefundTransactionAmount,
- LocalDate relatedRefundTransactionDate) {
+ public Money
totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor
processor, final Long loanId,
+ LocalDate relatedRefundTransactionDate, List<LoanTransaction>
newTransactions, List<Long> oldTransactionIds) {
Loan loan = loanAssembler.assembleFrom(loanId);
- BigDecimal totalInterestBeforeRefund = totalInterest(loan,
BigDecimal.ZERO, relatedRefundTransactionDate);
- BigDecimal totalInterestAfterRefund = totalInterest(loan,
relatedRefundTransactionAmount, relatedRefundTransactionDate);
- return totalInterestBeforeRefund.subtract(totalInterestAfterRefund);
+ if (processor == null) {
+ processor = loan.getTransactionProcessor();
+ }
+ if (!(processor instanceof
AdvancedPaymentScheduleTransactionProcessor)) {
+ throw new IllegalArgumentException(
+ "Wrong processor implementation.
ProgressiveLoanInterestRefundServiceImpl requires
AdvancedPaymentScheduleTransactionProcessor");
+ }
+
+ List<LoanTransaction> transactionsToReprocess = new ArrayList<>();
+ List<LoanTransactionType> interestRefundTypes =
loan.getSupportedInterestRefundTransactionTypes();
+
+ List<LoanTransaction> transactions =
Stream.concat(loan.getLoanTransactions().stream() //
+ .filter(lt ->
isTransactionNeededForInterestRefundCalculations(lt) //
+ && oldTransactionIds.contains(lt.getId())), //
+ newTransactions.stream() //
+
.filter(this::isTransactionNeededForInterestRefundCalculations) //
+ .map(LoanTransaction::copyTransactionProperties)) //
+ .toList();
+
+ final AtomicReference<BigDecimal> refundFinal = new AtomicReference<>(
+ transactions.stream().filter(lt ->
interestRefundTypes.contains(lt.getTypeOf())) //
+
.map(LoanTransaction::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
+
+ transactions.stream().filter(loanTransaction ->
!interestRefundTypes.contains(loanTransaction.getTypeOf())) //
+ .forEach(lt -> simulateRepaymentForDisbursements(lt,
refundFinal, transactionsToReprocess)); //
+
+ return
recalculateTotalInterest((AdvancedPaymentScheduleTransactionProcessor)
processor, loan, relatedRefundTransactionDate,
+ transactionsToReprocess);
+ }
+
+ @Override
+ public Money getTotalInterestRefunded(List<LoanTransaction>
loanTransactions, MonetaryCurrency currency) {
+ return Money.of(currency,
loanTransactions.stream().filter(LoanTransaction::isNotReversed).filter(LoanTransaction::isInterestRefund)
+ .map(LoanTransaction::getAmount).reduce(BigDecimal.ZERO,
BigDecimal::add));
}
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
index 47b820906..269452c3e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
@@ -31,11 +31,13 @@ import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
+import
org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestRefundServiceImpl;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
@Configuration
public class LoanAccountAutoStarter {
@@ -104,8 +106,9 @@ public class LoanAccountAutoStarter {
@Bean
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
- public AdvancedPaymentScheduleTransactionProcessor
advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator) {
- return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator);
+ public AdvancedPaymentScheduleTransactionProcessor
advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
+ @Lazy ProgressiveLoanInterestRefundServiceImpl
progressiveLoanInterestRefundService) {
+ return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator,
progressiveLoanInterestRefundService);
}
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java
index 5631fc7ba..9047fb7d5 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java
@@ -309,7 +309,7 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"),
- transaction(1000.0, "Payout Refund", "09 February 2021"),
transaction(87.89, "Repayment", "01 February 2021"),
+ transaction(87.89, "Repayment", "01 February 2021"),
transaction(1000.0, "Payout Refund", "09 February 2021"),
transaction(10.49, "Interest Refund", "09 February 2021"));
});
}
@@ -680,12 +680,12 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
runAt("22 January 2021", () -> {
Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("PayoutRefund", "22
January 2021", 500F, loanId.intValue());
-
+ logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"), //
transaction(500.0, "Merchant Issued Refund", "14 January
2021"), //
transaction(1.78, "Interest Refund", "14 January 2021"), //
transaction(500.0, "Payout Refund", "22 January 2021"), //
- transaction(2.87, "Interest Refund", "22 January 2021") //
+ transaction(2.88, "Interest Refund", "22 January 2021") //
);
});
}
@@ -830,6 +830,7 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
runAt("26 January 2021", () -> {
Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("PayoutRefund", "26
January 2021", 400F, loanId.intValue());
+ logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(200.0, "Disbursement", "01
January 2021"), //
transaction(300.0, "Disbursement", "01 January 2021"), //
@@ -879,11 +880,11 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
});
runAt("1 February 2021", () -> {
Long loanId = loanIdRef.get();
- loanTransactionHelper.makeLoanRepayment("Repayment", "1 February
2021", 171.50F, loanId.intValue());
+ loanTransactionHelper.makeLoanRepayment("Repayment", "1 February
2021", 171.41F, loanId.intValue());
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01
January 2021"), //
transaction(500.0, "Disbursement", "05 January 2021"), //
- transaction(171.5, "Repayment", "01 February 2021"));
+ transaction(171.41, "Repayment", "01 February 2021"));
});
runAt("13 February 2021", () -> {
Long loanId = loanIdRef.get();
@@ -891,7 +892,7 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01
January 2021"), //
transaction(500.0, "Disbursement", "05 January 2021"), //
- transaction(171.5, "Repayment", "01 February 2021"), //
+ transaction(171.41, "Repayment", "01 February 2021"), //
transaction(250.0, "Payout Refund", "13 February 2021"), //
transaction(2.96, "Interest Refund", "13 February 2021") //
);
@@ -899,20 +900,21 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
runAt("24 February 2021", () -> {
Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("MerchantIssuedRefund",
"24 February 2021", 400F, loanId.intValue());
+ logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01
January 2021"), //
transaction(500.0, "Disbursement", "05 January 2021"), //
- transaction(171.5, "Repayment", "01 February 2021"), //
+ transaction(171.41, "Repayment", "01 February 2021"), //
transaction(250.0, "Payout Refund", "13 February 2021"), //
transaction(2.96, "Interest Refund", "13 February 2021"),
//
transaction(400.0, "Merchant Issued Refund", "24 February
2021"), //
- transaction(5.76, "Interest Refund", "24 February 2021") //
+ transaction(5.77, "Interest Refund", "24 February 2021") //
);
});
runAt("1 April 2021", () -> {
Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("Repayment", "1 March
2021", 171.41F, loanId.intValue());
- loanTransactionHelper.makeLoanRepayment("Repayment", "1 April
2021", 11.17F, loanId.intValue());
+ loanTransactionHelper.makeLoanRepayment("Repayment", "1 April
2021", 11.25F, loanId.intValue());
GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
@@ -973,6 +975,7 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
runAt("6 April 2021", () -> {
Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("MerchantIssuedRefund", "6
April 2021", 400F, loanId.intValue());
+ logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01
January 2021"), //
transaction(500.0, "Disbursement", "05 January 2021"), //
@@ -982,14 +985,14 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
transaction(250.0, "Payout Refund", "13 February 2021"), //
transaction(2.96, "Interest Refund", "13 February 2021"),
//
transaction(400.0, "Merchant Issued Refund", "06 April
2021"), //
- transaction(10.1, "Interest Refund", "06 April 2021") //
+ transaction(10.11, "Interest Refund", "06 April 2021") //
);
GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
Assertions.assertEquals(700, loanDetails.getStatus().getId());
- Assertions.assertEquals(160.15D, loanDetails.getTotalOverpaid());
+ Assertions.assertEquals(160.16D, loanDetails.getTotalOverpaid());
});
}
@@ -1042,6 +1045,7 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
runAt("8 February 2021", () -> {
Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("PayoutRefund", "8
February 2021", 250F, loanId.intValue());
+ logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"), //
transaction(400.0, "Payout Refund", "12 January 2021"), //
@@ -1050,12 +1054,12 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
transaction(0.66, "Interest Refund", "17 January 2021"), //
transaction(171.5, "Repayment", "01 February 2021"), //
transaction(250.0, "Payout Refund", "08 February 2021"), //
- transaction(2.60, "Interest Refund", "08 February 2021") //
+ transaction(2.61, "Interest Refund", "08 February 2021") //
);
});
runAt("1 March 2021", () -> {
Long loanId = loanIdRef.get();
- loanTransactionHelper.makeLoanRepayment("Repayment", "1 March
2021", 30.44F, loanId.intValue());
+ loanTransactionHelper.makeLoanRepayment("Repayment", "1 March
2021", 30.43F, loanId.intValue());
GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus());
@@ -1063,6 +1067,103 @@ public class LoanInterestRefundTest extends
BaseLoanIntegrationTest {
});
}
+ @Test
+ public void verifyUC18S1() {
+ AtomicReference<Long> loanIdRef = new AtomicReference<>();
+ runAt("1 January 2021", () -> {
+ PostLoanProductsResponse loanProduct = loanProductHelper
+
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL)
//
+ .daysInYearType(DaysInYearType.ACTUAL) //
+ .multiDisburseLoan(true)//
+ .disallowExpectedDisbursements(true)//
+ .maxTrancheCount(2)//
+
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.PAYOUT_REFUND)
//
+
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND)
//
+
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
+ );
+ Long loanId = applyAndApproveProgressiveLoan(client.getClientId(),
loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.9,
+ 12, null);
+ Assertions.assertNotNull(loanId);
+ loanIdRef.set(loanId);
+ disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
+ });
+ runAt("22 January 2021", () -> {
+ Long loanId = loanIdRef.get();
+ loanTransactionHelper.makeLoanRepayment("MerchantIssuedRefund",
"22 January 2021", 1000F, loanId.intValue());
+ logLoanTransactions(loanId);
+
+ verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"), //
+ transaction(1000.0, "Merchant Issued Refund", "22 January
2021"), //
+ transaction(5.70, "Interest Refund", "22 January 2021"), //
+ transaction(5.70, "Accrual", "22 January 2021") //
+ );
+ loanTransactionHelper.makeLoanRepayment("Repayment", "10 January
2021", 85.63F, loanId.intValue());
+ logLoanTransactions(loanId);
+
+ verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"), //
+ transaction(85.63, "Repayment", "10 January 2021"), //
+ transaction(1000.0, "Merchant Issued Refund", "22 January
2021"), //
+ transaction(5.42, "Interest Refund", "22 January 2021") //
+ );
+ });
+ }
+
+ @Test
+ public void verifyUC18S2() {
+ AtomicReference<Long> loanIdRef = new AtomicReference<>();
+ AtomicReference<Long> repaymentIdRef = new AtomicReference<>();
+ runAt("1 January 2021", () -> {
+ PostLoanProductsResponse loanProduct = loanProductHelper
+
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL)
//
+ .daysInYearType(DaysInYearType.ACTUAL) //
+ .multiDisburseLoan(true)//
+ .disallowExpectedDisbursements(true)//
+
.maxTrancheCount(2).addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.PAYOUT_REFUND)
//
+
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND)
//
+
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
+ );
+ Long loanId = applyAndApproveProgressiveLoan(client.getClientId(),
loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.9,
+ 12, null);
+ Assertions.assertNotNull(loanId);
+ loanIdRef.set(loanId);
+ disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
+ });
+ runAt("10 January 2021", () -> {
+ Long loanId = loanIdRef.get();
+ Long response =
loanTransactionHelper.makeLoanRepayment("Repayment", "10 January 2021", 85.63F,
loanId.intValue())
+ .getResourceId();
+ Assertions.assertNotNull(response);
+ repaymentIdRef.set(response);
+
+ verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"), //
+ transaction(85.63, "Repayment", "10 January 2021") //
+ );
+ });
+ runAt("22 January 2021", () -> {
+ Long loanId = loanIdRef.get();
+ loanTransactionHelper.makeLoanRepayment("MerchantIssuedRefund",
"22 January 2021", 1000F, loanId.intValue());
+ logLoanTransactions(loanId);
+
+ verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"), //
+ transaction(85.63, "Repayment", "10 January 2021"), //
+ transaction(1000.0, "Merchant Issued Refund", "22 January
2021"), //
+ transaction(5.42, "Interest Refund", "22 January 2021") //
+ );
+
+ Long repaymentId = repaymentIdRef.get();
+ loanTransactionHelper.reverseLoanTransaction(loanId.intValue(),
repaymentId, "10 January 2021", responseSpec);
+ logLoanTransactions(loanId);
+
+ verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01
January 2021"), //
+ reversedTransaction(85.63, "Repayment", "10 January
2021"), //
+ transaction(1000.0, "Merchant Issued Refund", "22 January
2021"), //
+ transaction(5.70, "Interest Refund", "22 January 2021"), //
+ transaction(5.70, "Accrual", "10 January 2021") //
+ );
+
+ });
+ }
+
private void logInstallmentsOfLoanDetails(GetLoansLoanIdResponse
loanDetails) {
log.info("index, dueDate, principal, fee, penalty, interest");
if (loanDetails != null && loanDetails.getRepaymentSchedule() != null
&& loanDetails.getRepaymentSchedule().getPeriods() != null) {