Skip to content

Commit

Permalink
FINERACT-1981: revise last installment payment calculation strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsaghy authored and kjozsa committed Oct 29, 2024
1 parent 94360c9 commit 2060061
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.function.Consumer;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;

Expand Down Expand Up @@ -196,4 +197,12 @@ void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate
public Money getZero() {
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
}

public Money getTotalOutstandingBalance() {
Money totalDisbursedAmount = repaymentPeriods.stream()
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount)).reduce(getZero(), Money::plus);
Money totalPrincipalPaidAmount = repaymentPeriods.stream().map(RepaymentPeriod::getPaidPrincipal).reduce(getZero(), Money::plus);

return MathUtil.negativeToZero(totalDisbursedAmount.minus(totalPrincipalPaidAmount, mc));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class RepaymentPeriod {
@Setter
@Getter
private Money emi;
@Setter
@Getter
private Money originalEmi;
@Getter
private Money paidPrincipal;
@Getter
Expand All @@ -62,6 +65,7 @@ public RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate d
this.fromDate = fromDate;
this.dueDate = dueDate;
this.emi = emi;
this.originalEmi = emi;
this.mc = mc;
this.interestPeriods = new ArrayList<>();
// There is always at least 1 interest period, by default with same from-due date as repayment period
Expand All @@ -76,6 +80,7 @@ public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod
this.fromDate = repaymentPeriod.fromDate;
this.dueDate = repaymentPeriod.dueDate;
this.emi = repaymentPeriod.emi;
this.originalEmi = repaymentPeriod.originalEmi;
this.interestPeriods = new ArrayList<>();
this.paidPrincipal = repaymentPeriod.paidPrincipal;
this.paidInterest = repaymentPeriod.paidInterest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@ public void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, Loca
@Override
public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate,
LocalDate transactionDate, Money principalAmount) {
findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate).ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate).ifPresent(rp -> {
rp.addPaidPrincipalAmount(principalAmount);
// If more was paid then calculated EMI, go with the original EMI instead
rp.setEmi(rp.getPaidPrincipal().isGreaterThan(rp.getEmi()) ? rp.getOriginalEmi() : rp.getEmi());
});
LocalDate balanceCorrectionDate = transactionDate;
if (repaymentPeriodDueDate.isBefore(transactionDate)) {
// If it is paid late, we need to calculate with the period due date
Expand Down Expand Up @@ -173,6 +177,12 @@ public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleMod
calculateOutstandingBalance(scheduleModelCopy);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);

boolean multiplePeriodIsUnpaid = scheduleModelCopy.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()).count() > 1L;
// If there is multiple unpaid installment, we can pay the original EMI, if applicable
if (multiplePeriodIsUnpaid) {
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
}

return new PayableDetails(repaymentPeriod.getEmi(), repaymentPeriod.getDuePrincipal(), repaymentPeriod.getDueInterest(),
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc));
}
Expand Down Expand Up @@ -208,7 +218,11 @@ private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestSchedu
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 100

Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
// TODO: move to the right place
// Find last period, which is not fully paid or it was paid more principal then calculated emi -> get due
// principal + diff
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream()
.filter(rp -> !rp.isFullyPaid() && rp.getEmi().plus(diff).isGreaterThan(rp.getPaidPrincipal()))
.reduce((first, second) -> second);
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc)));
}
Expand Down Expand Up @@ -245,6 +259,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)
&& !adjustedEqualMonthlyInstallmentValue.isLessThan(period.getTotalPaidAmount())) {
period.setEmi(adjustedEqualMonthlyInstallmentValue);
period.setOriginalEmi(adjustedEqualMonthlyInstallmentValue);
}
});
calculateOutstandingBalance(newScheduleModel);
Expand All @@ -265,6 +280,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv
}
final RepaymentPeriod newRepaymentPeriod = relatedPeriodFromNewModelIterator.next();
relatedRepaymentPeriod.setEmi(newRepaymentPeriod.getEmi());
relatedRepaymentPeriod.setOriginalEmi(newRepaymentPeriod.getEmi());
});
calculateOutstandingBalance(scheduleModel);
}
Expand Down Expand Up @@ -401,6 +417,7 @@ void calculateEMIOnPeriods(final List<RepaymentPeriod> repaymentPeriods, final P
repaymentPeriods.forEach(period -> {
if (!finalEqualMonthlyInstallment.isLessThan(period.getTotalPaidAmount())) {
period.setEmi(finalEqualMonthlyInstallment);
period.setOriginalEmi(finalEqualMonthlyInstallment);
}
});
}
Expand Down

0 comments on commit 2060061

Please sign in to comment.