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 LoanChargeService loanChargeService;
|
||||||
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||||
private final LoanMapper loanMapper;
|
private final LoanMapper loanMapper;
|
||||||
private final LoanTransactionProcessingService loadTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
private final LoanScheduleComponent loanSchedule;
|
private final LoanScheduleComponent loanSchedule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,14 +77,16 @@ public class LoanScheduleService {
|
|||||||
} else {
|
} else {
|
||||||
regenerateRepaymentSchedule(loan, generatorDTO);
|
regenerateRepaymentSchedule(loan, generatorDTO);
|
||||||
}
|
}
|
||||||
|
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||||
|
} else {
|
||||||
|
reprocessLoanTransactionsService.updateModel(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void regenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
public void regenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
||||||
final LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
|
final LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
|
||||||
final LoanScheduleDTO loanScheduleDTO = loadTransactionProcessingService.getRecalculatedSchedule(generatorDTO, loan);
|
final LoanScheduleDTO loanScheduleDTO = loanTransactionProcessingService.getRecalculatedSchedule(generatorDTO, loan);
|
||||||
if (loanScheduleDTO == null) {
|
if (loanScheduleDTO == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,7 @@ public class LoanScheduleService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadTransactionProcessingService.processPostDisbursementTransactions(loan);
|
loanTransactionProcessingService.processPostDisbursementTransactions(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
||||||
|
|||||||
+9
-108
@@ -18,136 +18,37 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
import java.math.MathContext;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
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.MoneyHelper;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
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.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
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;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
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.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
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.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
|
public interface LoanTransactionProcessingService {
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class LoanTransactionProcessingService {
|
|
||||||
|
|
||||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction,
|
||||||
private final LoanTermVariationsMapper loanMapper;
|
TransactionCtx ctx);
|
||||||
|
|
||||||
public ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction,
|
ChangedTransactionDetail reprocessLoanTransactions(String transactionProcessingStrategyCode, LocalDate disbursementDate,
|
||||||
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<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency,
|
||||||
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges) {
|
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
|
||||||
transactionProcessingStrategyCode);
|
|
||||||
return loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(disbursementDate, repaymentsOrWaivers, currency,
|
|
||||||
repaymentScheduleInstallments, charges);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode) {
|
LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode);
|
||||||
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan) {
|
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()) {
|
LoanScheduleDTO getRecalculatedSchedule(ScheduleGeneratorDTO generatorDTO, Loan loan);
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Probably this is not needed and can be eliminated, make sure to double check it
|
OutstandingAmountsDTO fetchPrepaymentDetail(ScheduleGeneratorDTO scheduleGeneratorDTO, LocalDate onDate, Loan loan);
|
||||||
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 = 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 processLatestTransaction(LoanTransaction loanTransaction, Loan loan);
|
||||||
|
|
||||||
|
void updateModel(Loan loan);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -1187,7 +1187,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
|
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransactionCtx ctx) {
|
public void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransactionCtx ctx) {
|
||||||
if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()) {
|
if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()) {
|
||||||
Loan loan = ctx.getInstallments().get(0).getLoan();
|
Loan loan = ctx.getInstallments().get(0).getLoan();
|
||||||
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isNpa() && !ctx.isChargedOff()) {
|
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isNpa() && !ctx.isChargedOff()) {
|
||||||
@@ -1240,17 +1240,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
LocalDate toDate = currentInstallment.getDueDate();
|
LocalDate toDate = currentInstallment.getDueDate();
|
||||||
boolean hasUpdate = false;
|
boolean hasUpdate = false;
|
||||||
|
|
||||||
if (!currentDate.equals(ctx.getLastOverdueBalanceChange())) {
|
if (!currentDate.equals(ctx.getModel().lastOverdueBalanceChange())) {
|
||||||
// if we have same date for fromDate & last overdue balance change then it means we have the up-to-date
|
// if we have same date for fromDate & last overdue balance change then it means we have the up-to-date
|
||||||
// model.
|
// model.
|
||||||
if (ctx.getLastOverdueBalanceChange() == null || currentInstallment.getFromDate().isAfter(ctx.getLastOverdueBalanceChange())) {
|
if (ctx.getModel().lastOverdueBalanceChange() == null
|
||||||
|
|| currentInstallment.getFromDate().isAfter(ctx.getModel().lastOverdueBalanceChange())) {
|
||||||
// first overdue hit for installment. setting overdue balance correction from instalment from date.
|
// first overdue hit for installment. setting overdue balance correction from instalment from date.
|
||||||
emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal);
|
emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal);
|
||||||
} else {
|
} else {
|
||||||
// not the first balance correction on installment period, then setting overdue balance correction from
|
// not the first balance correction on installment period, then setting overdue balance correction from
|
||||||
// last balance change's current date. previous interest period already has the correct balance
|
// last balance change's current date. previous interest period already has the correct balance
|
||||||
// correction.
|
// correction.
|
||||||
emiCalculator.addBalanceCorrection(ctx.getModel(), ctx.getLastOverdueBalanceChange(), overduePrincipal);
|
emiCalculator.addBalanceCorrection(ctx.getModel(), ctx.getModel().lastOverdueBalanceChange(), overduePrincipal);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasUpdate = true;
|
hasUpdate = true;
|
||||||
@@ -1263,7 +1264,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
lastOverdueBalanceChange = currentDate;
|
lastOverdueBalanceChange = currentDate;
|
||||||
}
|
}
|
||||||
emiCalculator.addBalanceCorrection(ctx.getModel(), lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated());
|
emiCalculator.addBalanceCorrection(ctx.getModel(), lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated());
|
||||||
ctx.setLastOverdueBalanceChange(lastOverdueBalanceChange);
|
ctx.getModel().lastOverdueBalanceChange(lastOverdueBalanceChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-3
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -38,8 +37,6 @@ import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanIntere
|
|||||||
public class ProgressiveTransactionCtx extends TransactionCtx {
|
public class ProgressiveTransactionCtx extends TransactionCtx {
|
||||||
|
|
||||||
private final ProgressiveLoanInterestScheduleModel model;
|
private final ProgressiveLoanInterestScheduleModel model;
|
||||||
@Setter
|
|
||||||
private LocalDate lastOverdueBalanceChange = null;
|
|
||||||
private List<LoanTransaction> alreadyProcessedTransactions = new ArrayList<>();
|
private List<LoanTransaction> alreadyProcessedTransactions = new ArrayList<>();
|
||||||
@Setter
|
@Setter
|
||||||
private Money sumOfInterestRefundAmount;
|
private Money sumOfInterestRefundAmount;
|
||||||
|
|||||||
+7
@@ -18,16 +18,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||||
|
|
||||||
public interface InterestScheduleModelRepositoryWrapper {
|
public interface InterestScheduleModelRepositoryWrapper {
|
||||||
|
|
||||||
|
Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId);
|
||||||
|
|
||||||
|
Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel);
|
||||||
|
|
||||||
String writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model);
|
String writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model);
|
||||||
|
|
||||||
Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
|
Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
|
||||||
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf);
|
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf);
|
||||||
|
|
||||||
|
Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate);
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-9
@@ -18,14 +18,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.FlushModeType;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
|
import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx;
|
||||||
import org.apache.fineract.portfolio.loanaccount.repository.ProgressiveLoanModelRepository;
|
import org.apache.fineract.portfolio.loanaccount.repository.ProgressiveLoanModelRepository;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||||
@@ -37,6 +46,9 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
|
|||||||
|
|
||||||
private final ProgressiveLoanModelRepository loanModelRepository;
|
private final ProgressiveLoanModelRepository loanModelRepository;
|
||||||
private final ProgressiveLoanInterestScheduleModelParserService progressiveLoanInterestScheduleModelParserService;
|
private final ProgressiveLoanInterestScheduleModelParserService progressiveLoanInterestScheduleModelParserService;
|
||||||
|
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
|
||||||
|
@PersistenceContext
|
||||||
|
private final EntityManager entityManager;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -45,18 +57,54 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String jsonModel = progressiveLoanInterestScheduleModelParserService.toJson(model);
|
String jsonModel = progressiveLoanInterestScheduleModelParserService.toJson(model);
|
||||||
ProgressiveLoanModel progressiveLoanModel = loanModelRepository.findOneByLoanId(loan.getId()).orElseGet(() -> {
|
try {
|
||||||
ProgressiveLoanModel plm = new ProgressiveLoanModel();
|
entityManager.setFlushMode(FlushModeType.COMMIT);
|
||||||
plm.setLoan(loan);
|
ProgressiveLoanModel progressiveLoanModel = loanModelRepository.findOneByLoanId(loan.getId()).orElseGet(() -> {
|
||||||
return plm;
|
ProgressiveLoanModel plm = new ProgressiveLoanModel();
|
||||||
});
|
plm.setLoan(loan);
|
||||||
progressiveLoanModel.setBusinessDate(ThreadLocalContextUtil.getBusinessDate());
|
return plm;
|
||||||
progressiveLoanModel.setLastModifiedDate(DateUtils.getAuditOffsetDateTime());
|
});
|
||||||
progressiveLoanModel.setJsonModel(jsonModel);
|
progressiveLoanModel.setBusinessDate(ThreadLocalContextUtil.getBusinessDate());
|
||||||
loanModelRepository.save(progressiveLoanModel);
|
progressiveLoanModel.setLastModifiedDate(DateUtils.getAuditOffsetDateTime());
|
||||||
|
progressiveLoanModel.setJsonModel(jsonModel);
|
||||||
|
loanModelRepository.save(progressiveLoanModel);
|
||||||
|
} finally {
|
||||||
|
entityManager.setFlushMode(FlushModeType.AUTO);
|
||||||
|
}
|
||||||
return jsonModel;
|
return jsonModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId) {
|
||||||
|
return loanModelRepository.findOneByLoanId(loanId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel) {
|
||||||
|
return progressiveLoanModel.map(ProgressiveLoanModel::getJsonModel) //
|
||||||
|
.map(jsonModel -> progressiveLoanInterestScheduleModelParserService.fromJson(jsonModel,
|
||||||
|
progressiveLoanModel.get().getLoan().getLoanProductRelatedDetail(), MoneyHelper.getMathContext(),
|
||||||
|
progressiveLoanModel.get().getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate) {
|
||||||
|
Optional<ProgressiveLoanModel> progressiveLoanModel = findOneByLoanId(loan.getId());
|
||||||
|
Optional<ProgressiveLoanInterestScheduleModel> savedModel;
|
||||||
|
if (progressiveLoanModel.isPresent() && !progressiveLoanModel.get().getBusinessDate().isAfter(businessDate)) {
|
||||||
|
savedModel = extractModel(progressiveLoanModel);
|
||||||
|
if (progressiveLoanModel.get().getBusinessDate().isBefore(businessDate) && savedModel.isPresent()) {
|
||||||
|
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||||
|
Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), savedModel.get());
|
||||||
|
ctx.setChargedOff(loan.isChargedOff());
|
||||||
|
advancedPaymentScheduleTransactionProcessor.recalculateInterestForDate(businessDate, ctx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
savedModel = Optional.empty();
|
||||||
|
}
|
||||||
|
return savedModel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(final Long loanId,
|
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(final Long loanId,
|
||||||
final LoanProductMinimumRepaymentScheduleRelatedDetail detail, final Integer installmentAmountInMultipliesOf) {
|
final LoanProductMinimumRepaymentScheduleRelatedDetail detail, final Integer installmentAmountInMultipliesOf) {
|
||||||
|
|||||||
+4
@@ -41,6 +41,7 @@ import java.util.function.Consumer;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
@@ -67,6 +68,9 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
private final Money zero;
|
private final Money zero;
|
||||||
private final Map<LoanInterestScheduleModelModifiers, Boolean> modifiers;
|
private final Map<LoanInterestScheduleModelModifiers, Boolean> modifiers;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private LocalDate lastOverdueBalanceChange;
|
||||||
|
|
||||||
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
|
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
|
||||||
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
|
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
|
||||||
final List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
|
final List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
|
||||||
|
|||||||
+189
@@ -0,0 +1,189 @@
|
|||||||
|
/**
|
||||||
|
* 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.commons.lang3.tuple.Pair;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
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.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
|
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.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LoanTransactionProcessingServiceImpl implements LoanTransactionProcessingService {
|
||||||
|
|
||||||
|
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||||
|
private final LoanTermVariationsMapper loanMapper;
|
||||||
|
private final InterestScheduleModelRepositoryWrapper modelRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction,
|
||||||
|
TransactionCtx ctx) {
|
||||||
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||||
|
transactionProcessingStrategyCode);
|
||||||
|
return loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Loan getLoan(List<LoanTransaction> loanTransactions, List<LoanRepaymentScheduleInstallment> installments,
|
||||||
|
Set<LoanCharge> charges) {
|
||||||
|
if (!ObjectUtils.isEmpty(loanTransactions)) {
|
||||||
|
return loanTransactions.getFirst().getLoan();
|
||||||
|
} else if (!ObjectUtils.isEmpty(installments)) {
|
||||||
|
return installments.getFirst().getLoan();
|
||||||
|
} else if (!ObjectUtils.isEmpty(charges)) {
|
||||||
|
return charges.iterator().next().getLoan();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("No loan found for the given transactions, installments or charges");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangedTransactionDetail reprocessLoanTransactions(String transactionProcessingStrategyCode, LocalDate disbursementDate,
|
||||||
|
List<LoanTransaction> loanTransactions, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||||
|
Set<LoanCharge> charges) {
|
||||||
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||||
|
transactionProcessingStrategyCode);
|
||||||
|
if (loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedProcessor) {
|
||||||
|
LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
||||||
|
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> result = advancedProcessor
|
||||||
|
.reprocessProgressiveLoanTransactions(disbursementDate, currentDate, loanTransactions, currency, installments, charges);
|
||||||
|
if (!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
||||||
|
modelRepository.writeInterestScheduleModel(getLoan(loanTransactions, installments, charges), result.getRight());
|
||||||
|
}
|
||||||
|
return result.getLeft();
|
||||||
|
} else {
|
||||||
|
return loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(disbursementDate, loanTransactions, currency,
|
||||||
|
installments, charges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode) {
|
||||||
|
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-14
@@ -53,6 +53,7 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
|||||||
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
|
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
|
||||||
private final EMICalculator emiCalculator;
|
private final EMICalculator emiCalculator;
|
||||||
private final LoanRepositoryWrapper loanRepository;
|
private final LoanRepositoryWrapper loanRepository;
|
||||||
|
private final InterestScheduleModelRepositoryWrapper modelRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(String loanProcessingStrategyCode) {
|
public boolean accept(String loanProcessingStrategyCode) {
|
||||||
@@ -78,6 +79,24 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
|||||||
&& businessDate.isAfter(i.getFromDate()) && !businessDate.isAfter(i.getDueDate())).findFirst();
|
&& businessDate.isAfter(i.getFromDate()) && !businessDate.isAfter(i.getDueDate())).findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
|
||||||
|
List<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
|
||||||
|
.filter(t -> !t.isAccrualActivity()).toList();
|
||||||
|
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
||||||
|
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
|
||||||
|
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||||
|
ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
|
||||||
|
final List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
|
||||||
|
.getTransactionChanges().stream().filter(change -> change.getOldTransaction() != null && change.getNewTransaction() != null)
|
||||||
|
.map(change -> change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
|
||||||
|
|
||||||
|
if (!replayedTransactions.isEmpty()) {
|
||||||
|
log.warn("Reprocessed transactions show differences: There are unsaved changes of the following transactions: {}",
|
||||||
|
replayedTransactions);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
|
public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
|
||||||
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency,
|
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency,
|
||||||
@@ -93,21 +112,10 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
|||||||
return MathUtil.subtractToZero(currentRepaymentPeriod.get().getInterestOutstanding(loan.getCurrency()).getAmount(),
|
return MathUtil.subtractToZero(currentRepaymentPeriod.get().getInterestOutstanding(loan.getCurrency()).getAmount(),
|
||||||
totalUnpaidPayableDueInterest);
|
totalUnpaidPayableDueInterest);
|
||||||
} else {
|
} else {
|
||||||
List<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
|
|
||||||
.filter(t -> !t.isAccrualActivity()).toList();
|
|
||||||
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
|
||||||
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
|
|
||||||
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
|
||||||
ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
|
|
||||||
final List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
|
|
||||||
.getTransactionChanges().stream()
|
|
||||||
.filter(change -> change.getOldTransaction() != null && change.getNewTransaction() != null)
|
|
||||||
.map(change -> change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
|
|
||||||
|
|
||||||
if (!replayedTransactions.isEmpty()) {
|
Optional<ProgressiveLoanInterestScheduleModel> savedModel = modelRepository.getSavedModel(loan, businessDate);
|
||||||
log.warn("Reprocessed transactions show differences: There are unsaved changes of the following transactions: {}",
|
|
||||||
replayedTransactions);
|
ProgressiveLoanInterestScheduleModel model = savedModel.orElseGet(() -> calculateModel(loan, businessDate));
|
||||||
}
|
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
OutstandingDetails outstandingDetails = emiCalculator.getOutstandingAmountsTillDate(model, businessDate);
|
OutstandingDetails outstandingDetails = emiCalculator.getOutstandingAmountsTillDate(model, businessDate);
|
||||||
if (!loan.isInterestRecalculationEnabled()) {
|
if (!loan.isInterestRecalculationEnabled()) {
|
||||||
|
|||||||
+17
-4
@@ -20,7 +20,9 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService;
|
import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
@@ -31,6 +33,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentSchedulePro
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
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.MoneyHolder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -40,7 +43,8 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
private final LoanAccountService loanAccountService;
|
private final LoanAccountService loanAccountService;
|
||||||
private final LoanAccountTransfersService loanAccountTransfersService;
|
private final LoanAccountTransfersService loanAccountTransfersService;
|
||||||
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||||
private final LoanTransactionProcessingService loadTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
|
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reprocessTransactions(final Loan loan) {
|
public void reprocessTransactions(final Loan loan) {
|
||||||
@@ -65,7 +69,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processPostDisbursementTransactions(final Loan loan) {
|
public void processPostDisbursementTransactions(final Loan loan) {
|
||||||
loadTransactionProcessingService.processPostDisbursementTransactions(loan).ifPresent(this::handleChangedDetail);
|
loanTransactionProcessingService.processPostDisbursementTransactions(loan).ifPresent(this::handleChangedDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,7 +102,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) {
|
public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = loadTransactionProcessingService.processLatestTransaction(
|
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService.processLatestTransaction(
|
||||||
loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()));
|
||||||
@@ -110,6 +114,15 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
handleChangedDetail(changedTransactionDetail);
|
handleChangedDetail(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateModel(Loan loan) {
|
||||||
|
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan,
|
||||||
|
ThreadLocalContextUtil.getBusinessDate());
|
||||||
|
if (savedModel.isEmpty()) {
|
||||||
|
reprocessTransactions(loan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) {
|
private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) {
|
||||||
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
final LoanTransaction newTransaction = change.getNewTransaction();
|
final LoanTransaction newTransaction = change.getNewTransaction();
|
||||||
@@ -126,7 +139,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
|
|
||||||
private ChangedTransactionDetail reprocessTransactionsAndFetchChangedTransactions(final Loan loan,
|
private ChangedTransactionDetail reprocessTransactionsAndFetchChangedTransactions(final Loan loan,
|
||||||
final List<LoanTransaction> loanTransactions) {
|
final List<LoanTransaction> loanTransactions) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = loadTransactionProcessingService.reprocessLoanTransactions(
|
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService.reprocessLoanTransactions(
|
||||||
loan.getTransactionProcessingStrategyCode(), loan.getDisbursementDate(), loanTransactions, loan.getCurrency(),
|
loan.getTransactionProcessingStrategyCode(), loan.getDisbursementDate(), loanTransactions, loan.getCurrency(),
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||||
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
+17
@@ -23,17 +23,33 @@ import java.util.List;
|
|||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansResponse;
|
import org.apache.fineract.client.models.PostLoansResponse;
|
||||||
|
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
|
private static BusinessStepHelper.BusinessStepsSnapshot originalConfig;
|
||||||
|
private static final BusinessStepHelper businessStepHelper = new BusinessStepHelper();
|
||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
Long loanId;
|
Long loanId;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setup() {
|
||||||
|
originalConfig = businessStepHelper.getConfigurationSnapshot("LOAN_CLOSE_OF_BUSINESS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void teardown() {
|
||||||
|
originalConfig.restore();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnpaidPayableNotDueInterestForProgressiveLoanInCaseOfEarlyRepayment() {
|
public void testUnpaidPayableNotDueInterestForProgressiveLoanInCaseOfEarlyRepayment() {
|
||||||
|
businessStepHelper.updateSteps("LOAN_CLOSE_OF_BUSINESS", "ADD_PERIODIC_ACCRUAL_ENTRIES", "LOAN_INTEREST_RECALCULATION");
|
||||||
runAt("1 January 2024", () -> {
|
runAt("1 January 2024", () -> {
|
||||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
|
||||||
@@ -98,6 +114,7 @@ public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnpaidPayableNotDueInterestForProgressiveLoanInCaseOfEarlyRepaymentAlmostFullyPaid2ndPeriod() {
|
public void testUnpaidPayableNotDueInterestForProgressiveLoanInCaseOfEarlyRepaymentAlmostFullyPaid2ndPeriod() {
|
||||||
|
businessStepHelper.updateSteps("LOAN_CLOSE_OF_BUSINESS", "LOAN_INTEREST_RECALCULATION");
|
||||||
runAt("15 March 2025", () -> {
|
runAt("15 March 2025", () -> {
|
||||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(
|
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(
|
||||||
create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(true));
|
create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(true));
|
||||||
|
|||||||
+51
-15
@@ -18,10 +18,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests;
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.ProgressiveLoanInterestScheduleModel;
|
import org.apache.fineract.client.models.ProgressiveLoanInterestScheduleModel;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ProgressiveLoanModelIntegrationTest extends BaseLoanIntegrationTest {
|
public class ProgressiveLoanModelIntegrationTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
private final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
private final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
@@ -29,26 +35,56 @@ public class ProgressiveLoanModelIntegrationTest extends BaseLoanIntegrationTest
|
|||||||
.getResourceId();
|
.getResourceId();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testModelReturnsNullAndThenSaveThenNotNull() {
|
public void testModelReturnsSavedModelAfterDisbursement() {
|
||||||
|
AtomicLong loanIdA = new AtomicLong();
|
||||||
runAt("1 January 2024", () -> {
|
runAt("1 January 2024", () -> {
|
||||||
Long loanId = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 January 2024", 1000.0, 96.32, 6, null);
|
Long loanId = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 January 2024", 1000.0, 96.32, 6, null);
|
||||||
|
loanIdA.set(loanId);
|
||||||
|
log.info("Testing on loanId: {}", loanId);
|
||||||
loanTransactionHelper.disburseLoan(loanId, "1 January 2024", 1000.0);
|
loanTransactionHelper.disburseLoan(loanId, "1 January 2024", 1000.0);
|
||||||
|
// Model saved automatically, fetching it. It should return non null
|
||||||
|
ProgressiveLoanInterestScheduleModel model = assertNotNullAndChanged(null, loanId);
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 January 2024", 12.78);
|
||||||
|
model = assertNotNullAndChanged(model, loanId);
|
||||||
|
assertNotNullAndSame(model, loanId);
|
||||||
|
});
|
||||||
|
runAt("28 February 2024", () -> {
|
||||||
|
Long loanId = loanIdA.get();
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
BigDecimal totalUnpaidPayableNotDueInterest = loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest();
|
||||||
|
BigDecimal totalUnpaidDueInterest = loanDetails.getSummary().getTotalUnpaidPayableDueInterest();
|
||||||
|
|
||||||
// Model not saved, fetching it. It should return null
|
Assertions.assertEquals(73.78, totalUnpaidPayableNotDueInterest.doubleValue(), 0.001);
|
||||||
ProgressiveLoanInterestScheduleModel progressiveLoanInterestScheduleModelResponse1 = ok(
|
Assertions.assertEquals(79.24, totalUnpaidDueInterest.doubleValue(), 0.001);
|
||||||
fineractClient().progressiveLoanApi.fetchModel(loanId));
|
|
||||||
|
|
||||||
assertThat(progressiveLoanInterestScheduleModelResponse1).isNull();
|
|
||||||
|
|
||||||
// Forcing Model recalculation and save to database. It should return the actual model.
|
|
||||||
ProgressiveLoanInterestScheduleModel ok = ok(fineractClient().progressiveLoanApi.updateModel(loanId));
|
|
||||||
assertThat(ok).isNotNull();
|
|
||||||
|
|
||||||
// Model saved in previous step. API should return the previous model.
|
|
||||||
ProgressiveLoanInterestScheduleModel progressiveLoanInterestScheduleModelResponse2 = ok(
|
|
||||||
fineractClient().progressiveLoanApi.fetchModel(loanId));
|
|
||||||
assertThat(progressiveLoanInterestScheduleModelResponse2).isNotNull();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ProgressiveLoanInterestScheduleModel assertNotNullAndChanged(ProgressiveLoanInterestScheduleModel prev, Long loanId) {
|
||||||
|
return assertNotNullAndChanged(prev, ok(fineractClient().progressiveLoanApi.fetchModel(loanId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgressiveLoanInterestScheduleModel assertNotNullAndSame(ProgressiveLoanInterestScheduleModel prev, Long loanId) {
|
||||||
|
return assertNotNullAndSame(prev, ok(fineractClient().progressiveLoanApi.fetchModel(loanId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgressiveLoanInterestScheduleModel assertNotNullAndSame(ProgressiveLoanInterestScheduleModel prev,
|
||||||
|
ProgressiveLoanInterestScheduleModel actual) {
|
||||||
|
if (actual == null) {
|
||||||
|
Assertions.fail("Model is null");
|
||||||
|
}
|
||||||
|
Assertions.assertEquals(prev.toString(), actual.toString());
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgressiveLoanInterestScheduleModel assertNotNullAndChanged(ProgressiveLoanInterestScheduleModel prev,
|
||||||
|
ProgressiveLoanInterestScheduleModel actual) {
|
||||||
|
if (actual == null) {
|
||||||
|
Assertions.fail("Model is null");
|
||||||
|
}
|
||||||
|
if (prev != null) {
|
||||||
|
Assertions.assertNotEquals(prev.toString(), actual.toString());
|
||||||
|
}
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -20,6 +20,8 @@ package org.apache.fineract.integrationtests.common;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
import org.apache.fineract.client.models.BusinessStep;
|
import org.apache.fineract.client.models.BusinessStep;
|
||||||
import org.apache.fineract.client.models.BusinessStepRequest;
|
import org.apache.fineract.client.models.BusinessStepRequest;
|
||||||
import org.apache.fineract.client.util.Calls;
|
import org.apache.fineract.client.util.Calls;
|
||||||
@@ -28,6 +30,13 @@ public class BusinessStepHelper {
|
|||||||
|
|
||||||
public BusinessStepHelper() {}
|
public BusinessStepHelper() {}
|
||||||
|
|
||||||
|
public BusinessStepsSnapshot getConfigurationSnapshot(String jobName) {
|
||||||
|
List<BusinessStep> businessSteps = Calls
|
||||||
|
.ok(FineractClientHelper.getFineractClient().businessStepConfiguration.retrieveAllConfiguredBusinessStep(jobName))
|
||||||
|
.getBusinessSteps();
|
||||||
|
return new BusinessStepsSnapshot(jobName, businessSteps);
|
||||||
|
}
|
||||||
|
|
||||||
public void updateSteps(String jobName, String... steps) {
|
public void updateSteps(String jobName, String... steps) {
|
||||||
long order = 0;
|
long order = 0;
|
||||||
List<BusinessStep> stepList = new ArrayList<>();
|
List<BusinessStep> stepList = new ArrayList<>();
|
||||||
@@ -41,4 +50,17 @@ public class BusinessStepHelper {
|
|||||||
Calls.ok(FineractClientHelper.getFineractClient().businessStepConfiguration.updateJobBusinessStepConfig(jobName,
|
Calls.ok(FineractClientHelper.getFineractClient().businessStepConfiguration.updateJobBusinessStepConfig(jobName,
|
||||||
new BusinessStepRequest().businessSteps(stepList)));
|
new BusinessStepRequest().businessSteps(stepList)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class BusinessStepsSnapshot {
|
||||||
|
|
||||||
|
private String jobName;
|
||||||
|
private List<BusinessStep> businessSteps;
|
||||||
|
|
||||||
|
public void restore() {
|
||||||
|
Calls.ok(FineractClientHelper.getFineractClient().businessStepConfiguration.updateJobBusinessStepConfig(jobName,
|
||||||
|
new BusinessStepRequest().businessSteps(businessSteps)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user