FINERACT-1981: pay-off transaction for progressive loans
This commit is contained in:
committed by
Adam Saghy
parent
721ceaa32c
commit
7304af57bc
+1
-1
@@ -250,7 +250,7 @@ public class LoanSummaryData {
|
|||||||
return BigDecimal.ZERO;
|
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) {
|
final CurrencyData currency) {
|
||||||
Integer remainingDays = period.getDaysInPeriod();
|
Integer remainingDays = period.getDaysInPeriod();
|
||||||
BigDecimal totalAccruedInterest = BigDecimal.ZERO;
|
BigDecimal totalAccruedInterest = BigDecimal.ZERO;
|
||||||
|
|||||||
+65
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+11
-11
@@ -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.DisbursementData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
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.data.ScheduleGeneratorDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||||
@@ -4368,8 +4369,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
|
loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
|
public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
|
||||||
LoanRepaymentScheduleInstallment installment;
|
OutstandingAmountsDTO outstandingAmounts;
|
||||||
|
|
||||||
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
|
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
|
||||||
final MathContext mc = MoneyHelper.getMathContext();
|
final MathContext mc = MoneyHelper.getMathContext();
|
||||||
@@ -4381,12 +4382,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
|
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||||
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
|
outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
|
||||||
scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
|
scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
|
||||||
} else {
|
} else {
|
||||||
installment = this.getTotalOutstandingOnLoan();
|
outstandingAmounts = this.getTotalOutstandingOnLoan();
|
||||||
}
|
}
|
||||||
return installment;
|
return outstandingAmounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||||
@@ -4460,11 +4461,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return annualNominalInterestRate;
|
return annualNominalInterestRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanRepaymentScheduleInstallment getTotalOutstandingOnLoan() {
|
private OutstandingAmountsDTO getTotalOutstandingOnLoan() {
|
||||||
Money feeCharges = Money.zero(loanCurrency());
|
|
||||||
Money penaltyCharges = Money.zero(loanCurrency());
|
|
||||||
Money totalPrincipal = Money.zero(loanCurrency());
|
Money totalPrincipal = Money.zero(loanCurrency());
|
||||||
Money totalInterest = Money.zero(loanCurrency());
|
Money totalInterest = Money.zero(loanCurrency());
|
||||||
|
Money feeCharges = Money.zero(loanCurrency());
|
||||||
|
Money penaltyCharges = Money.zero(loanCurrency());
|
||||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
||||||
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
|
||||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
|
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
|
||||||
@@ -4473,9 +4474,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loanCurrency()));
|
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loanCurrency()));
|
||||||
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency()));
|
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency()));
|
||||||
}
|
}
|
||||||
LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
return new OutstandingAmountsDTO(totalPrincipal.getCurrency()).principal(totalPrincipal).interest(totalInterest)
|
||||||
return new LoanRepaymentScheduleInstallment(null, 0, businessDate, businessDate, totalPrincipal.getAmount(),
|
.feeCharges(feeCharges).penaltyCharges(penaltyCharges);
|
||||||
totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalDate fetchInterestRecalculateFromDate() {
|
public LocalDate fetchInterestRecalculateFromDate() {
|
||||||
|
|||||||
+6
-6
@@ -583,20 +583,20 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
return interestPortionOfTransaction;
|
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);
|
Money principalPortionOfTransaction = Money.zero(currency);
|
||||||
if (transactionAmountRemaining.isZero()) {
|
if (transactionAmount.isZero()) {
|
||||||
return principalPortionOfTransaction;
|
return principalPortionOfTransaction;
|
||||||
}
|
}
|
||||||
final Money principalDue = getPrincipalOutstanding(currency);
|
final Money principalDue = getPrincipalOutstanding(currency);
|
||||||
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) {
|
if (transactionAmount.isGreaterThanOrEqualTo(principalDue)) {
|
||||||
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
|
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
|
||||||
principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
|
principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
|
||||||
} else {
|
} else {
|
||||||
this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmountRemaining).getAmount();
|
this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmount).getAmount();
|
||||||
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining);
|
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.principalCompleted = defaultToNullIfZero(this.principalCompleted);
|
this.principalCompleted = defaultToNullIfZero(this.principalCompleted);
|
||||||
|
|||||||
+9
-6
@@ -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.DisbursementData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
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.
|
* Method returns the amount payable to close the loan account as of today.
|
||||||
*/
|
*/
|
||||||
@Override
|
@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 LoanApplicationTerms loanApplicationTerms, final MathContext mc, Loan loan, final HolidayDetailDTO holidayDetailDTO,
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||||
|
|
||||||
@@ -2790,10 +2791,10 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
|||||||
|
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
||||||
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
||||||
Money feeCharges = Money.zero(currency);
|
|
||||||
Money penaltyCharges = Money.zero(currency);
|
|
||||||
Money totalPrincipal = Money.zero(currency);
|
Money totalPrincipal = Money.zero(currency);
|
||||||
Money totalInterest = Money.zero(currency);
|
Money totalInterest = Money.zero(currency);
|
||||||
|
Money feeCharges = Money.zero(currency);
|
||||||
|
Money penaltyCharges = Money.zero(currency);
|
||||||
for (final LoanRepaymentScheduleInstallment currentInstallment : loanScheduleDTO.getInstallments()) {
|
for (final LoanRepaymentScheduleInstallment currentInstallment : loanScheduleDTO.getInstallments()) {
|
||||||
if (currentInstallment.isNotFullyPaidOff()) {
|
if (currentInstallment.isNotFullyPaidOff()) {
|
||||||
totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
|
totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
|
||||||
@@ -2802,8 +2803,10 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
|||||||
penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
|
penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
return new OutstandingAmountsDTO(currency) //
|
||||||
return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(),
|
.principal(totalPrincipal) //
|
||||||
feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails);
|
.interest(totalInterest) //
|
||||||
|
.feeCharges(feeCharges) //
|
||||||
|
.penaltyCharges(penaltyCharges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -23,9 +23,9 @@ import java.time.LocalDate;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
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.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ public interface LoanScheduleGenerator {
|
|||||||
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||||
LocalDate rescheduleFrom);
|
LocalDate rescheduleFrom);
|
||||||
|
|
||||||
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms,
|
||||||
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-2
@@ -18,22 +18,29 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import static java.time.temporal.ChronoUnit.DAYS;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
@@ -48,6 +55,7 @@ import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
|||||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||||
@@ -222,10 +230,80 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||||
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||||
return null;
|
return switch (loanApplicationTerms.getPreClosureInterestCalculationStrategy()) {
|
||||||
|
case TILL_PRE_CLOSURE_DATE -> {
|
||||||
|
log.debug("calculating prepayment amount till pre closure date (Strategy A)");
|
||||||
|
OutstandingAmountsDTO outstandingAmounts = new OutstandingAmountsDTO(currency);
|
||||||
|
AtomicBoolean firstAfterPayoff = new AtomicBoolean(true);
|
||||||
|
loan.getRepaymentScheduleInstallments().forEach(installment -> {
|
||||||
|
boolean isInstallmentAfterPayoff = installment.getDueDate().isAfter(onDate);
|
||||||
|
|
||||||
|
outstandingAmounts.plusPrincipal(installment.getPrincipalOutstanding(currency));
|
||||||
|
if (isInstallmentAfterPayoff) {
|
||||||
|
if (firstAfterPayoff.getAndSet(false)) {
|
||||||
|
outstandingAmounts.plusInterest(calculatePayableInterest(loan, installment, onDate));
|
||||||
|
} else {
|
||||||
|
log.debug("Installment {} - {} is after payoff, not counting interest", installment.getFromDate(),
|
||||||
|
installment.getDueDate());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("adding interest for {} - {}: {}", installment.getFromDate(), installment.getDueDate(),
|
||||||
|
installment.getInterestOutstanding(currency));
|
||||||
|
outstandingAmounts.plusInterest(installment.getInterestOutstanding(currency));
|
||||||
|
}
|
||||||
|
outstandingAmounts.plusFeeCharges(installment.getFeeChargesOutstanding(currency));
|
||||||
|
outstandingAmounts.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency));
|
||||||
|
});
|
||||||
|
yield outstandingAmounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TILL_REST_FREQUENCY_DATE -> {
|
||||||
|
log.debug("calculating prepayment amount till rest frequency date (Strategy B)");
|
||||||
|
OutstandingAmountsDTO outstandingAmounts = new OutstandingAmountsDTO(currency);
|
||||||
|
loan.getRepaymentScheduleInstallments().forEach(installment -> {
|
||||||
|
boolean isPayoffAfterInstallmentFrom = installment.getFromDate().isAfter(onDate);
|
||||||
|
|
||||||
|
outstandingAmounts.plusPrincipal(installment.getPrincipalOutstanding(currency));
|
||||||
|
if (!isPayoffAfterInstallmentFrom) {
|
||||||
|
outstandingAmounts.plusInterest(installment.getInterestOutstanding(currency));
|
||||||
|
} else {
|
||||||
|
log.debug("Payoff after installment {}, not counting interest", installment.getDueDate());
|
||||||
|
}
|
||||||
|
outstandingAmounts.plusFeeCharges(installment.getFeeChargesOutstanding(currency));
|
||||||
|
outstandingAmounts.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency));
|
||||||
|
});
|
||||||
|
|
||||||
|
yield outstandingAmounts;
|
||||||
|
}
|
||||||
|
case NONE -> throw new UnsupportedOperationException("Pre-closure interest calculation strategy not supported");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Money calculatePayableInterest(Loan loan, LoanRepaymentScheduleInstallment installment, LocalDate onDate) {
|
||||||
|
RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||||
|
MonetaryCurrency currency = loan.getCurrency();
|
||||||
|
Money originalInterest = installment.getInterestCharged(currency);
|
||||||
|
log.debug("calculating interest for {} from {} to {}", originalInterest, installment.getFromDate(), installment.getDueDate());
|
||||||
|
|
||||||
|
LocalDate start = installment.getFromDate();
|
||||||
|
Money payableInterest = Money.zero(currency);
|
||||||
|
|
||||||
|
while (!start.isEqual(onDate)) {
|
||||||
|
long between = DAYS.between(start, installment.getDueDate());
|
||||||
|
Money dailyInterest = originalInterest.minus(payableInterest).dividedBy(between, roundingMode);
|
||||||
|
log.debug("Daily interest is {}: {} / {}, total: {}", dailyInterest, originalInterest.minus(payableInterest), between,
|
||||||
|
payableInterest.add(dailyInterest));
|
||||||
|
payableInterest = payableInterest.add(dailyInterest);
|
||||||
|
start = start.plusDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
payableInterest = payableInterest.minus(installment.getInterestPaid(currency).minus(installment.getInterestWaived(currency)));
|
||||||
|
|
||||||
|
log.debug("Payable interest is {}", payableInterest);
|
||||||
|
return payableInterest;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private, internal methods
|
// Private, internal methods
|
||||||
|
|||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* 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.loanschedule.domain;
|
||||||
|
|
||||||
|
import static java.math.BigDecimal.ZERO;
|
||||||
|
import static org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE;
|
||||||
|
import static org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
|
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.LoanRepaymentScheduleInstallment;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
class ProgressiveLoanScheduleGeneratorTest {
|
||||||
|
|
||||||
|
static class TestRow {
|
||||||
|
|
||||||
|
LocalDate fromDate;
|
||||||
|
LocalDate dueDate;
|
||||||
|
BigDecimal balance;
|
||||||
|
BigDecimal principal;
|
||||||
|
BigDecimal interest;
|
||||||
|
BigDecimal fee;
|
||||||
|
BigDecimal penalty;
|
||||||
|
boolean paid;
|
||||||
|
|
||||||
|
TestRow(LocalDate fromDate, LocalDate dueDate, BigDecimal balance, BigDecimal principal, BigDecimal interest, BigDecimal fee,
|
||||||
|
BigDecimal penalty, boolean paid) {
|
||||||
|
this.fromDate = fromDate;
|
||||||
|
this.dueDate = dueDate;
|
||||||
|
this.balance = balance;
|
||||||
|
this.principal = principal;
|
||||||
|
this.interest = interest;
|
||||||
|
this.fee = fee;
|
||||||
|
this.penalty = penalty;
|
||||||
|
this.paid = paid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(null, null);
|
||||||
|
private MonetaryCurrency usd = new MonetaryCurrency("USD", 2, null);
|
||||||
|
private HolidayDetailDTO holidays = new HolidayDetailDTO(false, null, null);
|
||||||
|
LoanRepaymentScheduleTransactionProcessor processor = mock(LoanRepaymentScheduleTransactionProcessor.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
ConfigurationDomainService domainService = mock(ConfigurationDomainService.class);
|
||||||
|
when(domainService.getRoundingMode()).thenReturn(RoundingMode.HALF_UP.ordinal());
|
||||||
|
ReflectionTestUtils.setField(MoneyHelper.class, "staticConfigurationDomainService", domainService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void beforeAll() {
|
||||||
|
((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(Logger.ROOT_LOGGER_NAME).setLevel(ch.qos.logback.classic.Level.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void afterAll() {
|
||||||
|
((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TestRow> testRows() {
|
||||||
|
return List.of(
|
||||||
|
new TestRow(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), BigDecimal.valueOf(83.57), BigDecimal.valueOf(16.43),
|
||||||
|
BigDecimal.valueOf(0.58), ZERO, ZERO, true),
|
||||||
|
new TestRow(LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1), BigDecimal.valueOf(67.05), BigDecimal.valueOf(16.52),
|
||||||
|
BigDecimal.valueOf(0.49), ZERO, ZERO, false),
|
||||||
|
new TestRow(LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1), BigDecimal.valueOf(50.43), BigDecimal.valueOf(16.62),
|
||||||
|
BigDecimal.valueOf(0.39), ZERO, ZERO, false),
|
||||||
|
new TestRow(LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1), BigDecimal.valueOf(33.71), BigDecimal.valueOf(16.72),
|
||||||
|
BigDecimal.valueOf(0.29), ZERO, ZERO, false),
|
||||||
|
new TestRow(LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1), BigDecimal.valueOf(16.90), BigDecimal.valueOf(16.81),
|
||||||
|
BigDecimal.valueOf(0.20), ZERO, ZERO, false),
|
||||||
|
new TestRow(LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1), BigDecimal.valueOf(00.90), BigDecimal.valueOf(16.90),
|
||||||
|
BigDecimal.valueOf(0.10), ZERO, ZERO, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void calculatePrepaymentAmount_TILL_PRE_CLOSURE_DATE() {
|
||||||
|
LoanApplicationTerms terms = mock(LoanApplicationTerms.class);
|
||||||
|
when(terms.getPreClosureInterestCalculationStrategy()).thenReturn(TILL_PRE_CLOSURE_DATE);
|
||||||
|
Loan loan = prepareLoanWithInstallments(testRows());
|
||||||
|
|
||||||
|
OutstandingAmountsDTO amounts = generator.calculatePrepaymentAmount(usd, LocalDate.of(2024, 2, 15), terms, MathContext.DECIMAL32,
|
||||||
|
loan, holidays, processor);
|
||||||
|
assertEquals(BigDecimal.valueOf(83.84), amounts.getTotalOutstanding().getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void calculatePrepaymentAmount_TILL_REST_FREQUENCY_DATE() {
|
||||||
|
LoanApplicationTerms terms = mock(LoanApplicationTerms.class);
|
||||||
|
when(terms.getPreClosureInterestCalculationStrategy()).thenReturn(TILL_REST_FREQUENCY_DATE);
|
||||||
|
Loan loan = prepareLoanWithInstallments(testRows());
|
||||||
|
|
||||||
|
OutstandingAmountsDTO amounts = generator.calculatePrepaymentAmount(usd, LocalDate.of(2024, 2, 15), terms, MathContext.DECIMAL32,
|
||||||
|
loan, holidays, processor);
|
||||||
|
assertEquals(BigDecimal.valueOf(84.06), amounts.getTotalOutstanding().getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void calculateSameDayPayoff() {
|
||||||
|
LoanApplicationTerms terms = mock(LoanApplicationTerms.class);
|
||||||
|
when(terms.getPreClosureInterestCalculationStrategy()).thenReturn(TILL_PRE_CLOSURE_DATE);
|
||||||
|
|
||||||
|
Loan loan = prepareLoanWithInstallments(List.of(
|
||||||
|
new TestRow(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), BigDecimal.valueOf(102), BigDecimal.valueOf(100),
|
||||||
|
BigDecimal.valueOf(2), ZERO, ZERO, false),
|
||||||
|
new TestRow(LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1), BigDecimal.valueOf(102), BigDecimal.valueOf(100),
|
||||||
|
BigDecimal.valueOf(2), ZERO, ZERO, false)));
|
||||||
|
|
||||||
|
OutstandingAmountsDTO amounts = generator.calculatePrepaymentAmount(usd, LocalDate.of(2024, 1, 1), terms, MathContext.DECIMAL32,
|
||||||
|
loan, holidays, processor);
|
||||||
|
assertEquals(BigDecimal.valueOf(200.0).longValue(), amounts.getTotalOutstanding().getAmount().longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Loan prepareLoanWithInstallments(List<TestRow> rows) {
|
||||||
|
Loan loan = mock(Loan.class);
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments = createInstallments(rows, loan, usd);
|
||||||
|
when(loan.getRepaymentScheduleInstallments()).thenReturn(installments);
|
||||||
|
when(loan.getCurrency()).thenReturn(usd);
|
||||||
|
return loan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LoanRepaymentScheduleInstallment> createInstallments(List<TestRow> rows, Loan loan, MonetaryCurrency usd) {
|
||||||
|
AtomicInteger count = new AtomicInteger(1);
|
||||||
|
return rows.stream().map(row -> {
|
||||||
|
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, count.incrementAndGet(), row.fromDate,
|
||||||
|
row.dueDate, row.principal, row.interest, row.fee, row.penalty, true, null, null, row.paid);
|
||||||
|
if (row.paid) {
|
||||||
|
installment.payPrincipalComponent(row.fromDate, Money.of(usd, row.principal));
|
||||||
|
installment.payInterestComponent(row.fromDate, Money.of(usd, row.interest));
|
||||||
|
installment.updateObligationMet(true);
|
||||||
|
}
|
||||||
|
return installment;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -91,6 +91,7 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
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.data.ScheduleGeneratorDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
@@ -732,7 +733,7 @@ public class LoanScheduleAssembler {
|
|||||||
loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel();
|
loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||||
LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId,
|
LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId,
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||||
@@ -748,7 +749,6 @@ public class LoanScheduleAssembler {
|
|||||||
|
|
||||||
return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loan, holidayDetailDTO,
|
return loanScheduleGenerator.calculatePrepaymentAmount(currency, onDate, loanApplicationTerms, mc, loan, holidayDetailDTO,
|
||||||
loanRepaymentScheduleTransactionProcessor);
|
loanRepaymentScheduleTransactionProcessor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assempleVariableScheduleFrom(final Loan loan, final String json) {
|
public void assempleVariableScheduleFrom(final Loan loan, final String json) {
|
||||||
|
|||||||
+7
-2
@@ -31,6 +31,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
|||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
|
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||||
@@ -115,8 +116,12 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan);
|
LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan);
|
||||||
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency,
|
OutstandingAmountsDTO outstandingAmountsDTO = this.loanScheduleAssembler.calculatePrepaymentAmount(currency, today,
|
||||||
today, loanApplicationTerms, loan, loan.getOfficeId(), loanRepaymentScheduleTransactionProcessor);
|
loanApplicationTerms, loan, loan.getOfficeId(), loanRepaymentScheduleTransactionProcessor);
|
||||||
|
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = new LoanRepaymentScheduleInstallment(null, 0, today, today,
|
||||||
|
outstandingAmountsDTO.principal().getAmount(), outstandingAmountsDTO.interest().getAmount(),
|
||||||
|
outstandingAmountsDTO.feeCharges().getAmount(), outstandingAmountsDTO.penaltyCharges().getAmount(), false, null);
|
||||||
|
|
||||||
Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency))
|
Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency))
|
||||||
.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
|
.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
|
||||||
Money interestDue = Money.zero(currency);
|
Money interestDue = Money.zero(currency);
|
||||||
|
|||||||
+11
-10
@@ -104,6 +104,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||||
@@ -473,20 +474,20 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
final LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
|
final LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
|
||||||
final LocalDate recalculateFrom = null;
|
final LocalDate recalculateFrom = null;
|
||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||||
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(scheduleGeneratorDTO, onDate);
|
final OutstandingAmountsDTO outstandingAmounts = loan.fetchPrepaymentDetail(scheduleGeneratorDTO, onDate);
|
||||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(repaymentTransactionType);
|
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(repaymentTransactionType);
|
||||||
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount();
|
final BigDecimal outstandingLoanBalance = outstandingAmounts.principal().getAmount();
|
||||||
final BigDecimal unrecognizedIncomePortion = null;
|
final BigDecimal unrecognizedIncomePortion = null;
|
||||||
BigDecimal adjustedChargeAmount = adjustPrepayInstallmentCharge(loan, onDate);
|
|
||||||
|
|
||||||
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
|
BigDecimal adjustedChargeAmount = adjustPrepayInstallmentCharge(loan, onDate);
|
||||||
loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount().subtract(adjustedChargeAmount),
|
BigDecimal totalAdjusted = outstandingAmounts.getTotalOutstanding().getAmount().subtract(adjustedChargeAmount);
|
||||||
loan.getNetDisbursalAmount(), loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(),
|
|
||||||
loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(),
|
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, totalAdjusted,
|
||||||
loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount().subtract(adjustedChargeAmount),
|
loan.getNetDisbursalAmount(), outstandingAmounts.principal().getAmount(), outstandingAmounts.interest().getAmount(),
|
||||||
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion,
|
outstandingAmounts.feeCharges().getAmount().subtract(adjustedChargeAmount), outstandingAmounts.penaltyCharges().getAmount(),
|
||||||
paymentOptions, ExternalId.empty(), null, null, outstandingLoanBalance, false, loanId, loan.getExternalId());
|
null, unrecognizedIncomePortion, paymentOptions, ExternalId.empty(), null, null, outstandingLoanBalance, false, loanId,
|
||||||
|
loan.getExternalId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal adjustPrepayInstallmentCharge(Loan loan, final LocalDate onDate) {
|
private BigDecimal adjustPrepayInstallmentCharge(Loan loan, final LocalDate onDate) {
|
||||||
|
|||||||
Reference in New Issue
Block a user