Skip to content

Commit

Permalink
FINERACT-1981: revise last installment payment strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsaghy authored and kjozsa committed Oct 30, 2024
1 parent 94360c9 commit 36bd4c3
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 1 deletion.
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,13 +136,23 @@ 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));
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
LocalDate balanceCorrectionDate = transactionDate;
if (repaymentPeriodDueDate.isBefore(transactionDate)) {
// If it is paid late, we need to calculate with the period due date
balanceCorrectionDate = repaymentPeriodDueDate;
}
addBalanceCorrection(scheduleModel, balanceCorrectionDate, principalAmount.negated());

repaymentPeriod.ifPresent(rp -> {
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
// fully paid
if (rp.getTotalPaidAmount().compareTo(rp.getEmi()) > 0) {
rp.setEmi(rp.getTotalPaidAmount());
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
}
});
}

@Override
Expand Down Expand Up @@ -173,6 +183,11 @@ public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleMod
calculateOutstandingBalance(scheduleModelCopy);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);

boolean multiplePeriodIsUnpaid = scheduleModelCopy.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()).count() > 1L;
if (multiplePeriodIsUnpaid && !targetDate.isAfter(repaymentPeriod.getFromDate())) {
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
}

return new PayableDetails(repaymentPeriod.getEmi(), repaymentPeriod.getDuePrincipal(), repaymentPeriod.getDueInterest(),
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc));
}
Expand Down Expand Up @@ -245,6 +260,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 +281,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 +418,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 36bd4c3

Please sign in to comment.