FINERACT-2107: Interest Refund - Transaction Amount Recalculation
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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) //
|
||||
|
||||
+4
@@ -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.
|
||||
}
|
||||
|
||||
+11
-3
@@ -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);
|
||||
}
|
||||
|
||||
+26
-2
@@ -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() //
|
||||
|
||||
+7
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -62,4 +62,6 @@ public interface EMICalculator {
|
||||
LocalDate targetDate);
|
||||
|
||||
OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate);
|
||||
|
||||
Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate subjectDate);
|
||||
}
|
||||
|
||||
+18
@@ -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); //
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+17
-7
@@ -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));
|
||||
|
||||
+2
@@ -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) {
|
||||
+62
-46
@@ -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);
|
||||
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());
|
||||
|
||||
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)); //
|
||||
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();
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>(
|
||||
loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList());
|
||||
return emiCalculator.getSumOfDueInterestsOnDate(modelAfter, relatedRefundTransactionDate);
|
||||
}
|
||||
|
||||
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();
|
||||
@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);
|
||||
}
|
||||
|
||||
payableInterest = installmentsToReprocess.stream() //
|
||||
.map(installment -> emiCalculator //
|
||||
.getDueAmounts(modelAfter, installment.getDueDate(), relatedRefundTransactionDate) //
|
||||
.getDueInterest() //
|
||||
.getAmount()) //
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add); //
|
||||
}
|
||||
return payableInterest;
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-2
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+114
-13
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user