FINERACT-1981: pay-off transaction for progressive loans

This commit is contained in:
Kristof Jozsa
2024-08-21 17:55:18 +02:00
committed by Adam Saghy
parent 721ceaa32c
commit 7304af57bc
11 changed files with 371 additions and 43 deletions
@@ -250,7 +250,7 @@ public class LoanSummaryData {
return BigDecimal.ZERO;
}
private static BigDecimal computeAccruedInterestTillDay(final LoanSchedulePeriodData period, final long untilDay,
public static BigDecimal computeAccruedInterestTillDay(final LoanSchedulePeriodData period, final long untilDay,
final CurrencyData currency) {
Integer remainingDays = period.getDaysInPeriod();
BigDecimal totalAccruedInterest = BigDecimal.ZERO;
@@ -0,0 +1,65 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.data;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@Data
@Accessors(chain = true, fluent = true)
public class OutstandingAmountsDTO {
private Money principal;
private Money interest;
private Money feeCharges;
private Money penaltyCharges;
public OutstandingAmountsDTO(MonetaryCurrency currency) {
this.principal = Money.zero(currency);
this.interest = Money.zero(currency);
this.feeCharges = Money.zero(currency);
this.penaltyCharges = Money.zero(currency);
}
public Money getTotalOutstanding() {
return principal() //
.plus(interest()) //
.plus(feeCharges()) //
.plus(penaltyCharges());
}
public void plusPrincipal(Money principal) {
this.principal = this.principal.plus(principal);
}
public void plusInterest(Money interest) {
this.interest = this.interest.plus(interest);
}
public void plusFeeCharges(Money feeCharges) {
this.feeCharges = this.feeCharges.plus(feeCharges);
}
public void plusPenaltyCharges(Money penaltyCharges) {
this.penaltyCharges = this.penaltyCharges.plus(penaltyCharges);
}
}
@@ -109,6 +109,7 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
@@ -4368,8 +4369,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
}
public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
LoanRepaymentScheduleInstallment installment;
public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
OutstandingAmountsDTO outstandingAmounts;
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
final MathContext mc = MoneyHelper.getMathContext();
@@ -4381,12 +4382,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
} else {
installment = this.getTotalOutstandingOnLoan();
outstandingAmounts = this.getTotalOutstandingOnLoan();
}
return installment;
return outstandingAmounts;
}
public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
@@ -4460,11 +4461,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return annualNominalInterestRate;
}
private LoanRepaymentScheduleInstallment getTotalOutstandingOnLoan() {
Money feeCharges = Money.zero(loanCurrency());
Money penaltyCharges = Money.zero(loanCurrency());
private OutstandingAmountsDTO getTotalOutstandingOnLoan() {
Money totalPrincipal = Money.zero(loanCurrency());
Money totalInterest = Money.zero(loanCurrency());
Money feeCharges = Money.zero(loanCurrency());
Money penaltyCharges = Money.zero(loanCurrency());
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
@@ -4473,9 +4474,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loanCurrency()));
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency()));
}
LocalDate businessDate = DateUtils.getBusinessLocalDate();
return new LoanRepaymentScheduleInstallment(null, 0, businessDate, businessDate, totalPrincipal.getAmount(),
totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails);
return new OutstandingAmountsDTO(totalPrincipal.getCurrency()).principal(totalPrincipal).interest(totalInterest)
.feeCharges(feeCharges).penaltyCharges(penaltyCharges);
}
public LocalDate fetchInterestRecalculateFromDate() {
@@ -583,20 +583,20 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
return interestPortionOfTransaction;
}
public Money payPrincipalComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
public Money payPrincipalComponent(final LocalDate transactionDate, final Money transactionAmount) {
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
final MonetaryCurrency currency = transactionAmount.getCurrency();
Money principalPortionOfTransaction = Money.zero(currency);
if (transactionAmountRemaining.isZero()) {
if (transactionAmount.isZero()) {
return principalPortionOfTransaction;
}
final Money principalDue = getPrincipalOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) {
if (transactionAmount.isGreaterThanOrEqualTo(principalDue)) {
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
} else {
this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmountRemaining).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining);
this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmount).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmount);
}
this.principalCompleted = defaultToNullIfZero(this.principalCompleted);
@@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
@@ -2775,7 +2776,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
* Method returns the amount payable to close the loan account as of today.
*/
@Override
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final MonetaryCurrency currency, final LocalDate onDate,
public OutstandingAmountsDTO calculatePrepaymentAmount(final MonetaryCurrency currency, final LocalDate onDate,
final LoanApplicationTerms loanApplicationTerms, final MathContext mc, Loan loan, final HolidayDetailDTO holidayDetailDTO,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
@@ -2790,10 +2791,10 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
Money feeCharges = Money.zero(currency);
Money penaltyCharges = Money.zero(currency);
Money totalPrincipal = Money.zero(currency);
Money totalInterest = Money.zero(currency);
Money feeCharges = Money.zero(currency);
Money penaltyCharges = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment currentInstallment : loanScheduleDTO.getInstallments()) {
if (currentInstallment.isNotFullyPaidOff()) {
totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
@@ -2802,8 +2803,10 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
}
}
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(),
feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails);
return new OutstandingAmountsDTO(currency) //
.principal(totalPrincipal) //
.interest(totalInterest) //
.feeCharges(feeCharges) //
.penaltyCharges(penaltyCharges);
}
}
@@ -23,9 +23,9 @@ import java.time.LocalDate;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
@@ -38,8 +38,8 @@ public interface LoanScheduleGenerator {
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
LocalDate rescheduleFrom);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms,
MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
}