FINERACT-2107: Interest Refund - Transaction Amount Recalculation

This commit is contained in:
Soma Sörös
2024-11-14 09:59:08 +01:00
committed by Adam Saghy
parent 4e8fc2c0a8
commit 6286d1f821
14 changed files with 276 additions and 75 deletions
@@ -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() //
@@ -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;
}
}
@@ -62,4 +62,6 @@ public interface EMICalculator {
LocalDate targetDate);
OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate);
Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate subjectDate);
}
@@ -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); //
}
}
@@ -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);