FINERACT-2281: Interest Activities Not Posting For Loan With Repayment Reversal
This commit is contained in:
+106
-6
@@ -438,8 +438,108 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
processCreditTransaction(loanTransaction, ctx);
|
||||
}
|
||||
|
||||
protected void handleCreditBalanceRefund(LoanTransaction transaction, TransactionCtx ctx) {
|
||||
super.handleCreditBalanceRefund(transaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder());
|
||||
protected void handleCreditBalanceRefund(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||
MonetaryCurrency currency = ctx.getCurrency();
|
||||
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||
MoneyHolder overpaymentHolder = ctx.getOverpaymentHolder();
|
||||
|
||||
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
|
||||
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
var model = progressiveTransactionCtx.getModel();
|
||||
|
||||
// Copy and paste Logic from super.handleCreditBalanceRefund
|
||||
loanTransaction.resetDerivedComponents();
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
||||
List<LoanRepaymentScheduleInstallment> installmentToBeProcessed = installments.stream().filter(i -> !i.isDownPayment())
|
||||
.sorted(byDate).toList();
|
||||
final Money zeroMoney = Money.zero(currency);
|
||||
Money transactionAmount = loanTransaction.getAmount(currency);
|
||||
Money principalPortion = MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
|
||||
Money repaidAmount = MathUtil.negativeToZero(transactionAmount.minus(principalPortion));
|
||||
loanTransaction.setOverPayments(repaidAmount);
|
||||
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(repaidAmount));
|
||||
loanTransaction.updateComponents(principalPortion, zeroMoney, zeroMoney, zeroMoney);
|
||||
|
||||
if (principalPortion.isGreaterThanZero()) {
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
boolean loanTransactionMapped = false;
|
||||
LocalDate pastDueDate = null;
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installmentToBeProcessed) {
|
||||
pastDueDate = currentInstallment.getDueDate();
|
||||
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
||||
|
||||
emiCalculator.creditPrincipal(model, transactionDate, transactionAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
emiCalculator.payPrincipal(model, currentInstallment.getDueDate(), transactionDate, repaidAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
|
||||
// If already exists an additional installment just update the due date and
|
||||
// principal from the Loan chargeback / CBR transaction
|
||||
} else if (currentInstallment.isAdditional()) {
|
||||
if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) {
|
||||
currentInstallment.updateDueDate(transactionDate);
|
||||
}
|
||||
|
||||
currentInstallment.updateCredits(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// New installment will be added (N+1 scenario)
|
||||
if (!loanTransactionMapped) {
|
||||
if (transactionDate.equals(pastDueDate)) {
|
||||
// Transaction is on Maturity date, no additional installment is needed
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed
|
||||
.get(installmentToBeProcessed.size() - 1);
|
||||
|
||||
emiCalculator.creditPrincipal(model, transactionDate, transactionAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
emiCalculator.payPrincipal(model, currentInstallment.getDueDate(), transactionDate, repaidAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
} else {
|
||||
// transaction is after maturity date, create an additional installment
|
||||
Loan loan = loanTransaction.getLoan();
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, (installments.size() + 1),
|
||||
pastDueDate, transactionDate, transactionAmount.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(),
|
||||
zeroMoney.getAmount(), false, null);
|
||||
installment.markAsAdditional();
|
||||
installment.addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
installment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, installment,
|
||||
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||
}
|
||||
} else {
|
||||
super.handleCreditBalanceRefund(loanTransaction, currency, installments, overpaymentHolder);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasNoCustomCreditAllocationRule(LoanTransaction loanTransaction) {
|
||||
@@ -556,11 +656,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
// handle charge back before or on last installments due date
|
||||
|
||||
if (chargebackAllocation.get(PRINCIPAL).isGreaterThanZero()) {
|
||||
emiCalculator.chargebackPrincipal(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(PRINCIPAL));
|
||||
emiCalculator.creditPrincipal(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(PRINCIPAL));
|
||||
}
|
||||
|
||||
if (chargebackAllocation.get(INTEREST).isGreaterThanZero()) {
|
||||
emiCalculator.chargebackInterest(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(INTEREST));
|
||||
emiCalculator.creditInterest(model, loanTransaction.getTransactionDate(), chargebackAllocation.get(INTEREST));
|
||||
}
|
||||
|
||||
// update repayment periods until maturity date, for principal and interest portions
|
||||
@@ -2171,8 +2271,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
if (installment != null) {
|
||||
installment.updatePrincipal(rm.getDuePrincipal().getAmount());
|
||||
installment.updateInterestCharged(rm.getDueInterest().getAmount());
|
||||
installment.setCreditedInterest(rm.getChargebackInterest().getAmount());
|
||||
installment.setCreditedPrincipal(rm.getChargebackPrincipal().getAmount());
|
||||
installment.setCreditedInterest(rm.getCreditedInterest().getAmount());
|
||||
installment.setCreditedPrincipal(rm.getCreditedPrincipal().getAmount());
|
||||
installment.updateObligationsMet(ctx.getCurrency(), loanTransaction.getTransactionDate());
|
||||
}
|
||||
});
|
||||
|
||||
+5
-6
@@ -93,17 +93,16 @@ public interface EMICalculator {
|
||||
Money principalAmount);
|
||||
|
||||
/**
|
||||
* This method used for charge back principal portion. This method increases the outstanding balance. This method
|
||||
* creates a calculated "virtual" EMI for the applied period.
|
||||
* This method used for credit principal portion. This method increases the outstanding balance. This method creates
|
||||
* a calculated "virtual" EMI for the applied period.
|
||||
*/
|
||||
void chargebackPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money chargebackPrincipalAmount);
|
||||
void creditPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money creditedPrincipalAmount);
|
||||
|
||||
/**
|
||||
* This method used for charge back interest portion. This method adds extra interest due. This method creates a
|
||||
* This method used for credit interest portion. This method adds extra interest due. This method creates a
|
||||
* calculated "virtual" EMI for the applied period.
|
||||
*/
|
||||
void chargebackInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money chargebackInterestAmount);
|
||||
void creditInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money creditedInterestAmount);
|
||||
|
||||
/**
|
||||
* This method gives back the maximum of the due principal and maximum of the due interest for a requested day.
|
||||
|
||||
+34
-36
@@ -193,31 +193,30 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
repaymentPeriod.ifPresent(rp -> {
|
||||
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
|
||||
// fully paid
|
||||
if (transactionDateIsBefore && rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusChargeback())) {
|
||||
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalChargebackAmount()));
|
||||
if (transactionDateIsBefore && rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmounts())) {
|
||||
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
|
||||
} else if (transactionDateIsBefore
|
||||
&& rp.getTotalPaidAmount().isEqualTo(rp.getOriginalEmi().add(rp.getTotalChargebackAmount()))) {
|
||||
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalChargebackAmount()));
|
||||
&& rp.getTotalPaidAmount().isEqualTo(rp.getOriginalEmi().add(rp.getTotalCreditedAmount()))) {
|
||||
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
|
||||
}
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addChargebackAmountsToInterestPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money chargebackPrincipalAmount, Money chargeBackInterestAmount) {
|
||||
scheduleModel.repaymentPeriods().stream().filter(checkRepaymentPeriodIsInChargebackRange(scheduleModel, transactionDate))
|
||||
.findFirst()
|
||||
private void addCreditedAmountsToInterestPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money creditedPrincipalAmount, Money creditedInterestAmount) {
|
||||
scheduleModel.repaymentPeriods().stream().filter(checkRepaymentPeriodIsInCreditRange(scheduleModel, transactionDate)).findFirst()
|
||||
.flatMap(repaymentPeriod -> repaymentPeriod.getInterestPeriods().stream()
|
||||
.filter(interestPeriod -> interestPeriod.getFromDate().equals(transactionDate)).reduce((v1, v2) -> v2))
|
||||
.ifPresent(interestPeriod -> {
|
||||
interestPeriod.addChargebackPrincipalAmount(chargebackPrincipalAmount);
|
||||
interestPeriod.addChargebackInterestAmount(chargeBackInterestAmount);
|
||||
interestPeriod.addCreditedPrincipalAmount(creditedPrincipalAmount);
|
||||
interestPeriod.addCreditedInterestAmount(creditedInterestAmount);
|
||||
});
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Predicate<RepaymentPeriod> checkRepaymentPeriodIsInChargebackRange(ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
private static Predicate<RepaymentPeriod> checkRepaymentPeriodIsInCreditRange(ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
LocalDate transactionDate) {
|
||||
return repaymentPeriod -> scheduleModel.isLastRepaymentPeriod(repaymentPeriod)
|
||||
? !transactionDate.isBefore(repaymentPeriod.getFromDate()) && !transactionDate.isAfter(repaymentPeriod.getDueDate())
|
||||
@@ -225,23 +224,22 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void chargebackPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money chargebackPrincipalAmount) {
|
||||
addChargeback(scheduleModel, transactionDate, chargebackPrincipalAmount, scheduleModel.zero());
|
||||
public void creditPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money creditedPrincipalAmount) {
|
||||
addCredit(scheduleModel, transactionDate, creditedPrincipalAmount, scheduleModel.zero());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void chargebackInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money chargebackInterestAmount) {
|
||||
addChargeback(scheduleModel, transactionDate, scheduleModel.zero(), chargebackInterestAmount);
|
||||
public void creditInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money creditedInterestAmount) {
|
||||
addCredit(scheduleModel, transactionDate, scheduleModel.zero(), creditedInterestAmount);
|
||||
}
|
||||
|
||||
private void addChargeback(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money chargebackPrincipalAmount, Money chargebackInterestAmount) {
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(transactionDate, scheduleModel.zero(), chargebackPrincipalAmount)
|
||||
private void addCredit(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money creditedPrincipalAmount,
|
||||
Money creditedInterestAmount) {
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(transactionDate, scheduleModel.zero(), creditedPrincipalAmount)
|
||||
.ifPresent(repaymentPeriod -> {
|
||||
addChargebackAmountsToInterestPeriod(scheduleModel, transactionDate, chargebackPrincipalAmount,
|
||||
chargebackInterestAmount);
|
||||
addCreditedAmountsToInterestPeriod(scheduleModel, transactionDate, creditedPrincipalAmount, creditedInterestAmount);
|
||||
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
@@ -288,12 +286,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
@Override
|
||||
@NotNull
|
||||
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
|
||||
@NotNull LocalDate targetDate, boolean includeChargebackInterest) {
|
||||
@NotNull LocalDate targetDate, boolean includeCreditedInterest) {
|
||||
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
|
||||
targetDate);
|
||||
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
|
||||
return includeChargebackInterest ? repaymentPeriod.getCalculatedDueInterest()
|
||||
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(),
|
||||
return includeCreditedInterest ? repaymentPeriod.getCalculatedDueInterest()
|
||||
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getCreditedInterest(),
|
||||
recalculatedScheduleModelTillDate.mc());
|
||||
}
|
||||
|
||||
@@ -379,18 +377,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
.size() > nextIdx;
|
||||
if (thereIsInterestPeriodFromDateOnTargetDate) {
|
||||
// NOTE: If there is a next interest period with fromDate on the target date
|
||||
// then the related chargeback amounts comes from the next interest period too.
|
||||
// then the related credited amount comes from the next interest period too.
|
||||
InterestPeriod nextInterestPeriod = ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx);
|
||||
ip.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
|
||||
ip.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
|
||||
ip.addCreditedPrincipalAmount(nextInterestPeriod.getCreditedPrincipal());
|
||||
ip.addCreditedInterestAmount(nextInterestPeriod.getCreditedInterest());
|
||||
}
|
||||
ip.getRepaymentPeriod().getInterestPeriods()
|
||||
.subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
|
||||
});
|
||||
} else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod)
|
||||
&& (rp.getInterestPeriods().get(0).getChargebackInterest().isGreaterThanZero()
|
||||
|| rp.getInterestPeriods().get(0).getChargebackPrincipal().isGreaterThanZero())) {
|
||||
// NOTE: we need to check whether there is chargeback on the 1st interest period of the next
|
||||
&& (rp.getInterestPeriods().get(0).getCreditedInterest().isGreaterThanZero()
|
||||
|| rp.getInterestPeriods().get(0).getCreditedPrincipal().isGreaterThanZero())) {
|
||||
// NOTE: we need to check whether there is credited on the 1st interest period of the next
|
||||
// period
|
||||
// if so, we need to retain that interest period, but need to update due date to match with from
|
||||
// date -> 0 interest
|
||||
@@ -438,12 +436,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
MathContext mc = scheduleModel.mc();
|
||||
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(scheduleModel.zero(),
|
||||
(m1, m2) -> m1.plus(m2, mc)); // 1.46
|
||||
Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmiPlusChargeback).reduce(scheduleModel.zero(),
|
||||
(m1, m2) -> m1.plus(m2, mc)); // 101.48
|
||||
Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmiPlusCreditedAmounts)
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 101.48
|
||||
Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)) // 100
|
||||
.plus(scheduleModel.getTotalChargebackPrincipal(), mc); //
|
||||
.plus(scheduleModel.getTotalCreditedPrincipal(), mc); //
|
||||
|
||||
Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
|
||||
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
|
||||
@@ -451,8 +449,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> {
|
||||
repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc));
|
||||
if (repaymentPeriod.getEmi()
|
||||
.isLessThan(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalChargebackAmount(), mc))) {
|
||||
repaymentPeriod.setEmi(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalChargebackAmount(), mc));
|
||||
.isLessThan(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(), mc))) {
|
||||
repaymentPeriod.setEmi(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(), mc));
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
}
|
||||
});
|
||||
|
||||
+16
-14
@@ -53,8 +53,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
@Setter
|
||||
private BigDecimal rateFactorTillPeriodDueDate;
|
||||
|
||||
private Money chargebackPrincipal;
|
||||
private Money chargebackInterest;
|
||||
/** Stores credited principals. Related transactions: Chargeback or Credit Balance Refound */
|
||||
private Money creditedPrincipal;
|
||||
/** Stores credited principals. Related transaction: Chargeback */
|
||||
private Money creditedInterest;
|
||||
|
||||
private Money disbursementAmount;
|
||||
private Money balanceCorrectionAmount;
|
||||
@@ -66,8 +68,8 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
|
||||
public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod, MathContext mc) {
|
||||
return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(),
|
||||
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getChargebackPrincipal(),
|
||||
interestPeriod.getChargebackInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
|
||||
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getCreditedPrincipal(),
|
||||
interestPeriod.getCreditedInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
|
||||
interestPeriod.getOutstandingLoanBalance(), mc, interestPeriod.isPaused());
|
||||
}
|
||||
|
||||
@@ -77,8 +79,8 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
|
||||
public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod) {
|
||||
return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(),
|
||||
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getChargebackPrincipal(),
|
||||
interestPeriod.getChargebackInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
|
||||
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getCreditedPrincipal(),
|
||||
interestPeriod.getCreditedInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
|
||||
interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(), interestPeriod.isPaused());
|
||||
}
|
||||
|
||||
@@ -116,17 +118,17 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount, mc);
|
||||
}
|
||||
|
||||
public void addChargebackPrincipalAmount(final Money chargebackPrincipal) {
|
||||
this.chargebackPrincipal = MathUtil.plus(this.chargebackPrincipal, chargebackPrincipal, mc);
|
||||
public void addCreditedPrincipalAmount(final Money creditedPrincipal) {
|
||||
this.creditedPrincipal = MathUtil.plus(this.creditedPrincipal, creditedPrincipal, mc);
|
||||
}
|
||||
|
||||
public void addChargebackInterestAmount(final Money chargebackInterest) {
|
||||
this.chargebackInterest = MathUtil.plus(this.chargebackInterest, chargebackInterest, mc);
|
||||
public void addCreditedInterestAmount(final Money creditedInterest) {
|
||||
this.creditedInterest = MathUtil.plus(this.creditedInterest, creditedInterest, mc);
|
||||
}
|
||||
|
||||
public BigDecimal getCalculatedDueInterest() {
|
||||
if (isPaused) {
|
||||
return chargebackInterest.getAmount();
|
||||
return creditedInterest.getAmount();
|
||||
}
|
||||
|
||||
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
|
||||
@@ -136,7 +138,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
.multiply(getRateFactorTillPeriodDueDate(), mc) //
|
||||
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
|
||||
.multiply(BigDecimal.valueOf(getLength()), mc); //
|
||||
return MathUtil.negativeToZero(MathUtil.add(chargebackInterest.getAmount(), interestDueTillRepaymentDueDate, mc));
|
||||
return MathUtil.negativeToZero(MathUtil.add(creditedInterest.getAmount(), interestDueTillRepaymentDueDate, mc));
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
@@ -168,10 +170,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Include principal like amounts (all disbursement amount + chargeback principal)
|
||||
* Include principal like amounts (all disbursement amount + credited principal)
|
||||
*/
|
||||
public Money getCreditedAmounts() {
|
||||
return getDisbursementAmount().plus(getChargebackPrincipal(), mc);
|
||||
return getDisbursementAmount().plus(getCreditedPrincipal(), mc);
|
||||
}
|
||||
|
||||
public boolean isFirstInterestPeriod() {
|
||||
|
||||
+5
-6
@@ -304,8 +304,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back the total due interest amount in the whole repayment schedule. Also includes chargeback interest
|
||||
* amount.
|
||||
* Gives back the total due interest amount in the whole repayment schedule. Also includes credited interest amount.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -315,7 +314,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
|
||||
/**
|
||||
* Gives back the total due principal amount in the whole repayment schedule based on disbursements. Do not contain
|
||||
* chargeback principal amount.
|
||||
* credited principal amount.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -342,12 +341,12 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back the total chargeback principal amount in the whole repayment schedule.
|
||||
* Gives back the total credited principal amount in the whole repayment schedule.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getTotalChargebackPrincipal() {
|
||||
return MathUtil.negativeToZero(repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero, Money::plus),
|
||||
public Money getTotalCreditedPrincipal() {
|
||||
return MathUtil.negativeToZero(repaymentPeriods().stream().map(RepaymentPeriod::getCreditedPrincipal).reduce(zero, Money::plus),
|
||||
mc);
|
||||
}
|
||||
|
||||
|
||||
+24
-22
@@ -146,7 +146,7 @@ public final class RepaymentPeriod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back calculated due interest + chargeback interest
|
||||
* Gives back calculated due interest + credited interest
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -169,28 +169,29 @@ public final class RepaymentPeriod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back due interest + chargeback interest OR paid interest
|
||||
* Gives back due interest + credited interest OR paid interest
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getDueInterest() {
|
||||
if (dueInterestCalculation == null) {
|
||||
// Due interest might be the maximum paid if there is pay-off or early repayment
|
||||
dueInterestCalculation = Memo.of(
|
||||
() -> MathUtil.max(getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest()
|
||||
: MathUtil.min(getCalculatedDueInterest(), getEmiPlusChargeback(), false), getPaidInterest(), false),
|
||||
() -> new Object[] { paidPrincipal, paidInterest, interestPeriods });
|
||||
dueInterestCalculation = Memo
|
||||
.of(() -> MathUtil.max(
|
||||
getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest()
|
||||
: MathUtil.min(getCalculatedDueInterest(), getEmiPlusCreditedAmounts(), false),
|
||||
getPaidInterest(), false), () -> new Object[] { paidPrincipal, paidInterest, interestPeriods });
|
||||
}
|
||||
return dueInterestCalculation.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back an EMI amount which includes chargeback amounts as well
|
||||
* Gives back an EMI amount which includes credited amounts as well
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getEmiPlusChargeback() {
|
||||
return getEmi().plus(getTotalChargebackAmount(), mc); //
|
||||
public Money getEmiPlusCreditedAmounts() {
|
||||
return getEmi().plus(getTotalCreditedAmount(), mc); //
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,48 +200,49 @@ public final class RepaymentPeriod {
|
||||
* @return
|
||||
*/
|
||||
public Money getCalculatedDuePrincipal() {
|
||||
return MathUtil.negativeToZero(getEmiPlusChargeback().minus(getCalculatedDueInterest(), mc), mc);
|
||||
return MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getCalculatedDueInterest(), mc), mc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum of chargeback principals
|
||||
* Sum of credited principals
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getChargebackPrincipal() {
|
||||
public Money getCreditedPrincipal() {
|
||||
return MathUtil.negativeToZero(interestPeriods.stream() //
|
||||
.map(InterestPeriod::getChargebackPrincipal) //
|
||||
.map(InterestPeriod::getCreditedPrincipal) //
|
||||
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum of chargeback interests
|
||||
* Sum of credited interests
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getChargebackInterest() {
|
||||
public Money getCreditedInterest() {
|
||||
return MathUtil.negativeToZero(interestPeriods.stream() //
|
||||
.map(InterestPeriod::getChargebackInterest) //
|
||||
.map(InterestPeriod::getCreditedInterest) //
|
||||
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back due principal + chargeback principal or paid principal
|
||||
* Gives back due principal + credited principal or paid principal
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getDuePrincipal() {
|
||||
// Due principal might be the maximum paid if there is pay-off or early repayment
|
||||
return MathUtil.max(MathUtil.negativeToZero(getEmiPlusChargeback().minus(getDueInterest(), mc), mc), getPaidPrincipal(), false);
|
||||
return MathUtil.max(MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getDueInterest(), mc), mc), getPaidPrincipal(),
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back sum of all chargeback principal + chargeback interest
|
||||
* Gives back sum of all credited principal + credited interest
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getTotalChargebackAmount() {
|
||||
return getChargebackPrincipal().plus(getChargebackInterest(), mc);
|
||||
public Money getTotalCreditedAmount() {
|
||||
return getCreditedPrincipal().plus(getCreditedInterest(), mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,7 +255,7 @@ public final class RepaymentPeriod {
|
||||
}
|
||||
|
||||
public boolean isFullyPaid() {
|
||||
return getEmiPlusChargeback().isEqualTo(getTotalPaidAmount());
|
||||
return getEmiPlusCreditedAmounts().isEqualTo(getTotalPaidAmount());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+21
-21
@@ -1305,7 +1305,7 @@ class ProgressiveEMICalculatorTest {
|
||||
final LocalDate startDay = LocalDate.of(2024, 1, 1);
|
||||
|
||||
emiCalculator.payInterest(interestModel, dueDate, startDay.plusDays(3), toMoney(0.56));
|
||||
emiCalculator.chargebackInterest(interestModel, startDay.plusDays(3), toMoney(0.0));
|
||||
emiCalculator.creditInterest(interestModel, startDay.plusDays(3), toMoney(0.0));
|
||||
emiCalculator.addBalanceCorrection(interestModel, startDay.plusDays(3), toMoney(0.0));
|
||||
|
||||
checkDailyInterest(interestModel, dueDate, startDay, 1, 0.19, 0.19);
|
||||
@@ -2403,7 +2403,7 @@ class ProgressiveEMICalculatorTest {
|
||||
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49));
|
||||
|
||||
// full chargeback on duedate
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01));
|
||||
emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -2456,7 +2456,7 @@ class ProgressiveEMICalculatorTest {
|
||||
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49));
|
||||
|
||||
// partial chargeback
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(15.0));
|
||||
emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(15.0));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -2467,7 +2467,7 @@ class ProgressiveEMICalculatorTest {
|
||||
checkPeriod(interestSchedule, 5, 0, 17.09, 0.005833333333, 0.0991083333276, 0.1, 16.99, 0.0);
|
||||
|
||||
// full chargeback
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01));
|
||||
emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), toMoney(17.01));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -2520,7 +2520,7 @@ class ProgressiveEMICalculatorTest {
|
||||
emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1), toMoney(0.49));
|
||||
|
||||
// full chargeback on duedate
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, LocalDate.of(2024, 3, 15), toMoney(17.01));
|
||||
emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 3, 15), toMoney(17.01));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -2579,8 +2579,8 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
// chargeback
|
||||
txnDate = LocalDate.of(2024, 3, 1);
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -2637,8 +2637,8 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
// chargeback
|
||||
txnDate = LocalDate.of(2024, 7, 1);
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
|
||||
checkPeriod(interestSchedule, 5, 0, 17.00, 0.005833333333, 0.0985833333276, 0.59, 33.42, 0.0);
|
||||
}
|
||||
@@ -2689,8 +2689,8 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
// chargeback
|
||||
txnDate = LocalDate.of(2024, 3, 1);
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(14.51));
|
||||
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(14.51));
|
||||
emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -2747,7 +2747,7 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
// chargeback
|
||||
txnDate = LocalDate.of(2024, 3, 1);
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(15.0));
|
||||
emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(15.0));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -2804,10 +2804,10 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
// chargeback 1st
|
||||
txnDate = LocalDate.of(2024, 3, 1);
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(15.0));
|
||||
emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(15.0));
|
||||
// chargeback 2nd
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.5833333333, 0.58, 16.43, 83.57);
|
||||
@@ -3098,8 +3098,8 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
// chargeback
|
||||
txnDate = LocalDate.of(2024, 3, 15);
|
||||
emiCalculator.chargebackPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.chargebackInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
emiCalculator.creditPrincipal(interestSchedule, txnDate, toMoney(16.52));
|
||||
emiCalculator.creditInterest(interestSchedule, txnDate, toMoney(0.49));
|
||||
|
||||
PeriodDueDetails dueAmounts = emiCalculator.getDueAmounts(interestSchedule, LocalDate.of(2024, 4, 1), txnDate);
|
||||
Assertions.assertEquals(33.09, toDouble(interestSchedule.repaymentPeriods().get(2).getDuePrincipal()));
|
||||
@@ -3172,10 +3172,10 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
// chargeback
|
||||
txnDate = LocalDate.of(2024, 3, 15);
|
||||
emiCalculator.chargebackPrincipal(interestScheduleExpected, txnDate, toMoney(16.52));
|
||||
emiCalculator.chargebackInterest(interestScheduleExpected, txnDate, toMoney(0.49));
|
||||
emiCalculator.chargebackPrincipal(interestScheduleActual, txnDate, toMoney(16.52));
|
||||
emiCalculator.chargebackInterest(interestScheduleActual, txnDate, toMoney(0.49));
|
||||
emiCalculator.creditPrincipal(interestScheduleExpected, txnDate, toMoney(16.52));
|
||||
emiCalculator.creditInterest(interestScheduleExpected, txnDate, toMoney(0.49));
|
||||
emiCalculator.creditPrincipal(interestScheduleActual, txnDate, toMoney(16.52));
|
||||
emiCalculator.creditInterest(interestScheduleActual, txnDate, toMoney(0.49));
|
||||
|
||||
verifyAllPeriods(interestScheduleExpected, interestScheduleActual);
|
||||
interestScheduleActual = copyJson(interestScheduleExpected);
|
||||
|
||||
Reference in New Issue
Block a user