|
39 | 39 | import org.apache.fineract.infrastructure.core.service.DateUtils; |
40 | 40 | import org.apache.fineract.infrastructure.core.service.MathUtil; |
41 | 41 | import org.apache.fineract.organisation.monetary.data.CurrencyData; |
| 42 | +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; |
42 | 43 | import org.apache.fineract.organisation.monetary.domain.Money; |
43 | 44 | import org.apache.fineract.portfolio.common.domain.DaysInMonthType; |
44 | 45 | import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType; |
|
47 | 48 | import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; |
48 | 49 | import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; |
49 | 50 | import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; |
| 51 | +import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType; |
| 52 | +import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; |
50 | 53 | import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; |
51 | 54 | import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; |
52 | 55 | import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator; |
53 | 56 | import org.apache.fineract.portfolio.loanproduct.calc.data.EmiAdjustment; |
54 | 57 | import org.apache.fineract.portfolio.loanproduct.calc.data.EmiChangeOperation; |
| 58 | +import org.apache.fineract.portfolio.loanproduct.calc.data.EqualAmortizationValues; |
55 | 59 | import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod; |
56 | 60 | import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails; |
57 | 61 | import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails; |
@@ -1584,4 +1588,185 @@ private long getUncountablePeriods(final List<RepaymentPeriod> relatedRepaymentP |
1584 | 1588 | .filter(repaymentPeriod -> originalEmi.isLessThan(repaymentPeriod.getTotalPaidAmount())) // |
1585 | 1589 | .count(); // |
1586 | 1590 | } |
| 1591 | + |
| 1592 | + private void accelerateRepaymentDueDateTo(ProgressiveLoanInterestScheduleModel interestSchedule, RepaymentPeriod repaymentPeriod, |
| 1593 | + LocalDate transactionDate) { |
| 1594 | + repaymentPeriod.setDueDate(transactionDate); |
| 1595 | + repaymentPeriod.getInterestPeriods().getLast().setDueDate(transactionDate); |
| 1596 | + calculateRateFactorForRepaymentPeriod(repaymentPeriod, interestSchedule); |
| 1597 | + } |
| 1598 | + |
| 1599 | + private void accelerateMaturityDateTo(ProgressiveLoanInterestScheduleModel interestSchedule, LocalDate transactionDate) { |
| 1600 | + Optional<RepaymentPeriod> repaymentPeriod = interestSchedule.findRepaymentPeriod(transactionDate); |
| 1601 | + if (repaymentPeriod.isPresent()) { |
| 1602 | + if (!repaymentPeriod.get().getDueDate().isEqual(transactionDate)) { |
| 1603 | + accelerateRepaymentDueDateTo(interestSchedule, repaymentPeriod.get(), transactionDate); |
| 1604 | + } |
| 1605 | + if (!interestSchedule.isLastRepaymentPeriod(repaymentPeriod.get())) { |
| 1606 | + interestSchedule.repaymentPeriods().removeIf(rp -> rp.getDueDate().isAfter(transactionDate)); |
| 1607 | + } |
| 1608 | + } |
| 1609 | + } |
| 1610 | + |
| 1611 | + private void updateEMIForReAgeEqualAmortization(List<RepaymentPeriod> repaymentPeriods, Money principal, Money interest, |
| 1612 | + Money feesPenaltiesOutstanding, EqualAmortizationValues feesPenaltiesEqualAmortizationValues, MonetaryCurrency currency) { |
| 1613 | + EqualAmortizationValues interestEAV = calculateEqualAmortizationValues(interest, repaymentPeriods.size(), null, currency); |
| 1614 | + EqualAmortizationValues principalEAV = calculateAdjustedEqualAmortizationValues(principal, |
| 1615 | + principal.add(interest).add(feesPenaltiesOutstanding), |
| 1616 | + interestEAV.value().add(feesPenaltiesEqualAmortizationValues.value()), repaymentPeriods.size(), null, currency); |
| 1617 | + RepaymentPeriod last = repaymentPeriods.getLast(); |
| 1618 | + EqualAmortizationValues emiAEV = principalEAV.add(interestEAV); |
| 1619 | + repaymentPeriods.forEach(rp -> { |
| 1620 | + boolean isLast = last.equals(rp); |
| 1621 | + rp.setReAgedInterest(interestEAV.calculateValue(isLast)); |
| 1622 | + Money emi = emiAEV.calculateValue(isLast); |
| 1623 | + rp.setEmi(emi); |
| 1624 | + rp.setOriginalEmi(emi); |
| 1625 | + }); |
| 1626 | + } |
| 1627 | + |
| 1628 | + @Override |
| 1629 | + public OutstandingDetails precalculateReAgeEqualAmortizationAmount(ProgressiveLoanInterestScheduleModel interestSchedule, |
| 1630 | + LocalDate transactionDate, LoanReAgeParameter reageParameter) { |
| 1631 | + return getOutstandingAmountsTillDate(interestSchedule, |
| 1632 | + reageParameter.getInterestHandlingType().equals(LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST) |
| 1633 | + ? transactionDate |
| 1634 | + : interestSchedule.getMaturityDate()); |
| 1635 | + } |
| 1636 | + |
| 1637 | + @Override |
| 1638 | + public void reAgeEqualAmortization(ProgressiveLoanInterestScheduleModel interestSchedule, LocalDate transactionDate, |
| 1639 | + LoanReAgeParameter reageParameter, Money feesPenaltiesOutstanding, |
| 1640 | + EqualAmortizationValues feesPenaltiesEqualAmortizationValues) { |
| 1641 | + LocalDate originalMaturityDate = interestSchedule.getMaturityDate(); |
| 1642 | + boolean isAfterOriginalMaturityDate = transactionDate.isAfter(originalMaturityDate); |
| 1643 | + List<RepaymentPeriod> reAgedRepaymentPeriods = new ArrayList<>(reageParameter.getNumberOfInstallments()); |
| 1644 | + OutstandingDetails reAgeingAmounts = precalculateReAgeEqualAmortizationAmount(interestSchedule, transactionDate, reageParameter); |
| 1645 | + |
| 1646 | + // calculate already paid balances from transaction date |
| 1647 | + OutstandingDetails paidBalancesFromTransactionDate = calculatePaidBalancesAfterDate(interestSchedule, transactionDate); |
| 1648 | + |
| 1649 | + // set maturity date to transaction date and remove all repayment periods after it. |
| 1650 | + accelerateMaturityDateTo(interestSchedule, transactionDate); |
| 1651 | + |
| 1652 | + // close all open repayment period while keep paid amounts |
| 1653 | + interestSchedule.repaymentPeriods().forEach(rp -> { |
| 1654 | + rp.getInterestPeriods().getLast() |
| 1655 | + .addCreditedInterestAmount(MathUtil.min(rp.getOutstandingInterest(), rp.getCreditedInterest(), false).negated()); |
| 1656 | + rp.setEmi(rp.getTotalPaidAmount()); |
| 1657 | + rp.setOutstandingMovedDueToReAging(true); |
| 1658 | + }); |
| 1659 | + |
| 1660 | + // stop calculate unrecognised interest at this point because all |
| 1661 | + interestSchedule.getLastRepaymentPeriod().setNoUnrecognisedInterest(true); |
| 1662 | + |
| 1663 | + if (!paidBalancesFromTransactionDate.getOutstandingInterest().isZero() |
| 1664 | + || !paidBalancesFromTransactionDate.getOutstandingPrincipal().isZero()) { |
| 1665 | + createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(interestSchedule, |
| 1666 | + paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest()); |
| 1667 | + } |
| 1668 | + |
| 1669 | + updateModelForReageEqualAmortization(interestSchedule, reageParameter, reAgedRepaymentPeriods, isAfterOriginalMaturityDate); |
| 1670 | + |
| 1671 | + updateEMIForReAgeEqualAmortization(reAgedRepaymentPeriods, reAgeingAmounts.getOutstandingPrincipal(), |
| 1672 | + reAgeingAmounts.getOutstandingInterest(), feesPenaltiesOutstanding, feesPenaltiesEqualAmortizationValues, |
| 1673 | + interestSchedule.zero().getCurrency()); |
| 1674 | + |
| 1675 | + calculateOutstandingBalance(interestSchedule); |
| 1676 | + |
| 1677 | + LocalDate zeroInterestFrom = DateUtils.min(transactionDate, originalMaturityDate); |
| 1678 | + interestSchedule.addInterestRate(zeroInterestFrom, BigDecimal.ZERO); |
| 1679 | + |
| 1680 | + calculateLastUnpaidRepaymentPeriodEMI(interestSchedule, transactionDate); |
| 1681 | + |
| 1682 | + } |
| 1683 | + |
| 1684 | + private void updateModelForReageEqualAmortization(ProgressiveLoanInterestScheduleModel interestSchedule, |
| 1685 | + LoanReAgeParameter reageParameter, List<RepaymentPeriod> reAgedRepaymentPeriods, boolean isAfterOriginalMaturityDate) { |
| 1686 | + int numberOfInstallmentsToAdd = reageParameter.getNumberOfInstallments(); |
| 1687 | + LocalDate toDate = reageParameter.getStartDate(); |
| 1688 | + RepaymentPeriod previous = interestSchedule.getLastRepaymentPeriod(); |
| 1689 | + int frequency = reageParameter.getFrequencyNumber(); |
| 1690 | + PeriodFrequencyType frequencyType = reageParameter.getFrequencyType(); |
| 1691 | + |
| 1692 | + if (!isAfterOriginalMaturityDate) { |
| 1693 | + // merge first reaged period |
| 1694 | + RepaymentPeriod firstReAgedPeriod = interestSchedule.getLastRepaymentPeriod(); |
| 1695 | + firstReAgedPeriod.setDueDate(toDate); |
| 1696 | + firstReAgedPeriod.getLastInterestPeriod().setDueDate(toDate); |
| 1697 | + firstReAgedPeriod.setReAged(true); |
| 1698 | + firstReAgedPeriod.getPrevious().ifPresent(prev -> prev.setNoUnrecognisedInterest(true)); |
| 1699 | + reAgedRepaymentPeriods.add(firstReAgedPeriod); |
| 1700 | + |
| 1701 | + // update params for next reage repayment period calculation |
| 1702 | + numberOfInstallmentsToAdd--; |
| 1703 | + toDate = scheduledDateGenerator.getRepaymentPeriodDate(frequencyType, frequency, toDate); |
| 1704 | + } |
| 1705 | + |
| 1706 | + // insert new reaged repayment periods |
| 1707 | + for (int i = 0; i < numberOfInstallmentsToAdd; i++) { |
| 1708 | + RepaymentPeriod repaymentPeriod = RepaymentPeriod.create(previous, previous.getDueDate(), toDate, interestSchedule.zero(), |
| 1709 | + previous.getMc(), previous.getLoanProductRelatedDetail()); |
| 1710 | + repaymentPeriod.setTotalCapitalizedIncomeAmount(previous.getTotalCapitalizedIncomeAmount()); |
| 1711 | + repaymentPeriod.setTotalDisbursedAmount(previous.getTotalDisbursedAmount()); |
| 1712 | + repaymentPeriod.setReAged(true); |
| 1713 | + interestSchedule.repaymentPeriods().add(repaymentPeriod); |
| 1714 | + reAgedRepaymentPeriods.add(repaymentPeriod); |
| 1715 | + previous = repaymentPeriod; |
| 1716 | + toDate = scheduledDateGenerator.getRepaymentPeriodDate(frequencyType, frequency, toDate); |
| 1717 | + } |
| 1718 | + } |
| 1719 | + |
| 1720 | + private void createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(ProgressiveLoanInterestScheduleModel interestSchedule, |
| 1721 | + Money totalPaidPrincipal, Money totalPaidInterest) { |
| 1722 | + RepaymentPeriod targetPeriod = interestSchedule.getLastRepaymentPeriod(); |
| 1723 | + |
| 1724 | + Money paidInterestToAdd = totalPaidInterest.minus(targetPeriod.getPaidInterest()); |
| 1725 | + Money paidPrincipalToAdd = totalPaidPrincipal.minus(targetPeriod.getPaidPrincipal()); |
| 1726 | + targetPeriod.addPaidInterestAmount(paidInterestToAdd); |
| 1727 | + targetPeriod.addPaidPrincipalAmount(paidPrincipalToAdd); |
| 1728 | + targetPeriod.setEmi(targetPeriod.getTotalPaidAmount()); |
| 1729 | + targetPeriod.setReAged(true); |
| 1730 | + targetPeriod.setReAgedEarlyRepaymentHolder(true); |
| 1731 | + |
| 1732 | + RepaymentPeriod repaymentPeriodToInsert = RepaymentPeriod.create(targetPeriod, targetPeriod.getDueDate(), |
| 1733 | + interestSchedule.getMaturityDate(), interestSchedule.zero(), interestSchedule.mc(), |
| 1734 | + interestSchedule.loanProductRelatedDetail()); |
| 1735 | + repaymentPeriodToInsert.setReAged(true); |
| 1736 | + interestSchedule.repaymentPeriods().add(repaymentPeriodToInsert); |
| 1737 | + } |
| 1738 | + |
| 1739 | + private OutstandingDetails calculatePaidBalancesAfterDate(ProgressiveLoanInterestScheduleModel interestSchedule, |
| 1740 | + LocalDate transactionDate) { |
| 1741 | + Money principal = interestSchedule.repaymentPeriods().stream().filter(rp -> !rp.getDueDate().isBefore(transactionDate)) |
| 1742 | + .map(RepaymentPeriod::getPaidPrincipal).reduce(interestSchedule.zero(), Money::add); |
| 1743 | + Money interest = interestSchedule.repaymentPeriods().stream().filter(rp -> !rp.getDueDate().isBefore(transactionDate)) |
| 1744 | + .map(RepaymentPeriod::getPaidInterest).reduce(interestSchedule.zero(), Money::add); |
| 1745 | + return new OutstandingDetails(principal, interest); |
| 1746 | + } |
| 1747 | + |
| 1748 | + @Override |
| 1749 | + public EqualAmortizationValues calculateEqualAmortizationValues(Money totalOutstanding, Integer numberOfInstallments, |
| 1750 | + Integer installmentAmountInMultiplesOf, MonetaryCurrency currency) { |
| 1751 | + if (totalOutstanding.isGreaterThanZero()) { |
| 1752 | + Money equalMonthlyValue = totalOutstanding.dividedBy(numberOfInstallments, totalOutstanding.getMc()); |
| 1753 | + if (installmentAmountInMultiplesOf != null) { |
| 1754 | + equalMonthlyValue = Money.roundToMultiplesOf(equalMonthlyValue, installmentAmountInMultiplesOf); |
| 1755 | + } |
| 1756 | + Money adjustmentForLastInstallment = totalOutstanding.minus(equalMonthlyValue.multipliedBy(numberOfInstallments)); |
| 1757 | + return new EqualAmortizationValues(equalMonthlyValue, adjustmentForLastInstallment); |
| 1758 | + } |
| 1759 | + return new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)); |
| 1760 | + } |
| 1761 | + |
| 1762 | + @Override |
| 1763 | + public EqualAmortizationValues calculateAdjustedEqualAmortizationValues(Money outstanding, Money total, |
| 1764 | + Money sumOfOtherEqualAmortizationValues, Integer numberOfInstallments, Integer installmentAmountInMultiplesOf, |
| 1765 | + MonetaryCurrency currency) { |
| 1766 | + EqualAmortizationValues calculatedEMI = calculateEqualAmortizationValues(total, numberOfInstallments, |
| 1767 | + installmentAmountInMultiplesOf, currency); |
| 1768 | + Money value = calculatedEMI.value().minus(sumOfOtherEqualAmortizationValues); |
| 1769 | + Money adjust = outstanding.minus(value.multipliedBy(numberOfInstallments)); |
| 1770 | + return new EqualAmortizationValues(value, adjust); |
| 1771 | + } |
1587 | 1772 | } |
0 commit comments