FINERACT-2080: separate LoanRepaymentScheduleTransactionProcessorFactory from loan

This commit is contained in:
budaidev
2025-04-03 10:50:50 +02:00
committed by Adam Saghy
parent 741cdb1e42
commit 16e70527ea
22 changed files with 284 additions and 216 deletions
@@ -107,13 +107,8 @@ 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.data.TransactionChangeData;
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.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.loanschedule.domain.LoanScheduleModel;
@@ -365,8 +360,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@Transient
private boolean accountNumberRequiresAutoGeneration;
@Transient
private LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
@Transient
private LoanLifecycleStateMachine loanLifecycleStateMachine;
@@ -769,37 +762,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return applyLoanChargeTransaction;
}
public void handleChargePaidTransaction(final LoanCharge charge, final LoanTransaction chargesPayment,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final Integer installmentNumber) {
chargesPayment.updateLoan(this);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge,
chargesPayment.getAmount(getCurrency()).getAmount(), installmentNumber);
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
addLoanTransaction(chargesPayment);
loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, this);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
.fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
for (final LoanRepaymentScheduleInstallment installment : installments) {
boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
if (installment.getInstallmentNumber().equals(installmentNumber) || (installmentNumber == null
&& charge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))) {
chargePaymentInstallments.add(installment);
break;
}
}
final Set<LoanCharge> loanCharges = new HashSet<>(1);
loanCharges.add(charge);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment, new TransactionCtx(getCurrency(),
chargePaymentInstallments, loanCharges, new MoneyHolder(getTotalOverpaidAsMoney()), null));
updateLoanSummaryDerivedFields();
doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine);
}
public LocalDate getLastRepaymentPeriodDueDate(final boolean includeRecalculatedInterestComponent) {
LocalDate lastRepaymentDate = getDisbursementDate();
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
@@ -2245,10 +2207,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return receivableInterest;
}
public void setHelpers(final LoanLifecycleStateMachine loanLifecycleStateMachine,
final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory) {
public void setHelpers(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
this.loanLifecycleStateMachine = loanLifecycleStateMachine;
this.transactionProcessorFactory = transactionProcessorFactory;
}
public boolean isSyncDisbursementWithMeeting() {
@@ -2601,89 +2561,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return (this.actualMaturityDate != null) ? (referenceDate.compareTo(this.actualMaturityDate) >= 0) : false;
}
public ChangedTransactionDetail processTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
getActiveCharges());
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
change.getNewTransaction().updateLoan(this);
}
/*
* Commented since throwing exception if external id present for one of the transactions. for this need to save
* the reversed transactions first and then new transactions.
*/
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
.map(TransactionChangeData::getNewTransaction).toList();
this.loanTransactions.addAll(newTransactions);
updateLoanSummaryDerivedFields();
return changedTransactionDetail;
}
/*
* Probably this is buggy when a chargeback transaction happens
*/
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
return Optional.empty();
}
// 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(
getDisbursementDate(), copyTransactions, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
updateLoanSummaryDerivedFields();
return Optional.of(changedTransactionDetail);
}
public LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
if (!isInterestBearingAndInterestRecalculationEnabled() || isNpa || isChargedOff()) {
return null;
}
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory()
.create(this.loanRepaymentScheduleDetail.getLoanScheduleType(), interestMethod);
final MathContext mc = MoneyHelper.getMathContext();
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(),
loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom());
}
public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
OutstandingAmountsDTO outstandingAmounts;
if (isInterestBearingAndInterestRecalculationEnabled() && !isChargeOffOnDate(onDate)) {
final MathContext mc = MoneyHelper.getMathContext();
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor);
} else {
outstandingAmounts = this.getTotalOutstandingOnLoan();
}
return outstandingAmounts;
}
public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final Integer loanTermFrequency = this.termFrequency;
NthDayType nthDayType = null;
@@ -2754,22 +2631,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return annualNominalInterestRate;
}
private OutstandingAmountsDTO getTotalOutstandingOnLoan() {
Money totalPrincipal = Money.zero(getCurrency());
Money totalInterest = Money.zero(getCurrency());
Money feeCharges = Money.zero(getCurrency());
Money penaltyCharges = Money.zero(getCurrency());
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(getCurrency()));
totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(getCurrency()));
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(getCurrency()));
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(getCurrency()));
}
return new OutstandingAmountsDTO(totalPrincipal.getCurrency()).principal(totalPrincipal).interest(totalInterest)
.feeCharges(feeCharges).penaltyCharges(penaltyCharges);
}
public LocalDate fetchInterestRecalculateFromDate() {
LocalDate recalculatedOn;
if (this.interestRecalculatedOn == null) {
@@ -3510,10 +3371,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
isTopup = topup;
}
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor() {
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
}
public boolean isProgressiveSchedule() {
return getLoanProductRelatedDetail().getLoanScheduleType() == PROGRESSIVE;
}
@@ -19,19 +19,28 @@
package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
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.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
@RequiredArgsConstructor
public class LoanChargeService {
private final LoanChargeValidator loanChargeValidator;
private final LoanTransactionProcessingService loanTransactionProcessingService;
public void recalculateAllCharges(final Loan loan) {
Set<LoanCharge> charges = loan.getActiveCharges();
@@ -79,6 +88,37 @@ public class LoanChargeService {
charge = loanCharge;
}
}
loan.handleChargePaidTransaction(charge, paymentTransaction, loanLifecycleStateMachine, installmentNumber);
handleChargePaidTransaction(loan, charge, paymentTransaction, loanLifecycleStateMachine, installmentNumber);
}
public void handleChargePaidTransaction(final Loan loan, final LoanCharge charge, final LoanTransaction chargesPayment,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final Integer installmentNumber) {
chargesPayment.updateLoan(loan);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge,
chargesPayment.getAmount(loan.getCurrency()).getAmount(), installmentNumber);
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
loan.addLoanTransaction(chargesPayment);
loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, loan);
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
.fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
for (final LoanRepaymentScheduleInstallment installment : installments) {
boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
if (installment.getInstallmentNumber().equals(installmentNumber) || (installmentNumber == null
&& charge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))) {
chargePaymentInstallments.add(installment);
break;
}
}
final Set<LoanCharge> loanCharges = new HashSet<>(1);
loanCharges.add(charge);
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargesPayment,
new TransactionCtx(loan.getCurrency(), chargePaymentInstallments, loanCharges,
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
loan.updateLoanSummaryDerivedFields();
loan.doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine);
}
}
@@ -42,7 +42,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachin
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
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.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
@@ -61,6 +60,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
private final LoanRefundService loanRefundService;
private final LoanRefundValidator loanRefundValidator;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanTransactionProcessingService loanTransactionProcessingService;
@Override
public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command,
@@ -124,8 +124,6 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
loanRefundValidator.validateTransactionAmountThreshold(loan, adjustedTransaction);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor();
final LoanRepaymentScheduleInstallment currentInstallment = loan
.fetchLoanRepaymentScheduleInstallmentByDueDate(loanTransaction.getTransactionDate());
@@ -135,7 +133,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
if (isTransactionChronologicallyLatest && adjustedTransaction == null
&& (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled()) && !loan.isForeclosure()) {
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
reprocess = false;
@@ -26,7 +26,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
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.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanRefundValidator;
@@ -35,6 +34,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanRefundValidat
public class LoanRefundService {
private final LoanRefundValidator loanRefundValidator;
private final LoanTransactionProcessingService loadTransactionProcessingService;
public void makeRefund(final Loan loan, final LoanTransaction loanTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
@@ -106,10 +106,9 @@ public class LoanRefundService {
loanRefundValidator.validateTransactionDateNotInFuture(loanTransactionDate);
loanRefundValidator.validateTransactionAmountThreshold(loan, null);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor();
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
loadTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
loan.updateLoanSummaryDerivedFields();
loan.doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine);
@@ -35,6 +35,7 @@ public class LoanScheduleService {
private final LoanChargeService loanChargeService;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanTransactionProcessingService loadTransactionProcessingService;
/**
* Ability to regenerate the repayment schedule based on the loans current details/state.
@@ -79,7 +80,7 @@ public class LoanScheduleService {
public void regenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
final LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
final LoanScheduleDTO loanSchedule = loan.getRecalculatedSchedule(generatorDTO);
final LoanScheduleDTO loanSchedule = loadTransactionProcessingService.getRecalculatedSchedule(generatorDTO, loan);
if (loanSchedule == null) {
return;
}
@@ -107,7 +108,7 @@ public class LoanScheduleService {
}
}
}
loan.processPostDisbursementTransactions();
loadTransactionProcessingService.processPostDisbursementTransactions(loan);
}
public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
@@ -0,0 +1,151 @@
/**
* 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.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.loanproduct.domain.InterestMethod;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LoanTransactionProcessingService {
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
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,
List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
transactionProcessingStrategyCode);
return loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(disbursementDate, repaymentsOrWaivers, currency,
repaymentScheduleInstallments, charges);
}
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode) {
return transactionProcessorFactory.determineProcessor(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<>();
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
return Optional.empty();
}
// 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());
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 = loan.constructLoanApplicationTerms(generatorDTO);
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 = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
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);
}
}
@@ -29,7 +29,6 @@ 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.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.springframework.stereotype.Service;
@@ -41,6 +40,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
private final LoanAccountService loanAccountService;
private final LoanAccountTransfersService loanAccountTransfersService;
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
private final LoanTransactionProcessingService loadTransactionProcessingService;
@Override
public void reprocessTransactions(final Loan loan) {
@@ -65,7 +65,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
@Override
public void processPostDisbursementTransactions(final Loan loan) {
loan.processPostDisbursementTransactions().ifPresent(this::handleChangedDetail);
loadTransactionProcessingService.processPostDisbursementTransactions(loan).ifPresent(this::handleChangedDetail);
}
@Override
@@ -98,7 +98,8 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
@Override
public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) {
final ChangedTransactionDetail changedTransactionDetail = loan.getTransactionProcessor().processLatestTransaction(loanTransaction,
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()
@@ -125,10 +126,9 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
private ChangedTransactionDetail reprocessTransactionsAndFetchChangedTransactions(final Loan loan,
final List<LoanTransaction> loanTransactions) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor();
final ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
loan.getDisbursementDate(), loanTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
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);
}