From 6286d1f8211dd9b60a1e372b48f1f060f089d25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soma=20S=C3=B6r=C3=B6s?= Date: Thu, 7 Nov 2024 18:18:11 +0100 Subject: [PATCH] FINERACT-2107: Interest Refund - Transaction Amount Recalculation --- .../resources/features/LoanRepayment.feature | 2 +- .../portfolio/loanaccount/domain/Loan.java | 6 + .../loanaccount/domain/LoanTransaction.java | 4 + .../service/InterestRefundService.java | 14 +- ...edPaymentScheduleTransactionProcessor.java | 28 +++- .../impl/ProgressiveTransactionCtx.java | 7 + .../loanproduct/calc/EMICalculator.java | 2 + .../calc/ProgressiveEMICalculator.java | 18 +++ ...ymentScheduleTransactionProcessorTest.java | 2 +- .../domain/LoanAccountDomainServiceJpa.java | 24 +++- .../InterestRefundServiceDelegate.java | 2 + ...gressiveLoanInterestRefundServiceImpl.java | 116 +++++++++------- .../starter/LoanAccountAutoStarter.java | 7 +- .../LoanInterestRefundTest.java | 127 ++++++++++++++++-- 14 files changed, 280 insertions(+), 79 deletions(-) rename {fineract-loan => fineract-provider}/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundServiceDelegate.java (95%) 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 81fe59167a8..5e19a7c79d5 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 e039e505d4a..4ba2c3b2445 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.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 void handleMaturityDateActivate() { } } + public List 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 9f14dd04529..98b5166c817 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 boolean isInterestRefund() { 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 266d025bf1d..ba27cc0783b 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 newTransactions, List oldTransactionIds); + Money getTotalInterestRefunded(List 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 ab321b70cad..dc35c20c327 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 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.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 Pair repr 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 void processLatestTransaction(LoanTransaction loanTransaction, Transactio 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 void processLatestTransaction(LoanTransaction loanTransaction, Transactio } } + private void handleInterestRefund(LoanTransaction loanTransaction, TransactionCtx ctx) { + + if (ctx instanceof ProgressiveTransactionCtx progCtx) { + Money interestBeforeRefund = emiCalculator.getSumOfDueInterestsOnDate(progCtx.getModel(), loanTransaction.getDateOf()); + List unmodifiedTransactionIds = progCtx.getAlreadyProcessedTransactions().stream().filter(LoanTransaction::isNotReversed) + .map(AbstractPersistableCustom::getId).toList(); + List 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 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 05fe590ff2f..833311fd2a1 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 alreadyProcessedTransactions = new ArrayList<>(); + @Setter + private Money sumOfInterestRefundAmount; public ProgressiveTransactionCtx(MonetaryCurrency currency, List installments, Set 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 0f9221174ca..f701cdfca1b 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 @@ Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel int 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 e33c2fec8f0..e9d21cf85f7 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 ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDeta 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 d04ba491ed6..cb1c285fdd5 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 @@ public static void destruct() { @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 dcbba6ed0b8..af9617b25d6 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.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 void updateLoanCollateralStatus(Set loanCollate 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 Pair makeRefund(final Loan loan, final 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 a4aa2f06282..7635b0a5956 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 @@ 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; 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 62ec46dcef5..8dc0ab31bb4 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.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 @@ @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 refundFinal, List 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 @@ private static void simulateRepaymentForDisbursements(LoanTransaction lt, final } } - private BigDecimal totalInterest(final Loan loan, BigDecimal refundAmount, LocalDate relatedRefundTransactionDate) { - final AtomicReference refundFinal = new AtomicReference<>(refundAmount); - - BigDecimal payableInterest = BigDecimal.ZERO; - if (loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isDisbursement)) { - List transactionsToReprocess = new ArrayList<>(); - List 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 installmentsToReprocess = new ArrayList<>( - loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList()); - - Pair 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 transactionsToReprocess) { + List installmentsToReprocess = new ArrayList<>( + loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList()); + + Pair 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 newTransactions, List 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 transactionsToReprocess = new ArrayList<>(); + List interestRefundTypes = loan.getSupportedInterestRefundTransactionTypes(); + + List 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 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 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 47b820906d0..269452c3e2d 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.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 LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTra @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 5631fc7baea..9047fb7d50e 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 void verifyUC02b() { 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 void verifyUC11() { 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 void verifyUC14() { 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 void verifyUC15() { }); 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 void verifyUC15() { 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 void verifyUC15() { 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 void verifyUC16() { 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 void verifyUC16() { 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 void verifyUC17() { 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 void verifyUC17() { 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 void verifyUC17() { }); } + @Test + public void verifyUC18S1() { + AtomicReference 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 loanIdRef = new AtomicReference<>(); + AtomicReference 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) {