Skip to content

Commit

Permalink
FINERACT-2107: Interest Refund - Partial Repayment
Browse files Browse the repository at this point in the history
  • Loading branch information
somasorosdpc committed Oct 30, 2024
1 parent 94360c9 commit 01932b5
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleMod
interestPeriod = repaymentPeriod.getInterestPeriods().stream()
.filter(ip -> targetDate.isAfter(ip.getFromDate()) && !targetDate.isAfter(ip.getDueDate())).findFirst().orElseThrow();
}
scheduleModelCopy.repaymentPeriods().stream()
.filter(rp -> targetDate.isAfter(rp.getFromDate()) && !targetDate.isAfter(rp.getDueDate())).findFirst()
.ifPresent(rp -> rp.getInterestPeriods().stream()
.filter(ip -> targetDate.isAfter(ip.getFromDate()) && !targetDate.isAfter(ip.getDueDate()))
.reduce((one, two) -> two).ifPresent(ip -> ip.setDueDate(targetDate)));
interestPeriod.setDueDate(adjustedTargetDate);
int index = repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
repaymentPeriod.getInterestPeriods().subList(index + 1, repaymentPeriod.getInterestPeriods().size()).clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;

import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REPAYMENT;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
Expand Down Expand Up @@ -54,53 +54,48 @@ public boolean canHandle(Loan loan) {
return loan != null && loan.isInterestBearing() && processor.accept(loan.getTransactionProcessingStrategyCode());
}

private static boolean omitDisbursements(LoanTransaction lt, final AtomicReference<BigDecimal> refundFinal) {
private static void simulateRepaymentForDisbursements(LoanTransaction lt, final AtomicReference<BigDecimal> refundFinal,
List<LoanTransaction> collect) {
collect.add(lt);
if (lt.getTypeOf().isDisbursement() && refundFinal.get().compareTo(BigDecimal.ZERO) > 0) {
if (lt.getAmount().compareTo(refundFinal.get()) <= 0) {
collect.add(
new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), REPAYMENT.getValue(), lt.getDateOf(), lt.getAmount(),
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, null));
refundFinal.set(refundFinal.get().subtract(lt.getAmount()));
return false;
} else {
collect.add(
new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), REPAYMENT.getValue(), lt.getDateOf(), refundFinal.get(),
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, null));
refundFinal.set(BigDecimal.ZERO);
}
}
return true;
}

private static LoanTransaction calculateReducedAmountDisbursements(LoanTransaction lt, final AtomicReference<BigDecimal> refundFinal) {
if (lt.getTypeOf().isDisbursement() && refundFinal.get().compareTo(BigDecimal.ZERO) > 0) {
LoanTransaction result = new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), lt.getTypeOf().getValue(), lt.getDateOf(),
lt.getAmount().subtract(refundFinal.get()), lt.getPrincipalPortion(), lt.getInterestPortion(),
lt.getFeeChargesPortion(), lt.getPenaltyChargesPortion(),
lt.getOverPaymentPortion(lt.getLoan().getCurrency()).getAmount(), lt.isReversed(), lt.getPaymentDetail(),
lt.getExternalId());
refundFinal.set(BigDecimal.ZERO);
return result;
}
return lt;
}

private BigDecimal totalInterest(final Loan loan, BigDecimal refundAmount, LocalDate relatedRefundTransactionDate) {
final AtomicReference<BigDecimal> refundFinal = new AtomicReference<>(refundAmount);
List<LoanTransaction> transactionsToReprocess = loan.getLoanTransactions().stream().filter(lt -> !lt.isReversed()) //
.filter(lt -> !lt.isAccrual() && !lt.isAccrualActivity()) //
.filter(lt -> omitDisbursements(lt, refundFinal)) //
.map(lt -> calculateReducedAmountDisbursements(lt, refundFinal)).toList();

List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>(
loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList());

ProgressiveLoanInterestScheduleModel modelAfter = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
transactionsToReprocess, loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges()).getRight();
BigDecimal payableInterest = BigDecimal.ZERO;
if (modelAfter != null && loan.getStatus().isActive()) {
LoanRepaymentScheduleInstallment actualInstallment = loan.getRelatedRepaymentScheduleInstallment(relatedRefundTransactionDate);
if (actualInstallment == null) {
actualInstallment = loan.getLastLoanRepaymentScheduleInstallment();
}
payableInterest = emiCalculator.getPayableDetails(modelAfter, actualInstallment.getDueDate(), relatedRefundTransactionDate)
.getPayableInterest().getAmount();
if (loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isDisbursement)) {
List<LoanTransaction> transactionsToReprocess = new ArrayList<>();
loan.getLoanTransactions().stream().filter(lt -> !lt.isReversed()) //
.filter(lt -> !lt.isAccrual() && !lt.isAccrualActivity()) //
.forEach(lt -> simulateRepaymentForDisbursements(lt, refundFinal, transactionsToReprocess)); //

List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>(
loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList());

ProgressiveLoanInterestScheduleModel modelAfter = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
transactionsToReprocess, loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges()).getRight();

payableInterest = installmentsToReprocess.stream() //
.map(installment -> emiCalculator //
.getPayableDetails(modelAfter, installment.getDueDate(), relatedRefundTransactionDate) //
.getPayableInterest() //
.getAmount()) //
.reduce(BigDecimal.ZERO, BigDecimal::add); //
}
BigDecimal paidInterest = installmentsToReprocess.stream().map(i -> i.getInterestPaid(loan.getCurrency())).filter(Objects::nonNull)
.map(Money::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
return payableInterest.add(paidInterest);
return payableInterest;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1296,4 +1296,10 @@ public static class FuturePaymentAllocationRule {

public static final String LAST_INSTALLMENT = "LAST_INSTALLMENT";
}

public static class SupportedInterestRefundTypesItem {

public static final String MERCHANT_ISSUED_REFUND = "MERCHANT_ISSUED_REFUND";
public static final String PAYOUT_REFUND = "PAYOUT_REFUND";
}
}
Loading

0 comments on commit 01932b5

Please sign in to comment.