FINERACT-2220: Use stored ProgressiveLoanInterestScheduleModel for calculation
This commit is contained in:
+6
-4
@@ -38,7 +38,7 @@ public class LoanScheduleService {
|
||||
private final LoanChargeService loanChargeService;
|
||||
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||
private final LoanMapper loanMapper;
|
||||
private final LoanTransactionProcessingService loadTransactionProcessingService;
|
||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||
private final LoanScheduleComponent loanSchedule;
|
||||
|
||||
/**
|
||||
@@ -77,14 +77,16 @@ public class LoanScheduleService {
|
||||
} else {
|
||||
regenerateRepaymentSchedule(loan, generatorDTO);
|
||||
}
|
||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||
} else {
|
||||
reprocessLoanTransactionsService.updateModel(loan);
|
||||
}
|
||||
|
||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||
}
|
||||
|
||||
public void regenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
||||
final LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
|
||||
final LoanScheduleDTO loanScheduleDTO = loadTransactionProcessingService.getRecalculatedSchedule(generatorDTO, loan);
|
||||
final LoanScheduleDTO loanScheduleDTO = loanTransactionProcessingService.getRecalculatedSchedule(generatorDTO, loan);
|
||||
if (loanScheduleDTO == null) {
|
||||
return;
|
||||
}
|
||||
@@ -112,7 +114,7 @@ public class LoanScheduleService {
|
||||
}
|
||||
}
|
||||
}
|
||||
loadTransactionProcessingService.processPostDisbursementTransactions(loan);
|
||||
loanTransactionProcessingService.processPostDisbursementTransactions(loan);
|
||||
}
|
||||
|
||||
public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
||||
|
||||
+9
-108
@@ -18,136 +18,37 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.OutstandingAmountsDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
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.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTermVariationsMapper;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LoanTransactionProcessingService {
|
||||
public interface LoanTransactionProcessingService {
|
||||
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||
private final LoanTermVariationsMapper loanMapper;
|
||||
ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction,
|
||||
TransactionCtx ctx);
|
||||
|
||||
public ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction,
|
||||
TransactionCtx ctx) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
transactionProcessingStrategyCode);
|
||||
return loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, ctx);
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail reprocessLoanTransactions(String transactionProcessingStrategyCode, LocalDate disbursementDate,
|
||||
ChangedTransactionDetail reprocessLoanTransactions(String transactionProcessingStrategyCode, LocalDate disbursementDate,
|
||||
List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
transactionProcessingStrategyCode);
|
||||
return loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(disbursementDate, repaymentsOrWaivers, currency,
|
||||
repaymentScheduleInstallments, charges);
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
|
||||
|
||||
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode) {
|
||||
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
|
||||
}
|
||||
LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode);
|
||||
|
||||
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
loan.getTransactionProcessingStrategyCode());
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> copyTransactions = new ArrayList<>();
|
||||
Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan);
|
||||
|
||||
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
LoanScheduleDTO getRecalculatedSchedule(ScheduleGeneratorDTO generatorDTO, Loan loan);
|
||||
|
||||
// TODO: Probably this is not needed and can be eliminated, make sure to double check it
|
||||
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
|
||||
copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
|
||||
}
|
||||
final ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
loan.getDisbursementDate(), copyTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||
loan.getActiveCharges());
|
||||
OutstandingAmountsDTO fetchPrepaymentDetail(ScheduleGeneratorDTO scheduleGeneratorDTO, LocalDate onDate, Loan loan);
|
||||
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
|
||||
return Optional.of(changedTransactionDetail);
|
||||
}
|
||||
|
||||
public LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO, Loan loan) {
|
||||
if (!loan.isInterestBearingAndInterestRecalculationEnabled() || loan.isNpa() || loan.isChargedOff()) {
|
||||
return null;
|
||||
}
|
||||
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory()
|
||||
.create(loan.getLoanRepaymentScheduleDetail().getLoanScheduleType(), interestMethod);
|
||||
|
||||
final MathContext mc = MoneyHelper.getMathContext();
|
||||
|
||||
final LoanApplicationTerms loanApplicationTerms = loanMapper.constructLoanApplicationTerms(generatorDTO, loan);
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
loan.getTransactionProcessingStrategyCode());
|
||||
|
||||
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, generatorDTO.getHolidayDetailDTO(),
|
||||
loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
|
||||
}
|
||||
|
||||
public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate, Loan loan) {
|
||||
OutstandingAmountsDTO outstandingAmounts;
|
||||
|
||||
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargeOffOnDate(onDate)) {
|
||||
final MathContext mc = MoneyHelper.getMathContext();
|
||||
|
||||
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
|
||||
final LoanApplicationTerms loanApplicationTerms = loanMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, loan);
|
||||
|
||||
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
|
||||
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
loan.getTransactionProcessingStrategyCode());
|
||||
outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(loan.getCurrency(), onDate, loanApplicationTerms, mc, loan,
|
||||
scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
|
||||
} else {
|
||||
outstandingAmounts = getTotalOutstandingOnLoan(loan);
|
||||
}
|
||||
return outstandingAmounts;
|
||||
}
|
||||
|
||||
private OutstandingAmountsDTO getTotalOutstandingOnLoan(Loan loan) {
|
||||
Money totalPrincipal = Money.zero(loan.getCurrency());
|
||||
Money totalInterest = Money.zero(loan.getCurrency());
|
||||
Money feeCharges = Money.zero(loan.getCurrency());
|
||||
Money penaltyCharges = Money.zero(loan.getCurrency());
|
||||
List<LoanRepaymentScheduleInstallment> repaymentSchedule = loan.getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
|
||||
totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(loan.getCurrency()));
|
||||
totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(loan.getCurrency()));
|
||||
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loan.getCurrency()));
|
||||
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loan.getCurrency()));
|
||||
}
|
||||
return new OutstandingAmountsDTO(totalPrincipal.getCurrency()).principal(totalPrincipal).interest(totalInterest)
|
||||
.feeCharges(feeCharges).penaltyCharges(penaltyCharges);
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -38,4 +38,5 @@ public interface ReprocessLoanTransactionsService {
|
||||
|
||||
void processLatestTransaction(LoanTransaction loanTransaction, Loan loan);
|
||||
|
||||
void updateModel(Loan loan);
|
||||
}
|
||||
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransactionsService {
|
||||
|
||||
private final LoanAccountService loanAccountService;
|
||||
private final LoanAccountTransfersService loanAccountTransfersService;
|
||||
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||
private final LoanTransactionProcessingService loadTransactionProcessingService;
|
||||
|
||||
@Override
|
||||
public void reprocessTransactions(final Loan loan) {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
||||
allNonContraTransactionsPostDisbursement);
|
||||
handleChangedDetail(changedTransactionDetail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) {
|
||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
|
||||
handleChangedDetail(changedTransactionDetail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
|
||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
||||
loan.retrieveListOfTransactionsForReprocessing());
|
||||
handleChangedDetail(changedTransactionDetail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processPostDisbursementTransactions(final Loan loan) {
|
||||
loadTransactionProcessingService.processPostDisbursementTransactions(loan).ifPresent(this::handleChangedDetail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLoanCharge(final Loan loan, final LoanCharge loanCharge) {
|
||||
final boolean removed = loanCharge.isActive();
|
||||
if (removed) {
|
||||
loanCharge.setActive(false);
|
||||
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
||||
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(),
|
||||
loan.getActiveCharges());
|
||||
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
|
||||
}
|
||||
|
||||
loan.removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge);
|
||||
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loan.getCurrency())) {
|
||||
/*
|
||||
* TODO Vishwas Currently we do not allow removing a loan charge after a loan is approved (hence there is no
|
||||
* need to adjust any loan transactions).
|
||||
*
|
||||
* Consider removing this block of code or logically completing it for the future by getting the list of
|
||||
* affected Transactions
|
||||
*/
|
||||
reprocessTransactions(loan);
|
||||
return;
|
||||
}
|
||||
loan.getLoanCharges().remove(loanCharge);
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) {
|
||||
final ChangedTransactionDetail changedTransactionDetail = loadTransactionProcessingService.processLatestTransaction(
|
||||
loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()));
|
||||
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||
.map(TransactionChangeData::getNewTransaction).peek(transaction -> transaction.updateLoan(loan)).toList();
|
||||
loan.getLoanTransactions().addAll(newTransactions);
|
||||
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
handleChangedDetail(changedTransactionDetail);
|
||||
}
|
||||
|
||||
private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) {
|
||||
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||
final LoanTransaction newTransaction = change.getNewTransaction();
|
||||
final LoanTransaction oldTransaction = change.getOldTransaction();
|
||||
|
||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction);
|
||||
|
||||
if (oldTransaction != null) {
|
||||
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(), newTransaction);
|
||||
}
|
||||
}
|
||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail reprocessTransactionsAndFetchChangedTransactions(final Loan loan,
|
||||
final List<LoanTransaction> loanTransactions) {
|
||||
final ChangedTransactionDetail changedTransactionDetail = loadTransactionProcessingService.reprocessLoanTransactions(
|
||||
loan.getTransactionProcessingStrategyCode(), loan.getDisbursementDate(), loanTransactions, loan.getCurrency(),
|
||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||
change.getNewTransaction().updateLoan(loan);
|
||||
}
|
||||
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||
.map(TransactionChangeData::getNewTransaction).toList();
|
||||
loan.getLoanTransactions().addAll(newTransactions);
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user