FINERACT-2220: Use stored ProgressiveLoanInterestScheduleModel for processLatestTransaction if applicable
This commit is contained in:
+10
-16
@@ -127,34 +127,28 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan
|
||||
.fetchLoanRepaymentScheduleInstallmentByDueDate(loanTransaction.getTransactionDate());
|
||||
|
||||
boolean reprocess = loan.isForeclosure() || !isTransactionChronologicallyLatest || adjustedTransaction != null
|
||||
|| !DateUtils.isEqualBusinessDate(loanTransaction.getTransactionDate()) || currentInstallment == null
|
||||
|| !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(loanTransaction.getAmount(loan.getCurrency()));
|
||||
|
||||
// TODO FINERACT-2220 fix processLatestTransaction to save model.
|
||||
if (loan.isProgressiveSchedule() && loan.isInterestBearing()) {
|
||||
reprocess = true;
|
||||
}
|
||||
|
||||
if (isTransactionChronologicallyLatest && adjustedTransaction == null
|
||||
&& (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled()) && !loan.isForeclosure()) {
|
||||
boolean reprocessOnPostConditions = false;
|
||||
boolean processLatest = isTransactionChronologicallyLatest //
|
||||
&& adjustedTransaction == null // covers reversals
|
||||
&& !loan.isForeclosure() //
|
||||
&& loanTransactionProcessingService.canProcessLatestTransactionOnly(loan, loanTransaction, currentInstallment); //
|
||||
if (processLatest) {
|
||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||
reprocess = false;
|
||||
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
if (!loan.isProgressiveSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
|
||||
reprocess = true;
|
||||
reprocessOnPostConditions = true;
|
||||
} else {
|
||||
final LoanRepaymentScheduleInstallment nextInstallment = loan
|
||||
.fetchRepaymentScheduleInstallment(currentInstallment.getInstallmentNumber() + 1);
|
||||
if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(loan.getCurrency()).isGreaterThanZero()) {
|
||||
reprocess = true;
|
||||
reprocessOnPostConditions = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reprocess) {
|
||||
if (!processLatest || reprocessOnPostConditions) {
|
||||
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
|
||||
} else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) {
|
||||
|
||||
+3
@@ -36,6 +36,9 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleD
|
||||
|
||||
public interface LoanTransactionProcessingService {
|
||||
|
||||
boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
|
||||
LoanRepaymentScheduleInstallment currentInstallment);
|
||||
|
||||
ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction,
|
||||
TransactionCtx ctx);
|
||||
|
||||
|
||||
+5
@@ -71,6 +71,11 @@ public class EmbeddableProgressiveLoanScheduleGenerator {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValidModelForDate(Long loanId, LocalDate targetDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate) {
|
||||
return Optional.empty();
|
||||
|
||||
+4
@@ -1225,6 +1225,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
adjustOverduePrincipalForInstallment(targetDate, currentInstallment, overDuePrincipal, aggregatedOverDuePrincipal,
|
||||
ctx);
|
||||
}
|
||||
if (aggregatedOverDuePrincipal.isGreaterThanZero() && (ctx.getModel().lastOverdueBalanceChange() == null
|
||||
|| ctx.getModel().lastOverdueBalanceChange().isBefore(targetDate))) {
|
||||
ctx.getModel().lastOverdueBalanceChange(targetDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -47,8 +47,14 @@ public class ProgressiveTransactionCtx extends TransactionCtx {
|
||||
public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
|
||||
ProgressiveLoanInterestScheduleModel model) {
|
||||
this(currency, installments, charges, overpaymentHolder, changedTransactionDetail, model, Money.zero(currency));
|
||||
}
|
||||
|
||||
public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
|
||||
ProgressiveLoanInterestScheduleModel model, Money sumOfInterestRefundAmount) {
|
||||
super(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
|
||||
sumOfInterestRefundAmount = model.zero();
|
||||
this.sumOfInterestRefundAmount = sumOfInterestRefundAmount;
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -36,5 +36,7 @@ public interface InterestScheduleModelRepositoryWrapper {
|
||||
Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
|
||||
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf);
|
||||
|
||||
boolean hasValidModelForDate(Long loanId, LocalDate targetDate);
|
||||
|
||||
Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate);
|
||||
}
|
||||
|
||||
+33
-12
@@ -58,6 +58,7 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
|
||||
}
|
||||
String jsonModel = progressiveLoanInterestScheduleModelParserService.toJson(model);
|
||||
try {
|
||||
// Note: FlushMode set because otherwise it could cause a concurrent writing exception
|
||||
entityManager.setFlushMode(FlushModeType.COMMIT);
|
||||
ProgressiveLoanModel progressiveLoanModel = loanModelRepository.findOneByLoanId(loan.getId()).orElseGet(() -> {
|
||||
ProgressiveLoanModel plm = new ProgressiveLoanModel();
|
||||
@@ -88,21 +89,41 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValidModelForDate(Long loanId, LocalDate targetDate) {
|
||||
try {
|
||||
// Note: FlushMode set because an unwanted flush caused reverse replay new transactions
|
||||
entityManager.setFlushMode(FlushModeType.COMMIT);
|
||||
Optional<ProgressiveLoanModel> progressiveLoanModel = findOneByLoanId(loanId);
|
||||
LocalDate modelUpdatedTilDate = progressiveLoanModel.map(ProgressiveLoanModel::getBusinessDate).orElse(null);
|
||||
return progressiveLoanModel.isPresent() && !targetDate.isBefore(modelUpdatedTilDate);
|
||||
} finally {
|
||||
entityManager.setFlushMode(FlushModeType.AUTO);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
try {
|
||||
// Note: FlushMode set because an unwanted flush caused reverse replay new transactions
|
||||
entityManager.setFlushMode(FlushModeType.COMMIT);
|
||||
Optional<ProgressiveLoanModel> progressiveLoanModel = findOneByLoanId(loan.getId());
|
||||
Optional<ProgressiveLoanInterestScheduleModel> savedModel;
|
||||
if (progressiveLoanModel.isPresent() && !progressiveLoanModel.get().getBusinessDate().isAfter(businessDate)) {
|
||||
savedModel = extractModel(progressiveLoanModel);
|
||||
if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) {
|
||||
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();
|
||||
}
|
||||
} else {
|
||||
savedModel = Optional.empty();
|
||||
return savedModel;
|
||||
} finally {
|
||||
entityManager.setFlushMode(FlushModeType.AUTO);
|
||||
}
|
||||
return savedModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+16
-22
@@ -92,7 +92,6 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper;
|
||||
@@ -109,6 +108,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
|
||||
@@ -160,6 +160,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
private final LoanAccountService loanAccountService;
|
||||
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -889,28 +890,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
}
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory
|
||||
.determineProcessor(loan.getTransactionProcessingStrategyCode());
|
||||
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate);
|
||||
|
||||
boolean reprocess = loan.isInterestBearingAndInterestRecalculationEnabled() //
|
||||
|| loan.isForeclosure() //
|
||||
|| !isTransactionChronologicallyLatest //
|
||||
|| !DateUtils.isEqualBusinessDate(transactionDate) //
|
||||
|| currentInstallment == null //
|
||||
|| !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(refundTransaction.getAmount(loan.getCurrency())); //
|
||||
|
||||
if (!reprocess) {
|
||||
loan.getLoanTransactions().add(refundTransaction);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(refundTransaction,
|
||||
boolean processLatest = isTransactionChronologicallyLatest //
|
||||
&& !loan.isForeclosure() //
|
||||
&& loanTransactionProcessingService.canProcessLatestTransactionOnly(loan, refundTransaction, currentInstallment); //
|
||||
if (processLatest) {
|
||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), refundTransaction,
|
||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||
loan.getLoanTransactions().add(refundTransaction);
|
||||
if (interestRefundTransaction != null) {
|
||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
|
||||
interestRefundTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||
loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||
loan.addLoanTransaction(interestRefundTransaction);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(interestRefundTransaction,
|
||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||
}
|
||||
} else {
|
||||
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
@@ -924,12 +918,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
|
||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||
}
|
||||
|
||||
// Store and flush newly created transaction to generate PK
|
||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction);
|
||||
if (interestRefundTransaction != null) {
|
||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction);
|
||||
}
|
||||
// Store and flush newly created transaction to generate PK
|
||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction);
|
||||
if (interestRefundTransaction != null) {
|
||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction);
|
||||
}
|
||||
|
||||
loanLifecycleStateMachine.determineAndTransition(loan, transactionDate);
|
||||
|
||||
+49
@@ -38,9 +38,12 @@ 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.LoanTransactionType;
|
||||
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.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx;
|
||||
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;
|
||||
@@ -59,11 +62,57 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
||||
private final LoanTermVariationsMapper loanMapper;
|
||||
private final InterestScheduleModelRepositoryWrapper modelRepository;
|
||||
|
||||
@Override
|
||||
public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
|
||||
LoanRepaymentScheduleInstallment currentInstallment) {
|
||||
if (!loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
return true;
|
||||
}
|
||||
if (!DateUtils.isEqualBusinessDate(loanTransaction.getTransactionDate())) {
|
||||
return false;
|
||||
}
|
||||
if (loan.isProgressiveSchedule()) {
|
||||
return modelRepository.hasValidModelForDate(loan.getId(), loanTransaction.getTransactionDate());
|
||||
}
|
||||
return currentInstallment != null
|
||||
&& currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(loanTransaction.getAmount(loan.getCurrency()));
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail processLatestTransactionProgressiveInterestRecalculation(
|
||||
AdvancedPaymentScheduleTransactionProcessor advancedProcessor, Loan loan, LoanTransaction loanTransaction) {
|
||||
Optional<ProgressiveLoanInterestScheduleModel> savedModel = modelRepository.getSavedModel(loan,
|
||||
loanTransaction.getTransactionDate());
|
||||
|
||||
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
|
||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
||||
new ChangedTransactionDetail(), savedModel.orElse(null), getTotalRefundInterestAmount(loan));
|
||||
progressiveContext.getAlreadyProcessedTransactions().addAll(loan.retrieveListOfTransactionsForReprocessing());
|
||||
progressiveContext.setChargedOff(loan.isChargedOff());
|
||||
ChangedTransactionDetail result = advancedProcessor.processLatestTransaction(loanTransaction, progressiveContext);
|
||||
if (savedModel.isPresent() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
||||
modelRepository.writeInterestScheduleModel(loan, savedModel.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Money getTotalRefundInterestAmount(Loan loan) {
|
||||
List<LoanTransactionType> supportedInterestRefundTransactionTypes = loan.getSupportedInterestRefundTransactionTypes();
|
||||
if (supportedInterestRefundTransactionTypes != null && supportedInterestRefundTransactionTypes.isEmpty()) {
|
||||
return Money.zero(loan.getCurrency());
|
||||
}
|
||||
return loan.getLoanTransactions().stream().filter(LoanTransaction::isNotReversed).filter(LoanTransaction::isInterestRefund)
|
||||
.map(t -> t.getAmount(loan.getCurrency())).reduce(Money.zero(loan.getCurrency()), Money::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction,
|
||||
TransactionCtx ctx) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
transactionProcessingStrategyCode);
|
||||
if (loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedProcessor
|
||||
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
return processLatestTransactionProgressiveInterestRecalculation(advancedProcessor, loanTransaction.getLoan(), loanTransaction);
|
||||
}
|
||||
return loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, ctx);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user